"""Side by side, per layer: the slicer's rings (package), A (ringfit.py: slicer topology, contours fitted to the points)
and B (ringfit.py: contours from the points' sheets), over GT and the slab's points moved onto the plane. Writes
../report/compare_<name>_<cloud>.html (with the parts counts and errors of the three).

    env -u PYTHONPATH PYTHONWARNINGS=ignore .venv/bin/python ~/code/data/meshes/fcp_2710_tests/scripts/compare_slices.py bowl_gso clean_10000 20,60,100,110,114,116,118,120
"""
import sys
import time
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import trimesh

sys.path.insert(0, str(Path(__file__).parent))
import ringfit as rf  # noqa: E402
import hybrid as hy  # noqa: E402
import splits as sp  # noqa: E402
import fcp  # noqa: E402
from fcp._slice import PointSlicer, centres  # noqa: E402
from parts_report import CSS, OBJ, REPORT, ROOT, UP, stats  # noqa: E402


def closed(R):
    return np.vstack([R, R[:1]])


def run(P, up, ref, f):
    d = f(P, up=up)
    m = fcp.decode(d, device="cuda")
    return d, (stats(d, ref, m)[0] if m is not None else float("nan")), {k: dict(v) for k, v in rf.DEBUG.items()}


def main(name, cloud, layers):
    g = trimesh.load(OBJ[name], force="mesh")
    P = np.asarray(trimesh.load(ROOT / "pointclouds" / f"{name}_{cloud}.ply").vertices)
    up = UP.get(name, (0, 0, 1))
    ref = np.asarray(g.sample(50_000, seed=1))
    base = fcp.encode(P, device="cuda", up=up)
    be = stats(base, ref, fcp.decode(base, device="cuda"))[0]
    dA, eA, DA = run(P, up, ref, rf.encode_ringfit)
    dB, eB, DB = run(P, up, ref, rf.encode_graph)
    dH = hy.encode_hybrid(P, up=up)
    mH = fcp.decode(dH, device="cuda")
    eH = stats(dH, ref, mH)[0] if mH is not None else float("nan")
    DH = {k: dict(v) for k, v in hy.DEBUG.items()}
    sl = PointSlicer(P, up=up, device="cuda")
    sc = sl.pose.scale.astype(float)
    X = (P - sl.pose.center) @ sl.pose.rotation
    NX = sl.world_normals() @ sl.pose.rotation
    s = float(sl.spacing)
    c = centres(128)
    fig, axs = plt.subplots(len(layers), 4, figsize=(20, 5 * len(layers)), dpi=80, squeeze=False)
    for row, k in zip(axs, layers):
        Q, _ = rf.slab(X, NX, c[k] * sc[2], s)
        gt = [L * sc[:2] for L in sp.gt_section(g, sl.pose, c[k])]
        sets = [("slicer (package)", DA[k]["slicer"], None), (f"A: slicer topology, fitted", DA[k]["fitted"], DA[k]["used"]),
                (f"B: point sheets, fitted", DB[k]["fitted"], DB[k]["used"]),
                (f"hybrid (layer from {DH[k]['used']})", DH[k]["final"], None)]
        allxy = np.vstack(gt + [np.zeros((1, 2))] + DA[k]["slicer"]) if gt else np.vstack([Q, np.zeros((1, 2))])
        lo, hi = allxy.min(0), allxy.max(0)
        mrg = 0.08 * (hi - lo).max() + 2 * s
        for ax, (title, rings, used) in zip(row, sets):
            ax.scatter(Q[:, 0], Q[:, 1], s=3, c="#a0a0a0", lw=0, zorder=1)
            for L in gt:
                ax.plot(L[:, 0], L[:, 1], c="k", lw=1.6, zorder=2)
            for i, R in enumerate(rings):
                col = "#d1495b" if rf.signed_area(np.asarray(R)) > 0 else "#2e86ab"
                ax.plot(*closed(np.asarray(R)).T, c=col, lw=1.3, zorder=3)
            extra = ""
            if used is not None:
                extra = f", N {min(used) if used else 0}-{max(used) if used else 0}"
            if title.startswith("B"):
                extra += f", {DB[k]['dropped']} sheets dropped"
            ax.set_title(f"layer {k} · {title}: {len(rings)} rings{extra}", fontsize=8)
            ax.set_xlim(lo[0] - mrg, hi[0] + mrg); ax.set_ylim(lo[1] - mrg, hi[1] + mrg)
            ax.set_aspect("equal"); ax.tick_params(labelsize=6)
    fig.text(0.5, 0.002, "black: GT · grey: the slab's points (±1 spacing, near-vertical surfaces, moved onto the plane) · "
             "red: rings running counter-clockwise · blue: clockwise (holes, when the orientation is right)",
             ha="center", fontsize=9)
    fig.tight_layout(rect=(0, 0.01, 1, 1))
    tag = f"compare_{name}_{cloud}"
    fig.savefig(REPORT / f"img/{tag}.png")
    plt.close(fig)
    (REPORT / f"{tag}.html").write_text(f"""<!doctype html><html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>{name}: slicer, A, B, hybrid</title><style>{CSS}</style></head><body>
<p><a href="index.html">← report</a> · <a href="analysis_ringfit.html">analysis</a></p>
<h1>{name} ({cloud.replace('_', ' ')}): slicer, A, B, hybrid</h1>
<table><tr><th>method</th><th>parts</th><th>mean/diag</th></tr>
<tr><td>slicer (package)</td><td>{len(base.parts)}</td><td>{be:.5f}</td></tr>
<tr><td>A: slicer topology, contours fitted to the points</td><td>{len(dA.parts)}</td><td>{eA:.5f}</td></tr>
<tr><td>B: contours from the points' sheets</td><td>{len(dB.parts)}</td><td>{eB:.5f}</td></tr>
<tr><td>hybrid: slicer by default, repaired from the neighbouring layers, all fitted</td><td>{len(dH.parts)}</td><td>{eH:.5f}</td></tr></table>
<p class="mut">Hybrid titles: which set the layer uses (S: the slicer's rings refitted; U / D: the layer below / above carried and refitted).</p>
<p class="mut">Metric canonical frame (x right, y up), spacing {s:.2f}. Prototypes in <code>scripts/ringfit.py</code>.
Generated {time.strftime('%Y-%m-%d %H:%M')} by <code>scripts/compare_slices.py</code>.</p>
<img src="img/{tag}.png">
</body></html>""")
    print(tag, len(base.parts), len(dA.parts), len(dB.parts), len(dH.parts), f"{be:.5f} {eA:.5f} {eB:.5f} {eH:.5f}")


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2], [int(x) for x in sys.argv[3].split(",")])
