from __future__ import annotations

from collections import defaultdict
from itertools import groupby
from operator import itemgetter
from typing import TYPE_CHECKING

from rich import box
from rich.table import Table
from rich.text import Text

from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import VerticalScroll
from textual.widgets import Static

if TYPE_CHECKING:
    from textual.screen import Screen


class BindingsTable(Static):
    """A widget to display bindings."""

    COMPONENT_CLASSES = {
        "bindings-table--key",
        "bindings-table--description",
        "bindings-table--divider",
        "bindings-table--header",
    }

    DEFAULT_CSS = """
    BindingsTable {
        width: auto;
        height: auto;
    }
    """

    def render_bindings_table(self) -> Table:
        """Render a table with all the key bindings.

        Returns:
            A Rich Table.
        """

        bindings = self.screen.active_bindings.values()

        key_style = self.get_component_rich_style("bindings-table--key")
        divider_transparent = (
            self.get_component_styles("bindings-table--divider").color.a == 0
        )
        table = Table(
            padding=(0, 0),
            show_header=False,
            box=box.SIMPLE if divider_transparent else box.HORIZONTALS,
            border_style=self.get_component_rich_style("bindings-table--divider"),
        )
        table.add_column("", justify="right")

        header_style = self.get_component_rich_style("bindings-table--header")
        previous_namespace: object = None
        for namespace, _bindings in groupby(bindings, key=itemgetter(0)):
            table_bindings = list(_bindings)
            if not table_bindings:
                continue

            if namespace.BINDING_GROUP_TITLE:
                title = Text(namespace.BINDING_GROUP_TITLE, end="")
                title.stylize(header_style)
                table.add_row("", title)

            action_to_bindings: defaultdict[str, list[tuple[Binding, bool, str]]]
            action_to_bindings = defaultdict(list)
            for _, binding, enabled, tooltip in table_bindings:
                if not binding.system:
                    action_to_bindings[binding.action].append(
                        (binding, enabled, tooltip)
                    )

            description_style = self.get_component_rich_style(
                "bindings-table--description"
            )

            def render_description(binding: Binding) -> Text:
                """Render description text from a binding."""
                text = Text.from_markup(
                    binding.description, end="", style=description_style
                )
                if binding.tooltip:
                    if binding.description:
                        text.append(" ")
                    text.append(binding.tooltip, "dim")
                return text

            get_key_display = self.app.get_key_display
            for multi_bindings in action_to_bindings.values():
                binding, enabled, tooltip = multi_bindings[0]
                keys_display = " ".join(
                    dict.fromkeys(  # Remove duplicates while preserving order
                        get_key_display(binding) for binding, _, _ in multi_bindings
                    )
                )
                table.add_row(
                    Text(keys_display, style=key_style),
                    render_description(binding),
                )
            if namespace != previous_namespace:
                table.add_section()

            previous_namespace = namespace

        return table

    def render(self) -> Table:
        return self.render_bindings_table()


class KeyPanel(VerticalScroll, can_focus=False):
    """
    Shows bindings for currently focused widget.
    """

    DEFAULT_CSS = """
    KeyPanel {
        split: right;
        width: 33%;
        min-width: 30;
        max-width: 60;
        border-left: vkey $foreground 30%;
        padding: 0 1;
        height: 1fr;
        padding-right: 1;
        align: center top;

        &> BindingsTable > .bindings-table--key {
            color: $text-accent;
            text-style: bold;
            padding: 0 1;
        }

        &> BindingsTable > .bindings-table--description {
            color: $foreground;
        }

        &> BindingsTable > .bindings-table--divider {
            color: transparent;
        }

        &> BindingsTable > .bindings-table--header {        
            color: $text-primary;
            text-style: underline;
        }

        #bindings-table {
            width: auto;
            height: auto;
        }
    }
    """

    DEFAULT_CLASSES = "-textual-system"

    def compose(self) -> ComposeResult:
        yield BindingsTable(shrink=True, expand=False)

    async def on_mount(self) -> None:
        mount_screen = self.screen

        async def bindings_changed(screen: Screen) -> None:
            """Update bindings."""
            if not screen.app.app_focus:
                return
            if self.is_attached and screen is mount_screen:
                await self.recompose()

        def _bindings_changed(screen: Screen) -> None:
            self.call_after_refresh(bindings_changed, screen)

        self.set_class(self.app.ansi_color, "-ansi-scrollbar")
        self.screen.bindings_updated_signal.subscribe(self, _bindings_changed)

    def on_unmount(self) -> None:
        self.screen.bindings_updated_signal.unsubscribe(self)
