#!/usr/bin/env python3
"""JUDGE de cena (João) — pros clips que NÃO sincaram: VLM olha 1 frame e diz se é CERIMÔNIA.
NÃO-cerimônia = erro de decupagem → separa + TAGGEIA (making-of noiva/noivo, decoração, ensaio,
entrada) p/ os modelos futuros. Cerimônia-mas-não-sincada → mantém ORANGE (ex o BEIJO C8246).
Gatilho = falha de sync (só julga os suspeitos → barato). READ-ONLY. Kimi via vlm.py.
Uso: sync_judge.py decupagem_<x>.json [--all]  (--all julga todos, p/ validar)"""
import os, sys, json, glob, re, subprocess, argparse
from vlm import ask_image

PROMPT = (
 "Frame from a Brazilian wedding. Classify the SCENE into exactly one label.\n"
 "KEY RULE for church scenes: the CEREMONY is the RITE with GUESTS SEATED in the pews and/or a "
 "CELEBRANT/PRIEST officiating. If the venue is EMPTY (no guests) and the couple is alone "
 "POSING (kissing, hands, looking at camera) = that is the post-ceremony ENSAIO, NOT the ceremony.\n"
 "CEREMONY PROCESSIONAL: bride/groom/bridesmaids in FULL CEREMONY ATTIRE (wedding dress, bouquet, "
 "suit) formally WALKING DOWN an aisle or decorated staircase BEFORE the vows — this is the START "
 "of the rite, classify as CEREMONY even if the staircase/hallway looks similar to a reception "
 "entrance, even with no guests visible in THIS frame (camera may be tight on the walker).\n"
 "- CEREMONY: rite IN PROGRESS (guests seated/celebrant present) OR the bridal processional/cortejo "
 "walking in BEFORE the vows in full ceremony attire (dress+bouquet / suit, formal decorated path)\n"
 "- SAIDA: the RECESSIONAL — end of the ceremony, couple walking OUT together FACING FORWARD, happy/"
 "celebrating (smiling, laughing, arms up), applause from guests, often sparklers/petals/bubbles/"
 "confetti; the celebrant is NO LONGER in scene and the bridesmaids/groomsmen (padrinhos) are lined "
 "up in a characteristic formation. This is STILL CEREMONY (the happiest peak of it), keep it — NOT "
 "ensaio. Distinguish from ENSAIO: SAIDA is a moving celebratory exit WITH guests/applause present, "
 "ensaio is the couple alone posing quietly afterwards.\n"
 "- ENSAIO: couple alone POSING/RELAXED (casual interaction, not a formal walk-in), no guests, no "
 "rite, no bouquet-in-hand formal walk (post-ceremony portrait session)\n"
 "- MAKINGOF_BRIDE: beauty salon / bride getting ready (hair, makeup, dress)\n"
 "- MAKINGOF_GROOM: groom getting ready (suit, tie)\n"
 "- ENTRANCE: grand RECEPTION/PARTY entrance — couple already married (no longer walking TO the "
 "rite), party/DJ lighting or reception decor visible, distinct from the ceremony processional above\n"
 "- ARRIVAL: car, getting out of vehicle, arriving at venue\n"
 "- DECORATION: empty decorated venue, flowers, table setting, no main people\n"
 "- PARTY: reception/dance floor, party lights\n"
 "- OTHER\n"
 "Look carefully: are there GUESTS in the pews? Is the person in FULL ceremony attire formally "
 "walking in (=CEREMONY processional) vs casually posing (=ENSAIO)? Answer strict JSON: "
 "{\"scene\":\"LABEL\",\"guests\":true/false,\"conf\":0-1,\"why\":\"<=6 words\"}")


