"""`lighting_mood` 한 칸에 무엇이 들어 있나 (2026-08-27, 감사 1-H).

Codex 판정: 이 필드는 **장면 사실**(광원이 있다·켜져 있다·어디에)과
**연출**(대비·색조·공기)을 한 문장에 섞는다. 그래서 절을 통째로 옮기면
광원 자체가 사라진다.

값을 갈라 받으려면(`lighting_facts_en` / `lighting_treatment_en`) 먼저
**실제로 무엇이 쓰여 있는지** 봐야 한다 — 안 보고 스키마를 가르면 저작
모델이 못 채우는 칸을 만든다.

★**코드가 의미로 쪼개지 않는다.** 여기서 세는 것은 **얼마나 섞여 있나**를
알기 위한 것이지, 이 셈으로 프로덕션이 가르는 것이 아니다.

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

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

ROOT = pathlib.Path(__file__).resolve().parents[3]

# 광원이 **있다**는 것을 말하는 낱말 — 이미지에 실체로 그려지는 것
_SOURCE = re.compile(
    r"\b(lamp|lamps|streetlamp|streetlight|bulb|bulbs|fluorescent|neon|"
    r"candle|candles|fire|flame|torch|flashlight|headlight|headlights|"
    r"window|windows|skylight|monitor|screen|sun|sunlight|moonlight|"
    r"daylight|lantern|sconce|spotlight|floodlight)\b", re.I)
# 광원의 **상태** — 켜짐/꺼짐/깜빡임
_STATE = re.compile(
    r"\b(lit|unlit|on|off|switched|flickers?|flickering|glowing|glows|"
    r"burning|burns|dark|darkened|extinguished)\b", re.I)
# **연출** — 빛을 어떻게 보이게 하나
_TREAT = re.compile(
    r"\b(contrast|falloff|haze|hazy|diffuse|diffused|soft|softly|harsh|"
    r"moody|muted|desaturated|saturated|warm|cool|golden|sickly|grim|"
    r"grimy|palette|tone|toned|tint|tinted|grade|graded|cinematic|"
    r"atmospheric|bleak|somber|melancholy|oppressive|serene)\b", re.I)


def main() -> None:
    vals: list[tuple[str, str]] = []
    for cp in sorted((ROOT / "projects").glob(
            "*/checkpoints/episodes/*/shot_staging/manifest*.json")):
        try:
            d = json.loads(cp.read_text(encoding="utf-8"))
        except Exception:
            continue
        for sh in _walk_shots(d):
            v = str(sh.get("lighting_mood") or "").strip()
            if v:
                vals.append((cp.parent.parent.name[:8], v))

    if not vals:
        print("  shot_staging 체크포인트에서 lighting_mood 를 못 찾았다")
        print("  — 경로/모양을 먼저 확인할 것")
        return

    n = len(vals)
    both = src = trt = neither = 0
    lens = []
    for _, v in vals:
        s = bool(_SOURCE.search(v)) or bool(_STATE.search(v))
        t = bool(_TREAT.search(v))
        lens.append(len(v))
        if s and t:
            both += 1
        elif s:
            src += 1
        elif t:
            trt += 1
        else:
            neither += 1

    print(f"■ lighting_mood 실측 — {n} 칸")
    print(f"  평균 {sum(lens)//n}자 · 최장 {max(lens)}자 · 최단 {min(lens)}자\n")
    print(f"  ★사실+연출이 한 문장   {both:5d}  ({both*100//n}%)")
    print(f"   사실만               {src:5d}  ({src*100//n}%)")
    print(f"   연출만               {trt:5d}  ({trt*100//n}%)")
    print(f"   둘 다 안 잡힘        {neither:5d}  ({neither*100//n}%)")
    print("\n  ── 섞인 것 표본 ──")
    shown = 0
    for tag, v in vals:
        if _SOURCE.search(v) and _TREAT.search(v):
            print(f"   [{tag}] {v[:150]}")
            shown += 1
            if shown >= 8:
                break
    print("\n  ── 둘 다 안 잡힌 것 표본 (낱말표 밖) ──")
    shown = 0
    for tag, v in vals:
        if not (_SOURCE.search(v) or _STATE.search(v) or _TREAT.search(v)):
            print(f"   [{tag}] {v[:150]}")
            shown += 1
            if shown >= 5:
                break

    words = Counter()
    for _, v in vals:
        words.update(w.lower() for w in re.findall(r"[A-Za-z]{4,}", v))
    print("\n  ── 가장 잦은 낱말 20 (낱말표가 놓친 축을 찾으려고) ──")
    print("   " + " · ".join(f"{w}({c})" for w, c in words.most_common(20)))


def _walk_shots(d):
    """manifest 모양이 판마다 달라 shots 를 넓게 훑는다."""
    if isinstance(d, dict):
        if "shots" in d and isinstance(d["shots"], list):
            for sh in d["shots"]:
                if isinstance(sh, dict):
                    yield sh
        for v in d.values():
            yield from _walk_shots(v)
    elif isinstance(d, list):
        for v in d:
            yield from _walk_shots(v)


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