from __future__ import annotations

from typing import TYPE_CHECKING

from textual._animator import Animation, EasingFunction
from textual._types import AnimationLevel, CallbackType
from textual.css.scalar import Scalar, ScalarOffset

if TYPE_CHECKING:
    from textual.css.styles import StylesBase
    from textual.widget import Widget


class ScalarAnimation(Animation):
    def __init__(
        self,
        widget: Widget,
        styles: StylesBase,
        start_time: float,
        attribute: str,
        value: ScalarOffset | Scalar,
        duration: float | None,
        speed: float | None,
        easing: EasingFunction,
        on_complete: CallbackType | None = None,
        level: AnimationLevel = "full",
    ):
        assert (
            speed is not None or duration is not None
        ), "One of speed or duration required"
        self.widget = widget
        self.styles = styles
        self.start_time = start_time
        self.attribute = attribute
        self.final_value = value
        self.easing = easing
        self.on_complete = on_complete
        self.level = level

        size = widget.outer_size
        viewport = widget.app.size

        self.start = getattr(styles, attribute).resolve(size, viewport)
        self.destination = value.resolve(size, viewport)

        if speed is not None:
            distance = self.start.get_distance_to(self.destination)
            self.duration = distance / speed
        else:
            assert duration is not None, "Duration expected to be non-None"
            self.duration = duration

    def __call__(
        self, time: float, app_animation_level: AnimationLevel = "full"
    ) -> bool:
        factor = min(1.0, (time - self.start_time) / self.duration)
        eased_factor = self.easing(factor)

        if (
            eased_factor >= 1
            or app_animation_level == "none"
            or app_animation_level == "basic"
            and self.level == "full"
        ):
            setattr(self.styles, self.attribute, self.final_value)
            return True

        if hasattr(self.start, "blend"):
            value = self.start.blend(self.destination, eased_factor)
        else:
            value = self.start + (self.destination - self.start) * eased_factor
        current = self.styles.get_rule(self.attribute)
        if current != value:
            setattr(self.styles, self.attribute, value)

        return False

    async def stop(self, complete: bool = True) -> None:
        """Stop the animation.

        Args:
            complete: Flag to say if the animation should be taken to completion.

        Note:
            [`on_complete`][Animation.on_complete] will be called regardless
            of the value provided for `complete`.
        """
        if complete:
            setattr(self.styles, self.attribute, self.final_value)
        await self.invoke_callback()

    def __eq__(self, other: object) -> bool:
        if isinstance(other, ScalarAnimation):
            return (
                self.final_value == other.final_value
                and self.duration == other.duration
            )
        return False
