# Source code for atom.dict

    # --------------------------------------------------------------------------------------
    # Copyright (c) 2013-2025, Nucleic Development Team.
    #
    # Distributed under the terms of the Modified BSD License.
    #
    # The full license is in the file LICENSE, distributed with this software.
    # --------------------------------------------------------------------------------------
    from collections import defaultdict

    from .catom import DefaultValue, Member, Validate
    from .instance import Instance
    from .typing_utils import extract_types, is_optional

    class Dict(Member):
        """A value of type `dict`."""

        __slots__ = ()

        def __init__(self, key=None, value=None, default=None):
            """Initialize a Dict.

            Parameters
            ----------
            key : Member, type, tuple of types, or None, optional
                A member to use for validating the types of keys allowed in
                the dict. This can also be a type or a tuple of types, which
                will be wrapped with an Instance member. If this is not
                given, no key validation is performed.

            value : Member, type, tuple of types, or None, optional
                A member to use for validating the types of values allowed
                in the dict. This can also be a type or a tuple of types,
                which will be wrapped with an Instance member. If this is
                not given, no value validation is performed.

            default : dict, optional
                The default dict of items. A new copy of this dict will be
                created for each atom instance.

            """
            self.set_default_value_mode(DefaultValue.Dict, default)
            if key is not None and not isinstance(key, Member):
                opt, types = is_optional(extract_types(key))
                key = Instance(types, optional=opt)
            if value is not None and not isinstance(value, Member):
                opt, types = is_optional(extract_types(value))
                value = Instance(types, optional=opt)
            self.set_validate_mode(Validate.Dict, (key, value))

        def set_name(self, name):
            """Assign the name to this member.

            This method is called by the Atom metaclass when a class is
            created. This makes sure the name of the internal members are
            also updated.

            """
            super(Dict, self).set_name(name)
            key, value = self.validate_mode[1]
            if key is not None:
                key.set_name(name + "|key")
            if value is not None:
                value.set_name(name + "|value")

        def set_index(self, index):
            """Assign the index to this member.

            This method is called by the Atom metaclass when a class is
            created. This makes sure the index of the internal members are
            also updated.

            """
            super(Dict, self).set_index(index)
            key, value = self.validate_mode[1]
            if key is not None:
                key.set_index(index)
            if value is not None:
                value.set_index(index)

        def clone(self):
            """Create a clone of the member.

            This will clone the internal dict key and value members if they exist.

            """
            clone = super(Dict, self).clone()
            key, value = self.validate_mode[1]
            if key is not None or value is not None:
                key_clone = key.clone() if key is not None else None
                value_clone = value.clone() if value is not None else None
                mode, _ = self.validate_mode
                clone.set_validate_mode(mode, (key_clone, value_clone))
            return clone

    class _DefaultWrapper:
        __slots__ = ("wrapped",)

        def __init__(self, wrapped):
            self.wrapped = wrapped

        def __call__(self, atom):
            return self.wrapped()

        def __repr__(self):
            return repr(self.wrapped)

    class DefaultDict(Member):
        """A value of type `dict` implementing __missing__"""

        __slots__ = ()

        def __init__(self, key=None, value=None, default=None, *, missing=None):
            """Initialize a DefaultDict.

            Parameters
            ----------
            key : Member, type, tuple of types, or None, optional
                A member to use for validating the types of keys allowed in
                the dict. This can also be a type or a tuple of types, which
                will be wrapped with an Instance member. If this is not
                given, no key validation is performed.

            value : Member, type, tuple of types, or None, optional
                A member to use for validating the types of values allowed
                in the dict. This can also be a type or a tuple of types,
                which will be wrapped with an Instance member. If this is
                not given, no value validation is performed.

            default : dict or None, optional
                The default dict of items. A new copy of this dict will be
                created for each atom instance.

            missing : Callable[[], Any] or None, optional
                Factory to build a default value for a missing key in the dictionary.

            """
            self.set_default_value_mode(DefaultValue.DefaultDict, default)
            if key is not None and not isinstance(key, Member):
                opt, types = is_optional(extract_types(key))
                key = Instance(types, optional=opt)
            if value is not None and not isinstance(value, Member):
                opt, types = is_optional(extract_types(value))
                # Assume a default value can be created to avoid the need to specify a
                # missing factory in simple case even for custom types.
                value = Instance(types, optional=opt, args=())

            if missing is not None:
                if not callable(missing):
                    raise ValueError(
                        f"The missing argument expect a callable, got {missing}"
                    )
                try:
                    missing()
                except Exception as e:
                    raise ValueError(
                        "The missing argument expect a callable taking no argument. "
                        "Trying to call it with not argument failed with the chained "
                        "exception."
                    ) from e
                missing = _DefaultWrapper(missing)

            if isinstance(default, defaultdict):
                if missing is not None:
                    raise ValueError(
                        "Both a missing factory and a default value which is a default "
                        "dictionary were specified. When using a default dict as default "
                        "value missing should be omitted."
                    )
                missing = _DefaultWrapper(default.default_factory)

            if (
                missing is None
                and value is not None
                and value.default_value_mode[0]
                not in (DefaultValue.NoOp, DefaultValue.NonOptional)
            ):
                missing = value.do_default_value

            if missing is None:
                raise ValueError(
                    "No missing value factory was specified and none could be "
                    "deduced from the value member."
                )

            self.set_validate_mode(Validate.DefaultDict, (key, value, missing))

        def set_name(self, name):
            """Assign the name to this member.

            This method is called by the Atom metaclass when a class is
            created. This makes sure the name of the internal members are
            also updated.

            """
            super().set_name(name)
            key, value, _ = self.validate_mode[1]
            if key is not None:
                key.set_name(name + "|key")
            if value is not None:
                value.set_name(name + "|value")

        def set_index(self, index):
            """Assign the index to this member.

            This method is called by the Atom metaclass when a class is
            created. This makes sure the index of the internal members are
            also updated.

            """
            super().set_index(index)
            key, value, _ = self.validate_mode[1]
            if key is not None:
                key.set_index(index)
            if value is not None:
                value.set_index(index)

        def clone(self):
            """Create a clone of the member.

            This will clone the internal dict key and value members if they exist.

            """
            clone = super().clone()
            mode, (key, value, missing) = self.validate_mode
            key_clone = key.clone() if key is not None else None
            value_clone = value.clone() if value is not None else None
            clone.set_validate_mode(mode, (key_clone, value_clone, missing))
            return clone