#!/usr/bin/env python3
"""
Sidon-Sofa Coloring v3: Fine q-value sweep + n=34 extension.

Extends v2 with:
  - 100 q-values from 0.5 to 2.0 (50 steps below q=1, 50 above)
  - n=34 added to n-values {8, 13, 21, 34}
  - Focus: map the q=1 phase boundary precisely

Usage:
  python3 sidon_sofa_coloring_v3.py [--seed N] [--quick]
"""
import sys, json, math, hashlib, time, os, random
from pathlib import Path
from fractions import Fraction

HERE = Path(__file__).resolve().parent
OUT_DIR = HERE.parent / ".openresearch" / "artifacts"
OUT_DIR.mkdir(parents=True, exist_ok=True)

# Import v2 functions
sys.path.insert(0, str(HERE))
import importlib.util
spec = importlib.util.spec_from_file_location("v2", HERE / "sidon_sofa_coloring_v2.py")
v2 = importlib.util.module_from_spec(spec)
spec.loader.exec_module(v2)

# ── Configuration ──────────────────────────────────────────────────

Q_VALUES = [round(0.5 + i * 1.5 / 99, 6) for i in range(100)]
N_VALUES = [8, 13, 21, 34]
SHAPES = ["half_disc", "rectangle", "sidon_polar", "gerver_like", "hammersley"]
CHI_VALUES = [1, 2, 3, 5, 7]

def deterministic_seed(n, shape_name, qi):
    """Deterministic hash from (n, shape, qi) — NOT randomized like hash()."""
    import hashlib
    key = f"{n}:{shape_name}:{qi}".encode()
    return int(hashlib.sha256(key).hexdigest()[:8], 16)

def run_experiment(seed=0, quick=False):
    rng = random.Random(seed)
    T = 24
    motion = v2.make_l_corridor_motion(T)
    
    n_values = [8, 13, 21] if quick else N_VALUES
    q_values = [Fraction(1, 2), Fraction(3, 4), Fraction(1, 1),
                Fraction(4, 3), Fraction(2, 1)] if quick else Q_VALUES
    
    data = []
    t_start = time.time()
    
    for n in n_values:
        for shape_name in SHAPES:
            for qi, q in enumerate(q_values):
                q_float = float(q)
                if shape_name == "half_disc":
                    radius = 0.5 * (1 + q_float) / 2
                    boundary = v2.make_half_disc_boundary(n, radius=radius)
                elif shape_name == "rectangle":
                    w = 0.9 * (1 + q_float) / 2
                    h = 0.9 * (2 - (1 + q_float) / 2)
                    boundary = v2.make_rectangle_boundary(n, width=w, height=h)
                elif shape_name == "sidon_polar":
                    sidon_1d, M = v2.crt_sidon_set(n)
                    base_r = 0.4 * (1 + q_float) / 2
                    boundary = v2.make_sidon_polar_boundary(n, sidon_1d, M,
                                                           base_radius=base_r)
                elif shape_name == "gerver_like":
                    boundary = v2.make_gerver_like_boundary(n)
                elif shape_name == "hammersley":
                    boundary = v2.make_hammersley_boundary(n)
                
                area = float(v2.polygon_area(boundary))
                adj = v2.build_conflict_graph(boundary, motion)
                
                combo_seed = seed + deterministic_seed(n, shape_name, qi) % (2**31)
                chi_actual = v2.chromatic_number(adj, seed=combo_seed)
                n_edges = sum(len(neighbors) for neighbors in adj.values()) // 2
                max_deg = max(len(adj[i]) for i in range(len(adj))) if adj else 0
                
                for chi_target in CHI_VALUES:
                    feasible = chi_actual <= chi_target
                    data.append({
                        "n": n,
                        "shape": shape_name,
                        "q": str(q) if isinstance(q, Fraction) else f"{q:.6f}",
                        "q_value": q_float,
                        "chi_target": chi_target,
                        "chi_actual": chi_actual,
                        "feasible": feasible,
                        "area": area,
                        "a_star": area if feasible else 0.0,
                        "n_edges": n_edges,
                        "max_degree": max_deg,
                        "n_motion_samples": T,
                    })
                
                # Progress
                log_interval = 1 if quick else 10
                if qi % log_interval == 0:
                    print(f"  n={n:2d} shape={shape_name:12s} q={q_float:.4f} "
                          f"area={area:.4f} edges={n_edges} χ={chi_actual}", flush=True)
    
    t_elapsed = time.time() - t_start
    
    results = {
        "experiment": "sidon_sofa_coloring_v3",
        "direction": "A: Fine q-value sweep + n=34",
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "seed": seed,
        "config": {
            "n_values": n_values,
            "chi_values": CHI_VALUES,
            "q_values": [str(q) for q in q_values] if quick else [f"{q:.6f}" for q in Q_VALUES],
            "q_count": len(q_values),
            "n_motion_samples": T,
            "corridor_width": 1,
            "eps_tolerance": 0.05,
            "shapes": SHAPES,
            "chromatic_method": "DSATUR + exact for <=16 vertices",
            "quick": quick,
        },
        "data": data,
        "elapsed_s": round(t_elapsed, 2),
    }
    
    content = json.dumps(results, indent=2)
    results["sha256"] = hashlib.sha256(content.encode()).hexdigest()
    return results

