from collections.abc import Callable
from collections.abc import Iterable
from collections.abc import Iterator
from collections.abc import Mapping
from typing import Any
from typing import Generic
from typing import Literal
from typing import NoReturn
from typing import overload
from typing import TypeVar

from .mixins import ImmutableDictMixin
from .mixins import ImmutableListMixin
from .mixins import ImmutableMultiDictMixin
from .mixins import UpdateDictMixin

D = TypeVar("D")
K = TypeVar("K")
T = TypeVar("T")
V = TypeVar("V")
_CD = TypeVar("_CD", bound="CallbackDict[Any, Any]")

def is_immutable(self: object) -> NoReturn: ...
def iter_multi_items(
    mapping: Mapping[K, V | Iterable[V]] | Iterable[tuple[K, V]],
) -> Iterator[tuple[K, V]]: ...

class ImmutableList(ImmutableListMixin[V]): ...

class TypeConversionDict(dict[K, V]):
    @overload
    def get(self, key: K, default: None = ..., type: None = ...) -> V | None: ...
    @overload
    def get(self, key: K, default: D, type: None = ...) -> D | V: ...
    @overload
    def get(self, key: K, default: D, type: Callable[[V], T]) -> D | T: ...
    @overload
    def get(self, key: K, type: Callable[[V], T]) -> T | None: ...

class ImmutableTypeConversionDict(ImmutableDictMixin[K, V], TypeConversionDict[K, V]):
    def copy(self) -> TypeConversionDict[K, V]: ...
    def __copy__(self) -> ImmutableTypeConversionDict[K, V]: ...

class MultiDict(TypeConversionDict[K, V]):
    def __init__(
        self,
        mapping: Mapping[K, Iterable[V] | V] | Iterable[tuple[K, V]] | None = None,
    ) -> None: ...
    def __getitem__(self, item: K) -> V: ...
    def __setitem__(self, key: K, value: V) -> None: ...
    def add(self, key: K, value: V) -> None: ...
    @overload
    def getlist(self, key: K) -> list[V]: ...
    @overload
    def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ...
    def setlist(self, key: K, new_list: Iterable[V]) -> None: ...
    def setdefault(self, key: K, default: V | None = None) -> V: ...
    def setlistdefault(
        self, key: K, default_list: Iterable[V] | None = None
    ) -> list[V]: ...
    def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ...  # type: ignore
    def lists(self) -> Iterator[tuple[K, list[V]]]: ...
    def values(self) -> Iterator[V]: ...  # type: ignore
    def listvalues(self) -> Iterator[list[V]]: ...
    def copy(self) -> MultiDict[K, V]: ...
    def deepcopy(self, memo: Any = None) -> MultiDict[K, V]: ...
    @overload
    def to_dict(self) -> dict[K, V]: ...
    @overload
    def to_dict(self, flat: Literal[False]) -> dict[K, list[V]]: ...
    def update(  # type: ignore
        self, mapping: Mapping[K, Iterable[V] | V] | Iterable[tuple[K, V]]
    ) -> None: ...
    @overload
    def pop(self, key: K) -> V: ...
    @overload
    def pop(self, key: K, default: V | T = ...) -> V | T: ...
    def popitem(self) -> tuple[K, V]: ...
    def poplist(self, key: K) -> list[V]: ...
    def popitemlist(self) -> tuple[K, list[V]]: ...
    def __copy__(self) -> MultiDict[K, V]: ...
    def __deepcopy__(self, memo: Any) -> MultiDict[K, V]: ...

class _omd_bucket(Generic[K, V]):
    prev: _omd_bucket[K, V] | None
    next: _omd_bucket[K, V] | None
    key: K
    value: V
    def __init__(self, omd: OrderedMultiDict[K, V], key: K, value: V) -> None: ...
    def unlink(self, omd: OrderedMultiDict[K, V]) -> None: ...

class OrderedMultiDict(MultiDict[K, V]):
    _first_bucket: _omd_bucket[K, V] | None
    _last_bucket: _omd_bucket[K, V] | None
    def __init__(self, mapping: Mapping[K, V] | None = None) -> None: ...
    def __eq__(self, other: object) -> bool: ...
    def __getitem__(self, key: K) -> V: ...
    def __setitem__(self, key: K, value: V) -> None: ...
    def __delitem__(self, key: K) -> None: ...
    def keys(self) -> Iterator[K]: ...  # type: ignore
    def __iter__(self) -> Iterator[K]: ...
    def values(self) -> Iterator[V]: ...  # type: ignore
    def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ...  # type: ignore
    def lists(self) -> Iterator[tuple[K, list[V]]]: ...
    def listvalues(self) -> Iterator[list[V]]: ...
    def add(self, key: K, value: V) -> None: ...
    @overload
    def getlist(self, key: K) -> list[V]: ...
    @overload
    def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ...
    def setlist(self, key: K, new_list: Iterable[V]) -> None: ...
    def setlistdefault(
        self, key: K, default_list: Iterable[V] | None = None
    ) -> list[V]: ...
    def update(  # type: ignore
        self, mapping: Mapping[K, V] | Iterable[tuple[K, V]]
    ) -> None: ...
    def poplist(self, key: K) -> list[V]: ...
    @overload
    def pop(self, key: K) -> V: ...
    @overload
    def pop(self, key: K, default: V | T = ...) -> V | T: ...
    def popitem(self) -> tuple[K, V]: ...
    def popitemlist(self) -> tuple[K, list[V]]: ...

