"""ComponentBuilder -- Fluent builder API for rich component message layouts.

Chains component additions and builds a final list of component props
suitable for multi-component layouts.

Example::

    from dexfather import ComponentBuilder

    components = (
        ComponentBuilder()
        .header("Portfolio Summary")
        .divider()
        .section("Your top holdings this week.", title="Overview")
        .stats([
            {"label": "SOL", "value": "$142.50", "change": "+5.2%", "change_type": "positive"},
            {"label": "BONK", "value": "$0.00002", "change": "-1.1%", "change_type": "negative"},
        ])
        .actions([
            {"text": "Refresh", "value": "refresh", "style": "primary"},
            {"text": "Details", "value": "details"},
        ])
        .build()
    )
"""

from __future__ import annotations

import time
from typing import Any, Literal, Optional, TypedDict, Union


# ── TypedDict definitions for component props ────────────────────────────────

class StatItem(TypedDict, total=False):
    """A single stat entry for the stats component."""
    label: str  # required
    value: str  # required
    change: str
    change_type: Literal["positive", "negative", "neutral"]


class TableHeader(TypedDict, total=False):
    """Column header for the table component."""
    key: str  # required
    label: str  # required
    sortable: bool


class TabItem(TypedDict, total=False):
    """Tab definition for the tabs component."""
    id: str  # required
    label: str  # required
    content: str  # required


class AccordionSection(TypedDict, total=False):
    """Section definition for the accordion component."""
    id: str  # required
    title: str  # required
    content: str  # required
    default_open: bool


class CarouselItem(TypedDict, total=False):
    """Item definition for the carousel component."""
    title: str  # required
    description: str
    image_url: str
    action: dict[str, str]


class SelectOption(TypedDict):
    """Option for select_menu elements."""
    text: str
    value: str


class ContextElement(TypedDict, total=False):
    """Element for context components (text or image)."""
    type: Literal["text", "image"]  # required
    text: str
    image_url: str


class ButtonElement(TypedDict, total=False):
    """Button definition for action rows."""
    text: str  # required
    value: str  # required
    style: Literal["default", "primary", "secondary", "destructive", "success", "info"]
    url: str


class LeaderboardEntry(TypedDict, total=False):
    """Entry for leaderboard components."""
    rank: int  # required
    name: str  # required
    value: str  # required
    avatar_url: str
    change: str
    change_type: Literal["positive", "negative", "neutral"]


class CandleData(TypedDict):
    """OHLCV candle data point for price_chart."""
    t: int
    o: float
    h: float
    l: float
    c: float
    v: float


class TokenInfo(TypedDict, total=False):
    """Token entry for token_table components."""
    name: str  # required
    symbol: str  # required
    price: str  # required
    change_24h: str
    market_cap: str
    volume_24h: str
    logo_url: str


class TaskItem(TypedDict, total=False):
    """Task entry for task_progress components."""
    id: str  # required
    label: str  # required
    status: Literal["pending", "running", "done", "error"]  # required
    result: str
    duration: str


class UserCardStat(TypedDict):
    """Stat entry for user_card components."""
    label: str
    value: str


class AccessoryButton(TypedDict, total=False):
    """Accessory button for section components."""
    type: Literal["button"]  # required, always "button"
    text: str  # required
    value: str  # required
    style: str


class FormElement(TypedDict, total=False):
    """Form element definition (text_input, select_menu, checkbox, textarea, image_upload)."""
    type: Literal["text_input", "select_menu", "checkbox", "textarea", "image_upload"]  # required
    name: str  # required
    label: str
    placeholder: str
    required: bool
    min_length: int
    max_length: int
    options: list[SelectOption]
    multi: bool
    description: str
    initial_value: bool


