#!/usr/bin/env python3
"""Render a small, self-contained Markdown report to browser-friendly HTML."""

from __future__ import annotations

import argparse
import html
import re
from pathlib import Path


def slugify(text: str, used: set[str]) -> str:
    slug = re.sub(r"`([^`]+)`", r"\1", text).strip().lower()
    slug = re.sub(r"[^\w\u4e00-\u9fff]+", "-", slug, flags=re.UNICODE).strip("-")
    if not slug:
        slug = "section"
    base = slug
    counter = 2
    while slug in used:
        slug = f"{base}-{counter}"
        counter += 1
    used.add(slug)
    return slug


def parse_inline(text: str) -> str:
    parts = re.split(r"(`[^`]*`)", text)
    rendered: list[str] = []
    for part in parts:
        if part.startswith("`") and part.endswith("`"):
            rendered.append(f"<code>{html.escape(part[1:-1])}</code>")
            continue

        escaped = html.escape(part)
        escaped = re.sub(
            r"\[([^\]]+)\]\(([^)]+)\)",
            lambda m: (
                f'<a href="{html.escape(m.group(2), quote=True)}">'
                f"{html.escape(m.group(1))}</a>"
            ),
            escaped,
        )
        escaped = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", escaped)
        escaped = re.sub(r"\*([^*]+)\*", r"<em>\1</em>", escaped)
        rendered.append(escaped)
    return "".join(rendered)


def is_table_separator(line: str) -> bool:
    stripped = line.strip()
    if "|" not in stripped:
        return False
    cells = [cell.strip() for cell in stripped.strip("|").split("|")]
    return bool(cells) and all(re.fullmatch(r":?-{3,}:?", cell or "") for cell in cells)


def split_table_row(line: str) -> list[str]:
    return [cell.strip() for cell in line.strip().strip("|").split("|")]


def render_table(lines: list[str]) -> str:
    header = split_table_row(lines[0])
    rows = [split_table_row(line) for line in lines[2:]]

    thead = "".join(f"<th>{parse_inline(cell)}</th>" for cell in header)
    body_rows = []
    for row in rows:
        padded = row + [""] * (len(header) - len(row))
        cells = "".join(f"<td>{parse_inline(cell)}</td>" for cell in padded[: len(header)])
        body_rows.append(f"<tr>{cells}</tr>")

    return (
        '<div class="table-wrap"><table>'
        f"<thead><tr>{thead}</tr></thead>"
        f"<tbody>{''.join(body_rows)}</tbody>"
        "</table></div>"
    )


def render_markdown(markdown: str) -> tuple[str, list[tuple[int, str, str]]]:
    lines = markdown.splitlines()
    blocks: list[str] = []
    toc: list[tuple[int, str, str]] = []
    used_slugs: set[str] = set()
    paragraph: list[str] = []
    i = 0

    def flush_paragraph() -> None:
        if paragraph:
            text = " ".join(line.strip() for line in paragraph).strip()
            if text:
                blocks.append(f"<p>{parse_inline(text)}</p>")
            paragraph.clear()

    while i < len(lines):
        line = lines[i]
        stripped = line.strip()

        if not stripped:
            flush_paragraph()
            i += 1
            continue

        if stripped.startswith("```"):
            flush_paragraph()
            language = stripped[3:].strip()
            code_lines: list[str] = []
            i += 1
            while i < len(lines) and not lines[i].strip().startswith("```"):
                code_lines.append(lines[i])
                i += 1
            if i < len(lines):
                i += 1
            class_name = f' class="language-{html.escape(language, quote=True)}"' if language else ""
            blocks.append(f"<pre><code{class_name}>{html.escape(chr(10).join(code_lines))}</code></pre>")
            continue

        heading = re.match(r"^(#{1,6})\s+(.+)$", stripped)
        if heading:
            flush_paragraph()
            level = len(heading.group(1))
            title = heading.group(2).strip()
            anchor = slugify(title, used_slugs)
            toc.append((level, title, anchor))
            blocks.append(
                f'<h{level} id="{anchor}"><a class="anchor" href="#{anchor}" '
                f'aria-label="Link to this section">#</a>{parse_inline(title)}</h{level}>'
            )
            i += 1
            continue

        if stripped.startswith("|") and i + 1 < len(lines) and is_table_separator(lines[i + 1]):
            flush_paragraph()
            table_lines = [lines[i], lines[i + 1]]
            i += 2
            while i < len(lines) and lines[i].strip().startswith("|"):
                table_lines.append(lines[i])
                i += 1
            blocks.append(render_table(table_lines))
            continue

        ordered = re.match(r"^\d+\.\s+(.+)$", stripped)
        unordered = re.match(r"^[-*]\s+(.+)$", stripped)
        if ordered or unordered:
            flush_paragraph()
            list_tag = "ol" if ordered else "ul"
            item_pattern = r"^\d+\.\s+(.+)$" if ordered else r"^[-*]\s+(.+)$"
            items: list[str] = []
            while i < len(lines):
                item = re.match(item_pattern, lines[i].strip())
                if not item:
                    break
                items.append(f"<li>{parse_inline(item.group(1).strip())}</li>")
                i += 1
            blocks.append(f"<{list_tag}>{''.join(items)}</{list_tag}>")
            continue

        if stripped.startswith(">"):
            flush_paragraph()
            quote_lines: list[str] = []
            while i < len(lines) and lines[i].strip().startswith(">"):
                quote_lines.append(lines[i].strip()[1:].strip())
                i += 1
            quote = " ".join(quote_lines).strip()
            blocks.append(f"<blockquote><p>{parse_inline(quote)}</p></blockquote>")
            continue

        paragraph.append(line)
        i += 1

    flush_paragraph()
    return "\n".join(blocks), toc


