"""RUN-056 - LESR Paper 06: actual Lebesgue shape state, target-specific ledgers and the first closure graph.

LESR Paper 06 (2026-09-20) returns the series to the Lebesgue line. The value bracket 0.8344 <= a_Leb <=
0.8440935944 is not a shape bracket; a finite witness family forces only existentially (every cover holds SOME
placement of each witness), so no pointwise support corridor exists yet. Closure is target-relative: three ledgers -
value proof, the root-6217 barrier shape, the global extremal shape - and REOPEN_FOR_SHAPE does not undo a proof.
The root-6217 symmetric state (y3 = y5 = rho5 = 0, d3 = s) is a barrier: four directions +-theta*, pi +- theta*
where D, B3 and B5 all support at 1/2, area 0.83498611 > A3+. Cell 131 is value-closed yet holds a candidate below the
tight reference. Four equal contact multipliers on the orbit balance every witness in translation and rotation
(a lemma for reflection-symmetric constant-width bodies), but g = A^T lambda is left open (Task B). A first actual
closure graph is drawn. It ships the ledgers, the four-contact template, the graph (JSON, DOT, PNG, SVG), four
compute-deferred tasks, a state record and VALIDATION. It is evidence, not a canonical document. Here the barrier is
recomputed, the template's lemma and Task B are carried out, the graph and ledgers are read against the text and
against this tree's earlier runs.

Checks
  A0 anchors      the statements below occur in the document as written
  W1 barrier      sec. 13-17, 27: x3*, theta*, x5*, the four directions, the supports there, the area, the
                  stabilizer, against the text, the ledger and the template
  W2 template     sec. 29-32, 48 (Task B): the translation balance; the rotation lemma at theta* and at random
                  angles; g = A^T lambda with the four contacts - the cos 2 theta moment - and with every contact
  W3 graph        sec. 43-45: the graph's nodes and edges, their target ids, acyclicity, its roles and edge types
                  against sec. 44, sec. 51 and Paper 04; the DOT and SVG against the JSON; the ledger against the text
  W4 prior runs   sec. 10, 22, 25: the ledger's statuses against RUN-048's coverage of the rho5 = 0 slice, RUN-049's
                  search-only share of root 6217, and RUN-052's polish of q-dagger
  W5 records      the state and the task list against the sections that carry them
  P1 package      the VALIDATION record against the bytes and the archive

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

import numpy as np
from scipy.optimize import nnls

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
from run052_lesr_paper02 import stabilizer
from run055_lesr_paper05 import kernel_system, modes, solve_dual, solve_primal

FOLDER = "main.zip!/LESR_Skew_Field_Variational/"
PKG = FOLDER + "LESR_Paper06_Package_v0.1.zip!/"
DOC = PKG + "LESR_Paper06_Actual_Lebesgue_Shape_State_and_First_Closure_Graph_v0.1.md"
LEDGER = PKG + "LESR_Paper06_Target_Specific_Ledgers_v0.1.json"
TEMPLATE = PKG + "LESR_Paper06_Four_Contact_Dual_Template_v0.1.json"
GRAPH = PKG + "LESR_Paper06_Actual_Closure_Graph_v0.1.json"
DOT = PKG + "LESR_Paper06_Actual_Closure_Graph_v0.1.dot"
SVG = PKG + "LESR_Paper06_Actual_Closure_Graph_v0.1.svg"
PNG = PKG + "LESR_Paper06_Actual_Closure_Graph_v0.1.png"
TASKS = PKG + "LESR_Paper06_COMPUTE_DEFERRED_Tasks_v0.1.json"
STATE = PKG + "LESR_State_Paper06_v0.1.json"
VALIDATION = PKG + "LESR_Paper06_VALIDATION.json"
PAPER04 = FOLDER + "LESR_Paper04_Package_v0.1.zip!/LESR_Paper04_Closure_Graph_Schema_v0.1.json"
PAPER07 = FOLDER + "LESR_Paper07_Package_v0.1.zip!/LESR_Paper07_Root6217_Barrier_Shape_Reconstruction_v0.1.md"
LOG = TREE / "data" / "gate-logs" / "run056-lesr-paper06.json"
LOG48 = TREE / "data" / "gate-logs" / "run048-uesfcm-cycles-12-14.json"
LOG49 = TREE / "data" / "gate-logs" / "run049-uesfcm-cycles-15-17.json"
MANIFEST = TREE / "data" / "SOURCE_MANIFEST.v1.json"
CACHE = TREE / "data" / "cache" / "run056-searches.json"     # data/cache is not committed
X30, S = -0.01215883209899, 0.00882          # the anchor's x3 and the far-branch radius (Cycle 06)
X5S = 0.019770946347947549                  # RUN-048's contact translation, from its 80-digit solution
A3PLUS = 0.834781191
ZPOL_AREA = 0.8347809458213                 # RUN-052's mirror-symmetric polish of q-dagger


def load_inputs():
    blobs = {n: source_bytes(n) for n in SOURCES}
    want = (DOC, LEDGER, TEMPLATE, GRAPH, DOT, SVG, PNG, TASKS, STATE, VALIDATION)
    got = {leaf.id: d for leaf, d in walk_sources(blobs, keep=lambda l: l.id in want + (PAPER04, PAPER07)) 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"), "ledger": json.loads(got[LEDGER]), "template": json.loads(got[TEMPLATE]), "graph": json.loads(got[GRAPH]),
            "dot": got[DOT].decode("utf-8"), "svg": got[SVG].decode("utf-8"), "tasks": json.loads(got[TASKS]), "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},
            "paper04_graph": json.loads(got[PAPER04]), "paper07": got[PAPER07].decode("utf-8"),
            "log48": json.loads(LOG48.read_text(encoding="utf-8")), "log49": json.loads(LOG49.read_text(encoding="utf-8"))}


# ---------------------------------------------------------------- the barrier

def barrier_state():
    """the symmetric barrier (x3*, 0, 0, x5*, 0): theta* = arccos(1 / (2 (R3 + x3*))), the four directions, every
    piece's support there against the hull's, the exact hull's area, the stabilizer of its sampled support on the
    translation gauge"""
    x3 = X30 - S
    th = math.acos(1 / (2 * (1 / math.sqrt(3) + x3)))
    dirs = [th, -th, math.pi + th, math.pi - th]
    v = [x3, 0.0, 0.0, X5S, 0.0]
    P = r44.base_pieces(v)
    hull = r44.support(P)
    sup = {}
    for name, idx in (("D", [0]), ("B3", list(range(1, 7))), ("B5", list(range(7, 17)))):
        h_body = r44.support([P[k] for k in idx])
        sup[name] = [float(h_body(t)[0]) for t in dirs]
    hh = [float(hull(t)[0]) for t in dirs]
    N = 5040
    grid = 2 * np.pi * np.arange(N) / N
    h = hull(grid)
    a1, b1 = 2 * float(np.mean(h * np.cos(grid))), 2 * float(np.mean(h * np.sin(grid)))
    return {"x3": x3, "theta": th, "dirs": dirs, "supports": sup, "hull": hh, "area": r46.area(v), "stabilizer": list(stabilizer(h - a1 * np.cos(grid) - b1 * np.sin(grid), N))}


# ---------------------------------------------------------------- the template and Task B

def own_derivative(pieces, ref, t):
    """h'_W(theta) of a placed witness about its reference point: (p - ref).u'(theta) for the piece that supports it"""
    C = np.array([p[0] for p in pieces], dtype=float)
    rho = np.array([p[1] for p in pieces], dtype=float)
    vals = C @ np.array([math.cos(t), math.sin(t)]) + rho
    for k, p in enumerate(pieces):
        if p[2] is not None and abs((t - p[2][0] + math.pi) % (2 * math.pi) - math.pi) > p[2][1]:
            vals[k] = -np.inf
    k = int(np.argmax(vals))
    return float((C[k] - ref) @ np.array([-math.sin(t), math.cos(t)]))


def template_scan(seed=56):
    """sec. 29-31: equal multipliers on the four directions - translation sum; for B3 and B5 at the barrier, the sum of
    h'_W over the orbit at theta* and at 200 random angles (the lemma). Task B (sec. 32, 48): every measure carried by
    the four directions has cos 2 theta moment cos 2 theta* times its mass, so g = A^T lambda needs
    g_cos2 = P cos 2 theta* (P = g_0, the perimeter); the least residual over lambda >= 0 on the twelve contact rows
    (three witnesses at four directions) for modes to M = 2, 4, 8; and the full-contact system of RUN-055 at the
    barrier, with the area's rate toward the anchor (d3 released)"""
    b = barrier_state()
    v = [b["x3"], 0.0, 0.0, X5S, 0.0]
    P = r44.base_pieces(v)
    refs = {"B3": np.array([v[0], v[1]]), "B5": np.array([v[3], v[4]])}
    parts = {"B3": P[1:7], "B5": P[7:17]}
    trans = float(np.hypot(sum(math.cos(t) for t in b["dirs"]), sum(math.sin(t) for t in b["dirs"])))
    rot_star = {k: sum(own_derivative(parts[k], refs[k], t) for t in b["dirs"]) for k in parts}
    rng = np.random.default_rng(seed)
    lemma = 0.0
    for _ in range(200):
        t = rng.uniform(0, 2 * math.pi)
        for k in parts:
            lemma = max(lemma, abs(sum(own_derivative(parts[k], refs[k], x) for x in (t, -t, math.pi + t, math.pi - t))))
    out = {"translation": trans, "rotation_star": rot_star, "lemma": lemma, "task_b": {}}
    for M in (2, 4, 8):
        A, B, g = kernel_system(v, M)
        F = modes(M)
        A4 = np.array([[float(f(t)) for f in F] for t in b["dirs"] for _ in range(3)])
        lam, res = nnls(A4.T, g)
        out["task_b"][str(M)] = {"residual": float(res), "g0": float(g[0]), "gcos2": float(g[3]), "pcos2": float(g[0] * math.cos(2 * b["theta"]))}
        if M == 4:
            out["full"] = {"primal": solve_primal(A, B, g), "dual": solve_dual(A, B, g)}
    e = np.array([1.0, 0, 0, 0, 0])            # x3 up: B3 back toward the anchor, d3 below s
    out["toward_anchor"] = (r46.area([v[i] + 1e-7 * e[i] for i in range(5)]) - r46.area(v)) / 1e-7
    return out


