"""Card stack viewport with 3-section CSS Grid layout, slot rendering,"""

# AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/components/viewport.ipynb.

# %% auto #0
__all__ = ['render_slot_card', 'render_all_slots_oob', 'render_viewport']

# %% ../../nbs/components/viewport.ipynb #v1000003
from typing import Any, Callable, List, Optional

from fasthtml.common import Div, Script, Hidden

# Tailwind utilities
from cjm_fasthtml_tailwind.utilities.effects import opacity
from cjm_fasthtml_tailwind.utilities.flexbox_and_grid import (
    flex_display, flex_direction, justify, items, gap, grid_display
)
from cjm_fasthtml_tailwind.utilities.layout import overflow, position, inset, z
from cjm_fasthtml_tailwind.utilities.interactivity import cursor, touch
from cjm_fasthtml_tailwind.utilities.sizing import w, h
from cjm_fasthtml_tailwind.utilities.spacing import p, m
from cjm_fasthtml_tailwind.utilities.transitions_and_animation import transition, duration, ease
from cjm_fasthtml_tailwind.core.base import combine_classes

# Local imports
from ..core.config import CardStackConfig
from ..core.html_ids import CardStackHtmlIds
from ..core.models import CardStackState, CardRenderContext, CardStackUrls
from ..core.constants import CardRole
from ..helpers.focus import resolve_focus_slot, calculate_viewport_window
from .states import render_placeholder_card

# %% ../../nbs/components/viewport.ipynb #v1000005
def _render_mode_sync_script(
    active_mode: Optional[str] = None,  # Active keyboard mode name (None = navigation)
) -> Any:  # Script element that syncs keyboard mode state
    """Generate script to sync keyboard navigation mode with rendered UI state."""
    target_mode = active_mode if active_mode else "navigation"

    return Script(f"""
        (function() {{
            if (typeof window.kbNav !== 'undefined') {{
                const state = window.kbNav.getState();
                const currentMode = state ? state.currentMode : 'navigation';
                const targetMode = '{target_mode}';
                if (targetMode !== 'navigation' && currentMode !== targetMode) {{
                    window.kbNav.enterMode(targetMode);
                }} else if (targetMode === 'navigation' && currentMode !== 'navigation') {{
                    window.kbNav.exitMode();
                }}
            }}
        }})();
    """)

# %% ../../nbs/components/viewport.ipynb #v1000007
def _render_click_overlay(
    item_index: int,  # Index of the item this slot represents
    urls: CardStackUrls,  # URL bundle for navigation
) -> Any:  # Transparent click overlay element
    """Render transparent click-to-focus overlay for a context card slot."""
    return Div(
        cls=combine_classes(
            position.absolute, inset(0), z(10),
            cursor.pointer
        ),
        hx_post=urls.nav_to_index,
        hx_vals=f'{{"target_index": {item_index}}}',
        hx_swap="none"
    )

