# Copyright 2026 Marimo. All rights reserved.
from __future__ import annotations

from typing import TYPE_CHECKING, Final, Literal

from marimo._output.formatting import as_html
from marimo._output.hypertext import Html
from marimo._output.md import md
from marimo._output.rich_help import mddoc
from marimo._plugins.stateless.lazy import lazy as lazy_ui
from marimo._plugins.ui._core.ui_element import UIElement

if TYPE_CHECKING:
    from collections.abc import Callable


@mddoc
class tabs(UIElement[str, str]):
    """Display objects in a tabbed view.

    Examples:
        Show content in tabs:
        ```python
        tab1 = mo.vstack([mo.ui.slider(1, 10), mo.ui.text(), mo.ui.date()])

        tab2 = mo.md("You can show arbitrary content in a tab.")

        tabs = mo.ui.tabs({"Heading 1": tab1, "Heading 2": tab2})
        ```

        Control which tab is selected:
        ```python
        tabs = mo.ui.tabs(
            {"Heading 1": tab1, "Heading 2": tab2}, value="Heading 2"
        )
        ```

        Tab content can be lazily loaded:
        ```python
        tabs = mo.ui.tabs(
            {"Heading 1": tab1, "Heading 2": expensive_component}, lazy=True
        )
        ```

        Stack tabs vertically (useful when there are many tabs or long labels):
        ```python
        tabs = mo.ui.tabs(
            {f"Section {i}": f"Content {i}" for i in range(20)},
            orientation="vertical",
        )
        ```

    Attributes:
        value (str): The name of the selected tab.

    Args:
        tabs (dict[str, object]): A dictionary of tab names to tab content; strings
            are interpreted as markdown.
        value (str, optional): The name of the tab to open. Defaults to the first tab.
        lazy (bool, optional): Whether to lazily load the tab content.
            This is a convenience that wraps each tab in a `mo.lazy`
            component. Defaults to False.
        orientation ("horizontal" | "vertical", optional): The orientation of
            the tab bar. Use "vertical" to stack the tabs in a side panel. Defaults to "horizontal".
        label (str, optional): A descriptive name for the tab. Defaults to "".
        on_change (Callable[[dict[str, object]], None], optional): Optional callback
            to run when this element's value changes.
    """

    _name: Final[str] = "marimo-tabs"

    def __init__(
        self,
        tabs: dict[str, object],
        value: str | None = None,
        lazy: bool = False,
        *,
        orientation: Literal["horizontal", "vertical"] = "horizontal",
        label: str = "",
        on_change: Callable[[str], None] | None = None,
    ) -> None:
        def render_content(tab: object) -> Html:
            if lazy:
                return lazy_ui(tab)
            if isinstance(tab, str):
                return md(tab)
            return as_html(tab)

        # Retain strong references to the rendered tab contents. The slotted
        # HTML only freezes their text, and the UI element registry holds
        # elements weakly, so without this a UI element placed in a tab would
        # be garbage collected and lose its interactivity.
        self._children: list[Html] = [
            render_content(tab) for tab in tabs.values()
        ]

        tab_items = "".join(
            [
                "<div data-kind='tab'>" + child.text + "</div>"
                for child in self._children
            ]
        )

        self._tab_keys = list(tabs.keys())
        tab_labels = [md(label).text for label in tabs]

        index = (
            str(self._tab_keys.index(value))
            if value in self._tab_keys and tabs
            else None
        )

        super().__init__(
            component_name=self._name,
            initial_value=index or "",
            label=label,
            args={"tabs": tab_labels, "orientation": orientation},
            on_change=on_change,
            slotted_html=tab_items,
        )

    def _convert_value(self, value: str) -> str:
        if not value:
            if self._tab_keys:
                return self._tab_keys[0]
            return ""
        index = int(value)
        return self._tab_keys[index]
