"""Greedy bbox IoU matching between reference and candidate page boxes."""
from __future__ import annotations

import json
from pathlib import Path
from typing import Dict, List, Sequence

Box = Sequence[float]


def iou(a: Box, b: Box) -> float:
    ax0, ay0, ax1, ay1 = a
    bx0, by0, bx1, by1 = b
    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
    if inter <= 0:
        return 0.0
    area_a = max(0.0, ax1 - ax0) * max(0.0, ay1 - ay0)
    area_b = max(0.0, bx1 - bx0) * max(0.0, by1 - by0)
    union = area_a + area_b - inter
    return inter / union if union > 0 else 0.0


def match_boxes(ref: List[Box], cand: List[Box], iou_threshold: float = 0.5) -> Dict[str, float]:
    used = [False] * len(cand)
    matched_ious: List[float] = []
    for r in ref:
        best_j, best_iou = -1, 0.0
        for j, c in enumerate(cand):
            if used[j]:
                continue
            cur = iou(r, c)
            if cur > best_iou:
                best_iou, best_j = cur, j
        if best_j >= 0 and best_iou >= iou_threshold:
            used[best_j] = True
            matched_ious.append(best_iou)
    matched = len(matched_ious)
    return {
        "matched": matched,
        "missed": len(ref) - matched,
        "extra": len(cand) - matched,
        "mean_matched_iou": sum(matched_ious) / matched if matched else 0.0,
    }


def bbox_iou_over_dirs(ref_dir: Path, cand_dir: Path, iou_threshold: float = 0.5) -> Dict:
    per_file: Dict[str, Dict[str, float]] = {}
    for ref_path in sorted(ref_dir.glob("*.json")):
        ref_boxes = json.loads(ref_path.read_text(encoding="utf-8")).get("boxes", [])
        cand_path = cand_dir / ref_path.name
        cand_boxes = (
            json.loads(cand_path.read_text(encoding="utf-8")).get("boxes", [])
            if cand_path.exists()
            else []
        )
        per_file[ref_path.name] = match_boxes(ref_boxes, cand_boxes, iou_threshold)
    n = len(per_file) or 1
    return {
        "mean_bbox_iou": sum(v["mean_matched_iou"] for v in per_file.values()) / n,
        "mean_missed_lines": sum(v["missed"] for v in per_file.values()) / n,
        "mean_extra_lines": sum(v["extra"] for v in per_file.values()) / n,
        "per_file": per_file,
    }
