"""Drive split_table over the fused over-merge region with the real agents.

The Set-of-Mark locator fuses the stacked reconciliations of this shape into one
region (documented on VISA_991_Q126.pdf p11, table 10 — both period
reconciliations captured as a single markdown table). Taking that over-merge as
the given input, this:

  1. captures the fused region with region-constrained Camelot (the over-merge
     "before"),
  2. reads the real docling table count + per-table boxes for the page (the
     count / boundaries the split consumes),
  3. runs split_table with the real api LLM client — the vision count probe and
     the per-sub-region correct_structure are genuine calls — and prints the
     resulting period tables.

No mocks. Requires the api backend (ANTHROPIC_AUTH_TOKEN in .env):
  QUBER_LLM_BACKEND=api uv run python experiments/que262/run_split_demo.py
"""

from __future__ import annotations

import asyncio
import json
import os
from pathlib import Path
from typing import List, Tuple

from quber.agents.llm_client import get_llm_client
from quber.core.extractors.base import ExtractedTable
from quber.core.extractors.camelot.correspondence.geometry import norm_bbox_to_table_area
from quber.core.extractors.camelot.correspondence.recovery import camelot_targeted
from quber.core.extractors.set_of_mark.split import split_table
from quber.core.parsers.docling_parser import TunedFinancialParser

HERE = Path(__file__).parent
PAGE_W, PAGE_H = 612.0, 792.0
NormBox = Tuple[float, float, float, float]


def docling_provenance(pdf: Path, page: int) -> Tuple[int, List[NormBox]]:
    """Real docling table count + per-table boxes (normalized 0..1 top-left) on `page`."""
    doc = TunedFinancialParser().parse(pdf)
    boxes: List[NormBox] = []
    for t in doc.tables:
        prov = t.prov[0]
        if prov.page_no != page:
            continue
        size = doc.pages[prov.page_no].size
        tl = prov.bbox.to_top_left_origin(size.height)
        boxes.append((tl.l / size.width, tl.t / size.height, tl.r / size.width, tl.b / size.height))
    boxes.sort(key=lambda b: (b[1] + b[3]) / 2.0)
    return len(boxes), boxes


def capture_fused(pdf: Path, region: NormBox, page: int) -> ExtractedTable:
    """Region-constrained Camelot over the whole fused region: the over-merged table."""
    area = norm_bbox_to_table_area(region, PAGE_W, PAGE_H)
    cand = camelot_targeted(str(pdf), page, area, ordinal=1)
    return ExtractedTable(
        title="Non-GAAP Financial Measures (fused region)",
        markdown=cand.markdown if cand is not None else "",
        page=page,
        bbox=cand.bbox if cand is not None else None,
        source=str(pdf),
        flavor="stream",
        som_region=region,
        camelot_accuracy=cand.accuracy if cand is not None else 0.0,
    )


def show(label: str, t: ExtractedTable) -> None:
    region = tuple(round(v, 3) for v in t.som_region) if t.som_region else None
    print(f"\n  [{label}] som_region={region} accuracy={t.camelot_accuracy:.1f} corrected={t.llm_corrected}")
    print(f"    title    : {t.title!r}")
    print(f"    subtitle : {t.subtitle!r}")
    print(f"    footnotes: {t.footnotes}")
    for line in t.markdown.splitlines():
        print(f"    {line}")


async def run_one(pdf: Path) -> None:
    print("=" * 96)
    spec = json.loads(pdf.with_suffix(".boxes.json").read_text())
    region, page = tuple(spec["region"]), spec["page"]
    print(f"{pdf.name}  —  {spec['count']} stacked reconciliations")

    fused = capture_fused(pdf, region, page)
    rows = [r for r in fused.markdown.splitlines() if r.strip()]
    print(f"\n[before] one fused ExtractedTable over the region: {len(rows)} markdown rows")

    count, boundaries = docling_provenance(pdf, page)
    print(f"[docling] count={count}; {len(boundaries)} per-table boxes")

    llm = get_llm_client("api")
    result = await split_table(fused, count, boundaries, llm, correct_sem=asyncio.Semaphore(4))
    print(f"\n[after] split_table returned {len(result)} table(s)")
    for i, t in enumerate(result, start=1):
        show(f"table {i}/{len(result)}", t)


async def main() -> None:
    if os.environ.get("QUBER_LLM_BACKEND") != "api":
        raise SystemExit("Set QUBER_LLM_BACKEND=api (real agents; no mock evidence).")
    for name in ("synthetic_2stack.pdf", "synthetic_3stack.pdf"):
        await run_one(HERE / name)
    print("=" * 96)


if __name__ == "__main__":
    asyncio.run(main())
