#!/usr/bin/env python3
"""
pcv — Provenance-Carrying Value: the attributed-synthesis module's core.

A SEPARATE module from the refuse-to-be-wrong engine (general_core, S036). This one
COMBINES arbitrary, messy, even conflicting facts into a tangible result and NEVER refuses
— its honesty is structural, not truth-based: it never dissolves provenance. Every value
carries where it came from and how it was produced, so the whole result replays from its
primary references.

Design:
  PCV = a node in a derivation DAG.
    - leaf: a value + a Source (citation/audit reference + the verbatim datum).
    - op node: value = OPS[op](child values...), inputs = the child PCVs, op recorded.
  Operations are PURE functions in a NAMED REGISTRY (OPS), so the DAG is fully serializable
  and the value at every node is REPLAYABLE without storing any closure.
  Even method choices (weights, thresholds) are leaves with a "method:" source — so NOTHING
  in the result is unattributed.

Guarantees this module provides (checked by the functions below + the experiments):
  - replay(pcv): recompute every node from the leaves; every stored value must match.
  - orphans(pcv): leaves with no Source. A trustworthy synthesis result has ZERO.
  - leaf_sources(pcv): the full set of primary references — never merged away.
  - audit_trail(pcv): the ordered, human-readable derivation (how it was done).
  - label is always SYNTHESIS_DERIVED, never VERIFIED_FACT (the exact engine owns that).

Deterministic, pure stdlib, zero RNG.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import Any, Callable


# ── the value's epistemic label — the separation invariant ──────────────────────────────
SYNTHESIS_DERIVED = "derived(trace-attached)"   # this module only ever emits this
VERIFIED_FACT = "verified-fact"                 # only the refuse-to-be-wrong engine emits this


@dataclass(frozen=True)
class Source:
    sid: str        # stable id within a derivation
    ref: str        # citation: URL / audit reference / "method:<who/why>"
    datum: str      # the datum AS CITED (verbatim where it's external)

    def to_dict(self):
        return {"sid": self.sid, "ref": self.ref, "datum": self.datum}


@dataclass
class PCV:
    value: Any
    op: str                       # "leaf" or a key in OPS
    inputs: tuple = ()            # tuple[PCV]; empty for a leaf
    source: Source | None = None  # set iff op == "leaf"
    note: str = ""

    def to_dict(self):
        return {
            "value": self.value, "op": self.op, "note": self.note,
            "source": self.source.to_dict() if self.source else None,
            "inputs": [c.to_dict() for c in self.inputs],
        }


# ── the pure-operation registry (provenance-propagating) ────────────────────────────────
OPS: dict[str, Callable] = {
    "add":      lambda *xs: sum(xs),
    "sub":      lambda a, b: a - b,
    "mul":      lambda a, b: a * b,
    "div":      lambda a, b: a / b,
    "min":      lambda *xs: min(xs),
    "max":      lambda *xs: max(xs),
    "mean":     lambda *xs: sum(xs) / len(xs),
    "spread":   lambda *xs: max(xs) - min(xs),          # range width across sources
    "count":    lambda *xs: len(xs),
    "wmean":    lambda *pairs: (                          # pairs flattened: v1,w1,v2,w2,...
                    sum(pairs[i] * pairs[i + 1] for i in range(0, len(pairs), 2))
                    / sum(pairs[i + 1] for i in range(0, len(pairs), 2))),
    "concat":   lambda *xs: " | ".join(str(x) for x in xs),
    "gt":       lambda a, b: a > b,
    "select":   lambda cond, a, b: a if cond else b,     # traceable branch
}


def leaf(sid: str, value: Any, ref: str, datum: str, note: str = "") -> PCV:
    """A primary fact with its citation. External data: ref=URL. Method choice: ref='method:...'."""
    return PCV(value=value, op="leaf", inputs=(), source=Source(sid, ref, datum), note=note)


def combine(op: str, *inputs: PCV, note: str = "") -> PCV:
    """Combine sourced values by a NAMED pure op. Provenance = the inputs' DAGs + this node.
    Never dissolves sources; never refuses."""
    if op not in OPS:
        raise KeyError(f"unknown op {op!r} (not in registry — would break replay)")
    val = OPS[op](*[i.value for i in inputs])
    return PCV(value=val, op=op, inputs=tuple(inputs), source=None, note=note)


# ── the guarantees (mechanical) ─────────────────────────────────────────────────────────
def replay(pcv: PCV) -> tuple[Any, bool]:
    """Re-derive every node's value from the leaves via OPS. Returns (recomputed_root, ok)
    where ok is True iff EVERY node's stored value equals its recomputed value."""
    if pcv.op == "leaf":
        return pcv.value, True
    ok = True
    child_vals = []
    for c in pcv.inputs:
        cv, cok = replay(c)
        ok = ok and cok
        child_vals.append(cv)
    recomputed = OPS[pcv.op](*child_vals)
    return recomputed, (ok and recomputed == pcv.value)


def orphans(pcv: PCV) -> list[PCV]:
    """Leaves with NO source — i.e. unattributed values. A clean result has zero."""
    if pcv.op == "leaf":
        return [] if pcv.source is not None else [pcv]
    out = []
    for c in pcv.inputs:
        out.extend(orphans(c))
    return out


def leaf_sources(pcv: PCV) -> list[Source]:
    """Every primary reference under this result, in stable order (never merged away)."""
    if pcv.op == "leaf":
        return [pcv.source] if pcv.source else []
    seen, out = set(), []
    for c in pcv.inputs:
        for s in leaf_sources(c):
            if s.sid not in seen:
                seen.add(s.sid); out.append(s)
    return out


def audit_trail(pcv: PCV) -> list[str]:
    """Human-readable derivation: leaves first (cited), then each op with its inputs→output."""
    lines, seen = [], set()

    def walk(n: PCV):
        for c in n.inputs:
            walk(c)
        key = id(n)
        if key in seen:
            return
        seen.add(key)
        if n.op == "leaf":
            lines.append(f"[{n.source.sid}] {n.value!r}  <-  {n.source.ref}  (\"{n.source.datum}\")")
        else:
            ins = ", ".join(repr(c.value) for c in n.inputs)
            lines.append(f"{n.op}({ins}) = {n.value!r}" + (f"   # {n.note}" if n.note else ""))
    walk(pcv)
    return lines


def assessment(pcv: PCV) -> dict:
    """Package a tangible result: value + label + every source + the audit trail + checks."""
    rec, ok = replay(pcv)
    orph = orphans(pcv)
    return {
        "result": pcv.value,
        "label": SYNTHESIS_DERIVED,            # NEVER 'verified-fact'
        "replay_ok": ok,
        "replay_value": rec,
        "n_orphan_values": len(orph),
        "sources": [s.to_dict() for s in leaf_sources(pcv)],
        "audit_trail": audit_trail(pcv),
        "dag": pcv.to_dict(),
    }
