"""Projection schemas for the BEAT-grain atom-version read model (REC-235 Phase 0).

Distinct from ``board.py`` (the selector board read model, ``get_board``) and
``scene_version.py`` (the scene-VERSION read model, ``get_scene_versions``): this is the
atom resolver surface — address ``atom:{episode}/beat/{beat_id}@tN`` → artifact +
continuity facets, resolved against the ACTIVE scene version, batch-independent. Pure
projection; never written. Mirrors the LIVE pattern — Pydantic ``_Versioned`` models in
``recoil/api/schemas/`` imported into ``workspace.readmodel`` (exactly as ``BoardModel`` /
``SceneVersionsModel`` are), NOT dataclasses defined in ``readmodel.py``.
"""

from __future__ import annotations

from typing import Any

from pydantic import Field

from recoil.api.schemas._base import _Versioned


class AtomVersionModel(_Versioned):
    """One atom-version (a Take ``@tN``) resolved to artifact + facets + canon flag.

    No-data convention (mirrors ``BoardModel``): a not-found beat / take index, or a
    non-addressable episode token, returns this model carrying a non-empty ``reason``
    (``artifact=None``, ``facets={}``). A malformed URN never reaches here — it raises
    ``ValueError`` at parse time (fail-loud).
    """

    atom_version_urn: str
    beat_urn: str
    take_index: int
    artifact: str | None = None
    facets: dict[str, Any] = Field(default_factory=dict)
    is_canon: bool = False
    reason: str | None = None


class AtomModel(_Versioned):
    """The atom (a Beat): its ordered atom-versions, canon pointer, and unpointed count.

    ``newer_unpointed_versions`` is the BEAT-grain analogue of ``BoardModel``'s
    batch-grain field — the count of takes whose ``take_index`` exceeds the canon take's
    (0 when no canon pointer is set). No-data → a non-empty ``reason`` (mirrors
    ``get_board``).
    """

    beat_urn: str
    atom_version_urns: list[str] = Field(default_factory=list)
    take_indices: list[int] = Field(default_factory=list)
    canon_take_index: int | None = None
    canon_urn: str | None = None
    newer_unpointed_versions: int = 0
    reason: str | None = None


class WardrobeMismatch(_Versioned):
    char_id: str
    phases_by_atom: dict[str, str]   # atom_version_urn -> wardrobe_phase_id that atom assigns this char


class ContinuityReport(_Versioned):
    checked: list[str]                            # atom-version URNs requested
    wardrobe_mismatches: list[WardrobeMismatch]   # empty == consistent across RESOLVED atoms
    unresolved: list[str]                         # atom-version URNs that resolved no-data (excluded from the check)
