"""Scoring for the QUE-245 spike: IoU matching and accuracy checks."""

from __future__ import annotations

from typing import List, Optional, Sequence, Tuple

Box = Tuple[float, float, float, float]


def iou(a: Box, b: Box) -> float:
    ax0, ay0, ax1, ay1 = min(a[0], a[2]), min(a[1], a[3]), max(a[0], a[2]), max(a[1], a[3])
    bx0, by0, bx1, by1 = min(b[0], b[2]), min(b[1], b[3]), max(b[0], b[2]), max(b[1], b[3])
    ix0, iy0 = max(ax0, bx0), max(ay0, by0)
    ix1, iy1 = min(ax1, bx1), min(ay1, by1)
    iw, ih = max(0.0, ix1 - ix0), max(0.0, iy1 - iy0)
    inter = iw * ih
    union = (ax1 - ax0) * (ay1 - ay0) + (bx1 - bx0) * (by1 - by0) - inter
    return inter / union if union > 0 else 0.0


def match(gt: Sequence[Box], pred: Sequence[Box]) -> List[Tuple[int, Optional[int], float]]:
    """Greedy GT->pred matching by IoU. Returns (gt_idx, pred_idx|None, iou)."""
    used = set()
    out = []
    for gi, g in enumerate(gt):
        best_j, best = None, 0.0
        for pj, p in enumerate(pred):
            if pj in used:
                continue
            v = iou(g, p)
            if v > best:
                best, best_j = v, pj
        if best_j is not None:
            used.add(best_j)
        out.append((gi, best_j, best))
    return out


def top_clip_pts(gt: Box, pred: Box, page_h_pts: float = 792.0) -> float:
    """How far below the GT top the prediction starts (positive = clipped title)."""
    return (min(pred[1], pred[3]) - min(gt[1], gt[3])) * page_h_pts


def page_metrics(gt: Sequence[Box], pred: Sequence[Box]):
    m = match(gt, pred)
    matched = [(gi, pj, v) for (gi, pj, v) in m if pj is not None]
    ious = [v for _, _, v in matched]
    extra = len(pred) - len(matched)  # unmatched predictions (false positives / splits)
    missed = sum(1 for _, pj, _ in m if pj is None)
    clips = [top_clip_pts(gt[gi], pred[pj]) for gi, pj, _ in matched]
    return {
        "gt": len(gt),
        "pred": len(pred),
        "count_match": len(gt) == len(pred),
        "mean_iou": sum(ious) / len(ious) if ious else 0.0,
        "min_iou": min(ious) if ious else 0.0,
        "missed": missed,
        "extra": extra,
        "max_top_clip_pts": max(clips) if clips else 0.0,
        "matches": m,
    }