# ---------------------------------------------------------------- the cache

def _key(name, fn, extra=""):
    import run008_round05_saturation_gate as r8
    import run009_round06_witness_exchange as r9
    import run052_lesr_paper02 as r52
    import run053_lesr_paper03 as r53
    import run055_lesr_paper05 as r55
    digest = hashlib.sha256(inspect.getsource(fn).encode())
    for helper in HELPERS[name]:
        digest.update(inspect.getsource(helper).encode())
    for path in (r8.__file__, r9.__file__, r52.__file__, r53.__file__, r55.__file__):
        digest.update(open(path, "rb").read())
    digest.update(extra.encode())
    return digest.hexdigest()


HELPERS = {"barrier": (r44.support, r44.base_pieces, r46.area, r46.hull), "template": (barrier_state, own_derivative, r44.support, r44.base_pieces, r46.area)}


def searches():
    cached = json.loads(CACHE.read_text(encoding="utf-8")) if CACHE.exists() else {}
    out = {}
    for name, fn in (("barrier", barrier_state), ("template", template_scan)):
        key = _key(name, fn)
        if cached.get(name, {}).get("key") != key:
            cached[name] = {"key": key, "value": json.loads(json.dumps(fn()))}
            CACHE.parent.mkdir(parents=True, exist_ok=True)
            CACHE.write_text(json.dumps(cached, indent=1) + "\n", encoding="utf-8")
        out[name] = cached[name]["value"]
    return out


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

