"""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, ...]
    # Task-block fields this template leaves out of the HTML. Their rows still
    # exist in the data and the markdown, so prose keeps citing their ids — but
    # an anchor to a row this document never renders scrolls nowhere, which
    # reads as a broken report rather than as a deliberate omission.
    omitted_fields: tuple[str, ...] = ()


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