#!/usr/bin/env python3
"""Render the independent chain-term audit as a one-row-per-chain HTML report."""

from __future__ import annotations

import html
import json
from pathlib import Path


HERE = Path(__file__).resolve().parent
DATA = json.loads((HERE / "chain-term-audit-2026-08-21.json").read_text(encoding="utf-8"))
PROJECT = HERE.parents[1]
OUTPUT = PROJECT / "reports" / "2026-08-21-chain-catalog-domestic-audit.html"
PREVIEW = PROJECT / ".runtime" / "chain-catalog-review" / "index.html"


ORDER = {
    "복수 업체 공용 사용 확인": 0,
    "제한적 업계 사용 확인": 1,
    "특정 판매처식 세부명 가능성": 2,
    "사내 거래명만 확인": 3,
    "현재 사용 근거 불충분": 4,
}


CSS_CLASS = {
    "복수 업체 공용 사용 확인": "common",
    "제한적 업계 사용 확인": "limited",
    "특정 판매처식 세부명 가능성": "seller",
    "사내 거래명만 확인": "internal",
    "현재 사용 근거 불충분": "missing",
}


ACTION = {
    "복수 업체 공용 사용 확인": "이름 사용 가능",
    "제한적 업계 사용 확인": "실물 구조 확인 후 사용",
    "특정 판매처식 세부명 가능성": "공용 계열명으로 정규화 검토",
    "사내 거래명만 확인": "공급처에 명칭·실물 재확인",
    "현재 사용 근거 불충분": "현재 이름은 보류",
}


def esc(value) -> str:
    return html.escape(str(value), quote=True)


def link_list(items: list[dict], empty: str, raw: bool = False) -> str:
    if not items:
        return f'<span class="empty">{esc(empty)}</span>'
    rendered = []
    for item in items:
        source = item.get("seller") or item.get("domain")
        badge = "새 발견" if item.get("newly_found") else item.get("evidence_kind", "기존 확인")
        badge_class = "new" if item.get("newly_found") else ("raw" if raw else "use")
        rendered.append(
            f'''<li><a href="{esc(item['url'])}" target="_blank" rel="noopener noreferrer"><strong>{esc(source)}</strong><span aria-hidden="true">↗</span></a><span class="badge {badge_class}">{esc(badge)}</span><small>{esc(item['title'])}</small></li>'''
        )
    return f'<ul class="links">{"".join(rendered)}</ul>'


def row_markup(row: dict, display_index: int) -> str:
    status = row["terminology_status"]
    css = CSS_CLASS[status]
    aliases = " / ".join(row["aliases"]) or "—"
    suppliers = "".join(f'<span class="supplier">{esc(name)}</span>' for name in row["internal_supplier_names"])
    if not suppliers:
        suppliers = '<span class="empty">없음</span>'
    search = " ".join(
        [
            row["name"], aliases, status, row["terminology_reason"],
            " ".join(item.get("seller", "") for item in row["raw_procurement_listings"]),
            " ".join(row["internal_supplier_names"]),
        ]
    ).lower()
    return f'''<tr data-status="{css}" data-search="{esc(search)}">
      <td class="number">{display_index:02d}</td>
      <td class="identity" data-label="체인명 · 별칭"><strong>{esc(row['name'])}</strong><span>{esc(aliases)}</span><details><summary>검색어 {len(row['searched_terms'])}개</summary><small>{esc(' · '.join(row['searched_terms']))}</small></details></td>
      <td class="decision" data-label="용어 판정"><span class="status {css}">{esc(status)}</span><strong>{esc(ACTION[status])}</strong></td>
      <td class="reason" data-label="판정 이유">{esc(row['terminology_reason'])}</td>
      <td data-label="국내 웹 사용 근거">{link_list(row['term_evidence'], '유효한 국내 웹 사용 근거 없음')}</td>
      <td data-label="공개 원재료 · 주문 페이지">{link_list(row['raw_procurement_listings'], '공개 원재료 페이지 미확인', raw=True)}</td>
      <td data-label="TN 실제 공급처"><div class="suppliers">{suppliers}</div></td>
      <td class="rights" data-label="게시 시 처리">판매처 자산 비복제<br><small>독자 촬영·별도 생성 이미지만 사용</small></td>
    </tr>'''


