"""Status navigation manager for interactive widget navigation.

This module provides navigation state management and keyboard routing for
interactive status widgets. It coordinates with MessageDisplayCoordinator to
prevent render conflicts during navigation.
"""

import asyncio
import logging
import time
from typing import Any, Dict, List, Optional, Tuple

from core.events.models import EventType
from core.io.status.toggle_handler import create_toggle_handler_from_widget
from core.io.status.navigation_state import (
    StatusNavigationState,
    NavigationMode,
    SelectionType,
    UndoAction,
)
from core.io.status.constants import WIDGET_BG_COLOR_NAMES
from core.io.visual_effects import ShimmerEffect

logger = logging.getLogger(__name__)


class StatusNavigationManager:
    """Manages navigation state with MessageDisplayCoordinator awareness.

    This manager handles keyboard routing for status widget navigation,
    coordinates with the message display system to prevent render conflicts,
    and manages the navigation lifecycle.

    Attributes:
        renderer: TerminalRenderer instance for rendering
        coordinator: MessageDisplayCoordinator for render coordination
        event_bus: EventBus for emitting widget interaction events
        state: StatusNavigationState for tracking navigation state
        layout: Current status layout for navigation bounds
    """

    def __init__(self, renderer, coordinator, event_bus, widget_registry=None, layout: Optional[Any] = None, layout_manager: Optional[Any] = None, config: Optional[Any] = None):
        """Initialize the navigation manager.

        Args:
            renderer: TerminalRenderer instance
            coordinator: MessageDisplayCoordinator instance
            event_bus: EventBus instance for emitting widget events
            widget_registry: StatusWidgetRegistry for widget info lookup
            layout: Optional status layout for bounds calculation
            layout_manager: Optional layout manager for layout editing
            config: Optional config for first-run help check
        """
        self.renderer = renderer
        self.coordinator = coordinator
        self.event_bus = event_bus
        self.widget_registry = widget_registry
        # Use the new StatusNavigationState with mode support
        self.state = StatusNavigationState()
        self.layout = layout
        self.layout_manager = layout_manager
        self.config = config
        self._first_run_help_checked = False

        # Shimmer effect for navigation mode
        self._shimmer = ShimmerEffect(speed=1, wave_width=6)
        self._shimmer_task: Optional[asyncio.Task] = None
        self._shimmer_running = False

        logger.debug("StatusNavigationManager initialized")

    def set_layout(self, layout: Any) -> None:
        """Set the current status layout for navigation bounds.

        Args:
            layout: Status layout configuration
        """
        self.layout = layout
        logger.debug("Navigation layout updated")

    def set_widget_registry(self, widget_registry: Any) -> None:
        """Set the widget registry for widget info lookup.

        Args:
            widget_registry: StatusWidgetRegistry instance
        """
        self.widget_registry = widget_registry
        logger.debug("Widget registry set for navigation")

    def set_layout_manager(self, layout_manager: Any) -> None:
        """Set the layout manager for layout editing.

        Args:
            layout_manager: StatusLayoutManager instance
        """
        self.layout_manager = layout_manager
        logger.debug("Layout manager set for navigation")

    @property
    def active(self) -> bool:
        """Check if navigation mode is currently active.

        Returns:
            True if navigation mode is active, False otherwise
        """
        return self.state.active

    async def toggle_navigation_mode(self) -> bool:
        """Toggle navigation mode on/off (Tab key handler).

        Returns:
            True if navigation mode is now active, False if inactive
        """
        is_active = await self.state.is_active()

        if is_active:
            # ESC behavior: always exit to Input Mode
            await self.exit_navigation_mode()
            return False
        else:
            # Check first-run help BEFORE entering navigation mode
            # (modal shows while still in INPUT mode, avoiding state conflicts)
            await self._check_first_run_help()

            # Enter navigation mode
            result = await self.enter_navigation_mode()
            logger.info(f"[NAV] toggle_navigation_mode: enter_navigation_mode returned {result}")
            if not result:
                logger.warning("[NAV] toggle_navigation_mode: enter_navigation_mode failed!")
            return result

    async def enter_navigation_mode(self) -> bool:
        """Enter navigation mode (transitions to STATUS_FOCUS).

        Returns:
            True if successfully entered navigation mode
        """
        logger.info("[NAV] enter_navigation_mode called")

        # Check if already active
        current_mode = await self.state.get_mode()
        if current_mode != NavigationMode.INPUT:
            logger.debug(f"Navigation mode already active (mode={current_mode.value})")
            return True

        logger.info("[NAV] Checking coordinator state")
        logger.info(f"[NAV] is_writing_messages={self.coordinator.is_writing_messages()}, _in_alternate_buffer={self.coordinator._in_alternate_buffer}")
        # Check if LLM is streaming or messages are queued
        if self.coordinator.is_writing_messages():
            logger.warning("Cannot enter navigation during message display")
            return False

        # Check if in alternate buffer (modal/fullscreen active)
        if self.coordinator._in_alternate_buffer:
            logger.warning("Cannot enter navigation during modal/fullscreen mode")
            return False

        logger.info("[NAV] Transitioning to STATUS_FOCUS mode")
        # Use the new mode transition method
        await self.state.transition_to_status_focus()
        logger.info("[NAV] Status focus mode set")

        # Signal coordinator: navigation active, pause message renders
        logger.info("[NAV] Signaling coordinator")
        self.coordinator.set_navigation_active(True)

        # Render initial selection
        logger.info("[NAV] About to render navigation state")
        await self.render_navigation_state()
        logger.info("[NAV] Render navigation state complete")

        # Start shimmer animation loop if any widgets have effects
        await self._start_effect_animation()

        logger.info("Entered navigation mode (STATUS_FOCUS)")
        return True

    async def exit_navigation_mode(self) -> None:
        """Exit navigation mode (restore message display).

        ESC key behavior:
        - EDIT mode -> STATUS_FOCUS (first Esc)
        - STATUS_FOCUS -> INPUT (second Esc)
        """
        current_mode = await self.state.get_mode()
        active_widget_id = self.state.active_widget_id

        # Deactivate any active widget interaction
        if self.state.interaction_active:
            await self.state.deactivate_widget()

        # Mode-based exit behavior
        if current_mode == NavigationMode.EDIT:
            # First Esc: EDIT -> STATUS_FOCUS
            await self.state.transition_to_status_focus_from_edit()
            await self.render_navigation_state()
            logger.info("Exited EDIT mode, now in STATUS_FOCUS")
        else:
            # STATUS_FOCUS -> INPUT (full exit)
            await self.state.transition_to_input_mode()

            # Stop effect animation
            await self._stop_effect_animation()

            # Signal coordinator: navigation done, resume message renders
            self.coordinator.set_navigation_active(False)

            # Emit deactivation event if there was an active widget
            if active_widget_id:
                await self.event_bus.emit_with_hooks(
                    EventType.WIDGET_DEACTIVATED,
                    {
                        "widget_id": active_widget_id,
                        "reason": "navigation_exit"
                    },
                    "StatusNavigationManager.exit_navigation_mode"
                )

            # Restore normal rendering
            await self.renderer.render_active_area()
            logger.info("Exited navigation mode to INPUT")

    # Aliases for KeyPressHandler compatibility
    async def activate_navigation(self) -> bool:
        """Alias for transition_to_status_focus (for Tab key)."""
        return await self.transition_to_status_focus()

    async def deactivate_navigation(self) -> None:
        """Alias for exit_navigation_mode (direct exit to INPUT)."""
        await self.exit_navigation_mode()

    async def handle_keypress(self, key_press) -> bool:
        """Handle keypress during navigation mode.

        Routes keypresses to appropriate handlers:
        - Arrow keys -> handle_arrow_key
        - Enter -> handle_enter_key
        - Escape -> exit_navigation_mode
        - Space -> handle_space_key (for toggle widgets)

        Args:
            key_press: KeyPress object from key parser

        Returns:
            True if keypress was handled, False otherwise
        """
        if not self.active:
            return False

        key_name = getattr(key_press, 'name', None) or getattr(key_press, 'key', None)
        if not key_name:
            return False

        # Map arrow key names to our format
        if key_name in ("ArrowUp", "Up"):
            return await self.handle_arrow_key("up")
        elif key_name in ("ArrowDown", "Down"):
            return await self.handle_arrow_key("down")
        elif key_name in ("ArrowLeft", "Left"):
            return await self.handle_arrow_key("left")
        elif key_name in ("ArrowRight", "Right"):
            return await self.handle_arrow_key("right")
        elif key_name == "Home":
            return await self.handle_arrow_key("home")
        elif key_name == "End":
            return await self.handle_arrow_key("end")
        elif key_name == "Enter":
            await self.handle_enter_key()
            return True
        elif key_name == "Escape":
            # Cascading mode exit: EDIT -> STATUS_FOCUS -> INPUT
            await self.transition_to_input_mode()
            return True
        elif key_name == "Space" or (hasattr(key_press, 'char') and key_press.char == ' '):
            await self.handle_space_key()
            return True
        elif key_name == "F1":
            # Show help overlay from navigation mode
            await self._show_help_from_navigation()
            return True
        elif key_name == "?" or (hasattr(key_press, 'char') and key_press.char == '?'):
            # Show help overlay from navigation mode
            await self._show_help_from_navigation()
            return True
        elif key_name == "Ctrl+Z":
            # Undo last action
            await self.handle_undo()
            return True

        # Digit keys 1-9: Quick jump to widget in current row
        char = getattr(key_press, 'char', None)
        if char and char.isdigit() and '1' <= char <= '9':
            digit = int(char)
            if await self._handle_digit_key(digit):
                return True

        # Layout editing shortcuts
        char = getattr(key_press, 'char', None)
        name = getattr(key_press, 'name', None)
        logger.info(f"[NAV] Layout editing check: char='{char}', name='{name}', key_name='{key_name}'")

        # Get current mode for edit mode checks
        current_mode = await self.state.get_mode()

        # 'e' key: Enter edit mode from STATUS_FOCUS
        if char == 'e' or name == 'e':
            if current_mode == NavigationMode.STATUS_FOCUS:
                logger.info("[NAV] 'e' pressed - entering EDIT mode")
                await self._enter_edit_mode()
                return True
            else:
                logger.info(f"[NAV] 'e' pressed but not in STATUS_FOCUS (mode={current_mode.value})")

        # Edit mode only shortcuts
        if current_mode == NavigationMode.EDIT:
            # Check both char and name for flexibility
            if char == 'a' or char == '+' or name == 'a':
                # Add widget at selected slot
                logger.info("[NAV] Matched 'a' or '+' -> handle_add_widget_at_slot")
                await self.handle_add_widget_at_slot()
                return True
            elif char == 'd' or char == '-' or name == 'd':
                # Remove selected widget (only in edit mode)
                logger.info("[NAV] Matched 'd' or '-' -> handle_remove_widget")
                await self.handle_remove_widget()
                return True
            elif char == 'c' or name == 'c':
                # Toggle widget color (only in edit mode)
                logger.info("[NAV] Matched 'c' -> handle_toggle_widget_color")
                await self.handle_toggle_widget_color()
                return True
            elif char == 'x' or name == 'x':
                # Toggle widget effect (only in edit mode)
                logger.info("[NAV] Matched 'x' -> handle_toggle_widget_effect")
                await self.handle_toggle_widget_effect()
                return True
            elif char == 'A' or name == 'A':
                # Add new row (Shift+A)
                logger.info("[NAV] Matched 'A' -> handle_add_row")
                await self.handle_add_row()
                return True
            elif char == 'R' or name == 'R':
                # Remove/hide current row (Shift+R) - only if empty
                logger.info("[NAV] Matched 'R' -> handle_remove_row")
                await self.handle_remove_row()
                return True
        else:
            # Not in edit mode - log why we're not handling these keys
            if char in ('d', '-', 'c', 'x') or name in ('d', 'c', 'x'):
                logger.info(f"[NAV] '{char}' pressed but not in EDIT mode - ignoring")

        # Unhandled key
        return False

    async def handle_arrow_key(self, key: str) -> bool:
        """Handle arrow key navigation.

        For toggle widgets that are activated:
        - Left/Right cycles through states
        - Up/Down exits toggle interaction and resumes normal navigation

        For edit mode navigation:
        - Left/Right moves through slot-widget-slot pattern
        - Up/Down moves between rows

        For normal navigation (STATUS_FOCUS):
        - Arrows move selection between widgets
        - Home/End jump to first/last widget

        Args:
            key: Key identifier ("up", "down", "left", "right", "home", "end")

        Returns:
            True if navigation was handled, False otherwise
        """
        if not await self.state.is_active():
            return False

        # Check if a toggle widget is currently active for state cycling
        if await self.state.is_interacting():
            active_widget_id = self.state.active_widget_id
            if active_widget_id:
                widget_info = await self._get_active_widget_info(active_widget_id)
                if widget_info and widget_info.get("interaction_type") == "toggle":
                    return await self._handle_toggle_arrow_key(key, widget_info)

        # Check if in EDIT mode for slot navigation
        current_mode = await self.state.get_mode()
        is_edit_mode = (current_mode == NavigationMode.EDIT)

        # Get layout bounds (include hidden rows in edit mode)
        max_row, max_widget = self._get_layout_bounds(include_hidden=is_edit_mode)

        if key == "up":
            delta_row = -1
            if is_edit_mode:
                # In edit mode: move with slot consideration
                await self.state.move_selection_with_slot(delta_row, 0)
                await self._clamp_selection_to_bounds(max_row, max_widget)
            else:
                # In status focus: normal widget navigation
                await self.state.move_selection(-1, 0)
                await self._clamp_selection_to_bounds(max_row, max_widget)
        elif key == "down":
            delta_row = 1
            if is_edit_mode:
                # In edit mode: move with slot consideration
                await self.state.move_selection_with_slot(delta_row, 0)
                await self._clamp_selection_to_bounds(max_row, max_widget)
            else:
                # In status focus: normal widget navigation
                await self.state.move_selection(1, 0)
                await self._clamp_selection_to_bounds(max_row, max_widget)
        elif key == "left":
            if is_edit_mode:
                # In edit mode: move to previous slot or widget
                await self.state.move_selection_with_slot(0, -1)
                await self._clamp_selection_to_bounds(max_row, max_widget)
            else:
                # In status focus: move to previous widget
                await self.state.move_selection(0, -1)
                await self._clamp_selection_to_bounds(max_row, max_widget)
        elif key == "right":
            if is_edit_mode:
                # In edit mode: move to next slot or widget
                await self.state.move_selection_with_slot(0, 1)
                await self._clamp_selection_to_bounds(max_row, max_widget)
            else:
                # In status focus: move to next widget
                await self.state.move_selection(0, 1)
                await self._clamp_selection_to_bounds(max_row, max_widget)
        elif key == "home":
            # Jump to first position
            old_row, old_widget = await self.state.get_selection()
            if is_edit_mode:
                # In edit mode: go to first slot
                await self.state.set_slot_selection(0, 0)
            else:
                # In status focus: go to first widget
                await self.state.set_selection(0, 0)
            await self._emit_widget_selected(old_row, old_widget, 0, 0, key)
            await self.render_navigation_state()
            return True
        elif key == "end":
            # Jump to last position
            old_row, old_widget = await self.state.get_selection()
            if is_edit_mode:
                # In edit mode: go to last slot
                # Slot index = number of widgets + 1 (slot after last widget)
                row, _ = await self.state.get_selection()
                row_obj = self._get_row_at_index(row, include_hidden=True)
                if row_obj and hasattr(row_obj, "widgets"):
                    num_widgets = len(row_obj.widgets)
                    await self.state.set_slot_selection(row, num_widgets)
                else:
                    # Fallback: go to last row, first slot
                    await self.state.set_slot_selection(max_row, 0)
            else:
                # In status focus: go to last widget
                await self.state.set_selection(max_row, max_widget)
            await self._emit_widget_selected(old_row, old_widget, max_row, max_widget, key)
            await self.render_navigation_state()
            return True
        else:
            return False

        # Render updated selection
        await self.render_navigation_state()

        return True

    async def _get_active_widget_info(self, widget_id: str) -> Optional[Dict[str, Any]]:
        """Get widget info for an active widget by ID.

        Args:
            widget_id: Widget ID to look up

        Returns:
            Widget info dict or None if not found
        """
        if not self.widget_registry:
            return None

        status_widget = self.widget_registry.get(widget_id)
        if status_widget:
            return {
                "id": widget_id,
                "interaction_type": getattr(status_widget, "interaction_type", "none"),
                "command": getattr(status_widget, "command", None),
                "on_activate": getattr(status_widget, "on_activate", None),
                "states": getattr(status_widget, "states", None),
            }
        return None

    async def _handle_toggle_arrow_key(self, key: str, widget_info: Dict[str, Any]) -> bool:
        """Handle arrow key when toggle widget is active.

        Args:
            key: Key identifier ("up", "down", "left", "right", "home", "end")
            widget_info: Widget information dictionary

        Returns:
            True if key was handled, False otherwise
        """
        widget_id = widget_info.get("id", "")

        # Left/Right: cycle states
        if key == "left":
            logger.info(f"Toggle {widget_id}: cycling to previous state")
            # Record state snapshot before toggle
            await self._record_action()
            await self._cycle_toggle_state(widget_info, direction="prev")
            await self.render_navigation_state()
            return True
        elif key == "right":
            logger.info(f"Toggle {widget_id}: cycling to next state")
            # Record state snapshot before toggle
            await self._record_action()
            await self._cycle_toggle_state(widget_info, direction="next")
            await self.render_navigation_state()
            return True
        # Up/Down: exit toggle interaction, resume normal navigation
        elif key in ("up", "down"):
            logger.info(f"Exiting toggle interaction for {widget_id}")
            await self.state.deactivate_widget()
            await self.render_navigation_state()
            return True
        # Home/End: exit toggle interaction and jump
        elif key in ("home", "end"):
            await self.state.deactivate_widget()
            # Let the normal handler process home/end after deactivation
            return False

        return False

    async def _cycle_toggle_state(self, widget_info: Dict[str, Any], direction: str = "next") -> Optional[str]:
        """Cycle toggle widget state.

        Args:
            widget_info: Widget information dictionary
            direction: "next" or "prev"

        Returns:
            New state, or None if error
        """
        widget_id = widget_info.get("id", "")
        handler = widget_info.get("on_activate")

        if not handler:
            logger.warning(f"Toggle widget {widget_id} has no activation handler")
            return None

        try:
            # Get current widget position for config persistence
            row, widget_index = await self.state.get_selection()

            # Store direction and position in interaction_data for the handler to use
            await self.state.set_interaction_data({
                "direction": direction,
                "row_id": row,
                "widget_index": widget_index
            })

            context = self._get_widget_context()
            result = await handler(widget_id, context)

            # Emit action executed event
            new_state = result.get("new_state") if isinstance(result, dict) else None
            await self._emit_action_executed(widget_id, "toggle", result)

            return new_state

        except Exception as e:
            logger.error(f"Error cycling toggle state for {widget_id}: {e}", exc_info=True)
            await self._emit_action_executed(widget_id, "toggle", None, error=str(e))
            return None

    async def handle_enter_key(self) -> bool:
        """Handle Enter key to activate selected widget or add widget at slot.

        In EDIT mode:
        - On slot: Show widget picker to insert at that slot position
        - On widget: Activate widget (for testing)

        In STATUS_FOCUS mode:
        - On widget: Activate widget (modal, toggle, etc.)

        Returns:
            True if action was handled, False otherwise
        """
        if not await self.state.is_active():
            return False

        # Don't activate if already interacting
        if await self.state.is_interacting():
            logger.debug("Widget interaction already active")
            return False

        # Check current mode and selection type
        current_mode = await self.state.get_mode()
        row, selection_type, index = await self.state.get_full_selection()

        # In edit mode, handle slots specially
        if current_mode == NavigationMode.EDIT:
            if selection_type == SelectionType.SLOT:
                # On slot: show widget picker to insert at slot position
                logger.info(f"Enter on slot at row={row}, slot_index={index}")
                return await self.handle_add_widget_at_slot()
            # Fall through to widget activation for widgets in edit mode

        # Get selected widget info (for widget selection)
        if selection_type == SelectionType.WIDGET:
            widget_idx = index
            widget_info = self._get_widget_at_position(row, widget_idx)

            if not widget_info:
                logger.warning(f"No widget at position ({row}, {widget_idx})")
                return False

            widget_id = widget_info.get("id")
            logger.info(f"Activating widget: {widget_id}")

            # Emit activation event
            await self.event_bus.emit_with_hooks(
                EventType.WIDGET_ACTIVATED,
                {
                    "widget_id": widget_id,
                    "row": row,
                    "widget_index": widget_idx,
                    "interaction_type": widget_info.get("interaction_type", "none")
                },
                "StatusNavigationManager.handle_enter_key"
            )

            # Mark widget as active
            await self.state.activate_widget(widget_id)

            # Trigger widget activation
            await self.activate_widget(widget_info)

            return True

        return False

    async def handle_space_key(self) -> bool:
        """Handle Space key for quick toggle (toggle widgets only).

        Returns:
            True if toggle was handled, False otherwise
        """
        if not await self.state.is_active():
            return False

        # Get selected widget info
        row, widget_idx = await self.state.get_selection()
        widget_info = self._get_widget_at_position(row, widget_idx)

        if not widget_info:
            return False

        # Only toggle if widget is toggle-type
        if widget_info.get("interaction_type") == "toggle":
            widget_id = widget_info.get("id")
            logger.info(f"Quick toggle widget: {widget_id}")

            # Record state snapshot before toggle
            await self._record_action()

            # For Space quick-toggle: cycle state directly (no interactive mode)
            # This is different from Enter which puts widget in interactive mode for arrows
            await self._cycle_toggle_state(widget_info, direction="next")

            # Re-render to show updated state
            await self.render_navigation_state()

            return True

        return False

    async def handle_escape_key(self) -> bool:
        """Handle ESC key - always exits to Input Mode.

        This implements single-level exit: ESC from Navigation Mode
        returns to Input Mode (not to a parent navigation state).

        Returns:
            True if navigation was exited
        """
        if await self.state.is_active():
            await self.exit_navigation_mode()
            return True
        return False

    async def _handle_digit_key(self, digit: int) -> bool:
        """Quick jump to widget at position (digit-1) in current row.

        Handles digit keys 1-9 for quick navigation to widgets within the
        currently selected row. Invalid widget positions are handled
        gracefully - the key is consumed but no jump occurs.

        Args:
            digit: Digit key pressed (1-9)

        Returns:
            True if key was handled (always returns True to consume the key)
        """
        if not await self.state.is_active():
            return False

        # Calculate widget index (1-9 maps to 0-8)
        widget_idx = digit - 1

        # Get current row
        current_row, _ = await self.state.get_selection()
        row_obj = self._get_row_at_index(current_row, include_hidden=False)

        if not row_obj or not hasattr(row_obj, "widgets"):
            logger.debug(f"[NAV] Cannot jump to widget {digit}: row {current_row} not found")
            # Consume the key anyway to prevent exiting navigation mode
            return True

        # Check if widget index is valid for this row
        if widget_idx < 0 or widget_idx >= len(row_obj.widgets):
            logger.debug(
                f"[NAV] Cannot jump to widget {digit}: "
                f"row {current_row} has {len(row_obj.widgets)} widgets"
            )
            # Consume the key anyway to prevent exiting navigation mode
            return True

        # Jump to widget
        old_row, old_widget = await self.state.get_selection()
        await self.state.set_widget_selection(current_row, widget_idx)

        # Ensure selection type is WIDGET
        await self.state.set_selection_type(SelectionType.WIDGET)

        logger.info(
            f"[NAV] Quick jump to widget {digit}: "
            f"row={current_row}, widget={widget_idx}"
        )

        # Emit event and render
        await self._emit_widget_selected(old_row, old_widget, current_row, widget_idx, f"digit_{digit}")
        await self.render_navigation_state()

        return True

    # =========================================================================
    # LAYOUT EDITING METHODS
    # =========================================================================

    async def handle_add_widget(self) -> bool:
        """Handle 'a' or '+' key - show widget picker with row selection.

        Shows a modal with available widgets and allows Tab to change target row.

        Returns:
            True if widget was added, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot add widget - no layout manager")
            return False

        # Get current row index for initial selection
        current_row_idx, _ = await self.state.get_selection()

        # Force reload layout from config for accurate visible rows
        fresh_layout = self.layout_manager.reload()

        # Get all visible rows for Tab cycling
        visible_rows = []
        if fresh_layout and hasattr(fresh_layout, "rows"):
            visible_rows = [r for r in fresh_layout.rows if getattr(r, "visible", True)]

        if not visible_rows:
            logger.warning("No visible rows to add widget to")
            return False

        # Get all available widgets from registry
        available_widgets = []
        if self.widget_registry:
            for widget in self.widget_registry.get_all():
                widget_id = widget.id
                desc = getattr(widget, "description", widget_id)
                available_widgets.append({
                    "id": widget_id,
                    "label": widget_id,
                    "description": desc,
                })

        if not available_widgets:
            logger.info("No available widgets to add")
            return False

        # Show widget picker modal with row selection
        result = await self._show_widget_picker_modal(
            available_widgets,
            "Add Widget",
            initial_row_idx=current_row_idx,
            visible_rows=visible_rows
        )

        if result:
            widget_info = result.get('widget', {})
            widget_id = widget_info.get("id")
            target_row_id = result.get('target_row_id')

            if widget_id and target_row_id:
                # Add widget to target row using layout manager
                success = self.layout_manager.add_widget_to_row(target_row_id, widget_id)
                if success:
                    # Save and refresh
                    self.layout_manager.save()
                    self.layout = self.layout_manager.get_layout()
                    await self.render_navigation_state()
                    logger.info(f"Added widget '{widget_id}' to row {target_row_id}")
                    return True

        return False

    async def handle_remove_widget(self) -> bool:
        """Handle 'd' or '-' key - remove currently selected widget.

        Shows confirmation modal if deleting the last widget in a row.

        Returns:
            True if widget was removed, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot remove widget - no layout manager")
            return False

        # Get full selection info including type
        row, selection_type, index = await self.state.get_full_selection()
        logger.debug(f"handle_remove_widget: row={row}, type={selection_type.value}, index={index}")

        # Can only delete when a widget is selected, not a slot
        if selection_type == SelectionType.SLOT:
            logger.info("Cannot remove - slot is selected, not a widget")
            return False

        widget_idx = index  # Use the index from full selection
        current_row = self._get_row_at_index(row, include_hidden=True)
        if not current_row:
            logger.warning(f"Cannot remove widget - row {row} not found")
            return False

        # Get widget info for confirmation (include hidden rows for edit mode)
        widget_info = self._get_widget_at_position(row, widget_idx, include_hidden=True)
        if not widget_info:
            logger.warning(f"No widget at position ({row}, {widget_idx})")
            return False

        widget_id = widget_info.get("id", "unknown")
        row_id = getattr(current_row, "id", row + 1)

        # Record action for undo BEFORE making the change
        # Get widget config before deletion - DEEP COPY to preserve state
        # Record state snapshot before deletion
        await self._record_action()

        # Remove widget from row
        success = self.layout_manager.remove_widget_from_row(row_id, widget_idx)
        if success:
            # Save and refresh
            self.layout_manager.save()
            self.layout = self.layout_manager.get_layout()

            # Adjust selection if needed
            # Use include_hidden=True for edit mode (hidden rows are visible)
            max_row, max_widget = self._get_layout_bounds(include_hidden=True)

            # Adjust selection if widget index is now out of bounds
            # Use >= to handle case where deleting only widget (0 >= 0 is True)
            if widget_idx >= max_widget and max_widget >= 0:
                # Move selection to last valid widget index
                await self.state.set_selection(row, max(0, max_widget))
            elif max_widget < 0:
                # Row is now empty, move selection to slot instead
                await self.state.set_selection_type(SelectionType.SLOT)
                # Ensure slot index is valid (will be 0 for empty row)
                await self.state.set_slot_index(row, 0)

            await self.render_navigation_state()
            logger.info(f"Removed widget '{widget_id}' from row {row_id}")
            return True

        return False

    async def handle_add_row(self) -> bool:
        """Handle 'A' (Shift+A) key - add a new visible row.

        Returns:
            True if row was added, False otherwise
        """
        logger.info("[NAV] handle_add_row called")
        if not self.layout_manager:
            logger.warning("[NAV] Cannot add row - no layout manager")
            return False

        logger.info(f"[NAV] Current rows: {len(self.layout_manager.get_layout().rows)}")

        # Add new row using layout manager
        new_row_id = self.layout_manager.add_new_row()
        logger.info(f"[NAV] add_new_row returned: {new_row_id}")

        if new_row_id:
            # Make the new row visible
            vis_result = self.layout_manager.set_row_visibility(new_row_id, True)
            logger.info(f"[NAV] set_row_visibility({new_row_id}, True) returned: {vis_result}")
            # Save and refresh
            save_result = self.layout_manager.save()
            logger.info(f"[NAV] save() returned: {save_result}")
            self.layout = self.layout_manager.get_layout()
            await self.render_navigation_state()
            logger.info(f"[NAV] Added new row {new_row_id}")
            return True
        else:
            logger.info("[NAV] Cannot add row - at maximum (6)")
            return False

    async def handle_remove_row(self) -> bool:
        """Handle 'R' (Shift+R) key - hide current row (only if empty).

        Returns:
            True if row was hidden, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot remove row - no layout manager")
            return False

        # Get current row
        row, _ = await self.state.get_selection()
        current_row = self._get_row_at_index(row)
        if not current_row:
            logger.warning(f"Cannot remove row - row {row} not found")
            return False

        row_id = getattr(current_row, "id", row + 1)

        # Check if row has widgets
        if hasattr(current_row, "widgets") and current_row.widgets:
            logger.info(f"Cannot hide row {row_id} - remove widgets first")
            return False

        # Hide the row
        success = self.layout_manager.set_row_visibility(row_id, False)
        if success:
            # Save and refresh
            self.layout_manager.save()
            self.layout = self.layout_manager.get_layout()

            # Adjust selection if needed
            max_row, _ = self._get_layout_bounds()
            if row > max_row:
                await self.state.set_selection(max(0, max_row), 0)

            await self.render_navigation_state()
            logger.info(f"Hidden row {row_id}")
            return True

        return False

    async def _record_action(self) -> None:
        """Record current state snapshot before any action.

        Uses Memento Pattern - captures full state so no per-action undo logic needed.
        """
        snapshot = self.layout_manager.get_config_snapshot()
        undo_action = UndoAction(
            action_type="snapshot",  # All actions use snapshot approach now
            data={"config": snapshot},
            timestamp=time.time()
        )
        await self.state.push_undo(undo_action)
        logger.debug("Recorded state snapshot for undo")

    async def handle_undo(self) -> bool:
        """Handle Ctrl+Z - undo last action.

        Restores previous state snapshot (Memento Pattern).
        Uses lock to prevent race conditions.

        Returns:
            True if action was undone, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot undo - no layout manager")
            return False

        # Check if we're in navigation mode before allowing undo
        current_mode = await self.state.get_mode()
        if current_mode == NavigationMode.INPUT:
            logger.info("Undo only available in navigation mode")
            return False

        # Acquire lock for entire undo operation
        async with self.state._lock:
            action = await self.state.pop_undo()
            if not action:
                logger.info("Nothing to undo")
                return False

            logger.info("Undoing last action - restoring state snapshot")

            try:
                # Restore layout config from snapshot
                snapshot = action.data.get("config")
                if snapshot:
                    self.layout_manager.restore_config_snapshot(snapshot)
                    self.layout = self.layout_manager.get_layout()
                    
                    # Save and refresh
                    self.layout_manager.save()
                    await self.render_navigation_state()
                    logger.info("Undo completed - state restored")
                    return True
                else:
                    logger.error("Undo snapshot missing config data")
                    return False

            except Exception as e:
                logger.error(f"Error during undo: {e}", exc_info=True)
                return False

    async def _undo_color(self, data: Dict[str, Any]) -> None:
        """Restore widget color.

        Args:
            data: Contains row_id, widget_idx, and previous_color
        """
        row_id = data['row_id']
        widget_idx = data['widget_idx']
        previous_color = data['previous_color']

        row = self.layout_manager.get_row(row_id)
        if row and widget_idx < len(row.widgets):
            row.widgets[widget_idx].color = previous_color
            logger.info(f"Restored color for widget {widget_idx} in row {row_id} to {previous_color}")

    async def _undo_delete(self, data: Dict[str, Any]) -> None:
        """Restore deleted widget.

        Args:
            data: Contains row_id, widget_idx, and widget_config (deleted widget)
        """
        row_id = data['row_id']
        widget_idx = data['widget_idx']
        widget_config = data['widget_config']

        row = self.layout_manager.get_row(row_id)
        if row:
            row.widgets.insert(widget_idx, widget_config)
            logger.info(f"Restored deleted widget at index {widget_idx} in row {row_id}")

    async def _undo_add(self, data: Dict[str, Any]) -> None:
        """Remove added widget.

        Args:
            data: Contains row_id and widget_idx (position of added widget)
        """
        row_id = data['row_id']
        widget_idx = data['widget_idx']

        row = self.layout_manager.get_row(row_id)
        if row and widget_idx < len(row.widgets):
            removed_widget = row.widgets.pop(widget_idx)
            logger.info(f"Removed added widget at index {widget_idx} in row {row_id}: {removed_widget.id}")

    async def _undo_toggle(self, data: Dict[str, Any]) -> None:
        """Restore toggle state.

        Args:
            data: Contains row_id, widget_idx, and previous_state
        """
        row_id = data['row_id']
        widget_idx = data['widget_idx']
        previous_state = data['previous_state']

        row = self.layout_manager.get_row(row_id)
        if row and widget_idx < len(row.widgets):
            widget = row.widgets[widget_idx]
            # Use 'toggle_state' key (correct key for toggle widgets)
            if 'toggle_state' in widget.config:
                widget.config['toggle_state'] = previous_state
                logger.info(f"Restored toggle state for widget {widget_idx} in row {row_id} to {previous_state}")
            # Fallback to 'state' key for compatibility
            elif 'state' in widget.config:
                widget.config['state'] = previous_state
                logger.info(f"Restored state for widget {widget_idx} in row {row_id} to {previous_state}")

    async def _undo_edit(self, data: Dict[str, Any]) -> None:
        """Restore widget config after edit.

        Args:
            data: Contains row_id, widget_idx, and previous_config
        """
        row_id = data['row_id']
        widget_idx = data['widget_idx']
        previous_config = data['previous_config']

        row = self.layout_manager.get_row(row_id)
        if row and widget_idx < len(row.widgets):
            row.widgets[widget_idx].config = previous_config.copy()
            logger.info(f"Restored config for widget {widget_idx} in row {row_id}")

    async def _undo_reorder(self, data: Dict[str, Any]) -> None:
        """Restore widget order after reorder.

        Args:
            data: Contains row_id, old_idx, and new_idx
        """
        row_id = data['row_id']
        old_idx = data['old_idx']
        new_idx = data['new_idx']

        row = self.layout_manager.get_row(row_id)
        if row and 0 <= new_idx < len(row.widgets):
            # Move widget back from new_idx to old_idx
            widget = row.widgets.pop(new_idx)
            row.widgets.insert(old_idx, widget)
            logger.info(f"Restored widget order in row {row_id}: moved from {new_idx} back to {old_idx}")

    # =========================================================================
    # MODE TRANSITION METHODS (for Edit Mode UI spec)
    # =========================================================================

    async def transition_to_status_focus(self) -> bool:
        """Transition from INPUT to STATUS_FOCUS mode (Tab key handler).

        Returns:
            True if successfully transitioned to STATUS_FOCUS mode
        """
        logger.info("[NAV] transition_to_status_focus called")

        # Check if already in STATUS_FOCUS or EDIT mode
        current_mode = await self.state.get_mode()
        if current_mode != NavigationMode.INPUT:
            logger.debug(f"Already in {current_mode.value} mode, not transitioning to STATUS_FOCUS")
            return False

        # Check if LLM is streaming or messages are queued
        if self.coordinator.is_writing_messages():
            logger.warning("Cannot enter navigation during message display")
            return False

        # Check if in alternate buffer (modal/fullscreen active)
        if self.coordinator._in_alternate_buffer:
            logger.warning("Cannot enter navigation during modal/fullscreen mode")
            return False

        # Transition to STATUS_FOCUS mode
        await self.state.transition_to_status_focus()

        # Signal coordinator: navigation active, pause message renders
        self.coordinator.set_navigation_active(True)

        # Render initial selection
        await self.render_navigation_state()

        logger.info("Transitioned to STATUS_FOCUS mode")
        return True

    async def transition_to_edit_mode(self) -> bool:
        """Transition from STATUS_FOCUS to EDIT mode ('e' key handler).

        Returns:
            True if successfully transitioned to EDIT mode
        """
        logger.info("[NAV] transition_to_edit_mode called")

        # Check if in STATUS_FOCUS mode
        current_mode = await self.state.get_mode()
        if current_mode != NavigationMode.STATUS_FOCUS:
            logger.warning(f"Cannot transition to EDIT mode from {current_mode.value}")
            return False

        # Transition to EDIT mode
        await self.state.transition_to_edit_mode()

        # Render edit mode state
        await self.render_navigation_state()

        logger.info("Transitioned to EDIT mode")
        return True

    async def transition_to_input_mode(self) -> None:
        """Transition from any mode to INPUT mode (Esc key handler).

        Implements cascading exit:
        - EDIT -> STATUS_FOCUS (first Esc)
        - STATUS_FOCUS -> INPUT (second Esc)

        For single-level exit from any mode directly to INPUT, use
        exit_navigation_mode() instead.
        """
        current_mode = await self.state.get_mode()

        if current_mode == NavigationMode.EDIT:
            # First Esc: EDIT -> STATUS_FOCUS
            logger.info("[NAV] Esc in EDIT mode -> transitioning to STATUS_FOCUS")
            await self.state.transition_to_status_focus_from_edit()
            await self.render_navigation_state()
        elif current_mode == NavigationMode.STATUS_FOCUS:
            # Second Esc: STATUS_FOCUS -> INPUT
            logger.info("[NAV] Esc in STATUS_FOCUS mode -> transitioning to INPUT")
            await self.state.transition_to_input_mode()

            # Signal coordinator: navigation done, resume message renders
            self.coordinator.set_navigation_active(False)

            # Restore normal rendering
            await self.renderer.render_active_area()

            logger.info("Transitioned to INPUT mode")
        else:
            logger.debug(f"Already in INPUT mode, ignoring Esc")

    async def _enter_edit_mode(self) -> bool:
        """Internal handler for 'e' key - alias for transition_to_edit_mode."""
        return await self.transition_to_edit_mode()

    # =========================================================================
    # EDIT MODE SPECIFIC METHODS
    # =========================================================================

    async def handle_add_widget_at_slot(self) -> bool:
        """Handle 'a' or '+' key in edit mode, or Enter on slot - add widget at selected slot.

        Shows WidgetPickerModal to browse and select from available widgets.

        Returns:
            True if widget was added, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot add widget - no layout manager")
            return False

        if not self.widget_registry:
            logger.warning("Cannot add widget - no widget registry")
            return False

        # Get current selection
        row, selection_type, index = await self.state.get_full_selection()

        if selection_type != SelectionType.SLOT:
            logger.info("Must select a slot to add widget (use arrows to move to +)")
            return False

        # Get row ID for insertion
        current_row = self._get_row_at_index(row)
        row_id = getattr(current_row, "id", row + 1)

        # Show widget picker modal using proper WidgetPickerModal class
        from .widget_picker import WidgetPickerModal

        picker = WidgetPickerModal(
            widget_registry=self.widget_registry,
            layout_manager=self.layout_manager,
            row_id=row_id,
            slot_index=index,
            renderer=self.renderer
        )

        # Show picker using modal system
        selected_widget_id = await self._show_widget_picker(picker)

        if selected_widget_id:
            # Record state snapshot before adding
            await self._record_action()

            # Insert widget at slot position (index = slot position)
            success = self.layout_manager.insert_widget_at_position(row_id, selected_widget_id, index)
            if success:
                # Save and refresh
                self.layout_manager.save()
                self.layout = self.layout_manager.get_layout()
                await self.render_navigation_state()
                logger.info(f"Added widget '{selected_widget_id}' to row {row_id} at slot {index}")
                return True

        return False

    async def handle_toggle_widget_color(self) -> bool:
        """Handle 'c' key in edit mode - toggle widget background color.

        Returns:
            True if color was toggled, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot toggle color - no layout manager")
            return False

        # Get current selection
        row, selection_type, index = await self.state.get_full_selection()

        if selection_type != SelectionType.WIDGET:
            logger.info("Must select a widget to toggle color")
            return False

        # Get row and widget
        current_row = self._get_row_at_index(row)
        if not current_row or not hasattr(current_row, "widgets"):
            return False

        if index >= len(current_row.widgets):
            return False

        widget_config = current_row.widgets[index]
        row_id = getattr(current_row, "id", row + 1)

        # Toggle through color cycle: None -> dark[0] -> dark[1] -> primary[0] -> secondary[0] -> None
        current_color = getattr(widget_config, "color", "none")
        color_cycle = WIDGET_BG_COLOR_NAMES
        try:
            current_idx = color_cycle.index(current_color)
            new_color = color_cycle[(current_idx + 1) % len(color_cycle)]
        except ValueError:
            new_color = "dark0"

        logger.info(f"[COLOR TOGGLE] row={row_id}, widget_idx={index}, current='{current_color}', new='{new_color}'")

        # Record state snapshot before color change
        await self._record_action()

        # Update widget color
        success = self.layout_manager.set_widget_color(row_id, index, new_color)
        if success:
            # Verify the color was actually updated
            updated_widget = current_row.widgets[index]
            logger.info(f"[COLOR TOGGLE] After update: widget_config.color='{updated_widget.color}'")
            # Save and refresh
            self.layout_manager.save()
            self.layout = self.layout_manager.get_layout()
            await self.render_navigation_state()
            logger.info(f"Toggled widget color to {new_color}")
            return True

        return False

    async def handle_toggle_widget_effect(self) -> bool:
        """Handle 'x' key in edit mode - toggle widget visual effect.

        Cycles through: none -> shimmer -> pulse -> none

        Returns:
            True if effect was toggled, False otherwise
        """
        if not self.layout_manager:
            logger.warning("Cannot toggle effect - no layout manager")
            return False

        # Get current selection
        row, selection_type, index = await self.state.get_full_selection()

        if selection_type != SelectionType.WIDGET:
            logger.info("Must select a widget to toggle effect")
            return False

        # Get row and widget
        current_row = self._get_row_at_index(row)
        if not current_row or not hasattr(current_row, "widgets"):
            return False

        if index >= len(current_row.widgets):
            return False

        widget_config = current_row.widgets[index]
        row_id = getattr(current_row, "id", row + 1)

        # Toggle through effect cycle: none -> shimmer -> pulse -> none
        from core.io.status.constants import WIDGET_EFFECT_NAMES
        current_effect = getattr(widget_config, "effect", "none")
        effect_cycle = WIDGET_EFFECT_NAMES
        try:
            current_idx = effect_cycle.index(current_effect)
            new_effect = effect_cycle[(current_idx + 1) % len(effect_cycle)]
        except ValueError:
            new_effect = "shimmer"

        logger.info(f"[EFFECT TOGGLE] row={row_id}, widget_idx={index}, current='{current_effect}', new='{new_effect}'")

        # Record state snapshot before effect change
        await self._record_action()

        # Update widget effect using layout manager
        success = self.layout_manager.cycle_widget_effect(row_id, index)
        if success:
            # Save and refresh
            self.layout_manager.save()
            self.layout = self.layout_manager.get_layout()

            # Start or stop animation based on whether any widgets have effects
            if self._has_widgets_with_effects():
                await self._start_effect_animation()
            else:
                await self._stop_effect_animation()

            await self.render_navigation_state()
            logger.info(f"Toggled widget effect to {new_effect}")
            return True

        return False

    async def _show_widget_picker_modal_simple(
        self,
        widgets: List[Dict[str, Any]]
    ) -> Optional[Dict[str, Any]]:
        """Show a simplified modal to pick a widget (no row selection).

        Uses MessageDisplayCoordinator for proper buffer lifecycle management.

        Args:
            widgets: List of widget info dicts with id, label, description

        Returns:
            Widget dict with 'id' or None if cancelled
        """
        import sys
        import os
        import re
        import select as select_module

        from core.ui.design_system import T, S, solid, solid_fg
        from core.io.terminal_state import get_terminal_size

        def strip_ansi(text):
            return re.sub(r'\033\[[0-9;]*m', '', text)

        # Use coordinator for proper render state management
        self.coordinator.enter_alternate_buffer()

        selected = None
        try:
            term_width, term_height = get_terminal_size()

            # Calculate modal dimensions
            modal_width = min(60, term_width - 4)
            modal_height = len(widgets) + 5  # title bar + title + widgets + footer bar + footer

            start_col = max(1, (term_width - modal_width) // 2)
            start_row = max(1, (term_height - modal_height) // 2)

            # Enter ANSI alternate buffer for clean modal display
            sys.stdout.write('\033[?1049h')  # Enter alternate screen buffer
            sys.stdout.write('\033[?25l')    # Hide cursor
            sys.stdout.flush()

            selected_idx = 0

            def render_picker(sel_idx: int) -> None:
                sys.stdout.write('\033[2J')  # Clear screen

                current_row = start_row

                # Title bar (top border)
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                sys.stdout.write(solid_fg("▄" * modal_width, T().primary[0]))
                current_row += 1

                # Title
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                title_line = solid(f"  {S.BOLD}Add Widget{S.RESET_BOLD}", T().primary[0], T().text, modal_width)
                sys.stdout.write(title_line)
                current_row += 1

                # Widget options
                for i, w in enumerate(widgets):
                    label = w.get("label", w.get("id", ""))
                    desc = w.get("description", "")
                    desc_part = f" - {desc}" if desc else ""

                    if i == sel_idx:
                        indicator = f"> {S.BOLD}{label}{S.RESET_BOLD}"
                        fg_color = T().text
                        bg_color = T().primary[0]
                    else:
                        indicator = f"  {label}"
                        fg_color = T().text_dim
                        bg_color = T().dark[0]

                    opt_line = f"  {indicator}{desc_part}"
                    # Truncate if needed
                    max_line_width = modal_width - 4
                    visible_len = len(strip_ansi(opt_line))
                    if visible_len > max_line_width:
                        opt_line = opt_line[:max_line_width - 3] + "..."

                    sys.stdout.write(f'\033[{current_row};{start_col}H')
                    sys.stdout.write(solid(opt_line, bg_color, fg_color, modal_width))
                    current_row += 1

                # Footer bar (bottom border)
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                sys.stdout.write(solid_fg("▀" * modal_width, T().primary[0]))
                current_row += 1

                # Footer text
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                footer = "  ↑↓ select · enter add · esc cancel"
                sys.stdout.write(solid(footer, T().dark[0], T().text_dim, modal_width))

                sys.stdout.flush()

            # Initial render
            render_picker(selected_idx)

            # Input loop - read bytes directly for escape sequences
            fd = sys.stdin.fileno()

            def read_key():
                """Read a key, handling escape sequences."""
                char = os.read(fd, 1).decode('utf-8', errors='ignore')
                if not char:
                    return ''

                if char == '\x1b':
                    # Use 0.1s timeout to handle tmux/SSH latency
                    readable, _, _ = select_module.select([fd], [], [], 0.1)
                    if readable:
                        char2 = os.read(fd, 1).decode('utf-8', errors='ignore')
                        if char2 == '[':
                            readable2, _, _ = select_module.select([fd], [], [], 0.1)
                            if readable2:
                                char3 = os.read(fd, 1).decode('utf-8', errors='ignore')
                                return f'ESC[{char3}'
                            return 'ESC['
                        return f'ESC{char2}'
                    return 'ESC'
                return char

            while True:
                key = read_key()

                if key == 'ESC[A':  # Up arrow
                    selected_idx = (selected_idx - 1) % len(widgets)
                    render_picker(selected_idx)
                elif key == 'ESC[B':  # Down arrow
                    selected_idx = (selected_idx + 1) % len(widgets)
                    render_picker(selected_idx)
                elif key == 'ESC':  # Standalone ESC - cancel
                    break
                elif key == '\r' or key == '\n':
                    selected = widgets[selected_idx]
                    break
                elif key == 'j':  # vim down
                    selected_idx = (selected_idx + 1) % len(widgets)
                    render_picker(selected_idx)
                elif key == 'k':  # vim up
                    selected_idx = (selected_idx - 1) % len(widgets)
                    render_picker(selected_idx)
                elif key == 'q':  # q to quit
                    break
                elif len(key) == 1 and key.isdigit():
                    idx = int(key) - 1
                    if 0 <= idx < len(widgets):
                        selected = widgets[idx]
                        break

        except Exception as e:
            logger.debug(f"Widget picker error: {e}")

        finally:
            # Exit ANSI alternate buffer and show cursor
            sys.stdout.write('\033[?25h')    # Show cursor
            sys.stdout.write('\033[?1049l')  # Exit alternate screen buffer
            sys.stdout.flush()

            # Reset render state via coordinator (CRITICAL for clean recovery)
            self.coordinator.exit_alternate_buffer(restore_state=True)

        return selected

    async def _show_widget_picker(self, picker: Any) -> Optional[str]:
        """Show WidgetPickerModal and return selected widget ID.

        Uses MessageDisplayCoordinator for proper buffer lifecycle management.

        Args:
            picker: WidgetPickerModal instance

        Returns:
            Widget ID if selected, None if cancelled
        """
        import sys
        import os
        import select as select_module

        # Use coordinator for proper render state management
        self.coordinator.enter_alternate_buffer()

        try:
            # Enter ANSI alternate buffer for clean modal display
            sys.stdout.write('\033[?1049h')  # Enter alternate screen buffer
            sys.stdout.write('\033[?25l')    # Hide cursor
            sys.stdout.flush()

            # Show picker
            picker.show()

            # Create a simple keypress class that WidgetPickerModal expects
            class KeyPress:
                def __init__(self, name=None, char=None):
                    self.name = name
                    self.char = char

            fd = sys.stdin.fileno()

            def read_key():
                """Read a key, handling escape sequences."""
                char = os.read(fd, 1).decode('utf-8', errors='ignore')
                if not char:
                    return None

                if char == '\x1b':
                    # Use 0.1s timeout to handle tmux/SSH latency
                    readable, _, _ = select_module.select([fd], [], [], 0.1)
                    if readable:
                        char2 = os.read(fd, 1).decode('utf-8', errors='ignore')
                        if char2 == '[':
                            readable2, _, _ = select_module.select([fd], [], [], 0.1)
                            if readable2:
                                char3 = os.read(fd, 1).decode('utf-8', errors='ignore')
                                # Map escape sequences
                                if char3 == 'A':
                                    return KeyPress(name='Up')
                                elif char3 == 'B':
                                    return KeyPress(name='Down')
                                elif char3 == 'D':
                                    return KeyPress(name='Left')
                                elif char3 == 'C':
                                    return KeyPress(name='Right')
                                elif char3 == 'H':
                                    return KeyPress(name='Home')
                                elif char3 == 'F':
                                    return KeyPress(name='End')
                                elif char3 in ('5', '6'):
                                    # Page Up/Down (may need another byte)
                                    readable3, _, _ = select_module.select([fd], [], [], 0.05)
                                    if readable3:
                                        char4 = os.read(fd, 1).decode('utf-8', errors='ignore')
                                        if char3 == '5' and char4 == '~':
                                            return KeyPress(name='PageUp')
                                        elif char3 == '6' and char4 == '~':
                                            return KeyPress(name='PageDown')
                                return KeyPress(name='Unknown')
                            return KeyPress(name='Escape')
                        return KeyPress(name='Escape')
                    return KeyPress(name='Escape')
                elif char == '\r' or char == '\n':
                    return KeyPress(name='Enter')
                elif char == '\x7f':
                    return KeyPress(name='Backspace')
                elif char == '\x01':
                    return KeyPress(name='Ctrl+U')
                elif char.isprintable():
                    return KeyPress(char=char)
                return None

            # Initial render
            lines = picker.render()
            for line in lines:
                sys.stdout.write('\033[K')  # Clear line
                sys.stdout.write(line + '\n')
            sys.stdout.flush()

            # Input loop
            while picker.is_visible():
                key = read_key()
                if key is None:
                    continue

                handled = picker.handle_keypress(key)

                if handled:
                    # Re-render
                    sys.stdout.write('\033[H')  # Move cursor to top
                    lines = picker.render()
                    for line in lines:
                        sys.stdout.write('\033[K')  # Clear line
                        sys.stdout.write(line + '\n')
                    sys.stdout.flush()

                # Check if picker was closed
                if not picker.is_visible():
                    break

        except Exception as e:
            logger.error(f"Widget picker error: {e}", exc_info=True)

        finally:
            # Exit ANSI alternate buffer and show cursor
            sys.stdout.write('\033[?25h')    # Show cursor
            sys.stdout.write('\033[?1049l')  # Exit alternate screen buffer
            sys.stdout.flush()

            # Reset render state via coordinator (CRITICAL for clean recovery)
            self.coordinator.exit_alternate_buffer(restore_state=True)

        # Return selected widget ID
        return picker.get_selected_widget()

    def _get_row_at_index(self, row_idx: int, include_hidden: bool = False) -> Optional[Any]:
        """Get row object at the given index.

        Args:
            row_idx: Row index (0-based)
            include_hidden: If True, include hidden rows (for edit mode)

        Returns:
            Row object or None
        """
        if not self.layout:
            return None

        try:
            if hasattr(self.layout, "rows"):
                if include_hidden:
                    # In edit mode, use all rows
                    rows = self.layout.rows
                else:
                    # Normal mode, only visible rows
                    rows = [r for r in self.layout.rows if getattr(r, "visible", True)]
                if 0 <= row_idx < len(rows):
                    return rows[row_idx]
        except Exception as e:
            logger.warning(f"Error getting row at index {row_idx}: {e}")

        return None

    async def _enter_edit_mode(self) -> bool:
        """Enter edit mode from STATUS_FOCUS.

        Shows insertion slots between widgets for precise widget placement.

        Returns:
            True if successfully entered edit mode
        """
        logger.info("[EDIT] Entering edit mode")
        await self.state.transition_to_edit_mode()
        await self.render_navigation_state()
        logger.info("Entered EDIT mode")
        return True

    async def _show_widget_picker_modal_with_row(
        self,
        widgets: List[Dict[str, Any]],
        title: str,
        initial_row_idx: int = 0,
        visible_rows: List[Any] = None
    ) -> Optional[Dict[str, Any]]:
        """Show a modal to pick a widget with row selection.

        Uses MessageDisplayCoordinator for proper buffer lifecycle management.

        Args:
            widgets: List of widget info dicts with id, label, description
            title: Modal title (base title, row info appended)
            initial_row_idx: Initial target row index
            visible_rows: List of visible row objects for Tab cycling

        Returns:
            Dict with 'widget' and 'target_row' or None if cancelled
        """
        import sys
        import os
        import re
        import select as select_module

        from core.ui.design_system import T, S, solid, solid_fg
        from core.io.terminal_state import get_terminal_size

        def strip_ansi(text):
            return re.sub(r'\033\[[0-9;]*m', '', text)

        # Use coordinator for proper render state management
        self.coordinator.enter_alternate_buffer()

        selected = None
        try:
            term_width, term_height = get_terminal_size()

            # Calculate modal dimensions
            modal_width = min(60, term_width - 4)
            modal_height = len(widgets) + 5  # title bar + title + widgets + footer bar + footer

            start_col = max(1, (term_width - modal_width) // 2)
            start_row = max(1, (term_height - modal_height) // 2)

            # Enter ANSI alternate buffer for clean modal display
            sys.stdout.write('\033[?1049h')  # Enter alternate screen buffer
            sys.stdout.write('\033[?25l')    # Hide cursor
            sys.stdout.flush()

            selected_idx = 0
            target_row_idx = initial_row_idx

            def get_row_id(idx):
                if visible_rows and 0 <= idx < len(visible_rows):
                    return getattr(visible_rows[idx], 'id', idx + 1)
                return idx + 1

            def render_picker(sel_idx: int, row_idx: int) -> None:
                sys.stdout.write('\033[2J')  # Clear screen

                current_row = start_row

                # Title bar (top border)
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                sys.stdout.write(solid_fg("▄" * modal_width, T().primary[0]))
                current_row += 1

                # Title with row info
                row_id = get_row_id(row_idx)
                num_rows = len(visible_rows) if visible_rows else 1
                row_info = f" (Tab: change row {row_idx + 1}/{num_rows})" if num_rows > 1 else ""
                full_title = f"Add Widget to Row {row_id}{row_info}"

                sys.stdout.write(f'\033[{current_row};{start_col}H')
                title_line = solid(f"  {S.BOLD}{full_title}{S.RESET_BOLD}", T().primary[0], T().text, modal_width)
                sys.stdout.write(title_line)
                current_row += 1

                # Widget options
                for i, w in enumerate(widgets):
                    label = w.get("label", w.get("id", ""))
                    desc = w.get("description", "")
                    desc_part = f" - {desc}" if desc else ""

                    if i == sel_idx:
                        indicator = f"> {S.BOLD}{label}{S.RESET_BOLD}"
                        fg_color = T().text
                        bg_color = T().primary[0]
                    else:
                        indicator = f"  {label}"
                        fg_color = T().text_dim
                        bg_color = T().dark[0]

                    opt_line = f"  {indicator}{desc_part}"
                    # Truncate if needed
                    max_line_width = modal_width - 4
                    visible_len = len(strip_ansi(opt_line))
                    if visible_len > max_line_width:
                        opt_line = opt_line[:max_line_width - 3] + "..."

                    sys.stdout.write(f'\033[{current_row};{start_col}H')
                    sys.stdout.write(solid(opt_line, bg_color, fg_color, modal_width))
                    current_row += 1

                # Footer bar (bottom border)
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                sys.stdout.write(solid_fg("▀" * modal_width, T().primary[0]))
                current_row += 1

                # Footer text
                sys.stdout.write(f'\033[{current_row};{start_col}H')
                footer = "  ↑↓ widget · Tab row · enter select · esc cancel"
                sys.stdout.write(solid(footer, T().dark[0], T().text_dim, modal_width))

                sys.stdout.flush()

            # Initial render
            render_picker(selected_idx, target_row_idx)

            # Input loop - read bytes directly for escape sequences
            fd = sys.stdin.fileno()

            def read_key():
                """Read a key, handling escape sequences."""
                # Read one byte
                char = os.read(fd, 1).decode('utf-8', errors='ignore')
                if not char:
                    return ''

                if char == '\x1b':
                    # Check if more bytes are available (arrow keys send 3 bytes quickly)
                    # Use 0.1s timeout to handle tmux/SSH latency
                    readable, _, _ = select_module.select([fd], [], [], 0.1)
                    if readable:
                        char2 = os.read(fd, 1).decode('utf-8', errors='ignore')
                        if char2 == '[':
                            readable2, _, _ = select_module.select([fd], [], [], 0.1)
                            if readable2:
                                char3 = os.read(fd, 1).decode('utf-8', errors='ignore')
                                return f'ESC[{char3}'
                            return 'ESC['
                        return f'ESC{char2}'
                    return 'ESC'
                return char

            while True:
                key = read_key()

                if key == 'ESC[A':  # Up arrow
                    selected_idx = (selected_idx - 1) % len(widgets)
                    render_picker(selected_idx, target_row_idx)
                elif key == 'ESC[B':  # Down arrow
                    selected_idx = (selected_idx + 1) % len(widgets)
                    render_picker(selected_idx, target_row_idx)
                elif key == '\t':  # Tab - cycle through rows
                    if visible_rows and len(visible_rows) > 1:
                        target_row_idx = (target_row_idx + 1) % len(visible_rows)
                        render_picker(selected_idx, target_row_idx)
                elif key == 'ESC':  # Standalone ESC - cancel
                    break
                elif key == '\r' or key == '\n':
                    selected = {
                        'widget': widgets[selected_idx],
                        'target_row_idx': target_row_idx,
                        'target_row_id': get_row_id(target_row_idx)
                    }
                    break
                elif key == 'j':  # vim down
                    selected_idx = (selected_idx + 1) % len(widgets)
                    render_picker(selected_idx, target_row_idx)
                elif key == 'k':  # vim up
                    selected_idx = (selected_idx - 1) % len(widgets)
                    render_picker(selected_idx, target_row_idx)
                elif key == 'q':  # q to quit
                    break
                elif len(key) == 1 and key.isdigit():
                    idx = int(key) - 1
                    if 0 <= idx < len(widgets):
                        selected = {
                            'widget': widgets[idx],
                            'target_row_idx': target_row_idx,
                            'target_row_id': get_row_id(target_row_idx)
                        }
                        break

        except Exception as e:
            logger.debug(f"Widget picker error: {e}")

        finally:
            # Exit ANSI alternate buffer and show cursor
            sys.stdout.write('\033[?25h')    # Show cursor
            sys.stdout.write('\033[?1049l')  # Exit alternate screen buffer
            sys.stdout.flush()

            # Reset render state via coordinator (CRITICAL for clean recovery)
            self.coordinator.exit_alternate_buffer(restore_state=True)

        return selected

    async def activate_widget(self, widget_info: Dict[str, Any]) -> None:
        """Activate widget (modal/toggle/edit) with coordinator awareness.

        Args:
            widget_info: Dictionary containing widget information including:
                - id: Widget ID
                - interaction_type: Type of interaction ("modal", "toggle", "inline_edit", "action")
                - on_activate: Optional async activation handler
        """
        widget_id = widget_info.get("id")
        interaction_type = widget_info.get("interaction_type", "none")
        command = widget_info.get("command")

        # For command widgets: execute the slash command
        if interaction_type == "command" and command:
            logger.info(f"[CMD] Executing command for widget {widget_id}: {command}")
            try:
                # Exit navigation mode first
                await self.exit_navigation_mode()

                # Get the app through the renderer chain to access command execution
                app = self._get_app()
                if app and hasattr(app, "input_handler") and app.input_handler:
                    # Note: attribute is _command_mode_handler (with underscore prefix)
                    cmd_handler = getattr(app.input_handler, "_command_mode_handler", None)
                    if cmd_handler and hasattr(cmd_handler, "execute_command_string"):
                        success = await cmd_handler.execute_command_string(command)
                        logger.info(f"[CMD] Command execution result: {success}")
                    else:
                        logger.warning(f"[CMD] Cannot execute command - _command_mode_handler not available")
                else:
                    logger.warning(f"[CMD] Cannot execute command - app.input_handler not available")

                # Emit action executed event
                await self._emit_action_executed(widget_id, interaction_type, {"command": command})
            except Exception as e:
                logger.error(f"[CMD] Error executing command {command}: {e}", exc_info=True)
            return

        # For modals: use coordinator's buffer management
        elif interaction_type == "modal":
            try:
                # Enter alternate buffer via coordinator (sync method)
                logger.debug(f"[MODAL] Entering alternate buffer for {widget_id}")
                self.coordinator.enter_alternate_buffer()
                logger.debug(f"[MODAL] Alternate buffer entered")

                # Call widget activation handler if present
                handler = widget_info.get("on_activate")
                result = None
                if handler:
                    context = self._get_widget_context()
                    logger.debug(f"[MODAL] Got context: {type(context)}")
                    result = await handler(widget_id, context)
                    logger.debug(f"[MODAL] Handler returned: {type(result)}")
                    await self._show_modal_result(result)
                    logger.debug(f"[MODAL] Modal result shown")

                # Emit action executed event
                await self._emit_action_executed(widget_id, interaction_type, result)
            except Exception as e:
                logger.error(f"[MODAL] Error activating widget {widget_id}: {e}", exc_info=True)
            finally:
                # Exit alternate buffer, restore state cleanly (sync method)
                logger.debug(f"[MODAL] Exiting alternate buffer")
                self.coordinator.exit_alternate_buffer(restore_state=True)
                await self.exit_navigation_mode()
                logger.debug(f"[MODAL] Cleanup complete")

        elif interaction_type == "toggle":
            # Toggle widgets: Enter puts into interactive mode for arrow key cycling
            # DON'T call handler on Enter - arrows will trigger state changes
            await self.state.activate_widget(widget_id)
            logger.info(f"[TOGGLE] Widget {widget_id} entered toggle interaction mode (awaiting arrow keys)")

            # Re-render to show interactive state indicator
            await self.render_navigation_state()

            # Note: handler is NOT called here - it's called when arrow keys cycle the state
            # This allows Enter to "activate" the toggle for fine-grained control with arrows

        elif interaction_type == "inline_edit":
            # Inline edit widgets edit in place
            handler = widget_info.get("on_activate")
            result = None
            if handler:
                context = self._get_widget_context()
                result = await handler(widget_id, context)
                await self._show_inline_editor(result)

            # Emit action executed event
            await self._emit_action_executed(widget_id, interaction_type, result)

        elif interaction_type == "action":
            # Action widgets execute commands
            handler = widget_info.get("on_activate")
            result = None
            if handler:
                context = self._get_widget_context()
                result = await handler(widget_id, context)

            # Emit action executed event
            await self._emit_action_executed(widget_id, interaction_type, result)

        elif interaction_type == "none" or interaction_type is None:
            # Non-interactive widget - do nothing silently
            # This is expected for widgets like clock, stats, status that are display-only
            logger.debug(f"Widget {widget_id} has no interaction type (display-only)")

        else:
            logger.warning(f"Unknown interaction type: {interaction_type}")
            await self._emit_action_executed(widget_id, interaction_type, None, error=f"Unknown interaction type: {interaction_type}")

    def _has_widgets_with_effects(self) -> bool:
        """Check if any widget in the layout has an effect configured.

        Returns:
            True if at least one widget has an effect other than 'none'
        """
        if not self.layout:
            return False

        for row in self.layout.rows:
            if not row.visible:
                continue
            for widget in row.widgets:
                effect = getattr(widget, "effect", "none")
                if effect != "none":
                    return True
        return False

    async def _start_effect_animation(self) -> None:
        """Start background task for animating widget effects."""
        if self._shimmer_task is not None:
            return  # Already running

        if not self._has_widgets_with_effects():
            return  # No effects to animate

        self._shimmer_running = True
        self._shimmer_task = asyncio.create_task(self._effect_animation_loop())
        logger.debug("Started effect animation loop")

    async def _stop_effect_animation(self) -> None:
        """Stop the effect animation background task."""
        self._shimmer_running = False
        if self._shimmer_task is not None:
            self._shimmer_task.cancel()
            try:
                await self._shimmer_task
            except asyncio.CancelledError:
                pass
            self._shimmer_task = None
            logger.debug("Stopped effect animation loop")

    async def _effect_animation_loop(self) -> None:
        """Background loop that periodically re-renders for effect animation."""
        try:
            while self._shimmer_running:
                # Re-render to update effect animation
                await self.render_navigation_state()
                # ~10 FPS for subtle animation
                await asyncio.sleep(0.1)
        except asyncio.CancelledError:
            pass
        except Exception as e:
            logger.error(f"Effect animation loop error: {e}")

    async def render_navigation_state(self) -> None:
        """Render navigation state with selection highlight.

        This triggers a status re-render with the current selection.
        """
        logger.info("[NAV] render_navigation_state: invalidating cache")
        # Invalidate renderer cache to force fresh render
        self.renderer.invalidate_render_cache()

        # Trigger render with navigation state
        logger.info("[NAV] render_navigation_state: calling render_active_area()")
        await self.renderer.render_active_area()
        logger.info("[NAV] render_navigation_state: render_active_area() complete")

    def _get_layout_bounds(self, include_hidden: bool = False) -> Tuple[int, int]:
        """Get maximum row and widget indices from layout.

        Args:
            include_hidden: If True, include hidden rows in bounds (for edit mode).
                           If False, only count visible rows (for normal navigation).

        Returns:
            Tuple of (max_row, max_widget_index)
        """
        if not self.layout:
            return (0, 0)

        try:
            if hasattr(self.layout, "rows"):
                # Get rows based on mode
                if include_hidden:
                    # Edit mode: all rows are visible
                    rows = self.layout.rows
                else:
                    # Normal mode: only visible rows
                    rows = [r for r in self.layout.rows if getattr(r, "visible", True)]

                max_row = max(0, len(rows) - 1)

                # Get max widgets in current row (for horizontal navigation)
                current_row, _ = self.state.selected_row, self.state.selected_widget_index
                max_widget = 0
                if 0 <= current_row < len(rows):
                    row = rows[current_row]
                    if hasattr(row, "widgets"):
                        max_widget = max(0, len(row.widgets) - 1)
                return (max_row, max_widget)
            elif hasattr(self.layout, "__len__"):
                max_row = len(self.layout) - 1
                return (max_row, 0)
        except Exception as e:
            logger.warning(f"Error getting layout bounds: {e}")

        return (0, 0)

    async def _clamp_selection_to_bounds(self, max_row: int, max_widget: int) -> None:
        """Clamp current selection to be within bounds.

        Preserves selection type (WIDGET vs SLOT) when clamping in EDIT mode.

        Args:
            max_row: Maximum row index
            max_widget: Maximum widget index for current row
        """
        # Get full selection info including type
        row, selection_type, index = await self.state.get_full_selection()
        clamped_row = min(row, max_row)

        if selection_type == SelectionType.SLOT:
            # For slots: max slot index = num widgets in row (slots are 0 to N for N widgets)
            # max_widget is the max widget index, so max slot = max_widget + 1
            max_slot = max_widget + 1 if max_widget >= 0 else 0
            clamped_index = min(index, max_slot)
            if clamped_row != row or clamped_index != index:
                await self.state.set_slot_selection(clamped_row, clamped_index)
                logger.debug(f"Clamped slot selection: ({row}, {index}) -> ({clamped_row}, {clamped_index})")
        else:
            # For widgets: clamp to max widget index
            clamped_index = min(index, max_widget)
            if clamped_row != row or clamped_index != index:
                await self.state.set_widget_selection(clamped_row, clamped_index)
                logger.debug(f"Clamped widget selection: ({row}, {index}) -> ({clamped_row}, {clamped_index})")

    def _get_widget_at_position(self, row: int, widget_index: int, include_hidden: bool = False) -> Optional[Dict[str, Any]]:
        """Get widget information at the specified position.

        Args:
            row: Row index (0-based)
            widget_index: Widget index within row
            include_hidden: If True, include hidden rows (for edit mode)

        Returns:
            Widget info dict with id, interaction_type, on_activate or None if not found
        """
        if not self.layout:
            return None

        try:
            # Navigate layout structure to find widget_id from WidgetConfig
            if hasattr(self.layout, "rows"):
                # Get rows based on mode
                if include_hidden:
                    rows = self.layout.rows
                else:
                    rows = [r for r in self.layout.rows if getattr(r, "visible", True)]
                if 0 <= row < len(rows):
                    row_obj = rows[row]
                    if hasattr(row_obj, "widgets"):
                        if 0 <= widget_index < len(row_obj.widgets):
                            widget_config = row_obj.widgets[widget_index]
                            # WidgetConfig has {id, width}
                            widget_id = getattr(widget_config, "id", None) or getattr(widget_config, "widget_id", None)

                            if not widget_id:
                                return None

                            # Look up actual StatusWidget from registry for interactive properties
                            if self.widget_registry:
                                status_widget = self.widget_registry.get(widget_id)
                                if status_widget:
                                    return {
                                        "id": widget_id,
                                        "interaction_type": getattr(status_widget, "interaction_type", "none"),
                                        "command": getattr(status_widget, "command", None),
                                        "on_activate": getattr(status_widget, "on_activate", None),
                                    }
                                else:
                                    logger.debug(f"Widget {widget_id} not found in registry")
                            else:
                                logger.warning("Widget registry not set, cannot get widget info")

                            # Fallback: return minimal info if registry not available
                            return {
                                "id": widget_id,
                                "interaction_type": "none",
                                "command": None,
                                "on_activate": None,
                            }
        except Exception as e:
            logger.warning(f"Error getting widget at position: {e}")

        return None

    def _get_app(self) -> Any:
        """Get the application instance.

        Returns:
            TerminalLLMChat app instance or None
        """
        # Try direct app reference first (wired in application.py)
        if hasattr(self, "app") and self.app is not None:
            return self.app

        # Fallback: Try renderer.terminal_renderer.app chain
        if hasattr(self.renderer, "terminal_renderer"):
            term_renderer = self.renderer.terminal_renderer
            if hasattr(term_renderer, "app"):
                return term_renderer.app

        # Fallback: Try renderer.app directly
        if hasattr(self.renderer, "app"):
            return self.renderer.app

        return None

    def _get_widget_context(self) -> Any:
        """Get context object for widget activation.

        Returns:
            WidgetContext object with services (llm_service, profile_manager, etc.)
            Also includes navigation_state for toggle widgets to access direction info.
        """
        # The layout_renderer has the WidgetContext set by the application
        # See: application.py lines 284-291 where WidgetContext is created
        # and self.layout_renderer.set_context(widget_context) is called
        if hasattr(self.renderer, "layout_renderer"):
            layout_renderer = self.renderer.layout_renderer
            if layout_renderer and hasattr(layout_renderer, "_context"):
                ctx = layout_renderer._context
                if ctx:
                    # Add navigation_state reference for toggle/inline edit widgets
                    ctx.navigation_state = self.state
                    # Ensure layout_manager is accessible (for widget config persistence)
                    if not hasattr(ctx, "layout_manager") or ctx.layout_manager is None:
                        ctx.layout_manager = self.layout_manager
                    return ctx

        # Fallback: check if renderer has direct context
        if hasattr(self.renderer, "context"):
            ctx = self.renderer.context
            if ctx:
                ctx.navigation_state = self.state
                if not hasattr(ctx, "layout_manager") or ctx.layout_manager is None:
                    ctx.layout_manager = self.layout_manager
            return ctx

        return None

    async def _show_modal_result(self, result: Any) -> None:
        """Show modal result to user using ANSI alternate buffer.

        Called from activate_widget() which manages coordinator lifecycle.
        Uses ANSI alternate screen buffer to preserve main buffer content.

        Args:
            result: Modal config dict with title, options, footer
        """
        if not result or not isinstance(result, dict):
            logger.debug(f"Invalid modal result: {result}")
            return

        title = result.get("title", "Modal")
        options = result.get("options", [])
        footer = result.get("footer", "↑↓ navigate · enter select · esc cancel")

        if not options:
            logger.warning("Modal config has no options")
            return

        import sys
        import os
        import re
        import select as select_module

        from core.ui.design_system import T, S, solid, solid_fg
        from core.io.terminal_state import get_terminal_size

        def strip_ansi(text):
            return re.sub(r'\033\[[0-9;]*m', '', text)

        try:
            term_width, term_height = get_terminal_size()

            # Calculate needed width based on content
            max_modal_width = 80
            padding = 2

            content_width_needed = max(len(strip_ansi(title)), len(strip_ansi(footer)))
            for opt in options:
                label = opt.get("label", "")
                description = opt.get("description", "")
                desc_part = f" - {description}" if description else ""
                opt_text = f"  > {label}{desc_part}"
                content_width_needed = max(content_width_needed, len(strip_ansi(opt_text)))

            # Calculate modal width with constraints
            modal_width = min(max_modal_width, term_width - padding * 2, content_width_needed + 4)
            modal_width = max(modal_width, 40)  # Minimum width

            # Calculate modal dimensions
            modal_height = 1 + len(options) + 2  # Title bar + content + footer

            # Calculate centering position
            start_col = max(0, (term_width - modal_width) // 2)
            start_row = max(0, (term_height - modal_height) // 2)

            # Enter ANSI alternate screen buffer for clean modal display
            sys.stdout.write('\033[?1049h')  # Enter alternate screen buffer
            sys.stdout.write('\033[?25l')    # Hide cursor
            sys.stdout.flush()

            selected_idx = 0
            selected = None

            def render_modal(selected_index: int) -> None:
                """Render the modal with selection indicator."""
                # Clear screen and position cursor
                sys.stdout.write('\033[2J')  # Clear entire screen
                sys.stdout.write(f'\033[{start_row + 1};{start_col + 1}H')  # Position cursor

                # Render title bar
                sys.stdout.write(solid_fg("▄" * modal_width, T().primary[0]) + '\n')
                title_line = solid(f"  {S.BOLD}{title}{S.RESET_BOLD}", T().primary[0], T().text, modal_width)
                sys.stdout.write(title_line + '\n')

                # Render options with selection indicator
                for i, opt in enumerate(options):
                    label = opt.get("label", "")
                    description = opt.get("description", "")
                    desc_part = f" - {description}" if description else ""

                    # Selection indicator
                    if i == selected_index:
                        indicator = f"> {S.BOLD}{label}{S.RESET_BOLD}"
                        fg_color = T().text  # Bright text for selected
                        bg_color = T().primary[0]  # Primary bg for selected
                    else:
                        indicator = f"  {label}"
                        fg_color = T().text_dim
                        bg_color = T().dark[0]

                    opt_line = f"  {indicator}{desc_part}"

                    # Truncate if needed (check visible length without ANSI)
                    max_line_width = modal_width - 2
                    visible_len = len(strip_ansi(opt_line))
                    if visible_len > max_line_width:
                        excess = visible_len - max_line_width
                        opt_line = opt_line[:-excess - 3] + "..."

                    sys.stdout.write(solid(opt_line, bg_color, fg_color, modal_width) + '\n')

                # Render footer
                sys.stdout.write(solid_fg("▀" * modal_width, T().primary[0]) + '\n')
                footer_text = f"  {footer}"
                # Truncate footer if needed (check visible length)
                if len(strip_ansi(footer_text)) > modal_width - 2:
                    footer_text = footer_text[:modal_width - 5] + "..."
                sys.stdout.write(solid(footer_text, T().dark[0], T().text_dim, modal_width) + '\n')

                sys.stdout.flush()

            # Initial render
            render_modal(selected_idx)

            # Input loop using os.read for raw input
            fd = sys.stdin.fileno()

            while True:
                char = os.read(fd, 1).decode('utf-8', errors='ignore')
                if not char:
                    continue

                # Handle escape sequences (arrow keys, esc)
                if char == '\x1b':
                    # Check if more data available (timeout 0.1s)
                    readable, _, _ = select_module.select([fd], [], [], 0.1)
                    if readable:
                        next_char = os.read(fd, 1).decode('utf-8', errors='ignore')
                        if next_char == '[':
                            # Arrow key sequence
                            readable2, _, _ = select_module.select([fd], [], [], 0.05)
                            if readable2:
                                direction = os.read(fd, 1).decode('utf-8', errors='ignore')
                                if direction == 'A':  # Up arrow
                                    selected_idx = (selected_idx - 1) % len(options)
                                    render_modal(selected_idx)
                                elif direction == 'B':  # Down arrow
                                    selected_idx = (selected_idx + 1) % len(options)
                                    render_modal(selected_idx)
                        # Not an arrow sequence, ignore
                    else:
                        # No more chars, this was standalone ESC - cancel
                        logger.info("Modal cancelled (ESC)")
                        break

                # Handle Enter to confirm selection
                elif char == '\r' or char == '\n':
                    selected = options[selected_idx]
                    logger.info(f"Selected: {selected.get('label')} -> action: {selected.get('action')}")
                    break

                # Handle number keys (1-9) for quick select
                elif char.isdigit():
                    idx = int(char) - 1
                    if 0 <= idx < len(options):
                        selected = options[idx]
                        logger.info(f"Selected (quick): {selected.get('label')} -> action: {selected.get('action')}")
                        break

        except Exception as e:
            logger.debug(f"Modal input error: {e}")

        finally:
            # Exit ANSI alternate screen buffer and show cursor
            sys.stdout.write('\033[?25h')    # Show cursor
            sys.stdout.write('\033[?1049l')  # Exit alternate screen buffer
            sys.stdout.flush()

        # Execute selected action if any
        if selected:
            action = selected.get("action")
            if action and callable(action):
                try:
                    await action()
                except Exception as e:
                    logger.error(f"Error executing modal action: {e}", exc_info=True)

    async def _show_inline_editor(self, result: Any) -> None:
        """Show inline editor result.

        Uses InlineEditorService to provide consistent inline editing behavior.
        This method is called from navigation mode when activating inline_edit widgets.

        Args:
            result: Editor config dict from widget activation handler:
                - type: "text", "slider", or "dropdown"
                - current: Current value
                - max_length: Max text length (for text type)
                - placeholder: Placeholder text (for text type)
                - min/max/step/presets: For slider type
                - on_save: Callback function to save the result
        """
        if not result or not isinstance(result, dict):
            logger.warning(f"Invalid inline editor result: {result}")
            return

        # Import service to avoid circular dependency
        from .inline_editor_service import InlineEditorService

        # Create service and show editor
        service = InlineEditorService(self.renderer, self.state)
        await service.show_editor(result)

        # Re-render navigation state to show updated widget
        await self.render_navigation_state()

    async def _show_help_from_navigation(self) -> None:
        """Show help overlay from navigation mode (F1 or ? key).

        Emits a SHOW_HELP_OVERLAY event that can be handled by the application
        to display keyboard shortcuts help.
        """
        try:
            logger.info("Showing help overlay from navigation mode")
            from ...events import EventType

            result = await self.event_bus.emit_with_hooks(
                EventType.SHOW_HELP_OVERLAY,
                {"source": "navigation_manager"},
                "navigation",
            )
            logger.info(f"Help overlay event emitted: {result}")
        except Exception as e:
            logger.error(f"Error showing help overlay: {e}")

    async def get_status(self) -> Dict[str, Any]:
        """Get current navigation status for debugging.

        Returns:
            Dictionary with navigation state information
        """
        active = await self.state.is_active()
        interacting = await self.state.is_interacting()
        row, widget_idx = await self.state.get_selection()

        return {
            "navigation_active": active,
            "interaction_active": interacting,
            "selected_row": row,
            "selected_widget_index": widget_idx,
            "active_widget_id": self.state.active_widget_id,
            "history_length": len(self.state.navigation_history),
        }

    async def _emit_widget_selected(
        self, old_row: int, old_widget: int, new_row: int, new_widget: int, key: str
    ) -> None:
        """Emit WIDGET_SELECTED event when selection changes.

        Args:
            old_row: Previous row index
            old_widget: Previous widget index
            new_row: New row index
            new_widget: New widget index
            key: Key that triggered the selection change
        """
        # Only emit if selection actually changed
        if old_row == new_row and old_widget == new_widget:
            return

        # Get widget info for old and new selections
        old_widget_info = self._get_widget_at_position(old_row, old_widget)
        new_widget_info = self._get_widget_at_position(new_row, new_widget)

        await self.event_bus.emit_with_hooks(
            EventType.WIDGET_SELECTED,
            {
                "old_widget_id": old_widget_info.get("id") if old_widget_info else None,
                "new_widget_id": new_widget_info.get("id") if new_widget_info else None,
                "old_position": {"row": old_row, "widget_index": old_widget},
                "new_position": {"row": new_row, "widget_index": new_widget},
                "key": key,
            },
            "StatusNavigationManager._emit_widget_selected"
        )

    async def _emit_action_executed(
        self, widget_id: Optional[str], interaction_type: str, result: Any, error: Optional[str] = None
    ) -> None:
        """Emit WIDGET_ACTION_EXECUTED event when widget action completes.

        Args:
            widget_id: ID of the widget that executed the action
            interaction_type: Type of interaction that was executed
            result: Result from the action handler
            error: Optional error message if action failed
        """
        await self.event_bus.emit_with_hooks(
            EventType.WIDGET_ACTION_EXECUTED,
            {
                "widget_id": widget_id,
                "interaction_type": interaction_type,
                "result": result,
                "error": error,
                "success": error is None,
            },
            "StatusNavigationManager._emit_action_executed"
        )

    async def _check_first_run_help(self) -> None:
        """Mark first-run help as shown.

        The navigation mode indicator is self-explanatory:
        'NAVIGATE   ←→↑↓:move  Enter:act  e:edit  Esc:exit'

        No modal or additional help message needed - the indicator
        provides all necessary information inline.
        """
        logger.debug(f"_check_first_run_help called: config={self.config is not None}, checked={self._first_run_help_checked}")

        if not self.config:
            logger.warning("Cannot mark first-run help: config not available")
            return

        if self._first_run_help_checked:
            logger.debug("First-run help already checked this session, skipping")
            return

        try:
            # Just mark the flag - no modal/message needed
            # Navigation mode indicator is self-explanatory
            self.config.set("status.navigation.help_shown", True)
            self.config.save()
            logger.info("First-run help flag set (navigation mode indicator is self-explanatory)")
            self._first_run_help_checked = True

        except Exception as e:
            logger.error(f"Error setting first-run help flag: {e}", exc_info=True)
