from __future__ import annotations

from functools import partial
from typing import TYPE_CHECKING, cast

import rich.repr
from rich.cells import cell_len
from rich.console import ConsoleRenderable, RenderableType
from typing_extensions import Literal, Self

from textual import events

if TYPE_CHECKING:
    from textual.app import RenderResult

from rich.style import Style

from textual.binding import Binding
from textual.content import Content, ContentText
from textual.css._error_tools import friendly_list
from textual.geometry import Size
from textual.message import Message
from textual.reactive import reactive
from textual.widget import Widget

ButtonVariant = Literal["default", "primary", "success", "warning", "error"]
"""The names of the valid button variants.

These are the variants that can be used with a [`Button`][textual.widgets.Button].
"""

_VALID_BUTTON_VARIANTS = {"default", "primary", "success", "warning", "error"}


class InvalidButtonVariant(Exception):
    """Exception raised if an invalid button variant is used."""


class Button(Widget, can_focus=True):
    """A simple clickable button.

    Clicking the button will send a [Button.Pressed][textual.widgets.Button.Pressed] message,
    unless the `action` parameter is provided.

    """

    ALLOW_SELECT = False

    DEFAULT_CSS = """
    Button {
        width: auto;
        min-width: 16;
        height:auto;
        line-pad: 1;
        text-align: center;
        content-align: center middle;
        pointer: pointer;

        &.-style-flat {
            text-style: bold;
            color: auto 90%;
            background: $surface;
            border: block $surface;
            &:hover {
                background: $primary;
                border: block $primary;
            }
            &:focus {
                text-style: $button-focus-text-style;
            }
            &.-active {
                background: $surface;
                border: block $surface;
                tint: $background 30%;
            }
            &:disabled {
                color: auto 50%;
                pointer: not-allowed;
            }

            &.-primary {
                background: $primary-muted;
                border: block $primary-muted;
                color: $text-primary;
                &:hover {
                    color: $text;
                    background: $primary;
                    border: block $primary;
                }
            }
            &.-success {
                background: $success-muted;
                border: block $success-muted;
                color: $text-success;
                &:hover {
                    color: $text;
                    background: $success;
                    border: block $success;
                }
            }
            &.-warning {
                background: $warning-muted;
                border: block $warning-muted;
                color: $text-warning;
                &:hover {
                    color: $text;
                    background: $warning;
                    border: block $warning;
                }
            }
            &.-error {
                background: $error-muted;
                border: block $error-muted;
                color: $text-error;
                &:hover {
                    color: $text;
                    background: $error;
                    border: block $error;
                }
            }
        }
        &.-style-default {
            text-style: bold;
            color: $button-foreground;
            background: $surface;
            border: none;
            border-top: tall $surface-lighten-1;
            border-bottom: tall $surface-darken-1;
            

            &.-textual-compact {
                border: none !important;
            }

            &:disabled {
                text-opacity: 0.6;
                pointer: not-allowed;
            }

            &:focus {
                text-style: $button-focus-text-style;
                background-tint: $foreground 5%;
            }
            &:hover {
                border-top: tall $surface;
                background: $surface-darken-1;
            }
    
            &.-active {
                background: $surface;
                border-bottom: tall $surface-lighten-1;
                border-top: tall $surface-darken-1;
                tint: $background 30%;
            }

            &.-primary {
                color: $button-color-foreground;
                background: $primary;
                border-top: tall $primary-lighten-3;
                border-bottom: tall $primary-darken-3;

                &:hover {
                    background: $primary-darken-2;
                    border-top: tall $primary;
                }

                &.-active {
                    background: $primary;
                    border-bottom: tall $primary-lighten-3;
                    border-top: tall $primary-darken-3;
                }
            }

            &.-success {
                color: $button-color-foreground;
                background: $success;
                border-top: tall $success-lighten-2;
                border-bottom: tall $success-darken-3;

                &:hover {
                    background: $success-darken-2;
                    border-top: tall $success;
                }

                &.-active {
                    background: $success;
                    border-bottom: tall $success-lighten-2;
                    border-top: tall $success-darken-2;
                }
            }

            &.-warning{
                color: $button-color-foreground;
                background: $warning;
                border-top: tall $warning-lighten-2;
                border-bottom: tall $warning-darken-3;

                &:hover {
                    background: $warning-darken-2;
                    border-top: tall $warning;
                }

                &.-active {
                    background: $warning;
                    border-bottom: tall $warning-lighten-2;
                    border-top: tall $warning-darken-2;
                }
            }

            &.-error {
                color: $button-color-foreground;
                background: $error;
                border-top: tall $error-lighten-2;
                border-bottom: tall $error-darken-3;

                &:hover {
                    background: $error-darken-1;
                    border-top: tall $error;
                }

                &.-active {
                    background: $error;
                    border-bottom: tall $error-lighten-2;
                    border-top: tall $error-darken-2;
                }
            }
        }
    }
    """

    BINDINGS = [Binding("enter", "press", "Press button", show=False)]

    label: reactive[ContentText] = reactive[ContentText](Content.empty())
    """The text label that appears within the button."""

    variant = reactive("default", init=False)
    """The variant name for the button."""

    compact = reactive(False, toggle_class="-textual-compact")
    """Make the button compact (without borders)."""

    flat = reactive(False)
    """Enable alternative flat button style."""

    class Pressed(Message):
        """Event sent when a `Button` is pressed and there is no Button action.

        Can be handled using `on_button_pressed` in a subclass of
        [`Button`][textual.widgets.Button] or in a parent widget in the DOM.
        """

        def __init__(self, button: Button) -> None:
            self.button: Button = button
            """The button that was pressed."""
            super().__init__()

        @property
        def control(self) -> Button:
            """An alias for [Pressed.button][textual.widgets.Button.Pressed.button].

            This will be the same value as [Pressed.button][textual.widgets.Button.Pressed.button].
            """
            return self.button

    def __init__(
        self,
        label: ContentText | None = None,
        variant: ButtonVariant = "default",
        *,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        tooltip: RenderableType | None = None,
        action: str | None = None,
        compact: bool = False,
        flat: bool = False,
    ):
        """Create a Button widget.

        Args:
            label: The text that appears within the button.
            variant: The variant of the button.
            name: The name of the button.
            id: The ID of the button in the DOM.
            classes: The CSS classes of the button.
            disabled: Whether the button is disabled or not.
            tooltip: Optional tooltip.
            action: Optional action to run when clicked.
            compact: Enable compact button style.
            flat: Enable alternative flat look buttons.
        """
        super().__init__(name=name, id=id, classes=classes, disabled=disabled)

        if label is None:
            label = self.css_identifier_styled

        self.variant = variant
        self.flat = flat
        self.compact = compact
        self.set_reactive(Button.label, Content.from_text(label))

        self.action = action
        self.active_effect_duration = 0.2
        """Amount of time in seconds the button 'press' animation lasts."""

        if tooltip is not None:
            self.tooltip = tooltip

    def get_content_width(self, container: Size, viewport: Size) -> int:
        assert isinstance(self.label, Content)
        try:
            return max([cell_len(line) for line in self.label.plain.splitlines()]) + 2
        except ValueError:
            # Empty string label
            return 2

    def __rich_repr__(self) -> rich.repr.Result:
        yield from super().__rich_repr__()
        yield "variant", self.variant, "default"

    def validate_variant(self, variant: str) -> str:
        if variant not in _VALID_BUTTON_VARIANTS:
            raise InvalidButtonVariant(
                f"Valid button variants are {friendly_list(_VALID_BUTTON_VARIANTS)}"
            )
        return variant

    def watch_variant(self, old_variant: str, variant: str):
        self.remove_class(f"-{old_variant}")
        self.add_class(f"-{variant}")

    def watch_flat(self, flat: bool) -> None:
        self.set_class(flat, "-style-flat")
        self.set_class(not flat, "-style-default")

    def validate_label(self, label: ContentText) -> Content:
        """Parse markup for self.label"""
        return Content.from_text(label)

    def render(self) -> RenderResult:
        assert isinstance(self.label, Content)
        return self.label

    def post_render(
        self, renderable: RenderableType, base_style: Style
    ) -> ConsoleRenderable:
        return cast(ConsoleRenderable, renderable)

    async def _on_click(self, event: events.Click) -> None:
        event.stop()
        if not self.has_class("-active"):
            self.press()

    def press(self) -> Self:
        """Animate the button and send the [Pressed][textual.widgets.Button.Pressed] message.

        Can be used to simulate the button being pressed by a user.

        Returns:
            The button instance.
        """
        if self.disabled or not self.display:
            return self
        # Manage the "active" effect:
        self._start_active_affect()
        # ...and let other components know that we've just been clicked:
        if self.action is None:
            self.post_message(Button.Pressed(self))
        else:
            self.call_later(
                self.app.run_action, self.action, default_namespace=self._parent
            )
        return self

    def _start_active_affect(self) -> None:
        """Start a small animation to show the button was clicked."""
        if self.active_effect_duration > 0:
            self.add_class("-active")
            self.set_timer(
                self.active_effect_duration, partial(self.remove_class, "-active")
            )

    def action_press(self) -> None:
        """Activate a press of the button."""
        if not self.has_class("-active"):
            self.press()

    @classmethod
    def success(
        cls,
        label: ContentText | None = None,
        *,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        flat: bool = False,
    ) -> Button:
        """Utility constructor for creating a success Button variant.

        Args:
            label: The text that appears within the button.
            name: The name of the button.
            id: The ID of the button in the DOM.
            classes: The CSS classes of the button.
            disabled: Whether the button is disabled or not.
            flat: Enable alternative flat look buttons.

        Returns:
            A [`Button`][textual.widgets.Button] widget of the 'success'
                [variant][textual.widgets.button.ButtonVariant].
        """
        return Button(
            label=label,
            variant="success",
            name=name,
            id=id,
            classes=classes,
            disabled=disabled,
            flat=flat,
        )

    @classmethod
    def warning(
        cls,
        label: ContentText | None = None,
        *,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        flat: bool = False,
    ) -> Button:
        """Utility constructor for creating a warning Button variant.

        Args:
            label: The text that appears within the button.
            name: The name of the button.
            id: The ID of the button in the DOM.
            classes: The CSS classes of the button.
            disabled: Whether the button is disabled or not.
            flat: Enable alternative flat look buttons.

        Returns:
            A [`Button`][textual.widgets.Button] widget of the 'warning'
                [variant][textual.widgets.button.ButtonVariant].
        """
        return Button(
            label=label,
            variant="warning",
            name=name,
            id=id,
            classes=classes,
            disabled=disabled,
            flat=flat,
        )

    @classmethod
    def error(
        cls,
        label: ContentText | None = None,
        *,
        name: str | None = None,
        id: str | None = None,
        classes: str | None = None,
        disabled: bool = False,
        flat: bool = False,
    ) -> Button:
        """Utility constructor for creating an error Button variant.

        Args:
            label: The text that appears within the button.
            name: The name of the button.
            id: The ID of the button in the DOM.
            classes: The CSS classes of the button.
            disabled: Whether the button is disabled or not.
            flat: Enable alternative flat look buttons.

        Returns:
            A [`Button`][textual.widgets.Button] widget of the 'error'
                [variant][textual.widgets.button.ButtonVariant].
        """
        return Button(
            label=label,
            variant="error",
            name=name,
            id=id,
            classes=classes,
            disabled=disabled,
            flat=flat,
        )
