"""File collection utilities for vibelexity."""

from __future__ import annotations

import fnmatch
from pathlib import Path

from pathspec import PathSpec

_PY_EXTENSIONS = {".py"}
_TS_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx"}
_GO_EXTENSIONS = {".go"}
_SUPPORTED_EXTENSIONS = _PY_EXTENSIONS | _TS_EXTENSIONS | _GO_EXTENSIONS

_TEST_DIRS = {"tests", "test", "__tests__"}
_PY_TEST_PATTERNS = {"test_*.py", "*_test.py", "conftest.py"}
_TS_TEST_PATTERNS = {"*.test.ts", "*.spec.ts", "*.test.tsx", "*.spec.tsx"}
_GO_TEST_PATTERNS = {"*_test.go"}


def _load_gitignore_specs(directory: Path) -> dict[tuple[str, ...], PathSpec]:
    """Load PathSpec objects for all .gitignore files in directory and subdirectories."""
    specs_by_dir = {}
    for gitignore_path in sorted(directory.rglob(".gitignore")):
        dir_path = gitignore_path.parent.relative_to(directory)
        patterns = gitignore_path.read_text().splitlines()
        specs_by_dir[dir_path.parts or ("",)] = PathSpec.from_lines(
            "gitwildmatch", patterns
        )
    return specs_by_dir


def _is_ignored(rel_path: Path, specs_by_dir: dict[tuple[str, ...], PathSpec]) -> bool:
    """Check if a relative path matches any gitignore pattern from its ancestors."""
    for depth in range(len(rel_path.parts)):
        ancestor = rel_path.parts[:depth] or ("",)
        if ancestor not in specs_by_dir:
            continue
        spec = specs_by_dir[ancestor]
        path_from_ancestor = "/".join(rel_path.parts[depth:])
        if spec.match_file(path_from_ancestor):
            return True
    return False


def _is_test_file(rel_path: Path) -> bool:
    """Check if a file is a test file by name or directory convention."""
    for part in rel_path.parts[:-1]:
        if part in _TEST_DIRS:
            return True
    name = rel_path.name
    ext = rel_path.suffix.lower()
    if ext in _PY_EXTENSIONS:
        patterns = _PY_TEST_PATTERNS
    elif ext in _TS_EXTENSIONS:
        patterns = _TS_TEST_PATTERNS
    elif ext in _GO_EXTENSIONS:
        patterns = _GO_TEST_PATTERNS
    else:
        return False
    return any(fnmatch.fnmatch(name, pat) for pat in patterns)


def _collect_source_files(directory: Path, include_tests: bool = False) -> list[Path]:
    """Recursively find all supported source files under a directory."""
    specs_by_dir = _load_gitignore_specs(directory)
    files = []
    for path in sorted(directory.rglob("*")):
        if not path.is_file() or path.suffix.lower() not in _SUPPORTED_EXTENSIONS:
            continue
        rel = path.relative_to(directory)
        if any(part.startswith(".") for part in rel.parts):
            continue
        if specs_by_dir and _is_ignored(rel, specs_by_dir):
            continue
        if not include_tests and _is_test_file(rel):
            continue
        files.append(path)
    return files
