"""Boxes, in the three frames the scan workflow has to speak.

A scan returns a box as `{left, top, right, bottom}`, each 0..1 with the page's
top-left as origin. The page it scanned was the real page, so that box is already
a source coordinate and needs no mapping back.

The parse states a box in PDF points, usually with the page's bottom-left as
origin. The table pipeline states a box in PDF points bottom-left too, and states
a region normalized top-left.

These convert between them so no caller writes the arithmetic twice.
"""

from __future__ import annotations

from typing import Optional, Tuple

from docling_core.types.doc.base import CoordOrigin
from docling_core.types.doc.document import DocItem

from quber.core.extractors.camelot.correspondence.geometry import camelot_bbox_to_norm
from quber.core.figures.models import Box

#: A box as (x1, y1, x2, y2), 0..1 with the page's top-left as origin.
NormBox = Tuple[float, float, float, float]

#: A box as (x1, y1, x2, y2) in PDF points with the page's bottom-left as origin.
PointBox = Tuple[float, float, float, float]


def norm_box(box: Optional[Box]) -> Optional[NormBox]:
    """A returned box (0..1, top-left origin, keyed left/top/right/bottom) as a tuple."""
    if not box:
        return None
    try:
        left, top, right, bottom = box["left"], box["top"], box["right"], box["bottom"]
    except KeyError:
        return None
    return (min(left, right), min(top, bottom), max(left, right), max(top, bottom))


def point_box(box: Optional[Box], width: float, height: float) -> Optional[PointBox]:
    """A returned box as PDF points with the page's bottom-left as origin.

    That is the frame the table pipeline measures in, so a cell box read off a
    scan lands in the same frame as one Camelot measured.
    """
    normalized = norm_box(box)
    if normalized is None:
        return None
    x1, y1, x2, y2 = normalized
    return (x1 * width, (1.0 - y2) * height, x2 * width, (1.0 - y1) * height)


def prov_box(item: DocItem, width: float, height: float) -> Optional[NormBox]:
    """An element's provenance box in the normalized top-left frame."""
    if not item.prov:
        return None
    bbox = item.prov[0].bbox
    if bbox.coord_origin == CoordOrigin.TOPLEFT:
        return (bbox.l / width, bbox.t / height, bbox.r / width, bbox.b / height)
    return camelot_bbox_to_norm((bbox.l, bbox.b, bbox.r, bbox.t), width, height)
