"""자체 완결 HTML 리포트 — 표 중심, 프롬프트는 줄 발췌만.

측정 정의 라벨(metrics/attribute 의 정의와 일치):
- 발송량은 전수 합(외삽 없음), 단위는 '자'(code point).
- '정적' = 그 스텝 호출의 90% 이상에 실린 줄.
"""
import html
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List

CSS = """
body{font-family:'Apple SD Gothic Neo',sans-serif;margin:24px;
     background:#fafafa;color:#222}
h1{font-size:22px} h2{font-size:17px;margin-top:32px}
table{border-collapse:collapse;background:#fff;font-size:13px}
th,td{border:1px solid #ddd;padding:4px 8px;text-align:right}
th{background:#f0f0f0} td.l,th.l{text-align:left}
td.line{font-family:monospace;font-size:11px;max-width:680px;
        overflow-wrap:anywhere;text-align:left}
.warn{color:#b00} .dim{color:#888}
"""


def _esc(s: object) -> str:
    return html.escape(str(s))


def render(
    out_dir: Path,
    meta: Dict[str, Any],
    steps: Dict[str, Dict[str, Any]],
    dups: List[Dict[str, Any]],
    curves: Dict[str, List[Dict[str, Any]]],
    attributions: Dict[str, Dict[str, Any]],
) -> Path:
    out_dir.mkdir(parents=True, exist_ok=True)
    rows = sorted(steps.items(), key=lambda kv: -kv[1]["total_chars"])
    grand = sum(r["total_chars"] for r in steps.values())

    parts: List[str] = []
    parts.append('<meta charset="utf-8">')
    parts.append("<title>Opik 프롬프트 감사</title>")
    parts.append(f"<style>{CSS}</style>")
    parts.append("<h1>Opik 역추적 프롬프트 감사</h1>")
    parts.append(
        f"<p class=dim>창: {_esc(meta.get('since'))} ~ "
        f"{_esc(meta.get('until', ''))} · trace {meta.get('trace_count')}건"
        f" · 총 발송(전수 합) {grand:,}자 · 생성 "
        f"{datetime.now(timezone.utc).isoformat(timespec='seconds')}</p>")
    parts.append(
        "<p class=dim>측정 정의: 단위는 자(code point, wire byte·token "
        "아님) · trace=Opik 기록 1건(provider retry 미반영) · 정적=그 "
        "스텝 호출의 90% 이상에 실린 줄 · system/user 역할만 집계.</p>")

    parts.append("<h2>① 스텝별 발송량 (전수 합, 내림차순)</h2>")
    parts.append(
        "<table><tr><th class=l>스텝</th><th>호출</th><th>sys 평균</th>"
        "<th>usr 평균</th><th>sys 정적</th><th>usr 정적</th>"
        "<th>총량(자)</th><th>비중</th></tr>")
    for step, r in rows:
        share = r["total_chars"] / grand * 100 if grand else 0
        parts.append(
            f"<tr><td class=l>{_esc(step)}</td><td>{r['calls']}</td>"
            f"<td>{r['system_avg']:,}</td><td>{r['user_avg']:,}</td>"
            f"<td>{r['system_static_ratio']}</td>"
            f"<td>{r['user_static_ratio']}</td>"
            f"<td>{r['total_chars']:,}</td><td>{share:.1f}%</td></tr>")
    parts.append("</table>")

    parts.append("<h2>② 교차 스텝 중복 조각 (노출량 상위 25)</h2>")
    parts.append(
        "<p class=dim>role 이 user 뿐인 행은 여러 스텝이 공유한 입력 "
        "데이터(시나리오 전문 등)일 수 있다 — 규칙 중복과 다른 부류이니 "
        "발췌를 보고 가를 것.</p>")
    parts.append(
        "<table><tr><th class=l>문장(발췌)</th><th class=l>role</th>"
        "<th>스텝 수</th><th class=l>스텝</th><th>노출량(자)</th></tr>")
    for d in dups[:25]:
        parts.append(
            f"<tr><td class=line>{_esc(d['line'][:220])}</td>"
            f"<td class=l>{_esc('+'.join(d['roles']))}</td>"
            f"<td>{d['step_count']}</td>"
            f"<td class=l>{_esc(', '.join(d['steps'][:6]))}"
            f"{' …' if len(d['steps']) > 6 else ''}</td>"
            f"<td>{d['exposure_chars']:,}</td></tr>")
    parts.append("</table>")

    parts.append("<h2>③ 팩 버전 성장 추이 (표시 구간 첫→끝 Δ 큰 순)</h2>")
    parts.append(
        "<p class=dim>bytes=버전 디렉토리 안 stem 파일 전체 합(schema "
        "포함, os byte). system.md 열은 그 stem 단독. Δ는 아래 나열된 "
        "구간(최근 5버전)의 첫→끝 차이다.</p>")
    grown = []
    for module, rs in curves.items():
        tail = rs[-5:]
        delta = tail[-1]["bytes"] - tail[0]["bytes"]
        sys_delta = (tail[-1]["stems"].get("system.md", 0)
                     - tail[0]["stems"].get("system.md", 0))
        grown.append((delta, sys_delta, module, tail))
    grown.sort(reverse=True)
    parts.append(
        "<table><tr><th class=l>module</th><th class=l>버전(월일): 합계"
        "</th><th>구간 Δ(합계)</th><th>구간 Δ(system.md)</th></tr>")
    for delta, sys_delta, module, tail in grown[:20]:
        seq = " → ".join(
            f"v{r['version']}({r['ts'][4:8]}): {r['bytes']:,}"
            for r in tail)
        cls = " class=warn" if delta > 0 else ""
        parts.append(
            f"<tr><td class=l>{_esc(module)}</td><td class=l>{_esc(seq)}"
            f"</td><td{cls}>{delta:+,}</td><td>{sys_delta:+,}</td></tr>")
    parts.append("</table>")

    parts.append("<h2>④ 스텝별 정적분의 팩 귀속 (발송량 상위 스텝)</h2>")
    parts.append(
        "<p class=dim>한계: 현재 checkout 최신 팩만 색인(당시 활성판·"
        "오버레이 미반영) · exact-line 일치 · 같은 줄이 여러 팩에 있으면 "
        "정렬상 첫 파일로 귀속. 40자 미만 줄은 귀속 대상 밖.</p>")
    for step, r in rows[:8]:
        att = attributions.get(step)
        if not att:
            continue
        parts.append(f"<h3 style='font-size:14px'>{_esc(step)}</h3>")
        parts.append(
            "<table><tr><th class=l>팩 파일(module/version/stem)</th>"
            "<th>정적 자</th></tr>")
        for src, b in list(att["pack_bytes"].items())[:6]:
            parts.append(
                f"<tr><td class=l>{_esc(src)}</td><td>{b:,}</td></tr>")
        parts.append(
            f"<tr><td class='l dim'>팩 색인 밖(코드 내장·조립 산문 등)"
            f"</td><td>{att['unmatched_bytes']:,}</td></tr>")
        parts.append("</table>")

    out = out_dir / "report.html"
    out.write_text("\n".join(parts), encoding="utf-8")
    return out
