#!/usr/bin/env python3
"""W22 fresh E2E 갤러리 빌드 — 캐논 자산 + 직행 스틸 + 전체 스틸.

산출: scratchpad/forest_exp/w22b_e2e.html (+ out/w22b_e2e/ 이미지 복사)
"""
import html
import json
import shutil
from pathlib import Path

import psycopg2

PID = "114a8883-e960-4b97-97c7-0ba00a469750"
EID = "a230c90c-3878-4fe6-ae67-754927a431d2"
ROOT = Path("/Users/manta/Documents/Projects/TheRoad-I1")
EXP = ROOT / "scratchpad" / "forest_exp"
OUT = EXP / "out" / "w22b_e2e"
OUT.mkdir(parents=True, exist_ok=True)

conn = psycopg2.connect(host="localhost", user="theroad",
                        password="theroad_dev_2026", dbname="theroad")
cur = conn.cursor()


def copy_img(src: str, name: str) -> str:
    p = Path(src)
    if not p.is_absolute():
        p = ROOT / p
    dst = OUT / name
    if not dst.exists():
        shutil.copyfile(p, dst)
    return f"out/w22b_e2e/{name}"


# ── 1. 캐논 자산 (실사 + 맵) ──
cur.execute("""
SELECT pipeline_role, pipeline_metadata_json, file_path FROM image_asset
WHERE project_id=%s AND pipeline_role IN ('outdoor_canon_photo','outdoor_canon_map')
ORDER BY pipeline_role
""", (PID,))
canon = {}
for role, meta_j, fp in cur.fetchall():
    meta = json.loads(meta_j) if meta_j else {}
    place = meta.get("place_id") or meta.get("group_id")
    if not place:
        # 파일명 canon_<place>_(photo|map).png → <place>
        stem = Path(fp).stem
        place = stem.removeprefix("canon_")
        for suf in ("_photo", "_map", "_master"):
            place = place.removesuffix(suf)
    canon.setdefault(place, {})[role] = fp

# ── 2. place_spec 마커 ──
spec_path = (ROOT / "projects" / PID / "checkpoints" / "episodes" / EID /
             "outdoor_place_spec" / "manifest.json")
spec_groups = json.loads(spec_path.read_text())["data"]["groups"]

# ── 3. grounding ──
gr_path = (ROOT / "projects" / PID / "checkpoints" / "episodes" / EID /
           "outdoor_shot_grounding" / "manifest.json")
grounding = {}
if gr_path.exists():
    gdata = json.loads(gr_path.read_text())["data"]
    # shots 키 탐색 (구조 유연)
    shots = gdata.get("shots") or gdata.get("groundings") or {}
    if isinstance(shots, dict):
        grounding = shots

# ── 4. 씬 스틸 (primary 60) ──
cur.execute("""
SELECT ia.file_path, ia.pipeline_metadata_json, ia.prompt_used, ia.created_at,
       ss.scene_index, ss.shot_index
FROM image_asset ia JOIN scene_still ss ON ss.id = ia.still_id
WHERE ia.project_id=%s AND ia.asset_type='scene' AND ia.is_primary=1
ORDER BY ss.scene_index, ss.shot_index
""", (PID,))
stills = cur.fetchall()

# ── HTML ──
P = []
P.append("""<!doctype html><html lang="ko"><head><meta charset="utf-8">
<title>W22 2회차 — GPT-5.6 전면 적용 fresh E2E — 금월도 (2026-07-10)</title>
<style>
body{font-family:'Apple SD Gothic Neo',sans-serif;background:#111;color:#ddd;margin:20px}
h1,h2,h3{color:#fff} .grid{display:flex;flex-wrap:wrap;gap:14px}
.card{background:#1c1c1e;border-radius:8px;padding:10px;max-width:460px}
.card img{max-width:440px;border-radius:4px;display:block}
.cap{font-size:12px;color:#aaa;margin-top:6px;white-space:pre-wrap}
.badge{display:inline-block;background:#2563eb;color:#fff;border-radius:4px;
padding:1px 7px;font-size:11px;margin-right:5px}
.badge.indoor{background:#555}
details{margin-top:4px} summary{cursor:pointer;font-size:12px;color:#7aa2f7}
pre{font-size:11px;color:#9a9;white-space:pre-wrap;max-height:260px;overflow:auto}
table{border-collapse:collapse;font-size:12px} td,th{border:1px solid #333;padding:3px 8px}
</style></head><body>
<h1>W22 2회차 (GPT-5.6 Sol/Terra/Luna 전면) — 금월도 기획안+1부 (2026-07-10)</h1>
<p>프로젝트 114a8883 · 64스텝 완주 · 스틸 77/77 · 직행 37 · RCV 0 ·
전 분석 GPT-5.6 (Sol/Terra/Luna) · 캐논 2ref+9절 직행</p>""")

# 섹션 1 — 캐논
P.append("<h2>① 장소 캐논 자산 (10그룹 — 실사 마스터 + FP 스타일 맵)</h2>")
for place in sorted(canon):
    pair = canon[place]
    spec = spec_groups.get(place, {})
    items = (spec.get("spec") or {}).get("items", [])
    marker_txt = " · ".join(f"{i['code']}={i['name_en'][:40]}" for i in items[:8])
    P.append(f"<h3>{html.escape(place)}</h3><div class='grid'>")
    for role in ("outdoor_canon_photo", "outdoor_canon_map"):
        fp = pair.get(role)
        if not fp:
            continue
        rel = copy_img(fp, f"{place}_{role.rsplit('_',1)[-1]}.png")
        P.append(f"<div class='card'><img src='{rel}'>"
                 f"<div class='cap'>{role}</div></div>")
    P.append("</div>")
    if marker_txt:
        P.append(f"<div class='cap'>마커: {html.escape(marker_txt)}</div>")

# 섹션 2 — 직행 스틸
P.append(f"<h2>② 씬 스틸 {len(stills)}장 (직행=파란 배지, 실내/기타=회색)</h2><div class='grid'>")
n_direct = 0
for fp, meta_j, prompt, ts, si, shi in stills:
    meta = json.loads(meta_j) if meta_j else {}
    roles = [r.get("role", "") for r in (meta.get("actual_attached_refs") or [])]
    direct = any(r.startswith("outdoor_canon") for r in roles)
    if direct:
        n_direct += 1
    name = f"S{si}_{shi}.png"
    rel = copy_img(fp, name)
    badge = ("<span class='badge'>직행</span>" if direct
             else "<span class='badge indoor'>일반</span>")
    prm = html.escape((prompt or "")[:1400])
    P.append(
        f"<div class='card'>{badge}<b>S{si} sh{shi}</b>"
        f"<img src='{rel}'>"
        f"<div class='cap'>refs: {html.escape(', '.join(roles))}</div>"
        f"<details><summary>prompt_used</summary><pre>{prm}</pre></details>"
        f"</div>")
P.append("</div>")
P.append(f"<p>직행 스틸 합계: {n_direct} / {len(stills)}</p>")
P.append("</body></html>")

(EXP / "w22b_e2e.html").write_text("\n".join(P))
print(f"gallery: {EXP/'w22b_e2e.html'} | 직행 {n_direct}/{len(stills)} | 캐논 {len(canon)}그룹")
