import enum
import sys
import types
from _typeshed import DataclassInstance
from builtins import type as Type  # alias to avoid name clashes with fields named "type"
from collections.abc import Callable, Iterable, Mapping
from types import GenericAlias
from typing import Any, Final, Generic, Literal, Protocol, TypeVar, overload, type_check_only
from typing_extensions import Never, TypeIs

_T = TypeVar("_T")
_T_co = TypeVar("_T_co", covariant=True)

__all__ = [
    "dataclass",
    "field",
    "Field",
    "FrozenInstanceError",
    "InitVar",
    "KW_ONLY",
    "MISSING",
    "fields",
    "asdict",
    "astuple",
    "make_dataclass",
    "replace",
    "is_dataclass",
]

_DataclassT = TypeVar("_DataclassT", bound=DataclassInstance)

@type_check_only
class _DataclassFactory(Protocol):
    def __call__(
        self,
        cls: type[_T],
        /,
        *,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
    ) -> type[_T]: ...

# define _MISSING_TYPE as an enum within the type stubs,
# even though that is not really its type at runtime
# this allows us to use Literal[_MISSING_TYPE.MISSING]
# for background, see:
#   https://github.com/python/typeshed/pull/5900#issuecomment-895513797
class _MISSING_TYPE(enum.Enum):
    MISSING = enum.auto()

MISSING: Final = _MISSING_TYPE.MISSING

class KW_ONLY: ...

@overload
def asdict(obj: DataclassInstance) -> dict[str, Any]: ...
@overload
def asdict(obj: DataclassInstance, *, dict_factory: Callable[[list[tuple[str, Any]]], _T]) -> _T: ...

@overload
def astuple(obj: DataclassInstance) -> tuple[Any, ...]: ...
@overload
def astuple(obj: DataclassInstance, *, tuple_factory: Callable[[list[Any]], _T]) -> _T: ...

if sys.version_info >= (3, 11):
    @overload
    def dataclass(
        cls: type[_T],
        /,
        *,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
    ) -> type[_T]: ...
    @overload
    def dataclass(
        cls: None = None,
        /,
        *,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
    ) -> Callable[[type[_T]], type[_T]]: ...
else:
    @overload
    def dataclass(
        cls: type[_T],
        /,
        *,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
    ) -> type[_T]: ...
    @overload
    def dataclass(
        cls: None = None,
        /,
        *,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
    ) -> Callable[[type[_T]], type[_T]]: ...

# See https://github.com/python/mypy/issues/10750
@type_check_only
class _DefaultFactory(Protocol[_T_co]):
    def __call__(self) -> _T_co: ...

class Field(Generic[_T]):
    if sys.version_info >= (3, 14):
        __slots__ = (
            "name",
            "type",
            "default",
            "default_factory",
            "repr",
            "hash",
            "init",
            "compare",
            "metadata",
            "kw_only",
            "doc",
            "_field_type",
        )
    else:
        __slots__ = (
            "name",
            "type",
            "default",
            "default_factory",
            "repr",
            "hash",
            "init",
            "compare",
            "metadata",
            "kw_only",
            "_field_type",
        )
    name: str
    type: Type[_T] | str | Any
    default: _T | Literal[_MISSING_TYPE.MISSING]
    default_factory: _DefaultFactory[_T] | Literal[_MISSING_TYPE.MISSING]
    repr: bool
    hash: bool | None
    init: bool
    compare: bool
    metadata: types.MappingProxyType[Any, Any]

    if sys.version_info >= (3, 14):
        doc: str | None

    kw_only: bool | Literal[_MISSING_TYPE.MISSING]

    if sys.version_info >= (3, 14):
        def __init__(
            self,
            default: _T,
            default_factory: Callable[[], _T],
            init: bool,
            repr: bool,
            hash: bool | None,
            compare: bool,
            metadata: Mapping[Any, Any],
            kw_only: bool,
            doc: str | None,
        ) -> None: ...
    else:
        def __init__(
            self,
            default: _T,
            default_factory: Callable[[], _T],
            init: bool,
            repr: bool,
            hash: bool | None,
            compare: bool,
            metadata: Mapping[Any, Any],
            kw_only: bool,
        ) -> None: ...

    def __set_name__(self, owner: Type[Any], name: str) -> None: ...
    def __class_getitem__(cls, item: Any, /) -> GenericAlias: ...

