#!/usr/bin/env python3
"""shot_validator 프롬프트를 **표에 없는 변화**로 태워 본다 (unseen probe).

왜 필요한가 — v8 의 대응표에 「닫힌 목록 아님」이라고 적어 두어도, 표에 없는
변화(회전·진동·색 변화)를 만나면 규칙 밖으로 볼 위험이 있다. v9 는 사건
종류가 아니라 **위상 보존 연산자**(대상·속성·위상·방향·정도 다섯 칸)로
바꿨는데, 그것이 표 밖에서도 서는지 확인해야 한다.

★프로덕션 코드 경로를 그대로 쓴다 — `ShotValidatorStep._validate_scene`.
측정 도구가 프로덕션보다 단순하면 있는 문제를 숨긴다.

usage: probe_shot_validator.py [반복횟수]   (기본 3)
"""
import json
import re
import sys

sys.path.insert(0, "/Users/manta/Documents/Projects/TheRoad-I1/scratchpad")
from _opik_env import opik_target  # noqa: E402,F401  ★cwd 를 backend 로 고정

from app.core.database import SessionLocal          # noqa: E402
from app.core.steps.shot_validator_step import ShotValidatorStep  # noqa: E402
from app.modules.prompt_loader import (             # noqa: E402
    load_prompt, load_schema, _list_module_versions)

N = int(sys.argv[1]) if len(sys.argv) > 1 else 3

# 각 probe = (이름, 씬 원문, shot description, 지켜야 할 것, 나오면 안 되는 것)
# ★시나리오 의존 없음 — 어떤 작품에도 없는 일반 사물만 쓴다.
# ★**재작성을 강제하는 모양**으로 쓴다. 실제 실패 입력이 「깜빡이며 일순간
#   어두워지는」처럼 두 동작이 이어진 꼴이었고, 재작성이 일어나야 freeze
#   연산자가 작동한다. 이미 단일 순간인 description 을 주면 모델이 「고칠 것
#   없음」을 돌려주고, 그러면 **아무것도 시험하지 않은 채 초록이 뜬다**
#   (첫 판에서 실제로 그렇게 5/5 가짜 초록이 나왔다).
PROBES = [
    ("회전(정도 명시)",
     "천장의 낡은 선풍기가 천천히 돈다. 아래에서 한 사람이 그것을 올려다본다.",
     "천장 선풍기 날개가 천천히 돌며 아래로 그림자를 드리우는 가운데 "
     "인물이 고개를 들어 그것을 올려다보는 상반신.",
     r"도는|돌고|돌아가|회전", r"멈[춘춰]|정지|멎은|빠르게 도"),
    ("진동(위상=진행)",
     "탁자 위에 놓인 기기가 진동한다. 곁에 앉은 사람이 그것을 내려다본다.",
     "탁자 위의 기기가 진동하며 조금씩 자리를 옮기는 동안 곁의 인물이 "
     "손을 뻗어 그것을 내려다보는 구도.",
     r"진동|떨리|흔들", r"조용|고요|정지한 기기"),
    ("색 변화(방향 보존)",
     "건널목의 신호가 초록에서 노랑으로 바뀐다. 한 사람이 그 앞에 서 있다.",
     "건널목 신호가 초록에서 노랑으로 바뀌며 주변이 물드는 사이 "
     "인물이 그 앞에 멈춰 서는 전경.",
     r"노랑|노란", r"빨[강간]|초록으로 바뀐|파란"),
    ("열림 — 정도가 명시됨",
     "문이 살짝 열린다. 그 틈으로 바깥의 빛이 들어온다.",
     "문이 살짝 열리며 그 틈으로 바깥 빛이 들어와 바닥에 선을 긋는 순간의 실내.",
     r"살짝|조금|틈|가느다", r"반쯤|활짝|완전히 열린|닫힌 문"),
    ("완료 위상은 완료로",
     "복도의 불이 나갔다. 사람들이 어둠 속에 서 있다.",
     "복도의 불이 나가며 어둠이 내려앉는 가운데 사람들이 그 자리에 "
     "멈춰 서 있는 전경.",
     r"나간|꺼[진져]|어둠|어두", r"깜빡|명멸|흔들리는 빛|켜진 채"),
]


def run():
    db = SessionLocal()
    step = ShotValidatorStep(
        step_id="shot_validator",
        project_id="probe", episode_id="probe", db=db,
        project_config={},
    )
    system = load_prompt("shot_validator", "system", db=db)
    schema = load_schema("shot_validator", "validator_schema", db=db)
    ver = _list_module_versions("shot_validator")[0]
    print(f"shot_validator {ver} · system {len(system):,}자 · {N}회 반복\n")

    tally = {p[0]: {"지킴": 0, "깨짐": 0, "판단불가": 0, "재작성없음": 0} for p in PROBES}
    for i in range(1, N + 1):
        print(f"── {i}회차")
        for name, scene_text, desc, keep, ban in PROBES:
            scene = {"scene_index": 1,
                     "shots": [{"shot_index": 1, "description": desc}]}
            try:
                out, nchanged = step._validate_scene(
                    scene, scene_text, "", system, schema, None)
            except Exception as exc:
                print(f"   {name:<18} ★호출 실패 {type(exc).__name__}: {exc}")
                tally[name]["판단불가"] += 1
                continue
            sh = (out.get("shots") or [{}])[0]
            rd = sh.get("description", "") or desc
            # ★재작성이 안 일어났으면 freeze 연산자를 **아무것도 안 태운** 것이다.
            #   그것을 「지킴」으로 세면 가짜 초록이 된다.
            if nchanged == 0:
                tally[name]["재작성없음"] += 1
                print(f"   {name:<18} ⚠재작성 없음 — 이 probe 는 연산자를 안 태웠다")
                print(f"      {rd[:120]}")
                continue
            bad = re.search(ban, rd)
            good = re.search(keep, rd)
            if bad:
                verdict, mark = "깨짐", f"★깨짐({bad.group(0)})"
            elif good:
                verdict, mark = "지킴", "✅지킴"
            else:
                verdict, mark = "판단불가", "? 판단불가"
            tally[name][verdict] += 1
            print(f"   {name:<18} {mark:<14}")
            print(f"      원본: {sh.get('original_description', desc)[:110]}")
            print(f"      고침: {rd[:110]}")
            print(f"      why : {(sh.get('validator_reason') or '')[:110]}")
        print()

    print("── 합계 ──")
    for name in tally:
        t = tally[name]
        print(f"  {name:<18} 지킴 {t['지킴']} · ★깨짐 {t['깨짐']} · "
              f"판단불가 {t['판단불가']} · ⚠재작성없음 {t['재작성없음']}")
    return tally


if __name__ == "__main__":
    run()
