# util/_immutabledict_cy.py
# Copyright (C) 2010-2026 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: disable-error-code="misc, arg-type, type-arg, untyped-decorator"
from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Hashable
from typing import Literal
from typing import Mapping
from typing import NoReturn
from typing import Optional
from typing import TypeVar

from .typing import Self

# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
    # NOTE: the cython compiler needs this "import cython" in the file, it
    # can't be only "from sqlalchemy.util import cython" with the fallback
    # in that module
    import cython
except ModuleNotFoundError:
    from sqlalchemy.util import cython


def _is_compiled() -> bool:
    """Utility function to indicate if this module is compiled or not."""
    return cython.compiled  # type: ignore[no-any-return,unused-ignore]


# END GENERATED CYTHON IMPORT

if cython.compiled:
    from cython.cimports.cpython.dict import PyDict_Update
else:
    PyDict_Update = dict.update


def _immutable_fn(obj: object) -> NoReturn:
    raise TypeError(f"{obj.__class__.__name__} object is immutable")


class ReadOnlyContainer:
    __slots__ = ()

    def _readonly(self) -> NoReturn:
        raise TypeError(
            f"{self.__class__.__name__} object is immutable and/or readonly"
        )

    def __delitem__(self, key: Any) -> NoReturn:
        self._readonly()

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        self._readonly()

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        self._readonly()


_KT = TypeVar("_KT", bound=Hashable)
_VT = TypeVar("_VT", bound=Any)


@cython.cclass
class ImmutableDictBase(Dict[_KT, _VT]):
    # NOTE: this method is required in 3.9 and speeds up the use case
    # ImmutableDictBase[str,int](a_dict) significantly
    @classmethod
    def __class_getitem__(  # type: ignore[override]
        cls, key: Any
    ) -> type[Self]:
        return cls

    def __delitem__(self, key: Any) -> NoReturn:
        _immutable_fn(self)

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def clear(self) -> NoReturn:
        _immutable_fn(self)

    def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def popitem(self) -> NoReturn:
        _immutable_fn(self)

    def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def update(self, *arg: Any, **kw: Any) -> NoReturn:
        _immutable_fn(self)


# NOTE: can't extend from ImmutableDictBase[_KT, _VT] due to a compiler
# crash in doing so. Extending from ImmutableDictBase is ok, but requires
# a type checking section and other workaround for the crash
@cython.cclass
class immutabledict(Dict[_KT, _VT]):
    """An immutable version of a dict."""

    # ImmutableDictBase start
    @classmethod
    def __class_getitem__(  # type: ignore[override]
        cls, key: Any
    ) -> type[Self]:
        return cls

    def __delitem__(self, key: Any) -> NoReturn:
        _immutable_fn(self)

    def __setitem__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def __setattr__(self, key: Any, value: Any) -> NoReturn:
        _immutable_fn(self)

    def clear(self) -> NoReturn:
        _immutable_fn(self)

    def pop(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def popitem(self) -> NoReturn:
        _immutable_fn(self)

    def setdefault(self, key: Any, default: Optional[Any] = None) -> NoReturn:
        _immutable_fn(self)

    def update(self, *arg: Any, **kw: Any) -> NoReturn:
        _immutable_fn(self)

    # ImmutableDictBase end

    def __repr__(self) -> str:
        return f"immutabledict({dict.__repr__(self)})"

    @cython.annotation_typing(False)  # avoid cython crash from generic return
    def union(
        self, *dicts: Optional[Mapping[_KT, _VT]]
    ) -> immutabledict[_KT, _VT]:
        return self._union_other(dicts)  # type: ignore[no-any-return]

    @cython.annotation_typing(False)  # avoid cython crash from generic return
    def merge_with(
        self, *dicts: Optional[Mapping[_KT, _VT]]
    ) -> immutabledict[_KT, _VT]:
        # this is an alias of union
        return self._union_other(dicts)  # type: ignore[no-any-return]

    @cython.cfunc
    @cython.inline
    def _union_other(self, others: tuple) -> immutabledict:
        size = len(others)
        if size == 0:
            return self

        # only_one == immutabledict : we found exactly one immutabledict that
        # has contents; no other dict / immutabledict has any contents
        #
        # only_one is None : we found more than one dict / immutabledict that
        # has contents
        #
        # only_one is False : we've found nothing that is not an empty
        # immutabledict
        only_one: immutabledict | None | Literal[False]

        if self:
            self_is_empty = False
            only_one = self
        else:
            only_one = False
            self_is_empty = True

        for i in range(size):
            d = others[i]
            if not d:
                continue

            if only_one is False and isinstance(d, immutabledict):
                only_one = d
            else:
                only_one = None
                break

        if only_one is False:
            return self
        elif only_one is not None:
            return only_one

        result: immutabledict = immutabledict()
        if not self_is_empty:
            PyDict_Update(result, self)

        for i in range(size):
            d = others[i]
            if not d:
                continue
            if isinstance(d, dict):
                # c version of PyDict_Update supports only dicts
                PyDict_Update(result, d)
            else:
                dict.update(result, d)

        return result

    def copy(self) -> Self:
        return self

    def __reduce__(self) -> Any:
        return immutabledict, (dict(self),)

    # PEP 584
    def __ior__(self, __value: Any, /) -> NoReturn:
        _immutable_fn(self)

    def __or__(  # type: ignore[override]
        self, __value: Mapping[_KT, _VT], /
    ) -> immutabledict[_KT, _VT]:
        return immutabledict(
            dict.__or__(self, __value),  # type: ignore[call-overload,operator,unused-ignore]  # noqa: E501
        )

    def __ror__(  # type: ignore[override]
        self, __value: Mapping[_KT, _VT], /
    ) -> immutabledict[_KT, _VT]:
        return immutabledict(
            dict.__ror__(self, __value),  # type: ignore[call-overload,operator,unused-ignore]  # noqa: E501
        )
