# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/

import os
import sys
from typing import (
    Any,
    Callable,
    Dict,
    List,
    NoReturn,
    Optional,
    Sequence as Sequence,
    Tuple,
    TYPE_CHECKING,
    Type,
    TypeVar,
    Union,
)
import warnings

if sys.version_info >= (3, 8):
    from typing import (
        Literal,
        Protocol,
        SupportsIndex as SupportsIndex,
        TypedDict,
        runtime_checkable,
    )
else:
    from typing_extensions import (
        Literal,
        Protocol,
        SupportsIndex as SupportsIndex,
        TypedDict,
        runtime_checkable,
    )

if TYPE_CHECKING:
    from git.objects import Commit, Tree, TagObject, Blob
    from git.repo import Repo

PathLike = Union[str, "os.PathLike[str]"]
"""A :class:`str` (Unicode) based file or directory path."""

TBD = Any
"""Alias of :class:`~typing.Any`, when a type hint is meant to become more specific."""

_T = TypeVar("_T")
"""Type variable used internally in GitPython."""

AnyGitObject = Union["Commit", "Tree", "TagObject", "Blob"]
"""Union of the :class:`~git.objects.base.Object`-based types that represent actual git
object types.

As noted in :class:`~git.objects.base.Object`, which has further details, these are:

* :class:`Blob <git.objects.blob.Blob>`
* :class:`Tree <git.objects.tree.Tree>`
* :class:`Commit <git.objects.commit.Commit>`
* :class:`TagObject <git.objects.tag.TagObject>`

Those GitPython classes represent the four git object types, per
:manpage:`gitglossary(7)`:

* "blob": https://git-scm.com/docs/gitglossary#def_blob_object
* "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
* "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
* "tag object": https://git-scm.com/docs/gitglossary#def_tag_object

For more general information on git objects and their types as git understands them:

* "object": https://git-scm.com/docs/gitglossary#def_object
* "object type": https://git-scm.com/docs/gitglossary#def_object_type

:note:
    See also the :class:`Tree_ish` and :class:`Commit_ish` unions.
"""

Tree_ish = Union["Commit", "Tree", "TagObject"]
"""Union of :class:`~git.objects.base.Object`-based types that are typically tree-ish.

See :manpage:`gitglossary(7)` on "tree-ish":
https://git-scm.com/docs/gitglossary#def_tree-ish

:note:
    :class:`~git.objects.tree.Tree` and :class:`~git.objects.commit.Commit` are the
    classes whose instances are all tree-ish. This union includes them, but also
    :class:`~git.objects.tag.TagObject`, only **most** of whose instances are tree-ish.
    Whether a particular :class:`~git.objects.tag.TagObject` peels (recursively
    dereferences) to a tree or commit, rather than a blob, can in general only be known
    at runtime. In practice, git tag objects are nearly always used for tagging commits,
    and such tags are tree-ish because commits are tree-ish.

:note:
    See also the :class:`AnyGitObject` union of all four classes corresponding to git
    object types.
"""

Commit_ish = Union["Commit", "TagObject"]
"""Union of :class:`~git.objects.base.Object`-based types that are typically commit-ish.

See :manpage:`gitglossary(7)` on "commit-ish":
https://git-scm.com/docs/gitglossary#def_commit-ish

:note:
    :class:`~git.objects.commit.Commit` is the only class whose instances are all
    commit-ish. This union type includes :class:`~git.objects.commit.Commit`, but also
    :class:`~git.objects.tag.TagObject`, only **most** of whose instances are
    commit-ish. Whether a particular :class:`~git.objects.tag.TagObject` peels
    (recursively dereferences) to a commit, rather than a tree or blob, can in general
    only be known at runtime. In practice, git tag objects are nearly always used for
    tagging commits, and such tags are of course commit-ish.

:note:
    See also the :class:`AnyGitObject` union of all four classes corresponding to git
    object types.
"""

GitObjectTypeString = Literal["commit", "tag", "blob", "tree"]
"""Literal strings identifying git object types and the
:class:`~git.objects.base.Object`-based types that represent them.

See the :attr:`Object.type <git.objects.base.Object.type>` attribute. These are its
values in :class:`~git.objects.base.Object` subclasses that represent git objects. These
literals therefore correspond to the types in the :class:`AnyGitObject` union.

These are the same strings git itself uses to identify its four object types.
See :manpage:`gitglossary(7)` on "object type":
https://git-scm.com/docs/gitglossary#def_object_type
"""

