"""발송 줄 → 팩 파일 귀속 + 팩 버전 성장 추이.

프롬프트 팩 규약: prompts/_base/<module>/<버전.YYYYMMDDHHmm>/<stem>.md

한계(Codex 검토 2026-08-15 반영, 리포트에도 명시):
- **현재 checkout 의 최신 버전 디렉토리만** 색인한다 — trace 당시 활성
  팩·registry 고정판·프로젝트 오버레이(prompts/projects/...)는 다루지
  않는다. 주행이 최신 checkout 으로 돌았을 때만 귀속이 정확하다.
- exact-line 일치만 쓴다 — 조립 중 치환·접합·개행 변화가 있으면 miss,
  같은 줄이 여러 팩에 있으면 **정렬 순서상 첫 파일**로 귀속된다(다대일
  거짓 귀속 가능 — 소유자는 provenance 가 아니라 후보 중 하나다).
"""
import re
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Tuple

VERSION_RE = re.compile(r"^(\d+)\.(\d{12})$")
INDEX_MIN_LINE = 40


def _versions(module_dir: Path) -> List[Tuple[int, str, Path]]:
    out = []
    for d in sorted(module_dir.iterdir()):
        if not d.is_dir():
            continue
        m = VERSION_RE.match(d.name)
        if m:
            out.append((int(m.group(1)), m.group(2), d))
    return sorted(out)


def growth_curves(prompts_root: Path) -> Dict[str, List[Dict[str, Any]]]:
    """module 별 버전 크기 추이.

    행: {version, ts, bytes(버전 디렉토리 안 파일 크기 합 — schema 등
    stem 전부 포함), stems: {파일명: bytes}} — system.md 단독 추이는
    stems 에서 읽는다. 'bytes' 는 os byte (팩은 utf-8 파일)라 발송 '자'
    단위와 다르다 — 추이 비교 전용.
    """
    curves: Dict[str, List[Dict[str, Any]]] = {}
    base = prompts_root / "_base"
    if not base.is_dir():
        return curves
    for module_dir in sorted(base.iterdir()):
        if not module_dir.is_dir():
            continue
        rows = []
        for ver, ts, vdir in _versions(module_dir):
            stems = {f.name: f.stat().st_size
                     for f in sorted(vdir.iterdir()) if f.is_file()}
            rows.append({"version": ver, "ts": ts,
                         "bytes": sum(stems.values()), "stems": stems})
        if len(rows) >= 2:
            curves[module_dir.name] = rows
    return curves


def build_line_index(
    prompts_root: Path, min_line: int = INDEX_MIN_LINE,
) -> Dict[str, str]:
    """최신 버전 팩 파일들의 줄(min_line 자 이상) → 'module/version/stem'."""
    index: Dict[str, str] = {}
    base = prompts_root / "_base"
    if not base.is_dir():
        return index
    for module_dir in sorted(base.iterdir()):
        if not module_dir.is_dir():
            continue
        vers = _versions(module_dir)
        if not vers:
            continue
        _, _, latest = vers[-1]
        for f in sorted(latest.iterdir()):
            if not f.is_file() or f.suffix not in (".md", ".txt", ".json"):
                continue
            try:
                text = f.read_text(encoding="utf-8")
            except Exception:  # noqa: BLE001 — 못 읽는 팩은 귀속만 빠진다
                continue
            key = f"{module_dir.name}/{latest.name}/{f.name}"
            for line in text.splitlines():
                if len(line) >= min_line:
                    index.setdefault(line, key)
    return index


def attribute_lines(
    common_lines: List[str],
    index: Dict[str, str],
    min_line: int = INDEX_MIN_LINE,
) -> Dict[str, Any]:
    """정적 줄들을 팩 파일별로 묶는다.

    색인이 min_line 미만 줄을 안 담으므로 **여기서도 같은 문턱으로
    거른다** — v1 은 안 걸러서 짧은 정상 팩 줄이 전부 '색인 밖'으로
    새어 그 칸을 부풀렸다(Codex 검토 실증).
    """
    by_source: Dict[str, int] = defaultdict(int)
    unmatched = 0
    for line in common_lines:
        if len(line) < min_line:
            continue
        src = index.get(line)
        if src:
            by_source[src] += len(line)
        else:
            unmatched += len(line)
    return {
        "pack_bytes": dict(
            sorted(by_source.items(), key=lambda kv: -kv[1])),
        "unmatched_bytes": unmatched,
    }