class CombinedMultiDict(ImmutableMultiDictMixin[K, V], MultiDict[K, V]):  # type: ignore
    dicts: list[MultiDict[K, V]]
    def __init__(self, dicts: Iterable[MultiDict[K, V]] | None) -> None: ...
    @classmethod
    def fromkeys(cls, keys: Any, value: Any = None) -> NoReturn: ...
    def __getitem__(self, key: K) -> V: ...
    @overload  # type: ignore
    def get(self, key: K) -> V | None: ...
    @overload
    def get(self, key: K, default: V | T = ...) -> V | T: ...
    @overload
    def get(
        self, key: K, default: T | None = None, type: Callable[[V], T] = ...
    ) -> T | None: ...
    @overload
    def getlist(self, key: K) -> list[V]: ...
    @overload
    def getlist(self, key: K, type: Callable[[V], T] = ...) -> list[T]: ...
    def _keys_impl(self) -> set[K]: ...
    def keys(self) -> set[K]: ...  # type: ignore
    def __iter__(self) -> set[K]: ...  # type: ignore
    def items(self, multi: bool = False) -> Iterator[tuple[K, V]]: ...  # type: ignore
    def values(self) -> Iterator[V]: ...  # type: ignore
    def lists(self) -> Iterator[tuple[K, list[V]]]: ...
    def listvalues(self) -> Iterator[list[V]]: ...
    def copy(self) -> MultiDict[K, V]: ...
    @overload
    def to_dict(self) -> dict[K, V]: ...
    @overload
    def to_dict(self, flat: Literal[False]) -> dict[K, list[V]]: ...
    def __contains__(self, key: K) -> bool: ...  # type: ignore
    def has_key(self, key: K) -> bool: ...

class ImmutableDict(ImmutableDictMixin[K, V], dict[K, V]):
    def copy(self) -> dict[K, V]: ...
    def __copy__(self) -> ImmutableDict[K, V]: ...

class ImmutableMultiDict(  # type: ignore
    ImmutableMultiDictMixin[K, V], MultiDict[K, V]
):
    def copy(self) -> MultiDict[K, V]: ...
    def __copy__(self) -> ImmutableMultiDict[K, V]: ...

class ImmutableOrderedMultiDict(  # type: ignore
    ImmutableMultiDictMixin[K, V], OrderedMultiDict[K, V]
):
    def _iter_hashitems(self) -> Iterator[tuple[int, tuple[K, V]]]: ...
    def copy(self) -> OrderedMultiDict[K, V]: ...
    def __copy__(self) -> ImmutableOrderedMultiDict[K, V]: ...

class CallbackDict(UpdateDictMixin[K, V], dict[K, V]):
    def __init__(
        self,
        initial: Mapping[K, V] | Iterable[tuple[K, V]] | None = None,
        on_update: Callable[[_CD], None] | None = None,
    ) -> None: ...

class HeaderSet(set[str]):
    _headers: list[str]
    _set: set[str]
    on_update: Callable[[HeaderSet], None] | None
    def __init__(
        self,
        headers: Iterable[str] | None = None,
        on_update: Callable[[HeaderSet], None] | None = None,
    ) -> None: ...
    def add(self, header: str) -> None: ...
    def remove(self, header: str) -> None: ...
    def update(self, iterable: Iterable[str]) -> None: ...  # type: ignore
    def discard(self, header: str) -> None: ...
    def find(self, header: str) -> int: ...
    def index(self, header: str) -> int: ...
    def clear(self) -> None: ...
    def as_set(self, preserve_casing: bool = False) -> set[str]: ...
    def to_header(self) -> str: ...
    def __getitem__(self, idx: int) -> str: ...
    def __delitem__(self, idx: int) -> None: ...
    def __setitem__(self, idx: int, value: str) -> None: ...
    def __contains__(self, header: str) -> bool: ...  # type: ignore
    def __len__(self) -> int: ...
    def __iter__(self) -> Iterator[str]: ...
