"""연결 갤러리: 각 샷의 최종 이미지 ← 실제 첨부된 가이드/배경/캐릭터(input_image_ids) 를
나란히 보여줘 '가이드가 샷/배경에 연결되어 생성됨' 을 육안 확인. 재생성 결과 SOT=DB."""
import os, shutil, json, psycopg2

ROOT = "/Users/manta/Documents/Projects/TheRoad-I1"
EP = "286f3ba4-5738-49b7-a7e4-ce0295fd4352"
OUT = os.path.join(ROOT, "backend/scratchpad/conn_gallery")
IMG = os.path.join(OUT, "img")
os.makedirs(IMG, exist_ok=True)

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

# 최신 scene_still asset per still (재생성분) + still 씬/샷
cur.execute("""
SELECT DISTINCT ON (ia.still_id) ss.scene_index, ss.shot_index, ia.id, ia.file_path,
       ia.input_image_ids, LEFT(ss.shot_description,60)
FROM image_asset ia JOIN scene_still ss ON ss.id=ia.still_id
WHERE ia.episode_id=%s AND ia.pipeline_role='scene_still' AND ia.still_id IS NOT NULL
ORDER BY ia.still_id, ia.created_at::timestamptz DESC
""", (EP,))
stills = cur.fetchall()

ROLE_KO = {
    "outdoor_camera_sketch": "야외 마네킹 스케치", "registered_pose_guide": "등록 pose 가이드",
    "indoor_pose_guide": "실내 pose 가이드", "background_render": "배경",
    "reference_composite": "캐릭터 합성", "reference_face": "얼굴",
    "character_state_variant": "상태 변형", "reference_outlook": "아웃룩",
    "outdoor_aerial_base": "항공뷰", "outdoor_shot_blocking": "블로킹",
}
GUIDE_ROLES = {"outdoor_camera_sketch", "registered_pose_guide", "indoor_pose_guide"}

def resolve(asset_id):
    cur.execute("SELECT file_path, pipeline_role FROM image_asset WHERE id=%s", (asset_id,))
    r = cur.fetchone()
    return r if r else (None, None)

def cp(fp, tag):
    if not fp:
        return None
    src = os.path.join(ROOT, fp)
    if not os.path.exists(src):
        return None
    dst_name = f"{tag}_{os.path.basename(fp)}"
    shutil.copy(src, os.path.join(IMG, dst_name))
    return "img/" + dst_name

cards = []
n_guide = n_total = 0
for sc, sh, aid, fp, iii, desc in stills:
    n_total += 1
    final_rel = cp(fp, f"S{sc}sh{sh}_final")
    refs = []
    has_guide = False
    try:
        ids = json.loads(iii) if iii and iii not in ("null", "") else []
    except Exception:
        ids = []
    for rid in ids:
        rfp, rrole = resolve(rid)
        if rrole in GUIDE_ROLES:
            has_guide = True
        rel = cp(rfp, f"S{sc}sh{sh}_{rrole}")
        refs.append((rel, ROLE_KO.get(rrole, rrole or "?")))
    if has_guide:
        n_guide += 1
    cards.append((sc, sh, desc, final_rel, refs, has_guide))

cards.sort(key=lambda c: (0 if c[5] else 1, c[0], c[1]))  # 가이드 샷 먼저

def ref_html(refs):
    if not refs:
        return '<span class="none">첨부 참조 없음(환경/인서트)</span>'
    out = []
    for rel, label in refs:
        img = f'<img src="{rel}">' if rel else '<div class="miss">파일없음</div>'
        out.append(f'<figure>{img}<figcaption>{label}</figcaption></figure>')
    return "".join(out)

rows = []
for sc, sh, desc, final_rel, refs, has_guide in cards:
    badge = '<span class="gb">가이드 연결</span>' if has_guide else ''
    fimg = f'<img src="{final_rel}">' if final_rel else '<div class="miss">파일없음</div>'
    rows.append(f"""
    <div class="card {'guide' if has_guide else ''}">
      <div class="hd">S{sc} · shot {sh} {badge}<span class="desc">{desc or ''}</span></div>
      <div class="body">
        <figure class="final">{fimg}<figcaption>최종 샷</figcaption></figure>
        <div class="arrow">←</div>
        <div class="refs">{ref_html(refs)}</div>
      </div>
    </div>""")

html = f"""<!doctype html><html lang=ko><head><meta charset=utf-8>
<title>연결 갤러리 — 최종 샷 ← 첨부 가이드/배경/캐릭터</title>
<style>
body{{background:#0d1117;color:#e6edf3;font-family:-apple-system,"Noto Sans KR",sans-serif;margin:0;padding:20px}}
h1{{font-size:18px}} .sub{{color:#8b949e;font-size:13px;margin-bottom:16px}}
.card{{border:1px solid #30363d;border-radius:10px;margin-bottom:14px;overflow:hidden}}
.card.guide{{border-color:#2dd4bf}}
.hd{{background:#161b22;padding:8px 12px;font-weight:700;font-size:14px;display:flex;gap:10px;align-items:center}}
.desc{{font-weight:400;color:#8b949e;font-size:12px;margin-left:auto;max-width:50%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}
.gb{{background:#2dd4bf;color:#0d1117;border-radius:999px;padding:1px 8px;font-size:11px}}
.body{{display:flex;align-items:center;gap:12px;padding:12px;overflow-x:auto}}
figure{{margin:0;text-align:center}} figure img{{height:190px;border-radius:6px;display:block}}
.final img{{height:230px;border:2px solid #bc8cff}}
figcaption{{font-size:11px;color:#9aa7b4;margin-top:4px}}
.arrow{{font-size:28px;color:#f778ba;flex:none}}
.refs{{display:flex;gap:10px}}
.none{{color:#6e7681;font-size:13px}} .miss{{width:150px;height:190px;background:#21262d;display:flex;align-items:center;justify-content:center;color:#6e7681;border-radius:6px}}
</style></head><body>
<h1>연결 갤러리 — 최종 샷 ← 실제 첨부된 가이드/배경/캐릭터</h1>
<div class="sub">에피소드 재생성 후 input_image_ids(=실제 생성 입력) 기준. 가이드 연결 샷 {n_guide} / 총 {n_total}. 가이드 샷을 위로 정렬.</div>
{''.join(rows)}
</body></html>"""

with open(os.path.join(OUT, "index.html"), "w") as f:
    f.write(html)
print(f"gallery: {OUT}/index.html  (가이드연결 {n_guide}/{n_total} 샷, img {len(os.listdir(IMG))}개)")
cur.close(); conn.close()