def frame(src, dst, ss):
    if os.path.exists(dst):
        return dst
    r = subprocess.run(["ffmpeg", "-y", "-ss", str(ss), "-i", src, "-frames:v", "1",
                        "-vf", "scale=320:-1", dst], capture_output=True)
    return dst if r.returncode == 0 and os.path.exists(dst) else None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("decupagem"); ap.add_argument("--momento", default="cerimonia")
    ap.add_argument("--all", action="store_true")
    ap.add_argument("--sync-json", help="placements_<x>_<momento>.json (lista COMPLETA de "
                     "candidatos, pré-exclusão) — fonte default se existir, senão placements.json")
    a = ap.parse_args()
    decup = json.load(open(a.decupagem)); base = decup["base"]
    name = decup.get("casamento", "w").split()[0].lower()
    ws = os.path.join("weddings", name)
    jf = os.path.join(ws, "scene_judge.json")
    verdicts = json.load(open(jf)) if os.path.exists(jf) else {}  # MERGE, nunca overwrite —
    # lição: sobrescrever perdia os já julgados quando rodava de novo só com os novos targets.
    import sync_ingest as si
    folders = {c: si.find_videos_multi(base, ci["pasta"]) for c, ci in decup["cameras"].items()}
    fdir = os.path.join(ws, "judge_frames"); os.makedirs(fdir, exist_ok=True)

    # FONTE: lista de candidatos PRÉ-exclusão (sync_json), não placements.json pós-corte — lição
    # (Nathália): placements.py já excluiu clips que NUNCA chegaram a ser julgados (faixa de
    # convidados confirmados cortava antes do judge ver), gap circular permanente (17/73 clips
    # do cam1 nunca julgados, incluindo o início real da cerimônia C8193-95).
    if a.sync_json and os.path.exists(a.sync_json):
        P = json.load(open(a.sync_json))
        cand = []
        for fname, c in P["clips"].items():
            stem = os.path.splitext(fname)[0]
            cam = c["cam"]
            cand.append({"name": f"{cam}_{stem}", "cam": cam, "stem": stem, "orange": True})
    else:
        pl = json.load(open(os.path.join(ws, "placements.json")))
        cand = [{"name": c["name"], "cam": c["cam"],
                 "stem": re.sub(r'^[^_]+_[^_]+_', '', c["name"]), "orange": c["orange"]}
                for c in pl["clips"]]

    targets = [c for c in cand if a.all or c["name"] not in verdicts or (not a.sync_json and c["orange"])]
    print(f"julgando {len(targets)} clips (de {len(cand)} candidatos; {len(verdicts)} já no cache)")
    toks = 0
    for c in targets:
        src = next((v for v in folders.get(c["cam"], [])
                   if os.path.splitext(os.path.basename(v))[0] == c["stem"]), None)
        if not src:
            print(f"  ! fonte sumiu: {c['name']}"); continue
        fr = frame(src, os.path.join(fdir, f"{c['name']}.jpg"), 2)
        if not fr:
            continue
        txt, usage = ask_image(fr, PROMPT)
        toks += usage.get("total_tokens", 0)
        m = re.search(r'\{.*\}', txt, re.S)
        v = {}
        try:
            v = json.loads(m.group(0)) if m else {}
        except Exception:
            v = {"scene": "PARSE_ERR", "raw": txt[:60]}
        # CEREMONY (rito com convidados/celebrante) e SAIDA (recessional, pico feliz — ainda é
        # cerimônia, João) contam. CEREMONY_PROCESSIONAL (variação inventada pelo modelo) NÃO:
        # "noiva descendo escadaria de vestido com buquê" é visualmente IDÊNTICO entre processional
        # real e revelação do vestido no making-of, frame único não distingue (lição Nathália:
        # C8195 marcado CEREMONY_PROCESSIONAL era making-of, confirmado por João — falso positivo).
        v["is_ceremony"] = v.get("scene") in ("CEREMONY", "SAIDA")
        verdicts[c["name"]] = v
        json.dump(verdicts, open(jf, "w"), indent=1, ensure_ascii=False)  # salva incremental
        print(f"  {c['name'][-22:]:22} -> {v.get('scene','?'):16} {v.get('why','')}")
    nonc = [n for n, v in verdicts.items() if not v.get("is_ceremony")]
    print(f"\n{len(verdicts)} julgados (total) | NÃO-cerimônia: {len(nonc)} | tokens nessa run: {toks}")
    # custo REAL medido no billing Moonshot (jun/2026): ~$0.243/M tokens (≈$0.0003/img). Não $2/M.
    print(f"tokens {toks} ≈ ${toks/1e6*0.243:.4f} ({len(verdicts)} imgs, ~$0.0003/img)  -> {ws}/scene_judge.json")


if __name__ == "__main__":
    main()
