import json
from collections.abc import Collection
from typing import (
    Any,
    AnyStr,
    Callable,
    Protocol,
    Self,
    dataclass_transform,
)

from ._abstractions import AbstractJSONWizard, W
from ._bases_meta import BaseJSONWizardMeta
from ._type_def import Decoder, Encoder, JSONObject, ListOfJSONObject
from .enums import KeyCase

def first_declared_attr_in_mro(cls: type, name: str) -> Callable | Any | None: ...
def set_from_dict_and_to_dict_if_needed(cls: type) -> None: ...
def configure_wizard_class(cls: type,
                           str: bool = False,
                           debug: bool | int = False,
                           case: KeyCase | str | None = None,
                           dump_case: KeyCase | str | None = None,
                           load_case: KeyCase | str | None  = None):
    ...

class SerializerHookMixin(Protocol):
    @classmethod
    def _pre_from_dict(cls: type[Self], o: JSONObject) -> JSONObject:
        """
        Optional hook that runs before the dataclass instance is
        loaded, and before it is converted from a dictionary object
        via :meth:`from_dict`.

        To override this, subclasses need to implement this method.
        A simple example is shown below:

        >>> from dataclasses import dataclass
        >>> from dataclass_wizard import JSONWizard
        >>> from dataclass_wizard._type_def import JSONObject
        >>>
        >>>
        >>> @dataclass
        >>> class MyClass(JSONWizard):
        >>>     a_bool: bool
        >>>
        >>>     @classmethod
        >>>     def _pre_from_dict(cls, o: JSONObject) -> JSONObject:
        >>>         # o = o.copy()  # Copying the `dict` object is optional
        >>>         o['a_bool'] = True  # Add a new key/value pair
        >>>         return o
        >>>
        >>> c = MyClass.from_dict({})
        >>> assert c == MyClass(a_bool=True)
        """
        ...

    def _pre_to_dict(self: Self) -> Self:
        # noinspection PyDunderSlots, PyUnresolvedReferences
        """
        Optional hook that runs before the dataclass instance is processed and
        before it is converted to a dictionary object via :meth:`to_dict`.

        To override this, subclasses need to extend from :class:`DumpMixIn`
        and implement this method. A simple example is shown below:

        >>> from dataclasses import dataclass
        >>> from dataclass_wizard import JSONWizard
        >>>
        >>>
        >>> @dataclass
        >>> class MyClass(JSONWizard):
        >>>     my_str: str
        >>>
        >>>     def _pre_to_dict(self):
        >>>         self.my_str = self.my_str.swapcase()
        >>>         return self
        >>>
        >>> assert MyClass('test').to_dict() == {'myStr': 'TEST'}
        """
        ...


class _JSONWizardMixin(AbstractJSONWizard, SerializerHookMixin):
    """
    Mixin class to allow a `dataclass` sub-class to be easily converted
    to and from JSON.

    """

    class Meta(BaseJSONWizardMeta):
        """
        Inner meta class that can be extended by sub-classes for additional
        customization with the JSON load / dump process.
        """
        # Class attribute to enable detection of the class type.
        __is_inner_meta__ = True

    @classmethod
    def from_json(cls: type[W], string: AnyStr, *,
                  decoder: Decoder = json.loads,
                  **decoder_kwargs) -> W | list[W]:
        """
        Converts a JSON `string` to an instance of the dataclass, or a list of
        the dataclass instances.
        """
        ...

    @classmethod
    def from_list(cls: type[W], o: ListOfJSONObject) -> list[W]:
        """
        Converts a Python `list` object to a list of the dataclass instances.
        """
        # alias: fromlist(cls, o)
        ...

    @classmethod
    def from_dict(cls: type[W], o: JSONObject) -> W:
        # alias: fromdict(cls, o)
        ...

    def to_dict(self: W,
                *,
                dict_factory=dict,
                exclude: Collection[str] | None = None,
                skip_defaults: bool | None = None,
                ) -> JSONObject:
        """
        Converts the dataclass instance to a Python dictionary object that is
        JSON serializable.

        Example usage:

          @dataclass
          class C(JSONWizard):
              x: int
              y: int
              z: bool = True

          c = C(1, 2, True)
          assert c.to_dict(skip_defaults=True) == {'x': 1, 'y': 2}

        If given, 'dict_factory' will be used instead of built-in dict.
        The function applies recursively to field values that are
        dataclass instances. This will also look into built-in containers:
        tuples, lists, and dicts.
        """
        # alias: asdict(self)
        ...

    def to_json(self: W, *,
                encoder: Encoder = json.dumps,
                **encoder_kwargs) -> str:
        """
        Converts the dataclass instance to a JSON `string` representation.
        """
        ...

    @classmethod
    def list_to_json(cls: type[W],
                     instances: list[W],
                     encoder: Encoder = json.dumps,
                     **encoder_kwargs) -> str:
        """
        Converts a ``list`` of dataclass instances to a JSON `string`
        representation.
        """
        ...

@dataclass_transform()
class DataclassWizard(_JSONWizardMixin):
    class Meta(BaseJSONWizardMeta):
        ...
    @classmethod
    def from_dict(cls: type[W], o: JSONObject) -> W: ...
    @classmethod
    def from_list(cls: type[W], o: ListOfJSONObject) -> list[W]: ...
    @classmethod
    def from_json(cls: type[W], string: AnyStr, *, decoder: Decoder = ..., **decoder_kwargs) -> W | list[W]: ...
    def to_dict(self: W, *, dict_factory=..., exclude: Collection[str] | None = ..., skip_defaults: bool | None = ...) -> JSONObject: ...
    def to_json(self: W, *, encoder: Encoder = ..., **encoder_kwargs) -> str: ...
    @classmethod
    def list_to_json(cls: type[W], instances: list[W], encoder: Encoder = ..., **encoder_kwargs) -> str: ...

class JSONWizard(_JSONWizardMixin): ...

def _str_fn() -> Callable[[W], str]:
    """
    Converts the dataclass instance to a *prettified* JSON string
    representation, when the `str()` method is invoked.
    """
    ...
