from __future__ import annotations

import os
from pathlib import Path

from mikoshi.ignore import IgnoreMatcher


class ScanError(RuntimeError):
    pass


def _is_binary(path: Path) -> bool:
    try:
        with path.open("rb") as handle:
            chunk = handle.read(4096)
    except OSError:
        return True
    if b"\x00" in chunk:
        return True
    try:
        chunk.decode("utf-8")
    except UnicodeDecodeError:
        return True
    return False


def scan_repo(
    repo_root: Path,
    matcher: IgnoreMatcher,
    max_bytes: int,
) -> list[Path]:
    if not repo_root.exists() or not repo_root.is_dir():
        raise ScanError(f"Repository path does not exist: {repo_root}")

    files: list[Path] = []
    for root, dirs, filenames in os.walk(repo_root):
        root_path = Path(root)
        dirs[:] = [
            d
            for d in dirs
            if not matcher.is_ignored(root_path / d)
        ]
        for name in filenames:
            file_path = root_path / name
            if matcher.is_ignored(file_path):
                continue
            if file_path.is_symlink():
                continue
            try:
                size = file_path.stat().st_size
            except OSError:
                continue
            if size > max_bytes:
                continue
            if _is_binary(file_path):
                continue
            files.append(file_path)

    return sorted(files)
