"""round6.html 빌더 — 6라운드(뿌리 결합 v3 + 실외 체인 재구동) 진행형 페이지.

5라운드 교훈 반영: 부분 페이지 즉시 게시 + 진행중 표시(⏳) + 미완료 시 30s 자동
새로고침. 프롬프트 원문은 runlog.jsonl 에서 산출 경로로 역참조(실호출 SOT).
"""
import html as _html
import json
from pathlib import Path

import forest_lib as F

PAGE = F.EXP / "round6.html"


def _runlog_by_out():
    m = {}
    p = F.EXP / "runlog.jsonl"
    if not p.exists():
        return m
    for line in p.read_text().splitlines():
        try:
            ev = json.loads(line)
        except Exception:
            continue
        if ev.get("kind") == "img" and ev.get("out") and ev.get("prompt"):
            m[ev["out"]] = ev
    return m


def _plan(name):
    try:
        return F.load_plan(name)
    except Exception:
        return None


def _details(title, body, open_=False):
    o = " open" if open_ else ""
    return (f"<details{o}><summary>{_html.escape(title)}</summary>"
            f"<pre>{_html.escape(body or '')}</pre></details>")


def _cell(png: Path, title: str, sub: str = "", extra_html: str = "",
          runlog=None, ko: str = "", small=False):
    cls = "cell small" if small else "cell"
    head = f"<h4>{_html.escape(title)}</h4>"
    subline = f"<div class='ref'>{_html.escape(sub)}</div>" if sub else ""
    if not png.exists():
        return (f"<div class='{cls}'>{head}{subline}"
                f"<div class='pend'>⏳ 생성 중… (완료되면 자동 표시)</div>"
                f"{extra_html}</div>")
    rel = png.relative_to(F.EXP)
    det = ""
    if ko:
        det += _details("프롬프트 (한국어)", ko)
    ev = (runlog or {}).get(str(png))
    if ev:
        refs = ev.get("refs") or []
        refline = ("<div class='ref'>참조: "
                   + ", ".join(_html.escape(Path(r).name) for r in refs)
                   + f" · {ev.get('engine', '')}</div>") if refs else \
            f"<div class='ref'>참조 없음 (순수 T2I) · {ev.get('engine', '')}</div>"
        subline += refline
        det += _details("실호출 프롬프트 원문 (영어)", ev.get("prompt", ""))
    return (f"<div class='{cls}'>{head}{subline}"
            f"<a href='{rel}' target='_blank'><img src='{rel}'></a>"
            f"{det}{extra_html}</div>")


def _verdict(key, verdicts):
    txt = (verdicts or {}).get(key)
    if not txt:
        return ""
    return f"<div class='verdict'>판정: {_html.escape(txt)}</div>"


def _readback_line(v):
    rb = _plan(f"root_readback_v3{v}")
    if not rb:
        return ""
    letters = ", ".join(rb.get("letters_found") or [])
    inte = rb.get("interior_layout_exposed")
    return (f"<div class='rb'>readback: 글자 [{_html.escape(letters)}] · "
            f"실내노출={'YES' if inte else 'no'}</div>")


