"""document_ir.py — the surface-neutral, GEOMETRY-CARRYING document intermediate.

This is the pivot of the whole efferent projector. A DocumentIR is NOT a text
tree. It is a projection of a meaning-geometry region that carries, at every
leaf, BOTH:

  * the realized surface text  (``Block.sentences``)   — what a TEXT projector reads,
  * the source geometry        (``Block.provenance``)  — what a MUSIC / IMAGE /
                                                          VIDEO projector reads.

Because the IR holds the geometry, not just the words, the SAME
plan -> realize -> cohere pipeline drives every surface. A markdown projector
renders the sentences; a music projector reads the provenance edges (salience,
importance, polarity, relation) and maps them onto a symbolic-music surface;
an image/video projector (documented seam) would read the same geometry.

Nothing in this module invents content. Every :class:`Provenance` points at a
real engram node id and a real relation. That is the faithfulness contract made
structural: a claim with no provenance cannot exist in the IR.
"""
from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


# --------------------------------------------------------------------------- #
#  Provenance — the geometry an emitted claim traces to. FAITHFULNESS is here.
# --------------------------------------------------------------------------- #
@dataclass
class Provenance:
    """One geometry edge behind one realized claim.

    ``kind`` distinguishes a FACT (a structural edge asserted by the geometry,
    spoken as fact) from an INTERPRETATION (something attributed, spoken with
    attribution) — the facts-as-facts + interpretations-attributed discipline
    (memory 80927e26). ``polarity`` is SACRED: a negated edge stays negated.
    """
    subj_id: str | None          # source engram node id of the subject
    subject: str | None          # normalized subject surface
    relation: str                # predicate lemma (e.g. "use", "contain", "be")
    obj: str | None              # normalized object / complement surface
    polarity: str = "aff"        # "aff" | "neg"  (SACRED — never silently flipped)
    confidence: float = 0.0      # extraction confidence in [0,1]
    node_id: str | None = None   # engram node the claim was extracted from
    kind: str = "fact"           # "fact" | "interpretation"
    importance: float = 0.0      # source node importance (drives music/emphasis)
    salience: float = 0.0        # source node salience

    def trace(self) -> str:
        arrow = "-->" if self.polarity == "aff" else "--NOT-->"
        return (f"[{(self.node_id or '?')[:8]}] {self.subject!r} {arrow}"
                f"{self.relation} {self.obj!r} (conf {self.confidence:.2f})")


@dataclass
class Block:
    """A passage: one or more faithful sentences + the geometry they trace to.

    ``sentences`` and ``provenance`` are index-aligned where possible: sentence
    ``i`` was realized from ``provenance[i]``. A COHERE transition sentence with
    no new geometry carries a provenance whose ``kind == "connective"`` so the
    audit can see it introduced no new claim.
    """
    sentences: list[str] = field(default_factory=list)
    provenance: list[Provenance] = field(default_factory=list)
    role: str = "body"           # "body" | "lead" | "transition"

    def text(self) -> str:
        return " ".join(s.rstrip(". ") + "." for s in self.sentences if s.strip())


@dataclass
class Section:
    heading: str
    level: int = 2               # markdown heading level / outline depth
    blocks: list[Block] = field(default_factory=list)
    seed_ids: list[str] = field(default_factory=list)   # geometry nodes of section
    summary: str = ""            # one-line grounded gloss (for pptx bullets / TOC)

    def all_provenance(self) -> list[Provenance]:
        out: list[Provenance] = []
        for b in self.blocks:
            out.extend(b.provenance)
        return out


@dataclass
class DocumentIR:
    """The surface-neutral document. Built ONCE, projected to ANY surface."""
    title: str
    subtitle: str = ""
    sections: list[Section] = field(default_factory=list)
    seed_id: str | None = None            # the geometry region root
    format_spec: dict[str, Any] = field(default_factory=dict)  # requested shape
    meta: dict[str, Any] = field(default_factory=dict)

    # -- geometry facets (what non-text projectors consume) ----------------- #
    def all_provenance(self) -> list[Provenance]:
        out: list[Provenance] = []
        for s in self.sections:
            out.extend(s.all_provenance())
        return out

    def claim_count(self) -> int:
        return sum(1 for p in self.all_provenance() if p.kind in ("fact", "interpretation"))

    def ungrounded_count(self) -> int:
        """Claims with no traceable node — MUST be zero for a faithful doc."""
        return sum(1 for p in self.all_provenance()
                   if p.kind in ("fact", "interpretation") and not p.node_id)
