"""Radix accordion components."""

from __future__ import annotations

from typing import Any, List, Literal, Tuple, Union

from reflex.components.component import Component, ComponentNamespace
from reflex.components.core.colors import color
from reflex.components.core.cond import cond
from reflex.components.lucide.icon import Icon
from reflex.components.radix.primitives.base import RadixPrimitiveComponent
from reflex.components.radix.themes.base import LiteralAccentColor, LiteralRadius
from reflex.event import EventHandler
from reflex.style import Style
from reflex.vars import get_uuid_string_var
from reflex.vars.base import LiteralVar, Var

LiteralAccordionType = Literal["single", "multiple"]
LiteralAccordionDir = Literal["ltr", "rtl"]
LiteralAccordionOrientation = Literal["vertical", "horizontal"]
LiteralAccordionVariant = Literal["classic", "soft", "surface", "outline", "ghost"]

DEFAULT_ANIMATION_DURATION = 250
DEFAULT_ANIMATION_EASING = "cubic-bezier(0.87, 0, 0.13, 1)"


def _inherited_variant_selector(
    variant: Var[LiteralAccordionVariant] | LiteralAccordionVariant,
    *selectors: str,
) -> str:
    """Create a multi CSS selector for targeting variant against the given selectors.

    Args:
        variant: The variant to target.
        selectors: The selectors to apply the variant to (default &)

    Returns:
        A CSS selector that is more specific on elements that directly set the variant.
    """
    if not selectors:
        selectors = ("&",)
    # Prefer the `data-variant` that is set directly on the selector,
    # but also inherit the `data-variant` from any parent element.
    return ", ".join(
        [
            f"{selector}[data-variant='{variant}'], *:where([data-variant='{variant}']) {selector}"
            for selector in selectors
        ]
    )


class AccordionComponent(RadixPrimitiveComponent):
    """Base class for all @radix-ui/accordion components."""

    library = "@radix-ui/react-accordion@^1.1.2"

    # The color scheme of the component.
    color_scheme: Var[LiteralAccentColor]

    # The variant of the component.
    variant: Var[LiteralAccordionVariant]

    def add_style(self):
        """Add style to the component."""
        if self.color_scheme is not None:
            self.custom_attrs["data-accent-color"] = self.color_scheme
        if self.variant is not None:
            self.custom_attrs["data-variant"] = self.variant

    def _exclude_props(self) -> list[str]:
        return ["color_scheme", "variant"]


def on_value_change(value: Var[str | List[str]]) -> Tuple[Var[str | List[str]]]:
    """Handle the on_value_change event.

    Args:
        value: The value of the event.

    Returns:
        The value of the event.
    """
    return (value,)


class AccordionRoot(AccordionComponent):
    """An accordion component."""

    tag = "Root"

    alias = "RadixAccordionRoot"

    # The type of accordion (single or multiple).
    type: Var[LiteralAccordionType]

    # The value of the item to expand.
    value: Var[Union[str, List[str]]]

    # The default value of the item to expand.
    default_value: Var[Union[str, List[str]]]

    # Whether or not the accordion is collapsible.
    collapsible: Var[bool]

    # Whether or not the accordion is disabled.
    disabled: Var[bool]

    # The reading direction of the accordion when applicable.
    dir: Var[LiteralAccordionDir]

    # The orientation of the accordion.
    orientation: Var[LiteralAccordionOrientation]

    # The radius of the accordion corners.
    radius: Var[LiteralRadius]

    # The time in milliseconds to animate open and close
    duration: Var[int] = LiteralVar.create(DEFAULT_ANIMATION_DURATION)

    # The easing function to use for the animation.
    easing: Var[str] = LiteralVar.create(DEFAULT_ANIMATION_EASING)

    # Whether to show divider lines between items.
    show_dividers: Var[bool]

    _valid_children: List[str] = ["AccordionItem"]

    # Fired when the opened the accordions changes.
    on_value_change: EventHandler[on_value_change]

    def _exclude_props(self) -> list[str]:
        return [
            *super()._exclude_props(),
            "radius",
            "duration",
            "easing",
            "show_dividers",
        ]

    def add_style(self):
        """Add style to the component.

        Returns:
            The style of the component.
        """
        if self.radius is not None:
            self.custom_attrs["data-radius"] = self.radius
        if self.variant is None:
            # The default variant is classic
            self.custom_attrs["data-variant"] = "classic"

        style = {
            "border_radius": "var(--radius-4)",
            "box_shadow": f"0 2px 10px {color('black', 1, alpha=True)}",
            "&[data-variant='classic']": {
                "background_color": color("accent", 9),
                "box_shadow": f"0 2px 10px {color('black', 4, alpha=True)}",
            },
            "&[data-variant='soft']": {
                "background_color": color("accent", 3),
            },
            "&[data-variant='outline']": {
                "border": f"1px solid {color('accent', 6)}",
            },
            "&[data-variant='surface']": {
                "border": f"1px solid {color('accent', 6)}",
                "background_color": "var(--accent-surface)",
            },
            "&[data-variant='ghost']": {
                "background_color": "none",
                "box_shadow": "None",
            },
            "--animation-duration": f"{self.duration}ms",
            "--animation-easing": self.easing,
        }
        if self.show_dividers is not None:
            style["--divider-px"] = cond(self.show_dividers, "1px", "0")
        else:
            style["&[data-variant='outline']"]["--divider-px"] = "1px"
            style["&[data-variant='surface']"]["--divider-px"] = "1px"
        return Style(style)