# %% ../../nbs/components/viewport.ipynb #v1000009
def render_slot_card(
    slot_index: int,  # Index of this slot in the viewport (0-based)
    focus_slot: int,  # Which slot is the focused position
    card_items: List[Any],  # Full items list
    item_index: int,  # Item index (negative or >= len for placeholder)
    render_card: Callable,  # Callback: (item, CardRenderContext) -> FT
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    oob: bool = False,  # Whether to render as OOB swap
) -> Any:  # Slot content wrapper
    """Render a single card for a viewport slot."""
    total_items = len(card_items)
    is_placeholder = item_index < 0 or item_index >= total_items
    slot_id = ids.viewport_slot(item_index)
    is_focused = slot_index == focus_slot
    card_role: CardRole = "focused" if is_focused else "context"
    distance = slot_index - focus_slot
    prefix = config.prefix

    # Determine placeholder type based on position relative to focus
    placeholder_type = "start" if slot_index < focus_slot else "end"

    # Render content
    if is_placeholder:
        content = render_placeholder_card(placeholder_type)
    else:
        context = CardRenderContext(
            card_role=card_role,
            index=item_index,
            total_items=total_items,
            is_first=(item_index == 0),
            is_last=(item_index == total_items - 1),
            active_mode=state.active_mode,
            card_scale=state.card_scale,
            distance_from_focus=distance,
        )
        content = render_card(card_items[item_index], context)

    # Focus styling from config
    focus_cls = combine_classes(
        config.style.focus_shadow, config.style.focus_border_radius
    ) if is_focused else ""

    # Mode sync script in focused slot OOB updates
    mode_sync = _render_mode_sync_script(state.active_mode) if (oob and is_focused) else None

    # Click-to-focus overlay for context cards (not placeholders)
    click_overlay = None
    if config.click_to_focus and not is_focused and not is_placeholder:
        click_overlay = _render_click_overlay(item_index, urls)

    # Slot container — context cards get configurable padding via CSS custom property
    slot_cls = combine_classes(
        "viewport-slot",
        p(f'[var(--{prefix}-slot-padding)]') if not is_focused else "",
        w.full,
        focus_cls,
        position.relative if click_overlay else ""
    )

    return Div(
        content,
        click_overlay,
        mode_sync,
        id=slot_id,
        cls=slot_cls,
        tabindex="0" if is_focused else "-1",
        hx_swap_oob="innerHTML" if oob else None
    )

# %% ../../nbs/components/viewport.ipynb #v1000021
def render_all_slots_oob(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
) -> List[Any]:  # List of OOB elements (3 sections)
    """Render all viewport sections with OOB swap for granular updates."""
    total_items = len(card_items)
    focus_slot = resolve_focus_slot(state.focus_position, state.visible_count)
    prefix = config.prefix

    viewport_indices = calculate_viewport_window(
        state.focused_index, total_items, state.visible_count, state.focus_position
    )

    before_cards = []
    focused_card = None
    after_cards = []

    for slot_index, item_index in enumerate(viewport_indices):
        card_el = render_slot_card(
            slot_index=slot_index, focus_slot=focus_slot,
            card_items=card_items, item_index=item_index,
            render_card=render_card, state=state,
            config=config, ids=ids, urls=urls, oob=False,
        )

        if slot_index < focus_slot:
            before_cards.append(card_el)
        elif slot_index == focus_slot:
            focused_card = card_el
        else:
            after_cards.append(card_el)

    # Section styling — gap via CSS custom property
    section_cls = lambda alignment: combine_classes(
        flex_display, flex_direction.col, alignment, items.center,
        w.full, gap(f'[var(--{prefix}-section-gap)]'), overflow.hidden
    )

    before_section = Div(
        *before_cards,
        id=ids.viewport_section_before,
        cls=section_cls(justify.end),
        hx_swap_oob="innerHTML"
    )

    mode_sync = _render_mode_sync_script(state.active_mode)
    focused_section = Div(
        focused_card, mode_sync,
        id=ids.viewport_section_focused,
        cls=combine_classes(
            flex_display, justify.center, items.center, w.full,
            p.x(f'[var(--{prefix}-focus-padding-x)]'),
            p.b(f'[var(--{prefix}-focus-padding-b)]')
        ),
        hx_swap_oob="innerHTML"
    )

    after_section = Div(
        *after_cards,
        id=ids.viewport_section_after,
        cls=section_cls(justify.start),
        hx_swap_oob="innerHTML"
    )

    return [before_section, focused_section, after_section]

# %% ../../nbs/components/viewport.ipynb #v1000031
def _grid_template_rows(
    focus_position: Optional[int] = None,  # Focus slot offset (None=center, -1=bottom, 0=top)
) -> str:  # CSS grid-template-rows value
    """Compute CSS grid-template-rows based on focus position intent."""
    if focus_position is None:
        return "1fr auto 1fr"  # Center: always 3-section
    elif focus_position == 0:
        return "auto 1fr"  # Top: focused first
    elif focus_position < 0:
        return "1fr auto"  # Bottom: focused last
    else:
        return "1fr auto 1fr"  # Custom positive: always 3-section