Lit_commit_ish: Type[Literal["commit", "tag"]]
"""Deprecated. Type of literal strings identifying typically-commitish git object types.

Prior to a bugfix, this type had been defined more broadly. Any usage is in practice
ambiguous and likely to be incorrect. This type has therefore been made a static type
error to appear in annotations. It is preserved, with a deprecated status, to avoid
introducing runtime errors in code that refers to it, but it should not be used.

Instead of this type:

* For the type of the string literals associated with :class:`Commit_ish`, use
  ``Literal["commit", "tag"]`` or create a new type alias for it. That is equivalent to
  this type as currently defined (but usable in statically checked type annotations).

* For the type of all four string literals associated with :class:`AnyGitObject`, use
  :class:`GitObjectTypeString`. That is equivalent to the old definition of this type
  prior to the bugfix (and is also usable in statically checked type annotations).
"""


def _getattr(name: str) -> Any:
    if name != "Lit_commit_ish":
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

    warnings.warn(
        "Lit_commit_ish is deprecated. It is currently defined as "
        '`Literal["commit", "tag"]`, which should be used in its place if desired. It '
        'had previously been defined as `Literal["commit", "tag", "blob", "tree"]`, '
        "covering all four git object type strings including those that are never "
        "commit-ish. For that, use the GitObjectTypeString type instead.",
        DeprecationWarning,
        stacklevel=2,
    )
    return Literal["commit", "tag"]


if not TYPE_CHECKING:  # Preserve static checking for undefined/misspelled attributes.
    __getattr__ = _getattr


def __dir__() -> List[str]:
    return [*globals(), "Lit_commit_ish"]


# Config_levels ---------------------------------------------------------

Lit_config_levels = Literal["system", "global", "user", "repository"]
"""Type of literal strings naming git configuration levels.

These strings relate to which file a git configuration variable is in.
"""

ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["repository"]]
"""Static type of a tuple of the four strings representing configuration levels."""

# Progress parameter type alias -----------------------------------------

CallableProgress = Optional[Callable[[int, Union[str, float], Union[str, float, None], str], None]]
"""General type of a function or other callable used as a progress reporter for cloning.

This is the type of a function or other callable that reports the progress of a clone,
when passed as a ``progress`` argument to :meth:`Repo.clone <git.repo.base.Repo.clone>`
or :meth:`Repo.clone_from <git.repo.base.Repo.clone_from>`.

:note:
    Those :meth:`~git.repo.base.Repo.clone` and :meth:`~git.repo.base.Repo.clone_from`
    methods also accept :meth:`~git.util.RemoteProgress` instances, including instances
    of its :meth:`~git.util.CallableRemoteProgress` subclass.

:note:
    Unlike objects that match this type, :meth:`~git.util.RemoteProgress` instances are
    not directly callable, not even when they are instances of
    :meth:`~git.util.CallableRemoteProgress`, which wraps a callable and forwards
    information to it but is not itself callable.

:note:
    This type also allows ``None``, for cloning without reporting progress.
"""

# -----------------------------------------------------------------------------------


def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None) -> None:
    """For use in exhaustive checking of a literal or enum in if/else chains.

    A call to this function should only be reached if not all members are handled, or if
    an attempt is made to pass non-members through the chain.

    :param inp:
        If all members are handled, the argument for `inp` will have the
        :class:`~typing.Never`/:class:`~typing.NoReturn` type.
        Otherwise, the type will mismatch and cause a mypy error.

    :param raise_error:
        If ``True``, will also raise :exc:`ValueError` with a general
        "unhandled literal" message, or the exception object passed as `exc`.

    :param exc:
        It not ``None``, this should be an already-constructed exception object, to be
        raised if `raise_error` is ``True``.
    """
    if raise_error:
        if exc is None:
            raise ValueError(f"An unhandled literal ({inp!r}) in an if/else chain was found")
        else:
            raise exc


class Files_TD(TypedDict):
    """Dictionary with stat counts for the diff of a particular file.

    For the :class:`~git.util.Stats.files` attribute of :class:`~git.util.Stats`
    objects.
    """

    insertions: int
    deletions: int
    lines: int


class Total_TD(TypedDict):
    """Dictionary with total stats from any number of files.

    For the :class:`~git.util.Stats.total` attribute of :class:`~git.util.Stats`
    objects.
    """

    insertions: int
    deletions: int
    lines: int
    files: int


class HSH_TD(TypedDict):
    """Dictionary carrying the same information as a :class:`~git.util.Stats` object."""

    total: Total_TD
    files: Dict[PathLike, Files_TD]


@runtime_checkable
class Has_Repo(Protocol):
    """Protocol for having a :attr:`repo` attribute, the repository to operate on."""

    repo: "Repo"


@runtime_checkable
class Has_id_attribute(Protocol):
    """Protocol for having :attr:`_id_attribute_` used in iteration and traversal."""

    _id_attribute_: str
