from __future__ import annotations

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

import rich.repr
from rich.text import Text

from textual import events
from textual.app import ComposeResult
from textual.binding import Binding
from textual.containers import HorizontalGroup, ScrollableContainer
from textual.reactive import reactive
from textual.widget import Widget
from textual.widgets import Label

if TYPE_CHECKING:
    from textual.screen import Screen


@rich.repr.auto
class KeyGroup(HorizontalGroup):
    DEFAULT_CSS = """
    KeyGroup {
        width: auto;        
    }
    """


@rich.repr.auto
class FooterKey(Widget):
    ALLOW_SELECT = False
    COMPONENT_CLASSES = {
        "footer-key--key",
        "footer-key--description",
    }

    DEFAULT_CSS = """
    FooterKey {
        width: auto;
        height: 1;
        text-wrap: nowrap;
        background: $footer-item-background;
        .footer-key--key {
            color: $footer-key-foreground;
            background: $footer-key-background;
            text-style: bold;
            padding: 0 1;
        }

        .footer-key--description {
            padding: 0 1 0 0;
            color: $footer-description-foreground;
            background: $footer-description-background;
        }

        &:hover {
            color: $footer-key-foreground;
            background: $block-hover-background;
        }

        &.-disabled {
            text-style: dim;
        }

        &.-compact {
            .footer-key--key {
                padding: 0;
            }
            .footer-key--description {
                padding: 0 0 0 1;
            }
        }
    }
    """

    compact = reactive(True)
    """Display compact style."""

    def __init__(
        self,
        key: str,
        key_display: str,
        description: str,
        action: str,
        disabled: bool = False,
        tooltip: str = "",
        classes="",
    ) -> None:
        self.key = key
        self.key_display = key_display
        self.description = description
        self.action = action
        self._disabled = disabled
        if disabled:
            classes += " -disabled"
        super().__init__(classes=classes)
        self.shrink = False
        if tooltip:
            self.tooltip = tooltip

    def render(self) -> Text:
        key_style = self.get_component_rich_style("footer-key--key")
        description_style = self.get_component_rich_style("footer-key--description")
        key_display = self.key_display
        key_padding = self.get_component_styles("footer-key--key").padding
        description_padding = self.get_component_styles(
            "footer-key--description"
        ).padding

        description = self.description
        if description:
            label_text = Text.assemble(
                (
                    " " * key_padding.left + key_display + " " * key_padding.right,
                    key_style,
                ),
                (
                    " " * description_padding.left
                    + description
                    + " " * description_padding.right,
                    description_style,
                ),
            )
        else:
            label_text = Text.assemble((key_display, key_style))

        label_text.stylize_before(self.rich_style)
        return label_text

    def on_mouse_down(self) -> None:
        if self._disabled:
            self.app.bell()
        else:
            self.app.simulate_key(self.key)

    def _watch_compact(self, compact: bool) -> None:
        self.set_class(compact, "-compact")


class FooterLabel(Label):
    """Text displayed in the footer (used by binding groups)."""