ANCHORS = {
    "A0": [r"0.8344\lea_{\mathrm{Leb}}\le0.8440935944.", r"\operatorname{Area}(U)\ge\Lambda(F).", r"H_F(\mathbfg^\star)\subseteqU",
           r"x_3^\star=-0.02097883209899.", r"\theta_\star=\arccos\frac1{2c_3}\approx0.454045216293184.", r"x_5^\star\approx0.01977094634794755.",
           r"h_3=h_5=h_D=\frac12.", r"0.83498611>A_3^+.", r"A<0.83498643461455335<0.8349865.", r"68\to61.", r"0.8349862484449208<0.8349865.",
           r"\sum_{\theta\in\Theta_\star}h'(\theta)=0.", r"g=A^T\lambda.", r"\text{PROOF-CLOSED}\not\Rightarrow\text{SHAPE-IRRELEVANT}."],
}
ROLES_44 = ["TARGET", "OUTER_FEASIBLE", "FORCING_SYSTEM", "MINIMIZER_BASIN", "FAR_BRANCH", "BARRIER", "DIAGNOSTIC_STRATUM", "THRESHOLD_CLOSED",
            "SHAPE_REOPEN", "SHAPE_PRUNED", "EXCHANGE_GATE", "UNBOUND_SHAPE_TARGET"]
