"""Fast parallel directory walking for Python, powered by Rust.

[![PyPI](https://img.shields.io/pypi/v/speedywalk.svg)](https://pypi.org/project/speedywalk/)
[![CI](https://github.com/Peter554/speedywalk/actions/workflows/check.yml/badge.svg)](https://github.com/Peter554/speedywalk/actions/workflows/check.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://peter554.github.io/speedywalk/)

Find the repo [here](https://github.com/Peter554/speedywalk).

## Features

- 🚀 **Fast**: Uses the rust [ignore](https://crates.io/crates/ignore) crate for fast directory traversal.
- ⚡ **Parallel**: Multi-threaded directory traversal.
- 🎯 **Smart filtering**: Built-in support for `.gitignore`, `.ignore`, and glob patterns.
- 🔒 **Type-safe**: Full type hints.

## Installation

```bash
pip install speedywalk
```

## Quick Start

```python
import speedywalk

# Find all Python files, respecting .gitignore
for entry in speedywalk.walk(".", filters=["*.py"]):
    if entry.is_file:
        print(entry.path)

# Custom configuration
for entry in speedywalk.walk(
    ".",
    filters=["*.yaml", "*.yml"],
    ignore_dirs=["node_modules", "venv"],
    max_depth=3,
    threads=4,
):
    print(entry.path_str, entry.is_dir)
```
"""

from __future__ import annotations

import dataclasses
import functools
from collections.abc import Collection, Iterator
from pathlib import Path

from speedywalk import _core  # ty: ignore[unresolved-import]

_PathLike = Path | str


def walk(
    root: _PathLike,
    *,
    filters: str | Collection[str] = (),
    ignore_dirs: _PathLike | Collection[_PathLike] = (),
    ignore_hidden: bool = True,
    respect_git_ignore: bool = True,
    respect_global_git_ignore: bool = True,
    respect_git_exclude: bool = True,
    respect_ignore: bool = True,
    follow_symlinks: bool = False,
    max_depth: int | None = None,
    min_depth: int | None = None,
    max_filesize: int | None = None,
    threads: int = 0,
) -> Iterator[DirEntry]:
    """Walk a directory tree in parallel, yielding DirEntry objects.

    This function uses Rust's `ignore` crate for fast parallel directory traversal
    with built-in support for gitignore rules and other common ignore patterns.

    ## Arguments

    - `root`: The root directory to start walking from.
    - `filters`: Glob patterns to filter files (any matching pattern includes the file).
      Example: `["*.py", "*.txt"]`
    - `ignore_dirs`: Directories to ignore (absolute or relative to root).
    - `ignore_hidden`: If True, ignore hidden files and directories.
    - `respect_git_ignore`: If True, respect .gitignore files.
    - `respect_global_git_ignore`: If True, respect global gitignore.
    - `respect_git_exclude`: If True, respect .git/info/exclude.
    - `respect_ignore`: If True, respect .ignore files.
    - `follow_symlinks`: If True, follow symbolic links.
    - `max_depth`: Maximum depth to descend.
    - `min_depth`: Minimum depth before yielding entries.
    - `max_filesize`: Maximum file size in bytes to consider.
    - `threads`: Number of threads to use (0 for automatic, based on CPU count).

    ## Yields

    `DirEntry` objects representing files and directories found during the walk.

    ## Raises

    `OSError` if an error occurs while walking (e.g., permission denied).

    ## Example

    ```python
    for entry in walk(".", filters=["*.py"], max_depth=2):
        if entry.is_file:
            print(entry.path)
    ```
    """
    # Convert root to string
    root_str = str(root)

    # Convert filters to list
    filters_list = [filters] if isinstance(filters, str) else list(filters)

    # Convert ignore_dirs to list of strings
    ignore_dirs_list = (
        [str(ignore_dirs)]
        if isinstance(ignore_dirs, _PathLike)
        else [str(d) for d in ignore_dirs]
    )

    # Call the Rust implementation which returns an iterator
    walk_iterator = _core.walk(
        root_str,
        filters_list,
        ignore_dirs_list,
        ignore_hidden,
        respect_git_ignore,
        respect_global_git_ignore,
        respect_git_exclude,
        respect_ignore,
        follow_symlinks,
        max_depth,
        min_depth,
        max_filesize,
        threads,
    )

    # Wrap each core entry in a DirEntry and yield
    for core_entry in walk_iterator:
        yield DirEntry(_core_entry=core_entry)


@dataclasses.dataclass(frozen=True)
class DirEntry:
    """A directory entry returned by walk().

    This class wraps the Rust DirEntry and provides convenient access to
    path information and file type checks.
    """

    _core_entry: _core.DirEntry

    @functools.cached_property
    def path(self) -> Path:
        """The full path as a Path object."""
        return Path(self._core_entry.path)

    @functools.cached_property
    def path_str(self) -> str:
        """The full path as a string."""
        return self._core_entry.path

    @functools.cached_property
    def is_file(self) -> bool:
        """True if this entry is a regular file."""
        return self._core_entry.is_file

    @functools.cached_property
    def is_dir(self) -> bool:
        """True if this entry is a directory."""
        return self._core_entry.is_dir

    @functools.cached_property
    def is_symlink(self) -> bool:
        """True if this entry is a symbolic link."""
        return self._core_entry.is_symlink
