#!/usr/bin/env python3
"""대용량 파일 요약 생성 스크립트.

지정된 파일 또는 자동 감지된 25KB+ 파일에 대해 *.summary.md 파일을 생성합니다.

사용법:
    python3 scripts/generate_file_summaries.py /path/to/file.py
    python3 scripts/generate_file_summaries.py --auto
    python3 scripts/generate_file_summaries.py --auto --dir /home/jay/workspace
"""

import argparse
import ast
import os
import re
import sys
from datetime import date
from pathlib import Path


WORKSPACE = Path(os.environ.get("WORKSPACE_ROOT", "/home/jay/workspace"))
AUTO_DETECT_MIN_BYTES = 25 * 1024  # 25KB


# ── Python 파일 파싱 ──────────────────────────────────────────────────────────

def _get_docstring_first_line(node: ast.AST) -> str:
    """AST 노드에서 docstring 첫줄을 추출합니다."""
    if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
        return ""
    if (
        node.body
        and isinstance(node.body[0], ast.Expr)
        and isinstance(node.body[0].value, ast.Constant)
        and isinstance(node.body[0].value.value, str)
    ):
        return node.body[0].value.value.strip().splitlines()[0]
    return ""


def _node_end_line(node: ast.AST, _source_lines: list[str]) -> int:
    """AST 노드의 마지막 줄 번호를 반환합니다."""
    end = getattr(node, "end_lineno", None)
    if end is not None:
        return end  # type: ignore[return-value]
    return getattr(node, "lineno", 1)


def parse_python_file(filepath: Path) -> dict:
    """Python 파일을 ast로 파싱하여 클래스/함수 구조를 반환합니다."""
    source = filepath.read_text(encoding="utf-8", errors="replace")
    source_lines = source.splitlines()

    try:
        tree = ast.parse(source)
    except SyntaxError as e:
        return {"error": str(e), "classes": [], "functions": []}

    classes = []
    top_functions = []

    for node in ast.iter_child_nodes(tree):
        if isinstance(node, ast.ClassDef):
            methods = []
            for child in ast.iter_child_nodes(node):
                if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
                    methods.append({
                        "name": child.name,
                        "start": child.lineno,
                        "end": _node_end_line(child, source_lines),
                        "doc": _get_docstring_first_line(child),
                    })
            classes.append({
                "name": node.name,
                "start": node.lineno,
                "end": _node_end_line(node, source_lines),
                "doc": _get_docstring_first_line(node),
                "methods": methods,
            })
        elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
            top_functions.append({
                "name": node.name,
                "start": node.lineno,
                "end": _node_end_line(node, source_lines),
                "doc": _get_docstring_first_line(node),
            })

    return {"classes": classes, "functions": top_functions}


# ── JS/TS 파일 파싱 ───────────────────────────────────────────────────────────

def parse_js_file(filepath: Path) -> dict:
    """JS/TS 파일을 정규식으로 파싱하여 컴포넌트/함수/섹션 구조를 반환합니다."""
    source = filepath.read_text(encoding="utf-8", errors="replace")
    lines = source.splitlines()

    components = []
    functions = []
    sections = []

    # 섹션 마커: /* ── 섹션명 ── */ 또는 // ── 섹션명 ──
    section_pattern = re.compile(r"(?:/\*|//)\s*[─\-─━]+\s*(.+?)\s*[─\-─━]+\s*(?:\*/)?$")
    # const ComponentName = (또는 function ComponentName)
    component_pattern = re.compile(r"^(?:export\s+)?(?:default\s+)?(?:const|function)\s+([A-Z][a-zA-Z0-9_]*)\s*[=(]")
    # 일반 함수
    func_pattern = re.compile(r"^(?:export\s+)?(?:async\s+)?function\s+([a-z_][a-zA-Z0-9_]*)\s*\(")
    arrow_func_pattern = re.compile(r"^(?:export\s+)?const\s+([a-z_][a-zA-Z0-9_]*)\s*=\s*(?:async\s+)?\(")

    for i, line in enumerate(lines, start=1):
        stripped = line.strip()
        m = section_pattern.search(stripped)
        if m:
            sections.append({"name": m.group(1).strip(), "line": i})
            continue
        m = component_pattern.match(stripped)
        if m:
            components.append({"name": m.group(1), "line": i})
            continue
        m = func_pattern.match(stripped)
        if m:
            functions.append({"name": m.group(1), "line": i})
            continue
        m = arrow_func_pattern.match(stripped)
        if m:
            functions.append({"name": m.group(1), "line": i})

    return {"components": components, "functions": functions, "sections": sections}