class ComponentBuilder:
    """Fluent builder for rich component layouts.

    Each method appends a component dict to an internal list and returns
    ``self`` for chaining.  Call :meth:`build` to get the final list, or
    :meth:`build_single` to get the first component (for single-component sends).
    """

    __slots__ = ("_components",)

    def __init__(self) -> None:
        self._components: list[dict[str, Any]] = []

    # ── Core components ──────────────────────────────────────────────

    def header(
        self,
        text: str,
        *,
        size: Optional[Literal["sm", "md", "lg"]] = None,
    ) -> ComponentBuilder:
        """Add a header component.

        Args:
            text: Header text (max 256 chars).
            size: Text size -- 'sm', 'md', or 'lg' (default 'md').
        """
        c: dict[str, Any] = {"type": "header", "text": text}
        if size is not None:
            c["size"] = size
        self._components.append(c)
        return self

    def section(
        self,
        text: str,
        *,
        title: Optional[str] = None,
        accessory: Optional[AccessoryButton] = None,
    ) -> ComponentBuilder:
        """Add a section component with text and optional title/accessory.

        Args:
            text: Section body text (max 3000 chars).
            title: Optional section title (max 256 chars).
            accessory: Optional button accessory dict with text, value, and optional style.
        """
        c: dict[str, Any] = {"type": "section", "text": text}
        if title is not None:
            c["title"] = title
        if accessory is not None:
            c["accessory"] = accessory
        self._components.append(c)
        return self

    def divider(self) -> ComponentBuilder:
        """Add a divider line."""
        self._components.append({"type": "divider"})
        return self

    def context(
        self,
        elements: list[ContextElement],
    ) -> ComponentBuilder:
        """Add a context bar with text and image elements.

        Args:
            elements: List of context elements, each with type ('text' or 'image')
                      and corresponding text or image_url fields.
        """
        self._components.append({"type": "context", "elements": elements})
        return self

    def actions(
        self,
        buttons: list[ButtonElement],
    ) -> ComponentBuilder:
        """Add an action buttons row.

        Args:
            buttons: List of button dicts with ``text`` and ``value``, plus optional
                     ``style`` (default|primary|secondary|destructive|success|info) and ``url``.
        """
        elements = [{"type": "button", **b} for b in buttons]
        self._components.append({"type": "actions", "elements": elements})
        return self

    def image(
        self,
        image_url: str,
        *,
        alt_text: Optional[str] = None,
        title: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add an image component.

        Args:
            image_url: URL of the image (max 2048 chars).
            alt_text: Alternative text for accessibility (max 256 chars).
            title: Optional title displayed above the image (max 256 chars).
        """
        c: dict[str, Any] = {"type": "image", "image_url": image_url}
        if alt_text is not None:
            c["alt_text"] = alt_text
        if title is not None:
            c["title"] = title
        self._components.append(c)
        return self

    def alert(
        self,
        alert_type: Literal["info", "warning", "success", "error"],
        title: str,
        description: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add an alert component.

        Args:
            alert_type: Alert severity -- 'info', 'warning', 'success', or 'error'.
            title: Alert title (max 256 chars).
            description: Optional description (max 1000 chars).
        """
        c: dict[str, Any] = {"type": "alert", "alert_type": alert_type, "title": title}
        if description is not None:
            c["description"] = description
        self._components.append(c)
        return self

    def progress(
        self,
        value: float,
        *,
        label: Optional[str] = None,
        show_percentage: Optional[bool] = None,
    ) -> ComponentBuilder:
        """Add a progress bar component.

        Args:
            value: Progress value from 0 to 100.
            label: Optional label text (max 128 chars).
            show_percentage: Whether to display percentage text.
        """
        c: dict[str, Any] = {"type": "progress", "value": value}
        if label is not None:
            c["label"] = label
        if show_percentage is not None:
            c["show_percentage"] = show_percentage
        self._components.append(c)
        return self

    def stats(
        self,
        items: list[StatItem],
        *,
        columns: Optional[Literal[2, 3, 4]] = None,
    ) -> ComponentBuilder:
        """Add a stats grid component.

        Args:
            items: List of stat items (1-12), each with label, value, and optional
                   change/change_type fields.
            columns: Number of grid columns (2, 3, or 4). Default 2.
        """
        c: dict[str, Any] = {"type": "stats", "items": items}
        if columns is not None:
            c["columns"] = columns
        self._components.append(c)
        return self

    # ── Table & Advanced UI ──────────────────────────────────────────

    def table(
        self,
        headers: list[TableHeader],
        rows: list[dict[str, str]],
        *,
        page_size: Optional[int] = None,
        page: Optional[int] = None,
    ) -> ComponentBuilder:
        """Add a data table component.

        Args:
            headers: Column definitions with key, label, and optional sortable flag.
            rows: List of row dicts mapping header keys to cell values.
            page_size: Rows per page (1-100).
            page: Current page number (1-based).
        """
        c: dict[str, Any] = {"type": "table", "headers": headers, "rows": rows}
        if page_size is not None:
            c["page_size"] = page_size
        if page is not None:
            c["page"] = page
        self._components.append(c)
        return self

    def tabs(
        self,
        tab_list: list[TabItem],
        *,
        default_tab: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a tabs component.

        Args:
            tab_list: List of tab definitions with id, label, and content.
            default_tab: ID of the default active tab.
        """
        c: dict[str, Any] = {"type": "tabs", "tabs": tab_list}
        if default_tab is not None:
            c["default_tab"] = default_tab
        self._components.append(c)
        return self

    def accordion(
        self,
        sections: list[AccordionSection],
    ) -> ComponentBuilder:
        """Add an accordion component.

        Args:
            sections: List of collapsible sections with id, title, content,
                      and optional default_open flag.
        """
        self._components.append({"type": "accordion", "sections": sections})
        return self

    def carousel(
        self,
        items: list[CarouselItem],
    ) -> ComponentBuilder:
        """Add a carousel component.

        Args:
            items: List of carousel items with title, optional description,
                   image_url, and action button.
        """
        self._components.append({"type": "carousel", "items": items})
        return self

    def user_card(
        self,
        name: str,
        *,
        username: Optional[str] = None,
        avatar_url: Optional[str] = None,
        stats: Optional[list[UserCardStat]] = None,
        action_label: Optional[str] = None,
        action_value: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a user card component.

        Args:
            name: Display name (max 64 chars).
            username: Optional @username (max 64 chars).
            avatar_url: Optional avatar image URL (max 2048 chars).
            stats: Optional list of stat entries (max 6), each with label and value.
            action_label: Optional action button text (max 64 chars).
            action_value: Optional action button value (max 256 chars).
        """
        c: dict[str, Any] = {"type": "user_card", "name": name}
        if username is not None:
            c["username"] = username
        if avatar_url is not None:
            c["avatar_url"] = avatar_url
        if stats is not None:
            c["stats"] = stats
        if action_label is not None:
            c["action_label"] = action_label
        if action_value is not None:
            c["action_value"] = action_value
        self._components.append(c)
        return self

    # ── DeFi components ──────────────────────────────────────────────

    def token_table(
        self,
        tokens: list[TokenInfo],
        *,
        columns: Optional[list[str]] = None,
    ) -> ComponentBuilder:
        """Add a token table component.

        Args:
            tokens: List of token info dicts (1-50) with name, symbol, price,
                    and optional change_24h, market_cap, volume_24h, logo_url.
            columns: Optional list of column keys to display.
        """
        c: dict[str, Any] = {"type": "token_table", "tokens": tokens}
        if columns is not None:
            c["columns"] = columns
        self._components.append(c)
        return self

    def token_card(
        self,
        name: str,
        symbol: str,
        price: str,
        *,
        logo_url: Optional[str] = None,
        change_24h: Optional[str] = None,
        market_cap: Optional[str] = None,
        volume_24h: Optional[str] = None,
        holders: Optional[int] = None,
        mint: Optional[str] = None,
        buy_label: Optional[str] = None,
        sell_label: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a token card component.

        Args:
            name: Token name (max 64 chars).
            symbol: Token symbol (max 16 chars).
            price: Formatted price string (max 64 chars).
            logo_url: Optional token logo URL (max 2048 chars).
            change_24h: Optional 24h change string (e.g. '+5.2%').
            market_cap: Optional formatted market cap string.
            volume_24h: Optional formatted 24h volume string.
            holders: Optional holder count.
            mint: Optional token mint address (max 128 chars).
            buy_label: Custom label for the buy button (default 'Buy').
            sell_label: Custom label for the sell button (default 'Sell').
        """
        c: dict[str, Any] = {
            "type": "token_card",
            "name": name,
            "symbol": symbol,
            "price": price,
        }
        if logo_url is not None:
            c["logo_url"] = logo_url
        if change_24h is not None:
            c["change_24h"] = change_24h
        if market_cap is not None:
            c["market_cap"] = market_cap
        if volume_24h is not None:
            c["volume_24h"] = volume_24h
        if holders is not None:
            c["holders"] = holders
        if mint is not None:
            c["mint"] = mint
        if buy_label is not None:
            c["buy_label"] = buy_label
        if sell_label is not None:
            c["sell_label"] = sell_label
        self._components.append(c)
        return self

    def price_chart(
        self,
        data: list[CandleData],
        *,
        timeframes: Optional[list[str]] = None,
    ) -> ComponentBuilder:
        """Add a price chart component.

        Args:
            data: List of OHLCV candle data points (1-1000).
            timeframes: Optional list of available timeframe labels (e.g. ['1m', '5m', '1h']).
        """
        c: dict[str, Any] = {"type": "price_chart", "data": data}
        if timeframes is not None:
            c["timeframes"] = timeframes
        self._components.append(c)
        return self

    def wallet_address(
        self,
        address: str,
        *,
        chain: Optional[str] = None,
        label: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a wallet address component.

        Args:
            address: Wallet address string (max 128 chars).
            chain: Optional chain identifier (e.g. 'solana', 'ethereum').
            label: Optional descriptive label (max 64 chars).
        """
        c: dict[str, Any] = {"type": "wallet_address", "address": address}
        if chain is not None:
            c["chain"] = chain
        if label is not None:
            c["label"] = label
        self._components.append(c)
        return self

    def leaderboard(self, entries: list[LeaderboardEntry]) -> ComponentBuilder:
        """Add a leaderboard component.

        Args:
            entries: List of leaderboard entries (1-100) with rank, name, value,
                     and optional avatar_url, change, change_type.
        """
        self._components.append({"type": "leaderboard", "entries": entries})
        return self

    # ── Form components ──────────────────────────────────────────────

    def form(
        self,
        form_id: str,
        elements: list[FormElement],
        *,
        submit_label: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a form container component.

        Args:
            form_id: Unique form identifier (max 64 chars).
            elements: List of form field definitions (max 20). Each element must
                      have a type (text_input, select_menu, checkbox, textarea,
                      image_upload) and a name field.
            submit_label: Custom submit button text (max 64 chars, default 'Submit').
        """
        c: dict[str, Any] = {"type": "form", "form_id": form_id, "elements": elements}
        if submit_label is not None:
            c["submit_label"] = submit_label
        self._components.append(c)
        return self

    def text_input(
        self,
        name: str,
        *,
        label: Optional[str] = None,
        placeholder: Optional[str] = None,
        required: Optional[bool] = None,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
    ) -> ComponentBuilder:
        """Add a text input field (typically inside a form).

        Args:
            name: Field name for form collection (max 64 chars).
            label: Optional label text (max 128 chars).
            placeholder: Optional placeholder text (max 256 chars).
            required: Whether the field is required.
            min_length: Minimum input length.
            max_length: Maximum input length.
        """
        c: dict[str, Any] = {"type": "text_input", "name": name}
        if label is not None:
            c["label"] = label
        if placeholder is not None:
            c["placeholder"] = placeholder
        if required is not None:
            c["required"] = required
        if min_length is not None:
            c["min_length"] = min_length
        if max_length is not None:
            c["max_length"] = max_length
        self._components.append(c)
        return self

    def select_menu(
        self,
        name: str,
        options: list[SelectOption],
        *,
        label: Optional[str] = None,
        multi: Optional[bool] = None,
        placeholder: Optional[str] = None,
    ) -> ComponentBuilder:
        """Add a select menu field (typically inside a form).

        Args:
            name: Field name for form collection (max 64 chars).
            options: List of options, each with text and value.
            label: Optional label text (max 128 chars).
            multi: Whether multiple selections are allowed.
            placeholder: Optional placeholder text (max 256 chars).
        """
        c: dict[str, Any] = {"type": "select_menu", "name": name, "options": options}
        if label is not None:
            c["label"] = label
        if multi is not None:
            c["multi"] = multi
        if placeholder is not None:
            c["placeholder"] = placeholder
        self._components.append(c)
        return self

    def checkbox(
        self,
        name: str,
        label: str,
        *,
        description: Optional[str] = None,
        initial_value: Optional[bool] = None,
    ) -> ComponentBuilder:
        """Add a checkbox field (typically inside a form).

        Args:
            name: Field name for form collection (max 64 chars).
            label: Checkbox label text (max 256 chars).
            description: Optional description text (max 512 chars).
            initial_value: Initial checked state.
        """
        c: dict[str, Any] = {"type": "checkbox", "name": name, "label": label}
        if description is not None:
            c["description"] = description
        if initial_value is not None:
            c["initial_value"] = initial_value
        self._components.append(c)
        return self

    def textarea(
        self,
        name: str,
        *,
        label: Optional[str] = None,
        placeholder: Optional[str] = None,
        required: Optional[bool] = None,
        min_length: Optional[int] = None,
        max_length: Optional[int] = None,
    ) -> ComponentBuilder:
        """Add a textarea field (typically inside a form).

        Args:
            name: Field name for form collection (max 64 chars).
            label: Optional label text (max 128 chars).
            placeholder: Optional placeholder text (max 256 chars).
            required: Whether the field is required.
            min_length: Minimum input length.
            max_length: Maximum input length.
        """
        c: dict[str, Any] = {"type": "textarea", "name": name}
        if label is not None:
            c["label"] = label
        if placeholder is not None:
            c["placeholder"] = placeholder
        if required is not None:
            c["required"] = required
        if min_length is not None:
            c["min_length"] = min_length
        if max_length is not None:
            c["max_length"] = max_length
        self._components.append(c)
        return self

    # ── Live update components ──────────────────────────────────────

    def live_price_ticker(
        self,
        token: str,
        price: float,
        change: float,
        *,
        interval: Optional[int] = None,
    ) -> ComponentBuilder:
        """Add a live price ticker component.

        Args:
            token: Token symbol (e.g. 'SOL', 'BTC', max 32 chars).
            price: Current price as a number.
            change: Change percentage (e.g. 5.23 or -2.1).
            interval: Optional update interval in seconds (1-60, display only).
        """
        c: dict[str, Any] = {
            "type": "live_price_ticker",
            "token": token,
            "price": price,
            "change": change,
        }
        if interval is not None:
            c["interval"] = interval
        self._components.append(c)
        return self

    def task_progress(
        self,
        title: str,
        tasks: list[TaskItem],
    ) -> ComponentBuilder:
        """Add a task progress component.

        Args:
            title: Title for the task list (max 256 chars).
            tasks: List of task dicts (1-50) with id, label, status
                   (pending|running|done|error), and optional result, duration.
        """
        self._components.append({
            "type": "task_progress",
            "title": title,
            "tasks": tasks,
        })
        return self

    # ── Terminal ──────────────────────────────────────────────────────

    def build(self) -> list[dict[str, Any]]:
        """Build and return the final list of component props."""
        return list(self._components)

    def build_single(self) -> dict[str, Any]:
        """Get the first component (for single-component sends)."""
        if not self._components:
            raise ValueError("ComponentBuilder has no components to build")
        return self._components[0]
