"""File filtering patterns and FileFilter class.

Implements aggressive file filtering at discovery phase to reduce token bloat.
Filter order:
1. ALWAYS_EXCLUDE — not overridable (.git/, __pycache__/, etc.)
2. .gitignore — files ignored by VCS
3. DEFAULT_EXCLUDE — default patterns (overridable)
4. include_patterns (from per-repo config) — add back files
5. exclude_patterns (from per-repo config) — additional exclusions
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from pathspec import PathSpec

from unfamiliar.analysis.filters.gitignore import load_gitignore_spec

if TYPE_CHECKING:
    from pathlib import Path

    from pathspec.gitignore import GitIgnoreSpec

# Hard exclusions that cannot be overridden
ALWAYS_EXCLUDE: tuple[str, ...] = (
    # VCS
    ".git/",
    ".hg/",
    ".svn/",
    # Python cache
    "__pycache__/",
    "*.pyc",
    "*.pyo",
    "*.pyd",
    ".Python",
    "*.so",
    # Node
    "node_modules/",
    # Build artifacts
    "dist/",
    "build/",
    "*.egg-info/",
    ".eggs/",
    # IDE
    ".idea/",
    ".vscode/",
    "*.swp",
    "*.swo",
    # Lock files (regenerated)
    "poetry.lock",
    "pdm.lock",
    # Large generated files
    "*.min.js",
    "*.min.css",
)

# Default patterns for data/docs/config (overridable via include_patterns)
DEFAULT_EXCLUDE: tuple[str, ...] = (
    # Documentation
    "*.md",
    "*.rst",
    "*.txt",
    "docs/",
    "LICENSE*",
    "COPYING*",
    # Data files
    "*.json",
    "*.yaml",
    "*.yml",
    "*.toml",
    "*.ini",
    "*.cfg",
    "*.conf",
    "data/",
    # Media
    "*.png",
    "*.jpg",
    "*.jpeg",
    "*.gif",
    "*.svg",
    "*.ico",
    "*.webp",
    "*.mp3",
    "*.mp4",
    "*.wav",
    "*.pdf",
    # Markup/styles
    "*.html",
    "*.css",
    "*.scss",
    "*.less",
    # Config/env
    ".env*",
    "*.env",
    ".*ignore",
    # CI/CD
    ".github/",
    ".gitlab-ci.yml",
    ".travis.yml",
    "Jenkinsfile",
    # Docker
    "Dockerfile*",
    "docker-compose*.yml",
    ".dockerignore",
    # Test config
    "pytest.ini",
    "setup.cfg",
    "tox.ini",
    ".coveragerc",
    ".pre-commit-config.yaml",
    # Misc
    "Makefile",
    "*.log",
)

# Files that override DEFAULT_EXCLUDE (e.g., dependency manifests we need)
ALWAYS_INCLUDE: tuple[str, ...] = (
    "package.json",  # JS dependencies
    "package-lock.json",
    "yarn.lock",
    "pnpm-lock.yaml",
    "requirements.txt",  # Python dependencies
    "requirements*.txt",
    "pyproject.toml",  # Python project config (contains dependencies)
)


class FileFilter:
    """Filter files based on exclude/include patterns and gitignore.

    Filter application order:
    1. ALWAYS_EXCLUDE — hard exclusions, cannot be overridden
    2. gitignore patterns — files ignored by VCS
    3. DEFAULT_EXCLUDE — soft exclusions, can be overridden by include_patterns
    4. include_patterns — user-specified patterns to add back
    5. exclude_patterns — user-specified additional exclusions
    """

    def __init__(
        self,
        include_patterns: list[str] | None = None,
        exclude_patterns: list[str] | None = None,
    ) -> None:
        """Initialize filter with optional user patterns.

        Args:
            include_patterns: Patterns to add back (override DEFAULT_EXCLUDE).
            exclude_patterns: Additional patterns to exclude.
        """
        # Compile ALWAYS_EXCLUDE at construction
        self._always_exclude = PathSpec.from_lines("gitignore", ALWAYS_EXCLUDE)

        # Compile DEFAULT_EXCLUDE
        self._default_exclude = PathSpec.from_lines("gitignore", DEFAULT_EXCLUDE)

        # Compile ALWAYS_INCLUDE
        self._always_include = PathSpec.from_lines("gitignore", ALWAYS_INCLUDE)

        # User-provided patterns
        self._include_patterns = PathSpec.from_lines(
            "gitignore", include_patterns or []
        )
        self._exclude_patterns = PathSpec.from_lines(
            "gitignore", exclude_patterns or []
        )

        # Gitignore spec (loaded separately)
        self._gitignore_spec: GitIgnoreSpec | None = None

    def load_gitignore(self, repo_path: Path) -> None:
        """Load .gitignore patterns from repository.

        Args:
            repo_path: Path to repository root.

        Does nothing if .gitignore doesn't exist.
        """

        self._gitignore_spec = load_gitignore_spec(repo_path)

    def should_exclude(self, path: str) -> bool:
        """Check if a file path should be excluded from analysis.

        Args:
            path: Relative path from repo root.

        Returns:
            True if file should be excluded, False to include.
        """
        # Edge cases: empty or invalid paths
        if not path or path.startswith("/"):
            return True

        # Layer 1: ALWAYS_EXCLUDE — cannot be overridden
        if self._always_exclude.match_file(path):
            return True

        # Layer 2: gitignore patterns
        if self._gitignore_spec is not None and self._gitignore_spec.match_file(path):
            return True

        # Layer 3: DEFAULT_EXCLUDE (but check ALWAYS_INCLUDE first)
        if self._default_exclude.match_file(path):
            # Check if ALWAYS_INCLUDE or user include_patterns overrides
            if self._always_include.match_file(path):
                return False
            return not self._include_patterns.match_file(path)

        # Layer 4: user include_patterns for non-default-excluded files
        if self._include_patterns.match_file(path):
            return False

        # Layer 5: user exclude_patterns
        return self._exclude_patterns.match_file(path)
