import abc
import pathlib
import sys
import types
from _collections_abc import dict_keys, dict_values
from _typeshed import StrPath
from collections.abc import Iterable, Iterator, Mapping
from email.message import Message
from importlib.abc import MetaPathFinder
from os import PathLike
from pathlib import Path
from re import Pattern
from typing import Any, ClassVar, Generic, NamedTuple, TypeVar, overload
from typing_extensions import Self, TypeAlias

_T = TypeVar("_T")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")

__all__ = [
    "Distribution",
    "DistributionFinder",
    "PackageNotFoundError",
    "distribution",
    "distributions",
    "entry_points",
    "files",
    "metadata",
    "requires",
    "version",
]

if sys.version_info >= (3, 10):
    __all__ += ["PackageMetadata", "packages_distributions"]

if sys.version_info >= (3, 10):
    from importlib.metadata._meta import PackageMetadata as PackageMetadata, SimplePath
    def packages_distributions() -> Mapping[str, list[str]]: ...

    _SimplePath: TypeAlias = SimplePath

else:
    _SimplePath: TypeAlias = Path

class PackageNotFoundError(ModuleNotFoundError):
    @property
    def name(self) -> str: ...  # type: ignore[override]

if sys.version_info >= (3, 13):
    _EntryPointBase = object
elif sys.version_info >= (3, 11):
    class DeprecatedTuple:
        def __getitem__(self, item: int) -> str: ...

    _EntryPointBase = DeprecatedTuple
else:
    class _EntryPointBase(NamedTuple):
        name: str
        value: str
        group: str

class EntryPoint(_EntryPointBase):
    pattern: ClassVar[Pattern[str]]
    if sys.version_info >= (3, 11):
        name: str
        value: str
        group: str

        def __init__(self, name: str, value: str, group: str) -> None: ...

    def load(self) -> Any: ...  # Callable[[], Any] or an importable module
    @property
    def extras(self) -> list[str]: ...
    if sys.version_info >= (3, 9):
        @property
        def module(self) -> str: ...
        @property
        def attr(self) -> str: ...
    if sys.version_info >= (3, 10):
        dist: ClassVar[Distribution | None]
        def matches(
            self,
            *,
            name: str = ...,
            value: str = ...,
            group: str = ...,
            module: str = ...,
            attr: str = ...,
            extras: list[str] = ...,
        ) -> bool: ...  # undocumented

    def __hash__(self) -> int: ...
    def __eq__(self, other: object) -> bool: ...
    if sys.version_info >= (3, 11):
        def __lt__(self, other: object) -> bool: ...
    if sys.version_info < (3, 12):
        def __iter__(self) -> Iterator[Any]: ...  # result of iter((str, Self)), really

if sys.version_info >= (3, 12):
    class EntryPoints(tuple[EntryPoint, ...]):
        def __getitem__(self, name: str) -> EntryPoint: ...  # type: ignore[override]
        def select(
            self,
            *,
            name: str = ...,
            value: str = ...,
            group: str = ...,
            module: str = ...,
            attr: str = ...,
            extras: list[str] = ...,
        ) -> EntryPoints: ...
        @property
        def names(self) -> set[str]: ...
        @property
        def groups(self) -> set[str]: ...

elif sys.version_info >= (3, 10):
    class DeprecatedList(list[_T]): ...

    class EntryPoints(DeprecatedList[EntryPoint]):  # use as list is deprecated since 3.10
        # int argument is deprecated since 3.10
        def __getitem__(self, name: int | str) -> EntryPoint: ...  # type: ignore[override]
        def select(
            self,
            *,
            name: str = ...,
            value: str = ...,
            group: str = ...,
            module: str = ...,
            attr: str = ...,
            extras: list[str] = ...,
        ) -> EntryPoints: ...
        @property
        def names(self) -> set[str]: ...
        @property
        def groups(self) -> set[str]: ...

if sys.version_info >= (3, 10) and sys.version_info < (3, 12):
    class Deprecated(Generic[_KT, _VT]):
        def __getitem__(self, name: _KT) -> _VT: ...
        @overload
        def get(self, name: _KT) -> _VT | None: ...
        @overload
        def get(self, name: _KT, default: _T) -> _VT | _T: ...
        def __iter__(self) -> Iterator[_KT]: ...
        def __contains__(self, *args: object) -> bool: ...
        def keys(self) -> dict_keys[_KT, _VT]: ...
        def values(self) -> dict_values[_KT, _VT]: ...

    class SelectableGroups(Deprecated[str, EntryPoints], dict[str, EntryPoints]):  # use as dict is deprecated since 3.10
        @classmethod
        def load(cls, eps: Iterable[EntryPoint]) -> Self: ...
        @property
        def groups(self) -> set[str]: ...
        @property
        def names(self) -> set[str]: ...
        @overload
        def select(self) -> Self: ...
        @overload
        def select(
            self,
            *,
            name: str = ...,
            value: str = ...,
            group: str = ...,
            module: str = ...,
            attr: str = ...,
            extras: list[str] = ...,
        ) -> EntryPoints: ...

