__author__ = 'ispaneli'
__email__ = 'ispanelki@gmail.com'

__version__ = '0.0.1'

__doc__ = """
TChoice
=======

Lightweight implementation of Django-like `TextChoices` and `IntegerChoices` 
for Python applications.

Problem Statement:
When working with FastAPI or other frameworks, it's often useful to have 
enumerations with labels for better readability and usability in models, 
forms, and APIs. While Django provides `TextChoices` and `IntegerChoices` 
for this purpose, they are tightly coupled to the Django framework. 
`tchoice` offers a standalone solution, allowing you to use similar 
functionality without requiring Django.

----------------------------------------------------------------------------

Author: @ispaneli
E-mail: ispanelki@gmail.com
GitHub repository: <https://github.com/ispaneli/tchoice>
"""

# Django sources: https://github.com/django/django

import enum
from types import DynamicClassAttribute


class Promise:
    """
    Base class for the proxy class created in the closure of the lazy function.
    It's used to recognize promises in code.
    """

    pass


class ChoicesMeta(enum.EnumMeta):
    """A metaclass for creating a enum choices."""

    def __new__(metacls, classname, bases, classdict, **kwds):
        labels = []
        for key in classdict._member_names:
            value = classdict[key]
            if isinstance(value, (list, tuple)) and len(value) > 1 and isinstance(value[-1], (Promise, str)):
                *value, label = value
                value = tuple(value)
            else:
                label = key.replace("_", " ").title()
            labels.append(label)
            # Use dict.__setitem__() to suppress defenses against double
            # assignment in enum's classdict.
            dict.__setitem__(classdict, key, value)
        cls = super().__new__(metacls, classname, bases, classdict, **kwds)
        for member, label in zip(cls.__members__.values(), labels):
            member._label_ = label
        return enum.unique(cls)

    def __contains__(cls, member):
        if not isinstance(member, enum.Enum):
            # Allow non-enums to match against member values.
            return any(x.value == member for x in cls)
        return super().__contains__(member)

    @property
    def names(cls):
        empty = ["__empty__"] if hasattr(cls, "__empty__") else []
        return empty + [member.name for member in cls]

    @property
    def choices(cls):
        empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
        return empty + [(member.value, member.label) for member in cls]

    @property
    def labels(cls):
        return [label for _, label in cls.choices]

    @property
    def values(cls):
        return [value for value, _ in cls.choices]

    def __str__(cls):
        """
        Custom method: Для удобной печати Enum-подобных структур.
        """
        return str({member.value for member in cls})


class Choices(enum.Enum, metaclass=ChoicesMeta):
    """Class for creating enumerated choices."""

    @DynamicClassAttribute
    def label(self):
        return self._label_

    @property
    def do_not_call_in_templates(self):
        return True

    def __str__(self):
        """
        Use value when cast to str, so that Choices set as model instance
        attributes are rendered as expected in templates and similar contexts.
        """
        return str(self.value)

    # A similar format was proposed for Python 3.10.
    def __repr__(self):
        return f"{self.__class__.__qualname__}.{self._name_}"


class IntegerChoices(int, Choices):
    """Class for creating enumerated integer choices."""

    pass


class TextChoices(str, Choices):
    """Class for creating enumerated string choices."""

    def _generate_next_value_(name, start, count, last_values):
        return name
