"""Shared utilities for the local MCP server."""

import fnmatch
import pathlib

from cade_mcp_local.config import DEFAULT_IGNORE_PATTERNS, get_project_root


def resolve_path(
    path_str: str,
    base_dir: pathlib.Path | None = None,
    confine_to_root: bool = False,
) -> pathlib.Path:
    """Resolve a path safely, handling relative, absolute, and home paths.

    Args:
        path_str: The path string to resolve.
        base_dir: Base directory for relative paths. Defaults to project root.
        confine_to_root: If True, raises PathSecurityError when the resolved
            path falls outside the project root.

    Returns:
        Resolved absolute path.

    Raises:
        PathSecurityError: If confine_to_root is True and path escapes project root.

    Examples:
        >>> resolve_path("src/main.py")  # Relative to project root
        >>> resolve_path("/etc/passwd")   # Absolute, returned as-is
        >>> resolve_path("~/Documents")   # Home directory expansion
    """
    if path_str is None:
        raise TypeError("path_str cannot be None")

    if base_dir is None:
        base_dir = get_project_root()

    path = pathlib.Path(path_str)

    # Handle home directory expansion
    if path_str.startswith("~"):
        resolved = path.expanduser().resolve()
    elif path.is_absolute():
        resolved = path.resolve()
    else:
        resolved = (base_dir / path).resolve()

    if confine_to_root:
        root = get_project_root().resolve()
        try:
            resolved.relative_to(root)
        except ValueError:
            from cade_mcp_local.errors import PathSecurityError

            raise PathSecurityError(
                str(path_str),
                f"path resolves outside project root ({root})",
            )

    return resolved


def should_ignore(
    path: pathlib.Path,
    ignore_patterns: list[str] | None = None,
) -> bool:
    """Check if a path should be ignored based on patterns.

    Args:
        path: The path to check.
        ignore_patterns: Patterns to check against. Defaults to DEFAULT_IGNORE_PATTERNS.

    Returns:
        True if the path should be ignored.

    Pattern types supported:
        - "*.ext" - Match files with extension
        - "*pattern*" - Glob patterns
        - "dirname" - Exact directory/file name match
    """
    if ignore_patterns is None:
        ignore_patterns = DEFAULT_IGNORE_PATTERNS

    name = path.name
    path_parts = path.parts

    for pattern in ignore_patterns:
        # Extension patterns: "*.pyc"
        if pattern.startswith("*."):
            if name.endswith(pattern[1:]):
                return True
        # Glob patterns: "*test*"
        elif "*" in pattern:
            if fnmatch.fnmatch(name, pattern):
                return True
        # Exact name match in any path component
        else:
            if pattern in path_parts or name == pattern:
                return True

    return False


def make_relative(path: pathlib.Path, base: pathlib.Path | None = None) -> str:
    """Convert an absolute path to a relative path string.

    Args:
        path: The path to convert.
        base: Base directory. Defaults to project root.

    Returns:
        Relative path string, or absolute path if not under base.
    """
    if base is None:
        base = get_project_root()

    try:
        return str(path.relative_to(base))
    except ValueError:
        return str(path)
