"""Stable view-model types shared by task-specific HTML renderers."""
from __future__ import annotations

from dataclasses import dataclass
from typing import Callable


@dataclass(frozen=True)
class HtmlRunMeta:
    task_key: str
    task_type: str
    seq: str
    source_report: str
    # Wall-clock milliseconds for the run, or None when the team-state
    # carried no timestamps to measure between.
    elapsed_ms: int | None = None


@dataclass(frozen=True)
class VisualNode:
    id: str
    label: str
    # Which column the figure puts this node in, and which fill it draws it
    # with. Both are drawing instructions: `group` is often a path prefix and
    # `status` is often a constant. The text table printed them raw under the
    # headings "group" and "status", so in a single-root project every
    # component read "src · stable" — a layout key and a literal, neither a
    # fact about the project. Anything the reader should see goes in `note`.
    group: str
    status: str
    detail: str
    # Files the node stands for. The figure's text alternative is the only
    # place a reader can look them up, so they travel with the node.
    paths: tuple[str, ...] = ()
    # What this node is, in the reader's words. Empty when the figure has
    # nothing to say beyond the label.
    note: str = ""


@dataclass(frozen=True)
class VisualEdge:
    source: str
    target: str
    label: str
    kind: str


@dataclass(frozen=True)
class FigureModel:
    figure_id: str
    kind: str
    title: str
    summary: str
    nodes: tuple[VisualNode, ...]
    edges: tuple[VisualEdge, ...]
    svg: str

    @property
    def table_row_ids(self) -> tuple[str, ...]:
        return tuple(node.id for node in self.nodes)


@dataclass(frozen=True)
class HumanReportView:
    task_type: str
    template_name: str
    context: dict[str, object]
    figures: tuple[FigureModel, ...]
    # Record fields (dotted paths from the record root, list steps implicit)
    # whose rows this template anchors as `id-<row id>`. The anchor index is
    # built from this declaration, not from a guess about the template: an id
    # the record defines outside these paths lands in the evidence ledger, so
    # a citation still resolves, and an id declared here without an anchor in
    # the rendered page is what the index tests fail on.
    anchored_fields: tuple[str, ...] = ()
    # Fields whose rows repeat under every parent row (a direction's scope
    # commitments). They anchor as `id-<parent>-<row>` inside the parent's
    # card (`common.scoped_anchor_map`) and never take a page-global anchor.
    scoped_anchor_fields: tuple[str, ...] = ()


@dataclass(frozen=True)
class HtmlRoute:
    template_name: str
    view_builder: Callable[[dict], HumanReportView]
