from os import PathLike
from pathlib import Path
from typing import Literal, overload

from typing_extensions import Self

class FastarError(Exception):
    """Base exception for all fastar errors."""

class ArchiveClosedError(FastarError):
    """Exception raised when attempting to use a closed archive."""

class ArchiveUnpackingError(FastarError):
    """Exception raised when unpacking an archive fails."""

class ArchiveAppendingError(FastarError):
    """Exception raised when appending to an archive fails."""

class NameDerivationError(ArchiveAppendingError):
    """Exception raised when a file name cannot be derived from a path."""

class ArchiveWriter:
    """A tar archive writer that supports compressed and uncompressed formats."""

    @classmethod
    def open(
        cls,
        path: str | Path | PathLike[str],
        mode: Literal["w", "w:gz", "w:zst"],
    ) -> Self:
        """
        Open a tar archive for writing.

        Args:
            path: Path to the archive file to create
            mode: Write mode - 'w' for uncompressed, 'w:gz' for gzip compressed, 'w:zst' for zstd compressed

        Returns:
            An ArchiveWriter instance

        Raises:
            ValueError: If an unsupported mode is provided
            OSError: If the file cannot be opened
        """

    def append(
        self,
        path: str | Path | PathLike[str],
        arcname: str | Path | PathLike[str] | None = None,
        recursive: bool = True,
        dereference: bool = False,
    ) -> None:
        """
        Append a file or directory to the archive.

        Args:
            path: Path to the file or directory to append
            arcname: Name to use in the archive (defaults to the filename)
            recursive: If True and path is a directory, append all contents recursively
            dereference: If True, append the target of symlinks instead of the symlink itself

        Raises:
            ArchiveClosedError: If the archive is already closed
            ArchiveAppendingError: If the target cannot be appended to the archive
            OSError: If there's an error reading the target file or directory
        """

    def close(self) -> None:
        """
        Close the archive and flush all pending writes.

        Raises:
            OSError: If there's an error flushing the archive
        """

    def __enter__(self) -> Self:
        """Enter the context manager."""

    def __exit__(self, exc_type, exc_value, traceback) -> bool:
        """Exit the context manager, closing the archive."""

class ArchiveReader:
    """A tar archive reader that supports compressed and uncompressed formats."""

    @classmethod
    def open(
        cls,
        path: str | Path | PathLike[str],
        mode: Literal["r", "r:", "r:gz", "r:zst"],
    ) -> Self:
        """
        Open a tar archive for reading.

        Args:
            path: Path to the archive file to read
            mode: Read mode - 'r' for transparent compression, 'r:gz' for gzip compressed, 'r:zst' for zstd compressed

        Returns:
            An ArchiveReader instance

        Raises:
            ValueError: If an unsupported mode is provided
            OSError: If the file cannot be opened
        """

    def unpack(
        self, to: str | Path | PathLike[str], preserve_mtime: bool = True
    ) -> None:
        """
        Unpack all contents of the archive and put them into the specified directory.

        Args:
            to: Destination directory path
            preserve_mtime: whether to preserve file modification times

        Raises:
            ArchiveClosedError: If the archive is already closed
            ArchiveUnpackingError: If the archive cannot be unpacked
            OSError: If unpacking fails due to I/O errors
        """

    def close(self) -> None:
        """Close the archive."""

    def __enter__(self) -> Self:
        """Enter the context manager."""

    def __exit__(self, exc_type, exc_value, traceback) -> bool:
        """Exit the context manager, closing the archive."""

@overload
def open(
    path: str | Path | PathLike[str], mode: Literal["w", "w:gz", "w:zst"]
) -> ArchiveWriter:
    """
    Open a tar archive for writing.

    Args:
        path: Path to the archive file to create
        mode: Write mode - 'w' for uncompressed, 'w:gz' for gzip compressed, 'w:zst' for zstd compressed

    Returns:
        An ArchiveWriter instance

    Raises:
        ValueError: If an unsupported mode is provided
        OSError: If the file cannot be opened
    """

@overload
def open(
    path: str | Path | PathLike[str], mode: Literal["r", "r:", "r:gz", "r:zst"]
) -> ArchiveReader:
    """
    Open a tar archive for reading.

    Args:
        path: Path to the archive file to read
        mode: Read mode - 'r' for transparent compression, 'r:gz' for gzip compressed, 'r:zst' for zstd compressed

    Returns:
        An ArchiveReader instance

    Raises:
        ValueError: If an unsupported mode is provided
        OSError: If the file cannot be opened
    """
