#!/usr/bin/env python3
"""육안 판정 갤러리 — **불이 켜져 있는가** 하나만 묻는다.

왜 작은가: 이번 판의 물음은 하나다. 시나리오의 「형광등이 한 번 깜빡인다」가
그림에서도 켜진 상태로 남았는가. 컷마다 **이미지 · 그 이미지를 실제로 만든
프롬프트(`image_asset.prompt_used`) · 조명 구절**을 나란히 놓으면 눈으로 갈린다.

★수치로 「해소됐다」고 말하지 않는다. 텍스트 층은 이미 쟀고
(scene_detail 조명 왜곡 0/6), 여기서 볼 것은 **그림**이다.

★프롬프트는 DB 에 남은 `prompt_used` 를 쓴다 — 재구성하면 실제로 나간 것과
달라질 수 있다.

usage: build_light_gallery.py <project_id> <episode_id>
"""
import html as H
import re
import shutil
import sys
from pathlib import Path

sys.path.insert(0, "/Users/manta/Documents/Projects/TheRoad-I1/scratchpad")
import _opik_env  # noqa: E402,F401  ★cwd 를 backend 로 고정 (설정 로드)

from app.core.database import SessionLocal    # noqa: E402
from sqlalchemy import text                   # noqa: E402

ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
PROJ, EPI = sys.argv[1], sys.argv[2]
OUT = ROOT / "artifact" / "20260825_light_gallery"

LIT = re.compile(r"(lit by|flicker\w*|waver\w*|glow\w*|overhead[^.,;]{0,24}light|"
                 r"fluorescent[^.,;]{0,44})", re.I)
DARK = re.compile(r"(goes? out|gone dark|black-?out|power cut|switched? off|"
                  r"unlit|extinguish\w*|pitch dark)", re.I)


def mark(t):
    t = H.escape(t or "")
    t = DARK.sub(lambda m: f'<b class="bad">{m.group(0)}</b>', t)
    t = LIT.sub(lambda m: f'<b class="ok">{m.group(0)}</b>', t)
    return t


