#!/usr/bin/env python3
"""Stage-1 outdoor guide gallery — aerial->blocking->sketch per group + pose brief. 커밋금지."""
import json, os, shutil, re
import psycopg2
from pathlib import Path

PID="0e07d5e3-3fdd-4b9b-a54f-33aa59a5e204"
EID="92910173-fd2c-46ad-9d60-2b02dead4c0c"
ROOT=Path("/Users/manta/Documents/Projects/TheRoad-I1")
MAN=ROOT/f"projects/{PID}/checkpoints/episodes/{EID}/outdoor_site_layout/manifest.json"
OUT=ROOT/"scratchpad/e2e_gallery"; OUT.mkdir(parents=True, exist_ok=True)
IMGDIR=OUT/"img"; IMGDIR.mkdir(exist_ok=True)

c=psycopg2.connect(host='localhost',user='theroad',password='theroad_dev_2026',dbname='theroad'); cur=c.cursor()

# pose briefs: match to scene action snippet (first 60 chars) -> posture summary
cur.execute("SELECT user_prompt, output_text FROM llm_call_log WHERE operation_type='outdoor_pose_brief' ORDER BY created_at")
pose_by_action={}
for up,out in cur.fetchall():
    try: figs=json.loads(out or '{}').get('figures',[])
    except: figs=[]
    summ="; ".join(f"{f.get('slot')}={f.get('body_posture')}/{str(f.get('limb_action'))[:40]}" for f in figs) or "(0 figures -> fail-closed)"
    # action snippet from user prompt (grab the cinematic still line)
    m=re.search(r'(Photorealistic cinematic still.*?)(?:\n\n|Spatial|$)', up or '', re.S)
    act=(m.group(1) if m else '')[:400]
    pose_by_action[act[:80]]=(summ, act)

# assets by group
cur.execute("SELECT pipeline_role, file_path, pipeline_metadata_json FROM image_asset WHERE episode_id=%s AND pipeline_role LIKE 'outdoor_%%' ORDER BY pipeline_role", (EID,))
from collections import defaultdict
grp=defaultdict(dict)
meta_by_grp={}
for role,fp,meta in cur.fetchall():
    m=json.loads(meta or '{}'); gid=m.get('group_id','?')
    grp[gid][role]=fp
    if role=='outdoor_camera_sketch':
        meta_by_grp[gid]=m

cg=json.load(open(MAN))['data']['composition_guides']
# map group_id -> guide entry (for pose_brief_figures, scope, blocking reason)
guide_by_grp={}
for shotkey,g in cg.items():
    guide_by_grp[g.get('group_id')]=(shotkey,g)

def copy(fp, name):
    src=ROOT/fp if not os.path.isabs(fp) else Path(fp)
    if not src.exists():
        # try under projects dir
        alt=ROOT/"projects"/fp
        src=alt if alt.exists() else src
    if src.exists():
        dst=IMGDIR/name; shutil.copy(src,dst); return "img/"+name
    return None

rows_html=[]
for gid in sorted(grp):
    roles=grp[gid]
    shotkey,g = guide_by_grp.get(gid,("?",{}))
    loc=g.get('location_id','?'); scope=g.get('guide_scope_type','?')
    figs=g.get('pose_brief_figures','?'); present=g.get('pose_brief_present','?')
    bstage=g.get('blocking_stage','?')
    a=copy(roles.get('outdoor_aerial_base',''), f"{gid}_aerial.png") if 'outdoor_aerial_base' in roles else None
    b=copy(roles.get('outdoor_shot_blocking',''), f"{gid}_blocking.png") if 'outdoor_shot_blocking' in roles else None
    s=copy(roles.get('outdoor_camera_sketch',''), f"{gid}_sketch.png") if 'outdoor_camera_sketch' in roles else None
    cells=""
    for label,img in [("aerial base",a),("blocking",b),("camera sketch (POSE)",s)]:
        if img: cells+=f'<div class=cell><div class=lbl>{label}</div><img src="{img}"></div>'
        else: cells+=f'<div class=cell><div class=lbl>{label}</div><div class=none>—</div></div>'
    rows_html.append(f"""<div class=grp>
      <h3>{gid} &nbsp; <span class=loc>{loc}</span> &nbsp; shot {shotkey} &nbsp; <span class=scope>{scope}</span></h3>
      <div class=meta>pose_brief_present={present} · figures={figs} · blocking_stage={bstage}</div>
      <div class=rowimgs>{cells}</div>
    </div>""")

html=f"""<html><head><meta charset=utf-8><title>Stage-1 Outdoor Guides — pose restoration</title>
<style>body{{background:#111;color:#ddd;font-family:sans-serif;margin:12px}}
h3{{margin:14px 0 2px}} .loc{{color:#6cf}} .scope{{color:#fc6;font-size:13px}}
.meta{{color:#9c9;font-size:12px;margin-bottom:4px}}
.rowimgs{{display:flex;gap:8px}} .cell{{flex:1;text-align:center}}
.cell img{{width:100%;border:1px solid #333;border-radius:4px}}
.lbl{{font-size:11px;color:#aaa;margin-bottom:2px}} .none{{color:#555;padding:40px}}
.grp{{border-bottom:1px solid #333;padding-bottom:10px;margin-bottom:6px}}</style></head><body>
<h1>Stage-1 야외 가이드 — 포즈 복원 검증 ({len(grp)} groups)</h1>
<p style="color:#9c9">aerial base → blocking → camera sketch. 스케치 마네킹이 씬 액션의 실제 포즈를 반영하는지 육안 확인.</p>
{''.join(rows_html)}
</body></html>"""
(OUT/"index.html").write_text(html)
print(f"gallery -> {OUT}/index.html  ({len(grp)} groups, images in {IMGDIR})")
print("pose summaries:")
for k,(summ,act) in pose_by_action.items():
    print("  -",summ)