class AccordionItem(AccordionComponent):
    """An accordion component."""

    tag = "Item"

    alias = "RadixAccordionItem"

    # A unique identifier for the item.
    value: Var[str]

    # When true, prevents the user from interacting with the item.
    disabled: Var[bool]

    # The header of the accordion item.
    header: Var[Union[Component, str]]
    # The content of the accordion item.
    content: Var[Union[Component, str]] = Var.create(None)

    _valid_children: List[str] = [
        "AccordionHeader",
        "AccordionTrigger",
        "AccordionContent",
    ]

    _valid_parents: List[str] = ["AccordionRoot"]

    @classmethod
    def create(
        cls,
        *children,
        **props,
    ) -> Component:
        """Create an accordion item.

        Args:
            *children: The list of children to use if header and content are not provided.
            **props: Additional properties to apply to the accordion item.

        Returns:
            The accordion item.
        """
        header = props.pop("header", None)
        content = props.pop("content", None)

        # The item requires a value to toggle (use a random unique name if not provided).
        value = props.pop("value", get_uuid_string_var())

        if "AccordionItem" not in (
            cls_name := props.pop("class_name", "AccordionItem")
        ):
            cls_name = f"{cls_name} AccordionItem"

        color_scheme = props.get("color_scheme")
        variant = props.get("variant")

        if (header is not None) and (content is not None):
            children = [
                AccordionHeader.create(
                    AccordionTrigger.create(
                        header,
                        AccordionIcon.create(
                            color_scheme=color_scheme,
                            variant=variant,
                        ),
                        color_scheme=color_scheme,
                        variant=variant,
                    ),
                    color_scheme=color_scheme,
                    variant=variant,
                ),
                AccordionContent.create(
                    content,
                    color_scheme=color_scheme,
                    variant=variant,
                ),
            ]

        return super().create(*children, value=value, **props, class_name=cls_name)

    def add_style(self) -> dict[str, Any] | None:
        """Add style to the component.

        Returns:
            The style of the component.
        """
        divider_style = f"var(--divider-px) solid {color('gray', 6, alpha=True)}"
        return {
            "overflow": "hidden",
            "width": "100%",
            "margin_top": "1px",
            "border_top": divider_style,
            "&:first-child": {
                "margin_top": 0,
                "border_top": 0,
                "border_top_left_radius": "var(--radius-4)",
                "border_top_right_radius": "var(--radius-4)",
            },
            "&:last-child": {
                "border_bottom_left_radius": "var(--radius-4)",
                "border_bottom_right_radius": "var(--radius-4)",
            },
            "&:focus-within": {
                "position": "relative",
                "z_index": 1,
            },
            _inherited_variant_selector("ghost", "&:first-child"): {
                "border_radius": 0,
                "border_top": divider_style,
            },
            _inherited_variant_selector("ghost", "&:last-child"): {
                "border_radius": 0,
                "border_bottom": divider_style,
            },
        }

    def _exclude_props(self) -> list[str]:
        return ["header", "content"]


class AccordionHeader(AccordionComponent):
    """An accordion component."""

    tag = "Header"

    alias = "RadixAccordionHeader"

    @classmethod
    def create(cls, *children, **props) -> Component:
        """Create the Accordion header component.

        Args:
            *children: The children of the component.
            **props: The properties of the component.

        Returns:
            The Accordion header Component.
        """
        if "AccordionHeader" not in (
            cls_name := props.pop("class_name", "AccordionHeader")
        ):
            cls_name = f"{cls_name} AccordionHeader"

        return super().create(*children, class_name=cls_name, **props)

    def add_style(self) -> dict[str, Any] | None:
        """Add style to the component.

        Returns:
            The style of the component.
        """
        return {"display": "flex"}


