from typing import Any, Optional, Tuple, Union

from rich import box
from rich.color import Color
from rich.console import Group, RenderableType
from rich.style import Style
from rich.text import Text

from rich_toolkit._rich_components import Panel
from rich_toolkit.container import Container
from rich_toolkit.element import CursorOffset, Element
from rich_toolkit.form import Form
from rich_toolkit.input import Input
from rich_toolkit.menu import Menu
from rich_toolkit.progress import Progress

from .base import BaseStyle


class BorderedStyle(BaseStyle):
    box = box.SQUARE

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

    def _box(
        self,
        content: RenderableType,
        title: Union[str, Text, None],
        is_active: bool,
        border_color: Color,
        after: Tuple[str, ...] = (),
    ) -> RenderableType:
        return Group(
            Panel(
                content,
                title=title,
                title_align="left",
                highlight=is_active,
                width=50,
                box=self.box,
                border_style=Style(color=border_color),
            ),
            *after,
        )

    def render_container(
        self,
        element: Container,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        content = super().render_container(element, is_active, done, parent)

        if isinstance(element, Form):
            return self._box(content, element.title, is_active, Color.parse("white"))

        return content

    def render_input(
        self,
        element: Input,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **metadata: Any,
    ) -> RenderableType:
        validation_message: Tuple[str, ...] = ()

        if isinstance(parent, Form):
            return super().render_input(element, is_active, done, parent, **metadata)

        if message := self.render_validation_message(element):
            validation_message = (message,)

        title = self.render_input_label(
            element,
            is_active=is_active,
            parent=parent,
        )

        # Determine border color based on validation state
        if element.valid is False:
            try:
                border_color = self.console.get_style("error").color or Color.parse(
                    "red"
                )
            except Exception:
                # Fallback if error style is not defined
                border_color = Color.parse("red")
        else:
            border_color = Color.parse("white")

        return self._box(
            self.render_input_value(element, is_active=is_active, parent=parent),
            title,
            is_active,
            border_color,
            after=validation_message,
        )

    def render_menu(
        self,
        element: Menu,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
        **metadata: Any,
    ) -> RenderableType:
        validation_message: Tuple[str, ...] = ()

        menu = Text(justify="left")

        selected_prefix = Text(element.current_selection_char + " ")
        not_selected_prefix = Text(element.selection_char + " ")

        separator = Text("\t" if element.inline else "\n")

        content: list[RenderableType] = []

        if done:
            content.append(
                Text(
                    element.options[element.selected]["name"],
                    style=self.console.get_style("result"),
                )
            )

        else:
            # Get visible range for scrolling
            all_options = element.options
            start, end = element.visible_options_range
            visible_options = all_options[start:end]

            # Check if scrolling is needed (to reserve consistent space for indicators)
            needs_scrolling = element._needs_scrolling()

            # Always reserve space for "more above" indicator when scrolling is enabled
            if needs_scrolling:
                if element.has_more_above:
                    menu.append(Text(element.MORE_ABOVE_INDICATOR + "\n", style="dim"))
                else:
                    menu.append(Text(" " * len(element.MORE_ABOVE_INDICATOR) + "\n"))

            for idx, option in enumerate(visible_options):
                actual_idx = start + idx
                if actual_idx == element.selected:
                    prefix = selected_prefix
                    style = self.console.get_style("selected")
                else:
                    prefix = not_selected_prefix
                    style = self.console.get_style("text")

                is_last = idx == len(visible_options) - 1

                menu.append(
                    Text.assemble(
                        prefix,
                        option["name"],
                        separator if not is_last else "",
                        style=style,
                    )
                )

            # Always reserve space for "more below" indicator when scrolling is enabled
            if needs_scrolling:
                if element.has_more_below:
                    menu.append(Text("\n" + element.MORE_BELOW_INDICATOR, style="dim"))
                else:
                    menu.append(Text("\n" + " " * len(element.MORE_BELOW_INDICATOR)))

            if not element.options:
                menu = Text("No results found", style=self.console.get_style("text"))

            filter = (
                [
                    Text.assemble(
                        (element.filter_prompt, self.console.get_style("text")),
                        (element.text, self.console.get_style("text")),
                        "\n",
                    )
                ]
                if element.allow_filtering
                else []
            )

            content.extend(filter)
            content.append(menu)

            if message := self.render_validation_message(element):
                validation_message = (message,)

        result = Group(*content)

        return self._box(
            result,
            self.render_input_label(element),
            is_active,
            Color.parse("white"),
            after=validation_message,
        )

    def render_progress(
        self,
        element: Progress,
        is_active: bool = False,
        done: bool = False,
        parent: Optional[Element] = None,
    ) -> RenderableType:
        content: str | Group | Text = element.current_message
        title: Union[str, Text, None] = None

        title = element.title

        if element.logs and element._inline_logs:
            lines_to_show = (
                element.logs[-element.lines_to_show :]
                if element.lines_to_show > 0
                else element.logs
            )

            content = Group(
                *[
                    self.render_element(
                        line,
                        index=index,
                        max_lines=element.lines_to_show,
                        total_lines=len(element.logs),
                    )
                    for index, line in enumerate(lines_to_show)
                ]
            )

        border_color = Color.parse("white")

        if not done:
            colors = self._get_animation_colors(
                steps=10, animation_status="started", breathe=True
            )

            border_color = colors[self.animation_counter % 10]

        return self._box(content, title, is_active, border_color=border_color)

    def get_cursor_offset_for_element(
        self, element: Element, parent: Optional[Element] = None
    ) -> CursorOffset:
        top_offset = element.cursor_offset.top
        left_offset = element.cursor_offset.left + 2

        if isinstance(element, Input) and element.inline:
            # we don't support inline inputs yet in border style
            top_offset += 1
            inline_left_offset = (len(element.label) - 1) if element.label else 0
            left_offset = element.cursor_offset.left - inline_left_offset

        if isinstance(parent, Form):
            top_offset += 1

        return CursorOffset(top=top_offset, left=left_offset)