# ── Markdown 파일 파싱 ───────────────────────────────────────────────────────

def parse_md_file(filepath: Path) -> dict:
    """Markdown 파일에서 # 헤딩 구조를 추출합니다."""
    source = filepath.read_text(encoding="utf-8", errors="replace")
    lines = source.splitlines()
    headings = []
    heading_pattern = re.compile(r"^(#{1,6})\s+(.+)$")
    for i, line in enumerate(lines, start=1):
        m = heading_pattern.match(line)
        if m:
            headings.append({
                "level": len(m.group(1)),
                "title": m.group(2).strip(),
                "line": i,
            })
    return {"headings": headings}


# ── 요약 Markdown 생성 ────────────────────────────────────────────────────────

def _make_summary_python(filepath: Path, size_kb: float, total_lines: int) -> str:
    data = parse_python_file(filepath)
    today = date.today().isoformat()
    lines = [
        f"# {filepath.name} 요약",
        f"> 자동 생성: {today} | 원본: {size_kb}KB, {total_lines}줄",
        "",
        "## 구조",
    ]

    if data.get("error"):
        lines.append(f"\n> 파싱 오류: {data['error']}")
    else:
        classes = data.get("classes", [])
        functions = data.get("functions", [])

        if classes:
            lines.append("\n### 클래스")
            for cls in classes:
                doc_part = f" — {cls['doc']}" if cls["doc"] else ""
                lines.append(f"- **{cls['name']}** (line {cls['start']}-{cls['end']}){doc_part}")
                for method in cls.get("methods", []):
                    mdoc = f" — {method['doc']}" if method["doc"] else ""
                    lines.append(f"  - `{method['name']}` (line {method['start']}-{method['end']}){mdoc}")

        if functions:
            lines.append("\n### 독립 함수")
            for fn in functions:
                fdoc = f" — {fn['doc']}" if fn["doc"] else ""
                lines.append(f"- `{fn['name']}` (line {fn['start']}-{fn['end']}){fdoc}")

    lines.append("\n## 참조 가이드")
    lines.append("_아래 항목은 키워드로 내용 찾기 예시입니다. offset/limit으로 해당 줄 범위만 읽으세요._")
    if data.get("classes"):
        for cls in data["classes"][:5]:
            lines.append(f"- {cls['name']} 관련: line {cls['start']}-{min(cls['start'] + 50, cls['end'])} 읽기")
    if data.get("functions"):
        for fn in data["functions"][:5]:
            lines.append(f"- `{fn['name']}` 함수: line {fn['start']}-{fn['end']} 읽기")

    return "\n".join(lines) + "\n"


def _make_summary_js(filepath: Path, size_kb: float, total_lines: int) -> str:
    data = parse_js_file(filepath)
    today = date.today().isoformat()
    lines = [
        f"# {filepath.name} 요약",
        f"> 자동 생성: {today} | 원본: {size_kb}KB, {total_lines}줄",
        "",
        "## 구조",
    ]

    sections = data.get("sections", [])
    components = data.get("components", [])
    functions = data.get("functions", [])

    if sections:
        lines.append("\n### 섹션 마커")
        for s in sections:
            lines.append(f"- {s['name']} (line {s['line']})")

    if components:
        lines.append("\n### 컴포넌트")
        for c in components:
            lines.append(f"- **{c['name']}** (line {c['line']})")

    if functions:
        lines.append("\n### 함수")
        for fn in functions:
            lines.append(f"- `{fn['name']}` (line {fn['line']})")

    lines.append("\n## 참조 가이드")
    lines.append("_offset/limit으로 해당 줄 범위만 읽으세요._")
    for s in sections[:5]:
        lines.append(f"- {s['name']} 관련: line {s['line']} 부근 읽기")

    return "\n".join(lines) + "\n"


def _make_summary_md(filepath: Path, size_kb: float, total_lines: int) -> str:
    data = parse_md_file(filepath)
    today = date.today().isoformat()
    lines = [
        f"# {filepath.name} 요약",
        f"> 자동 생성: {today} | 원본: {size_kb}KB, {total_lines}줄",
        "",
        "## 헤딩 구조",
    ]
    for h in data.get("headings", []):
        indent = "  " * (h["level"] - 1)
        lines.append(f"{indent}- {'#' * h['level']} {h['title']} (line {h['line']})")

    return "\n".join(lines) + "\n"


