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:
        """module name"""
        ...

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:
        """Return hash(self)."""
        ...
    def __eq__(self, other: object) -> bool:
        """Return self==value."""
        ...
    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]:
            """Return the set of all names of all entry points."""
            ...
        @property
        def groups(self) -> set[str]:
            """Return the set of all groups of all entry points."""
            ...

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]:
            """Return the set of all names of all entry points."""
            ...
        @property
        def groups(self) -> set[str]:
            """
            Return the set of all groups of all entry points.

            For coverage while SelectableGroups is present.
            >>> EntryPoints().groups
            set()
            """
            ...

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]:
            """
            for coverage:
            >>> SelectableGroups().names
            set()
            """
            ...
        @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:
            """
            Return the parsed metadata for this Distribution.

            The returned object will have keys that name the various bits of
            metadata per the
            `Core metadata specifications <https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata>`_.

            Custom providers may provide the METADATA file or override this
            property.
            """
            ...
        @property
        def entry_points(self) -> EntryPoints:
            """
            Return EntryPoints for this distribution.

            Custom providers may provide the ``entry_points.txt`` file
            or override this property.
            """
            ...
    else:
        @property
        def metadata(self) -> Message:
            """
            Return the parsed metadata for this Distribution.

            The returned object will have keys that name the various bits of
            metadata.  See PEP 566 for details.
            """
            ...
        @property
        def entry_points(self) -> list[EntryPoint]: ...

    @property
    def version(self) -> str:
        """Return the 'Version' metadata for the distribution package."""
        ...
    @property
    def files(self) -> list[PackagePath] | None:
        """
        Files in this distribution.

        :return: List of PackagePath for this distribution or None

        Result is `None` if the metadata file that enumerates files
        (i.e. RECORD for dist-info, or installed-files.txt or
        SOURCES.txt for egg-info) is missing.
        Result may be empty if the metadata exists but is empty.

        Custom providers are recommended to provide a "RECORD" file (in
        ``read_text``) or override this property to allow for callers to be
        able to resolve filenames provided by the package.
        """
        ...
    @property
    def requires(self) -> list[str] | None:
        """Generated requirements specified for this Distribution"""
        ...
    if sys.version_info >= (3, 10):
        @property
        def name(self) -> str:
            """Return the 'Name' metadata for the distribution package."""
            ...
    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]:
            """
            The sequence of directory path that a distribution finder
            should search.

            Typically refers to Python installed package paths such as
            "site-packages" directories and defaults to ``sys.path``.
            """
            ...

    @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: ...
