#!/usr/bin/env python3
"""Render two 16:9 presentation pages from frozen canonical SR artifacts."""
from __future__ import annotations
import argparse, csv, json, re
from pathlib import Path
from typing import Any
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

BLUE, ORANGE, INK, MUTED, PALE = "#1769AA", "#D65F1E", "#17202A", "#52606D", "#F3F7FA"

def load_json(path: Path) -> dict[str, Any]: return json.loads(path.read_text(encoding="utf-8"))
def rows(path: Path) -> list[dict[str,str]]:
    with path.open(newline="", encoding="utf-8") as f: return list(csv.DictReader(f))
def similarity(path: Path) -> float: return float(rows(path)[-1]["similarity"])
def expression(root: Path, family: str, head: str) -> str:
    p=root/f"article-refit-{family}-topology-a-20260718/formulas/{head}.json"
    return load_json(p)["deployment_expression"]
def pretty(expr: str) -> str:
    rounded = re.sub(r"(?<![\w.])[-+]?(?:\d+\.\d*|\.\d+)(?:[eE][+-]?\d+)?", lambda match: f"{float(match.group()):.4f}", expr)
    return rounded.replace(" * ", r"\,").replace("Cd_rear_a", r"C_{D,r}^{a}").replace("Cl_rear_s", r"C_{L,r}^{s}").replace("Cl_F", r"C_{L,F}")
def save(fig: plt.Figure, out: Path, stem: str) -> None:
    out.mkdir(parents=True, exist_ok=True); fig.savefig(out/f"{stem}.png", dpi=200, facecolor="white"); fig.savefig(out/f"{stem}.pdf", facecolor="white"); plt.close(fig)
def base(title: str, subtitle: str):
    fig=plt.figure(figsize=(16,9), facecolor="white"); fig.text(.05,.94,title,fontsize=28,weight="bold",color=INK,va="top"); fig.text(.05,.885,subtitle,fontsize=13,color=MUTED,va="top"); return fig

def page_method(root: Path, out: Path) -> None:
    fig=base("From PPO trajectories to an executable symbolic controller", "Causal alignment, symmetry, all-case refitting, then closed-loop CFD—not offline fit alone")
    steps=[("1","PPO trajectories","states • forces • actions"),("2","Causal dataset","post-action state → next action"),("3","Topology discovery","G-symmetry-aware symbolic search"),("4","Coefficient refit","fixed topology • all family cases"),("5","CFD validation","40 / 200 / 400 steps • deletion")]
    for i,(n,h,d) in enumerate(steps):
        x=.05+i*.19; fig.patches.append(plt.Rectangle((x,.67),.16,.13,transform=fig.transFigure,fc=PALE,ec=BLUE,lw=1.5))
        fig.text(x+.012,.765,n,fontsize=20,weight="bold",color=BLUE);fig.text(x+.042,.765,h,fontsize=14,weight="bold",color=INK);fig.text(x+.012,.705,d,fontsize=10.5,color=MUTED)
        if i<4: fig.text(x+.17,.725,"→",fontsize=24,color=ORANGE,ha="center")
    formulas={f:(pretty(expression(root,f,"joint_front")),pretty(expression(root,f,"joint_rear_shared_upper"))) for f in ("karman","illusion")}
    for x,f,title in ((.05,"karman","Kármán shared family"),(.52,"illusion","Illusion shared numerical family")):
        fig.patches.append(plt.Rectangle((x,.25),.43,.34,transform=fig.transFigure,fc="white",ec="#CAD5DF",lw=1.2))
        fig.text(x+.025,.545,title,fontsize=18,weight="bold",color=BLUE if f=="karman" else ORANGE)
        front,rear=formulas[f]
        fig.text(x+.025,.485,r"$\alpha_F(x)=\frac{1}{2}[f(x)-f(Gx)]$",fontsize=14,color=INK)
        fig.text(x+.025,.435,rf"$f(x)={front}$",fontsize=14,color=INK)
        fig.text(x+.025,.375,rf"$\alpha_U(x)={rear}$",fontsize=14,color=INK)
        fig.text(x+.025,.325,r"$\alpha_L(x)=-\alpha_U(Gx)$",fontsize=14,color=INK)
        note=("Persistent rear counter-rotation constant dominates;\nrear lift correction is secondary." if f=="karman" else "Deployable shared family; deletion evidence does not\nestablish term uniqueness.")
        fig.text(x+.025,.285,note,fontsize=12,color=MUTED,linespacing=1.35)
    fig.text(.05,.12,"G mirrors upper/lower quantities and applies the signed symmetry map.  α is dimensionless cylinder surface speed.",fontsize=11.5,color=MUTED)
    save(fig,out,"01_sr_method_and_formulas")

