import os
import re

MAX_MATCHES = 100
MAX_OUTPUT = 100_000  # 100KB

# Common binary/generated patterns to skip
_SKIP_DIRS = {".git", "__pycache__", "node_modules", ".venv", "venv", ".tox", ".mypy_cache", ".ruff_cache"}
_BINARY_EXTS = {
    ".pyc", ".pyo", ".so", ".dylib", ".dll", ".exe", ".bin",
    ".png", ".jpg", ".gif", ".pdf", ".zip", ".tar", ".gz",
}


def _load_gitignore(root: str) -> list[str]:
    """Load .gitignore patterns (simple glob matching)."""
    path = os.path.join(root, ".gitignore")
    if not os.path.isfile(path):
        return []
    with open(path) as f:
        return [line.strip() for line in f if line.strip() and not line.startswith("#")]


def _should_skip(name: str, is_dir: bool) -> bool:
    if is_dir and name in _SKIP_DIRS:
        return True
    if not is_dir:
        _, ext = os.path.splitext(name)
        if ext.lower() in _BINARY_EXTS:
            return True
    return False


async def handler(params: dict) -> dict:
    """Search files for a regex pattern."""
    pattern = params["pattern"]
    path = params.get("path") or os.getcwd()
    ignore_case = params.get("ignore_case", False)
    context = params.get("context") or 0
    limit = params.get("limit") or MAX_MATCHES

    flags = re.IGNORECASE if ignore_case else 0
    try:
        regex = re.compile(pattern, flags)
    except re.error as e:
        raise ValueError(f"Invalid regex pattern: {e}")

    results = []
    match_count = 0

    # Single file search
    if os.path.isfile(path):
        files_to_search = [path]
    else:
        files_to_search = []
        for root, dirs, files in os.walk(path):
            # Prune skip dirs
            dirs[:] = [d for d in dirs if not _should_skip(d, True)]
            for fname in sorted(files):
                if not _should_skip(fname, False):
                    files_to_search.append(os.path.join(root, fname))

    for fpath in files_to_search:
        if match_count >= limit:
            break
        try:
            with open(fpath, "r", errors="replace") as f:
                lines = f.readlines()
        except (OSError, UnicodeDecodeError):
            continue

        for i, line in enumerate(lines):
            if match_count >= limit:
                break
            if regex.search(line):
                match_count += 1
                if context > 0:
                    start = max(0, i - context)
                    end = min(len(lines), i + context + 1)
                    for j in range(start, end):
                        marker = ">" if j == i else " "
                        results.append(f"{fpath}:{j + 1}:{marker}{lines[j].rstrip()}")
                    results.append("--")
                else:
                    results.append(f"{fpath}:{i + 1}:{line.rstrip()}")

    output = "\n".join(results)
    if len(output) > MAX_OUTPUT:
        output = output[:MAX_OUTPUT] + "\n... (truncated)"

    if not results:
        output = f"No matches found for pattern: {pattern}"

    return {"matches": output}
