# -*- coding: utf-8 -*-
"""
THE DESIGN FORGE — full m-sweep, staged exact solving, degree certification.

Ansatz class (from the verified anatomy):
    slope   v_m = (u^m, m x u^{m-1}, -x^m),  u = 1+xy   (integrability lemma: OK all m)
    map     F = A + z*v_m,
    A = ( y^2 P(a),  y + x y^2 Q(a),  x R(a) ),   a = xy,  P,Q,R unknown-coefficient polys.

Keller condition det J = c != 0 splits by z-degree:
    z^2 : det[v_x|v_y|v] == 0                      (lemma, verified symbolically)
    z^1 : det[v_x|A_y|v] + det[A_x|v_y|v] == 0     (LINEAR in unknowns -> linsolve)
    z^0 : det[A_x|A_y|v] == c                      (quadratic -> solve)

Certification of solutions: build F, verify det J == const != 0 EXACTLY, then compute
the generic fiber cardinality (map degree) by elimination at random rational targets.
degree == 1  => automorphism (Keller: birational Keller maps invert)  -> exclusion
degree >= 2  => NON-INJECTIVE KELLER MAP: a specimen.

Also: re-verify torus equivariance + polynomial descent, and compute the generic
fiber degree of Alpoge's map itself (a new invariant of the original object).
Incremental JSON writes after every stage (crash/timeout safe).
"""
import sympy as sp
import json, time, random, sys

t0 = time.time()
x, y, z, T = sp.symbols('x y z T')
u = 1 + x*y
random.seed(20260722)
OUT = 'post-jc-program/breakthrough/forge_results.json'
RES = {"torus": {}, "alpoge_degree": {}, "configs": []}

def save():
    json.dump(RES, open(OUT, 'w', encoding='utf-8'), indent=2, default=str)

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

F_alp = [(1+x*y)**3*z + y**2*(1+x*y)*(4+3*x*y),
         y + 3*x*(1+x*y)**2*z + 3*x*y**2*(4+3*x*y),
         2*x - 3*x**2*y - x**3*z]

# ---------- 0. torus equivariance + descent (re-verify, cheap) ----------------
sub = {x: T*x, y: y/T, z: z/T**2}
ok_t = (sp.simplify(F_alp[0].subs(sub, simultaneous=True) - F_alp[0]/T**2) == 0 and
        sp.simplify(F_alp[1].subs(sub, simultaneous=True) - F_alp[1]/T) == 0 and
        sp.simplify(F_alp[2].subs(sub, simultaneous=True) - T*F_alp[2]) == 0)
check("torus equivariance: F(tx, y/t, z/t^2) = diag(t^-2,t^-1,t) F  [identity in t]", ok_t)

A_, B_ = sp.symbols('a b')
def toab(e):
    p = sp.Poly(sp.expand(e), x, y, z); out = 0
    for mono, co in zip(p.monoms(), p.coeffs()):
        i, j, k = mono
        if i != j + 2*k:
            return None
        out += co * A_**j * B_**k
    return sp.expand(out)
P1 = toab(sp.expand(F_alp[1]*F_alp[2])); P2 = toab(sp.expand(F_alp[0]*F_alp[2]**2))
ok_d = P1 is not None and P2 is not None
check("descent: F2*F3 and F1*F3^2 are polynomials in a=xy, b=x^2 z", ok_d)
Jab = sp.factor(sp.Matrix([P1, P2]).jacobian([A_, B_]).det())
print("        descended plane map Jacobian (factored):", Jab, flush=True)
RES["torus"] = {"equivariance": ok_t, "descent": ok_d, "Jac_ab_factored": str(Jab)}
save()

# ---------- generic fiber degree by elimination -------------------------------
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)
        if len(zs) != 1:
            return None
        zs = zs[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)
        d_all, d_sf = sp.degree(expr, x), sp.degree(sf, x)
        outs.append((d_all, d_sf))
    print(f"        [{tag}] eliminant degrees (all, squarefree) per target: {outs}", flush=True)
    return outs

print("\n=== generic fiber degree of ALPOGE'S MAP (new invariant) ===", flush=True)
alp_deg = map_degree(F_alp, tries=2, tag="alpoge")
RES["alpoge_degree"] = {"eliminant_degrees": alp_deg}
save()

