#!/usr/bin/env python3
"""STAB-SWITCHES do-zero (6d, tremido-COM-conteúdo, judge moment-aware) -> stab_switches.json.
Localizador: segmento >=2.5s do UNI com jerk da ativa alto + alt coberta estável -> judge decide
(ápice fica tremido; comum troca). Uso: stab_gen.py

Usa p80 (pico, mesma agregação do unified.py::badness), não média: tremor breve dentro de um
segmento longo dilui na média e nunca dispara (validado: Paula do-zero4, segmento de 5.8s com
p80(ativa)=1.19 vs alt p80=0.06 — 20x mais estável — nunca virou candidato com mean_jerk; ver
STAB_EMOTION_JUDGE_IMPROVEMENT_PLAN.md §3.5)."""
import json, os, glob
import numpy as np
from vlm import ask_images, par_map
import judgelib as J
import vlib as V
import cache_manifest as cm

OUT = "stab_switches.json"
_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

def p80(jerk, cam, f0, f1):
    ts, js = V.jerk_series(jerk, cam, f0, f1)
    return float(np.percentile(js, 80)) if js is not None and len(js) else None

def main():
    # CACHE (backlog-30 grupo2): dezenas de calls. UNI.json é regenerado toda rodada -> content-
    # hash. shakecache/ inteiro entra por mtime+size (per-arquivo, já é idempotente por si só —
    # ver A.1.3 do backlog; aqui é só pra detectar footage NOVA/removida do diretório).
    _sig = {"content": cm.content_signature(["versions/UNI.json"]),
            "mtime": cm.input_signature(sorted(glob.glob("shakecache/*.json")) +
                                         [os.path.join(_SCRIPT_DIR, "prompts", "stab_judge_v1.md"),
                                          os.path.join(_SCRIPT_DIR, "judgelib.py"),
                                          os.path.join(_SCRIPT_DIR, "vlib.py")])}
    if cm.is_fresh(".", "stab_gen", __file__, _sig) and os.path.exists(OUT):
        print(f"[cache] stab_gen: script+input inalterados, pulando VLM — reusando {OUT}")
        return
    cov = V.coverage(); JERK = V.load_jerk()
    prompt = J.prompt("stab_judge_v1.md")
    def work(s):
        ja = p80(JERK, V.ANGLE_CAM[s["angle"]], s["f0"], s["f1"])
        if ja is None or ja < 1.0: return None
        alt = next((a for a in sorted(V.ANGLE_CAM) if a != s["angle"] and V.covers(cov, a, s["f0"], s["f1"])
                    and (p80(JERK, V.ANGLE_CAM[a], s["f0"], s["f1"]) or 9) < 0.5), None)
        if not alt: return None
        mid = (s["f0"]+s["f1"])//2
        a = J.frame_of(s["angle"], mid); b = J.frame_of(alt, mid)
        if not a or not b: return None
        txt, _ = ask_images([a, b], prompt)
        v = J.jparse(txt)
        st = "SWITCH" if v.get("switch") else "mantém(ápice?)"
        print(f"  {J.tc(s['f0'])} cam{s['angle']} j{round(ja,1)} vs cam{alt} -> {st} ({str(v.get('why',''))[:50]})")
        if not v.get("switch"): return None
        return {"f0": s["f0"], "f1": s["f1"], "from": s["angle"], "to": alt,
                "tc": J.tc(s["f0"])[:8], "why": f"judge: {v.get('why','')[:60]}"}
    out = [r for r in par_map(work, list(J.uni_segments(min_dur_s=2.5))) if r]
    json.dump(out, open(OUT, "w"), indent=1)
    cm.mark_done(".", "stab_gen", __file__, _sig)
    print(f"stab_switches.json: {len(out)} trocas")

if __name__ == "__main__":
    main()
