from typing import Any, Literal, Protocol

__all__ = 'RustNotify', 'WatchfilesRustInternalError'

__version__: str
"""The package version as defined in `Cargo.toml`, modified to match python's versioning semantics."""

class AbstractEvent(Protocol):
    def is_set(self) -> bool: ...

class RustNotify:
    """
    Interface to the Rust [notify](https://crates.io/crates/notify) crate which does
    the heavy lifting of watching for file changes and grouping them into events.
    """

    def __init__(
        self,
        watch_paths: list[str],
        debug: bool,
        force_polling: bool,
        poll_delay_ms: int,
        recursive: bool,
        ignore_permission_denied: bool,
    ) -> None:
        """
        Create a new `RustNotify` instance and start a thread to watch for changes.

        `FileNotFoundError` is raised if any of the paths do not exist.

        Args:
            watch_paths: file system paths to watch for changes, can be directories or files
            debug: if true, print details about all events to stderr
            force_polling: if true, always use polling instead of file system notifications
            poll_delay_ms: delay between polling for changes, only used if `force_polling=True`
            recursive: if `True`, watch for changes in sub-directories recursively, otherwise watch only for changes in
                the top-level directory, default is `True`.
            ignore_permission_denied: if `True`, permission denied errors are ignored while watching changes.
        """
    def watch(
        self,
        debounce_ms: int,
        step_ms: int,
        timeout_ms: int,
        stop_event: AbstractEvent | None,
    ) -> set[tuple[int, str]] | Literal['signal', 'stop', 'timeout']:
        """
        Watch for changes.

        This method will wait `timeout_ms` milliseconds for changes, but once a change is detected,
        it will group changes and return in no more than `debounce_ms` milliseconds.

        The GIL is released during a `step_ms` sleep on each iteration to avoid
        blocking python.

        Args:
            debounce_ms: maximum time in milliseconds to group changes over before returning.
            step_ms: time to wait for new changes in milliseconds, if no changes are detected
                in this time, and at least one change has been detected, the changes are yielded.
            timeout_ms: maximum time in milliseconds to wait for changes before returning,
                `0` means wait indefinitely, `debounce_ms` takes precedence over `timeout_ms` once
                a change is detected.
            stop_event: event to check on every iteration to see if this function should return early.
                The event should be an object which has an `is_set()` method which returns a boolean.

        Returns:
            See below.

        Return values have the following meanings:

        * Change details as a `set` of `(event_type, path)` tuples, the event types are ints which match
          [`Change`][watchfiles.Change], `path` is a string representing the path of the file that changed
        * `'signal'` string, if a signal was received
        * `'stop'` string, if the `stop_event` was set
        * `'timeout'` string, if `timeout_ms` was exceeded
        """
    def __enter__(self) -> RustNotify:
        """
        Does nothing, but allows `RustNotify` to be used as a context manager.

        !!! note

            The watching thead is created when an instance is initiated, not on `__enter__`.
        """
    def __exit__(self, *args: Any) -> None:
        """
        Calls [`close`][watchfiles._rust_notify.RustNotify.close].
        """
    def close(self) -> None:
        """
        Stops the watching thread. After `close` is called, the `RustNotify` instance can no
        longer be used, calls to [`watch`][watchfiles._rust_notify.RustNotify.watch] will raise a `RuntimeError`.

        !!! note

            `close` is not required, just deleting the `RustNotify` instance will kill the thread
            implicitly.

            As per [#163](https://github.com/samuelcolvin/watchfiles/issues/163) `close()` is only required because
            in the event of an error, the traceback in `sys.exc_info` keeps a reference to `watchfiles.watch`'s
            frame, so you can't rely on the `RustNotify` object being deleted, and thereby stopping
            the watching thread.
        """

class WatchfilesRustInternalError(RuntimeError):
    """
    Raised when RustNotify encounters an unknown error.

    If you get this a lot, please check [github](https://github.com/samuelcolvin/watchfiles/issues) issues
    and create a new issue if your problem is not discussed.
    """
