# -*- coding: utf-8 -*-
"""
ROUND 3 — (a) verify the collision curve lies over the double fold line downstairs,
(b) extend the forge to m = 4, 5 (does a SECOND specimen exist, or is m = 3 unique?),
(c) hash all breakthrough artifacts for the Paper II draft.
"""
import sympy as sp
import json, time, random, hashlib, os

t0 = time.time()
x, y, z, s = sp.symbols('x y z s')
u = 1 + x*y
random.seed(20260723)

def check(label, ok):
    print(f"[{'PASS' if ok else 'FAIL'}] {label}", flush=True)
    return ok

print("=== (a) the fold line downstairs ===", flush=True)
# descended coordinates a = xy, b = x^2 z ; verified Jac_ab(Phi) = 2*(3a+b-2)^2
# collision curve C(s) = (s, -3/(2s), 13/(2s^2)):
aC = sp.cancel((x*y).subs({x: s, y: -3/(2*s)}))
bC = sp.cancel((x**2*z).subs({x: s, z: sp.Rational(13, 2)/s**2}))
check(f"C(s) descends to the CONSTANT point (a,b) = ({aC}, {bC})",
      aC == sp.Rational(-3, 2) and bC == sp.Rational(13, 2))
check("that point lies EXACTLY on the fold line 3a + b - 2 = 0",
      sp.Rational(-9, 2) + sp.Rational(13, 2) - 2 == 0)
check("the z-axis descends to (a,b) = (0,0), which is OFF the fold line (value -2)",
      (0 + 0 - 2) == -2)
print("        => upstairs: 3:1 fold onto the invariant line;", flush=True)
print("           downstairs: Jacobian 2(3a+b-2)^2 vanishes to order 2 on the line", flush=True)
print("           the collision curve sits over. The singular fold of the plane map", flush=True)
print("           is exactly what the torus suspension renders etale upstairs.", flush=True)

print("\n=== (b) FORGE m = 4, 5 — is there a SECOND specimen? ===", flush=True)
OUT = 'post-jc-program/breakthrough/forge_m45.json'
RES = {"configs": []}

def map_degree(F, tries=2, tag=""):
    outs = []
    for tr in range(tries):
        t1, t2, t3 = [sp.Rational(random.randint(3, 60), random.randint(1, 7)) for _ in range(3)]
        zs = sp.solve(F[2] - t3, z)[0]
        G1 = sp.numer(sp.together(F[0].subs(z, zs) - t1))
        G2 = sp.numer(sp.together(F[1].subs(z, zs) - t2))
        Rr = sp.expand(sp.resultant(G1, G2, y))
        if Rr == 0:
            outs.append(("degenerate", None)); continue
        px = sp.Poly(Rr, x)
        k = min(m_[0] for m_ in px.monoms())
        expr = sp.expand(Rr / x**k)
        g = sp.gcd(expr, sp.diff(expr, x))
        sf = sp.cancel(expr / g)
        outs.append((int(sp.degree(expr, x)), int(sp.degree(sf, x))))
    print(f"        [{tag}] eliminant (all, squarefree): {outs}", flush=True)
    return outs