def build():
    recon = F.load_recon()
    runlog = _runlog_by_out()
    verdicts = _plan("r6_verdicts") or {}
    b_core = _plan("root_v3b_core") or {}
    choice = _plan("r6_root_choice") or {}
    shots_meta = _plan("r6_shots") or {}
    import s10_root_v3 as S10  # 지연 import (스타일/KO 상수)

    pend = []

    # ── §1 뿌리 결합 ──
    fps = []
    for m in recon["members"]:
        if m["is_indoor"]:
            continue
        for fp in (recon["fp_by_loc"].get(m["loc_id"]) or []):
            fps.append(Path(fp["png_path"]))
    # fp 원본은 http 서빙 루트 밖 — out/inputs 미러 사용
    mirror = F.OUT / "inputs"
    strip = ""
    for p in fps:
        cand = mirror / Path(p).name
        src = cand if cand.exists() else Path(p)
        rel = src.relative_to(F.EXP) if str(src).startswith(str(F.EXP)) else src
        strip += (f"<div class='thumb'><img src='{rel}'>"
                  f"<div>{_html.escape(Path(p).name)}<br>기하 앵커(실외 fp)</div></div>")
    for name, label in (("root_v2/root_v2.png", "4라운드 root_v2 (기하+마커 검증)"),
                        ("birdseye2/p1_plan_t2i.png", "5라운드 p1 (형태 검증)")):
        p = F.OUT / name
        if p.exists():
            strip += (f"<div class='thumb'><img src='{p.relative_to(F.EXP)}'>"
                      f"<div>{_html.escape(label)}</div></div>")

    root_cells = ""
    root_titles = {
        "a": "v3a — 템플릿 결합 (fp 3장 앵커 + p1 스타일 언어)",
        "b": "v3b — LLM 저작 코어 (fp 3장 앵커 + 공통 블록)",
        "c": "v3c — 2-hop 재스타일 (root_v2 → 형태 전환+기울임)",
    }
    root_ko = {"a": S10.STYLE_CORE_A_KO + "\n\n(+ 공통 블록: fp 참조 기하 고정"
                    "/부지 데이터/마커 12/우측 범례 — 영어 원문 참조)",
               "b": (b_core.get("core_ko") or "") + "\n\n(+ 동일 공통 블록)",
               "c": S10.C_RESTYLE_PROMPT_KO}
    for v in ("a", "b", "c"):
        png = F.OUT / "root_v3" / f"root_v3{v}.png"
        if not png.exists():
            pend.append(png)
        chosen = " ★채택" if choice.get("root") == v else ""
        root_cells += _cell(
            png, root_titles[v] + chosen, runlog=runlog, ko=root_ko.get(v, ""),
            extra_html=_readback_line(v) + _verdict(f"root_v3{v}", verdicts))

    # ── §2 실외 체인 ──
    chain_html = ""
    order_np = [("sketch.png", "camview 스케치 (라인아트 눈높이 hop)"),
                ("plate_hop.png", "plate — hop 경유 (스케치=구도 SOT)"),
                ("plate_direct.png", "plate — 직행 대조 (뿌리+staging 텍스트만)")]
    order_p = [("sketch.png", "camview 스케치"),
               ("plate_hop.png", "plate (hop)"),
               ("temp.png", "임시샷 (ENV=plate, CONTROL=스케치)"),
               ("contour.png", "윤곽 (C 경로)"),
               ("bg_C.png", "배경 재생성 (윤곽 기반)"),
               ("final_C.png", "최종샷 C (temp 경유)"),
               ("final_direct.png", "최종샷 직행 대조 (plate+passport만)")]
    for lane, order in (("no_person", order_np), ("person", order_p)):
        for sk in (shots_meta.get(lane) or []):
            s = recon["shots"][sk]
            st = s.get("staging") or {}
            chars = ", ".join(s.get("characters") or []) or "무인물"
            d = F.OUT / "outdoor_r6" / sk
            cells = ""
            for fn, cap in order:
                png = d / fn
                if not png.exists():
                    pend.append(png)
                cells += _cell(png, cap, runlog=runlog, small=True)
            # 4라운드 대비 (있으면)
            d4 = F.OUT / "outdoor_d" / sk
            if d4.exists():
                for fn, cap in (("sketch.png", "4라운드 스케치 (top-down 뿌리)"),
                                ("plate.png", "4라운드 plate (대비)")):
                    if (d4 / fn).exists():
                        cells += _cell(d4 / fn, cap, runlog=runlog, small=True)
            desc = s.get("description") or ""
            chain_html += (
                f"<div class='shotrow'><h3>{sk} — {chars} · "
                f"{_html.escape(st.get('framing_scale') or '')}</h3>"
                f"<div class='desc'>{_html.escape(desc)}</div>"
                f"<div class='row'>{cells}</div>"
                + _verdict(sk, verdicts) + "</div>")
    if not shots_meta:
        chain_html = "<div class='pend'>⏳ 체인 대기 — 뿌리 선정 후 시작</div>"

    refresh = "<meta http-equiv='refresh' content='30'>" if pend else ""
    choice_line = ""
    if choice.get("root"):
        choice_line = (f"<div class='verdict'>뿌리 채택: v3{choice['root']} — "
                       f"{_html.escape(choice.get('why', ''))}</div>")
    doc = f"""<meta charset='utf-8'>{refresh}<title>6라운드 — 뿌리 결합 + 실외 체인</title><style>
body{{font-family:sans-serif;background:#171717;color:#eee;margin:24px;max-width:1880px}}
h1{{font-size:22px}} h2{{color:#8fd;margin-top:30px}} h3{{color:#fd9;margin:4px 0}}
h4{{color:#fd9;margin:2px 0 6px;font-size:13px}}
.grid{{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}}
.row{{display:flex;gap:12px;overflow-x:auto;padding-bottom:6px}}
.cell{{background:#222;padding:10px;border-radius:10px}}
.cell img{{width:100%;border-radius:6px}}
.cell.small{{flex:0 0 340px}}
.thumb{{flex:0 0 190px;background:#222;padding:6px;border-radius:8px;font-size:10px;color:#aaa;text-align:center}}
.thumb img{{width:100%;border-radius:4px}}
.ref{{color:#8ac;font-size:12px;margin-bottom:6px}}
.rb{{color:#9c9;font-size:12px;margin-top:6px}}
.desc{{color:#bbb;font-size:12px;margin:2px 0 8px}}
.pend{{color:#fa5;padding:20px;font-size:14px}}
.verdict{{background:#1e3020;border-left:4px solid #4a4;padding:8px 12px;margin:8px 0;font-size:13px}}
pre{{white-space:pre-wrap;font-size:11px;color:#9c9;max-height:300px;overflow:auto;background:#1b1b1b;padding:8px}}
.guide{{background:#1e2430;padding:12px 16px;border-radius:8px;font-size:13px;line-height:1.7}}
.shotrow{{background:#1d1d1d;padding:12px;border-radius:10px;margin-bottom:14px}}
summary{{cursor:pointer;font-size:12px;color:#8ac}}
</style>
<h1>6라운드 — 전체 부지 뿌리(두 접근 결합) + 실외 체인 재구동</h1>
<div class='guide'>뿌리 = <b>건물 전체(옥탑 거주부+옥상+건물 몸체+지상 마당/골목/출입구)</b>를
한 장에, <b>fp 도면 언어(가는 윤곽선/플랫 색 채움/설비 기호) + ~30° 축측 입체감</b>.
결합: 4라운드 root_v2 의 <b>실외 fp 3장 기하 앵커</b> × 5라운드 p1 의 <b>형태/간결·중립 계약</b>
× 검증된 마커 12+영어 범례. 체인 = 검증 조합(무인물: sketch-hop+직행 생략 없이 hop/직행
<b>균일 A/B</b> · 인물: 임시샷 경유 C 경로, CONTROL=눈높이 스케치, v2 계약 3줄).</div>
<h2>§1 뿌리 v3 — 세 변형</h2>
<div class='row'>{strip}</div>
<div class='grid'>{root_cells}</div>
{choice_line}
<h2>§2 실외 체인 재구동 (뿌리 v3 기반)</h2>
{chain_html}
<div class='guide' style='margin-top:18px'>판정 기준: 기하 정합(fp 대비)·시점(staging 의도)
·무인/인원 준수·마커 누출 0·재질 사실감. 갤러리 1~5라운드는 gallery.html / birdseye.html.</div>
"""
    PAGE.write_text(doc)
    return PAGE


if __name__ == "__main__":
    print(build())
