"""Mixin for keyboard-navigable list screens.

Provides shared cursor movement, page navigation, and scroll-into-view
logic used by SkillStoreScreen, ThreadSelectorScreen, and
ModelSelectorScreen.
"""

from __future__ import annotations

from textual.containers import VerticalScroll
from textual.widgets import Static


class NavigableListMixin:
    """Keyboard navigation for modal list screens.

    Subclasses must provide:

    - ``_option_widgets: list`` — flat list of selectable widgets.
    - ``_selected_index: int`` — current selection cursor.
    - ``_nav_selected_css_class: str`` — CSS class for the selected state.
    - ``_nav_get_scroll()`` — return the ``VerticalScroll`` container.
    - ``_nav_render_widget(index, selected)`` — re-render a widget label.
    """

    _selected_index: int
    _option_widgets: list[Static]
    _nav_selected_css_class: str

    def _nav_get_scroll(self) -> VerticalScroll | None:
        """Return the VerticalScroll container for the active list."""
        raise NotImplementedError

    def _nav_render_widget(self, index: int, *, selected: bool) -> None:
        """Re-render the widget at *index* with the given selection state."""
        raise NotImplementedError

    # ── Cursor movement ───────────────────────────────────────────────

    def _move_selection(self, delta: int) -> None:
        """Move selection by *delta*, updating only the affected widgets."""
        if not self._option_widgets:
            return

        count = len(self._option_widgets)
        old = self._selected_index
        new = (old + delta) % count
        self._selected_index = new

        # Update old widget
        old_w = self._option_widgets[old]
        old_w.remove_class(self._nav_selected_css_class)
        self._nav_render_widget(old, selected=False)

        # Update new widget
        new_w = self._option_widgets[new]
        new_w.add_class(self._nav_selected_css_class)
        self._nav_render_widget(new, selected=True)

        # Scroll into view
        if new == 0:
            scroll = self._nav_get_scroll()
            if scroll:
                scroll.scroll_home(animate=False)
        else:
            new_w.scroll_visible()

    def action_move_up(self) -> None:
        self._move_selection(-1)

    def action_move_down(self) -> None:
        self._move_selection(1)

    # ── Page navigation ───────────────────────────────────────────────

    def _visible_page_size(self) -> int:
        """Return the number of items that fit in one visual page."""
        scroll = self._nav_get_scroll()
        if not scroll:
            return 10
        h = scroll.size.height
        return max(1, h) if h > 0 else 10

    def action_page_up(self) -> None:
        if not self._option_widgets:
            return
        page = self._visible_page_size()
        delta = max(0, self._selected_index - page) - self._selected_index
        if delta:
            self._move_selection(delta)

    def action_page_down(self) -> None:
        if not self._option_widgets:
            return
        count = len(self._option_widgets)
        page = self._visible_page_size()
        delta = (
            min(count - 1, self._selected_index + page) - self._selected_index
        )
        if delta:
            self._move_selection(delta)