def forge(m, dP, dQ, dR):
    cfg = {"m": m, "caps": [dP, dQ, dR], "status": "started", "detail": []}
    a = x*y
    ps = sp.symbols(f'p0:{dP+1}'); qs = sp.symbols(f'q0:{dQ+1}'); rs = sp.symbols(f'r0:{dR+1}')
    U = list(ps) + list(qs) + list(rs)
    P = sum(c_*a**i for i, c_ in enumerate(ps))
    Q = sum(c_*a**i for i, c_ in enumerate(qs))
    R = sum(c_*a**i for i, c_ in enumerate(rs))
    v = sp.Matrix([u**m, m*x*u**(m-1), -x**m])
    A = sp.Matrix([y**2*P, y + x*y**2*Q, x*R])
    Ax, Ay, vx, vy = A.diff(x), A.diff(y), v.diff(x), v.diff(y)
    assert sp.expand(sp.Matrix.hstack(vx, vy, v).det()) == 0
    M1 = sp.expand(sp.Matrix.hstack(vx, Ay, v).det() + sp.Matrix.hstack(Ax, vy, v).det())
    M0 = sp.expand(sp.Matrix.hstack(Ax, Ay, v).det())
    eqs1 = sp.Poly(M1, x, y).coeffs()
    lin = sp.linsolve(eqs1, U)
    if lin == sp.EmptySet:
        cfg["status"] = "stage1-EMPTY (EXCLUDED: slope admits no completion at these caps)"
        print(f"   m={m} caps=({dP},{dQ},{dR}): stage1 EMPTY -> EXCLUDED", flush=True)
        RES["configs"].append(cfg); json.dump(RES, open(OUT, 'w'), indent=2); return
    sol_t = list(lin)[0]
    sub1 = dict(zip(U, sol_t))
    free1 = sorted({s_ for e in sol_t for s_ in e.free_symbols if s_ in U}, key=str)
    M0s = sp.expand(M0.subs(sub1, simultaneous=True))
    p0 = sp.Poly(M0s, x, y)
    c_expr, eqs2 = sp.Integer(0), []
    for mono, co in zip(p0.monoms(), p0.coeffs()):
        if mono == (0, 0): c_expr = co
        else: eqs2.append(co)
    branches = (sp.solve(eqs2, free1, dict=True) if eqs2 else [dict()]) if free1 \
               else ([dict()] if all(sp.expand(e) == 0 for e in eqs2) else [])
    print(f"   m={m} caps=({dP},{dQ},{dR}): stage1 free={len(free1)}, branches={len(branches)}", flush=True)
    for bi, br in enumerate(branches):
        cb = sp.expand(c_expr.subs(br))
        fullsub = {k_: sp.expand(v_.subs(br)) for k_, v_ in sub1.items()}
        rem = sorted({s_ for e in list(fullsub.values()) + [cb] for s_ in e.free_symbols if s_ in U}, key=str)
        info = {"branch": bi, "c": str(cb), "free": [str(r_) for r_ in rem]}
        if cb == 0 and not rem:
            info["verdict"] = "c = 0: not Keller"
            print(f"        branch {bi}: c=0 -> dead", flush=True)
            cfg["detail"].append(info); continue
        vals_list = [dict()] if not rem else \
            [dict(zip(rem, tuple(random.choice([1, -1, 2, sp.Rational(1, 2)]) for _ in rem))) for _ in range(2)]
        for vals in vals_list:
            cval = sp.expand(cb.subs(vals))
            if cval == 0: continue
            inst = {k_: sp.expand(v_.subs(vals)) for k_, v_ in fullsub.items()}
            Fi = [sp.expand((A[i] + z*v[i]).subs(inst, simultaneous=True)) for i in range(3)]
            dJ = sp.expand(sp.Matrix(Fi).jacobian([x, y, z]).det())
            okk = (dJ == cval) and (dJ.free_symbols == set())
            tot = max(sp.total_degree(f) for f in Fi)
            degs = map_degree(Fi, tag=f"m={m} b{bi}") if okk else None
            v_ = {"keller": bool(okk), "det": str(dJ), "total_degree": int(tot),
                  "fibers": str(degs), "coeffs": {str(k2): str(v2) for k2, v2 in inst.items()}}
            if okk and degs:
                sf = {d_[1] for d_ in degs if d_[1] is not None}
                v_["verdict"] = ("AUTOMORPHISM (deg 1)" if sf == {1}
                                 else f"*** NEW SPECIMEN: degree {sf}, total_degree {tot} ***" if min(sf) >= 2
                                 else "inconclusive")
                print(f"        branch {bi} sample: {v_['verdict']}", flush=True)
            info.setdefault("samples", []).append(v_)
        cfg["detail"].append(info)
    cfg["status"] = "done"
    RES["configs"].append(cfg); json.dump(RES, open(OUT, 'w'), indent=2)

forge(4, 2, 2, 2)
forge(4, 3, 2, 2)
forge(4, 3, 3, 3)
forge(5, 3, 3, 3)
forge(5, 4, 3, 3)

print("\n=== (c) hash the breakthrough artifact set ===", flush=True)
D = 'post-jc-program/breakthrough'
hashes = {}
for fn in sorted(os.listdir(D)):
    p = os.path.join(D, fn)
    if os.path.isfile(p) and not fn.endswith('.html'):
        hashes[fn] = hashlib.sha256(open(p, 'rb').read()).hexdigest()
        print(f"  {hashes[fn][:16]}...  {fn}", flush=True)
json.dump(hashes, open(os.path.join(D, 'breakthrough_hashes.json'), 'w'), indent=2)
print(f"\ndone ({round(time.time()-t0,2)}s)", flush=True)