# %% ../../nbs/components/viewport.ipynb #v1000041
def render_viewport(
    card_items: List[Any],  # All data items
    state: CardStackState,  # Current card stack state
    config: CardStackConfig,  # Card stack configuration
    ids: CardStackHtmlIds,  # HTML IDs for this instance
    urls: CardStackUrls,  # URL bundle for navigation
    render_card: Callable,  # Card renderer callback
    form_input_name: str = "focused_index",  # Name for the focused index hidden input
) -> Any:  # Viewport component with 3-section layout
    """Render the card stack viewport with 3-section CSS Grid layout."""
    total_items = len(card_items)
    focus_slot = resolve_focus_slot(state.focus_position, state.visible_count)
    prefix = config.prefix

    viewport_indices = calculate_viewport_window(
        state.focused_index, total_items, state.visible_count, state.focus_position
    )

    before_cards = []
    focused_card = None
    after_cards = []

    for slot_index, item_index in enumerate(viewport_indices):
        card_el = render_slot_card(
            slot_index=slot_index, focus_slot=focus_slot,
            card_items=card_items, item_index=item_index,
            render_card=render_card, state=state,
            config=config, ids=ids, urls=urls, oob=False,
        )

        if slot_index < focus_slot:
            before_cards.append(card_el)
        elif slot_index == focus_slot:
            focused_card = card_el
        else:
            after_cards.append(card_el)

    # Section styling — gap via CSS custom property
    section_cls = lambda alignment: combine_classes(
        flex_display, flex_direction.col, alignment, items.center,
        w.full, gap(f'[var(--{prefix}-section-gap)]'), overflow.hidden
    )

    before_section = Div(
        *before_cards,
        id=ids.viewport_section_before,
        cls=section_cls(justify.end)
    )

    focused_section = Div(
        focused_card,
        id=ids.viewport_section_focused,
        cls=combine_classes(
            flex_display, justify.center, items.center, w.full,
            p.x(f'[var(--{prefix}-focus-padding-x)]'),
            p.b(f'[var(--{prefix}-focus-padding-b)]')
        )
    )

    after_section = Div(
        *after_cards,
        id=ids.viewport_section_after,
        cls=section_cls(justify.start)
    )

    # Grid template based on focus position intent (stable across count changes)
    grid_rows = _grid_template_rows(state.focus_position)

    inner_cls = combine_classes(
        grid_display, w.full, h.full, m.x.auto,
        gap(f'[var(--{prefix}-section-gap)]')
    )
    inner_style = f"grid-template-rows: {grid_rows}; max-width: {state.card_width}rem"

    # Outer container: padding via CSS custom properties, opacity reveal via Tailwind classes
    outer_cls = combine_classes(
        w.full,
        p.x(f'[var(--{prefix}-viewport-padding-x)]'),
        p.y(f'[var(--{prefix}-viewport-padding-y)]'),
        overflow.hidden, touch.none,
        opacity(0), transition.opacity, duration(150), ease._in
    )

    # CSS custom property declarations on outer container
    outer_style = config.style.css_vars_style(prefix)

    # Hidden input for focused index (needed for keyboard nav hx-include and OOB updates)
    focused_input = Hidden(
        id=ids.focused_index_input,
        name=form_input_name,
        value=str(state.focused_index),
    )

    return Div(
        Div(
            before_section,
            focused_section,
            after_section,
            id=ids.card_stack_inner,
            cls=inner_cls,
            style=inner_style
        ),
        _render_mode_sync_script(state.active_mode),
        focused_input,
        id=ids.card_stack,
        cls=outer_cls,
        style=outer_style,
        data_focused_index=str(state.focused_index),
        data_total_items=str(total_items),
        data_visible_count=str(state.visible_count)
    )
