"""Loading indicator widget."""

from __future__ import annotations

from time import monotonic

from rich.segment import Segment
from rich.style import Style
from textual.color import Gradient
from textual.events import Resize
from textual.reactive import var
from textual.strip import Strip
from textual.widget import Widget


class Loading(Widget):
    """Animated loading indicator with message."""

    COMPONENT_CLASSES = {"loading--text-width"}

    DEFAULT_CSS = """
    Loading {
        min-height: 1;
        content-align: center middle;
        color: $accent;
        text-style: not reverse;
    }
    """

    message = var("")

    def __init__(self, message: str) -> None:
        """Initialize the loading indicator.

        Args:
            message: Message to display while loading
        """
        super().__init__()
        self._message = message

        self._start_time = 0.0
        self.text_width: int | None = None
        self.text_strips: list[Strip] = []
        self.text_fetched = False
        self.text_indexes: list[int] = []
        self.text_offset = 0

    def position_indicator(self, segments: list[tuple[str, Style]]) -> Strip:
        """Center the indicator segments."""
        content_len = sum(len(x[0]) for x in segments)
        to_split = self.size.width - content_len

        prefix_size = to_split // 2
        prefix = " " * prefix_size

        remainder = to_split % 2

        suffix_size = prefix_size + remainder
        suffix = " " * suffix_size

        segments.insert(0, (suffix,))
        segments.append((prefix,))

        return Strip(Segment(*x) for x in segments)

    def build_text(self) -> list[Strip]:
        """Build text strips for the message."""
        msg = self.message
        msg_len = len(msg)
        raw_lines = msg.split("\n")
        words = [word for line in raw_lines for word in line.split(" ")]

        line_count = max(msg_len // self.text_width, 1)

        if msg_len > self.text_width and msg_len % self.text_width:
            line_count += 1

        def get_line() -> list[str]:
            counter = 0
            line_words: list[str] = []

            while counter < self.text_width:
                try:
                    word = words.pop(0)
                except IndexError:
                    break

                word_len = len(word) + 1

                if counter + word_len > self.text_width:
                    words.insert(0, word)
                    break

                line_words.append(word)
                counter += word_len

            return line_words

        lines = [" ".join(get_line()) for _ in range(line_count)]

        strips = []

        for line in lines:
            whitespace = self.size.width - len(line)
            remainder = whitespace % 2
            prefix_size = whitespace // 2
            suffix_size = prefix_size + remainder

            prefix = " " * prefix_size
            suffix = " " * suffix_size

            strips.append(Strip(Segment(x) for x in [prefix, line, suffix]))

        return strips

    def get_text_strips(self) -> None:
        """Calculate and cache text strips."""
        self.text_fetched = True

        text_style = self.get_component_styles("loading--text-width")

        try:
            text_width = int(str(text_style.width))
        except ValueError:
            text_width = None

        self.text_width = text_width or self.size.width

        self.text_strips = self.build_text()

        half = self.size.height // 2
        self.text_offset = max(half - len(self.text_strips) - 1, 0)
        self.text_indexes = [x + self.text_offset for x in range(len(self.text_strips))]

    def get_indicator(self) -> list[tuple[str, Style]]:
        """Get the animated indicator dots."""
        elapsed = monotonic() - self._start_time
        speed = 0.8
        dot = "\u25cf"
        _, _, background, color = self.colors

        gradient = Gradient(
            (0.0, background.blend(color, 0.1)),
            (0.7, color),
            (1.0, color.lighten(0.1)),
        )

        blends = [(elapsed * speed - dot_number / 8) % 1 for dot_number in range(5)]

        dots = [
            (
                f"{dot} ",
                Style.from_color(gradient.get_color((1 - blend) ** 2).rich_color),
            )
            for blend in blends
        ]

        return dots

    def on_mount(self) -> None:
        """Set up refresh timer."""
        self._start_time = monotonic()
        self.auto_refresh = 0.1

        def set_message() -> None:
            self.message = self._message

        self.call_later(set_message)

    def on_resize(self, event: Resize) -> None:
        """Recalculate text on resize."""
        self.get_text_strips()

    def render_line(self, y: int) -> Strip:
        """Render a single line."""
        dots = self.get_indicator()
        indicator = self.position_indicator(dots)

        if y in self.text_indexes:
            return self.text_strips[y - self.text_offset]
        elif y == self.size.height // 2:
            return indicator
        else:
            return Strip.blank(self.size.width)

    def watch_message(self) -> None:
        """Update text strips when message changes."""
        if self.size.width:
            self.get_text_strips()