def page_evidence(root: Path, out: Path) -> None:
    fig=base("Quantitative evidence at a glance", "Legacy DTW similarity; deterministic frozen artifacts; control-step windows stated explicitly")
    pkg=root/"article2-plotting-package-20260721"; standard=root/"article2-timeseries-csv-20260720"; longsr=root/"article2-long-timeseries-csv-20260720"
    scenes={"Kármán":["karman_re50","karman_re100","karman_re200","karman_re400"],"Illusion":["illusion_0.75L","illusion_1L","illusion_1.5L"]}
    vals={}
    for fam,ss in scenes.items():
        sr=np.array([similarity(longsr/s/"sr_dtw_convergence.csv") for s in ss]); ppo=np.array([similarity(pkg/"long_ppo"/s/"ppo_dtw_convergence.csv") for s in ss]); vals[fam]=(sr,ppo)
    gen=rows(root/"article2-generalization-summary-20260720/generalization.csv")
    deletion=rows(pkg/"tables/term_deletion.csv")
    cards=[(.05,.61,.42,.20,"400-step training cases",[(f"Kármán SR range",f"{vals['Kármán'][0].min():.3f}–{vals['Kármán'][0].max():.3f}"),("Kármán mean SR / PPO",f"{vals['Kármán'][0].mean():.3f} / {vals['Kármán'][1].mean():.3f}"),("Illusion SR range",f"{vals['Illusion'][0].min():.3f}–{vals['Illusion'][0].max():.3f}"),("Illusion mean SR / PPO",f"{vals['Illusion'][0].mean():.3f} / {vals['Illusion'][1].mean():.3f}")]),
    (.52,.61,.43,.20,"200-step unseen conditions",[("Kármán: 4 points",f"{min(float(r['legacy_dtw']) for r in gen if r['objective']=='karman'):.3f}–{max(float(r['legacy_dtw']) for r in gen if r['objective']=='karman'):.3f}"),("Illusion: 5 points",f"{min(float(r['legacy_dtw']) for r in gen if r['objective']=='illusion'):.3f}–{max(float(r['legacy_dtw']) for r in gen if r['objective']=='illusion'):.3f}"),("Evidence level","one realization / condition"),("Scope","sampled interpolation + extrapolation")])]
    for x,y,w,h,title,items in cards:
        fig.patches.append(plt.Rectangle((x,y),w,h,transform=fig.transFigure,fc=PALE,ec="#CAD5DF"));fig.text(x+.02,y+h-.045,title,fontsize=17,weight="bold",color=INK)
        for j,(k,v) in enumerate(items): fig.text(x+.02,y+h-.09-.032*j,k,fontsize=11,color=MUTED);fig.text(x+w-.02,y+h-.09-.032*j,v,fontsize=11.5,weight="bold",color=BLUE,ha="right")
    variants=[("K front term","k_front0"),("K rear lift","k_rear0"),("K rear constant","k_rear1"),("I worst mean deletion","i_front1")]
    fig.text(.05,.52,"40-step term deletion: mean parent-relative change",fontsize=18,weight="bold",color=INK)
    for i,(label,var) in enumerate(variants):
        sub=[r for r in deletion if r["variant"]==var]; delta=np.mean([float(r["delta_vs_parent_40_step"]) for r in sub]);x=.05+i*.225
        fig.patches.append(plt.Rectangle((x,.35),.20,.12,transform=fig.transFigure,fc="white",ec="#CAD5DF"));fig.text(x+.015,.43,label,fontsize=11.5,color=MUTED);fig.text(x+.015,.375,f"{delta:+.3f}",fontsize=22,weight="bold",color=ORANGE if delta<-.03 else BLUE)
    fig.text(.05,.235,"Read with care",fontsize=17,weight="bold",color=INK)
    cautions=["40-step deletion, 200-step generalization, and 400-step training summaries answer different questions.","Legacy DTW is an aligned trajectory-similarity metric—not causal proof or a physical delay estimate.","Unseen conditions are pointwise deterministic runs; no continuous parameter law or uncertainty band is claimed.","Offline action RMSE is intentionally omitted: imitation on PPO-visited states is not article performance."]
    for i,t in enumerate(cautions): fig.text(.07,.195-.038*i,"• "+t,fontsize=11.5,color=MUTED)
    save(fig,out,"02_sr_quantitative_evidence")

def main() -> int:
    ap=argparse.ArgumentParser();ap.add_argument("--repo-root",type=Path,default=Path(__file__).resolve().parents[3]);ap.add_argument("--output-dir",type=Path,default=None);a=ap.parse_args();root=a.repo_root.resolve()/"src/SR_analysis/results/runs";out=a.output_dir or root/"article2-plotting-package-20260721/presentation";page_method(root,out);page_evidence(root,out);return 0
if __name__=="__main__": raise SystemExit(main())