# ---------- THE FORGE ----------------------------------------------------------
def forge(m, dP, dQ, dR):
    cfg = {"m": m, "degP": dP, "degQ": dQ, "degR": dR, "status": "started",
           "branches": [], "specimens": [], "automorphisms": 0, "c0_branches": 0}
    t1 = time.time()
    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  # lemma
    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())
    # stage 1: linear
    eqs1 = sp.Poly(M1, x, y).coeffs()
    lin = sp.linsolve(eqs1, U)
    if lin == sp.EmptySet:
        cfg["status"] = "stage1-empty (EXCLUDED: no A completes this slope linearly)"
        RES["configs"].append(cfg); save(); 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)
    # stage 2: quadratic
    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)
    if free1:
        branches = sp.solve(eqs2, free1, dict=True) if eqs2 else [dict()]
    else:
        branches = [dict()] if all(sp.expand(e) == 0 for e in eqs2) else []
    print(f"   m={m} caps=({dP},{dQ},{dR}): stage1 free={len(free1)}, stage2 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)
        binfo = {"branch": bi, "c": str(cb), "free_params": [str(s) for s in rem], "samples": []}
        if cb == 0:
            cfg["c0_branches"] += 1; binfo["verdict"] = "c=0 (not Keller)"
            cfg["branches"].append(binfo); continue
        # sample instances
        vals_list = [dict(zip(rem, comb)) for comb in
                     ([tuple()] if not rem else
                      [tuple(random.choice([1, -1, 2, sp.Rational(1,2), 3]) for _ in rem) for _ in range(2)])]
        for vals in vals_list:
            inst = {k_: sp.expand(v_.subs(vals)) for k_, v_ in fullsub.items()}
            cval = sp.expand(cb.subs(vals))
            if cval == 0:
                continue
            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())
            ok_keller = (dJ == cval) and (dJ.free_symbols == set())
            degs = map_degree(Fi, tries=2, tag=f"m={m} b{bi}") if ok_keller else None
            tot = max(sp.total_degree(f, x, y, z) for f in Fi)
            samp = {"params": {str(k_): str(v_) for k_, v_ in vals.items()},
                    "keller_verified": bool(ok_keller), "det": str(dJ),
                    "total_degree": int(tot), "fiber_eliminant": str(degs)}
            # verdict from squarefree degrees (consistent across targets)
            if ok_keller and degs and all(isinstance(d[1], sp.Integer) or isinstance(d[1], int) for d in degs):
                sfset = {int(d[1]) for d in degs}
                if sfset == {1}:
                    samp["verdict"] = "degree 1 => AUTOMORPHISM (Keller birational => invertible)"
                    cfg["automorphisms"] += 1
                elif min(sfset) >= 2:
                    samp["verdict"] = f"degree {sfset} => NON-INJECTIVE KELLER SPECIMEN, total_degree {tot}"
                    cfg["specimens"].append(samp)
                else:
                    samp["verdict"] = f"inconsistent degrees {sfset} — needs deeper certification"
            binfo["samples"].append(samp)
        cfg["branches"].append(binfo)
    # membership test: does the Alpoge point satisfy this config's equations? (m=3 caps>= (2,1,1))
    if m == 3 and dP >= 2 and dQ >= 1 and dR >= 1:
        alp_pt = {ps[0]: 4, ps[1]: 7, ps[2]: 3, qs[0]: 12, qs[1]: 9, rs[0]: 2, rs[1]: -3}
        for s_ in U:
            alp_pt.setdefault(s_, 0)
        ok_mem = all(sp.expand(e.subs(alp_pt)) == 0 for e in eqs1) and \
                 all(sp.expand(e.subs(alp_pt).subs(alp_pt)) == 0
                     for e in sp.Poly(sp.expand(M0.subs(alp_pt, simultaneous=True)), x, y).coeffs()[0:0])
        # direct: plug into M1 and nonconstant part of M0
        M0a = sp.expand(M0.subs(alp_pt, simultaneous=True))
        pa = sp.Poly(M0a, x, y)
        nc = [co for mo, co in zip(pa.monoms(), pa.coeffs()) if mo != (0, 0)]
        ca = pa.coeff_monomial(1)
        ok_mem = all(sp.expand(sp.sympify(e)) == 0 for e in nc) and \
                 sp.expand(M1.subs(alp_pt, simultaneous=True)) == 0 and ca == -2
        check(f"   m=3: ALPOGE POINT (4,7,3|12,9|2,-3) satisfies all forge equations, c=-2", ok_mem)
        cfg["alpoge_membership"] = bool(ok_mem)
    cfg["status"] = "done"
    cfg["elapsed_sec"] = round(time.time() - t1, 2)
    RES["configs"].append(cfg); save()

print("\n=== FORGE m-SWEEP ===", flush=True)
forge(1, 1, 1, 1)
forge(1, 2, 2, 2)
forge(2, 1, 1, 1)
forge(2, 2, 2, 2)
forge(3, 2, 1, 1)   # Alpoge's exact shape class
forge(3, 2, 2, 2)   # exploratory superset

RES["elapsed_total_sec"] = round(time.time() - t0, 2)
save()
print(f"\nFORGE COMPLETE ({RES['elapsed_total_sec']}s). artifact: forge_results.json", flush=True)