def main() -> None:
    rows = sorted(DATA["rows"], key=lambda row: (ORDER[row["terminology_status"]], row["index"]))
    counts = DATA["status_counts"]
    common = counts["복수 업체 공용 사용 확인"]
    limited = counts["제한적 업계 사용 확인"]
    seller = counts["특정 판매처식 세부명 가능성"]
    internal = counts["사내 거래명만 확인"]
    missing = counts["현재 사용 근거 불충분"]
    new_raw_rows = sum(bool(row["new_raw_supplier_count"]) for row in rows)
    new_raw_links = sum(row["new_raw_supplier_count"] for row in rows)
    table_rows = "\n".join(row_markup(row, index) for index, row in enumerate(rows, 1))
    document = f'''<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="color-scheme" content="light">
  <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%23126b77'/%3E%3Cpath d='M8 11h16M8 16h16M8 21h16' stroke='white' stroke-width='2.5'/%3E%3C/svg%3E">
  <title>TN 체인 명칭 국내 업계 사용성 조사</title>
  <style>
    :root{{--ink:#233033;--soft:#51626a;--muted:#566b71;--bg:#eef4f5;--surface:#f8fbfb;--line:#d9e4e6;--accent:#126b77;--accent-soft:#d8ecef;--good:#1d6b59;--warn:#8b5d20;--bad:#934257;--purple:#685181;--font:"Pretendard","Apple SD Gothic Neo","Malgun Gothic",system-ui,-apple-system,sans-serif;--shadow:0 12px 40px rgba(35,48,51,.08)}}
    *{{box-sizing:border-box}}html{{scroll-behavior:smooth}}body{{margin:0;min-width:320px;background:var(--bg);color:var(--ink);font-family:var(--font);line-height:1.55}}a{{color:inherit}}button,input,select{{font:inherit}}a:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible{{outline:3px solid rgba(18,107,119,.28);outline-offset:2px}}
    .page{{width:min(1800px,calc(100% - 40px));margin:0 auto;padding:36px 0 72px}}.masthead{{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:24px;align-items:end;padding-bottom:24px;border-bottom:1px solid var(--line)}}.eyebrow{{margin:0 0 6px;color:var(--accent);font-size:12px;font-weight:800;letter-spacing:.12em;text-transform:uppercase}}h1{{margin:0;font-size:clamp(30px,4vw,52px);line-height:1.06;letter-spacing:-.045em}}.lead{{max-width:1000px;margin:14px 0 0;color:var(--soft);font-size:15px}}.meta{{color:var(--soft);font-size:12px;text-align:right}}.meta strong{{display:block;color:var(--ink);font-size:14px}}
    .stats{{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:8px;margin:18px 0}}.stat{{padding:13px 14px;border:1px solid var(--line);border-radius:10px;background:var(--surface)}}.stat strong{{display:block;font-size:27px;line-height:1.1}}.stat span{{display:block;margin-top:4px;color:var(--soft);font-size:12px}}.stat.good strong{{color:var(--good)}}.stat.warn strong{{color:var(--warn)}}.stat.bad strong{{color:var(--bad)}}
    .finding{{display:grid;grid-template-columns:1.1fr .9fr;gap:10px;margin-bottom:14px}}.card{{padding:16px 18px;border:1px solid var(--line);border-radius:12px;background:var(--surface)}}.card h2{{margin:0 0 7px;font-size:17px}}.card p,.card li{{margin:0;color:var(--soft);font-size:12.5px}}.card ul{{margin:7px 0 0;padding-left:18px}}.card strong{{color:var(--ink)}}
    .controls{{position:sticky;z-index:10;top:8px;display:grid;grid-template-columns:minmax(260px,1.4fr) minmax(250px,.7fr) auto;gap:8px;margin:14px 0 8px;padding:9px;border:1px solid var(--line);border-radius:12px;background:rgba(248,251,251,.95);box-shadow:var(--shadow);backdrop-filter:blur(12px)}}input,select{{min-height:43px;border:1px solid var(--line);border-radius:8px;background:#fff;color:var(--ink);padding:9px 11px}}.visible-count{{display:flex;align-items:center;justify-content:flex-end;min-width:120px;color:var(--soft);font-size:13px}}.visible-count strong{{margin-right:3px;color:var(--ink);font-size:18px}}.sort-note{{margin:0 0 10px;color:var(--soft);font-size:12px}}.sort-note strong{{color:var(--ink)}}
    .table-wrap{{overflow:auto;border:1px solid var(--line);border-radius:12px;background:var(--surface);box-shadow:var(--shadow)}}table{{width:100%;min-width:1360px;border-collapse:separate;border-spacing:0;table-layout:fixed;font-size:12px}}col.no{{width:42px}}col.identity{{width:145px}}col.decision{{width:165px}}col.reason{{width:205px}}col.web{{width:255px}}col.raw{{width:285px}}col.internal{{width:120px}}col.rights{{width:140px}}thead th{{position:sticky;z-index:2;top:0;padding:11px 8px;border-bottom:1px solid var(--line);background:#edf5f6;color:var(--soft);text-align:left;font-size:11px}}tbody td{{vertical-align:top;padding:12px 8px;border-bottom:1px solid var(--line);overflow-wrap:anywhere}}tbody tr:last-child td{{border-bottom:0}}tbody tr:hover{{background:#f2f8f8}}tr[hidden]{{display:none}}.number{{color:var(--muted);font-variant-numeric:tabular-nums}}.identity>strong{{display:block;font-size:14px}}.identity>span{{display:block;margin-top:3px;color:var(--soft)}}details{{margin-top:7px;color:var(--muted)}}summary{{cursor:pointer;font-size:11px}}details small{{display:block;margin-top:4px;line-height:1.4}}.decision>strong{{display:block;margin-top:6px;font-size:12px}}.reason{{color:var(--soft)}}
    .status,.badge,.supplier{{display:inline-flex;align-items:center;border-radius:999px;padding:3px 7px;font-size:10.5px;line-height:1.25}}.status.common{{background:#dcefe8;color:#246b58}}.status.limited{{background:#e3eef5;color:#3e6680}}.status.seller{{background:#fff0d8;color:#875b1e}}.status.internal{{background:#ece7f5;color:#685181}}.status.missing{{background:#f7e3e7;color:#93485b}}.links{{display:grid;gap:9px;margin:0;padding:0;list-style:none}}.links li{{padding-left:9px;border-left:2px solid var(--line)}}.links a{{text-decoration:none}}.links a:hover strong{{text-decoration:underline}}.links a span{{margin-left:3px;color:var(--accent)}}.links small{{display:block;margin-top:3px;color:var(--soft);line-height:1.35}}.badge{{margin-left:5px;padding:2px 5px;background:var(--accent-soft);color:#346c74}}.badge.new{{background:#fff0d8;color:#875b1e}}.badge.raw{{background:#e7edf4;color:#425d79}}.suppliers{{display:flex;flex-wrap:wrap;gap:5px}}.supplier{{background:#e7edf4;color:#425d79}}.empty{{color:var(--muted)}}.rights{{color:var(--soft)}}.rights small{{color:var(--muted)}}
    .notes{{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px}}.notes h2{{margin:0 0 6px;font-size:15px}}.raw-link{{display:inline-flex;margin-top:10px;color:var(--accent);font-weight:700;text-decoration:none}}
    @media(max-width:1200px){{.page{{width:min(100% - 20px,1800px);padding-top:22px}}.masthead,.finding,.notes{{grid-template-columns:1fr}}.meta{{text-align:left}}.stats{{grid-template-columns:repeat(2,minmax(0,1fr))}}.controls{{position:static;grid-template-columns:1fr}}.visible-count{{justify-content:flex-start}}.table-wrap{{overflow:visible;border:0;background:transparent;box-shadow:none}}table{{display:block;min-width:0}}colgroup,thead{{display:none}}tbody{{display:grid;gap:10px}}tbody tr{{display:grid;grid-template-columns:34px minmax(0,1fr);padding:11px 12px;border:1px solid var(--line);border-radius:10px;background:var(--surface);box-shadow:0 5px 18px rgba(35,48,51,.05)}}tbody td{{display:block;grid-column:2;padding:9px 0;border-bottom:1px solid var(--line)}}tbody td:last-child{{border-bottom:0}}tbody td.number{{grid-column:1;grid-row:1;padding-top:1px;border:0}}tbody td.identity{{grid-column:2;grid-row:1;padding-top:0}}tbody td[data-label]::before{{display:block;margin-bottom:4px;color:var(--muted);font-size:10px;font-weight:800;letter-spacing:.02em;content:attr(data-label)}}tbody td.identity::before{{display:none}}.links{{gap:7px}}}}
    @media print{{@page{{size:A4 portrait;margin:10mm}}body{{background:#fff;font-size:9pt}}.page{{width:100%;padding:0}}.controls{{display:none}}.stats{{grid-template-columns:repeat(3,1fr)}}.finding,.notes{{grid-template-columns:1fr}}.table-wrap{{overflow:visible;border:0;box-shadow:none}}table{{min-width:0;font-size:6.5pt;table-layout:auto}}thead th{{position:static}}tbody tr,.card,.stat{{break-inside:avoid}}}}
  </style>
</head>
<body>
<main class="page">
  <header class="masthead">
    <div><p class="eyebrow">TN Silver · 명칭 독립 검색</p><h1>체인명 77개 국내 업계 사용성 조사</h1><p class="lead"><strong>결론:</strong> {common}개는 서로 다른 국내 업체가 같은 이름을 사용했고, {limited}개는 공개 사례와 TN 공급처 기록을 함께 확인했다. {seller}개는 정확한 세부명이 한 판매처에만 연결되어 그대로 게시하기 전에 명칭 정규화가 필요하다.</p></div>
    <p class="meta"><strong>조사일 2026-08-21</strong>정식명·별칭 전부 개별 검색</p>
  </header>
  <section class="stats" aria-label="조사 요약">
    <div class="stat"><strong>77</strong><span>전체 후보</span></div><div class="stat good"><strong>{common}</strong><span>① 복수 업체 공용 사용</span></div><div class="stat good"><strong>{limited}</strong><span>② 제한적 업계 사용</span></div><div class="stat warn"><strong>{seller}</strong><span>③ 판매처식 세부명 가능</span></div><div class="stat warn"><strong>{internal}</strong><span>④ 사내 거래명만</span></div><div class="stat bad"><strong>{missing}</strong><span>⑤ 사용 근거 불충분</span></div>
  </section>
  <section class="finding">
    <article class="card"><h2>이번에는 쇼핑몰 목록에서 출발하지 않았다</h2><p><strong>77개 이름과 모든 별칭을 각각 국내 일반 웹에서 검색했다.</strong> 서로 다른 사업체가 같은 명칭을 쓰는지 확인했고, 동일 브랜드의 여러 입점몰·같은 회사의 복수 도메인·주얼리와 무관한 동명이의는 근거에서 제외했다.</p><ul><li>기존 5개 몰 밖에서 {new_raw_rows}개 체인의 원재료 페이지 {new_raw_links}개를 새로 확인했다.</li><li>POM과 체인북은 공개 판매처로 세지 않고 사내 근거로만 분리했다.</li></ul></article>
    <article class="card"><h2>‘공용 사용’의 정확한 뜻</h2><p><strong>같은 이름이 여러 국내 사업체에서 실제로 쓰인다는 뜻이다.</strong> 법정 표준명이나 구조 규격이 완전히 같다는 뜻은 아니다. 특히 컷팅·루루·물방울·십자볼 같은 세부형은 이미지 생성 전에 링크별 구조를 다시 맞춰야 한다.</p></article>
  </section>
  <div class="controls" aria-label="표 필터">
    <input id="query" type="search" placeholder="체인명·별칭·판매처·사내 공급처 검색" aria-label="체인 검색">
    <select id="status" aria-label="명칭 판정"><option value="all">모든 판정</option><option value="common">복수 업체 공용 사용</option><option value="limited">제한적 업계 사용</option><option value="seller">판매처식 세부명 가능성</option><option value="internal">사내 거래명만</option><option value="missing">사용 근거 불충분</option></select>
    <div class="visible-count"><strong id="visible">77</strong> / 77개 표시</div>
  </div>
  <p class="sort-note"><strong>표시 순서:</strong> ① 복수 업체 공용 사용 → ② 제한적 업계 사용 → ③ 특정 판매처식 세부명 가능성 → ④ 사내 거래명만 → ⑤ 사용 근거 불충분</p>
  <div class="table-wrap" tabindex="0" aria-label="체인명별 국내 업계 사용성 조사 표">
    <table><colgroup><col class="no"><col class="identity"><col class="decision"><col class="reason"><col class="web"><col class="raw"><col class="internal"><col class="rights"></colgroup>
      <thead><tr><th>No.</th><th>체인명 · 별칭</th><th>용어 판정</th><th>판정 이유</th><th>국내 웹 사용 근거</th><th>공개 원재료 · 주문 페이지</th><th>TN 실제 공급처</th><th>게시 시 처리</th></tr></thead>
      <tbody>{table_rows}</tbody>
    </table>
  </div>
  <section class="notes">
    <article class="card"><h2>판정에서 제외한 것</h2><ul><li>동일 브랜드 상품이 11번가·롯데온·SSG 등에 반복 입점한 것은 한 사용으로 판단했다.</li><li>한국아트체인 회사명, 스파커 회사·게임·장비, 타래체인 가방처럼 주얼리 구조와 무관한 동명이의는 제외했다.</li><li>해외 판매 페이지만 있는 명칭은 국내 업계 사용 근거로 채택하지 않았다.</li></ul></article>
    <article class="card"><h2>다음 선별 때 볼 것</h2><ul><li>①·②는 이름 후보로 유지하되 각 링크의 구조가 같은지 사진으로 대조한다.</li><li>③은 기본 계열명+구조 설명으로 이름을 바꿀지 검토한다.</li><li>④는 실제 공급처에 현재 주문명과 실물을 확인하고, ⑤는 우선 보류한다.</li></ul><a class="raw-link" href="/.runtime/chain-catalog-data/chain-term-audit-2026-08-21.json">JSON 원자료 보기 →</a></article>
  </section>
</main>
<script>
  const query=document.querySelector('#query');const status=document.querySelector('#status');const rows=[...document.querySelectorAll('tbody tr')];const visible=document.querySelector('#visible');
  function filter(){{const term=query.value.trim().toLowerCase();const state=status.value;let count=0;for(const row of rows){{const show=(!term||row.dataset.search.includes(term))&&(state==='all'||row.dataset.status===state);row.hidden=!show;if(show)count++}}visible.textContent=count}}
  query.addEventListener('input',filter);status.addEventListener('change',filter);
</script>
</body>
</html>'''
    OUTPUT.parent.mkdir(parents=True, exist_ok=True)
    PREVIEW.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT.write_text(document, encoding="utf-8")
    PREVIEW.write_text(document, encoding="utf-8")
    print(f"wrote {OUTPUT}")
    print(f"wrote {PREVIEW}")


if __name__ == "__main__":
    main()
