#!/usr/bin/env python3
"""Build a self-contained human-review HTML from the capability truth tables."""

from __future__ import annotations

import csv
import html
import json
from collections import Counter
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
CATALOG = ROOT / "capability-catalog.csv"
MATRIX = ROOT / "competitor-matrix.csv"
OUTPUT = ROOT / "smart-canteen-competitor-capability-review.html"


def read_rows(path: Path) -> list[dict[str, str]]:
    with path.open(encoding="utf-8-sig", newline="") as handle:
        return list(csv.DictReader(handle))


def esc(value: object) -> str:
    return html.escape("" if value is None else str(value), quote=True)


def main() -> None:
    catalog = read_rows(CATALOG)
    matrix = read_rows(MATRIX)
    if len(catalog) != len(matrix):
        raise SystemExit("capability catalog and competitor matrix row counts differ")

    competitors = list(matrix[0].keys())[7:-1]
    matrix_by_id = {row["capability_id"]: row for row in matrix}
    domains = ["前厅", "后厨", "食安", "营养健康", "经营管理", "平台与集成", "交付运维", "智能硬件"]
    domain_counts = Counter(row["业务域"] for row in catalog)
    priority_counts = Counter(row["标准版优先级"] for row in catalog)

    cards = "".join(
        f'<button class="domain-card" type="button" data-domain="{esc(domain)}">'
        f'<span>{esc(domain)}</span><strong>{domain_counts[domain]}</strong><small>个功能点</small></button>'
        for domain in domains
    )
    competitor_headers = "".join(f'<th class="vendor">{esc(name)}</th>' for name in competitors)
    rows: list[str] = []
    for item in catalog:
        status_cells = []
        row_status = matrix_by_id[item["capability_id"]]
        for competitor in competitors:
            status = row_status[competitor]
            status_cells.append(
                f'<td class="status-cell"><span class="status {esc(status)}">{esc(status)}</span></td>'
            )
        search_text = " ".join(
            item[key]
            for key in ("capability_id", "业务域", "一级模块", "二级模块", "功能点", "功能定义", "主要用户角色", "客户价值")
        ).lower()
        rows.append(
            f'<tr data-domain="{esc(item["业务域"])}" data-priority="{esc(item["标准版优先级"])}" '
            f'data-search="{esc(search_text)}">'
            f'<td class="id">{esc(item["capability_id"])}</td>'
            f'<td><span class="domain-tag">{esc(item["业务域"])}</span></td>'
            f'<td>{esc(item["一级模块"])}</td>'
            f'<td>{esc(item["二级模块"])}</td>'
            f'<td class="feature"><strong>{esc(item["功能点"])}</strong><small>{esc(item["功能定义"])}</small></td>'
            f'<td>{esc(item["主要用户角色"])}</td>'
            f'<td>{esc(item["使用端/触点"])}</td>'
            f'<td>{esc(item["验收观察点"])}</td>'
            f'<td><span class="priority {esc(item["标准版优先级"])}">{esc(item["标准版优先级"])}</span></td>'
            + "".join(status_cells)
            + "</tr>"
        )

    template = """<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>智慧营养健康餐厅竞品产品功能明细分析</title>
<style>
:root{--navy:#17365d;--blue:#2f75b5;--orange:#ed7d31;--ink:#172033;--muted:#667085;--line:#d9e2f3;--paper:#fff;--bg:#f4f7fb;--green:#e2f0d9;--green-ink:#375e26;--amber:#fff2cc;--amber-ink:#9c6500;--red:#fce4d6;--red-ink:#9c0006;--gray:#e7e6e6;--gray-ink:#666}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:"Microsoft YaHei","PingFang SC",system-ui,sans-serif}.hero{background:linear-gradient(125deg,#122c4f 0%,#214d7d 72%,#2f75b5 100%);color:#fff;padding:48px max(32px,calc((100vw - 1480px)/2));border-bottom:6px solid var(--orange)}.eyebrow{font-size:13px;letter-spacing:.15em;text-transform:uppercase;color:#bed6f0}.hero h1{font-size:34px;line-height:1.25;margin:12px 0 14px;max-width:980px}.hero p{margin:0;max-width:980px;color:#d8e7f7;line-height:1.8}.wrap{max-width:1480px;margin:auto;padding:28px 28px 56px}.notice{display:grid;grid-template-columns:1.1fr 1fr 1fr;gap:14px;margin-bottom:20px}.notice>div{background:#fff;border:1px solid var(--line);border-radius:12px;padding:16px 18px}.notice strong{display:block;color:var(--navy);margin-bottom:6px}.notice p{margin:0;color:var(--muted);font-size:13px;line-height:1.7}.cards{display:grid;grid-template-columns:repeat(8,minmax(118px,1fr));gap:10px;margin:18px 0}.domain-card{appearance:none;border:1px solid var(--line);background:var(--paper);border-radius:12px;padding:14px;text-align:left;cursor:pointer;color:var(--ink);transition:.18s}.domain-card:hover,.domain-card.active{border-color:var(--orange);box-shadow:0 8px 24px rgba(23,54,93,.10);transform:translateY(-1px)}.domain-card span,.domain-card small{display:block}.domain-card strong{display:block;font-size:25px;color:var(--navy);margin:4px 0}.domain-card small{color:var(--muted)}.toolbar{position:sticky;top:0;z-index:20;background:rgba(244,247,251,.96);backdrop-filter:blur(9px);padding:13px 0;display:flex;gap:10px;align-items:center;flex-wrap:wrap}.toolbar input,.toolbar select,.toolbar button{height:40px;border:1px solid #c9d5e7;border-radius:8px;background:#fff;padding:0 12px;font:inherit}.toolbar input{min-width:320px;flex:1}.toolbar button{background:var(--navy);color:#fff;border-color:var(--navy);cursor:pointer}.count{font-size:13px;color:var(--muted);margin-left:auto}.table-shell{background:#fff;border:1px solid var(--line);border-radius:14px;overflow:auto;max-height:72vh;box-shadow:0 10px 28px rgba(23,54,93,.08)}table{border-collapse:separate;border-spacing:0;min-width:2860px;width:100%;font-size:12px}th,td{padding:10px 9px;border-right:1px solid var(--line);border-bottom:1px solid var(--line);vertical-align:top;line-height:1.55}thead th{position:sticky;top:0;z-index:5;background:var(--navy);color:#fff;text-align:left;white-space:nowrap}.vendor{text-align:center;background:#b85318}.id{white-space:nowrap;color:#59708e;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.feature{min-width:310px}.feature small{display:block;color:var(--muted);font-weight:400;margin-top:5px}.domain-tag,.priority,.status{display:inline-flex;align-items:center;justify-content:center;border-radius:999px;padding:3px 8px;white-space:nowrap;font-weight:700}.domain-tag{background:#d9eaf7;color:#1f4e78}.priority.P0{background:#fde6d6;color:#a94400}.priority.P1{background:#e9eef5;color:#53657c}.status-cell{text-align:center}.status.有{background:var(--green);color:var(--green-ink)}.status.部分具备{background:var(--amber);color:var(--amber-ink)}.status.暂无{background:var(--red);color:var(--red-ink)}.status.待核实,.status.不适用{background:var(--gray);color:var(--gray-ink)}tbody tr:hover td{background:#fffaf5}footer{padding-top:18px;color:var(--muted);font-size:12px;line-height:1.8}@media(max-width:900px){.notice{grid-template-columns:1fr}.cards{grid-template-columns:repeat(2,1fr)}.hero{padding:34px 22px}.hero h1{font-size:27px}.wrap{padding:20px 14px}.toolbar input{min-width:100%}}
</style>
</head>
<body>
<header class="hero"><div class="eyebrow">COMPETITIVE PRODUCT CAPABILITY ATLAS · 2026-08-10</div><h1>智慧营养健康餐厅竞品产品功能明细分析</h1><p>以“业务域 → 一级模块 → 二级模块 → 功能点”四层结构，统一承载康比特与 11 家友商的横向核验。当前版本是产品规划与竞调底表，不是厂商排名，也不把资料空白解释为“没有”。</p></header>
<main class="wrap">
<section class="notice"><div><strong>共 __TOTAL__ 个功能点</strong><p>覆盖前厅、后厨、食安、营养健康、经营管理、平台与集成、交付运维、智能硬件八大业务域。</p></div><div><strong>P0 __P0__ 项 / P1 __P1__ 项</strong><p>优先级是标准版产品规划建议，不代表康比特当前已经完成或已经验收。</p></div><div><strong>证据口径</strong><p>A=合同/中标/验收/真实系统，B=内部表格/项目材料，C=公开资料，D=结构推导；原表基线为 2025-11/12，需按当前版本复核。</p></div></section>
<section class="cards">__CARDS__</section>
<section class="toolbar"><input id="search" type="search" placeholder="搜索模块、功能点、定义、角色或验收点"><select id="domain"><option value="">全部业务域</option>__DOMAIN_OPTIONS__</select><select id="priority"><option value="">全部优先级</option><option>P0</option><option>P1</option></select><button id="reset" type="button">重置筛选</button><span class="count">显示 <b id="visible">__TOTAL__</b> / __TOTAL__ 项</span></section>
<section class="table-shell"><table><thead><tr><th>能力ID</th><th>业务域</th><th>一级模块</th><th>二级模块</th><th>功能点 / 定义</th><th>主要用户角色</th><th>使用端 / 触点</th><th>验收观察点</th><th>优先级</th>__VENDOR_HEADERS__</tr></thead><tbody id="body">__ROWS__</tbody></table></section>
<footer>状态词仅允许：有 / 部分具备 / 暂无 / 待核实 / 不适用。当前矩阵未获得证据的项目统一标为“待核实”。详细来源、原始行号、补证条件与完整业务规则请查看同目录 Excel 的“证据台账”“待补证清单”“功能明细”工作表。</footer>
</main>
<script>
const body=document.getElementById('body'),rows=[...body.rows],search=document.getElementById('search'),domain=document.getElementById('domain'),priority=document.getElementById('priority'),visible=document.getElementById('visible');
function apply(){const q=search.value.trim().toLowerCase(),d=domain.value,p=priority.value;let n=0;for(const row of rows){const ok=(!q||row.dataset.search.includes(q))&&(!d||row.dataset.domain===d)&&(!p||row.dataset.priority===p);row.hidden=!ok;if(ok)n++}visible.textContent=n;document.querySelectorAll('.domain-card').forEach(x=>x.classList.toggle('active',x.dataset.domain===d))}
[search,domain,priority].forEach(x=>x.addEventListener('input',apply));document.getElementById('reset').addEventListener('click',()=>{search.value='';domain.value='';priority.value='';apply()});document.querySelectorAll('.domain-card').forEach(x=>x.addEventListener('click',()=>{domain.value=domain.value===x.dataset.domain?'':x.dataset.domain;apply()}));
</script>
</body></html>"""
    replacements = {
        "__TOTAL__": str(len(catalog)),
        "__P0__": str(priority_counts["P0"]),
        "__P1__": str(priority_counts["P1"]),
        "__CARDS__": cards,
        "__DOMAIN_OPTIONS__": "".join(f'<option>{esc(domain)}</option>' for domain in domains),
        "__VENDOR_HEADERS__": competitor_headers,
        "__ROWS__": "\n".join(rows),
    }
    for marker, value in replacements.items():
        template = template.replace(marker, value)
    OUTPUT.write_text(template, encoding="utf-8")
    print(json.dumps({"output": str(OUTPUT), "rows": len(catalog), "competitors": competitors}, ensure_ascii=False))


if __name__ == "__main__":
    main()