def write_eval(results):
    lines = [
        f"# Sidon-Sofa Coloring: Direction A v3 (fine q-sweep + n=34) Results",
        f"",
        f"**Experiment:** {results['experiment']}",
        f"**Date:** {results['timestamp']}",
        f"**Seed:** {results['seed']}",
        f"**SHA-256:** `{results['sha256']}`",
        f"",
        f"**Motion samples:** 24",
        f"**q-values swept:** {results['config']['q_count']}",
        f"**n-values:** {results['config']['n_values']}",
        f"",
        f"## q=1 Phase Boundary",
        f"",
        f"| Shape | n | q | Area | Edges | χ |",
        f"|-------|---|-------|------|-------|---|",
    ]
    for d in results["data"]:
        if d["chi_target"] == 7 and 0.9 <= d["q_value"] <= 1.1:
            lines.append(
                f"| {d['shape']:12s} | {d['n']:2d} | {d['q_value']:.4f} | "
                f"{d['area']:.4f} | {d['n_edges']:4d} | {d['chi_actual']:3d} |"
            )
    
    by_key = {}
    for d in results["data"]:
        if d["chi_target"] == 7:
            k = (d["shape"], d["n"])
            by_key.setdefault(k, set()).add(d["chi_actual"])
    
    lines += [f"", f"## χ Stability Across q", f"",
              f"| Shape | n | χ range | Stable? |",
              f"|-------|---|---------|--------|"]
    for (shape, n), chis in sorted(by_key.items()):
        delta = max(chis) - min(chis)
        lines.append(f"| {shape:12s} | {n:2d} | {min(chis)}-{max(chis)} | {'✓' if delta==0 else '⚠ Δ='+str(delta):7s} |")
    
    lines += [f"", f"## n=34 Results", f"",
              f"| Shape | Best χ | Min Area | Max Area |",
              f"|-------|--------|----------|----------|"]
    n34 = [d for d in results["data"] if d["n"] == 34 and d["chi_target"] == 7]
    for shape in SHAPES:
        sd = [d for d in n34 if d["shape"] == shape]
        if sd:
            lines.append(f"| {shape:12s} | {min(d['chi_actual'] for d in sd):7d} | "
                         f"{min(d['area'] for d in sd):.4f} | {max(d['area'] for d in sd):.4f} |")
    
    return "\n".join(lines)

def main():
    import argparse
    parser = argparse.ArgumentParser(description="Sidon-Sofa Coloring v3")
    parser.add_argument("--seed", type=int, default=0)
    parser.add_argument("--quick", action="store_true")
    args = parser.parse_args()
    
    print("=" * 62)
    print("  Sidon-Sofa Coloring v3: Fine q-sweep + n=34")
    print("=" * 62)
    print(f"  Seed: {args.seed}")
    print(f"  q-values: {5 if args.quick else 100}")
    print(f"  n-values: {N_VALUES}" if not args.quick else "  n-values: [8, 13, 21]")
    
    results = run_experiment(seed=args.seed, quick=args.quick)
    
    out_path = OUT_DIR / "sidon_sofa_coloring_v3.json"
    with open(out_path, "w") as f:
        json.dump(results, f, indent=2)
    print(f"\nResults → {out_path}")
    
    eval_path = OUT_DIR / "EVAL_v3.md"
    with open(eval_path, "w") as f:
        f.write(write_eval(results))
    print(f"EVAL    → {eval_path}")
    print(f"Elapsed: {results['elapsed_s']}s")
    print("=" * 62)
    print("  DONE")
    print("=" * 62)

if __name__ == "__main__":
    main()
