"""RUN-051 - LESR Paper 01: topology envelope and stratified shape space.

LESR Paper 01 (2026-09-20) builds the series' topology layer: the shape space H_conv / E(2); contact signatures
(winners, pieces, cyclic order, transitions, incidence, stabilizer) and the strata they cut out; a frontier order;
a finite-signature theorem for finite support-piece systems; area analyticity on a regular stratum; the transition
set and its generic codimension; the maximal carrier, the mandatory structural core and the minimal carrier; a
stability lemma and a stratum trust radius; and a monotone research-state principle. It ships a schema, a compiler
scaffold, a state record and VALIDATION. It is evidence, not a canonical document. Here the two theorems are
tested on the Lebesgue kernel's own pieces, the area's analyticity inside a stratum and its kink across a
transition are measured, and the schema, state, scaffold and package are read against the text and the bytes.

Checks
  A0 anchors      the definitions and theorems below occur in the document as written
  W1 signatures   sec. 10-11: the kernel's pairwise piece differences have at most two roots per turn; the number of
                  hull segments at random placements against the bound that gives
  W2 analyticity  sec. 2, 14: the area along x5 through Cycle 12's contact switch - a polynomial inside each
                  stratum, a kink at the switch where the pieces change
  W3 records      the schema and the state against the sections that carry them
  W4 scaffold     the compiler scaffold, read and never run
  P1 package      the VALIDATION record against the bytes and the archive

Usage: python code/run051_lesr_paper01.py [--drill] [--blind CID]
"""
import argparse
import copy
import hashlib
import json
import math
import re

import numpy as np

import gate
import run044_uesfcm_cycles_01_03 as r44
import run046_uesfcm_cycles_07_09 as r46
from corpus import SOURCES, TREE, source_bytes, walk_sources

FOLDER = "main.zip!/LESR_Skew_Field_Variational/"
PKG = FOLDER + "LESR_Paper01_Package_v0.1.zip!/"
DOC = PKG + "LESR_Paper01_Topology_Envelope_and_Stratified_Shape_Space_v0.1.md"
SCHEMA = PKG + "LESR_Paper01_Topology_Envelope_Schema_v0.1.json"
SCAFFOLD = PKG + "LESR_Paper01_Contact_Stratum_Compiler_Scaffold.py"
STATE = PKG + "LESR_State_Paper01_v0.1.json"
VALIDATION = PKG + "LESR_Paper01_VALIDATION.json"
LOG = TREE / "data" / "gate-logs" / "run051-lesr-paper01.json"
MANIFEST = TREE / "data" / "SOURCE_MANIFEST.v1.json"
X30, S = -0.01215883209899, 0.00882
X5S = 0.019770946347947549


def load_inputs():
    blobs = {n: source_bytes(n) for n in SOURCES}
    want = (DOC, SCHEMA, SCAFFOLD, STATE, VALIDATION)
    got = {leaf.id: d for leaf, d in walk_sources(blobs, keep=lambda l: l.id in want) if d is not None}
    rows = {row[0]: row for row in json.loads(MANIFEST.read_text(encoding="utf-8"))["leaves"]}
    return {"text": got[DOC].decode("utf-8"), "schema": json.loads(got[SCHEMA]), "scaffold": got[SCAFFOLD].decode("utf-8"),
            "state": json.loads(got[STATE]), "validation": json.loads(got[VALIDATION]), "bytes": {k: got[k] for k in want},
            "manifest_rows": {k: rows[k] for k in want}}


# ---------------------------------------------------------------- the searches

