from __future__ import annotations

from typing import TYPE_CHECKING
from typing import Any
from typing import TypeVar


WT = TypeVar("WT", bound="WrapperType")

if TYPE_CHECKING:  # pragma: no cover
    # Define _CustomList and _CustomDict as a workaround for:
    # https://github.com/python/mypy/issues/11427
    #
    # According to this issue, the typeshed contains a "lie"
    # (it adds MutableSequence to the ancestry of list and MutableMapping to
    # the ancestry of dict) which completely messes with the type inference for
    # Table, InlineTable, Array and Container.
    #
    # Importing from builtins is preferred over simple assignment, see issues:
    # https://github.com/python/mypy/issues/8715
    # https://github.com/python/mypy/issues/10068
    from builtins import dict as _CustomDict  # noqa: N812
    from builtins import float as _CustomFloat  # noqa: N812
    from builtins import int as _CustomInt  # noqa: N812
    from builtins import list as _CustomList  # noqa: N812
    from typing import Callable
    from typing import Concatenate
    from typing import ParamSpec
    from typing import Protocol

    P = ParamSpec("P")

    class WrapperType(Protocol):
        def _new(self: WT, value: Any) -> WT:
            ...

else:
    from collections.abc import MutableMapping
    from collections.abc import MutableSequence
    from numbers import Integral
    from numbers import Real

    class _CustomList(MutableSequence, list):
        """Adds MutableSequence mixin while pretending to be a builtin list"""

    class _CustomDict(MutableMapping, dict):
        """Adds MutableMapping mixin while pretending to be a builtin dict"""

    class _CustomInt(Integral, int):
        """Adds Integral mixin while pretending to be a builtin int"""

    class _CustomFloat(Real, float):
        """Adds Real mixin while pretending to be a builtin float"""


def wrap_method(
    original_method: Callable[Concatenate[WT, P], Any]
) -> Callable[Concatenate[WT, P], Any]:
    def wrapper(self: WT, *args: P.args, **kwargs: P.kwargs) -> Any:
        result = original_method(self, *args, **kwargs)
        if result is NotImplemented:
            return result
        return self._new(result)

    return wrapper
