#!/usr/bin/env python3
"""勘察外部产出物源目录：一次列清全部文件的路径 / 大小 / frontmatter / 首个 H1。

存在的理由：做 kind 映射只需要「路径 + 标题 + 有没有 kind 标签」，逐个 Read 会把上下文
烧在正文上。本脚本只抽这几样信号，几十个文件的源目录也能一次看完。

用法：
    python3 scan_source.py <源目录> [--json] [--all] [--max-bytes N]

    --json        输出 JSON（默认输出人可读表格）
    --all         不跳过噪音文件（默认跳过 .git/node_modules/.gitkeep 等）
    --max-bytes   读取每个文件用于提取标题的字节上限（默认 8192）
"""

from __future__ import annotations

import argparse
import json
import os
import sys

# 噪音：源仓自身的工程设施，与需求产出物无关。README 不在此列——它有时是正文入口，
# 该不该导由做映射的人判断，脚本只负责如实列出。
SKIP_DIRS = {
    ".git", ".svn", ".hg", "node_modules", "__pycache__", ".venv", "venv",
    ".idea", ".vscode", "dist", "build", "target", ".next", ".cache",
}
SKIP_FILES = {".gitkeep", ".DS_Store", ".gitignore", ".gitattributes"}

TEXT_EXT = {".md", ".markdown", ".txt", ".yaml", ".yml", ".json", ".csv"}


def parse_frontmatter(text: str) -> dict:
    """抽 YAML frontmatter 的标量字段。

    刻意不引 yaml 依赖：这里只需要 kind/title/jira 这类顶层标量做映射信号，
    简单逐行解析足够，且让脚本零依赖可直接跑。
    """
    if not text.startswith("---"):
        return {}
    lines = text.splitlines()
    if not lines or lines[0].strip() != "---":
        return {}
    out: dict[str, str] = {}
    for line in lines[1:]:
        s = line.strip()
        if s == "---":
            break
        if not s or s.startswith("#") or ":" not in s:
            continue
        key, _, value = s.partition(":")
        key = key.strip()
        value = value.strip().strip("'\"")
        if key and value and not key.startswith("-"):
            out[key] = value
    return out


def first_h1(text: str) -> str | None:
    for line in text.splitlines():
        s = line.strip()
        if s.startswith("# "):
            return s[2:].strip()
    return None


def scan(root: str, include_all: bool, max_bytes: int) -> list[dict]:
    items: list[dict] = []
    for dirpath, dirnames, filenames in os.walk(root):
        if not include_all:
            dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS and not d.startswith(".")]
        dirnames.sort()
        for name in sorted(filenames):
            if not include_all and (name in SKIP_FILES or name.startswith(".")):
                continue
            abs_path = os.path.join(dirpath, name)
            if not os.path.isfile(abs_path):
                continue
            rel = os.path.relpath(abs_path, root)
            ext = os.path.splitext(name)[1].lower()
            entry: dict = {
                "rel": rel.replace(os.sep, "/"),
                "abs": os.path.abspath(abs_path),
                "bytes": os.path.getsize(abs_path),
                "ext": ext,
            }
            if ext in TEXT_EXT:
                try:
                    with open(abs_path, "r", encoding="utf-8", errors="replace") as fh:
                        head = fh.read(max_bytes)
                except OSError as exc:  # 权限/损坏不该中断整次勘察
                    entry["error"] = str(exc)
                    items.append(entry)
                    continue
                fm = parse_frontmatter(head)
                if fm:
                    entry["frontmatter"] = fm
                h1 = first_h1(head)
                if h1:
                    entry["h1"] = h1
            items.append(entry)
    return items


def render_text(root: str, items: list[dict]) -> str:
    lines = [f"源目录: {root}", f"文件数: {len(items)}", ""]
    by_dir: dict[str, list[dict]] = {}
    for it in items:
        d = os.path.dirname(it["rel"]) or "."
        by_dir.setdefault(d, []).append(it)
    for d in sorted(by_dir):
        lines.append(f"[{d}]")
        for it in by_dir[d]:
            base = os.path.basename(it["rel"])
            bits = [f"{it['bytes']}B"]
            fm = it.get("frontmatter") or {}
            if fm.get("kind"):
                bits.append(f"kind={fm['kind']}")
            title = fm.get("title") or it.get("h1")
            if title:
                bits.append(f"«{title}»")
            lines.append(f"  {base}  ({', '.join(bits)})")
        lines.append("")
    return "\n".join(lines)


def main() -> int:
    ap = argparse.ArgumentParser(description="勘察外部产出物源目录")
    ap.add_argument("source", help="源目录")
    ap.add_argument("--json", action="store_true", help="输出 JSON")
    ap.add_argument("--all", action="store_true", help="不跳过噪音文件")
    ap.add_argument("--max-bytes", type=int, default=8192)
    args = ap.parse_args()

    root = os.path.abspath(args.source)
    if not os.path.isdir(root):
        print(json.dumps({"error": f"源目录不存在: {root}"}, ensure_ascii=False), file=sys.stderr)
        return 1

    items = scan(root, args.all, args.max_bytes)
    if args.json:
        print(json.dumps({"source": root, "count": len(items), "items": items}, ensure_ascii=False, indent=2))
    else:
        print(render_text(root, items))
    return 0


if __name__ == "__main__":
    sys.exit(main())
