"""Scan one page: submit it whole, read the figures and the page text back.

A nominated page is submitted on its own, as a one-page PDF. Billing is per
submitted page, so scanning six pages one at a time costs exactly what
submitting the six together would, and submitting per page is what makes the
response per page: one submission covering several pages returns a single
payload for all of them, and the fields that identify a scan — the job, the
model version, the credits — are document-level and would not divide.

The whole page goes in, not a crop of the chart. A chart's title is printed
above it and its note at the foot of the page, and both are only related to the
chart by where they sit, so cropping to the chart discards them and tiling
several charts onto one composite page destroys the arrangement that relates
them.

`page_scan` turns one dpt-2 response, a flat `chunks` list, into the record for
that page. Every figure becomes a chart record, every table becomes a grid with
a box on each of its cells, and every text and marginalia chunk is kept
alongside them — that is where the titles and the notes are. A response from a
dpt-3 model, the default, has a different shape and is read by
`quber.core.figures.dpt3` instead.

Nothing filters on what a chunk contains, so a figure that is not a chart still
becomes a record. A page's logo comes back as a figure of its own, and on a
branded deck that means a record per page on top of the charts. Reading the text
to decide which figures are charts would mean writing a rule against
descriptions that have no fixed shape, and the cost of a wrong rule is a
silently discarded chart.
"""

from __future__ import annotations

import asyncio
import tempfile
from pathlib import Path
from typing import Any, List, Optional

from loguru import logger

from quber.core.figures.grid import scanned_tables
from quber.core.figures.models import ChartContext, FigureRecord, PageScan, PageStatus
from quber.files.pdf import slice_page
from quber.providers.landing.client import parse_document

#: The figure scan's own default, independent of the parse default the ADE
#: client carries, and pinned to a dated version: the vendor's floating
#: aliases repoint silently, and reuse is keyed by generation rather than
#: version, so a drifting default would mix versions within one document
#: with nothing flagging it. Bump the pin deliberately, after evaluating the
#: new version. The dpt-2 line stays fully selectable by passing it as
#: `model`; each generation only ever consumes its own stored responses.
SCAN_DEFAULT_MODEL = "dpt-3-pro-20260710"

#: A one-page scan has come back in about five seconds, so it is polled more
#: often than a whole document, whose scan runs for minutes.
PAGE_POLL_SECONDS = 2.0

#: Content-level instruction for the figures on the page. It cannot change the
#: shape of what comes back — two attempts to force an HTML table returned
#: identical text — but it does tell the model what to expect on the page. A
#: superscript on a chart title routinely points at a note printed on a
#: different page, and saying so keeps the model from inventing a definition it
#: cannot see. It reaches dpt-2 models only: `parse_document` drops custom
#: prompts for a dpt-3 model, so a scan with `SCAN_DEFAULT_MODEL` sends none.
CHART_FIGURE_PROMPT = (
    "This page comes from a financial document. If the figure is a chart, read its "
    "plotted values, its axis labels and units, and its legend. A number or symbol in "
    "superscript refers to a note that is often printed on another page and so is not "
    "visible here; report the superscript as printed and do not guess what it refers to."
)

#: Chunk types kept beside the charts. `text` carries the chart titles, which the
#: scan returns as their own chunk with any superscript intact; `marginalia`
#: carries the notes printed at the foot of the page.
CONTEXT_TYPES = ("text", "marginalia")


async def scan_page(
    source: Path,
    page: int,
    model: str = SCAN_DEFAULT_MODEL,
    poll_seconds: float = PAGE_POLL_SECONDS,
) -> dict[str, Any]:
    """Submit page `page` of `source` as a one-page PDF and return the raw response."""
    with tempfile.TemporaryDirectory(prefix="quber-chart-") as tmp:
        submitted = await asyncio.to_thread(slice_page, source, page, Path(tmp) / f"p{page}.pdf")
        logger.info("Figure scan: submitting {} page {}", source.name, page)
        return await asyncio.to_thread(
            parse_document,
            submitted,
            model,
            {"figure": CHART_FIGURE_PROMPT},
            poll_seconds,
        )


def page_scan(
    response: dict[str, Any],
    page: int,
    model: str,
    picture_classes: List[str],
    response_artifact: Optional[str] = None,
    reused: bool = False,
) -> PageScan:
    """Build the record for one scanned page from its raw response.

    `page` is the source page number the submission came from. The response
    numbers its own single page from zero, so the source page is carried in
    rather than read back out of it.
    """
    meta = response.get("metadata") or {}
    job_id = meta.get("job_id")
    figures: List[FigureRecord] = []
    context: List[ChartContext] = []
    for chunk in response.get("chunks") or []:
        kind = chunk.get("type")
        grounding = chunk.get("grounding") or {}
        box = grounding.get("box")
        text = chunk.get("markdown") or ""
        if kind == "figure":
            figures.append(
                FigureRecord(page=page, text=text, box=box, chunk_id=chunk.get("id"), job_id=job_id)
            )
        elif kind in CONTEXT_TYPES:
            context.append(ChartContext(page=page, kind=kind, text=text, box=box, chunk_id=chunk.get("id")))

    tables = scanned_tables(response, page, job_id)

    credits = meta.get("credit_usage")
    if credits is None:
        credits = (meta.get("billing") or {}).get("total_credits")
    status: PageStatus = "figures" if figures else "tables" if tables else "empty"
    return PageScan(
        page=page,
        status=status,
        picture_classes=picture_classes,
        job_id=job_id,
        model=model,
        version=meta.get("version") or meta.get("model_version"),
        credits=credits,
        reused=reused,
        response_artifact=response_artifact,
        figures=figures,
        tables=tables,
        context=context,
    )
