"""손 절이 CAMERA 계약과 맞서는가 — 감사 1-C.

`handled_object_clause` 는 마지막 문장이 **"The framing stays on the
object."** 다. 같은 프롬프트의 CAMERA 절은 스스로를 「이 스틸의 구도
권위」라 선언한다. CAMERA 가 인물 중심(upper-body medium 등)을 말하는데
손 절이 물체 중심을 요구하면 모델은 둘 중 하나를 고른다.

그 절이 만들어진 이유는 **bg_only 샷에서 손이 누구 손인지**를 말하기
위해서였다(코드 주석). 그 목적에 프레이밍 문장은 필요 없다.

    $ .venv/bin/python tools/prompt_measure/audit_handled_vs_camera.py
"""
from __future__ import annotations

import json
import re
import sys
from collections import Counter
from pathlib import Path

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")

P_HANDLED = re.compile(r"^THE HAND THAT IS DOING THIS: (.+)$", re.M)
# ★**대소문자로 두 번 속았다.** v15 는 "…; the framing stays on the
#  object." (소문자), v17 은 "The framing stays on the object."
#  대문자만 잡는 패턴을 쓰니 v15 판이 통째로 0 으로 나왔다 —
#  「없다」가 아니라 「못 잡았다」였다. 대조에 **두 판 문안을 다**
#  넣어야 이 부류를 막는다.
P_FRAMING = re.compile(r"framing stays on the object", re.I)
# v15 만 있는, 인서트를 더 강하게 미는 구절
P_NOTHING_ELSE = re.compile(r"Nothing else of them needs to be in shot",
                            re.I)
# ★`CAMERA & FRAME (…):` 은 **머리말 뒤가 바로 개행**이다. 처음 쓴
#  `^CAMERA[^\n]*:(.+)$` 는 그 줄에서 매치에 실패해 **전 판 0건**이
#  나왔다 — 「없다」가 아니라 「못 잡았다」였다. 아래 `_camera_of` 가
#  양성 대조로 그것을 잡는다.
P_CAM_HEAD = re.compile(r"^CAMERA & FRAME\b[^\n]*$", re.M)
P_CAM_LINE = re.compile(r"^- CAMERA:\s*(.+)$", re.M)
P_CAM_SCALE = re.compile(r"^- FRAMING SCALE:\s*(.+)$", re.M)
P_NOPEOPLE = re.compile(r"NO PEOPLE")


def _prompts(rec):
    out = []

    def walk(v):
        if isinstance(v, str):
            if "THE HAND THAT IS DOING THIS" in v:
                out.append(v)
        elif isinstance(v, dict):
            for x in v.values():
                walk(x)
        elif isinstance(v, list):
            for x in v:
                walk(x)

    walk(rec)
    return out


_PC = """CAMERA & FRAME (framing authority; references give identity and place, not framing):
- CAMERA: a tight close-up on her face
- FRAMING SCALE: close-up
THE HAND THAT IS DOING THIS: … The framing stays on the object.
"""
# v15 실문안 — 소문자 갈래를 대조에 **반드시** 넣는다.
_PC15 = ("THE HAND THAT IS DOING THIS: … Nothing else of them needs to be"
         " in shot; the framing stays on the object.")


def main() -> int:
    # ★정규식이 **꺼져 있으면 0 이 「없다」로 읽힌다.** 처음 돌렸을 때
    #  실제로 그랬다 — 절 머리말 뒤가 개행이라 매치가 실패했다.
    for name, pat in (("CAMERA & FRAME", P_CAM_HEAD),
                      ("- CAMERA", P_CAM_LINE),
                      ("- FRAMING SCALE", P_CAM_SCALE),
                      ("framing 문장", P_FRAMING),
                      ("손 절", P_HANDLED)):
        if not pat.search(_PC):
            print(f"★대조 실패 — {name} 를 못 잡는다. 측정을 멈춘다.")
            return 1
    for name, pat in (("framing 문장(v15 소문자)", P_FRAMING),
                      ("Nothing else(v15)", P_NOTHING_ELSE)):
        if not pat.search(_PC15):
            print(f"★대조 실패 — {name} 를 못 잡는다. 측정을 멈춘다.")
            return 1
    print("■ 대조 — 일곱 패턴 모두 잡는다 (v15 소문자 갈래 포함) ✓\n")

    c = Counter()
    rows = []
    cams: "dict[str, list]" = {}
    for f in sorted(ROOT.glob("projects/*/images/*/scene/recipe/records.json")):
        try:
            data = json.loads(f.read_text(encoding="utf-8"))
        except Exception:
            continue
        data = data.get("data", data)
        proj = f.parts[-6][:8]
        n = n_frame = n_cam = n_person = 0
        shots = set()
        for key, rec in data.items():
            if not isinstance(rec, dict):
                continue
            for p in _prompts(rec):
                n += 1
                shots.add(key)
                framed = bool(P_FRAMING.search(p))
                cam = P_CAM_HEAD.search(p)
                person = not P_NOPEOPLE.search(p)
                n_frame += framed
                if P_NOTHING_ELSE.search(p):
                    c["nothing_else"] += 1
                n_cam += bool(cam)
                n_person += person
                # ★맞서는 자리 = **인물 샷**에서 둘이 함께 나간 것.
                #  무인 샷은 애초에 물체 중심이라 다툼이 없다.
                if framed and cam and person:
                    c["conflict"] += 1
                    _sc = P_CAM_SCALE.search(p)
                    _cl = P_CAM_LINE.search(p)
                    cams.setdefault(
                        (_sc.group(1).strip() if _sc else "(scale 없음)"),
                        []).append(
                        (proj, key, _cl.group(1).strip()[:90] if _cl else ""))
        if not n:
            continue
        rows.append((proj, len(shots), n, n_frame, n_cam, n_person))
        c["prompt"] += n
        c["frame"] += n_frame
        c["cam"] += n_cam
        c["person"] += n_person

    print("■ 손 절이 나간 프롬프트\n")
    print(f"  {'판':>9} {'샷':>5} {'프롬프트':>8} {'framing 문장':>12}"
          f" {'CAMERA 동반':>11} {'인물 샷':>7}")
    for r in sorted(rows, key=lambda x: -x[2]):
        print(f"  {r[0]:>9} {r[1]:>5} {r[2]:>8} {r[3]:>12} {r[4]:>11}"
              f" {r[5]:>7}")
    t = c["prompt"] or 1
    print(f"\n  합계 {c['prompt']}건")
    print(f"    framing 문장 동반  {c['frame']:>5} ({100.0*c['frame']/t:.0f}%)")
    print(f"    Nothing else(v15)  {c['nothing_else']:>5}"
          f" ({100.0*c['nothing_else']/t:.0f}%)")
    print(f"    CAMERA 동반        {c['cam']:>5} ({100.0*c['cam']/t:.0f}%)")
    print(f"    인물 샷            {c['person']:>5}"
          f" ({100.0*c['person']/t:.0f}%)")
    print(f"\n  ★맞서는 자리 = 인물 샷 + CAMERA + framing 문장"
          f"  **{c['conflict']}건** ({100.0*c['conflict']/t:.0f}%)")

    print("\n■ 그때 CAMERA 가 뭐라고 했나 — 많이 나온 순")
    for scale, xs in sorted(cams.items(), key=lambda x: -len(x[1]))[:10]:
        print(f"\n    ×{len(xs):<4} FRAMING SCALE: {scale}")
        print(f"          {xs[0][0]}/{xs[0][1]}: {xs[0][2]}")
    return 0


if __name__ == "__main__":
    sys.exit(main())
