"""What the dpt-3 track's digestion produces: one record per page, figure and table.

A `PageDigest` is one scanned page read out of a dpt-3 response. It holds more
than the `PageScan` it projects to, which is all the shared graft and capture
read. Within the track, footnote resolution reads each figure's value tables
and writes the resolved notes back, and text grouping reads the page's text
nodes and table cell rectangles. The orchestrator writes every digest to
`<base>.figure-digest.json`.

A `DigestedFigure` keeps the response's own separation of a figure into what
kind of figure it is, what the model said about it, and the values it read as a
small table. Each value cell carries a provenance marker separating a number
printed on the page from one estimated off the drawing's geometry; digestion
writes every cell `unclassified`, and no step classifies them. A
resolved footnote attaches to the specific cell its marker's label matches, so
an annotation qualifies the value it is printed on rather than the whole chart.

A `DigestedTable` is an image table with the response's own cell grid: every
cell arrives with its row, column and rectangle, which is what lets a
disagreement check read a crop of one cell instead of the whole page.
"""

from __future__ import annotations

from typing import List, Literal, Optional

from pydantic import BaseModel, Field

from quber.core.figures.models import Box

#: Whether a figure value is printed on the page or estimated from the drawing.
#: Digestion writes every value `unclassified`, and no step classifies them.
Provenance = Literal["printed", "estimate", "unclassified"]


class PageDigest(BaseModel):
    """One scanned page, digested from a dpt-3 response."""

    page: int = Field(description="1-based source page the submission came from")
    job_id: Optional[str] = Field(default=None, description="The scan job that produced the response")
    model: Optional[str] = Field(default=None, description="The model requested for the scan")
    version: Optional[str] = Field(default=None, description="The model version the response reported")
    credits: Optional[float] = Field(default=None, description="Credits the scan was billed")
    figures: List[DigestedFigure] = Field(default_factory=list)
    tables: List[DigestedTable] = Field(default_factory=list)
    context: List[DigestedText] = Field(
        default_factory=list,
        description="The page's other nodes — text and page furniture — kept as context",
    )


class DigestedFigure(BaseModel):
    """One figure node, with the parts the response separates kept separate."""

    page: int = Field(description="1-based source page the figure is printed on")
    kind: str = Field(
        default="",
        description="What the response typed the figure as: chart, diagram, illustration",
    )
    description: str = Field(default="", description="The model's description blocks, in order")
    box: Optional[Box] = Field(default=None, description="Normalized box around the figure")
    values: List[FigureValueTable] = Field(
        default_factory=list,
        description="The value tables the model read off the figure, as returned",
    )
    footnotes: List[ResolvedNote] = Field(
        default_factory=list,
        description="The figure's footnotes once resolution has run; empty before it",
    )
    node_id: Optional[str] = Field(default=None, description="The response's own id for the node")


class DigestedTable(BaseModel):
    """One table node, as a dense grid with the response's box on every cell."""

    page: int = Field(description="1-based source page the table is printed on")
    cells: List[List[str]] = Field(description="The grid, rows of cell text")
    cell_boxes: List[List[Optional[Box]]] = Field(
        description="Normalized box per cell, shaped exactly like `cells`; absent where none was returned"
    )
    box: Optional[Box] = Field(default=None, description="Normalized box around the whole table")
    node_id: Optional[str] = Field(default=None, description="The response's own id for the node")


class DigestedText(BaseModel):
    """A text or page-furniture node, kept as returned and attached to nothing."""

    page: int = Field(description="1-based source page the text is printed on")
    kind: str = Field(description="The node type as returned: text, marginalia, logo")
    text: str = Field(description="The node's text, exactly as returned")
    box: Optional[Box] = Field(default=None, description="Normalized box around the text")
    node_id: Optional[str] = Field(default=None, description="The response's own id for the node")


class FigureValueTable(BaseModel):
    """One value table read off a figure. The rows are kept exactly as returned;
    no shape is imposed, because two charts in one response lay their values out
    differently and a fixed reading of one layout misreads the next."""

    rows: List[List[FigureValueCell]] = Field(default_factory=list)


class FigureValueCell(BaseModel):
    """One cell of a figure's value table."""

    text: str = Field(description="The cell's text, exactly as returned")
    provenance: Provenance = Field(
        default="unclassified",
        description="Printed on the page, estimated from the drawing, or not yet classified",
    )
    footnotes: List[PlacedNote] = Field(
        default_factory=list,
        description="Resolved notes whose marker sits on this cell's label",
    )


class PlacedNote(BaseModel):
    """One resolved footnote attached to the specific value its marker annotates."""

    marker: str = Field(description="The marker as printed on the label")
    text: str = Field(description="The resolved note text")


class ResolvedNote(BaseModel):
    """One footnote resolution outcome for a figure.

    `sources` names where the text came from: `agent` for the correction
    agent's read of the page image, `document` for the parse's own footnote
    text on the page, `headings` for a section reference resolved to a heading
    pointer. Two sources that agree corroborate each other; a disagreement is
    carried as a flag by the run, never silently settled.
    """

    marker: str = Field(default="", description="The marker as printed; empty for an unmarked note")
    text: str = Field(description="The note text a reader should see")
    sources: List[str] = Field(default_factory=list)
    corroborated: bool = Field(
        default=False, description="True when the agent read and the document text agree"
    )
    placed: bool = Field(default=False, description="True when the note attached to a specific value cell")