class PackagePath(pathlib.PurePosixPath):
    def read_text(self, encoding: str = "utf-8") -> str: ...
    def read_binary(self) -> bytes: ...
    def locate(self) -> PathLike[str]: ...
    # The following attributes are not defined on PackagePath, but are dynamically added by Distribution.files:
    hash: FileHash | None
    size: int | None
    dist: Distribution

class FileHash:
    mode: str
    value: str
    def __init__(self, spec: str) -> None: ...

if sys.version_info >= (3, 12):
    class DeprecatedNonAbstract: ...
    _distribution_parent = DeprecatedNonAbstract
else:
    _distribution_parent = object

class Distribution(_distribution_parent):
    @abc.abstractmethod
    def read_text(self, filename: str) -> str | None: ...
    @abc.abstractmethod
    def locate_file(self, path: StrPath) -> _SimplePath: ...
    @classmethod
    def from_name(cls, name: str) -> Distribution: ...
    @overload
    @classmethod
    def discover(cls, *, context: DistributionFinder.Context) -> Iterable[Distribution]: ...
    @overload
    @classmethod
    def discover(
        cls, *, context: None = None, name: str | None = ..., path: list[str] = ..., **kwargs: Any
    ) -> Iterable[Distribution]: ...
    @staticmethod
    def at(path: StrPath) -> PathDistribution: ...

    if sys.version_info >= (3, 10):
        @property
        def metadata(self) -> PackageMetadata: ...
        @property
        def entry_points(self) -> EntryPoints: ...
    else:
        @property
        def metadata(self) -> Message: ...
        @property
        def entry_points(self) -> list[EntryPoint]: ...

    @property
    def version(self) -> str: ...
    @property
    def files(self) -> list[PackagePath] | None: ...
    @property
    def requires(self) -> list[str] | None: ...
    if sys.version_info >= (3, 10):
        @property
        def name(self) -> str: ...
    if sys.version_info >= (3, 13):
        @property
        def origin(self) -> types.SimpleNamespace: ...

class DistributionFinder(MetaPathFinder):
    class Context:
        name: str | None
        def __init__(self, *, name: str | None = ..., path: list[str] = ..., **kwargs: Any) -> None: ...
        @property
        def path(self) -> list[str]: ...

    @abc.abstractmethod
    def find_distributions(self, context: DistributionFinder.Context = ...) -> Iterable[Distribution]: ...

class MetadataPathFinder(DistributionFinder):
    @classmethod
    def find_distributions(cls, context: DistributionFinder.Context = ...) -> Iterable[PathDistribution]: ...
    if sys.version_info >= (3, 11):
        @classmethod
        def invalidate_caches(cls) -> None: ...
    elif sys.version_info >= (3, 10):
        # Yes, this is an instance method that has a parameter named "cls"
        def invalidate_caches(cls) -> None: ...

class PathDistribution(Distribution):
    _path: _SimplePath
    def __init__(self, path: _SimplePath) -> None: ...
    def read_text(self, filename: StrPath) -> str | None: ...
    def locate_file(self, path: StrPath) -> _SimplePath: ...

def distribution(distribution_name: str) -> Distribution: ...
@overload
def distributions(*, context: DistributionFinder.Context) -> Iterable[Distribution]: ...
@overload
def distributions(
    *, context: None = None, name: str | None = ..., path: list[str] = ..., **kwargs: Any
) -> Iterable[Distribution]: ...

if sys.version_info >= (3, 10):
    def metadata(distribution_name: str) -> PackageMetadata: ...

else:
    def metadata(distribution_name: str) -> Message: ...

if sys.version_info >= (3, 12):
    def entry_points(
        *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ...
    ) -> EntryPoints: ...

elif sys.version_info >= (3, 10):
    @overload
    def entry_points() -> SelectableGroups: ...
    @overload
    def entry_points(
        *, name: str = ..., value: str = ..., group: str = ..., module: str = ..., attr: str = ..., extras: list[str] = ...
    ) -> EntryPoints: ...

else:
    def entry_points() -> dict[str, list[EntryPoint]]: ...

def version(distribution_name: str) -> str: ...
def files(distribution_name: str) -> list[PackagePath] | None: ...
def requires(distribution_name: str) -> list[str] | None: ...
