from _typeshed import SupportsGetItem, SupportsKeysAndGetItem
from _typeshed.wsgi import WSGIEnvironment
from collections.abc import Collection, Iterable, Iterator, MutableMapping
from typing import Literal, Protocol, TypeVar, overload, type_check_only
from typing_extensions import Self

from webob.compat import cgi_FieldStorage, cgi_FieldStorage as _FieldStorageWithFile

__all__ = ["MultiDict", "NestedMultiDict", "NoVars", "GetDict"]

_T = TypeVar("_T")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_KT_co = TypeVar("_KT_co", covariant=True)
_VT_co = TypeVar("_VT_co", covariant=True)

@type_check_only
class _SupportsItemsWithIterableResult(Protocol[_KT_co, _VT_co]):
    def items(self) -> Iterable[tuple[_KT_co, _VT_co]]: ...

class MultiDict(MutableMapping[_KT, _VT]):
    @overload
    def __init__(self) -> None: ...
    @overload
    def __init__(self: MultiDict[str, _VT], **kwargs: _VT) -> None: ...  # pyright: ignore[reportInvalidTypeVarUse]  #11780
    @overload
    def __init__(self, m: _SupportsItemsWithIterableResult[_KT, _VT], /) -> None: ...
    @overload
    def __init__(
        self: MultiDict[str, _VT],  # pyright: ignore[reportInvalidTypeVarUse]  #11780
        m: _SupportsItemsWithIterableResult[str, _VT],
        /,
        **kwargs: _VT,
    ) -> None: ...
    @overload
    def __init__(self, m: Iterable[tuple[_KT, _VT]], /) -> None: ...
    @overload
    def __init__(
        self: MultiDict[str, _VT],  # pyright: ignore[reportInvalidTypeVarUse]  #11780
        m: Iterable[tuple[str, _VT]],
        /,
        **kwargs: _VT,
    ) -> None: ...

    @classmethod
    def view_list(cls, lst: list[tuple[_KT, _VT]]) -> Self: ...
    @classmethod
    def from_fieldstorage(cls, fs: cgi_FieldStorage) -> MultiDict[str, str | _FieldStorageWithFile]: ...
    def __getitem__(self, key: _KT) -> _VT: ...
    def __setitem__(self, key: _KT, value: _VT) -> None: ...
    def add(self, key: _KT, value: _VT) -> None: ...

    @overload
    def get(self, key: _KT, default: None = None) -> _VT | None: ...
    @overload
    def get(self, key: _KT, default: _VT) -> _VT: ...
    @overload
    def get(self, key: _KT, default: _T) -> _VT | _T: ...

    def getall(self, key: _KT) -> list[_VT]: ...
    def getone(self, key: _KT) -> _VT: ...
    def mixed(self) -> dict[_KT, _VT | list[_VT]]: ...
    def dict_of_lists(self) -> dict[_KT, list[_VT]]: ...
    def __delitem__(self, key: _KT) -> None: ...
    def __contains__(self, key: object) -> bool: ...
    has_key = __contains__
    def clear(self) -> None: ...
    def copy(self) -> Self: ...

    @overload
    def setdefault(self, key: _KT, default: None = None) -> _VT | None: ...
    @overload
    def setdefault(self, key: _KT, default: _VT) -> _VT: ...

    @overload
    def pop(self, key: _KT) -> _VT: ...
    @overload
    def pop(self, key: _KT, default: _T) -> _VT | _T: ...

    def popitem(self) -> tuple[_KT, _VT]: ...

    @overload  # type: ignore[override]
    def update(self: SupportsGetItem[str, _VT], **kwargs: _VT) -> None: ...
    @overload
    def update(self, m: Collection[tuple[_KT, _VT]], /) -> None: ...
    @overload
    def update(self: SupportsGetItem[str, _VT], m: Collection[tuple[str, _VT]], /, **kwargs: _VT) -> None: ...

    @overload
    def extend(self, other: _SupportsItemsWithIterableResult[_KT, _VT]) -> None: ...
    @overload
    def extend(self: MultiDict[str, _VT], other: _SupportsItemsWithIterableResult[str, _VT], **kwargs: _VT) -> None: ...
    @overload
    def extend(self, other: Iterable[tuple[_KT, _VT]]) -> None: ...
    @overload
    def extend(self: MultiDict[str, _VT], other: Iterable[tuple[str, _VT]], **kwargs: _VT) -> None: ...
    @overload
    def extend(self, other: SupportsKeysAndGetItem[_KT, _VT]) -> None: ...
    @overload
    def extend(self: MultiDict[str, _VT], other: SupportsKeysAndGetItem[str, _VT], **kwargs: _VT) -> None: ...
    @overload
    def extend(self: MultiDict[str, _VT], other: None = None, **kwargs: _VT) -> None: ...

    def __len__(self) -> int: ...
    def keys(self) -> Iterator[_KT]: ...  # type: ignore[override]
    __iter__ = keys
    def items(self) -> Iterator[tuple[_KT, _VT]]: ...  # type: ignore[override]
    def values(self) -> Iterator[_VT]: ...  # type: ignore[override]