def main():
    db = SessionLocal()
    rows = db.execute(text("""
        SELECT a.id, a.file_path, a.prompt_used, a.asset_type, a.status,
               s.scene_index, s.shot_index
        FROM image_asset a
        LEFT JOIN scene_still s ON s.id = a.still_id
        WHERE a.episode_id = :e AND a.still_id IS NOT NULL
        ORDER BY s.scene_index NULLS LAST, s.shot_index NULLS LAST, a.created_at
    """), {"e": EPI}).mappings().all()

    if not rows:
        # 아직 스틸이 안 붙었으면 파일 시스템의 scene/ 를 그대로 보여 준다.
        sdir = ROOT / "projects" / PROJ / "images" / EPI / "scene"
        pngs = sorted(sdir.glob("*.png")) if sdir.exists() else []
        print(f"still_id 붙은 image_asset 0건 — scene/ 의 {len(pngs)}장으로 대체",
              file=sys.stderr)
        rows = [{"id": p.stem, "file_path": str(p.relative_to(ROOT)),
                 "prompt_used": None, "asset_type": "scene", "status": "",
                 "scene_index": None, "shot_index": None} for p in pngs]

    OUT.mkdir(parents=True, exist_ok=True)
    (OUT / "img").mkdir(exist_ok=True)

    cards, copied, lit_n, dark_n = [], 0, 0, 0
    for r in rows:
        src = ROOT / r["file_path"]
        img = '<div class="noimg">이미지 파일 없음</div>'
        if src.exists():
            dst = OUT / "img" / src.name
            if not dst.exists():
                shutil.copy2(src, dst)
            copied += 1
            img = f'<img loading="lazy" src="img/{H.escape(src.name)}">'
        p = r["prompt_used"] or ""
        if DARK.search(p):
            v, cls = "★꺼짐으로 읽히는 말", "bad"
            dark_n += 1
        elif LIT.search(p):
            v, cls = "켜짐 표현 있음", "ok"
            lit_n += 1
        else:
            v, cls = ("조명 언급 없음" if p else "프롬프트 기록 없음"), "na"
        label = (f"S{r['scene_index']}sh{r['shot_index']}"
                 if r["scene_index"] is not None else str(r["id"])[:8])
        body = (f"<pre>{mark(p)}</pre>" if p else
                '<div class="noimg">프롬프트가 DB 에 안 남았다 — 판정은 그림으로만</div>')
        cards.append(f"""
<section class="card">
  <h2>{H.escape(label)} <span class="v {cls}">{v}</span>
      <small>{H.escape(r['asset_type'] or '')}</small></h2>
  <div class="row">{img}{body}</div>
</section>""")

    html = f"""<meta charset="utf-8">
<title>불이 켜져 있는가</title>
<style>
body{{margin:0;background:#14181a;color:#e6e7e4;
 font:15px/1.7 ui-sans-serif,-apple-system,"Apple SD Gothic Neo",sans-serif}}
.wrap{{max-width:1180px;margin:0 auto;padding:36px 24px 80px}}
h1{{font:600 30px/1.2 ui-serif,Georgia,serif;margin:0 0 6px}}
.dek{{color:#9aa0a5;margin:0 0 22px;max-width:72ch}}
.card{{border:1px solid #2a2f31;border-radius:10px;margin:0 0 18px;
 overflow:hidden;background:#191d1f}}
h2{{font:600 15px/1 ui-monospace,Menlo,monospace;margin:0;padding:13px 18px;
 border-bottom:1px solid #2a2f31;background:#1e2325;display:flex;gap:12px;
 align-items:center;flex-wrap:wrap}}
h2 small{{color:#6a706d;font-weight:400;margin-left:auto}}
.v{{font:600 11px/1 ui-monospace,Menlo,monospace;padding:4px 8px;
 border-radius:4px;letter-spacing:.05em}}
.v.ok{{background:#17302c;color:#6fbfab}} .v.bad{{background:#2e1e16;color:#e08a5f}}
.v.na{{background:#22282a;color:#8e9491}}
.row{{display:grid;grid-template-columns:minmax(0,460px) 1fr}}
@media(max-width:820px){{.row{{grid-template-columns:1fr}}}}
img{{width:100%;height:auto;display:block;border-right:1px solid #2a2f31}}
.noimg{{padding:56px 20px;text-align:center;color:#6a706d}}
pre{{margin:0;padding:18px;white-space:pre-wrap;word-break:break-word;
 font:13px/1.72 ui-monospace,Menlo,monospace;color:#bfc3c0;overflow-x:auto}}
b.ok{{color:#6fbfab;font-weight:650}} b.bad{{color:#e08a5f;font-weight:650}}
code{{background:#22282a;padding:1px 5px;border-radius:3px;font-size:13px}}
</style>
<div class="wrap">
<h1>불이 켜져 있는가</h1>
<p class="dek">시나리오는 「형광등이 <b class="ok">한 번 깜빡인다</b>」라고 적었다.
상류(<code>shot_validator</code>)가 그것을 「꺼졌다」로 바꾸던 것을 고쳤고,
텍스트 층에서는 배경 도면과 스틸 프롬프트가 모두 켜진 상태로 돌아왔다.
<b>여기서 볼 것은 그림이다</b> — 프롬프트가 맞다고 그림도 맞은 것은 아니다.</p>
<p class="dek">자산 {len(rows)}건 · 이미지 {copied}장 ·
켜짐 표현 {lit_n} · <b class="bad">꺼짐으로 읽히는 말 {dark_n}</b>.
초록은 켜짐을 말하는 구절, 주황은 꺼짐으로 읽히는 말이다.</p>
{''.join(cards)}
</div>"""
    (OUT / "index.html").write_text(html, encoding="utf-8")
    print(f"{OUT}/index.html · 자산 {len(rows)} · 이미지 {copied}장 · "
          f"켜짐 {lit_n} · 꺼짐 {dark_n}")


if __name__ == "__main__":
    main()
