###############################################################################
#
# (C) Copyright 2023 EVERYSK TECHNOLOGIES
#
# This is an unpublished work containing confidential and proprietary
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
#
###############################################################################
from typing import Any, Self


## Do not use this class, use the constant Undefined that is already defined in the python builtins. ##
class UndefinedType:
    """
    The UndefinedType class is used to represent an undefined value in the Everysk framework.
    This class is designed to be immutable and to always represent the same undefined value.
    We use it as default values for function parameters and class attributes to indicate that
    the value is not set and to differentiate it from None or other possible values.
    """

    default_error_message = 'This object is immutable.'
    default_parse_string = '__UNDEFINED_VALUE__'
    default_repr_string = '<Undefined value>'
    block = False

    def __init__(self) -> None:
        if self.block:
            raise NotImplementedError('Do not use this class, use the constant Undefined.')

    def __bool__(self) -> bool:
        """Undefined object is always False"""
        return False

    def __copy__(self) -> Self:
        """To keep consistence, this object will always be the same."""
        return self

    def __delattr__(self, name: str) -> None:
        """We could not delete attributes from this object."""
        raise AttributeError(self.default_error_message)

    def __deepcopy__(self, memo: dict | None = None) -> Self:
        """To keep consistence, this object will always be the same."""
        return self

    def __eq__(self, value: object) -> bool:
        """For an object created from the UndefinedType class to be equal to another, the classes must be equal."""
        return isinstance(value, type(self))

    def __getattr__(self, name: str) -> Any:
        """Undefined object don't have attributes."""
        raise AttributeError(self.default_error_message)

    def __hash__(self) -> int:
        """Must return an int that is used as hash for this object."""
        return id(self)

    def __repr__(self) -> str:
        """Fixed to be the same every time."""
        return self.default_repr_string

    def __setattr__(self, name: str, value: Any) -> None:
        """We can't set any attribute to this object."""
        if self.block:
            raise AttributeError(self.default_error_message)

    def __str__(self) -> str:
        """Fixed to be the same every time."""
        return self.default_repr_string