EDGES_51 = ["FORCES", "EXCLUDES", "SUPPORTS", "PRUNES_FOR_TARGET", "REOPENS_FOR_TARGET", "UPPER_FEASIBLE", "MERGES"]
REOPEN_9 = ["REOPEN_INVALID_PROOF", "REOPEN_FOR_SHAPE", "REOPEN_FOR_EXPANDED_DOMAIN"]
DISCOVERIES = {"finite-witness forcing is existential and set-valued, not a fixed pointwise inner support bound": (5, "CriticalNon-Implication"),
               "root6217 symmetric contact is a proof barrier, not F3 minimizer or Lebesgue extremal shape": (17, "這個漂亮形狀仍然不是答案"),
               "proof closure is target-relative and must not be inherited by exact-shape ledgers": (8, "Theorem—Closure是Target-Relative"),
               "cell131 is the canonical proof-closed / shape-reopen example": (24, "Cell131是最清楚的例子"),
               "symmetric four-contact orbit gives an exact placement-equilibrium dual template": (31, "Lemma—SymmetricFour-ContactPlacementBalance"),
               "global support corridor remains unbound until outer feasible support geometry is imported": (6, "Consequence—真正ShapeCorridor尚未建立")}
CLAIMS = {
    "barrier_claim": {"x3": [True, True], "theta": [True, True, True], "x5": True, "dirs": [True, True], "supports": True, "area": 0.834986110559,
                      "above": True, "stabilizer": [1, 1]},
    "template_claim": {"translation": True, "rotation_star": [True, True], "lemma": True,
                       "task_b": {"2": [1.6, 0.124, 2.05], "4": [2.0, 0.124, 2.05], "8": [3.0, 0.124, 2.05]}, "full": [True, True], "toward_anchor": -0.0331},
    "graph_claim": {"size": [23, 24], "target_ids": True, "acyclic": True, "reached": [], "roles_outside_44": ["COMPUTE_DATA_GATE", "SHAPE_REFERENCE"],
                    "edge_types": [20, ["CERT_PRUNE", "REOPEN_FOR_SHAPE", "UPPER_FEASIBLE"]], "dot": [True, True], "svg": True, "ledger": [True] * 8},
    "prior_claim": {"rho0_status": "CLOSED_FOR_A3PLUS", "arc_certified": 0.013, "search_only": 0.4415, "remaining": 61, "polish_below_A3plus": 2.45e-07,
                    "ceiling": 0.834781191},
    "records_claim": {"unmapped": [], "discoveries": 6, "tasks": ["TASK_A_BARRIER_SHAPE_TREE_REBUILD", "TASK_B_DUAL_CONTACT_MATRIX", "TASK_C_OUTER_SHAPE_IMPORT",
                                                                  "TASK_D_F3_NEAR_ANCHOR_RECONSTRUCTION"],
                      "tasks_in_text": True, "template_status": [True, True, True], "next": [True, False]},
    "package_claim": {"digests": [True] * 7, "unhashed": ["LESR_Paper06_Actual_Closure_Graph_v0.1.png", "LESR_Paper06_Actual_Closure_Graph_v0.1.svg"],
                      "norm": 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_barrier(w):
    s, led, tpl = w["search"]["barrier"], w["ledger"]["barrier_shape"]["symmetric_contact"], w["template"]
    got = {"x3": [s["x3"] == led["x3_star"], s["x3"] == -0.02097883209899], "theta": [abs(s["theta"] - led["theta_star"]) < 1e-15, abs(s["theta"] - tpl["theta_star"]) < 1e-15,
                                                                                       abs(s["theta"] - 0.454045216293184) < 5e-16],
           "x5": abs(led["x5_star"] - X5S) < 1e-16, "dirs": [max(abs(a - b) for a, b in zip(s["dirs"], d)) < 1e-15 for d in (led["directions"], tpl["directions"])],
           "supports": max(abs(x - 0.5) for vals in list(s["supports"].values()) + [s["hull"]] for x in vals) < 1e-15,
           "area": round(s["area"], 12), "above": s["area"] > A3PLUS, "stabilizer": s["stabilizer"]}
    w["w1"] = got
    return [] if got == w["barrier_claim"] else [f"the barrier: {got}"]


def w2_template(w):
    s = w["search"]["template"]
    tb = s["task_b"]
    got = {"translation": s["translation"] < 1e-15, "rotation_star": [abs(x) < 1e-15 for x in s["rotation_star"].values()], "lemma": s["lemma"] < 1e-14,
           "task_b": {M: [sig(v["residual"]), sig(v["gcos2"], 3), sig(v["pcos2"], 3)] for M, v in tb.items()},
           "full": [s["full"]["primal"] > -1e-9, s["full"]["dual"] is not None], "toward_anchor": sig(s["toward_anchor"], 3)}
    w["w2"] = got
    return [] if got == w["template_claim"] else [f"the template: {got}"]


def w3_graph(w):
    """sec. 43-45: every edge has a target id; the graph is acyclic and reached from QSTAR; its vocabulary against the
    series'; the DOT and SVG draw the same graph; the ledger's numbers are the text's"""
    g, led = w["graph"], w["ledger"]
    ids = [n["id"] for n in g["nodes"]]
    E = [(e["source"], e["target"]) for e in g["edges"]]
    indeg = {i: 0 for i in ids}
    for a, b in E:
        indeg[b] += 1
    order, frontier = [], [i for i in ids if indeg[i] == 0]
    while frontier:
        x = frontier.pop()
        order.append(x)
        for a, b in E:
            if a == x:
                indeg[b] -= 1
                if indeg[b] == 0:
                    frontier.append(b)
    reach, stack = {"QSTAR"}, ["QSTAR"]
    while stack:
        x = stack.pop()
        for a, b in E:
            if a == x and b not in reach:
                reach.add(b)
                stack.append(b)
    types = sorted({e["type"] for e in g["edges"]})
    known = set(EDGES_51) | set(REOPEN_9) | set(w["paper04_graph"]["edge_types"])
    dot_nodes = set(re.findall(r'^\s*"([A-Z0-9_]+)"\s*\[', w["dot"], re.M))
    dot_edges = set(re.findall(r'"([A-Z0-9_]+)"\s*->\s*"([A-Z0-9_]+)"', w["dot"]))
    svg_nodes = set(re.findall(r"<title>([A-Z0-9_]+)</title>", w["svg"])) - {"LESR06"}      # the graph's own title
    bs = led["barrier_shape"]
    got = {"size": [len(ids), len(E)], "target_ids": all(e.get("target_id") for e in g["edges"]), "acyclic": len(order) == len(ids),
           "reached": sorted(set(ids) - reach), "roles_outside_44": sorted({n["role"] for n in g["nodes"]} - set(ROLES_44)),
           "edge_types": [len(types), sorted(set(types) & known)], "dot": [dot_nodes == set(ids), dot_edges == set(E)], "svg": svg_nodes == set(ids),
           "ledger": [led["global_target"]["value_public_bracket"] == [0.8344, 0.8440935944], led["value_proof"]["threshold"] == A3PLUS,
                      led["value_proof"]["root6217_cycle17_formal_remaining_cells"] == 61, bs["tight_reference"] == 0.8349865,
                      bs["tight_reference_upper"] == 0.83498643461455335, bs["shape_prunable_by_tight_reference_cells"] == [28, 36, 68, 144, 155],
                      bs["threshold_only_closed_but_shape_reopen_priority"] == [129, 131], bs["numerical_low_area_priority_unresolved_from_cycle17"] == [125, 152, 150]]}
    w["w3"] = got
    return [] if got == w["graph_claim"] else [f"the graph: {got}"]


def w4_prior(w):
    """the ledger against this tree's earlier findings: RUN-048 (Cycle 13's rho5 = 0 certificate misses the arc side),
    RUN-049 (44 % of root 6217 closed by search only), RUN-052 (the polish of q-dagger below A3+)"""
    led, l48, l49 = w["ledger"], w["log48"]["w6"], w["log49"]["w6"]
    arc = l48["coverage_arc"]
    got = {"rho0_status": led["barrier_shape"]["rho0_value_proof_status"], "arc_certified": round(arc["c12"] / sum(arc.values()), 3),
           "search_only": l49["outside_200"], "remaining": led["value_proof"]["root6217_cycle17_formal_remaining_cells"],
           "polish_below_A3plus": sig(A3PLUS - ZPOL_AREA, 3), "ceiling": led["forcing_system"]["F3_exhibited_ceiling"]}
    w["w4"] = got
    return [] if got == w["prior_claim"] else [f"the prior runs: {got}"]


def w5_records(w):
    st, tasks, text = w["state"], w["tasks"], gate.squash(w["text"])
    got = {"unmapped": [x for x in st["discoveries"] if x not in DISCOVERIES or f"#{DISCOVERIES[x][0]}.{DISCOVERIES[x][1]}" not in text],
           "discoveries": len(st["discoveries"]), "tasks": [t["id"] for t in tasks["tasks"]],
           "tasks_in_text": all(x in text for x in ("TaskA—BarrierShapeTreeRebuild", "TaskB—DualContactMatrix", "TaskC—OuterUniversalCoverImport",
                                                     "TaskD—Near-Anchor$F_3$Reconstruction")),
           "template_status": [w["template"]["status"] == "PLACEMENT_EQUILIBRIUM_TEMPLATE_ONLY", "g = A^T lambda" in w["template"]["not_proved"],
                               "PLACEMENT_EQUILIBRIUM_TEMPLATE=PROVED" in text and "SHAPE_DUAL_STATIONARITY=OPEN" in text],
           "next": [st["next"] == "Paper 07 — Root-6217 Barrier Shape Reconstruction as Controlled LESR Test",
                    "Root-6217 Barrier Shape Reconstruction as Controlled LESR Test" in w["paper07"][:300]]}
    w["w5"] = got
    return [] if got == w["records_claim"] else [f"the records: {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["ledger_sha256"] == sha(B[LEDGER]), v["dual_template_sha256"] == sha(B[TEMPLATE]),
                       v["graph_json_sha256"] == sha(B[GRAPH]), v["graph_dot_sha256"] == sha(B[DOT]), v["tasks_sha256"] == sha(B[TASKS]),
                       v["state_sha256"] == sha(B[STATE])],
           "unhashed": sorted(k.split("!/")[-1] for k in (SVG, PNG) if sha(B[k]) not in json.dumps(v)),
           "norm": v["translation_balance_norm"] == w["template"]["translation_balance"]["norm"],
           "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_barrier, "W2": w2_template, "W3": w3_graph, "W4": w4_prior, "W5": w5_records, "P1": p1_package}
DEPENDS = {"A0": {"text"}, "W1": {"search", "ledger", "template", "barrier_claim"}, "W2": {"search", "template_claim"},
           "W3": {"graph", "ledger", "dot", "svg", "paper04_graph", "graph_claim"}, "W4": {"ledger", "log48", "log49", "prior_claim"},
           "W5": {"state", "tasks", "template", "text", "paper07", "records_claim"}, "P1": {"validation", "template", "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"]
    dot_lines = base["dot"].split("\n")
    first_edge = next(i for i, l in enumerate(dot_lines) if "->" in l)
    return [
        ("K0-unmodified", "control", {}, set()),
        ("K1-crlf", "control", {"text": text.replace("\n", "\r\n"), "dot": base["dot"].replace("\n", "\r\n")}, set()),
        ("K2-validation-keys-sorted", "control", {"validation": json.loads(json.dumps(base["validation"], sort_keys=True))}, set()),
        ("M1-x3-text", "mutation", {"text": text.replace("-0.02097883209899.", "-0.02097883209898.", 1)}, {"A0"}),
        ("M2-barrier-below", "mutation", {"search": edit(s, lambda x: x["barrier"].update(area=0.8347))}, {"W1"}),
        ("M3-ledger-theta", "mutation", {"ledger": edit(base["ledger"], lambda x: x["barrier_shape"]["symmetric_contact"].update(theta_star=0.4540452163))}, {"W1"}),
        ("M4-lemma-off", "mutation", {"search": edit(s, lambda x: x["template"].update(lemma=1e-6))}, {"W2"}),
        ("M5-moment-match", "mutation", {"search": edit(s, lambda x: x["template"]["task_b"]["2"].update(gcos2=x["template"]["task_b"]["2"]["pcos2"]))}, {"W2"}),
        ("M6-edge-target", "mutation", {"graph": edit(base["graph"], lambda x: x["edges"][5].pop("target_id"))}, {"W3"}),
        ("M7-dot-edge", "mutation", {"dot": "\n".join(dot_lines[:first_edge] + dot_lines[first_edge + 1:])}, {"W3"}),
        ("M8-coverage", "mutation", {"log48": edit(base["log48"], lambda x: x["w6"]["coverage_arc"].update(c12=10000))}, {"W4"}),
        ("M9-rho0-status", "mutation", {"ledger": edit(base["ledger"], lambda x: x["barrier_shape"].update(rho0_value_proof_status="NOT_ESTABLISHED"))}, {"W4"}),
        ("M10-state-discovery", "mutation", {"state": edit(base["state"], lambda x: x["discoveries"].append("the extremal cover is the barrier"))}, {"W5"}),
        ("M11-task-dropped", "mutation", {"tasks": edit(base["tasks"], lambda x: x["tasks"].pop())}, {"W5"}),
        ("M12-validation-digest", "mutation", {"validation": {**base["validation"], "graph_dot_sha256": "0" * 64}}, {"P1"}),
        ("M13-template-norm", "mutation", {"template": edit(base["template"], lambda x: x["translation_balance"].update(norm=1e-9))}, {"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": searches()}

    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", "w5", "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-056", "subject": "LESR Paper 06: actual Lebesgue shape state, target-specific ledgers, first closure graph", "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", "w5", "p1")}, "drill": summary}
    LOG.write_text(json.dumps(log, ensure_ascii=False, indent=1, default=str) + "\n", encoding="utf-8")


if __name__ == "__main__":
    main()
