from enum import IntEnum, StrEnum
from typing import Self, TypeVar

# define a TypeVar constrained to IntEnum/StrEnum
IntStrEnum = TypeVar("IntStrEnum", bound=IntEnum | StrEnum)


class IntEnumUseName(IntEnum):
    """
    A marker indicating that the attribute *name* should be used in lieu of *value*, when locating an
    instance of this class by looking for a *str* having its name. The usual procedure would be to look
    for an *int* having its value.

    Examples of such usage may be found in the *env_pomes* module. Note that this is the only situation
    justifying the use of this marker.
    """


class IntEnumDesc(IntEnum):
    """
     Adds the attribute *desc* to subclasses of *IntEnum*.
    """

    def __new__(cls,
                value: int,
                desc: str) -> Self:
        """
        Allow for the creation of subclasses of *IntEnum* having the extra *desc* attribute.

        The following are examples of creating the class *Status* with 3 instances:
        ::
            statically:
            class Status(IntEnumDesc):
                ACTIVE = (1, "Item is currently active")
                INACTIVE = (0, "Item is currently inactive")
                DELETED = (-1, "Item has been removed")

            dynamically:
            Status = IntEnumDesc("Status", {
                                 "ACTIVE": (1, "Item is currently active"),
                                 "INACTIVE": (0, "Item is currently inactive"),
                                 "DELETED": (-1, "Item has been removed")
                                 })

        :param value: value to be assigned to the instance
        :param desc: description to be assigned to the instance
        """
        result: int = int.__new__(cls,
                                  value)
        result._value_ = value
        result.desc: str = desc

        return result

    @classmethod
    def from_desc(cls,
                  value: str) -> IntEnumDesc | None:
        """
        Retrieve the instance having the attribute *desc* equal to *value*.

        :param value: the value to search for
        :return: the instance whose *desc* attributes matches *value*, or *None* if not found
        """
        # initialize the return variable
        result: IntEnumDesc | None = None

        # traverse the class
        for member in cls:
            if member.desc == value:
                result = member
                break

        return result


class IntEnumCode(IntEnum):
    """
     Adds the attributes *desc* and *code* to subclasses of *IntEnum*.
    """

    def __new__(cls,
                value: int,
                desc: str,
                code: str) -> Self:
        """
        Allow for the creation of subclasses of *IntEnum* having the extra *desc* and *code* attributes.

        The following are examples of creating the class *Status* with 3 instances:
        ::
            statically:
            class Status(IntEnumDesc):
                ACTIVE = (1, "Item is currently active", "C1")
                INACTIVE = (0, "Item is currently inactive", "C2")
                DELETED = (-1, "Item has been removed", "C3")

            dynamically:
            Status = IntEnumDesc("Status", {
                                 "ACTIVE": (1, "Item is currently active", "C1"),
                                 "INACTIVE": (0, "Item is currently inactive", "C2"),
                                 "DELETED": (-1, "Item has been removed", "C3")
                                 })

        :param value: value to be assigned to the instance
        :param desc: description to be assigned to the instance
        :param code: extra information to be assigned to the instance
        """
        result: int = int.__new__(cls,
                                  value)
        result._value_ = value
        result.desc: str = desc
        result.code: str = code

        return result

    @classmethod
    def from_code(cls,
                  value: str) -> IntEnumCode | None:
        """
        Retrieve the instance having the attribute *code* equal to *value*.

        :param value: the value to search for
        :return: the instance whose *code* attributes matches *value*, or *None* if not found
        """
        # initialize the return variable
        result: IntEnumCode | None = None

        # traverse the class
        for member in cls:
            if member.code == value:
                result = member
                break

        return result


class StrEnumDesc(StrEnum):
    """
     Adds the attribute *desc* to subclasses of *StrEnum*.
    """

    def __new__(cls,
                value: str,
                desc: str) -> Self:
        """
        Allow for the creation of subclasses of *StrEnum* having the extra *desc* attribute.

        The following are examples of creating the class *Status* with 3 instances:
        ::
            statically:
            class Status(StrEnumDesc):
                ACTIVE = ("active", "Item is currently active")
                INACTIVE = ("inactive", "Item is currently inactive")
                DELETED = ("deleted", "Item has been removed")

            dynamically:
            Status = StrEnumDesc("Status", {
                                 "ACTIVE": ("active", "Item is currently active"),
                                 "INACTIVE": ("inactive", "Item is currently inactive"),
                                 "DELETED": ("deleted", "Item has been removed")
                                 })

        :param value: value to be assigned to the instance
        :param desc: description to be assigned to the instance
        """
        result: str = str.__new__(cls,
                                  object=value)
        result._value_ = value
        result.desc: str = desc

        return result

    @classmethod
    def from_desc(cls,
                  value: str) -> StrEnumDesc | None:
        """
        Retrieve the instance having the attribute *desc* equal to *value*.

        :param value: the value to search for
        :return: the instance whose *desc* attributes matches *value*, or *None* if not found
        """
        # initialize the return variable
        result: StrEnumDesc | None = None

        # traverse the class
        for member in cls:
            if member.desc == value:
                result = member
                break

        return result


class StrEnumCode(StrEnumDesc):
    """
    Adds the attributes *desc* and *code* to subclasses of *StrEnum*.
    """

    def __new__(cls,
                value: str,
                desc: str,
                code: str) -> Self:
        """
        Allow for the creation of subclasses of *StrEnum* having the extra *desc* and *code* attributes.

        The following are examples of creating the class *Status* with 3 instances:
        ::
            statically:
            class Status(StrEnumDesc):
                ACTIVE = ("active", "Item is currently active", "C1")
                INACTIVE = ("inactive", "Item is currently inactive", "C2")
                DELETED = ("deleted", "Item has been removed", "C3")

            dynamically:
            Status = StrEnumDesc("Status", {
                                 "ACTIVE": ("active", "Item is currently active", "C1"),
                                 "INACTIVE": ("inactive", "Item is currently inactive", "C2"),
                                 "DELETED": ("deleted", "Item has been removed", "C3")
                                 })

        :param value: value to be assigned to the instance
        :param desc: description to be assigned to the instance
        :param code: extra information to be assigned to the instance
        """
        result: str = str.__new__(cls,
                                  object=value)
        result._value_ = value
        result.desc: str = desc
        result.code: str = code

        return result

    @classmethod
    def from_code(cls,
                  value: str) -> StrEnumCode | None:
        """
        Retrieve the instance having the attribute *code* equal to *value*.

        :param value: the value to search for
        :return: the instance whose *code* attributes matches *value*, or *None* if not found
        """
        # initialize the return variable
        result: StrEnumCode | None = None

        # traverse the class
        for member in cls:
            if member.code == value:
                result = member
                break

        return result
