"""
Solution: hybrid search and reranking, as in Lab 04's 02_advanced_retrieval.py.
"""


def rrf(rankings: list[list[str]], k: int = 60, depth: int | None = 20) -> list[str]:
    """Reciprocal rank fusion: each list gives 1 / (k + rank) to the ids in its top `depth`."""
    scores: dict[str, float] = {}
    for ranking in rankings:
        for rank, doc in enumerate(ranking[:depth] if depth else ranking, start=1):
            scores[doc] = scores.get(doc, 0.0) + 1 / (k + rank)
    return sorted(scores, key=lambda d: -scores[d])


def rank_of(ranking: list[str], right: str, top: int = 10) -> int | None:
    """1-based position of the right id in the top `top`, or None if it isn't there."""
    head = ranking[:top]
    return head.index(right) + 1 if right in head else None


def right_first(rankings: dict[str, list[str]], answers: dict[str, str]) -> int:
    """How many questions put their right id first."""
    return sum(1 for q, r in rankings.items() if r and r[0] == answers[q])


def rerank(pool: list[str], scores: dict[str, float]) -> list[str]:
    """Reorder a candidate pool by a model's scores, highest first; ties keep the pool's order.
    Ids outside the pool can't appear."""
    return sorted(pool, key=lambda d: -scores.get(d, 0.0))
