from abc import ABC, abstractmethod
from typing import Any, Generator, Iterable, List, Type, TypeVar
from typing_extensions import Literal

from rich.color import Color
from rich.console import (
    Console,
    ConsoleOptions,
    ConsoleRenderable,
    RenderableType,
    RenderResult,
)
from rich.segment import Segment
from rich.text import Text


from rich_toolkit.utils.colors import lighten


ConsoleRenderableClass = TypeVar(
    "ConsoleRenderableClass", bound=Type[ConsoleRenderable]
)


ANIMATION_STATUS = Literal["started", "stopped", "error", "no_animation"]


class BaseStyle(ABC):
    result_color: Color
    decoration_size: int

    def __init__(self) -> None:
        self.padding = 2
        self.cursor_offset = 0

        self._animation_counter = 0
        self.decoration_size = 2

    def empty_line(self) -> Text:
        return Text(" ")

    def with_decoration(
        self,
        *renderables: RenderableType,
        animation_status: ANIMATION_STATUS = "no_animation",
        **metadata: Any,
    ) -> ConsoleRenderable:
        class WithDecoration:
            @staticmethod
            def __rich_console__(
                console: Console, options: ConsoleOptions
            ) -> RenderResult:
                for content in renderables:
                    # our styles are potentially adding some paddings on the left
                    # and right sides, so we need to adjust the max_width to
                    # make sure that rich takes that into account
                    options = options.update(
                        max_width=console.width - self.decoration_size
                    )

                    lines = console.render_lines(content, options, pad=False)

                    for line in Segment.split_lines(
                        self.decorate(
                            lines=lines,
                            console=console,
                            animation_status=animation_status,
                            **metadata,
                        )
                    ):
                        yield from line
                        yield Segment.line()

        return WithDecoration()

    def decorate_class(
        self, klass: ConsoleRenderableClass, **metadata: Any
    ) -> ConsoleRenderableClass:
        style = self

        class Decorated(klass):  # type: ignore[valid-type,misc]
            def __rich_console__(
                self, console: Console, options: ConsoleOptions
            ) -> RenderResult:
                lines = Segment.split_lines(super().__rich_console__(console, options))  # type: ignore

                yield from style.decorate(lines=lines, console=console, **metadata)

        return Decorated  # type: ignore

    @abstractmethod
    def decorate(
        self,
        console: Console,
        lines: Iterable[List[Segment]],
        animation_status: ANIMATION_STATUS = "no_animation",
        **kwargs: Any,
    ) -> Generator[Segment, None, None]:
        raise NotImplementedError()

    def _get_animation_colors(
        self,
        console: Console,
        steps: int = 5,
        animation_status: ANIMATION_STATUS = "started",
        **metadata: Any,
    ) -> List[Color]:
        animated = animation_status == "started"

        if animation_status == "error":
            base_color = console.get_style("error").color

            if base_color is None:
                base_color = Color.parse("red")

        else:
            base_color = console.get_style("progress").bgcolor

        if not base_color:
            base_color = Color.parse("white")

        if animated and base_color.triplet is not None:
            return [lighten(base_color, 0.1 * i) for i in range(0, steps)]

        return [base_color] * steps
