"""Unit tests for the Camelot acquisition helpers.

`cells_to_boxes` aligns Camelot's per-cell geometry to the text grid's shape.
These tests exercise the alignment/clamping logic directly with lightweight
stand-in cell objects; the end-to-end population from a real PDF is covered by
running `camelot_worker` on sample documents.
"""

from __future__ import annotations

from dataclasses import dataclass

from quber.core.extractors.camelot.acquire import cells_to_boxes


@dataclass
class FakeCell:
    x1: float
    y1: float
    x2: float
    y2: float


def _cell(v: float) -> FakeCell:
    # A distinct, reconstructable box per marker value.
    return FakeCell(v, v + 1, v + 2, v + 3)


def test_boxes_align_one_to_one():
    grid = [["a", "b"], ["c", "d"]]
    raw = [[_cell(0), _cell(10)], [_cell(20), _cell(30)]]
    boxes = cells_to_boxes(raw, grid)
    assert [[len(r)] for r in boxes] == [[2], [2]]
    assert boxes[0][0] == (0.0, 1.0, 2.0, 3.0)
    assert boxes[1][1] == (30.0, 31.0, 32.0, 33.0)


def test_shape_matches_grid_not_raw_when_raw_is_wider():
    # Raw has an extra trailing column; output is clamped to the grid shape.
    grid = [["a", "b"]]
    raw = [[_cell(0), _cell(10), _cell(99)]]
    boxes = cells_to_boxes(raw, grid)
    assert boxes == [[(0.0, 1.0, 2.0, 3.0), (10.0, 11.0, 12.0, 13.0)]]


def test_missing_raw_positions_fill_none():
    # Grid is wider/taller than raw; the uncovered positions become None.
    grid = [["a", "b"], ["c", "d"]]
    raw = [[_cell(0)]]  # only one cell provided
    boxes = cells_to_boxes(raw, grid)
    assert boxes == [[(0.0, 1.0, 2.0, 3.0), None], [None, None]]


def test_none_raw_cells_yields_all_none_shaped_like_grid():
    grid = [["a", "b", "c"]]
    boxes = cells_to_boxes(None, grid)
    assert boxes == [[None, None, None]]


def test_empty_grid_yields_empty_boxes():
    assert cells_to_boxes([[_cell(0)]], []) == []


# --- grounded_grid: pair Camelot cell text with geometry for ExtractedTable ---

from quber.core.extractors.base import grounded_grid  # noqa: E402


def test_grounded_grid_pairs_text_with_normalized_boxes():
    # Camelot boxes come in PDF points, bottom-left origin; grounded_grid emits
    # them normalized 0..1 with a top-left origin (the som_region frame).
    cells = [["Revenue", "$10.9"], ["Net income", "$5.9"]]
    boxes = [[(0.0, 100.0, 200.0, 300.0), (400.0, 100.0, 600.0, 300.0)], [None, (700.0, 100.0, 900.0, 300.0)]]
    page_w, page_h = 1000.0, 500.0
    grid = grounded_grid(cells, boxes, page_w, page_h)
    assert [[c.text for c in r] for r in grid] == cells
    # (400, 100, 600, 300) bottom-left pts -> x 0.4..0.6, y (500-300)/500 .. (500-100)/500
    assert grid[0][1].text == "$10.9" and grid[0][1].box == (0.4, 0.4, 0.6, 0.8)
    assert grid[1][0].box is None  # missing box -> None, text preserved
    assert grid[1][0].text == "Net income"


def test_grounded_grid_handles_absent_geometry():
    # A candidate whose cell_boxes were never captured: every cell has no box,
    # but the grid still mirrors the text shape 1:1.
    cells = [["x", "y", "z"]]
    grid = grounded_grid(cells, [], 612.0, 792.0)
    assert [len(r) for r in grid] == [len(r) for r in cells]
    assert all(cell.box is None for row in grid for cell in row)
    assert [c.text for c in grid[0]] == ["x", "y", "z"]
