#!/usr/bin/env python3
"""Serve the SupplyAgent business-design visualizations locally."""

from __future__ import annotations

import argparse
import html
import mimetypes
import threading
import webbrowser
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import unquote, urlparse


VISUALIZATION_DIR = Path(__file__).resolve().parents[1]
PAGES_DIR = VISUALIZATION_DIR / "pages"

PAGES: tuple[tuple[str, str, str], ...] = (
    (
        "全局分类树-需求热度与关系分析.html",
        "00 · 平台全局需求地图",
        "用全量冰柱热力图呈现冷热分布，并从上游数据直接形成数百条平台需求和可追溯 Reason。",
    ),
    (
        "graph-structure-options.html",
        "01 · 图结构方案比较",
        "比较单一大树、自由图和树骨架关系图。",
    ),
    (
        "graph-structure-left-to-right.html",
        "02 · 从左到右的树图结构",
        "展示主题、分类树、具体节点、平台需求和证据的阅读方向。",
    ),
    (
        "business-graph-model.html",
        "03 · 业务图节点与边",
        "展示分类节点、需求元素、平台需求、关系和证据。",
    ),
    (
        "demand-feedback-loop.html",
        "04 · 需求—内容—表现闭环",
        "展示真实线上表现如何反向更新需求强度。",
    ),
    (
        "final-output-structure.html",
        "05 · 最终输出结构",
        "展示主题方向、平台需求和证据卡的组织方式。",
    ),
    (
        "node-drilldown-evidence-chain.html",
        "06 · 节点下钻证据链",
        "展示分类节点如何下挂元素、视频、短观点、长讨论并提取平台需求。",
    ),
    (
        "waiting-business-scope.html",
        "过程页 A · 业务范围确认",
        "对话过程中的可视化等待页。",
    ),
    (
        "waiting-demand-pipeline.html",
        "过程页 B · 需求流水线确认",
        "对话过程中的可视化等待页。",
    ),
    (
        "waiting-strength-lifecycle.html",
        "过程页 C · 强度生命周期确认",
        "对话过程中的可视化等待页。",
    ),
)


BASE_CSS = """
* { box-sizing: border-box; }
html, body { min-height: 100%; }
body {
  margin: 0;
  color: #0f172a;
  background: #f5f7fb;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
    "Segoe UI", sans-serif;
  line-height: 1.5;
}
a { color: inherit; }
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 12px 24px;
  color: #e2e8f0;
  background: rgba(15, 23, 42, .96);
  border-bottom: 1px solid #334155;
}
.topbar a { text-decoration: none; }
.topbar .brand { font-weight: 700; color: #fff; }
.topbar .meta { color: #94a3b8; font-size: 13px; }
.content {
  width: min(1480px, calc(100% - 32px));
  margin: 0 auto;
  padding: 28px 0 48px;
}
h1, h2, h3 { line-height: 1.25; }
h2 { margin: 0 0 8px; font-size: 26px; }
.subtitle { color: #64748b; margin: 0 0 20px; }
.options { display: flex; flex-direction: column; gap: 12px; }
.option, .card { cursor: pointer; }
.option.selected, .card.selected {
  border-color: #2563eb !important;
  box-shadow: 0 0 0 3px rgba(37, 99, 235, .14) !important;
}
.gallery-title { margin: 4px 0 8px; font-size: 30px; }
.gallery-intro { max-width: 880px; color: #64748b; margin: 0 0 24px; }
.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: 16px;
}
.gallery-card {
  display: block;
  padding: 18px;
  text-decoration: none;
  background: #fff;
  border: 1px solid #dbe3ec;
  border-radius: 15px;
  transition: .18s ease;
}
.gallery-card:hover {
  transform: translateY(-2px);
  border-color: #60a5fa;
  box-shadow: 0 12px 28px rgba(15, 23, 42, .09);
}
.gallery-card h3 { margin: 0 0 7px; font-size: 17px; }
.gallery-card p { margin: 0; color: #64748b; font-size: 13px; }
.reference {
  margin-top: 24px;
  padding: 18px;
  border: 1px solid #dbe3ec;
  border-radius: 15px;
  background: #fff;
}
.reference img {
  display: block;
  width: min(100%, 650px);
  height: auto;
  margin-top: 12px;
  border: 1px solid #e2e8f0;
}
"""


