"""
Chunk-level relevance (0–100) for file RAG retrieval.
"""
from __future__ import annotations

import re
from typing import Any, List, Optional, Sequence

_MAX_COSINE_DISTANCE = 2.0


def merge_document_chunks(dense: Any, sparse: Any) -> Any:
    """Merge dense and sparse views of the same chunk_id."""
    from app.services.document_nodes import DocumentChunk

    if dense and sparse:
        meta = dict(sparse.metadata or {})
        meta.update(dense.metadata or {})
        return DocumentChunk(
            chunk_id=dense.chunk_id,
            text=(dense.text or sparse.text or "").strip() or (sparse.text or ""),
            metadata=meta,
            distance=dense.distance if dense.distance is not None else sparse.distance,
            bm25_score=sparse.bm25_score if sparse.bm25_score is not None else dense.bm25_score,
            rerank_score=dense.rerank_score if dense.rerank_score is not None else sparse.rerank_score,
        )
    return dense or sparse


def chunk_relevance_scores_0_100(
    chunks: Sequence[Any],
    query: str = "",
) -> List[float]:
    """
    Map each chunk to a 0–100 relevance score (higher is better).

    Priority: rerank_score → distance → bm25_score → fusion_rrf → fusion_rank → 0.
    """
    if len(chunks) == 0:
        return []

    rerank_vals = [
        float(c.rerank_score)
        for c in chunks
        if getattr(c, "rerank_score", None) is not None and isinstance(c.rerank_score, (int, float))
    ]
    rmin = min(rerank_vals) if rerank_vals else 0.0
    rmax = max(rerank_vals) if rerank_vals else 1.0
    rerank_has_spread = bool(rerank_vals and rmax > rmin)

    bm25_vals = [
        float(c.bm25_score)
        for c in chunks
        if getattr(c, "bm25_score", None) is not None and float(c.bm25_score) > 0
    ]
    bmin = min(bm25_vals) if bm25_vals else 0.0
    bmax = max(bm25_vals) if bm25_vals else 1.0
    bspan = (bmax - bmin) or 1e-9

    fusion_vals = [
        float(c.fusion_rrf)
        for c in chunks
        if getattr(c, "fusion_rrf", None) is not None and isinstance(c.fusion_rrf, (int, float))
    ]
    fmin = min(fusion_vals) if fusion_vals else 0.0
    fmax = max(fusion_vals) if fusion_vals else 1.0
    fspan = (fmax - fmin) or 1e-9

    fusion_ranks = [
        int(c.fusion_rank)
        for c in chunks
        if getattr(c, "fusion_rank", None) is not None and isinstance(c.fusion_rank, int)
    ]
    fusion_rank_max = max(fusion_ranks) if fusion_ranks else 0

    out: List[float] = []
    for c in chunks:
        score: Optional[float] = None

        if getattr(c, "rerank_score", None) is not None and isinstance(c.rerank_score, (int, float)):
            rv = float(c.rerank_score)
            if rerank_vals and rerank_has_spread:
                score = 100.0 * (rv - rmin) / (rmax - rmin)
            elif rerank_vals:
                score = 100.0

        if score is None and getattr(c, "distance", None) is not None and isinstance(
            c.distance, (int, float)
        ):
            d = max(0.0, min(float(c.distance), _MAX_COSINE_DISTANCE))
            score = max(0.0, 100.0 - d * (100.0 / _MAX_COSINE_DISTANCE))

        if score is None and bm25_vals and getattr(c, "bm25_score", None) is not None:
            bv = float(c.bm25_score)
            if bv > 0:
                score = 100.0 * (bv - bmin) / bspan

        if score is None and fusion_vals and getattr(c, "fusion_rrf", None) is not None:
            fv = float(c.fusion_rrf)
            score = 100.0 * (fv - fmin) / fspan

        if score is None:
            fr = getattr(c, "fusion_rank", None)
            if fr is not None and isinstance(fr, int):
                denom = float(max(fusion_rank_max, 1))
                score = 100.0 * (1.0 - float(fr) / denom)

        out.append(round(float(score if score is not None else 0.0), 3))

    q_tokens = [
        tok
        for tok in re.findall(r"\w+", (query or "").lower())
        if len(tok) >= 3
    ]
    if q_tokens:
        for i, c in enumerate(chunks):
            text = (getattr(c, "text", None) or "").lower()
            filename = str((getattr(c, "metadata", None) or {}).get("filename") or "").lower()
            haystack = f"{text} {filename}"
            if not any(tok in haystack for tok in q_tokens):
                out[i] = min(out[i], 15.0)

    return out
