"""RUN-050 - LESR Paper 00: from skew fields to variational shape closure.

LESR Paper 00 (2026-09-20) opens the Lebesgue Extremal Shape Reconstruction series: value closure (the area gap)
does not close the shape, so it adds shape closure - support functions h, the curvature measure mu_h = h + h'' as
the convexity condition, skew-admissible perturbations, the Euclidean quotient, group orbits and Fourier modes,
the area functional and its first and second variations, contact strata, a support corridor and a multi-gap
closure state. It states its programmatic principles (no-slack boundary, KKT measures) as such. It is one of the
42 canonical documents (RUN-001). Here every formula it uses is checked, symbolically where it is an identity and
numerically on bodies of this tree's own; the documents it builds on are looked for in the archive; its one
programmatic principle is tested at a corner; its state and package records are read against the text and bytes.

Checks
  A0 anchors      the formulas below occur in the document as written
  W1 identities   sec. 2, 8, 12-13, 16-19: placement, curvature measure, area, first and second variation,
                  translation and rotation modes, the Fourier multiplier - symbolic and on bodies
  W2 principle    sec. 29 at a corner: an inward convexity-preserving perturbation with zero first variation
  W3 sources      sec. 1-6's predecessors, looked for in the archive
  W4 state        the state record's components against the sections that carry them
  P1 package      the VALIDATION record against the bytes and the archive
  R1 registry     Paper 00's one judgment reviewed; its status phrase defined

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

import numpy as np
import sympy as sp

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

FOLDER = "main.zip!/LESR_Skew_Field_Variational/"
PKG = FOLDER + "LESR_Skew_Field_Variational_Series_Paper00_Package_v0.1.zip!/"
DOC = PKG + "LESR_Paper00_From_Skew_Fields_to_Variational_Shape_Closure_v0.1.md"
STATE = PKG + "LESR_Skew_Field_Variational_State_00_v0.1.json"
VALIDATION = PKG + "LESR_Paper00_VALIDATION.json"
LOG = TREE / "data" / "gate-logs" / "run050-lesr-paper00.json"
MANIFEST = TREE / "data" / "SOURCE_MANIFEST.v1.json"
# the predecessors sec. 0-6 name, by the words that would name them in the archive
PREDECESSORS = {"SKEW-CALC v0.2": ["SKEW-CALC", "SKEW_CALC", "歪度計算"], "Moser skew field v0.1": ["Moser"], "Subtractive Topology v3.0": ["減法拓撲", "Subtractive"],
                "GSC": ["GSC"], "RVSC": ["RVSC"], "TGSA": ["TGSA"], "Geometric Skew": ["Geometric_Skew", "Geometric Skew"]}
SEED = [-0.01215883209899, -3.708198611978e-08, 1.256661190387, 0.02299660691706, -3.896981517063e-06]


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


# ---------------------------------------------------------------- the identities

def identities():
    """each formula as an identity in sympy (trigonometric polynomials with free coefficients), and on bodies"""
    t, e, a, b, phi0 = sp.symbols("theta epsilon a b phi0", real=True)
    c = sp.symbols("c0:5", real=True)
    d = sp.symbols("d1:5", real=True)
    f = sp.symbols("f1:5", real=True)
    g = sp.symbols("g1:5", real=True)
    h = c[0] + sum(c[m] * sp.cos(m * t) + d[m - 1] * sp.sin(m * t) for m in range(1, 5))
    p = f[0] * sp.cos(2 * t) + f[1] * sp.cos(3 * t) + g[0] * sp.sin(2 * t) + g[1] * sp.sin(4 * t) + f[2] + f[3] * sp.cos(t)
    I = lambda expr: sp.integrate(sp.expand(sp.expand_trig(expr)), (t, 0, 2 * sp.pi))
    A = lambda hh: sp.Rational(1, 2) * I(hh ** 2 - sp.diff(hh, t) ** 2)
    out = {}
    out["area_forms"] = sp.simplify(A(h) - sp.Rational(1, 2) * I(h * (h + sp.diff(h, t, 2)))) == 0
    Ae = A(h + e * p)
    out["first_variation"] = sp.simplify(sp.diff(Ae, e).subs(e, 0) - I(p * (h + sp.diff(h, t, 2)))) == 0
    out["second_variation"] = sp.simplify(sp.diff(Ae, e, 2) - I(p ** 2 - sp.diff(p, t) ** 2)) == 0
    tr = a * sp.cos(t) + b * sp.sin(t)
    out["translation_kernel"] = sp.simplify(sp.diff(tr, t, 2) + tr) == 0
    out["translation_area"] = sp.simplify(A(h + tr) - A(h)) == 0
    out["rotation_generator"] = sp.simplify(sp.diff(h.subs(t, t - phi0), phi0).subs(phi0, 0) + sp.diff(h, t)) == 0
    out["rotation_area"] = sp.simplify(sp.expand(A(h.subs(t, t - sp.pi / 7)) - A(h))) == 0
    m = sp.symbols("m", integer=True, positive=True)
    out["fourier_multiplier"] = sp.simplify(sp.diff(sp.cos(m * t), t, 2) + sp.cos(m * t) - (1 - m ** 2) * sp.cos(m * t)) == 0
    out["second_variation_modes"] = [int(I(sp.cos(k * t) ** 2 - sp.diff(sp.cos(k * t), t) ** 2) / sp.pi) for k in range(1, 5)]
    # the placement formula on a random point set
    rng = np.random.default_rng(50)
    P = rng.normal(size=(12, 2))
    worst = 0.0
    for _ in range(50):
        ph, tv, th = rng.uniform(0, 2 * np.pi), rng.normal(size=2), rng.uniform(0, 2 * np.pi)
        R = np.array([[math.cos(ph), -math.sin(ph)], [math.sin(ph), math.cos(ph)]])
        u = np.array([math.cos(th), math.sin(th)])
        lhs = float(np.max((P @ R.T + tv) @ u))
        rhs = float(np.max(P @ np.array([math.cos(th - ph), math.sin(th - ph)]))) + float(tv @ u)
        worst = max(worst, abs(lhs - rhs))
    out["placement"] = float(f"{worst:.0e}") if worst else 0.0
    # the area formula on bodies: the Reuleaux triangle by the curvature measure, the seed's hull against a dense polygon
    rt = 0.5 * sum(quad_arc(k) for k in range(3))
    out["reuleaux_triangle"] = [round(rt, 11), round((math.pi - math.sqrt(3)) / 2, 11)]
    hull = r44.family_area(SEED, 5, 16384)
    thd = 2 * np.pi * np.arange(200000) / 200000
    pts = contact_polygon(SEED, thd)
    poly = 0.5 * abs(float(np.dot(pts[:, 0], np.roll(pts[:, 1], -1)) - np.dot(np.roll(pts[:, 0], -1), pts[:, 1])))
    out["seed_hull"] = [float(f"{hull:.12f}"), float(f"{hull - poly:.1e}")]
    return {k: (bool(v) if isinstance(v, (bool, np.bool_)) or type(v).__name__ == "BooleanTrue" or type(v).__name__ == "BooleanFalse" else v)
            for k, v in out.items()}


def quad_arc(k, n=4001):
    """(1/2) * integral of h over the normals carried by the Reuleaux triangle's arc about corner k, where
    h + h'' = 1 (Simpson); the corners carry no curvature"""
    R = 1 / math.sqrt(3)
    V = [(R * math.cos(2 * math.pi * j / 3), R * math.sin(2 * math.pi * j / 3)) for j in range(3)]
    mid = 2 * math.pi * k / 3 + math.pi
    th = np.linspace(mid - math.pi / 6, mid + math.pi / 6, n)
    hv = V[k][0] * np.cos(th) + V[k][1] * np.sin(th) + 1.0
    w = np.ones(n)
    w[1:-1:2], w[2:-1:2] = 4, 2
    return float(np.sum(w * hv) * (th[1] - th[0]) / 3)


def contact_polygon(v, th):
    """the hull's contact points at the directions th (the support pieces' own points), in angular order"""
    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)
    ranged = [(k, p[2]) for k, p in enumerate(P) if p[2] is not None]
    V = C @ np.stack([np.cos(th), np.sin(th)]) + rho[:, None]
    for k, (mid, half) in ranged:
        V[k, np.abs(np.mod(th - mid + np.pi, 2 * np.pi) - np.pi) > half] = -np.inf
    k = np.argmax(V, axis=0)
    return C[k] + rho[k][:, None] * np.stack([np.cos(th), np.sin(th)], axis=1)


def corner():
    """sec. 29 at a corner: the unit square, the sector [0, pi/2] of normals its corner (1/2, 1/2) carries, and
    phi = -sin 2 theta there (0 elsewhere). Admissible while the edge atoms absorb phi'' 's jumps (eps <= 1/2);
    the first variation is 0; the area is exactly quadratic in eps"""
    t, e = sp.symbols("theta epsilon", real=True)
    h0 = (sp.cos(t) + sp.sin(t)) / 2
    phi = -sp.sin(2 * t)
    q = lambda hh: sp.integrate(hh ** 2 - sp.diff(hh, t) ** 2, (t, 0, sp.pi / 2)) / 2
    change = sp.simplify(q(h0 + e * phi) - q(h0))
    first = sp.simplify(sp.integrate(phi * (h0 + sp.diff(h0, t, 2)), (t, 0, sp.pi / 2)))
    jump = sp.diff(phi, t).subs(t, 0)          # phi' jumps from 0 to this at theta = 0 (and back at pi/2)
    return {"area_change": str(sp.factor(change)), "first_variation_interior": str(first), "atom_needed": str(-jump),
            "inside_density": str(sp.simplify(phi + sp.diff(phi, t, 2)))}


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

ANCHORS = {
    "W1": [r"h_{g_{\phi,t}\gamma}(\theta)=h_\gamma(\theta-\phi)+t\cdotu_\theta.", r"\rho_h(\theta)=h(\theta)+h''(\theta).", r"h+h''\ge0.",
           r"A[h]=\frac12\int_0^{2\pi}\left(h^2-(h')^2\right)d\theta.", r"A[h]=\frac12\inth\,d\mu_h.", r"\deltaA[h;\phi]=\int\phi\,d\mu_h.",
           r"\phi''+\phi=0.", r"\delta^2A[\phi]=\int_0^{2\pi}\left(\phi^2-(\phi')^2\right)d\theta.", r"\delta_{\mathrm{rot}}h=-h'.",
           r"(1-m^2)(a_m\cosm\theta+b_m\sinm\theta).", r"\mathscrH_{\mathrm{shape}}=\mathscrH_{\mathrm{conv}}/E(2)."],
    "W2": [r"\deltaA[h;\phi]<0", r"\phi<0\quad\text{on}I", "`PROGRAMMATICPRINCIPLE`"],
    "W3": ["`EML-NT-2026-SKEW-CALC_歪度計算綱領_v0.2`", "`Moser_歪度場研究初始化_v0.1`", "`減法拓撲學v3.0`", "SKEW-CALC、GSC、RVSC、GeometricSkew、TGSA、TGSA-P、SubtractiveTopology、UESFCM-LUC"],
    "W4": [r"\boxed{\text{Support-FunctionAdmissibleVariationCompiler}.}"],
}
CLAIMS = {
    "identity_claim": {"area_forms": True, "first_variation": True, "second_variation": True, "translation_kernel": True, "translation_area": True,
                       "rotation_generator": True, "rotation_area": True, "fourier_multiplier": True, "second_variation_modes": [0, -3, -8, -15],
                       "placement": 1e-15, "reuleaux_triangle": [0.70477092301, 0.70477092301], "seed_hull": [0.834780945912, 2.6e-10]},
    "corner_claim": {"area_change": "-3*pi*epsilon**2/8", "first_variation_interior": "0", "atom_needed": "2", "inside_density": "3*sin(2*theta)"},
    "source_claim": {"found": {"SKEW-CALC v0.2": 0, "Moser skew field v0.1": 0, "Subtractive Topology v3.0": 0, "GSC": 0, "RVSC": 0, "TGSA": 0,
                               "Geometric Skew": 0}},
    "state_claim": {"bridge": [True, {}], "next_gate": True, "recovered": 7},
    "package_claim": {"digests": [True, True], "utf8": True, "delimiters": ["PASS", []], "manifest": True},
}
DEFINITIONS = {"PROGRAMMATIC PRINCIPLE": ("contextual", "sec. 29: a principle the program works from, marked as not yet proved for the "
                                                        "Lebesgue universal-cover constraints")}
REVIEWS = [("PROGRAMMATIC PRINCIPLE", "", "reading", "PASS",
            "sec. 29's No-Slack Boundary Principle, labelled not yet a theorem; it is not one. As stated, delta A < 0 also needs mu_h(I) > 0: on "
            "a corner's sector of normals an admissible inward perturbation has delta A = 0 and lowers the area only at second order (W2)")]


def a0_anchors(w):
    text = gate.squash(w["text"])
    return [f"{cid}: anchor not found: {a[:50]}" for cid, frags in ANCHORS.items() for a in frags if a not in text]


def w1_identities(w):
    got = w["search"]["identities"]
    w["w1"] = got
    return [] if got == w["identity_claim"] else [f"the identities: {got}"]


def w2_principle(w):
    got = w["search"]["corner"]
    w["w2"] = got
    return [] if got == w["corner_claim"] else [f"the corner: {got}"]


def w3_sources(w):
    """the predecessors, by the words that would name them, among the archive's leaf names"""
    found = {k: sorted({n for n in w["names"] for word in words if word in n and "LESR" not in n}) for k, words in PREDECESSORS.items()}
    got = {"found": {k: len(v) for k, v in found.items()}}
    w["w3"] = got
    return [] if got == w["source_claim"] else [f"the sources: {got}"]