def render_page(title: str, body: str, toc: list[tuple[int, str, str]]) -> str:
    toc_items = "\n".join(
        f'<a class="toc-level-{level}" href="#{anchor}">{html.escape(text)}</a>'
        for level, text, anchor in toc
        if level <= 3
    )
    return f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{html.escape(title)}</title>
  <style>
    :root {{
      color-scheme: light;
      --bg: #f7f8fb;
      --paper: #ffffff;
      --ink: #17202a;
      --muted: #667085;
      --line: #d8dee8;
      --accent: #0f766e;
      --accent-soft: #e6f4f1;
      --code-bg: #101820;
      --code-ink: #f4f7fb;
      --table-head: #eef2f7;
    }}
    * {{ box-sizing: border-box; }}
    html {{ scroll-behavior: smooth; }}
    body {{
      margin: 0;
      background: var(--bg);
      color: var(--ink);
      font: 17px/1.75 -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC",
        "PingFang SC", "Microsoft YaHei", sans-serif;
    }}
    .shell {{
      display: grid;
      grid-template-columns: minmax(0, 1fr) 280px;
      gap: 32px;
      width: min(1220px, calc(100vw - 40px));
      margin: 36px auto;
      align-items: start;
    }}
    article {{
      background: var(--paper);
      border: 1px solid var(--line);
      border-radius: 8px;
      padding: 44px min(6vw, 72px);
      box-shadow: 0 18px 45px rgba(15, 23, 42, 0.08);
    }}
    nav {{
      position: sticky;
      top: 24px;
      max-height: calc(100vh - 48px);
      overflow: auto;
      background: var(--paper);
      border: 1px solid var(--line);
      border-radius: 8px;
      padding: 18px;
      box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06);
    }}
    nav strong {{
      display: block;
      margin-bottom: 10px;
      color: var(--ink);
      font-size: 14px;
    }}
    nav a {{
      display: block;
      color: var(--muted);
      text-decoration: none;
      font-size: 13px;
      line-height: 1.45;
      padding: 6px 0;
      border-top: 1px solid transparent;
    }}
    nav a:hover {{ color: var(--accent); }}
    .toc-level-2 {{ padding-left: 10px; }}
    .toc-level-3 {{ padding-left: 22px; }}
    h1, h2, h3, h4 {{
      color: var(--ink);
      line-height: 1.25;
      letter-spacing: 0;
    }}
    h1 {{
      margin: 0 0 28px;
      font-size: clamp(2rem, 5vw, 3.2rem);
    }}
    h2 {{
      margin: 42px 0 16px;
      padding-top: 8px;
      border-top: 1px solid var(--line);
      font-size: 1.72rem;
    }}
    h3 {{
      margin: 30px 0 12px;
      font-size: 1.24rem;
    }}
    p {{ margin: 14px 0; }}
    a {{ color: var(--accent); }}
    .anchor {{
      float: left;
      margin-left: -1.05em;
      width: 1em;
      opacity: 0;
      text-decoration: none;
    }}
    h1:hover .anchor, h2:hover .anchor, h3:hover .anchor {{ opacity: 0.55; }}
    code {{
      border-radius: 5px;
      padding: 0.12em 0.34em;
      background: var(--accent-soft);
      color: #0b5f59;
      font-size: 0.92em;
    }}
    pre {{
      overflow: auto;
      margin: 18px 0;
      padding: 18px 20px;
      border-radius: 8px;
      background: var(--code-bg);
      color: var(--code-ink);
      line-height: 1.55;
    }}
    pre code {{
      padding: 0;
      background: transparent;
      color: inherit;
      font-size: 0.95em;
    }}
    ol, ul {{ padding-left: 1.4em; }}
    li {{ margin: 8px 0; }}
    blockquote {{
      margin: 22px 0;
      padding: 12px 18px;
      border-left: 4px solid var(--accent);
      background: var(--accent-soft);
      border-radius: 0 8px 8px 0;
      color: #16423f;
    }}
    .table-wrap {{
      overflow-x: auto;
      margin: 20px 0 26px;
      border: 1px solid var(--line);
      border-radius: 8px;
    }}
    table {{
      width: 100%;
      border-collapse: collapse;
      min-width: 680px;
    }}
    th, td {{
      border-bottom: 1px solid var(--line);
      padding: 10px 12px;
      text-align: left;
      vertical-align: top;
    }}
    th {{
      background: var(--table-head);
      font-weight: 700;
      color: var(--ink);
    }}
    tr:last-child td {{ border-bottom: 0; }}
    @media (max-width: 980px) {{
      .shell {{
        display: block;
        width: min(100vw - 24px, 820px);
        margin: 12px auto 28px;
      }}
      article {{ padding: 28px 20px; }}
      nav {{
        position: static;
        margin-bottom: 14px;
        max-height: 240px;
      }}
    }}
    @media print {{
      body {{ background: #fff; }}
      .shell {{ display: block; width: auto; margin: 0; }}
      nav {{ display: none; }}
      article {{ border: 0; box-shadow: none; padding: 0; }}
    }}
  </style>
</head>
<body>
  <main class="shell">
    <article>
{body}
    </article>
    <nav aria-label="Table of contents">
      <strong>目录</strong>
{toc_items}
    </nav>
  </main>
</body>
</html>
"""


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("source", type=Path)
    parser.add_argument("output", type=Path)
    args = parser.parse_args()

    markdown = args.source.read_text(encoding="utf-8")
    body, toc = render_markdown(markdown)
    title = toc[0][1] if toc else args.source.stem
    args.output.write_text(render_page(title, body, toc), encoding="utf-8")
    print(args.output)


if __name__ == "__main__":
    main()