@rich.repr.auto
class Footer(ScrollableContainer, can_focus=False, can_focus_children=False):
    ALLOW_SELECT = False
    DEFAULT_CSS = """
    Footer {
        layout: horizontal;        
        color: $footer-foreground;
        background: $footer-background;
        dock: bottom;
        height: 1;
        scrollbar-size: 0 0;
        &.-compact {
            FooterLabel {
                margin: 0;
            }
            FooterKey {
                margin-right: 1;
            }
            FooterKey.-grouped {
                margin: 0 1;            
            }
            FooterKey.-command-palette  {
                padding-right: 0;
            }
        }
        FooterKey.-command-palette  {
            dock: right;
            padding-right: 1;
            border-left: vkey $foreground 20%;
        }
        HorizontalGroup.binding-group {            
            width: auto;
            height: 1;
            layout: horizontal;
        }
        KeyGroup.-compact {            
            FooterKey.-grouped {
                margin: 0;
            }
            margin: 0 1 0 0;
            padding-left: 1;
        }

        FooterKey.-grouped {
            margin: 0 1;            
        }
        FooterLabel {
            margin: 0 1 0 0;            
            color: $footer-description-foreground;
            background: $footer-description-background;
        }

        &:ansi {
            background: ansi_default;
            .footer-key--key {
                background: ansi_default;
                color: ansi_magenta;
            }
            .footer-key--description {
                background: ansi_default;
                color: ansi_default;
            }
            FooterKey:hover {
                text-style: underline;
                background: ansi_default;
                color: ansi_default;
                .footer-key--key {
                    background: ansi_default;
                }
            }
            FooterKey.-command-palette {
                background: ansi_default;
                border-left: vkey ansi_black;
            }
        }
        
    }
    """

    compact = reactive(False, toggle_class="-compact")
    """Display in compact style."""
    _bindings_ready = reactive(False, repaint=False)
    """True if the bindings are ready to be displayed."""
    show_command_palette = reactive(True)
    """Show the key to invoke the command palette."""
    combine_groups = reactive(True)
    """Combine bindings in the same group?"""

    def __init__(
        self,
        *children: Widget,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        show_command_palette: bool = True,
        compact: bool = False,
    ) -> None:
        """A footer to show key bindings.

        Args:
            *children: Child widgets.
            name: The name of the widget.
            id: The ID of the widget in the DOM.
            classes: The CSS classes for the widget.
            disabled: Whether the widget is disabled or not.
            show_command_palette: Show key binding to invoke the command palette, on the right of the footer.
            compact: Display a compact style (less whitespace) footer.
        """
        super().__init__(
            *children,
            name=name,
            id=id,
            classes=classes,
            disabled=disabled,
        )
        self.set_reactive(Footer.show_command_palette, show_command_palette)
        self.compact = compact

    def compose(self) -> ComposeResult:
        if not self._bindings_ready:
            return
        active_bindings = self.screen.active_bindings
        bindings = [
            (binding, enabled, tooltip)
            for (_, binding, enabled, tooltip) in active_bindings.values()
            if binding.show
        ]
        action_to_bindings: defaultdict[str, list[tuple[Binding, bool, str]]]
        action_to_bindings = defaultdict(list)
        for binding, enabled, tooltip in bindings:
            action_to_bindings[binding.action].append((binding, enabled, tooltip))

        self.styles.grid_size_columns = len(action_to_bindings)

        for group, multi_bindings_iterable in groupby(
            action_to_bindings.values(),
            lambda multi_bindings_: multi_bindings_[0][0].group,
        ):
            multi_bindings = list(multi_bindings_iterable)
            if group is not None and len(multi_bindings) > 1:
                with KeyGroup(classes="-compact" if group.compact else ""):
                    for multi_bindings in multi_bindings:
                        binding, enabled, tooltip = multi_bindings[0]
                        yield FooterKey(
                            binding.key,
                            self.app.get_key_display(binding),
                            "",
                            binding.action,
                            disabled=not enabled,
                            tooltip=tooltip or binding.description,
                            classes="-grouped",
                        ).data_bind(compact=Footer.compact)
                yield FooterLabel(group.description)
            else:
                for multi_bindings in multi_bindings:
                    binding, enabled, tooltip = multi_bindings[0]
                    yield FooterKey(
                        binding.key,
                        self.app.get_key_display(binding),
                        binding.description,
                        binding.action,
                        disabled=not enabled,
                        tooltip=tooltip,
                    ).data_bind(compact=Footer.compact)
        if self.show_command_palette and self.app.ENABLE_COMMAND_PALETTE:
            try:
                _node, binding, enabled, tooltip = active_bindings[
                    self.app.COMMAND_PALETTE_BINDING
                ]
            except KeyError:
                pass
            else:
                yield FooterKey(
                    binding.key,
                    self.app.get_key_display(binding),
                    binding.description,
                    binding.action,
                    classes="-command-palette",
                    disabled=not enabled,
                    tooltip=binding.tooltip or binding.description,
                )

    def bindings_changed(self, screen: Screen) -> None:
        self._bindings_ready = True
        if not screen.app.app_focus:
            return
        if self.is_attached and screen is self.screen:
            self.call_after_refresh(self.recompose)

    def _on_mouse_scroll_down(self, event: events.MouseScrollDown) -> None:
        if self.allow_horizontal_scroll:
            self.release_anchor()
            if self._scroll_right_for_pointer(animate=True):
                event.stop()
                event.prevent_default()

    def _on_mouse_scroll_up(self, event: events.MouseScrollUp) -> None:
        if self.allow_horizontal_scroll:
            self.release_anchor()
            if self._scroll_left_for_pointer(animate=True):
                event.stop()
                event.prevent_default()

    def on_mount(self) -> None:
        self.screen.bindings_updated_signal.subscribe(self, self.bindings_changed)

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