def _make_summary_generic(filepath: Path, size_kb: float, total_lines: int) -> str:
    today = date.today().isoformat()
    return (
        f"# {filepath.name} 요약\n"
        f"> 자동 생성: {today} | 원본: {size_kb}KB, {total_lines}줄\n\n"
        f"## 참고\n"
        f"지원되지 않는 파일 형식입니다. offset/limit으로 분할 읽기하세요.\n"
    )


def generate_summary(filepath: Path) -> Path:
    """파일 요약을 생성하고 *.summary.md 경로를 반환합니다."""
    filepath = filepath.resolve()
    if not filepath.is_file():
        raise FileNotFoundError(f"파일을 찾을 수 없습니다: {filepath}")

    size_bytes = filepath.stat().st_size
    size_kb = round(size_bytes / 1024, 1)
    source = filepath.read_text(encoding="utf-8", errors="replace")
    total_lines = source.count("\n") + 1

    suffix = filepath.suffix.lower()
    if suffix == ".py":
        content = _make_summary_python(filepath, size_kb, total_lines)
    elif suffix in (".js", ".ts", ".jsx", ".tsx"):
        content = _make_summary_js(filepath, size_kb, total_lines)
    elif suffix == ".md":
        content = _make_summary_md(filepath, size_kb, total_lines)
    else:
        content = _make_summary_generic(filepath, size_kb, total_lines)

    summary_path = filepath.parent / (filepath.name + ".summary.md")
    summary_path.write_text(content, encoding="utf-8")
    print(f"[OK] {summary_path} ({size_kb}KB → {len(content)} chars)")
    return summary_path


def auto_detect_large_files(root: Path, min_bytes: int = AUTO_DETECT_MIN_BYTES) -> list[Path]:
    """root 하위에서 min_bytes 이상인 파일을 수집합니다."""
    results = []
    skip_dirs = {".git", "__pycache__", "node_modules", ".venv", "venv", "logs", "output"}
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in skip_dirs]
        for fname in filenames:
            if fname.endswith(".summary.md"):
                continue
            fp = Path(dirpath) / fname
            try:
                if fp.stat().st_size >= min_bytes:
                    results.append(fp)
            except OSError:
                pass
    results.sort(key=lambda p: p.stat().st_size, reverse=True)
    return results


# ── CLI ───────────────────────────────────────────────────────────────────────

def main() -> None:
    parser = argparse.ArgumentParser(
        description="대용량 파일 요약 생성 스크립트",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""예시:
  python3 scripts/generate_file_summaries.py /path/to/file.py
  python3 scripts/generate_file_summaries.py --auto
  python3 scripts/generate_file_summaries.py --auto --dir /home/jay/workspace/dashboard
""",
    )
    parser.add_argument("files", nargs="*", help="요약할 파일 경로(들)")
    parser.add_argument(
        "--auto",
        action="store_true",
        help=f"25KB+ 파일을 자동 감지하여 요약 생성",
    )
    parser.add_argument(
        "--dir",
        type=Path,
        default=WORKSPACE,
        help="--auto 시 탐색할 루트 디렉토리 (기본: WORKSPACE)",
    )
    parser.add_argument(
        "--min-size",
        type=int,
        default=AUTO_DETECT_MIN_BYTES,
        help="--auto 시 최소 파일 크기(bytes, 기본: 25600)",
    )
    args = parser.parse_args()

    targets: list[Path] = []

    if args.files:
        targets = [Path(f) for f in args.files]

    if args.auto:
        detected = auto_detect_large_files(args.dir, min_bytes=args.min_size)
        print(f"[auto] {len(detected)}개 파일 감지됨 (>= {args.min_size // 1024}KB) in {args.dir}")
        targets.extend(detected)

    if not targets:
        parser.print_help()
        sys.exit(1)

    # 중복 제거 (순서 유지)
    seen: set[Path] = set()
    unique_targets: list[Path] = []
    for t in targets:
        rp = t.resolve()
        if rp not in seen:
            seen.add(rp)
            unique_targets.append(t)

    errors = 0
    for fp in unique_targets:
        try:
            generate_summary(Path(fp))
        except Exception as e:
            print(f"[ERROR] {fp}: {e}", file=sys.stderr)
            errors += 1

    if errors:
        print(f"\n{errors}개 파일 처리 실패.", file=sys.stderr)
        sys.exit(1)
    else:
        print(f"\n완료: {len(unique_targets)}개 파일 요약 생성.")


if __name__ == "__main__":
    main()