# NOTE: Actual return type is 'Field[_T]', but we want to help type checkers
# to understand the magic that happens at runtime.
if sys.version_info >= (3, 14):
    @overload  # `default` and `default_factory` are optional and mutually exclusive.
    def field(
        *,
        default: _T,
        default_factory: Literal[_MISSING_TYPE.MISSING] = ...,
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
        doc: str | None = None,
    ) -> _T: ...
    @overload
    def field(
        *,
        default: Literal[_MISSING_TYPE.MISSING] = ...,
        default_factory: Callable[[], _T],
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
        doc: str | None = None,
    ) -> _T: ...
    @overload
    def field(
        *,
        default: Literal[_MISSING_TYPE.MISSING] = ...,
        default_factory: Literal[_MISSING_TYPE.MISSING] = ...,
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
        doc: str | None = None,
    ) -> Any: ...
else:
    @overload  # `default` and `default_factory` are optional and mutually exclusive.
    def field(
        *,
        default: _T,
        default_factory: Literal[_MISSING_TYPE.MISSING] = ...,
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
    ) -> _T: ...
    @overload
    def field(
        *,
        default: Literal[_MISSING_TYPE.MISSING] = ...,
        default_factory: Callable[[], _T],
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
    ) -> _T: ...
    @overload
    def field(
        *,
        default: Literal[_MISSING_TYPE.MISSING] = ...,
        default_factory: Literal[_MISSING_TYPE.MISSING] = ...,
        init: bool = True,
        repr: bool = True,
        hash: bool | None = None,
        compare: bool = True,
        metadata: Mapping[Any, Any] | None = None,
        kw_only: bool | Literal[_MISSING_TYPE.MISSING] = ...,
    ) -> Any: ...

def fields(class_or_instance: DataclassInstance | type[DataclassInstance]) -> tuple[Field[Any], ...]: ...

# HACK: `obj: Never` typing matches if object argument is using `Any` type.
@overload
def is_dataclass(obj: Never) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...  # type: ignore[narrowed-type-not-subtype]  # pyright: ignore[reportGeneralTypeIssues]  # ty:ignore[invalid-type-guard-definition]
@overload
def is_dataclass(obj: type) -> TypeIs[type[DataclassInstance]]: ...
@overload
def is_dataclass(obj: object) -> TypeIs[DataclassInstance | type[DataclassInstance]]: ...

class FrozenInstanceError(AttributeError): ...

class InitVar(Generic[_T]):
    __slots__ = ("type",)
    type: Type[_T]  # ty:ignore[unbound-type-variable]
    def __init__(self, type: Type[_T]) -> None: ...

    @overload
    def __class_getitem__(
        cls, type: Type[_T]
    ) -> InitVar[_T]: ...  # pyright: ignore[reportInvalidTypeForm]  # ty:ignore[invalid-type-form]
    @overload
    def __class_getitem__(
        cls, type: Any
    ) -> InitVar[Any]: ...  # pyright: ignore[reportInvalidTypeForm]  # ty:ignore[invalid-type-form]

if sys.version_info >= (3, 14):
    def make_dataclass(
        cls_name: str,
        fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]],
        *,
        bases: tuple[type, ...] = (),
        namespace: dict[str, Any] | None = None,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
        module: str | None = None,
        decorator: _DataclassFactory = ...,
    ) -> type: ...

elif sys.version_info >= (3, 12):
    def make_dataclass(
        cls_name: str,
        fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]],
        *,
        bases: tuple[type, ...] = (),
        namespace: dict[str, Any] | None = None,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
        module: str | None = None,
    ) -> type: ...

elif sys.version_info >= (3, 11):
    def make_dataclass(
        cls_name: str,
        fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]],
        *,
        bases: tuple[type, ...] = (),
        namespace: dict[str, Any] | None = None,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
        weakref_slot: bool = False,
    ) -> type: ...

else:
    def make_dataclass(
        cls_name: str,
        fields: Iterable[str | tuple[str, Any] | tuple[str, Any, Any]],
        *,
        bases: tuple[type, ...] = (),
        namespace: dict[str, Any] | None = None,
        init: bool = True,
        repr: bool = True,
        eq: bool = True,
        order: bool = False,
        unsafe_hash: bool = False,
        frozen: bool = False,
        match_args: bool = True,
        kw_only: bool = False,
        slots: bool = False,
    ) -> type: ...

def replace(obj: _DataclassT, /, **changes: Any) -> _DataclassT: ...
