"""Utility functions for file-organizer.

Provides helper functions for file iteration, directory creation, safe file moves,
and human-readable byte formatting.
"""

from __future__ import annotations

import fnmatch
import shutil
from collections.abc import Iterator
from pathlib import Path


def iter_files(
    directory: Path,
    recursive: bool = False,
    ignore_patterns: list[str] | None = None,
) -> Iterator[Path]:
    """Iterate over files in a directory with optional filtering.

    Yields Path objects for each file (not directories) that doesn't match
    any of the ignore patterns.

    Args:
        directory: Root directory to scan.
        recursive: If True, recursively scan subdirectories using rglob.
            If False, only scan immediate children using iterdir().
        ignore_patterns: List of glob patterns (e.g., ["*.tmp", "*.log"]).
            Files matching any pattern are skipped.

    Yields:
        Path objects for each matching file.
    """
    ignore_patterns = ignore_patterns or []

    def should_ignore(path: Path) -> bool:
        """Check if a file name matches any ignore pattern."""
        return any(fnmatch.fnmatch(path.name, pattern) for pattern in ignore_patterns)

    if recursive:
        # Recursive: walk all subdirectories
        for path in directory.rglob("*"):
            if path.is_file() and not should_ignore(path):
                yield path
    else:
        # Non-recursive: only immediate children
        for path in directory.iterdir():
            if path.is_file() and not should_ignore(path):
                yield path


def ensure_dir(path: Path) -> Path:
    """Ensure directory exists, create if needed.

    Creates parent directories as needed. No-op if directory already exists.

    Args:
        path: Directory path to ensure exists.

    Returns:
        The same path (for chaining).
    """
    path.mkdir(parents=True, exist_ok=True)
    return path


def safe_move(source: Path, destination: Path) -> bool:
    """Safely move a file, handling conflicts by renaming.

    If destination exists, appends _1, _2, etc. to the filename
    until a unique name is found. Uses shutil.move for cross-filesystem support.

    Args:
        source: Source file path.
        destination: Destination file path.

    Returns:
        True if move succeeded, False on any error.
    """
    # Ensure destination directory exists
    destination.parent.mkdir(parents=True, exist_ok=True)

    # Handle name conflicts by appending counter
    if destination.exists():
        counter = 1
        stem = destination.stem
        suffix = destination.suffix
        while destination.exists():
            destination = destination.with_name(f"{stem}_{counter}{suffix}")
            counter += 1

    try:
        shutil.move(str(source), str(destination))
        return True
    except Exception:
        return False


def format_bytes(size: int) -> str:
    """Format bytes as human-readable string.

    Converts bytes to KB, MB, GB, or TB with one decimal place.

    Args:
        size: Size in bytes.

    Returns:
        Formatted string (e.g., "1.5 MB", "1024 B", "2.0 GB").
    """
    size_float = float(size)
    for unit in ["B", "KB", "MB", "GB", "TB"]:
        if size_float < 1024:
            return f"{size_float:.1f} {unit}"
        size_float /= 1024
    return f"{size_float:.1f} PB"
