"""카메라 지시 vs 산출 대조 평가 갤러리 — "대부분 정면" 관찰 검증용.

각 선정 샷에 대해 [최종 스틸] + [그 스틸을 만든 프롬프트의 CAMERA /
FRAMING SCALE 절 원문] 을 나란히 놓는다 — 지시가 측면·후면·부감인데
산출이 정면 응시로 수렴한 샷을 사람이 빠르게 걸러낼 수 있다.
판정 호출 0 = 비용 0.

사용: .venv/bin/python build_camera_eval_gallery.py
"""
from __future__ import annotations

import html as H
import json
import re
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PROJ = "c7e3b2e7-c545-4516-93b2-62a51a74d794"
EPI = "7c902020-4451-4967-9eb6-1e53c2b9b717"
RECIPE = ROOT / f"projects/{PROJ}/images/{EPI}/scene/recipe"
WEB = f"/projects/{PROJ}/images/{EPI}/scene/recipe"
OUT = ROOT / "artifact" / "20260812_카메라지시_대조_갤러리" / "index.html"


def tag_key(tag: str):
    s, sh = tag[1:].split("sh")
    return (int(s), int(sh))


def esc(x) -> str:
    return H.escape(str(x if x is not None else ""))


def extract_clause(prompt: str, head: str) -> str:
    """프롬프트에서 '- HEAD:' 로 시작하는 절 한 덩이를 떼어 온다."""
    m = re.search(rf"- {re.escape(head)}:\s*(.+?)(?=\n- [A-Z]|\n\n|$)",
                  prompt, re.S)
    return (m.group(1).strip() if m else "")


def main() -> None:
    records = json.loads((RECIPE / "records.json").read_text("utf-8"))
    rows = []
    n = 0
    cur = None
    for tag in sorted((k for k, v in records.items()
                       if "::" not in k and isinstance(v, dict)
                       and v.get("selected")), key=tag_key):
        r = records[tag]
        p = r.get("prompt") or ""
        cam = extract_clause(p, "CAMERA")
        scale = extract_clause(p, "FRAMING SCALE")
        si = tag_key(tag)[0]
        if si != cur:
            cur = si
            rows.append(f"<h2>S{si}</h2>")
        n += 1
        rows.append(
            f"<section class='row' id='{tag}'>"
            f"<div class='pane'><img loading='lazy' src='{WEB}/{tag}_sel.png'>"
            f"</div>"
            f"<div class='meta'><h3>{tag} <small>선정 {esc(r.get('selected'))}"
            f"</small></h3>"
            f"<p class='k'>FRAMING SCALE</p><p>{esc(scale) or '<i>절 없음</i>'}</p>"
            f"<p class='k'>CAMERA (이 스틸을 만든 지시 원문)</p>"
            f"<p>{esc(cam) or '<i>절 없음</i>'}</p></div></section>")

    html = (
        "<meta charset=\"utf-8\">\n"
        "<title>카메라 지시 vs 산출 대조 (사랑했지만 215)</title>\n"
        "<style>body{font-family:sans-serif;margin:16px;background:#111;"
        "color:#ddd}h2{color:#8cf;margin:24px 0 6px}"
        ".row{display:flex;gap:14px;border-top:1px solid #333;padding:12px 0}"
        ".pane img{width:640px;max-width:52vw;border-radius:6px}"
        ".meta{flex:1;font-size:13px;line-height:1.55}"
        ".meta h3{margin:0 0 6px}.meta small{color:#7a7}"
        ".k{color:#fa3;font-size:11px;margin:10px 0 2px;letter-spacing:.5px}"
        "p{margin:2px 0}</style>\n"
        f"<h1>카메라 지시 vs 산출 — {n}샷</h1>"
        "<p>보는 법: 오른쪽 지시(측면·비스듬·부감·시선 대상)와 왼쪽 산출을"
        " 대조 — 지시와 달리 인물이 렌즈 정면을 보거나 도열해 있으면 그"
        " 샷이 '정면 수렴' 사례다.</p>"
        + "\n".join(rows))
    OUT.parent.mkdir(parents=True, exist_ok=True)
    OUT.write_text(html, "utf-8")
    print(f"완료: {n}샷 → {OUT}")


if __name__ == "__main__":
    main()
