#!/usr/bin/env python3
"""grok 2.0 전환 카나리아 23샷 — 전 롤 갤러리 (2026-08-13).

사용자 지시: 선정 못 받은 후보까지 전부 표시. 샷마다
[직전본(nb2)] [a 롤] [b 롤(구도 변주)] [fix] 를 같은 크기로 나란히,
최종 선정본과 바이트 일치하는 후보에 "선정" 뱃지(바이트 대조 — 메타
해석이 아니라 실물 근거). 산출은 artifact/ 규약, 서빙은 8940 서브패스.
"""
from __future__ import annotations

import html
import json
import re
import shutil
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
PID = "c7e3b2e7-c545-4516-93b2-62a51a74d794"
EID = "7c902020-4451-4967-9eb6-1e53c2b9b717"
RECIPE = ROOT / "projects" / PID / "images" / EID / "scene" / "recipe"
PREV_DIR = ROOT / "artifact" / "20260812_QK카나리아_대조" / "new"
OUT = ROOT / "artifact" / "20260813_grok23샷_전롤갤러리"
IMG = OUT / "img"

TARGET_SCENES = {1, 4, 5, 9, 37, 39, 56, 64, 65}


def shot_scene(tag: str) -> int:
    m = re.match(r"S(\d+)sh\d+$", tag)
    return int(m.group(1)) if m else -1


def main() -> None:
    IMG.mkdir(parents=True, exist_ok=True)
    records = json.loads((RECIPE / "records.json").read_text(encoding="utf-8"))
    tags = sorted(
        (t for t in records if shot_scene(t) in TARGET_SCENES
         and (RECIPE / f"{t}_sel.png").is_file()),
        key=lambda t: (shot_scene(t), int(t.split("sh")[1])),
    )
    rows = []
    for tag in tags:
        sel = RECIPE / f"{tag}_sel.png"
        sel_bytes = sel.read_bytes()
        cells = []
        prev = PREV_DIR / f"{tag}_sel.png"
        if prev.is_file():
            shutil.copy2(prev, IMG / f"{tag}_prev.png")
            cells.append((f"img/{tag}_prev.png", "직전본 (nb2 카나리아)", ""))
        for роль in ("a", "b", "fix"):
            p = RECIPE / f"{tag}_{роль}.png"
            if not p.is_file():
                continue
            shutil.copy2(p, IMG / f"{tag}_{роль}.png")
            name = {"a": "a 롤 (기준)", "b": "b 롤 (구도 변주)",
                    "fix": "fix (수정본)"}[роль]
            badge = "선정" if p.read_bytes() == sel_bytes else ""
            cells.append((f"img/{tag}_{роль}.png", name, badge))
        if not any(b for _, _, b in cells):
            # 선정본이 어느 후보와도 바이트 불일치(후처리 등) — 선정본
            # 자체를 별도 셀로 노출해 숨김 0 을 보장한다.
            shutil.copy2(sel, IMG / f"{tag}_sel.png")
            cells.append((f"img/{tag}_sel.png", "최종 선정본", "선정"))
        rec = records.get(tag) or {}
        prompt = str(rec.get("prompt") or "")
        m = re.search(r"SHOT TEXT \(authoritative, Korean\): (.+)", prompt)
        shot_text = m.group(1).strip() if m else ""
        ref_mode = str(rec.get("ref_mode") or "")
        cell_html = "".join(
            '<div class="cell{sel}"><a href="{src}" target="_blank">'
            '<img loading="lazy" src="{src}"></a>'
            '<div class="cap">{name}{badge}</div></div>'.format(
                sel=" win" if badge else "", src=html.escape(src),
                name=html.escape(name),
                badge=(' <span class="badge">선정</span>' if badge else ""),
            )
            for src, name, badge in cells
        )
        rows.append(
            f'<h2>{html.escape(tag)} <span class="mode">'
            f'{html.escape(ref_mode)}</span></h2>'
            f'<p class="shot">{html.escape(shot_text)}</p>'
            f'<div class="row">{cell_html}</div>'
        )

    page = (
        '<meta charset="utf-8">\n'
        "<title>grok 2.0 카나리아 23샷 — 전 롤 갤러리</title>\n"
        "<style>\n"
        "body{margin:0;padding:24px;background:#14161a;color:#e8e8e8;"
        "font:15px/1.6 -apple-system,'Apple SD Gothic Neo',sans-serif}\n"
        "h1{font-size:20px} h2{font-size:16px;margin:34px 0 2px;"
        "color:#ffd479} .mode{font-size:12px;color:#8a94a6;font-weight:400}\n"
        ".shot{font-size:13px;color:#aab;margin:2px 0 8px}\n"
        ".row{display:flex;gap:10px;flex-wrap:wrap}\n"
        ".cell{flex:1 1 260px;max-width:460px}\n"
        ".cell img{width:100%;border:1px solid #3a3f47;border-radius:4px;"
        "cursor:zoom-in}\n"
        ".cell.win img{border:3px solid #8aff9e}\n"
        ".cap{font-size:12.5px;color:#aab;margin-top:3px}\n"
        ".badge{background:#8aff9e;color:#10251a;border-radius:3px;"
        "padding:0 6px;font-weight:700;font-size:12px}\n"
        "</style>\n"
        "<h1>grok 2.0 전환 카나리아 — 9씬 23샷 전 롤 (2026-08-13 05:15, "
        "실패 0)</h1>\n"
        "<p>구성: 최종 스틸=grok-imagine-image-2.0 + 컴팩트 조립(v17) + "
        "시네마틱 마감 절 + 2롤 ab(b=구도 변주) + 카메라 문법 flow v5/"
        "staging v22 + QK 판정. 각 샷: 직전본(nb2) → a 롤 → b 롤 → fix — "
        "<b>선정 못 받은 후보 포함 전부 표시</b>, 초록 테두리=최종 선정.</p>\n"
        + "\n".join(rows)
    )
    (OUT / "index.html").write_text(page, encoding="utf-8")
    n_img = len(list(IMG.glob("*.png")))
    print(f"갤러리 빌드 완료: 샷 {len(tags)}개, 이미지 {n_img}장 → {OUT}")


if __name__ == "__main__":
    main()
