#!/usr/bin/env python3
"""Generate the collaborator-facing catalog for every first-level work directory."""

from __future__ import annotations

import argparse
import html
import json
import re
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import quote


ROOT = Path(__file__).resolve().parents[2]
WORK_ROOT = ROOT / "work"
MARKDOWN_OUTPUT = WORK_ROOT / "WORK_DIRECTORY_GUIDE.md"
HTML_OUTPUT = WORK_ROOT / "work-directory-guide.html"

IGNORED_MARKDOWN = {
    "WORK_DIRECTORY_GUIDE.md",
    "source-index.md",
}

GENERIC_TITLES = {
    "summary",
    "intake summary",
    "readme",
    "overview",
    "task summary",
    "routing note",
}

GOAL_HEADINGS = {
    "goal",
    "purpose",
    "目标",
    "目的",
    "本次目标",
    "一句话说明",
    "说明",
}

DIRECTORY_OVERRIDES = {
    "2026-05-30-product-standardization-weekly-talking-points": (
        "智慧食堂产品标准化周会材料",
        "整理智慧食堂产品标准化周会讲点、方案文档抽取和可复跑 Word 生成脚本。",
    ),
    "2026-06-25-apusic-real-validation": (
        "金蝶天燕 Apusic 真实环境验证",
        "保存 Apusic 真实安装、PHP 7.3/7.4 兼容验证、监听检查脚本和来源资产哈希。",
    ),
    "2026-06-30-online-project-usage-consumption-report": (
        "在线项目使用与消费报告",
        "汇总在线项目使用、消费数据库明细、数据缺口以及 Excel、Markdown、HTML 报告。",
    ),
    "2026-07-07-midyear-business-review-meeting-intake": (
        "年中经营复盘会议资料接收",
        "预留年中经营复盘会议资料的接收位置；当前目录未见可供协同者阅读的有效文件，内容待补。",
    ),
    "2026-07-30-chongqing-group-order-premortem": (
        "重庆团购预定预演复盘",
        "保存重庆团购预定方案的 premortem 人审页面，用于提前识别上线失败模式和防范动作。",
    ),
    "team-learning": (
        "团队学习与问答沉淀",
        "汇总团队每日学习复盘、线程级 Markdown 记录，以及按成员和日期归档的脱敏 Codex 问答。",
    ),
}

RESTRICTED_DIRECTORY_PATTERNS = (
    (re.compile(r"personal-performance|jiang-interview", re.I), "个人或人事相关资料"),
    (
        re.compile(
            r"security|server-disk|domestic-server|apusic-real|connected-systems-full-data|"
            r"online-project-usage-consumption|tongweb-kingbase",
            re.I,
        ),
        "安全、基础设施或生产数据相关资料",
    ),
    (re.compile(r"withdrawal|db-admin", re.I), "财务或凭据相关资料"),
)


@dataclass(frozen=True)
class DirectoryRecord:
    name: str
    date: str
    month: str
    title: str
    description: str
    entry: str | None
    evidence: str
    needs_detail: bool