def w4_state(w):
    """the state record's new-bridge components, each against the section that carries it; its next gate"""
    text = w["text"]
    heads = re.findall(r"^# (\d+)\. (.+)$", text, re.M)
    words = {"support-function shape space": "新主對象", "curvature measure mu_h = h + h''": "曲率測度", "skew-admissible perturbation": "Skew-Admissible",
             "Euclidean group quotient": "Euclidean Group", "group orbit and stabilizer": "群論層", "Fourier/representation shape modes": "Fourier",
             "area first variation": "First Variation", "area second variation": "Second Variation", "topological contact strata": "拓樸層",
             "shape corridor": "Shape Corridor", "multi-gap shape closure": "Shape Closure 不再只追一個 gap"}
    sections = {k: [n for n, title in heads if v in title] for k, v in words.items()}
    st = w["state"]
    got = {"bridge": [st["new_bridge"] == list(words), {k: v for k, v in sections.items() if len(v) != 1}],
           "next_gate": st["next_exact_gate"] in text, "recovered": len(st["recovered_old_components"])}
    w["w4"] = got
    return [] if got == w["state_claim"] else [f"the state: {got}"]


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


def r1_registry(w):
    return registry.completeness(w["registry"], "LESR", w["definitions"], w["reviews"])


CHECKS = {"A0": a0_anchors, "W1": w1_identities, "W2": w2_principle, "W3": w3_sources, "W4": w4_state, "P1": p1_package, "R1": r1_registry}
DEPENDS = {"A0": {"text"}, "W1": {"search", "identity_claim"}, "W2": {"search", "corner_claim"}, "W3": {"names", "source_claim"},
           "W4": {"text", "state", "state_claim"}, "P1": {"validation", "doc_bytes", "state_bytes", "manifest_rows", "package_claim"},
           "R1": {"registry", "definitions", "reviews"}}


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")}, set()),
        ("K2-validation-keys-sorted", "control", {"validation": json.loads(json.dumps(base["validation"], sort_keys=True))}, set()),
        ("M1-first-variation-text", "mutation", {"text": text.replace(r"\delta A[h;\phi]", r"\delta A[h;\psi]", 1)}, {"A0"}),
        ("M2-identity-broken", "mutation", {"search": edit(s, lambda x: x["identities"].update(first_variation=False))}, {"W1"}),
        ("M3-mode-value", "mutation", {"search": edit(s, lambda x: x["identities"].update(second_variation_modes=[0, -3, -8, -16]))}, {"W1"}),
        ("M4-corner-first-variation", "mutation", {"search": edit(s, lambda x: x["corner"].update(first_variation_interior="-1"))}, {"W2"}),
        ("M5-predecessor-present", "mutation", {"names": base["names"] + ["main.zip!/SKEW/EML-NT-2026-SKEW-CALC_v0.2.md"]}, {"W3"}),
        ("M6-state-bridge", "mutation", {"state": edit(base["state"], lambda x: x["new_bridge"].pop())}, {"W4"}),
        ("M7-state-bytes", "mutation", {"state_bytes": base["state_bytes"] + b" "}, {"P1"}),
        ("M8-delimiter", "mutation", {"doc_bytes": base["doc_bytes"].replace("# 7. 新主對象".encode(), "# 7. 新主對象 \\(h\\)".encode(), 1)}, {"P1"}),
        ("M9-review-dropped", "mutation", {"reviews": []}, {"R1"}),
    ]


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": {"identities": identities(), "corner": corner()}}

    def build():
        return {**copy.deepcopy(base), "registry": registry.load(), "definitions": dict(DEFINITIONS), "reviews": list(REVIEWS), **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-050", "subject": "LESR Paper 00: from skew fields to variational shape closure", "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 not results["R1"]:
        registry.record("LESR", "RUN-050", DEFINITIONS, REVIEWS)
        print(f"registry: {len(DEFINITIONS)} definitions, {len(REVIEWS)} reviews recorded for LESR")


if __name__ == "__main__":
    main()
