#!/usr/bin/env python3
"""
verify_P148.py -- Addendum 148: provenance as a path in S3.

This verifier checks the log-map/tangent encoding claims and flags schema and
disambiguation gaps in the proposed provenance amendment.
"""

from __future__ import annotations

import math
import sys
from pathlib import Path

PASS = FAIL = 0
_N = 0


def check(desc, cond):
    global PASS, FAIL, _N
    _N += 1
    ok = bool(cond)
    PASS += ok
    FAIL += not ok
    print(f"  [{'PASS' if ok else 'FAIL'}] {_N:>2}. {desc}")
    return ok


class Verifier:
    """Same check semantics as verify_common.Verifier; modern output style."""

    def __init__(self, name):
        print(name)

    def check(self, label, computed, claimed, *, rel=1e-3, abs_tol=None, detail=""):
        if abs_tol is not None:
            ok = abs(computed - claimed) <= abs_tol
            err = abs(computed - claimed)
            err_detail = f"abs err={err:.6g}, tol={abs_tol:.6g}"
        else:
            if claimed == 0:
                ok = abs(computed) <= (rel or 1e-12)
                err_detail = f"abs value={abs(computed):.6g}, tol={rel:.6g}"
            else:
                err = (computed - claimed) / abs(claimed)
                ok = abs(err) <= (rel or 0)
                err_detail = f"rel err={100 * err:+.6g}%, tol={100 * (rel or 0):.6g}%"
        return self.record(label, ok, computed, claimed, err_detail + (f"; {detail}" if detail else ""))

    def record(self, label, ok, computed="", claimed="", detail=""):
        check(label + (f" -- {detail}" if detail else ""), ok)
        if computed != "" or claimed != "":
            print(f"        computed: {computed}")
            print(f"        claimed : {claimed}")
        return ok

    def summary(self):
        print(f"\n{'='*60}\nRESULT: {PASS} PASS / {FAIL} FAIL")
        return 1 if FAIL else 0


v = Verifier("P148 -- Provenance Path")

ROOT = Path(__file__).resolve().parents[1]
TEX = (ROOT / "148_Addendum_ProvenancePath.tex").read_text()


def log_norm(q: tuple[float, float, float, float], p: tuple[float, float, float, float]) -> float:
    dot = sum(a * b for a, b in zip(q, p))
    return math.acos(max(-1.0, min(1.0, dot)))


q0 = (1.0, 0.0, 0.0, 0.0)
q1 = (0.0, 1.0, 0.0, 0.0)
q4 = (1.0 / math.sqrt(2.0), 0.0, 0.0, 1.0 / math.sqrt(2.0))

v.record(
    "proposed tangent lives in a 3D tangent space",
    "\\TqS \\cong \\mathbb{R}^3" in TEX and "v \\in \\mathbb{R}^3" in TEX,
    computed="T_q S3 and v in R3 stated",
    claimed="provenance tangent is three-dimensional",
)
v.check("orthogonal quaternion log distance", log_norm(q0, q1), math.pi / 2.0, rel=1e-14)
v.check("halfway quaternion log distance", log_norm(q0, q4), math.pi / 4.0, rel=1e-14)
v.check("Oscillate k=4 step length", 2.0 * math.pi / 4.0, math.pi / 2.0, rel=1e-14)
v.record(
    "Plateau is identified with zero tangent",
    "v = 0 \\implies" in TEX and "\\textsc{Plateau}" in TEX,
    computed="v=0 -> Plateau rule present",
    claimed="Plateau unique by zero arc length",
)
v.record(
    "Fork/Weld ambiguity is acknowledged",
    "\\textsc{Fork} or \\textsc{Weld}" in TEX and "h_\\mathrm{prev}" in TEX,
    computed="needs h_prev / heat sign for Fork-Weld disambiguation",
    claimed="v alone gives partial provenance",
)
v.record(
    "provenance tangent is deterministic from endpoints",
    "deterministic function of $(q_\\mathrm{prev},\n    q_\\mathrm{curr})$" in TEX,
    computed="deterministic endpoint function stated",
    claimed="not a learned parameter",
)
v.record(
    "CanonNode tangent is rejected",
    "should \\emph{not} gain a tangent field" in TEX,
    computed="node stays structural, provenance belongs on transitions/edges",
    claimed="CanonNode unchanged",
)

v.record(
    "log-map formula covers the Plateau case as written",
    False,
    computed="the displayed formula excludes p=-q but is also 0/0 at p=q; Plateau uses q_prev=q_curr and needs an explicit v=0 branch",
    claimed="log_q(p) embeds p != -q into T_q S3",
    detail="Expected formula-domain fail.",
)
v.record(
    "homotopy class of minimal geodesic arcs distinguishes operators",
    False,
    computed="for non-antipodal endpoints the minimal geodesic in S3 is unique, so its homotopy class is not enough to encode Fork/Weld/Oscillate/Perturb without extra heat/length/operator assumptions",
    claimed="path type is homotopy class plus heat signature",
    detail="Expected path-type fail.",
)
v.record(
    "v alone gives unambiguous provenance for all five operators",
    False,
    computed="the paper itself says v gives only partial provenance: Fork/Weld need h_prev, and Oscillate/Perturb can overlap if zeta R = 2pi/k",
    claimed="tangent-vector encoding gives unambiguous provenance",
    detail="Expected disambiguation fail.",
)
v.record(
    "h_prev adds no historical/provenance record",
    False,
    computed="h_prev is explicitly a prior-state scalar needed to recover Delta h; whether this is allowed without a second time axis is left to a Wheel invariants audit",
    claimed="full disambiguation needs only one scalar and no second clock",
    detail="Expected architecture-status fail.",
)
v.record(
    "amended schema adds two scalar fields",
    False,
    computed="v is in R3, so the amendment adds three tangent coordinates plus h_prev, not two scalar fields",
    claimed="WheelState schema gains two frozen scalar fields (v and h_prev)",
    detail="Expected schema-count fail.",
)
v.record(
    "existing Wheel tests pass unchanged is verified",
    False,
    computed="P148 asserts the 20+ tests do not reference v or h_prev, but no test list or run output is included in the TeX",
    claimed="existing tests pass unchanged",
    detail="Expected reproducibility fail.",
)
v.record(
    "full Type-2 self-observation is closed",
    False,
    computed="open items remain for Fork/Weld h_prev interpretation, Oscillate/Perturb overlap, typed Mycelium edges, and P18-T2 uniqueness",
    claimed="Full Type-2 self-observation is achievable without a second time axis",
    detail="Expected status fail.",
)
v.record(
    "zeta R is guaranteed away from all 2pi/k",
    False,
    computed="the paper lists this as an open parameter-discipline constraint rather than proving it from fixed constants",
    claimed="Oscillate and Perturb are disjoint by magnitude",
    detail="Expected overlap fail.",
)

sys.exit(v.summary())
