"""Typing stub file for ModuleMapping."""

from abc import ABCMeta
from collections.abc import Mapping
from typing import Generic, TypeVar, overload

from .module import Module

__all__ = [
    "ModuleMapping",
    "ModuleName",
]

_ModuleT = TypeVar("_ModuleT", bound=Module, covariant=True)
_ModuleBaseT = TypeVar("_ModuleBaseT", bound=Module, covariant=True)

class ModuleName(Generic[_ModuleT]):
    """Class for typed Module names. At runtime delegated to str."""

    def __init__(self, value: str, /) -> None: ...
    def __len__(self) -> int: ...
    def __hash__(self) -> int: ...
    def __eq__(self, other: object) -> bool: ...
    def __getitem__(self, index: int) -> str: ...

class ModuleMapping(
    Mapping[ModuleName[_ModuleBaseT] | str, _ModuleBaseT], metaclass=ABCMeta
):
    """Custom dict type to provide better value type hints for Module key types."""

    @overload
    def __getitem__(self, key: ModuleName[_ModuleT], /) -> _ModuleT: ...
    @overload
    def __getitem__(self, key: str, /) -> _ModuleBaseT: ...
    @overload
    def __getitem__(
        self, key: ModuleName[_ModuleT] | str, /
    ) -> _ModuleT | _ModuleBaseT: ...
    @overload  # type: ignore[override]
    def get(self, key: ModuleName[_ModuleT], /) -> _ModuleT | None: ...
    @overload
    def get(self, key: str, /) -> _ModuleBaseT | None: ...
    @overload
    def get(
        self, key: ModuleName[_ModuleT] | str, /
    ) -> _ModuleT | _ModuleBaseT | None: ...

def _test_module_mapping_typing() -> None:
    """Test ModuleMapping overloads work as intended.

    This is tested during the mypy run and needs to be in this file.
    """
    from typing import Any, NewType, assert_type, cast

    from .iot.iotmodule import IotModule
    from .module import Module
    from .smart.smartmodule import SmartModule

    NewCommonModule = NewType("NewCommonModule", Module)
    NewIotModule = NewType("NewIotModule", IotModule)
    NewSmartModule = NewType("NewSmartModule", SmartModule)
    NotModule = NewType("NotModule", list)

    NEW_COMMON_MODULE: ModuleName[NewCommonModule] = ModuleName("NewCommonModule")
    NEW_IOT_MODULE: ModuleName[NewIotModule] = ModuleName("NewIotModule")
    NEW_SMART_MODULE: ModuleName[NewSmartModule] = ModuleName("NewSmartModule")

    # TODO Enable --warn-unused-ignores
    NOT_MODULE: ModuleName[NotModule] = ModuleName("NotModule")  # type: ignore[type-var]  # noqa: F841
    NOT_MODULE_2 = ModuleName[NotModule]("NotModule2")  # type: ignore[type-var]  # noqa: F841

    device_modules: ModuleMapping[Module] = cast(ModuleMapping[Module], {})
    assert_type(device_modules[NEW_COMMON_MODULE], NewCommonModule)
    assert_type(device_modules[NEW_IOT_MODULE], NewIotModule)
    assert_type(device_modules[NEW_SMART_MODULE], NewSmartModule)
    assert_type(device_modules["foobar"], Module)
    assert_type(device_modules[3], Any)  # type: ignore[call-overload]

    assert_type(device_modules.get(NEW_COMMON_MODULE), NewCommonModule | None)
    assert_type(device_modules.get(NEW_IOT_MODULE), NewIotModule | None)
    assert_type(device_modules.get(NEW_SMART_MODULE), NewSmartModule | None)
    assert_type(device_modules.get(NEW_COMMON_MODULE, default=[1, 2]), Any)  # type: ignore[call-overload]

    iot_modules: ModuleMapping[IotModule] = cast(ModuleMapping[IotModule], {})
    smart_modules: ModuleMapping[SmartModule] = cast(ModuleMapping[SmartModule], {})

    assert_type(smart_modules["foobar"], SmartModule)
    assert_type(iot_modules["foobar"], IotModule)

    # Test for covariance
    device_modules_2: ModuleMapping[Module] = iot_modules  # noqa: F841
    device_modules_3: ModuleMapping[Module] = smart_modules  # noqa: F841
    NEW_MODULE: ModuleName[Module] = NEW_SMART_MODULE  # noqa: F841
    NEW_MODULE_2: ModuleName[Module] = NEW_IOT_MODULE  # noqa: F841