def clean_markdown(text: str) -> str:
    text = re.sub(r"!\[[^]]*]\([^)]*\)", "", text)
    text = re.sub(r"\[([^]]+)]\([^)]*\)", r"\1", text)
    text = re.sub(r"<[^>]+>", " ", text)
    text = re.sub(r"[*_~]", "", text)
    text = re.sub(r"^[-+*]\s+", "", text.strip())
    text = re.sub(r"^\d+[.)]\s+", "", text)
    text = re.sub(r"/Users/[^/\s`]+/[^\s`]+", "[本地路径已省略]", text)
    text = re.sub(
        r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b",
        "[内部标识已省略]",
        text,
    )
    text = re.sub(r"\b[0-9a-fA-F]{24,64}\b", "[内部标识已省略]", text)
    text = re.sub(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", "[IP 已省略]", text)
    text = re.sub(r"\b[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\b", "[邮箱已省略]", text)
    text = re.sub(r"(?<!\d)1[3-9]\d{9}(?!\d)", "[手机号已省略]", text)
    text = re.sub(r"\s+", " ", text).strip()
    return text


def truncate(text: str, limit: int = 180) -> str:
    if len(text) <= limit:
        return text
    return text[: limit - 1].rstrip("，、；：,. ") + "…"


def humanize_slug(name: str) -> str:
    body = re.sub(r"^\d{4}-\d{2}-\d{2}-", "", name)
    return body.replace("-", " ").strip()


def preferred_markdown(directory: Path) -> Path | None:
    for filename in ("summary.md", "README.md", "readme.md", "intake.md"):
        candidate = directory / filename
        if candidate.is_file():
            return candidate

    root_candidates = sorted(
        path
        for path in directory.glob("*.md")
        if path.name not in IGNORED_MARKDOWN and not path.name.startswith("source-")
    )
    if root_candidates:
        return root_candidates[0]

    nested_candidates = sorted(
        path
        for path in directory.glob("*/*.md")
        if path.name not in IGNORED_MARKDOWN and not path.name.startswith("source-")
    )
    return nested_candidates[0] if nested_candidates else None


def first_other_entry(directory: Path) -> Path | None:
    preferred_suffixes = (".html", ".xlsx", ".docx", ".csv", ".py", ".sh")
    candidates = sorted(
        path
        for path in directory.rglob("*")
        if path.is_file()
        and not path.name.startswith(".")
        and path.suffix.lower() in preferred_suffixes
        and len(path.relative_to(directory).parts) <= 2
    )
    return candidates[0] if candidates else None


def first_content_block(lines: list[str], start: int) -> str | None:
    collected: list[str] = []
    in_fence = False
    for raw in lines[start:]:
        stripped = raw.strip()
        if stripped.startswith("```"):
            in_fence = not in_fence
            continue
        if in_fence:
            continue
        if stripped.startswith("#"):
            if collected:
                break
            continue
        if not stripped:
            if collected:
                break
            continue
        if stripped.startswith("|") or stripped.startswith(">"):
            continue
        metadata_pattern = (
            r"^-?\s*(?:\*\*)?"
            r"(?:status|reviewer|date|updated|version|evidence level|task id|audience|"
            r"human review status|primary delivery|source thread|来源线程|日期|状态|"
            r"数字来源|任务 id|生成时间|审核人|当前状态|更新日期)"
            r"(?:\*\*)?\s*[：:]"
        )
        if re.match(metadata_pattern, stripped, re.I):
            continue
        cleaned = clean_markdown(stripped)
        if len(cleaned) < 8:
            continue
        collected.append(cleaned)
        if len(" ".join(collected)) >= 70:
            break
    return truncate(" ".join(collected)) if collected else None


def extract_title_description(markdown_path: Path, directory_name: str) -> tuple[str, str | None]:
    text = markdown_path.read_text(encoding="utf-8", errors="ignore")
    lines = text.splitlines()
    title = ""
    title_index = 0
    for index, line in enumerate(lines):
        match = re.match(r"^#\s+(.+?)\s*$", line)
        if match:
            title = clean_markdown(match.group(1))
            title_index = index + 1
            break

    for index, line in enumerate(lines):
        match = re.match(r"^##+\s+(.+?)\s*$", line)
        if match and clean_markdown(match.group(1)).lower() in GOAL_HEADINGS:
            block = first_content_block(lines, index + 1)
            if block:
                if not title or title.lower() in GENERIC_TITLES:
                    title = humanize_slug(directory_name)
                return title, block

    block = first_content_block(lines, title_index)
    if not title or title.lower() in GENERIC_TITLES:
        title = humanize_slug(directory_name)
    return title, block


def build_record(directory: Path) -> DirectoryRecord:
    date_match = re.match(r"^(\d{4}-\d{2}-\d{2})-", directory.name)
    date = date_match.group(1) if date_match else "长期维护"
    month = date[:7] if date_match else "长期目录"

    markdown_path = preferred_markdown(directory)
    entry_path: Path | None = markdown_path
    needs_detail = False

    if markdown_path:
        title, description = extract_title_description(markdown_path, directory.name)
        evidence = markdown_path.name
    elif directory.name in DIRECTORY_OVERRIDES:
        title, description = DIRECTORY_OVERRIDES[directory.name]
        entry_path = first_other_entry(directory)
        evidence = "目录名与文件清单"
        needs_detail = "待补" in description
    else:
        title = humanize_slug(directory.name)
        description = None
        entry_path = first_other_entry(directory)
        evidence = "目录名与文件清单（待补说明）"
        needs_detail = True

    if directory.name in DIRECTORY_OVERRIDES and (not description or title.lower() in GENERIC_TITLES):
        title, description = DIRECTORY_OVERRIDES[directory.name]

    if not description:
        description = f"保存“{title}”相关工作资料；当前缺少明确的 README/summary，具体用途待目录负责人补充。"
        needs_detail = True

    if directory.name == "2026-07-07-midyear-business-review-meeting-intake":
        needs_detail = True

    for pattern, category in RESTRICTED_DIRECTORY_PATTERNS:
        if pattern.search(directory.name):
            description = f"【受限主题】本目录保存{category}；目录页只说明存在性和用途，具体内容按项目权限进入主要入口查看。"
            evidence = f"受限摘要 · {evidence}"
            needs_detail = False
            break

    entry = str(entry_path.relative_to(directory)) if entry_path else None
    return DirectoryRecord(
        name=directory.name,
        date=date,
        month=month,
        title=title,
        description=truncate(description),
        entry=entry,
        evidence=evidence,
        needs_detail=needs_detail,
    )


def list_records() -> list[DirectoryRecord]:
    directories = sorted(
        (path for path in WORK_ROOT.iterdir() if path.is_dir() and not path.name.startswith(".")),
        key=lambda path: path.name,
    )
    return [build_record(directory) for directory in directories]


def group_records(records: list[DirectoryRecord]) -> list[tuple[str, list[DirectoryRecord]]]:
    months = sorted({record.month for record in records if record.month != "长期目录"}, reverse=True)
    groups = [(month, [record for record in records if record.month == month]) for month in months]
    evergreen = [record for record in records if record.month == "长期目录"]
    if evergreen:
        groups.append(("长期目录", evergreen))
    return groups


def markdown_link(record: DirectoryRecord) -> str:
    target = record.name + (f"/{record.entry}" if record.entry else "/")
    label = record.entry or "打开目录"
    return f"[{label}]({target})"


def render_markdown(records: list[DirectoryRecord]) -> str:
    latest_date = max((record.date for record in records if record.date != "长期维护"), default="无")
    pending = sum(record.needs_detail for record in records)
    lines = [
        "# `work/` 工作资料目录（协同者版）",
        "",
        "本页解释 `work/` 下每一个一级文件夹是做什么的。它面向需要查资料、接续任务、复用成果或参加评审的协同者；领导版不展开这些执行目录。",
        "",
        f"- 一级文件夹：`{len(records)}` 个",
        f"- 目录覆盖：`{len(records)}/{len(records)}`",
        f"- 最新日期目录：`{latest_date}`",
        f"- 待补充明确说明：`{pending}` 个",
        "- 生成方式：优先读取各目录的 `summary.md`、`README.md` 或首个 Markdown；没有可读说明时依据文件清单给出受限介绍并标记待补。",
        "- 更新命令：`python3 control/scripts/generate_work_directory_guide.py`",
        "",
        "## 怎么使用",
        "",
        "1. 先按月份定位工作发生时间，再按目录名或介绍搜索主题。",
        "2. 点击“主要入口”读取该目录的 summary、README 或主要成果文件。",
        "3. 目录介绍用于导航，不替代目录内的证据、人工评审和真实系统验证。",
        "4. 新增一级文件夹后必须重跑生成器；`--check` 用于检查目录是否漏收或文档是否过期。",
        "",
    ]

    for month, group in group_records(records):
        lines.extend(
            [
                f"## {month}",
                "",
                "| 文件夹 | 介绍 | 主要入口 | 说明依据 |",
                "| --- | --- | --- | --- |",
            ]
        )
        for record in group:
            description = record.description.replace("|", "\\|")
            title = record.title.replace("|", "\\|")
            evidence = record.evidence.replace("|", "\\|")
            lines.append(
                f"| `{record.name}/`<br>{title} | {description} | {markdown_link(record)} | {evidence} |"
            )
        lines.append("")

    lines.extend(
        [
            "## 人工 review",
            "",
            "- Review 状态：待 Jack review。",
            "- Review 重点：目录介绍是否准确、是否有不应进入协同者入口的敏感主题、是否存在应该合并或迁移的目录。",
            "- 通过后：把认可的目录解释保留为协同者导航；被否定的解释改为待补说明，不把错误推断沉淀为知识。",
            "",
        ]
    )
    return "\n".join(lines)


def html_entry_link(record: DirectoryRecord) -> str:
    target = quote(record.name) + "/"
    label = "打开目录"
    if record.entry:
        target += "/".join(quote(part) for part in Path(record.entry).parts)
        label = record.entry
    return f'<a href="./{target}">{html.escape(label)}</a>'


def render_html(records: list[DirectoryRecord]) -> str:
    latest_date = max((record.date for record in records if record.date != "长期维护"), default="无")
    pending = sum(record.needs_detail for record in records)
    month_options = "".join(
        f'<option value="{html.escape(month)}">{html.escape(month)}</option>'
        for month, _ in group_records(records)
    )
    rows: list[str] = []
    for month, group in group_records(records):
        rows.append(
            f'<tr class="month-row" data-month="{html.escape(month)}"><th colspan="4">{html.escape(month)} · {len(group)} 个目录</th></tr>'
        )
        for record in group:
            searchable = " ".join((record.name, record.title, record.description, record.evidence)).lower()
            pending_badge = '<span class="badge warn">待补说明</span>' if record.needs_detail else ""
            rows.append(
                "<tr class=\"directory-row\" "
                f'data-month="{html.escape(record.month)}" data-search="{html.escape(searchable, quote=True)}">'
                "<td>"
                f'<code>{html.escape(record.name)}/</code><strong>{html.escape(record.title)}</strong>{pending_badge}'
                "</td>"
                f"<td>{html.escape(record.description)}</td>"
                f"<td>{html_entry_link(record)}</td>"
                f"<td><span class=\"evidence\">{html.escape(record.evidence)}</span></td>"
                "</tr>"
            )

    return f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>work 工作资料目录（协同者版）</title>
  <style>
    :root {{ --ink:#17211f; --muted:#60706c; --line:#dfe7e4; --paper:#f6f8f7; --card:#fff; --accent:#146c5c; --accent-soft:#e7f2ef; --warn:#9a5b13; --warn-soft:#fff4df; }}
    * {{ box-sizing:border-box; }}
    body {{ margin:0; color:var(--ink); background:var(--paper); font:15px/1.65 -apple-system,BlinkMacSystemFont,"Segoe UI","Microsoft YaHei",sans-serif; }}
    main {{ width:min(1480px,calc(100% - 32px)); margin:0 auto; padding:32px 0 64px; }}
    a {{ color:var(--accent); text-decoration:none; }}
    a:hover {{ text-decoration:underline; }}
    .topbar {{ display:flex; justify-content:space-between; gap:16px; align-items:center; margin-bottom:18px; }}
    .topbar a {{ font-weight:700; }}
    header {{ padding:34px; border:1px solid var(--line); border-radius:24px; background:linear-gradient(135deg,#fff 0%,#edf5f2 100%); box-shadow:0 16px 50px rgba(28,62,53,.07); }}
    .eyebrow {{ margin:0 0 6px; color:var(--accent); font-size:12px; font-weight:800; letter-spacing:.13em; text-transform:uppercase; }}
    h1 {{ margin:0; font-size:clamp(28px,4vw,48px); line-height:1.18; }}
    .lead {{ max-width:900px; margin:14px 0 0; color:var(--muted); font-size:17px; }}
    .metrics {{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px; margin-top:24px; }}
    .metric {{ padding:16px; border:1px solid var(--line); border-radius:15px; background:rgba(255,255,255,.82); }}
    .metric strong {{ display:block; font-size:24px; }}
    .metric span {{ color:var(--muted); }}
    .notice {{ margin:20px 0 0; padding:14px 16px; border-left:4px solid var(--accent); background:var(--accent-soft); border-radius:8px; }}
    .controls {{ position:sticky; top:0; z-index:4; display:grid; grid-template-columns:minmax(260px,1fr) 220px auto; gap:12px; margin:22px 0; padding:14px; border:1px solid var(--line); border-radius:16px; background:rgba(246,248,247,.94); backdrop-filter:blur(10px); }}
    input,select {{ width:100%; padding:11px 13px; border:1px solid #cfdad6; border-radius:10px; background:#fff; color:var(--ink); font:inherit; }}
    .count {{ align-self:center; color:var(--muted); white-space:nowrap; }}
    .table-wrap {{ overflow:auto; border:1px solid var(--line); border-radius:18px; background:var(--card); box-shadow:0 10px 35px rgba(28,62,53,.05); }}
    table {{ width:100%; border-collapse:collapse; min-width:980px; }}
    th,td {{ padding:15px 16px; border-bottom:1px solid var(--line); text-align:left; vertical-align:top; }}
    thead th {{ position:sticky; top:76px; z-index:2; background:#f0f5f3; font-size:13px; }}
    .month-row th {{ padding:12px 16px; color:#fff; background:#335b52; letter-spacing:.03em; }}
    td:first-child {{ width:27%; }} td:nth-child(2) {{ width:45%; }}
    code {{ display:block; margin-bottom:5px; color:#315048; font-size:12px; overflow-wrap:anywhere; }}
    td strong {{ display:block; }}
    .evidence {{ color:var(--muted); font-size:13px; }}
    .badge {{ display:inline-block; margin-top:7px; padding:2px 8px; border-radius:999px; font-size:11px; font-weight:700; }}
    .badge.warn {{ color:var(--warn); background:var(--warn-soft); }}
    .review {{ margin-top:24px; padding:24px; border:1px solid var(--line); border-radius:18px; background:#fff; }}
    .review h2 {{ margin-top:0; }}
    .empty {{ display:none; padding:28px; text-align:center; color:var(--muted); }}
    @media (max-width:800px) {{ .metrics {{ grid-template-columns:repeat(2,1fr); }} .controls {{ position:static; grid-template-columns:1fr; }} .topbar {{ align-items:flex-start; flex-direction:column; }} }}
  </style>
</head>
<body>
<main>
  <nav class="topbar"><a href="../TEAM_AI_NATIVE_COLLABORATION_GUIDE.html">← 返回团队 AI Native 协同者入口</a><span>内部协同目录 · Markdown 真源 + HTML 人审</span></nav>
  <header>
    <p class="eyebrow">Kangbite Digital · Collaborator Directory</p>
    <h1><code>work/</code> 工作资料目录</h1>
    <p class="lead">逐一解释 <code>work/</code> 下每个一级文件夹的用途、主要入口和说明依据。它帮助协同者找资料、接续任务和参加 review；领导版不展开这些执行细节。</p>
    <div class="metrics">
      <div class="metric"><strong>{len(records)}</strong><span>一级文件夹</span></div>
      <div class="metric"><strong>{len(records)}/{len(records)}</strong><span>目录覆盖</span></div>
      <div class="metric"><strong>{latest_date}</strong><span>最新日期目录</span></div>
      <div class="metric"><strong>{pending}</strong><span>待补明确说明</span></div>
    </div>
    <p class="notice">介绍优先读取目录内 <code>summary.md</code>、<code>README.md</code> 或首个 Markdown。介绍只负责导航，不替代原始证据、人工评审或真实系统验证。</p>
  </header>
  <section class="controls" aria-label="目录筛选">
    <input id="search" type="search" placeholder="搜索目录名、标题、介绍或说明依据">
    <select id="month"><option value="all">全部月份</option>{month_options}</select>
    <span class="count" id="count">显示 {len(records)} / {len(records)}</span>
  </section>
  <div class="table-wrap">
    <table>
      <thead><tr><th>文件夹</th><th>介绍</th><th>主要入口</th><th>说明依据</th></tr></thead>
      <tbody>{''.join(rows)}</tbody>
    </table>
    <div class="empty" id="empty">没有匹配的目录。</div>
  </div>
  <section class="review">
    <h2>请 Jack review</h2>
    <p><strong>当前状态：</strong>待 review。</p>
    <ul>
      <li>目录介绍是否准确，是否能让协同者看懂“这是干什么的”。</li>
      <li>是否有不应进入协同者入口的敏感主题。</li>
      <li>是否存在应该合并、迁移或补充 README/summary 的目录。</li>
    </ul>
    <p>认可的解释保留为协同者导航；被否定的解释改为待补说明，不把错误推断沉淀为正向知识。</p>
  </section>
</main>
<script>
  const rows = Array.from(document.querySelectorAll('.directory-row'));
  const monthRows = Array.from(document.querySelectorAll('.month-row'));
  const search = document.getElementById('search');
  const month = document.getElementById('month');
  const count = document.getElementById('count');
  const empty = document.getElementById('empty');
  function filterRows() {{
    const query = search.value.trim().toLowerCase();
    const selected = month.value;
    let visible = 0;
    const monthsWithRows = new Set();
    rows.forEach((row) => {{
      const matchesMonth = selected === 'all' || row.dataset.month === selected;
      const matchesSearch = !query || row.dataset.search.includes(query);
      const show = matchesMonth && matchesSearch;
      row.hidden = !show;
      if (show) {{ visible += 1; monthsWithRows.add(row.dataset.month); }}
    }});
    monthRows.forEach((row) => {{ row.hidden = !monthsWithRows.has(row.dataset.month); }});
    count.textContent = `显示 ${{visible}} / {len(records)}`;
    empty.style.display = visible ? 'none' : 'block';
  }}
  search.addEventListener('input', filterRows);
  month.addEventListener('change', filterRows);
</script>
</body>
</html>
"""


def write_or_check(check: bool) -> dict[str, object]:
    records = list_records()
    markdown = render_markdown(records)
    html_text = render_html(records)
    pending = sum(record.needs_detail for record in records)

    if check:
        stale = []
        for path, expected in ((MARKDOWN_OUTPUT, markdown), (HTML_OUTPUT, html_text)):
            if not path.is_file() or path.read_text(encoding="utf-8") != expected:
                stale.append(str(path.relative_to(ROOT)))
        if stale:
            raise SystemExit("目录文档缺失或已过期：" + ", ".join(stale))
    else:
        MARKDOWN_OUTPUT.write_text(markdown, encoding="utf-8")
        HTML_OUTPUT.write_text(html_text, encoding="utf-8")

    return {
        "directory_count": len(records),
        "documented_count": len(records),
        "pending_detail_count": pending,
        "markdown": str(MARKDOWN_OUTPUT.relative_to(ROOT)),
        "html": str(HTML_OUTPUT.relative_to(ROOT)),
        "mode": "check" if check else "write",
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--check", action="store_true", help="Fail when generated files are missing or stale.")
    args = parser.parse_args()
    print(json.dumps(write_or_check(args.check), ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()