class AccordionTrigger(AccordionComponent):
    """An accordion component."""

    tag = "Trigger"

    alias = "RadixAccordionTrigger"

    @classmethod
    def create(cls, *children, **props) -> Component:
        """Create the Accordion trigger component.

        Args:
            *children: The children of the component.
            **props: The properties of the component.

        Returns:
            The Accordion trigger Component.
        """
        if "AccordionTrigger" not in (
            cls_name := props.pop("class_name", "AccordionTrigger")
        ):
            cls_name = f"{cls_name} AccordionTrigger"

        return super().create(*children, class_name=cls_name, **props)

    def add_style(self) -> dict[str, Any] | None:
        """Add style to the component.

        Returns:
            The style of the component.
        """
        return {
            "color": color("accent", 11),
            "font_size": "1.1em",
            "line_height": 1,
            "justify_content": "space-between",
            "align_items": "center",
            "flex": 1,
            "display": "flex",
            "padding": "var(--space-3) var(--space-4)",
            "width": "100%",
            "box_shadow": f"0 var(--divider-px) 0 {color('gray', 6, alpha=True)}",
            "&[data-state='open'] > .AccordionChevron": {
                "transform": "rotate(180deg)",
            },
            "&:hover": {
                "background_color": color("accent", 4),
            },
            "& > .AccordionChevron": {
                "transition": "transform var(--animation-duration) var(--animation-easing)",
            },
            _inherited_variant_selector("classic"): {
                "color": "var(--accent-contrast)",
                "&:hover": {
                    "background_color": color("accent", 10),
                },
                "& > .AccordionChevron": {
                    "color": "var(--accent-contrast)",
                },
            },
        }


class AccordionIcon(Icon):
    """An accordion icon component."""

    @classmethod
    def create(cls, *children, **props) -> Component:
        """Create the Accordion icon component.

        Args:
            *children: The children of the component.
            **props: The properties of the component.

        Returns:
            The Accordion icon Component.
        """
        if "AccordionChevron" not in (
            cls_name := props.pop("class_name", "AccordionChevron")
        ):
            cls_name = f"{cls_name} AccordionChevron"

        return super().create(tag="chevron_down", class_name=cls_name, **props)


class AccordionContent(AccordionComponent):
    """An accordion component."""

    tag = "Content"

    alias = "RadixAccordionContent"

    def add_imports(self) -> dict:
        """Add imports to the component.

        Returns:
            The imports of the component.
        """
        return {"@emotion/react": "keyframes"}

    @classmethod
    def create(cls, *children, **props) -> Component:
        """Create the Accordion content component.

        Args:
            *children: The children of the component.
            **props: The properties of the component.

        Returns:
            The Accordion content Component.
        """
        if "AccordionContent" not in (
            cls_name := props.pop("class_name", "AccordionContent")
        ):
            cls_name = f"{cls_name} AccordionContent"

        return super().create(*children, class_name=cls_name, **props)

    def add_custom_code(self) -> list[str]:
        """Add custom code to the component.

        Returns:
            The custom code of the component.
        """
        return [
            """
const slideDown = keyframes`
from {
  height: 0;
}
to {
  height: var(--radix-accordion-content-height);
}
`
const slideUp = keyframes`
from {
  height: var(--radix-accordion-content-height);
}
to {
  height: 0;
}
`
"""
        ]

    def add_style(self) -> dict[str, Any] | None:
        """Add style to the component.

        Returns:
            The style of the component.
        """
        slide_down = LiteralVar.create(
            "${slideDown} var(--animation-duration) var(--animation-easing)",
        )

        slide_up = LiteralVar.create(
            "${slideUp} var(--animation-duration) var(--animation-easing)",
        )

        return {
            "overflow": "hidden",
            "color": color("accent", 11),
            "padding_x": "var(--space-4)",
            # Apply before and after content to avoid height animation jank.
            "&:before, &:after": {
                "content": "' '",
                "display": "block",
                "height": "var(--space-3)",
            },
            "&[data-state='open']": {"animation": slide_down},
            "&[data-state='closed']": {"animation": slide_up},
            _inherited_variant_selector("classic"): {
                "color": "var(--accent-contrast)",
            },
        }


class Accordion(ComponentNamespace):
    """Accordion component."""

    content = staticmethod(AccordionContent.create)
    header = staticmethod(AccordionHeader.create)
    item = staticmethod(AccordionItem.create)
    icon = staticmethod(AccordionIcon.create)
    root = staticmethod(AccordionRoot.create)
    trigger = staticmethod(AccordionTrigger.create)


accordion = Accordion()
