"""파이프라인 각 Stage 실행 후 자동으로 HTML 시각화를 생성.

save_snapshot()에서 호출됨. 각 stage의 실제 context 데이터로 HTML 생성.
JSON context 파일과 동일한 stage 이름을 사용.

생성되는 파일 (JSON context 파일과 1:1 매칭):
  stage_0.html               — MDX 정규화 결과 (섹션, 팝업, 이미지)
  stage_1a.html              — Kei 꼭지 + 영역 배정 (테이블)
  stage_1b.html              — 컨셉 구체화 (source_data, summary 추가)
  stage_1_5a.html            — 빈 컨테이너 (1280x720)
  stage_1_5a_content.html    — 컨테이너에 실제 콘텐츠 배치
  stage_1_5b.html            — 디자인 예산 (영역별 available_height/width)
  stage_1_7.html             — 블록 선택 표시
  stage_1_8_fit_before.html  — 적합성 검증 (재배분 전)
  stage_1_8_fit_after.html   — 재배분 후 + 보강 결과
  stage_1_8_blocks.html      — 재배분 후 컨테이너에 블록 배치
  stage_2.html               — HTML 생성 결과 (영역별 생성된 HTML)
  stage_3.html               — 렌더링 조립 → final.html 링크
  stage_4.html               — 품질 게이트 (측정값, 점수)
"""
from __future__ import annotations

import logging
from pathlib import Path
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from src.pipeline_context import PipelineContext

logger = logging.getLogger(__name__)

COLORS = {"배경": "#dc2626", "본심": "#2563eb", "첨부": "#16a34a", "결론": "#7c3aed"}
FONT_MAP = {"배경": "bg", "본심": "core", "첨부": "sidebar", "결론": "key_msg"}


def generate_step_html(stage_name: str, ctx: "PipelineContext", steps_dir: Path) -> None:
    """stage_name에 따라 적절한 시각화 HTML 생성. 파일명은 JSON context와 1:1 매칭."""
    try:
        if stage_name == "stage_0":
            _gen_stage_0(ctx, steps_dir)
        elif stage_name == "stage_1a":
            _gen_stage_1a(ctx, steps_dir)
        elif stage_name == "stage_1b":
            _gen_stage_1b(ctx, steps_dir)
        elif stage_name == "stage_1_5a":
            _gen_stage_1_5a(ctx, steps_dir)
            _gen_stage_1_5a_content(ctx, steps_dir)
        elif stage_name == "stage_1_5b":
            _gen_stage_1_5b(ctx, steps_dir)
        elif stage_name == "stage_1_7":
            _gen_stage_1_7(ctx, steps_dir)
        elif stage_name == "stage_1_8":
            # before와 filled는 pipeline.py Stage 1.8 내부에서 before context로 이미 생성됨
            # step_visualizer는 after context로 호출되므로 덮어쓰면 안 됨
            # blocks와 fit_after만 생성 (after 상태 반영)
            _gen_stage_1_8_blocks(ctx, steps_dir)
            _gen_stage_1_8_fit_after(ctx, steps_dir)
        elif stage_name == "stage_2":
            _gen_stage_2(ctx, steps_dir)
        elif stage_name == "stage_3":
            _gen_stage_3(ctx, steps_dir)
        elif stage_name == "stage_4":
            _gen_stage_4(ctx, steps_dir)
    except Exception as e:
        logger.warning(f"[step_viz] {stage_name} 시각화 실패: {e}")


# ══════════════════════════════════════
# 공통
# ══════════════════════════════════════

def _tokens():
    from src.fit_verifier import _load_design_tokens
    return _load_design_tokens()


def _calc_coords(containers: dict, ratio: tuple) -> dict:
    t = _tokens()
    pad = t.get("spacing_page", 40)
    gap = t.get("spacing_block", 20)
    small = t.get("spacing_small", 8)
    header_h = 66

    inner_w = 1280 - pad * 2
    body_w = int(inner_w * ratio[0] / 100) if ratio[0] > 0 else inner_w
    sidebar_w = inner_w - body_w - gap if ratio[1] > 0 else 0

    def gh(c):
        if hasattr(c, "height_px"): return c.height_px
        return c.get("height_px", 0) if isinstance(c, dict) else 0

    bg_h = gh(containers.get("배경", {}))
    core_h = gh(containers.get("본심", {}))
    sb_h = gh(containers.get("첨부", {}))
    ft_h = gh(containers.get("결론", {}))

    bg_top = pad + header_h + gap
    core_top = bg_top + bg_h + small
    ft_top = max(core_top + core_h, bg_top + sb_h) + gap

    return {
        "header": {"l": pad, "t": pad, "w": inner_w, "h": header_h},
        "배경": {"l": pad, "t": bg_top, "w": body_w, "h": bg_h},
        "본심": {"l": pad, "t": core_top, "w": body_w, "h": core_h},
        "첨부": {"l": pad + body_w + gap, "t": bg_top, "w": sidebar_w, "h": sb_h},
        "결론": {"l": pad, "t": ft_top, "w": inner_w, "h": ft_h},
    }


