from __future__ import annotations

import os
from pathlib import Path
from typing import Callable, Iterable, Sequence

import rich.repr

from textual._callback import invoke


@rich.repr.auto
class FileMonitor:
    """Monitors files for changes and invokes a callback when it does."""

    _paths: set[Path]

    def __init__(self, paths: Sequence[Path], callback: Callable[[], None]) -> None:
        """Monitor the given file paths for changes.

        Args:
            paths: Paths to monitor.
            callback: Callback to invoke if any of the paths change.
        """
        self._paths = set(paths)
        self.callback = callback
        self._modified = self._get_last_modified_time()

    def __rich_repr__(self) -> rich.repr.Result:
        yield self._paths

    def _get_last_modified_time(self) -> float:
        """Get the most recent modified time out of all files being watched."""
        modified_times = []
        for path in self._paths:
            try:
                modified_time = os.stat(path).st_mtime
            except FileNotFoundError:
                modified_time = 0
            modified_times.append(modified_time)
        return max(modified_times, default=0)

    def check(self) -> bool:
        """Check the monitored files. Return True if any were changed since the last modification time."""
        modified = self._get_last_modified_time()
        changed = modified != self._modified
        self._modified = modified
        return changed

    def add_paths(self, paths: Iterable[Path]) -> None:
        """Adds paths to start being monitored.

        Args:
            paths: The paths to be monitored.
        """
        self._paths.update(paths)

    async def __call__(self) -> None:
        if self.check():
            await self.on_change()

    async def on_change(self) -> None:
        """Called when any of the monitored files change."""
        await invoke(self.callback)
