#!/usr/bin/env python3
"""cine 변환 전/후 육안 대조 — `_sel` vs `_cine` (2026-08-29).

## 왜

PR #43 이 판정 단계 ④~⑥ 을 껐다. cine 변환은 그 스위치와 별개로 돌고
있고, **무검증으로 최종 이미지를 정한다**(#67 에서 「구도·장소를 통째로
바꾼다」로 잡힌 자리다). 이 판이 실제로 무엇을 바꿨는지는 눈으로 봐야
읽힌다 — 수치로는 안 잡힌다.

여기서 나란히 놓는 것:
  · `_sel`  = 선정 판정이 고른 원본
  · `_cine` = 그 위에 cine 변환을 태운 최종
  · 샷 텍스트 · 프롬프트가 못박은 FRAMING SCALE
  · 그 샷의 provenance(무엇이 변환의 직접 입력이었나)

★기록·파일에서만 읽는다 — 유료 호출 0.

usage: build_cine_acceptance.py <project_id> <episode_id> [출력디렉토리]
"""
from __future__ import annotations

import html
import json
import pathlib
import re
import subprocess
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
import _db  # noqa: E402
ROOT = pathlib.Path(__file__).resolve().parents[3]


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


def _shot_texts(episode_id: str) -> dict:
    """샷 텍스트 SOT — `scene_still.shot_description`.

    ★접속 정보는 설정에서만 온다(`_db`) — 비밀번호를 도구에 안 적는다.
    """
    out = {}
    for si, shi, txt in _db.rows(
            "SELECT scene_index, shot_index, "
            "replace(coalesce(shot_description,''), E'\\n',' ') "
            "FROM scene_still WHERE episode_id = :ep",
            {"ep": episode_id}):
        out[f"S{si}sh{shi}"] = txt
    return out


def main() -> int:
    argv = [a for a in sys.argv[1:] if not a.startswith("--")]
    if len(argv) < 2:
        print(__doc__)
        return 2
    project_id, episode_id = argv[0], argv[1]
    outdir = pathlib.Path(argv[2]) if len(argv) > 2 else (
        ROOT / "artifact" / "20260829_cine_acceptance")

    from app.core.config import settings

    rdir = (pathlib.Path(settings.projects_dir) / project_id / "images"
            / episode_id / "scene" / "recipe")
    recs = json.loads((rdir / "records.json").read_text())
    texts = _shot_texts(episode_id)

    rows = []
    for tag in sorted(recs):
        r = recs.get(tag)
        if not isinstance(r, dict):
            continue
        sel, cine = rdir / f"{tag}_sel.png", rdir / f"{tag}_cine.png"
        if sel.is_file() and cine.is_file():
            rows.append((tag, r, sel, cine))
    if not rows:
        print("★내 조회로는 _sel/_cine 쌍을 못 찾았다 — 「없다」로 읽지 마라. "
              f"경로: {rdir}")
        return 1

    P: list[str] = ['<meta charset="utf-8">',
                    "<title>cine 변환 전·후</title>", """<style>
:root{color-scheme:dark}
body{background:#111;color:#ddd;margin:0;padding:28px 32px;max-width:1700px;
 font:15px/1.65 -apple-system,'Apple SD Gothic Neo','Noto Sans KR',sans-serif}
h1{font-size:25px;margin:0 0 6px} .n{color:#8a8a8a;font-size:13px}
h2{font-size:19px;margin:30px 0 6px;border-bottom:1px solid #333;
 padding-bottom:6px}
.shot{border:1px solid #2a2a2a;border-radius:9px;padding:16px;margin:20px 0;
 background:#161616}
.txt{color:#e8e8e8;margin:6px 0 10px;padding:10px 12px;background:#1c1c1c;
 border-left:3px solid #4a7;border-radius:4px}
.txt code{color:#ffd88a;font-size:13px}
.imgs{display:flex;gap:16px;flex-wrap:wrap;margin-top:12px}
.imgs figure{margin:0;flex:1 1 460px;max-width:700px}
.imgs img{width:100%;border-radius:6px;border:2px solid #333;display:block}
.imgs figcaption{font-size:13px;color:#999;margin-top:6px}
.warn{background:#2a1c14;border-left:3px solid #b86;padding:11px 13px;
 border-radius:4px;margin:14px 0}
</style>"""]
    P.append("<h1>cine 변환이 무엇을 바꿨나 — 전·후</h1>")
    P.append(f'<p class="n">에피소드 {esc(episode_id)} · 쌍 {len(rows)}개 · '
             "기록·파일에서만 읽음(유료 호출 0)</p>")
    P.append('<div class="warn">'
             "왼쪽 <b>_sel</b> 이 선정 판정이 고른 원본이고, 오른쪽 "
             "<b>_cine</b> 이 최종이다. cine 은 <b>판정을 거치지 않는다</b> — "
             "구도·장소가 통째로 바뀌어도 아무도 반려하지 않는다(#67). "
             "그래서 여기서 볼 것은 「예뻐졌나」가 아니라 "
             "<b>샷 텍스트가 못박은 것이 살아 있나</b>다."
             "</div>")

    for tag, r, sel, cine in rows:
        prompt = ((r.get("roll_prompts") or {}).get("A") or "")
        keys = [ln.strip() for ln in prompt.splitlines()
                if re.search(r"FRAMING SCALE|LOCATION \(lock\)|CARRIED STATE",
                             ln)]
        P.append('<div class="shot">')
        P.append(f'<h2 style="border:0;margin:0">{esc(tag)}'
                 f'<span class="n"> · 선정 {esc(r.get("selected"))}</span></h2>')
        if texts.get(tag):
            P.append(f'<div class="txt"><b>샷 텍스트</b><br>'
                     f"{esc(texts[tag])}</div>")
        if keys:
            P.append('<div class="txt"><b>프롬프트가 못박은 것</b><br>'
                     + "<br>".join(f"<code>{esc(k[:300])}</code>"
                                   for k in keys) + "</div>")
        P.append('<div class="imgs">')
        for f, cap in ((sel, "_sel — 판정이 고른 원본"),
                       (cine, "_cine — cine 변환 후 (최종)")):
            rel = f.resolve().relative_to(ROOT)
            P.append(f'<figure><a href="/{esc(rel)}" target="_blank">'
                     f'<img src="/{esc(rel)}" loading="lazy"></a>'
                     f"<figcaption>{esc(cap)}</figcaption></figure>")
        P.append("</div></div>")

    outdir.mkdir(parents=True, exist_ok=True)
    (outdir / "index.html").write_text("\n".join(P), encoding="utf-8")
    rel = (outdir / "index.html").resolve().relative_to(ROOT)
    ip = subprocess.run(["ipconfig", "getifaddr", "en0"],
                        capture_output=True, text=True).stdout.strip()
    print(f"만듦: {outdir/'index.html'} · 쌍 {len(rows)}개")
    print(f"주소: http://{ip}:8940/{rel}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
