"""드리프트 수정 전후의 shot VE 배정을 샷 단위로 대조한다.

인덱스 드리프트는 **다른 씬의 VE 를 그 샷에 붙였다.** 그래서 수정의 효과는
"몇 개 샷의 등장인물 배정이 실제로 바뀌었는가"로 정확히 셀 수 있다. 다시
그릴 샷도 이 목록에서 고른다 — 눈대중이 아니라 바뀐 것에서.

    .venv/bin/python diff_shot_ve.py <old_manifest.json> [--top N]

old 는 `manifest_<stamp>.json` 보관본이나 백업본 아무거나. new 는 현재
`manifest.json` 을 읽는다.
"""
from __future__ import annotations

import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from app.core.config import settings  # noqa: E402

PROJ = "e716bafb-24bb-42b7-aea0-fdb383844ee8"
EPI = "d6a9aa85-b75e-400c-980c-4ee7e876a15b"


def load_ve(path: Path) -> dict:
    """(scene_index, shot_index) → visible_entity_ids 집합."""
    d = json.loads(path.read_text("utf-8"))
    out = {}
    for sc in (d.get("data") or {}).get("scenes") or []:
        si = sc.get("scene_index")
        for sh in sc.get("shots") or []:
            out[(si, sh.get("shot_index"))] = set(sh.get("visible_entity_ids") or [])
    return out


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("old")
    ap.add_argument("--top", type=int, default=25)
    a = ap.parse_args()

    cp = (Path(settings.projects_dir) / PROJ / "checkpoints" / "episodes" / EPI)
    new_path = cp / "shot_director" / "manifest.json"
    if not new_path.exists():
        sys.exit("새 shot_director manifest 가 아직 없다 — 실행이 안 끝났다")

    old, new = load_ve(Path(a.old)), load_ve(new_path)
    t2i = json.loads((cp / "entity_t2i" / "manifest.json").read_text("utf-8"))["data"]
    nm = {e["short_id"]: e["name"]
          for k in ("characters", "locations", "props")
          for e in t2i.get(k) or [] if e.get("short_id")}

    keys = sorted(set(old) | set(new))
    changed, only_old, only_new = [], [], []
    for k in keys:
        if k not in new:
            only_old.append(k)
            continue
        if k not in old:
            only_new.append(k)
            continue
        if old[k] != new[k]:
            changed.append(k)

    def chars(s):
        return sorted(nm.get(i, i) for i in s if i.startswith("C"))

    # 인물 배정이 바뀐 것을 먼저 — 눈에 보이는 결함이 거기서 난다.
    char_changed = [k for k in changed if set(chars(old[k])) != set(chars(new[k]))]

    print(f"샷 총 {len(keys)} · 양쪽 존재 {len(keys)-len(only_old)-len(only_new)}")
    print(f"VE 가 바뀐 샷      : {len(changed)}")
    print(f"  └ 그중 **인물**이 바뀐 샷 : {len(char_changed)}")
    print(f"옛것에만 : {len(only_old)}  새것에만 : {len(only_new)}")
    print()
    print(f"── 인물 배정이 바뀐 샷 (상위 {a.top}) ──")
    for si, shi in char_changed[:a.top]:
        o, n = chars(old[(si, shi)]), chars(new[(si, shi)])
        print(f"  S{si}sh{shi:<3} 옛 {o or '(없음)'}")
        print(f"  {'':11}새 {n or '(없음)'}")
    print()
    print("STEMS=" + ",".join(f"S{si}sh{shi}" for si, shi in char_changed))


if __name__ == "__main__":
    main()
