"""Parse .vrignore files (gitignore-like syntax) for excluding files from uploads."""

import fnmatch
import os
from pathlib import Path


def load_vrignore_patterns(project_dir: str | Path) -> list[str]:
    """Load patterns from a .vrignore file in the project directory.

    Supports gitignore-like syntax:
    - Blank lines and lines starting with # are ignored
    - Standard glob patterns (*, ?, [abc])
    - Leading / anchors to the project root
    - Trailing / matches directories only
    - ** matches any number of directories
    """
    vrignore_path = Path(project_dir) / ".vrignore"
    if not vrignore_path.exists():
        return []

    patterns = []
    with open(vrignore_path) as f:
        for line in f:
            line = line.rstrip("\n\r")
            # Skip empty lines and comments
            if not line.strip() or line.strip().startswith("#"):
                continue
            patterns.append(line.strip())

    return patterns


def matches_vrignore(rel_path: str, patterns: list[str], is_dir: bool = False) -> bool:
    """Check if a relative path matches any .vrignore pattern.

    Args:
        rel_path: Path relative to project root (forward slashes).
        patterns: List of parsed .vrignore patterns.
        is_dir: Whether the path is a directory.

    Returns:
        True if the path should be ignored.
    """
    # Normalize to forward slashes
    rel_path = rel_path.replace(os.sep, "/")
    parts = rel_path.split("/")

    for pattern in patterns:
        dir_only = pattern.endswith("/")
        anchored = pattern.startswith("/")

        if dir_only:
            stripped = pattern.rstrip("/")
            clean = stripped.lstrip("/") if anchored else stripped

            # For dir-only patterns, check if the path itself is that directory
            if is_dir:
                if anchored:
                    if _match_path(rel_path, clean):
                        return True
                else:
                    if _match_unanchored(rel_path, clean, parts):
                        return True

            # Also check if any parent component matches (e.g. "data/" excludes "data/big.csv")
            for i in range(len(parts) - 1):
                parent = "/".join(parts[: i + 1])
                if anchored:
                    if _match_path(parent, clean):
                        return True
                else:
                    if _match_path(parent, clean):
                        return True
                    if _match_path(parts[i], clean):
                        return True
            continue

        # Anchored pattern (starts with /) — match from root only
        if anchored:
            clean = pattern.lstrip("/")
            if _match_path(rel_path, clean):
                return True
        else:
            # Unanchored — match against full path and any basename/suffix
            if _match_unanchored(rel_path, pattern, parts):
                return True

    return False


def _match_unanchored(rel_path: str, pattern: str, parts: list[str]) -> bool:
    """Try matching an unanchored pattern against the full path and each sub-path."""
    if _match_path(rel_path, pattern):
        return True
    for i in range(len(parts)):
        sub_path = "/".join(parts[i:])
        if _match_path(sub_path, pattern):
            return True
    return False


def _match_path(path: str, pattern: str) -> bool:
    """Match a path against a single pattern, supporting ** for directory wildcards."""
    if "**" in pattern:
        # Convert ** to regex-compatible fnmatch
        # "a/**/b" should match "a/b", "a/x/b", "a/x/y/b"
        regex_pattern = pattern.replace("**/", "__GLOBSTAR__")
        regex_pattern = regex_pattern.replace("**", "__GLOBSTAR__")

        # Try matching with 0..N intermediate directories
        # Simple approach: split on ** and check prefix/suffix
        parts = pattern.split("**")
        if len(parts) == 2:
            prefix, suffix = parts
            prefix = prefix.rstrip("/")
            suffix = suffix.lstrip("/")

            if prefix and suffix:
                return (
                    _starts_with(path, prefix)
                    and _ends_with(path, suffix)
                )
            elif prefix:
                return _starts_with(path, prefix)
            elif suffix:
                return _ends_with(path, suffix)
            else:
                return True

    return fnmatch.fnmatch(path, pattern)


def _starts_with(path: str, prefix: str) -> bool:
    """Check if path starts with prefix pattern."""
    path_parts = path.split("/")
    prefix_parts = prefix.split("/")
    if len(path_parts) < len(prefix_parts):
        return False
    return all(fnmatch.fnmatch(p, pat) for p, pat in zip(path_parts, prefix_parts))


def _ends_with(path: str, suffix: str) -> bool:
    """Check if path ends with suffix pattern."""
    path_parts = path.split("/")
    suffix_parts = suffix.split("/")
    if len(path_parts) < len(suffix_parts):
        return False
    return all(
        fnmatch.fnmatch(p, pat)
        for p, pat in zip(path_parts[-len(suffix_parts):], suffix_parts)
    )
