"""무인 샷에 **사람 절**이 함께 나가는가 — 감사 1-B 의 근거를 다시 잰다.

감사 보고서는 CARRIED 하나만 지목했다. 그런데 조립부를 읽으면 `bg_only`
가 끄는 것은 `pose_clauses` 뿐이고 **MOVEMENT · FIGURES · CARRIED 셋은
그대로 나간다**(`still_recipe_service.py:3500-3507`). 그중 FIGURES 는
이름부터 사람 크기·깊이를 말한다.

그래서 셋을 **각각** 센다. 무엇이 얼마나 함께 나가는지를 보고 나서
계약을 정한다 — 한 칸만 보고 고치면 나머지 둘이 같은 자리에서 남는다.

    $ .venv/bin/python tools/prompt_measure/audit_person_clauses_in_bgonly.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")

# 조립부가 내는 절 머리말 그대로. **문구를 바꾸면 여기도 바꾼다** —
# 이 도구는 나간 글자를 찾는 것이지 뜻을 재는 것이 아니다.
CLAUSES = {
    "MOVEMENT": re.compile(r"^MOVEMENT \(follow exactly\): (.+)$", re.M),
    "FIGURES": re.compile(
        r"^FIGURES — size & depth \(follow exactly\): (.+)$", re.M),
    "CARRIED": re.compile(r"^CARRIED STATE \(persist exactly[^)]*\): (.+)$",
                          re.M),
}
# 무인 조항 — 팩 문안이 판마다 다를 수 있어 **두 모양**을 다 본다.
NO_PEOPLE = re.compile(r"NO PEOPLE|no people|No people", re.M)


def _prompts(rec: dict):
    """한 샷 기록에서 **나간 프롬프트 문자열**을 모두 꺼낸다.

    후보가 여럿이고 체인 판이 따로 있어, 한 칸만 보면 놓친다.
    """
    out = []

    def walk(v):
        if isinstance(v, str):
            if "CARRIED STATE" in v or "NO PEOPLE" in v or "MOVEMENT (" 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


def main() -> int:
    files = sorted(ROOT.glob("projects/*/images/*/scene/recipe/records.json"))
    if not files:
        print("records 를 못 찾았다"); return 1

    grand = Counter()
    rows = []
    # ★고유 문안을 모은다 — 같은 샷의 후보가 여럿이라 프롬프트를 세면
    #  한 샷이 서너 번 잡힌다. **무엇을 고칠지**는 문안 단위로 봐야 한다.
    uniq: "dict[str, list]" = {}
    for f in files:
        try:
            data = json.loads(f.read_text(encoding="utf-8"))
        except Exception:
            continue
        data = data.get("data", data)
        n_prompt = 0
        n_unmanned = 0
        shots_unmanned = set()
        shots_hit = {k: set() for k in CLAUSES}
        hit = Counter()
        for key, rec in data.items():
            if not isinstance(rec, dict):
                continue
            for p in _prompts(rec):
                n_prompt += 1
                if not NO_PEOPLE.search(p):
                    continue
                n_unmanned += 1
                shots_unmanned.add(key)
                for name, pat in CLAUSES.items():
                    m = pat.search(p)
                    if m:
                        hit[name] += 1
                        shots_hit[name].add(key)
                        if name == "CARRIED":
                            uniq.setdefault(m.group(1).strip(), []).append(
                                (f.parts[-6][:8], key))
        if not n_unmanned:
            continue
        rows.append((f.parts[-6][:8], n_prompt, n_unmanned,
                     len(shots_unmanned), len(shots_hit["CARRIED"]),
                     hit["MOVEMENT"], hit["FIGURES"], hit["CARRIED"]))
        grand["prompt"] += n_prompt
        grand["unmanned"] += n_unmanned
        grand["shot_unmanned"] += len(shots_unmanned)
        grand["shot_carried"] += len(shots_hit["CARRIED"])
        for k in CLAUSES:
            grand[k] += hit[k]

    print("■ 무인 조항이 든 프롬프트에 사람 절이 함께 나간 건수")
    print("  ★두 단위로 센다 — 같은 샷의 후보가 여럿이라 프롬프트를 세면"
          " 한 샷이 서너 번 잡힌다.\n")
    print(f"  {'판':>9} {'프롬프트':>8} {'무인':>6} {'무인샷':>7}"
          f" {'CARRIED샷':>10} {'MOVE':>5} {'FIG':>5} {'CARRIED':>8}")
    for r in sorted(rows, key=lambda x: -x[2]):
        print(f"  {r[0]:>9} {r[1]:>8} {r[2]:>6} {r[3]:>7} {r[4]:>10}"
              f" {r[5]:>5} {r[6]:>5} {r[7]:>8}")
    u = grand["unmanned"] or 1
    su = grand["shot_unmanned"] or 1
    print(f"\n  합계  프롬프트 {grand['prompt']}건 · 무인 {grand['unmanned']}건"
          f" · 무인 샷 {grand['shot_unmanned']}개")
    for k in CLAUSES:
        print(f"    {k:<9} 프롬프트 {grand[k]:>5}건 ({100.0*grand[k]/u:.0f}%)")
    print(f"    CARRIED   **샷** {grand['shot_carried']}개"
          f" ({100.0*grand['shot_carried']/su:.0f}%)")

    print(f"\n■ 고유 CARRIED 문안 {len(uniq)}개"
          f" — 여기서 「사람의 몸을 말하는가」를 가른다")
    out = ROOT / "backend/tools/prompt_measure/_carried_unmanned.json"
    out.write_text(json.dumps(
        [{"text": t, "shots": s[:5], "n": len(s)}
         for t, s in sorted(uniq.items(), key=lambda x: -len(x[1]))],
        ensure_ascii=False, indent=1), encoding="utf-8")
    print(f"  → {out.relative_to(ROOT)}")
    for t, s in sorted(uniq.items(), key=lambda x: -len(x[1]))[:12]:
        print(f"    ×{len(s):<3} {s[0][0]}/{s[0][1]}: {t[:130]}")
    return 0


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