from collections.abc import Sequence

from ..compat import _toga_lt_5
from .choices import Choices
from .immutablelist import ImmutableList


class validated_property:
    def __init__(
        self,
        *constants,
        string=False,
        integer=False,
        number=False,
        color=False,
        initial=None,
    ):
        """Define a simple validated property attribute.

        :param constants: Explicitly allowable values.
        :param string: Are strings allowed as values?
        :param integer: Are integers allowed as values?
        :param number: Are numbers allowed as values?
        :param color: Are colors allowed as values?
        :param initial: The initial value for the property. If the property has not been
            explicitly set, this is what is returned when it's accessed.
        """
        self.choices = Choices(
            *constants, string=string, integer=integer, number=number, color=color
        )
        self.initial = None if initial is None else self.validate(initial)

    def __set_name__(self, style_class, name):
        self.name = name
        style_class._BASE_PROPERTIES[style_class].add(name)
        style_class._BASE_ALL_PROPERTIES[style_class].add(name)

    def __get__(self, style, style_class=None):
        if style is None:
            return self

        return getattr(style, f"_{self.name}", self.initial)

    def __set__(self, style, value):
        if value is self:
            # This happens during autogenerated dataclass __init__ when no value is
            # supplied.
            return

        if value is None:
            raise ValueError(
                "Python `None` cannot be used as a style property value; "
                f"to reset a property, use del `style.{self.name}`."
            )

        value = self.validate(value)
        current = style[self.name]  # Fetches initial if not set

        setattr(style, f"_{self.name}", value)
        if value != current:
            ######################################################################
            # 08-2025: Backwards compatibility for Toga < 0.5.0
            ######################################################################
            try:
                style.apply(self.name)
            except TypeError:
                if _toga_lt_5():  # pragma: no cover
                    style.apply(self.name, value)
                else:
                    raise

            ######################################################################
            # End backwards compatibility
            ######################################################################

    def __delete__(self, style):
        try:
            current = getattr(style, f"_{self.name}")
            delattr(style, f"_{self.name}")
        except AttributeError:
            pass
        else:
            if current != self.initial:
                ######################################################################
                # 08-2025: Backwards compatibility for Toga < 0.5.0
                ######################################################################
                try:
                    style.apply(self.name)
                except TypeError:
                    if _toga_lt_5():  # pragma: no cover
                        style.apply(self.name, self.initial)
                    else:
                        raise

                ######################################################################
                # End backwards compatibility
                ######################################################################

    @property
    def _name_if_set(self):
        return f" {self.name}" if hasattr(self, "name") else ""

    def validate(self, value):
        try:
            return self.choices.validate(value)
        except ValueError as error:
            raise ValueError(
                f"Invalid value {value!r} for property{self._name_if_set}; "
                f"Valid values are: {self.choices}"
            ) from error

    def is_set_on(self, style):
        return hasattr(style, f"_{self.name}")


class list_property(validated_property):
    def validate(self, value):
        if isinstance(value, str):
            value = [value]
        elif not isinstance(value, Sequence):
            raise TypeError(
                f"Value for list property{self._name_if_set} must be a sequence."
            )

        if not value:
            name = getattr(self, "name", "prop_name")
            raise ValueError(
                "List properties cannot be set to an empty sequence; "
                f"to reset a property, use del `style.{name}`."
            )

        # This could be a comprehension, but then the error couldn't specify which value
        # is at fault.
        result = []
        for item in value:
            try:
                item = self.choices.validate(item)
            except ValueError as error:
                raise ValueError(
                    f"Invalid item value {item!r} for list "
                    f"property{self._name_if_set}; Valid values are: {self.choices}"
                ) from error
            result.append(item)

        return ImmutableList(result)