def _wrap(title, subtitle, slide_body):
    return f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{{margin:0;padding:0;box-sizing:border-box;}}
body{{background:#e5e5e5;padding:10px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}}
.bl{{display:flex;gap:0;margin-bottom:2px;}}.bl-m{{flex-shrink:0;width:1em;}}.bl-t{{flex:1;}}
</style></head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:4px;">{title}</div>
<div style="font-size:11px;color:#666;margin-bottom:8px;">{subtitle}</div>
<div style="width:1280px;height:720px;background:white;position:relative;border:1px solid #ccc;">
{slide_body}
</div></body></html>"""


def _hdr(c, title):
    return (f'<div style="position:absolute;left:{c["l"]}px;top:{c["t"]}px;width:{c["w"]}px;height:{c["h"]}px;'
            f'background:#f8fafc;border-bottom:3px solid #2563eb;display:flex;align-items:center;'
            f'padding:0 20px;font-size:22px;font-weight:900;color:#1e293b;">{title}</div>\n')


def _box(c, role, inner, extra=""):
    cl = COLORS.get(role, "#333")
    return (f'<div style="position:absolute;left:{c["l"]}px;top:{c["t"]}px;width:{c["w"]}px;height:{c["h"]}px;'
            f'border:2px solid {cl};border-radius:6px;background:{cl}08;overflow:hidden;{extra}">{inner}</div>\n')


# ══════════════════════════════════════
# Stage 0: MDX 정규화
# ══════════════════════════════════════

def _gen_stage_0(ctx, steps_dir):
    """MDX 정규화 결과: 섹션, 팝업, 이미지, 테이블 목록."""
    norm = ctx.normalized if hasattr(ctx, 'normalized') else {}
    if hasattr(norm, 'model_dump'):
        norm = norm.model_dump()
    elif not isinstance(norm, dict):
        norm = {}

    sections = norm.get("sections", [])
    popups = norm.get("popups", [])
    images = norm.get("images", [])
    tables = norm.get("tables", [])
    title = norm.get("title", ctx.analysis.title if ctx.analysis else "")

    sec_rows = ""
    for i, s in enumerate(sections):
        heading = s.get("heading", "") if isinstance(s, dict) else ""
        content = s.get("content", "") if isinstance(s, dict) else str(s)
        preview = content[:120].replace("<", "&lt;") + ("..." if len(content) > 120 else "")
        bg = "#f8fafc" if i % 2 == 0 else "#fff"
        sec_rows += f'<tr style="background:{bg};"><td style="padding:6px 8px;">{i+1}</td><td style="padding:6px 8px;font-weight:700;">{heading}</td><td style="padding:6px 8px;font-size:11px;">{preview}</td></tr>\n'

    popup_rows = ""
    for p in popups:
        pt = p.get("title", "") if isinstance(p, dict) else str(p)
        pc = p.get("content", "") if isinstance(p, dict) else ""
        popup_rows += f'<tr><td style="padding:6px 8px;font-weight:700;">{pt}</td><td style="padding:6px 8px;font-size:11px;">{len(pc)}자</td></tr>\n'

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}body{{background:#e5e5e5;padding:10px;font-family:sans-serif;word-break:keep-all;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:8px;">Stage 0: MDX 정규화</div>
<div style="font-size:12px;color:#555;margin-bottom:12px;">제목: <b>{title}</b> | 섹션: {len(sections)}개 | 팝업: {len(popups)}개 | 이미지: {len(images)}개 | 테이블: {len(tables)}개</div>
<div style="font-size:13px;font-weight:700;margin-bottom:4px;">섹션</div>
<table style="border-collapse:collapse;font-size:12px;width:100%;max-width:900px;margin-bottom:16px;">
<tr style="background:#1e293b;color:white;"><th style="padding:8px;">#</th><th style="padding:8px;">heading</th><th style="padding:8px;">content (미리보기)</th></tr>{sec_rows}</table>
<div style="font-size:13px;font-weight:700;margin-bottom:4px;">팝업</div>
<table style="border-collapse:collapse;font-size:12px;width:100%;max-width:600px;">
<tr style="background:#1e293b;color:white;"><th style="padding:8px;">title</th><th style="padding:8px;">분량</th></tr>{popup_rows}</table>
</body></html>"""
    (steps_dir / "stage_0.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1A: Kei 꼭지
# ══════════════════════════════════════

def _gen_stage_1a(ctx, steps_dir):
    ps = ctx.page_structure.roles
    rm = {}
    for role, info in ps.items():
        if isinstance(info, dict):
            for tid in info.get("topic_ids", []):
                rm[tid] = role

    rows = ""
    for t in ctx.topics:
        role = rm.get(t.id, "?")
        c = COLORS.get(role, "#333")
        bg = "#f8fafc" if t.id % 2 == 0 else "#fff"
        rows += (f'<tr style="background:{bg};"><td style="padding:6px 8px;text-align:center;">{t.id}</td>'
                 f'<td style="padding:6px 8px;font-weight:700;">{t.title}</td>'
                 f'<td style="padding:6px 8px;">{t.purpose}</td><td style="padding:6px 8px;">{t.layer}</td>'
                 f'<td style="padding:6px 8px;">{t.relation_type}</td>'
                 f'<td style="padding:6px 8px;color:{c};font-weight:700;">{role}</td></tr>\n')

    ps_info = "<br>".join(f"{r}: topic_ids={info.get('topic_ids')}, weight={info.get('weight')}"
                           for r, info in ps.items() if isinstance(info, dict))

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}body{{background:#e5e5e5;padding:10px;font-family:sans-serif;word-break:keep-all;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:8px;">Stage 1A/1B: Kei 꼭지 + 영역 배정</div>
<table style="border-collapse:collapse;font-size:12px;width:100%;max-width:900px;">
<tr style="background:#1e293b;color:white;"><th style="padding:8px;">ID</th><th style="padding:8px;">제목</th>
<th style="padding:8px;">purpose</th><th style="padding:8px;">layer</th><th style="padding:8px;">relation_type</th>
<th style="padding:8px;">영역</th></tr>{rows}</table>
<div style="margin-top:12px;font-size:12px;color:#555;"><b>페이지 구조:</b><br>{ps_info}</div></body></html>"""
    (steps_dir / "stage_1a.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1B: 컨셉 구체화
# ══════════════════════════════════════

def _gen_stage_1b(ctx, steps_dir):
    """Stage 1B 후 꼭지에 source_data, summary가 추가된 상태."""
    ps = ctx.page_structure.roles
    rm = {}
    for role, info in ps.items():
        if isinstance(info, dict):
            for tid in info.get("topic_ids", []):
                rm[tid] = role

    rows = ""
    for t in ctx.topics:
        role = rm.get(t.id, "?")
        c = COLORS.get(role, "#333")
        bg = "#f8fafc" if t.id % 2 == 0 else "#fff"
        sd = (t.source_data or "")[:150]
        sd_display = sd.replace("<", "&lt;") + ("..." if len(t.source_data or "") > 150 else "")
        summary = (t.summary or "")[:100] if hasattr(t, 'summary') else ""
        rows += (f'<tr style="background:{bg};"><td style="padding:6px 8px;text-align:center;">{t.id}</td>'
                 f'<td style="padding:6px 8px;font-weight:700;">{t.title}</td>'
                 f'<td style="padding:6px 8px;color:{c};">{role}</td>'
                 f'<td style="padding:6px 8px;">{t.layer}</td>'
                 f'<td style="padding:6px 8px;font-size:10px;">{sd_display}</td>'
                 f'<td style="padding:6px 8px;font-size:10px;color:#555;">{summary}</td></tr>\n')

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}body{{background:#e5e5e5;padding:10px;font-family:sans-serif;word-break:keep-all;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:8px;">Stage 1B: 컨셉 구체화</div>
<div style="font-size:11px;color:#666;margin-bottom:8px;">Stage 1A의 꼭지에 source_data(원본 텍스트)와 summary가 추가됨</div>
<table style="border-collapse:collapse;font-size:12px;width:100%;">
<tr style="background:#1e293b;color:white;"><th style="padding:8px;">ID</th><th style="padding:8px;">제목</th>
<th style="padding:8px;">영역</th><th style="padding:8px;">layer</th>
<th style="padding:8px;">source_data (미리보기)</th><th style="padding:8px;">summary</th></tr>{rows}</table></body></html>"""
    (steps_dir / "stage_1b.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.5a: 빈 컨테이너
# ══════════════════════════════════════

def _gen_stage_1_5a(ctx, steps_dir):
    coords = _calc_coords(ctx.containers, ctx.container_ratio)
    fh = ctx.font_hierarchy
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        fk = FONT_MAP[role]
        font = getattr(fh, fk, "?")
        inner = (f'<div style="text-align:center;margin-top:{max(0,c["h"]//2-15)}px;">'
                 f'<b style="color:{cl};font-size:13px;">{role}</b><br>'
                 f'<span style="color:#888;font-size:10px;">{c["w"]}x{c["h"]}px / font:{font}px</span></div>')
        body += _box(c, role, inner)

    r = ctx.container_ratio
    html = _wrap(f"Step 1: 빈 컨테이너 (Stage 1.5a)", f"비율 {r[0]}:{r[1]}", body)
    (steps_dir / "stage_1_5a.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.5a: 컨테이너에 콘텐츠 배치
# ══════════════════════════════════════

def _gen_stage_1_5a_content(ctx, steps_dir):
    coords = _calc_coords(ctx.containers, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)
    ps = ctx.page_structure.roles
    topic_map = {t.id: t for t in ctx.topics}

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        info = ps.get(role, {})
        tids = info.get("topic_ids", []) if isinstance(info, dict) else []

        lines = [f'<div style="font-size:10px;color:{cl};font-weight:700;margin-bottom:4px;">{role}</div>']
        for tid in tids:
            t = topic_map.get(tid)
            if not t:
                continue
            lines.append(f'<div style="font-size:11px;font-weight:700;margin-bottom:2px;">[꼭지{tid}] {t.title} — {t.purpose} · {t.layer}</div>')
            sd = t.source_data
            if sd:
                # 불릿으로 표시
                for sent in sd.split(", "):
                    sent = sent.strip()
                    if sent:
                        lines.append(f'<div class="bl" style="font-size:10px;color:#444;"><span class="bl-m">•</span><span class="bl-t">{sent}</span></div>')

        inner = f'<div style="padding:6px 10px;overflow:hidden;">{"".join(lines)}</div>'
        body += _box(c, role, inner)

    html = _wrap("Step 1b: 콘텐츠 배치 (꼭지 → 컨테이너)", "각 컨테이너에 배정된 꼭지의 source_data", body)
    (steps_dir / "stage_1_5a_content.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.5b: 디자인 예산
# ══════════════════════════════════════

def _gen_stage_1_5b(ctx, steps_dir):
    """영역별 디자인 예산 (available height/width, fits 여부)."""
    coords = _calc_coords(ctx.containers, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        ci = ctx.containers.get(role)
        if not ci:
            continue
        db = ci.design_budget
        if db and hasattr(db, 'model_dump'):
            db = db.model_dump()
        elif not isinstance(db, dict):
            db = {}

        avail_h = db.get("available_height_px", 0)
        avail_w = db.get("available_width_px", 0)
        fits = db.get("fits", False)
        icon = "✅" if fits else "⚠️"

        inner = (f'<div style="padding:6px 10px;">'
                 f'<div style="font-size:10px;color:{cl};font-weight:700;">{icon} {role} ({c["w"]}×{c["h"]}px)</div>'
                 f'<div style="font-size:10px;color:#555;">available: {avail_h}×{avail_w}px</div>'
                 f'<div style="font-size:10px;color:#555;">fits: {fits}</div>'
                 f'</div>')
        body += _box(c, role, inner)

    html = _wrap("Stage 1.5b: 디자인 예산", "영역별 available_height/width + fits 여부", body)
    (steps_dir / "stage_1_5b.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.7: 블록 선택
# ══════════════════════════════════════

def _gen_stage_1_7(ctx, steps_dir):
    coords = _calc_coords(ctx.containers, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        ref_list = ctx.references.get(role, [])

        lines = [f'<div style="font-size:10px;color:{cl};font-weight:700;margin-bottom:4px;">{role} ({c["w"]}x{c["h"]}px)</div>']
        for r in ref_list:
            bid = r.block_id
            var = r.variant
            vtype = r.visual_type
            line = f'<b>{bid}</b> ({var}) <span style="color:#888;font-size:9px;">{vtype}</span>'
            # 주종 정보 — model_dump에서 확인
            rd = r.model_dump() if hasattr(r, "model_dump") else {}
            # BlockReference에는 supporting 정보가 없음 — stage_1_7_context.json에서 확인
            lines.append(f'<div style="font-size:11px;margin-bottom:2px;">{line}</div>')

        inner = f'<div style="padding:6px 10px;">{"".join(lines)}</div>'
        body += _box(c, role, inner)

    html = _wrap("Step 2: 블록 선택 (Stage 1.7)", "layer 기반 주종 판단. 컨테이너 위에 블록 표시.", body)
    (steps_dir / "stage_1_7.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.8: 텍스트/그림 채운 상태 (filled)
# ══════════════════════════════════════

def _gen_stage_1_8_filled(ctx, steps_dir):
    """블록 디자인에 텍스트를 채운 상태. 공통 조립 함수(block_assembler) 사용."""
    from src.block_assembler import assemble_slide_html
    slide_html = assemble_slide_html(ctx)
    # 시각화 제목 삽입
    header = (
        '<div style="font-size:16px;font-weight:bold;margin-bottom:4px;">'
        'Stage 1.8: 블록 디자인에 텍스트 채운 상태 (fit 검증 전)</div>\n'
        '<div style="font-size:11px;color:#666;margin-bottom:8px;">'
        '블록 CSS + structured_text + font_hierarchy. 공통 조립 함수 사용.</div>\n'
    )
    html = slide_html.replace('</head><body>', '</head><body>\n' + header, 1)
    (steps_dir / "stage_1_8_filled.html").write_text(html, encoding="utf-8")


def _gen_stage_1_8_fit_before(ctx, steps_dir):
    """before: weight 비중대로 배정된 빈 컨테이너. fit 판단 없이 크기만 표시."""
    coords = _calc_coords(ctx.containers, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]

        ref_list = ctx.references.get(role, [])
        blocks = ", ".join(r.block_id for r in ref_list) if ref_list else "미선택"

        ps = ctx.page_structure.roles
        info = ps.get(role, {})
        weight = info.get("weight", 0) if isinstance(info, dict) else 0

        inner = (f'<div style="padding:6px 10px;">'
                 f'<div style="font-size:10px;color:{cl};font-weight:700;">{role} ({c["w"]}x{c["h"]}px)</div>'
                 f'<div style="font-size:10px;color:#555;">weight: {weight}</div>'
                 f'<div style="font-size:10px;color:#888;margin-top:2px;">블록: {blocks}</div>'
                 f'</div>')
        body += _box(c, role, inner)

    html = _wrap("Stage 1.8: before (weight 비중 초기 배정)", "빈 컨테이너. filled에서 텍스트를 채운 후 넘침 확인.", body)
    (steps_dir / "stage_1_8_fit_before.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.8: 재배분 후 + 보강
# ══════════════════════════════════════

def _gen_stage_1_8_fit_after(ctx, steps_dir):
    fit = ctx.fit_result
    enh = ctx.enhancement_result
    redist = fit.get("redistribution", {})
    roles_fit = fit.get("roles", {})

    # 재배분된 컨테이너
    new_c = {}
    for role, ci in ctx.containers.items():
        new_h = int(redist.get(role, ci.height_px))
        new_c[role] = {"height_px": new_h, "width_px": ci.width_px}

    coords = _calc_coords(new_c, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"
    body = _hdr(coords["header"], title)

    emps = enh.get("emphasis_blocks", [])
    bolds = enh.get("bold_keywords", {})
    sups = enh.get("supplement_blocks", [])

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        rf = roles_fit.get(role, {})
        status = rf.get("fit_status", "?")
        icon = {"OK": "✅", "TIGHT": "⚠️", "OVERFLOW": "❌"}.get(status, "?")
        old_h = rf.get("allocated_px", 0)
        new_h = int(redist.get(role, old_h))
        needed = rf.get("total_required_px", 0)
        delta = new_h - old_h

        ref_list = ctx.references.get(role, [])
        blocks = ", ".join(r.block_id for r in ref_list)

        delta_str = f" <span style='color:#16a34a;'>({delta:+d}px)</span>" if delta != 0 else ""

        inner = (f'<div style="padding:6px 10px;">'
                 f'<div style="font-size:10px;color:{cl};font-weight:700;">{icon} {role} ({c["w"]}x{new_h}px){delta_str}</div>'
                 f'<div style="font-size:10px;color:#555;">필요: {needed:.0f}px / 재배분 후: {new_h}px</div>'
                 f'<div style="font-size:10px;color:#888;margin-top:2px;">블록: {blocks}</div>')

        # 보강 정보
        role_emps = [e for e in emps if e.get("role") == role]
        role_bolds = bolds.get(role, [])
        role_sups = [s for s in sups if s.get("role") == role]

        if role_emps:
            for e in role_emps:
                inner += f'<div style="margin-top:3px;background:#991b1b;color:#fff;border-radius:2px;padding:2px 6px;font-size:9px;font-weight:700;">강조: {e.get("sentence","")[:40]}...</div>'
        if role_sups:
            for s in role_sups:
                inner += f'<div style="margin-top:3px;font-size:9px;color:#2563eb;">보충: {s.get("block_id")} ({s.get("content_source")})</div>'
        if role_bolds:
            inner += f'<div style="margin-top:3px;font-size:9px;color:#475569;">bold: {role_bolds[:4]}</div>'

        inner += '</div>'
        body += _box(c, role, inner)

    redist_str = ", ".join(f"{r}:{int(v)}px" for r, v in redist.items())
    html = _wrap("Step 3b: 재배분 후 + 보강 (Stage 1.8)", f"재배분: {redist_str}", body)
    (steps_dir / "stage_1_8_fit_after.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 1.8: 블록 디자인을 컨테이너에 배치
# ══════════════════════════════════════

def _gen_stage_1_8_blocks(ctx, steps_dir):
    """재배분된 컨테이너에 블록 SLOT 구조 + 블록 디자인 + 주종관계 표시.
    debug_steps/step2_phase_v.html 수준의 시각화."""
    import re as _re

    fit = ctx.fit_result or {}
    redist = fit.get("redistribution", {})
    topic_map = {t.id: t for t in ctx.topics}
    ps = ctx.page_structure.roles

    new_c = {}
    for role, ci in ctx.containers.items():
        new_h = int(redist.get(role, ci.height_px))
        new_c[role] = {"height_px": new_h, "width_px": ci.width_px}

    coords = _calc_coords(new_c, ctx.container_ratio)
    title = ctx.analysis.title or "슬라이드"

    all_block_css = set()
    slide_body = _hdr(coords["header"], title)
    legend_lines = []

    for role in ["배경", "본심", "첨부", "결론"]:
        c = coords[role]
        cl = COLORS[role]
        ref_list = ctx.references.get(role, [])
        info = ps.get(role, {})
        tids = info.get("topic_ids", []) if isinstance(info, dict) else []

        if not ref_list:
            slide_body += _box(c, role, f'<div style="padding:6px;color:#888;">블록 없음</div>')
            continue

        r0 = ref_list[0]
        bid = r0.block_id
        var = r0.variant
        is_hier = r0.is_hierarchical if hasattr(r0, 'is_hierarchical') else False
        sup_tids = r0.supporting_topic_ids if hasattr(r0, 'supporting_topic_ids') else []
        primary_tid = r0.topic_id if hasattr(r0, 'topic_id') and r0.topic_id else (tids[0] if tids else None)

        # 블록 디자인 HTML — SLOT 주석은 유지, 내용은 SLOT 마커로
        raw = r0.design_reference_html or ""
        # CSS 추출
        styles = _re.findall(r'<style>(.*?)</style>', raw, _re.DOTALL)
        for s in styles:
            all_block_css.add(s)
        clean = _re.sub(r'<style>.*?</style>', '', raw, flags=_re.DOTALL)

        # SLOT 주석을 보이는 텍스트로 변환
        def _slot_comment_to_visible(match):
            text = match.group(1).strip()
            if 'SLOT:' in text:
                return f'<span style="color:#888;font-size:9px;background:#f0f0f0;padding:1px 4px;border-radius:2px;">{text}</span>'
            return ''
        clean = _re.sub(r'<!--\s*(SLOT:[^>]+?)-->', _slot_comment_to_visible, clean)
        # 나머지 주석 제거
        clean = _re.sub(r'<!--.*?-->', '', clean, flags=_re.DOTALL)

        # 태그 라벨 (동적)
        tag_parts = [f"{role} ({c['w']}×{c['h']})", bid]
        if is_hier:
            sup_str = "+".join(f"꼭지{st}" for st in sup_tids)
            tag_parts.append(f"꼭지{primary_tid}(주)+{sup_str}(종) → 블록 1개")
        tag_label = " · ".join(tag_parts)

        # 종속 꼭지 SLOT 표시
        sub_slot = ""
        if is_hier and sup_tids:
            for st in sup_tids:
                st_topic = topic_map.get(st)
                st_purpose = st_topic.purpose if st_topic and hasattr(st_topic, 'purpose') else ""
                sub_slot += (
                    f'<div style="padding-left:8px;border-left:2px solid {cl};margin-top:4px;'
                    f'font-size:10px;color:{cl};">'
                    f'SLOT: 하위 (꼭지{st} — {st_purpose})</div>'
                )

        # key-msg SLOT (본심만)
        keymsg_slot = ""
        if role == "본심" and ctx.analysis.core_message:
            keymsg_slot = (
                f'<div style="background:#dbeafe;border:1px solid #2563eb;border-radius:3px;'
                f'padding:2px 6px;font-size:9px;color:#1e40af;font-weight:700;margin-top:4px;">'
                f'SLOT: key-msg</div>'
            )

        inner = (
            f'<div style="position:relative;padding-top:18px;width:100%;height:100%;">'
            f'<span style="position:absolute;top:-16px;left:4px;font-size:9px;font-weight:700;'
            f'background:white;padding:1px 6px;border-radius:3px;border:1px solid {cl};'
            f'color:{cl};white-space:nowrap;">{tag_label}</span>'
            f'{clean}{sub_slot}{keymsg_slot}</div>'
        )

        slide_body += (
            f'<div style="position:absolute;left:{c["l"]}px;top:{c["t"]}px;width:{c["w"]}px;'
            f'height:{c["h"]}px;border:2px dashed {cl};border-radius:6px;overflow:hidden;">'
            f'{inner}</div>\n'
        )

        # 범례
        if is_hier:
            primary_topic = topic_map.get(primary_tid)
            p_layer = primary_topic.layer if primary_topic and hasattr(primary_topic, 'layer') else ""
            legend_lines.append(
                f'• {role}: 꼭지{primary_tid}({p_layer}) + '
                f'{"+".join(f"꼭지{st}" for st in sup_tids)} → '
                f'<b>주종 관계 → {bid} 1개</b>'
            )
        else:
            for r in ref_list:
                t = topic_map.get(r.topic_id if hasattr(r, 'topic_id') else None)
                t_layer = t.layer if t and hasattr(t, 'layer') else ""
                legend_lines.append(f'• {role}: 꼭지{r.topic_id}({t_layer}) → <b>{r.block_id}</b>')

    css_block = "\n".join(all_block_css)
    legend_html = "<br>".join(legend_lines)

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>
*{{margin:0;padding:0;box-sizing:border-box;}}
body{{background:#e5e5e5;padding:10px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}}
:root{{--radius:6px;--line-height-ko:1.7;--color-accent:#2563eb;--color-primary:#1e293b;}}
{css_block}
</style></head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:4px;">Stage 1.8: SLOT 구조 + 블록 디자인 (재배분 후)</div>
<div style="font-size:11px;color:#666;margin-bottom:8px;">블록 디자인에 SLOT 마커 + 주종관계 표시. 다음 Stage 2에서 실제 콘텐츠로 채워짐.</div>
<div style="width:1280px;height:720px;background:white;position:relative;border:1px solid #ccc;">
{slide_body}
</div>
<div style="margin-top:16px;font-size:12px;color:#555;line-height:1.8;">
<b>블록 선택 근거 (layer 기반):</b><br>{legend_html}
</div></body></html>"""
    (steps_dir / "stage_1_8_blocks.html").write_text(html, encoding="utf-8")


def _gen_stage_2(ctx, steps_dir):
    """Stage 2 결과: 영역별 Sonnet 출력을 실제 렌더링하여 보여줌.
    각 역할(배경/본심/첨부/결론)의 HTML을 개별 컨테이너에 실제 렌더링."""
    gen = ctx.generated_html or {}
    sub_layouts = ctx.sub_layouts or {}
    ps = ctx.page_structure.roles

    # body_html에서 배경/본심 분리 (spacer로 구분)
    body_html = gen.get("body_html", "")
    sidebar_html = gen.get("sidebar_html", "")
    footer_html = gen.get("footer_html", "")

    # body_html = 배경 + spacer + 본심. spacer로 분리
    import re as _re
    spacer_pattern = r'<div style="height:\d+px;"></div>'
    body_parts = _re.split(spacer_pattern, body_html, maxsplit=1)
    bg_html = body_parts[0].strip() if len(body_parts) > 1 else ""
    core_html = body_parts[1].strip() if len(body_parts) > 1 else body_html.strip()

    # 역할별 HTML 매핑
    role_htmls = {}
    if bg_html and "배경" in ps:
        role_htmls["배경"] = bg_html
    if core_html and "본심" in ps:
        role_htmls["본심"] = core_html
    if sidebar_html and "첨부" in ps:
        role_htmls["첨부"] = sidebar_html
    if footer_html and "결론" in ps:
        role_htmls["결론"] = footer_html

    # 각 역할을 컨테이너 크기에 맞게 실제 렌더링
    fit = ctx.fit_result or {}
    redist = fit.get("redistribution", {})
    sections = []

    for role in ["배경", "본심", "첨부", "결론"]:
        rhtml = role_htmls.get(role, "")
        if not rhtml:
            continue
        cl = COLORS.get(role, "#333")
        ci = ctx.containers.get(role)
        if not ci:
            continue
        h = int(redist.get(role, ci.height_px))
        w = ci.width_px

        # sub_layout 정보
        layout = sub_layouts.get(role, {})
        scs = layout.get("sub_containers", [])
        sc_desc = " + ".join(f'{sc["name"]}({int(sc["width_px"])}×{int(sc["height_px"])}px)' for sc in scs) if scs else ""

        sections.append(
            f'<div style="margin-bottom:20px;">'
            f'<div style="font-size:13px;font-weight:700;color:{cl};margin-bottom:4px;">'
            f'{role} ({w}×{h}px)'
            f'{" — " + sc_desc if sc_desc else ""}</div>'
            f'<div style="width:{w}px;height:{h}px;border:2px solid {cl};border-radius:6px;'
            f'overflow:hidden;background:white;font-family:Pretendard Variable,sans-serif;'
            f'word-break:keep-all;">{rhtml}</div></div>'
        )

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}
body{{background:#e5e5e5;padding:10px;font-family:'Pretendard Variable','Noto Sans KR',sans-serif;word-break:keep-all;}}
:root{{--radius:6px;--line-height-ko:1.7;--color-accent:#2563eb;--color-primary:#1e293b;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:4px;">Stage 2: 영역별 HTML 생성 결과 (Sonnet)</div>
<div style="font-size:11px;color:#666;margin-bottom:12px;">각 역할의 Sonnet 출력을 컨테이너 크기에 맞게 실제 렌더링</div>
{"".join(sections)}
</body></html>"""
    (steps_dir / "stage_2.html").write_text(html, encoding="utf-8")


def _gen_stage_3(ctx, steps_dir):
    """Stage 3 결과: rendered_html을 별도 파일로 저장 + 링크.
    rendered_html 자체가 완성 HTML이므로 직접 렌더링 가능."""
    rendered = ctx.rendered_html or ""
    if rendered:
        # rendered_html을 별도 파일로 저장하여 브라우저에서 직접 확인 가능
        (steps_dir / "stage_3_rendered.html").write_text(rendered, encoding="utf-8")

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}
body{{background:#e5e5e5;padding:10px;font-family:sans-serif;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:4px;">Stage 3: 렌더링 조립 결과</div>
<div style="font-size:11px;color:#666;margin-bottom:8px;">Stage 2의 영역별 HTML을 슬라이드 프레임(CSS Grid)에 배치 + 후처리 적용</div>
<p style="margin-bottom:8px;"><a href="stage_3_rendered.html" style="font-size:16px;font-weight:700;">렌더링 결과 보기 (1280×720) →</a></p>
<p><a href="../final.html" style="font-size:14px;">final.html 보기 →</a></p>
<div style="margin-top:16px;font-size:12px;color:#555;">
Stage 3 후처리: sidebar width:100% 조정, 폰트 캡핑 (배경≤{ctx.font_hierarchy.bg}px, 첨부≤{ctx.font_hierarchy.sidebar}px), overflow 제거, bold 변환
</div>
</body></html>"""
    (steps_dir / "stage_3.html").write_text(html, encoding="utf-8")


# ══════════════════════════════════════
# Stage 4: 품질 게이트
# ══════════════════════════════════════

def _gen_stage_4(ctx, steps_dir):
    """Stage 4 결과: 측정값 + 품질 점수."""
    measurement = ctx.measurement or {}
    quality_score = ctx.quality_score if hasattr(ctx, 'quality_score') else "N/A"

    slide_m = measurement.get("slide", {})
    zones = measurement.get("zones", {})

    zone_rows = ""
    for zone_name, zone_data in zones.items():
        overflowed = zone_data.get("overflowed", False)
        excess = zone_data.get("excess_px", 0)
        client_h = zone_data.get("clientHeight", 0)
        scroll_h = zone_data.get("scrollHeight", 0)
        icon = "❌" if overflowed else "✅"
        bg = "#fee2e2" if overflowed else "#f0fdf4"
        zone_rows += (f'<tr style="background:{bg};"><td style="padding:6px 8px;">{icon} {zone_name}</td>'
                      f'<td style="padding:6px 8px;">{client_h}px</td>'
                      f'<td style="padding:6px 8px;">{scroll_h}px</td>'
                      f'<td style="padding:6px 8px;">{excess:+d}px</td></tr>\n')

    score_color = "#16a34a" if (isinstance(quality_score, (int, float)) and quality_score >= 80) else "#dc2626"

    html = f"""<!DOCTYPE html><html><head><meta charset="UTF-8">
<style>*{{margin:0;padding:0;box-sizing:border-box;}}body{{background:#e5e5e5;padding:10px;font-family:sans-serif;word-break:keep-all;}}</style>
</head><body>
<div style="font-size:16px;font-weight:bold;margin-bottom:8px;">Stage 4: 품질 게이트</div>
<div style="font-size:24px;font-weight:900;color:{score_color};margin-bottom:12px;">품질 점수: {quality_score}</div>
<div style="font-size:12px;color:#555;margin-bottom:4px;">슬라이드: clientHeight={slide_m.get("clientHeight", "?")}px, scrollHeight={slide_m.get("scrollHeight", "?")}px, overflow={slide_m.get("overflowed", "?")}</div>
<table style="border-collapse:collapse;font-size:12px;width:100%;max-width:600px;margin-top:8px;">
<tr style="background:#1e293b;color:white;"><th style="padding:8px;">영역</th><th style="padding:8px;">clientH</th><th style="padding:8px;">scrollH</th><th style="padding:8px;">excess</th></tr>{zone_rows}</table>
</body></html>"""
    (steps_dir / "stage_4.html").write_text(html, encoding="utf-8")
