"""Session sidebar widget for listing and searching chat sessions."""

from typing import TYPE_CHECKING, Any, Callable, cast

from textual import on, work
from textual.app import ComposeResult
from textual.containers import Horizontal, Vertical
from textual.widget import Widget
from textual.widgets import Checkbox, DataTable, Input, Static

from alloy_runtime_sdk.api_client.client import ApiClient
from alloy_runtime_types.dtos.sessions import SessionSummary
from alloy_runtime_sdk.logging.config import get_logger

from cli.tui.chat.messages import SessionSelected, SessionsLoaded, SessionsLoadError
from cli.tui.chat.store import ChatStore
from cli.tui.chat.types import ChatPhase, ChatState, format_session_time

logger = get_logger(__name__)

if TYPE_CHECKING:
    from cli.tui.app import AlloyRuntimeApp


class SessionSidebar(Widget):
    """Sidebar widget showing session list with search.

    Features:
        - Search sessions with debounced API calls
        - Display session list in a DataTable
        - Select sessions to load their messages
        - Toggle between named-only and all sessions

    Posts Messages:
        - SessionSelected: When user selects a session
        - SessionsLoaded: After successfully loading sessions
        - SessionsLoadError: When session loading fails
    """

    app: "AlloyRuntimeApp"

    # No bindings on widget - parent Screen handles all keybindings
    # and delegates to public methods on this widget

    DEFAULT_CSS = """
    SessionSidebar #show-untitled-row {
        height: auto;
        padding: 0 1;
    }
    
    SessionSidebar #show-untitled {
        height: auto;
        min-height: 1;
        padding: 0;
        margin: 0;
    }
    """

    def __init__(self, store: ChatStore) -> None:
        """Initialize the session sidebar.

        Args:
            store: ChatStore instance for state management.
        """
        super().__init__()
        self._store = store
        self._last_search = ""
        self._show_untitled = False  # Default: show only named sessions
        self._unsubscribe: Callable[[], None] | None = None
        # Track visibility to refresh when sidebar is opened
        self._was_visible = False
        # Guard against re-entrant state changes
        self._refreshing = False

    def compose(self) -> ComposeResult:
        """Compose the sidebar layout."""
        with Vertical(id="sidebar-root"):
            with Vertical(id="session-header"):
                yield Input(
                    placeholder="Search sessions...",
                    id="session-search",
                )
                with Horizontal(id="show-untitled-row"):
                    yield Checkbox("Show untitled", id="show-untitled", value=False)
                yield Static("Loading...", id="session-status")
            yield DataTable(id="session-table", cursor_type="row")

    def on_mount(self) -> None:
        table = self._table()
        table.add_columns("Session", "Time", "Msgs")

        # Subscribe to store changes
        self._unsubscribe = self._store.subscribe(self._on_state_change)

        # Load initial sessions
        self._load_sessions("")

    def on_unmount(self) -> None:
        """Unsubscribe from store on unmount."""
        if self._unsubscribe:
            self._unsubscribe()

    def _on_state_change(self, state: ChatState) -> None:
        """React to store state changes."""
        # Handle sidebar visibility
        self.display = state.sidebar_visible

        # Refresh sessions when sidebar becomes visible (with guard against recursion)
        becoming_visible = state.sidebar_visible and not self._was_visible
        self._was_visible = state.sidebar_visible

        if becoming_visible and not self._refreshing:
            self._refreshing = True
            self._load_sessions(self._last_search)

        # Update table when sessions change
        self._update_table(state.sessions)

        # Update status based on phase
        status = self.query_one("#session-status", Static)
        if state.phase == ChatPhase.LOADING_SESSIONS:
            status.update("Loading...")
        elif state.sessions:
            status.update(f"{len(state.sessions)} sessions")
        else:
            status.update("No sessions found")

    def _update_table(self, sessions: tuple[SessionSummary, ...]) -> None:
        table = self._table()
        table.clear()

        # Populate table with sessions (filtering is done server-side via named_only)
        for session in sessions:
            name = session.name or "[dim]Untitled[/]"
            table.add_row(
                name,
                format_session_time(session.last_activity_at),
                str(session.message_count),
            )

    def _table(self) -> DataTable[Any]:
        return cast(DataTable[Any], self.query_one("#session-table", DataTable))

    @on(Input.Changed, "#session-search")
    def on_search_changed(self, event: Input.Changed) -> None:
        """Handle search input changes with debouncing."""
        query = event.value.strip()
        if query != self._last_search:
            self._last_search = query
            self._store.set_session_search_query(query)
            self._load_sessions(query)

    @on(Checkbox.Changed, "#show-untitled")
    def on_show_untitled_changed(self, event: Checkbox.Changed) -> None:
        """Handle show untitled checkbox toggle - triggers a reload from server."""
        self._show_untitled = event.value
        # Reload sessions from server with new filter setting
        self._load_sessions(self._last_search)

    @on(DataTable.RowSelected, "#session-table")
    def on_row_selected(self, event: DataTable.RowSelected) -> None:
        """Handle session selection from table."""
        row_index = event.cursor_row
        sessions = self._store.state.sessions
        if 0 <= row_index < len(sessions):
            session = sessions[row_index]
            logger.debug(
                "sidebar_session_selected",
                session_id=str(session.id),
                session_name=session.name,
            )
            self.post_message(SessionSelected(session.id))

    @work(exclusive=True)
    async def _load_sessions(self, query: str) -> None:
        """Load sessions from API."""
        logger.debug("sidebar_loading_sessions", query=query or None)
        self._store.set_phase(ChatPhase.LOADING_SESSIONS)
        status = self.query_one("#session-status", Static)
        status.update("Loading...")

        try:
            client = await self._get_client()
            response = await client.list_sessions(
                search=query if query else None,
                named_only=not self._show_untitled,
                limit=50,
            )

            logger.debug("sidebar_sessions_loaded", count=len(response.sessions))
            self._store.set_sessions(list(response.sessions))
            self._store.set_phase(ChatPhase.IDLE)
            self.post_message(SessionsLoaded(list(response.sessions)))

        except Exception as e:
            logger.warning("sidebar_sessions_load_error", error=str(e))
            self._store.set_phase(ChatPhase.IDLE)
            status.update("[red]Error[/]")
            self.post_message(SessionsLoadError(str(e)))
        finally:
            self._refreshing = False

    async def _get_client(self) -> ApiClient:
        """Get the shared server client."""
        return await self.app.store.get_client()

    def action_focus_search(self) -> None:
        """Focus the search input."""
        self.query_one("#session-search", Input).focus()

    def refresh_sessions(self) -> None:
        """Refresh the session list with current search query."""
        self._load_sessions(self._last_search)
