from collections.abc import Iterable, Sequence
from types import TracebackType
from typing import IO, Any, AnyStr, Literal, Protocol, Self, overload, runtime_checkable

from storix.core import Tree
from storix.models import FileProperties
from storix.types import EchoMode, StorixPath, StrPathLike


@runtime_checkable
class Storage(Protocol):
    """Protocol for storage provider interface."""

    @property
    def root(self) -> StorixPath: ...
    @property
    def home(self) -> StorixPath: ...

    def chroot(self, new_root: StrPathLike) -> Self: ...
    def touch(self, path: StrPathLike, data: Any | None = None) -> bool: ...
    def echo(
        self,
        data: IO[AnyStr] | AnyStr | Iterable[AnyStr],
        path: StrPathLike,
        *,
        mode: EchoMode = 'w',
        chunksize: int = ...,
        content_type: str | None = None,
    ) -> bool: ...
    def cat(self, path: StrPathLike) -> bytes: ...
    def cd(self, path: StrPathLike | None = None) -> Self: ...
    def pwd(self) -> StorixPath: ...
    def mkdir(self, path: StrPathLike, *, parents: bool = False) -> None: ...
    def mv(self, source: StrPathLike, destination: StrPathLike) -> None: ...
    def cp(self, source: StrPathLike, destination: StrPathLike) -> None: ...
    def rm(self, path: StrPathLike) -> bool: ...
    def rmdir(self, path: StrPathLike, recursive: bool = False) -> bool: ...
    @overload
    def ls(
        self,
        path: StrPathLike | None = None,
        *,
        abs: Literal[False] = False,
        all: bool = True,
    ) -> Sequence[str]: ...
    @overload
    def ls(
        self, path: StrPathLike | None = None, *, abs: Literal[True], all: bool = True
    ) -> Sequence[StorixPath]: ...
    def ls(
        self, path: StrPathLike | None = None, *, abs: bool = False, all: bool = True
    ) -> Sequence[StorixPath | str]: ...
    def tree(self, path: StrPathLike | None = None, *, abs: bool = False) -> Tree: ...
    # TODO: bind stat and du return type to Node or Tree for du
    # the bound generic to TreeNode or Tree would cause circular import error...?
    def stat(self, path: StrPathLike) -> FileProperties: ...
    def du(self, path: StrPathLike | None = None) -> int: ...

    # non unix commands but useful utils
    def exists(self, path: StrPathLike) -> bool: ...
    def isdir(self, path: StrPathLike) -> bool: ...
    def isfile(self, path: StrPathLike) -> bool: ...
    def make_url(
        self,
        path: StrPathLike,
        *,
        astype: Literal['data_url'] = 'data_url',
    ) -> str: ...
    def make_data_url(self, path: StrPathLike) -> str: ...
    def parent(self, path: StrPathLike) -> StorixPath: ...
    def parents(self, path: StrPathLike) -> Sequence[StorixPath]: ...
    def empty(self, path: StrPathLike) -> bool: ...
    def is_root(self, path: StrPathLike) -> bool: ...

    def open(self) -> Self: ...
    def close(self) -> None: ...

    def __enter__(self) -> Self:
        return self.open()

    def __exit__(
        self,
        exc_type: type[BaseException],
        exc_value: BaseException,
        traceback: TracebackType,
    ) -> None:
        self.close()