def roots_scan(samples=300, seed=51):
    """every pair of the 17 pieces at random placements: sign changes of h_i - h_j over a turn (both pieces taken on
    their full circles; a vertex has radius 0), and the hull's segment count against 2 C(17, 2) + 2 * 8 arcs"""
    rng = np.random.default_rng(seed)
    th = 2 * np.pi * np.arange(20000) / 20000
    U = np.stack([np.cos(th), np.sin(th)])
    worst_roots, worst_segments, degenerate = 0, 0, 0
    for _ in range(samples):
        v = [rng.uniform(-0.05, 0.05), rng.uniform(-0.05, 0.05), rng.uniform(0, 2 * np.pi / 5), rng.uniform(-0.05, 0.05), rng.uniform(-0.05, 0.05)]
        P = r44.base_pieces(v)
        C = np.array([p[0] for p in P], dtype=float)
        rho = np.array([p[1] for p in P], dtype=float)
        H = C @ U + rho[:, None]
        for i in range(len(P)):
            for j in range(i + 1, len(P)):
                d = H[i] - H[j]
                if np.max(np.abs(d)) < 1e-12:
                    degenerate += 1
                    continue
                s = np.sign(d)
                worst_roots = max(worst_roots, int(np.sum(s != np.roll(s, 1))))
        worst_segments = max(worst_segments, len(r46.runs(r46.owners(v)[0])))
    return {"roots": worst_roots, "segments": worst_segments, "degenerate": degenerate, "bound": 2 * 17 * 16 // 2 + 2 * 8}


def switch_scan():
    """the area on the symmetric slice (x3*, 0, 0, x5, 0) at 21 points on each side of x5*, 1e-4 to 5e-4 away: a
    degree-6 polynomial through each side's points and its worst residual; the same fit across the switch; the
    pieces on each side"""
    x3 = X30 - S
    f = lambda x5: r46.area([x3, 0.0, 0.0, x5, 0.0])

    def fit(xs):
        ys = np.array([f(x) for x in xs])
        t = (np.asarray(xs) - np.mean(xs)) / (np.max(xs) - np.min(xs))
        c = np.polyfit(t, ys, 6)
        return float(np.max(np.abs(np.polyval(c, t) - ys)))
    below, above = np.linspace(X5S - 5e-4, X5S - 1e-4, 21), np.linspace(X5S + 1e-4, X5S + 5e-4, 21)
    across = np.linspace(X5S - 2e-4, X5S + 2e-4, 21)
    pieces = lambda x5: list(r46.canon(r46.runs([r46.PIECE[k] for k in r46.owners([x3, 0.0, 0.0, x5, 0.0])[0]])))
    return {"below": fit(below), "above": fit(above), "across": fit(across),
            "pieces_below": [pieces(x) == pieces(below[0]) for x in below].count(True), "pieces_above": [pieces(x) == pieces(above[0]) for x in above].count(True),
            "sides_differ": pieces(below[0]) != pieces(above[0])}


# ---------------------------------------------------------------- checks

ANCHORS = {
    "A0": [r"\mathscrX=\mathscrH_{\mathrm{conv}}/E(2).", r"\Gamma=(W,P,O,T,I,G).", r"\textbf{contact-combinatorialstratification}",
           r"\Gamma'\preceq\Gamma.", r"h(\theta;x)=\max_ih_i(\theta;x).", r"\partial_\thetaF_{ij}\neq0", r"A_\Gamma:X_0\to\mathbbR",
           r"\mathscrT_{\max}^{(n+1)}\subseteq\mathscrT_{\max}^{(n)}.", r"F_{\min}^{(n)}\subseteqF_{\min}^{(n+1)}.",
           r"\text{observedsignatures}=4.", r"\text{globalstrata}=4.", r"h_0+\deltah\in\mathscrS_\Gamma."],
}
CLAIMS = {
    "roots_claim": {"roots": 2, "degenerate": 0, "segments": 18, "within_bound": True},
    "switch_claim": {"inside": [1.2e-15, 1.6e-15], "across": 3.7e-06, "pieces": [21, 21, True]},
    "records_claim": {"signature": True, "statuses": [], "completed": 15, "not_claimed": 3, "next": True},
    "scaffold_facts": {"lines": 54, "defs": ["prune", "add_mandatory", "stable_stratum", "topology_gate"],
                       "classes": ["ContactSignature", "StratumState", "TopologyEnvelope"], "returns": ["MULTI_CHART", "STABLE_STRATUM", "TRANSITION_BOX"],
                       "sensitivity_constant": False, "computes_crossings": False},
    "package_claim": {"digests": [True, True, True, True], "utf8": True, "delimiters": ["PASS", []], "manifest": True},
}


def sig(x, n=2):
    return float(f"{x:.{n - 1}e}")


def a0_anchors(w):
    text = gate.squash(w["text"])
    return [f"anchor not found: {a[:60]}" for a in ANCHORS["A0"] if a not in text]


def w1_signatures(w):
    """sec. 10-11: at most two roots per pair (h_i - h_j = A cos(theta - beta) + dr), so the hull has at most
    2 C(17, 2) crossings plus the arcs' range ends"""
    r = w["search"]["roots"]
    got = {"roots": r["roots"], "degenerate": r["degenerate"], "segments": r["segments"], "within_bound": r["segments"] <= r["bound"]}
    w["w1"] = got
    return [] if got == w["roots_claim"] else [f"the signatures: {got}"]


def w2_analyticity(w):
    """sec. 2, 14: inside a stratum the area is analytic (a polynomial fits to rounding); across the switch it is not"""
    s = w["search"]["switch"]
    got = {"inside": [sig(s["below"]), sig(s["above"])], "across": sig(s["across"]), "pieces": [s["pieces_below"], s["pieces_above"], s["sides_differ"]]}
    w["w2"] = got
    return [] if got == w["switch_claim"] else [f"the analyticity: {got}"]


def w3_records(w):
    """the schema's six signature components are sec. 3's; its statuses are sec. 32's four and sec. 44's REOPEN; the
    state's 'not claimed' items are the text's own disclaimers"""
    sc, st, text = w["schema"], w["state"], gate.squash(w["text"])
    got = {"signature": sc["objects"]["contact_signature"] == ["winner_labels", "support_pieces", "cyclic_order", "transition_types", "incidence", "stabilizer_type"],
           "statuses": [s for s in sc["allowed_statuses"] if s not in text], "completed": len(st["completed"]), "not_claimed": len(st["not_claimed"]),
           "next": sc["next_paper"] in w["text"]}
    w["w3"] = got
    return [] if got == w["records_claim"] else [f"the records: {got}"]


def w4_scaffold(w):
    """the scaffold, read: what it implements and what the text asks of it"""
    code = w["scaffold"]
    got = {"lines": code.count("\n"), "defs": re.findall(r"^\s*def (\w+)", code, re.M), "classes": re.findall(r"^class (\w+)", code, re.M),
           "returns": sorted(set(re.findall(r'return "([A-Z_]+)"', code))),
           "sensitivity_constant": bool(re.search(r"perturbation_bound\s*\*|/\s*C\b|sensitivity", code)),
           "computes_crossings": "crossing" in code and "def " in code and bool(re.search(r"def \w*cross", code))}
    w["w4"] = got
    return [] if got == w["scaffold_facts"] else [f"the scaffold: {got}"]


def p1_package(w):
    v, B = w["validation"], w["bytes"]
    sha = lambda b: hashlib.sha256(b).hexdigest()
    body = B[DOC].decode("utf-8")
    got = {"digests": [v["paper_sha256"] == sha(B[DOC]), v["schema_sha256"] == sha(B[SCHEMA]), v["scaffold_sha256"] == sha(B[SCAFFOLD]),
                       v["state_sha256"] == sha(B[STATE])],
           "utf8": v["utf8"] is True, "delimiters": [v["math_delimiters"], sorted(set(re.findall(r"\\\(|\\\)|\\\[|\\\]", body)))],
           "manifest": all(w["manifest_rows"][k][2] == sha(b) for k, b in B.items())}
    w["p1"] = got
    return [] if got == w["package_claim"] else [f"the package: {got}"]


CHECKS = {"A0": a0_anchors, "W1": w1_signatures, "W2": w2_analyticity, "W3": w3_records, "W4": w4_scaffold, "P1": p1_package}
DEPENDS = {"A0": {"text"}, "W1": {"search", "roots_claim"}, "W2": {"search", "switch_claim"}, "W3": {"schema", "state", "text", "records_claim"},
           "W4": {"scaffold", "scaffold_facts"}, "P1": {"validation", "bytes", "manifest_rows", "package_claim"}}


def drill_cases(base):
    def edit(obj, fn):
        x = copy.deepcopy(obj)
        fn(x)
        return x

    text, s = base["text"], base["search"]
    return [
        ("K0-unmodified", "control", {}, set()),
        ("K1-crlf", "control", {"text": text.replace("\n", "\r\n"), "scaffold": base["scaffold"].replace("\n", "\r\n")}, set()),
        ("K2-validation-keys-sorted", "control", {"validation": json.loads(json.dumps(base["validation"], sort_keys=True))}, set()),
        ("M1-frontier-text", "mutation", {"text": text.replace("\\Gamma'\n\\preceq\n\\Gamma.", "\\Gamma'\n\\prec\n\\Gamma.", 1)}, {"A0"}),
        ("M2-three-roots", "mutation", {"search": edit(s, lambda x: x["roots"].update(roots=3))}, {"W1"}),
        ("M3-segments", "mutation", {"search": edit(s, lambda x: x["roots"].update(segments=300))}, {"W1"}),
        ("M4-no-kink", "mutation", {"search": edit(s, lambda x: x["switch"].update(across=1e-15))}, {"W2"}),
        ("M5-same-pieces", "mutation", {"search": edit(s, lambda x: x["switch"].update(sides_differ=False))}, {"W2"}),
        ("M6-schema-status", "mutation", {"schema": edit(base["schema"], lambda x: x["allowed_statuses"].append("FROZEN"))}, {"W3"}),
        ("M7-state-completed", "mutation", {"state": edit(base["state"], lambda x: x["completed"].pop())}, {"W3"}),
        ("M8-scaffold-status", "mutation", {"scaffold": base["scaffold"] + '\ndef extra():\n    return "INCONSISTENT_SIGNATURE"\n'}, {"W4"}),
        ("M9-validation-digest", "mutation", {"validation": {**base["validation"], "schema_sha256": "0" * 64}}, {"P1"}),
        ("M10-state-bytes", "mutation", {"bytes": {**base["bytes"], STATE: base["bytes"][STATE] + b" "}}, {"P1"}),
    ]


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--drill", action="store_true")
    ap.add_argument("--blind", metavar="CID")
    args = ap.parse_args()
    base = {**load_inputs(), "search": {"roots": roots_scan(), "switch": switch_scan()}}

    def build():
        return {**copy.deepcopy(base), **copy.deepcopy(CLAIMS)}

    if args.blind:
        CHECKS[args.blind] = lambda w: []
        args.drill = True
    w = build()
    results = {}
    for c, fn in CHECKS.items():
        try:
            results[c] = fn(w)
        except KeyError as e:
            results[c] = [f"claim missing: {e}"]
    for c, f in results.items():
        print(f"{c} {'PASS' if not f else 'FAIL'}", *(f"\n     {x}" for x in f[:6]))
    for key in ("w1", "w2", "w3", "w4", "p1"):
        print(key, "=", json.dumps(w.get(key), default=str, ensure_ascii=False))
    if not args.drill:
        return
    cases = drill_cases(base)
    if args.blind:
        cases = [c for c in cases if args.blind in c[3]]
    d = gate.run_drill(build, CHECKS, DEPENDS, cases)
    summary = gate.summarize(d, CHECKS)
    print(gate.report_drill(summary))
    for x in d:
        if not x["ok"]:
            print("   DRILL FAIL", x)
    if args.blind:
        n = sum(1 for x in d if args.blind in x["expected"] and args.blind not in x["tripped"])
        print(f"blind {args.blind}: drill reported it blind in {n} cases (nothing written)")
        return
    log = {"run": "RUN-051", "subject": "LESR Paper 01: topology envelope and stratified shape space", "document": DOC,
           "checks": {c: {"pass": not f, "failures": f} for c, f in results.items()},
           **{k: w.get(k) for k in ("w1", "w2", "w3", "w4", "p1")}, "drill": summary}
    LOG.write_text(json.dumps(log, ensure_ascii=False, indent=1, default=str) + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