class GetDict(MultiDict[str, str]):
    env: WSGIEnvironment

    @overload
    def __init__(self, data: _SupportsItemsWithIterableResult[str, str], env: WSGIEnvironment) -> None: ...
    @overload
    def __init__(self, data: Iterable[tuple[str, str]], env: WSGIEnvironment) -> None: ...

    def on_change(self) -> None: ...
    def __setitem__(self, key: str, value: str) -> None: ...
    def add(self, key: str, value: str) -> None: ...
    def __delitem__(self, key: str) -> None: ...
    def clear(self) -> None: ...
    def setdefault(self, key: str, default: str) -> str: ...  # type: ignore[override]

    @overload
    def pop(self, key: str) -> str: ...
    @overload
    def pop(self, key: str, default: _T) -> str | _T: ...

    def popitem(self) -> tuple[str, str]: ...

    @overload  # type: ignore[override]
    def update(self, **kwargs: str) -> None: ...
    @overload
    def update(self, m: Collection[tuple[str, str]], /, **kwargs: str) -> None: ...

    @overload
    def extend(self, other: _SupportsItemsWithIterableResult[str, str], **kwargs: str) -> None: ...
    @overload
    def extend(self, other: Iterable[tuple[str, str]], **kwargs: str) -> None: ...
    @overload
    def extend(self, other: SupportsKeysAndGetItem[str, str], **kwargs: str) -> None: ...
    @overload
    def extend(self, other: None = None, **kwargs: str) -> None: ...

    def copy(self) -> MultiDict[str, str]: ...  # type: ignore[override]

class NestedMultiDict(MultiDict[_KT, _VT]):
    # FIXME: It would be more accurate to use a Protocol here, which has a
    #        covariant _VT, instead of MultiDict
    dicts: tuple[MultiDict[_KT, _VT], ...]
    def __init__(self, *dicts: MultiDict[_KT, _VT]) -> None: ...
    def __getitem__(self, key: _KT) -> _VT: ...
    # NOTE: These methods all return exceptions, so this will give us
    #       somewhat sane type checker errors, we would prefer to use
    #       something like @type_error here, if it existed.
    #       This is only really necessary, because the inheritance hierachy
    #       is a mess.
    __setitem__: None  # type: ignore[assignment]
    add: None  # type: ignore[assignment]
    __delitem__: None  # type: ignore[assignment]
    clear: None  # type: ignore[assignment]
    setdefault: None  # type: ignore[assignment]
    pop: None  # type: ignore[assignment]
    popitem: None  # type: ignore[assignment]
    update: None  # type: ignore[assignment]
    def getall(self, key: _KT) -> list[_VT]: ...
    def copy(self) -> MultiDict[_KT, _VT]: ...  # type: ignore[override]
    def __contains__(self, key: object) -> bool: ...
    has_key = __contains__
    def __len__(self) -> int: ...
    def items(self) -> Iterator[tuple[_KT, _VT]]: ...  # type: ignore[override]
    def values(self) -> Iterator[_VT]: ...  # type: ignore[override]
    def keys(self) -> Iterator[_KT]: ...  # type: ignore[override]
    __iter__ = keys

class NoVars:
    reason: str
    def __init__(self, reason: str | None = None) -> None: ...

    @overload
    def get(self, key: str, default: None = None) -> None: ...
    @overload
    def get(self, key: str, default: _T) -> _T: ...

    def getall(self, key: str) -> list[str]: ...
    def mixed(self) -> dict[str, str | list[str]]: ...
    def dict_of_lists(self) -> dict[str, list[str]]: ...
    def __contains__(self, key: object) -> Literal[False]: ...
    has_key = __contains__
    def copy(self) -> Self: ...
    def __len__(self) -> Literal[0]: ...
    def __iter__(self) -> Iterator[str]: ...
    def keys(self) -> Iterator[str]: ...
    def values(self) -> Iterator[str]: ...
    def items(self) -> Iterator[tuple[str, str]]: ...
    def __bool__(self) -> Literal[False]: ...