def _page_metadata(filename: str) -> tuple[str, str] | None:
    for item_filename, title, description in PAGES:
        if filename == item_filename:
            return title, description
    return None


def _document(title: str, body: str, *, subtitle: str = "") -> bytes:
    subtitle_html = (
        f'<div class="meta">{html.escape(subtitle)}</div>' if subtitle else ""
    )
    document = f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,">
  <title>{html.escape(title)} · SupplyAgent</title>
  <style>{BASE_CSS}</style>
</head>
<body>
  <header class="topbar">
    <a class="brand" href="/">SupplyAgent 业务可视化</a>
    {subtitle_html}
  </header>
  <main class="content">{body}</main>
  <script>
    window.toggleSelect = function (el) {{
      const container = el.closest('.options') || el.closest('.cards') || el.parentElement;
      if (container) {{
        container.querySelectorAll('[data-choice]').forEach(function (item) {{
          item.classList.remove('selected');
        }});
      }}
      el.classList.add('selected');
    }};
  </script>
</body>
</html>"""
    return document.encode("utf-8")


def _gallery() -> bytes:
    cards = []
    for filename, title, description in PAGES:
        cards.append(
            f"""<a class="gallery-card" href="/view/{html.escape(filename)}">
  <h3>{html.escape(title)}</h3>
  <p>{html.escape(description)}</p>
</a>"""
        )
    body = f"""
<h1 class="gallery-title">SupplyAgent 业务框架可视化</h1>
<p class="gallery-intro">
  本目录保存需求汇总业务设计过程中形成的全部可视化材料。
  正式业务结论以 README_myself.md 为准；图稿用于解释树、图、需求、内容和反馈之间的关系。
</p>
<section class="gallery">{"".join(cards)}</section>
<section class="reference">
  <h2>用户提供的参考图</h2>
  <p class="subtitle">用于理解全局分类树、分类节点下挂元素，以及元素如何扩展成明确语言和长段讨论。</p>
  <a href="/assets/tree.jpeg" target="_blank" rel="noreferrer"><img src="/assets/tree.jpeg" alt="全局分类树局部参考图"></a>
  <a href="/assets/tree2.png" target="_blank" rel="noreferrer"><img src="/assets/tree2.png" alt="分类节点下挂元素参考图"></a>
  <a href="/assets/case.jpeg" target="_blank" rel="noreferrer"><img src="/assets/case.jpeg" alt="元素扩展为语言和讨论参考图"></a>
  <a href="/assets/liubai.png" target="_blank" rel="noreferrer"><img src="/assets/liubai.png" alt="全局需求地图留白调整参考图"></a>
</section>
"""
    return _document("可视化目录", body, subtitle="本地设计资料")


class VisualizationHandler(SimpleHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, directory=str(VISUALIZATION_DIR), **kwargs)

    def do_GET(self) -> None:  # noqa: N802
        path = unquote(urlparse(self.path).path)
        if path == "/":
            self._send_html(_gallery())
            return

        if path.startswith("/view/"):
            filename = Path(path.removeprefix("/view/")).name
            metadata = _page_metadata(filename)
            page_path = PAGES_DIR / filename
            if metadata is None or not page_path.is_file():
                self.send_error(404, "Visualization not found")
                return
            title, description = metadata
            fragment = page_path.read_text(encoding="utf-8")
            self._send_html(_document(title, fragment, subtitle=description))
            return

        super().do_GET()

    def _send_html(self, payload: bytes) -> None:
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(payload)))
        self.end_headers()
        self.wfile.write(payload)

    def log_message(self, format: str, *args: object) -> None:
        print(f"[visualization] {self.address_string()} - {format % args}")


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--host", default="127.0.0.1")
    parser.add_argument("--port", type=int, default=8765)
    parser.add_argument("--open", action="store_true", dest="open_browser")
    args = parser.parse_args()

    mimetypes.add_type("image/jpeg", ".jpeg")
    server = ThreadingHTTPServer((args.host, args.port), VisualizationHandler)
    url_host = "localhost" if args.host in {"127.0.0.1", "0.0.0.0"} else args.host
    url = f"http://{url_host}:{server.server_port}/"
    print(f"SupplyAgent visualization server: {url}")

    if args.open_browser:
        threading.Timer(0.3, webbrowser.open, args=(url,)).start()

    try:
        server.serve_forever()
    except KeyboardInterrupt:
        pass
    finally:
        server.server_close()


if __name__ == "__main__":
    main()
