"""Main application screen with two-pane layout."""

from __future__ import annotations

from dataclasses import replace
from typing import TYPE_CHECKING, ClassVar

from textual.binding import ActiveBinding, Binding, BindingType
from textual.screen import Screen
from textual.widgets import Footer, Header

from cogitus.config import (
    DEFAULT_EDIT_BODY_CURSOR_MODE,
    DEFAULT_NEW_IDEA_GROUP_MODE,
    EditBodyCursorMode,
    NewIdeaGroupMode,
)
from cogitus.metadata import AppMetadata, get_app_metadata
from cogitus.search import parse_search_query
from cogitus.ui.clipboard import copy_to_clipboard
from cogitus.ui.screens.idea_form_screen import (
    AboutScreen,
    ConfirmDialog,
    GroupDeleteReassignScreen,
    GroupFormScreen,
    HelpScreen,
    IdeaFormScreen,
    NameInputScreen,
)
from cogitus.ui.widgets.idea_list import IdeaListPanel
from cogitus.ui.widgets.idea_view import IdeaView

if TYPE_CHECKING:
    from collections.abc import Callable

    from textual.app import ComposeResult
    from textual.events import AppFocus, ScreenResume

    from cogitus.models.group import Group
    from cogitus.models.idea import Idea
    from cogitus.services.idea_service import IdeaService


class MainScreen(Screen[None]):
    """Two-pane main screen: idea list + detail view."""

    _SEARCH_MODE_DISABLED_ACTIONS: ClassVar[frozenset[str]] = frozenset(
        {
            "new_idea",
            "new_group",
            "delete_group",
            "delete_idea",
        }
    )
    _SEARCH_INPUT_DISABLED_ACTIONS: ClassVar[frozenset[str]] = frozenset(
        {
            "edit_idea",
            "rename_selected",
            "copy_idea_body",
        }
    )
    _SEARCH_INPUT_FOOTER_ACTIONS: ClassVar[frozenset[str]] = frozenset(
        {
            "footer_search_results",
            "footer_exit_search",
        }
    )
    _SEARCH_RESULTS_FOOTER_ACTIONS: ClassVar[frozenset[str]] = frozenset(
        {
            "footer_next_result",
            "footer_previous_result",
            "footer_back_to_search",
        }
    )

    BINDINGS: ClassVar[list[BindingType]] = [
        Binding("n", "new_idea", "New", key_display="n"),
        Binding("g", "new_group", "New Group", key_display="g"),
        Binding(
            "G",
            "delete_group",
            "Delete Group",
            key_display="G",
        ),
        Binding("e", "edit_idea", "Edit", key_display="e"),
        Binding("r", "rename_selected", "Rename", key_display="r"),
        Binding("d", "delete_idea", "Delete", key_display="d"),
        Binding("slash", "focus_search", "Search", key_display="/"),
        Binding("escape", "cancel_search", "Back", show=False),
        Binding(
            "down",
            "footer_search_results",
            "Results",
            show=True,
            key_display="Down",
            priority=True,
        ),
        Binding(
            "escape",
            "footer_exit_search",
            "Exit Search",
            show=True,
            key_display="Esc",
            priority=True,
        ),
        Binding(
            "a",
            "show_about",
            "About",
            key_display="a",
        ),
        Binding(
            "question_mark",
            "show_help",
            "Help",
            key_display="?",
        ),
        Binding(
            "tab",
            "toggle_focus",
            "Switch Pane",
            show=True,
        ),
        Binding(
            "ctrl+b",
            "toggle_list_panel",
            "Toggle List",
            key_display="ctrl+b",
        ),
        Binding(
            "y",
            "copy_idea_body",
            "Copy body",
            key_display="y",
        ),
        Binding("q", "quit_app", "Quit", key_display="q"),
    ]

    def __init__(
        self,
        service: IdeaService,
        *,
        initial_select_pk: int | None = None,
        on_selected_idea_changed: Callable[[int | None], None] | None = None,
        edit_body_cursor_mode: EditBodyCursorMode = (
            DEFAULT_EDIT_BODY_CURSOR_MODE
        ),
        new_idea_group_mode: NewIdeaGroupMode = (DEFAULT_NEW_IDEA_GROUP_MODE),
        app_metadata: AppMetadata | None = None,
    ) -> None:
        """Initialize with the idea service.

        Args:
            service: The IdeaService instance.
            initial_select_pk: Idea primary key to select on first load.
            on_selected_idea_changed: Callback for selected idea changes.
            edit_body_cursor_mode: Edit form body cursor mode.
            new_idea_group_mode: New idea group selection mode.
            app_metadata: App metadata shown in the top bar.
        """
        super().__init__()
        resolved_app_metadata = (
            get_app_metadata() if app_metadata is None else app_metadata
        )
        self._app_metadata = resolved_app_metadata
        self._service = service
        self._initial_select_pk = initial_select_pk
        self._on_selected_idea_changed = on_selected_idea_changed
        self._edit_body_cursor_mode = edit_body_cursor_mode
        self._new_idea_group_mode = new_idea_group_mode
        self._app_title = resolved_app_metadata.title
        self._app_version = resolved_app_metadata.version
        self.title = self._app_title
        self._selected_idea_pk: int | None = None
        self._active_pane: str = "list"
        self._focus_before_search: str = "list"

    def compose(self) -> ComposeResult:
        """Compose the main screen layout."""
        yield Header(icon=f"v{self._app_version}")
        yield IdeaListPanel(id="idea-list-panel")
        yield IdeaView(id="content-panel")
        yield Footer()

    def on_mount(self) -> None:
        """Load ideas when screen mounts."""
        self.refresh_ideas(select_pk=self._initial_select_pk)
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        panel.browse_widget().focus()

    def on_app_focus(self, _event: AppFocus) -> None:
        """Refresh visible relative timestamps when the app regains focus."""
        self._refresh_relative_timestamps()

    def on_screen_resume(self, _event: ScreenResume) -> None:
        """Refresh visible relative timestamps when this screen resumes."""
        self._refresh_relative_timestamps()

    def _refresh_relative_timestamps(self) -> None:
        """Refresh in-place relative timestamps in the visible idea tree."""
        self.query_one(
            "#idea-list-panel",
            IdeaListPanel,
        ).refresh_relative_timestamps()

    def refresh_ideas(
        self,
        select_pk: int | None = None,
        *,
        select_group_pk: int | None = None,
    ) -> None:
        """Reload the idea list from the service."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        panel.set_autocomplete_sources(
            tags=[tag.name for tag in self._service.list_tags_in_use()],
            groups=[group.name for group in self._service.list_groups()],
        )
        raw_search_query = panel.raw_search_query()
        search_query = raw_search_query.strip()
        if raw_search_query:
            if search_query:
                self._refresh_search_results(
                    panel,
                    search_query=search_query,
                    select_pk=select_pk,
                )
            return

        local_select_pk = select_pk
        if local_select_pk is None and select_group_pk is None:
            local_select_pk = self._selected_idea_pk

        view = self.query_one("#content-panel", IdeaView)
        grouped_ideas = self._service.list_ideas_grouped(None)
        panel.load_grouped_ideas(
            grouped_ideas,
            auto_select_first=select_group_pk is None,
        )
        self._refresh_grouped_ideas(
            panel,
            view,
            grouped_ideas=grouped_ideas,
            select_pk=local_select_pk,
            select_group_pk=select_group_pk,
        )

    def _refresh_grouped_ideas(
        self,
        panel: IdeaListPanel,
        view: IdeaView,
        *,
        grouped_ideas: list[tuple[Group, list[Idea]]],
        select_pk: int | None,
        select_group_pk: int | None,
    ) -> None:
        """Refresh normal grouped-idea tree state and right-pane preview."""
        has_ideas = any(group_ideas for _, group_ideas in grouped_ideas)
        if has_ideas:
            restored_idea = select_pk is not None and panel.select_idea(
                select_pk
            )
            if not restored_idea and not self._restore_group_selection(
                panel,
                view,
                select_group_pk=select_group_pk,
            ):
                return
            self._sync_panel_selection_preview(
                panel,
                view,
                commit_selection=True,
            )
        else:
            if not self._restore_group_selection(
                panel,
                view,
                select_group_pk=select_group_pk,
            ):
                return
            self._show_idea_preview(
                view,
                None,
                commit_selection=True,
            )

    def _restore_group_selection(
        self,
        panel: IdeaListPanel,
        view: IdeaView,
        *,
        select_group_pk: int | None,
    ) -> bool:
        """Restore requested group selection or clear the preview on failure."""
        if select_group_pk is None or panel.select_group(select_group_pk):
            return True
        self._clear_selected_idea_view(view)
        return False

    def _clear_selected_idea_view(self, view: IdeaView) -> None:
        """Clear the selected idea and show an empty preview pane."""
        self._show_idea_preview(
            view,
            None,
            commit_selection=True,
        )

    def _refresh_search_results(
        self,
        panel: IdeaListPanel,
        *,
        search_query: str,
        select_pk: int | None,
    ) -> None:
        """Refresh the active search view without clobbering preview state."""
        parsed = parse_search_query(search_query)
        search_results = self._service.search_results(search_query)
        panel.load_search_results(
            search_results,
            show_match_rows=parsed.text is not None,
            search_query=search_query,
        )
        matched_select_pk = False
        if select_pk is not None and any(
            result.idea.pk == select_pk for result in search_results
        ):
            matched_select_pk = panel.select_idea(select_pk)
        self._show_search_selection_preview(
            panel,
            commit_selection=(
                matched_select_pk or panel.is_search_results_focused()
            ),
        )

    def _show_search_selection_preview(
        self,
        panel: IdeaListPanel,
        *,
        commit_selection: bool,
    ) -> None:
        """Show the current search preview and optionally commit it."""
        view = self.query_one("#content-panel", IdeaView)
        self._sync_panel_selection_preview(
            panel,
            view,
            commit_selection=commit_selection,
        )

    def on_idea_list_panel_idea_selected(
        self, event: IdeaListPanel.IdeaSelected
    ) -> None:
        """Update detail view when an idea is selected."""
        view = self.query_one("#content-panel", IdeaView)
        fresh = self._service.get_idea(event.idea.pk)
        self._show_idea_preview(
            view,
            fresh,
            commit_selection=True,
        )

    def on_idea_list_panel_search_changed(
        self, event: IdeaListPanel.SearchChanged
    ) -> None:
        """Filter ideas based on search query."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        view = self.query_one("#content-panel", IdeaView)
        query = event.query.strip()
        if query:
            parsed = parse_search_query(query)
            panel.load_search_results(
                self._service.search_results(query),
                show_match_rows=parsed.text is not None,
                search_query=query,
            )
            self._show_search_selection_preview(
                panel,
                commit_selection=False,
            )
            return

        grouped_ideas = self._service.list_ideas_grouped(None)
        panel.load_grouped_ideas(grouped_ideas)
        if self._selected_idea_pk is not None:
            panel.select_idea(self._selected_idea_pk)
        self._sync_panel_selection_preview(
            panel,
            view,
            commit_selection=True,
        )

    def _sync_panel_selection_preview(
        self,
        panel: IdeaListPanel,
        view: IdeaView,
        *,
        commit_selection: bool,
    ) -> None:
        """Show the panel's selected idea in the preview pane."""
        self._show_idea_preview(
            view,
            panel.get_selected_idea(),
            commit_selection=commit_selection,
        )

    def _show_idea_preview(
        self,
        view: IdeaView,
        idea: Idea | None,
        *,
        commit_selection: bool,
    ) -> None:
        """Show one idea in the preview pane, or the empty state."""
        if commit_selection:
            self._set_selected_idea(None if idea is None else idea.pk)
        if idea is None:
            view.show_empty()
            return
        view.show_idea(idea)

    def action_new_idea(self) -> None:
        """Open the new idea form."""
        initial_group_pk = None
        if self._new_idea_group_mode == NewIdeaGroupMode.CONTEXTUAL:
            initial_group_pk = self._get_contextual_new_idea_group_pk()
        self.app.push_screen(
            IdeaFormScreen(
                self._service,
                initial_group_pk=initial_group_pk,
                edit_body_cursor_mode=self._edit_body_cursor_mode,
            ),
            callback=self._on_form_dismiss,
        )

    def _get_contextual_new_idea_group_pk(self) -> int | None:
        """Return current group context from the left idea tree selection."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        selected_group_pk = panel.get_selected_group_pk()
        if selected_group_pk is not None:
            return selected_group_pk

        selected_idea = panel.get_selected_idea()
        if selected_idea is None:
            return None
        return selected_idea.group.pk

    def action_new_group(self) -> None:
        """Open the new group form."""
        self.app.push_screen(
            GroupFormScreen(self._service),
            callback=self._on_group_form_dismiss,
        )

    def action_edit_idea(self) -> None:
        """Open the edit form for the selected idea."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        idea = panel.get_selected_idea()
        if idea is None:
            self.notify("No idea selected", severity="warning")
            return
        fresh = self._service.get_idea(idea.pk)
        if fresh is None:
            self.notify("Idea not found", severity="error")
            return
        self.app.push_screen(
            IdeaFormScreen(
                self._service,
                idea=fresh,
                edit_body_cursor_mode=self._edit_body_cursor_mode,
            ),
            callback=self._on_form_dismiss,
        )

    def action_delete_idea(self) -> None:
        """Delete the selected idea with confirmation."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        idea = panel.get_selected_idea()
        if idea is None:
            self.notify("No idea selected", severity="warning")
            return
        self.app.push_screen(
            ConfirmDialog(f'Delete "{idea.title}"?'),
            callback=lambda confirmed: self._on_delete_confirm(
                idea.pk, confirmed=confirmed
            ),
        )

    def action_rename_selected(self) -> None:
        """Rename the currently selected group or idea."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        group_pk = panel.get_selected_group_pk()
        if group_pk is not None:
            self._rename_selected_group(group_pk)
            return

        idea = panel.get_selected_idea()
        if idea is None:
            self.notify("Nothing renameable selected", severity="warning")
            return
        self._rename_selected_idea(idea.pk)

    def _rename_selected_group(self, group_pk: int) -> None:
        """Open rename flow for a selected group."""
        group = self._service.get_group(group_pk)
        if group is None:
            self.notify("Group not found", severity="error")
            return
        if group.name == self._service.default_group_name:
            self.notify(
                "Default group cannot be renamed",
                severity="warning",
            )
            return
        self.app.push_screen(
            NameInputScreen(
                title="Rename Group",
                initial_value=group.name,
                placeholder="Group name...",
            ),
            callback=lambda name: self._on_group_rename_dismiss(
                group.pk,
                name,
            ),
        )

    def _rename_selected_idea(self, idea_pk: int) -> None:
        """Open rename flow for a selected idea."""
        fresh = self._service.get_idea(idea_pk)
        if fresh is None:
            self.notify("Idea not found", severity="error")
            return
        self.app.push_screen(
            NameInputScreen(
                title="Rename Idea",
                initial_value=fresh.title,
                placeholder="Idea title...",
            ),
            callback=lambda title: self._on_idea_rename_dismiss(
                fresh.pk,
                title,
            ),
        )

    def action_delete_group(self) -> None:
        """Delete selected group with optional bulk move."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        group_pk = panel.get_selected_group_pk()
        if group_pk is None:
            self.notify("No group selected", severity="warning")
            return

        groups = self._service.list_groups()
        group = next((item for item in groups if item.pk == group_pk), None)
        if group is None:
            self.notify("Group not found", severity="error")
            return
        if group.name == self._service.default_group_name:
            self.notify("Default group cannot be deleted", severity="warning")
            return

        self._show_delete_group_flow(group_pk, group.name, groups)

    def _show_delete_group_flow(
        self,
        group_pk: int,
        group_name: str,
        groups: list[Group],
    ) -> None:
        """Open confirm or reassignment dialog for group deletion."""
        if not self._service.has_ideas_in_group(group_pk):
            self.app.push_screen(
                ConfirmDialog(f'Delete group "{group_name}"?'),
                callback=lambda confirmed: self._on_delete_group_confirm(
                    group_pk,
                    confirmed=confirmed,
                ),
            )
            return

        options = [
            (item.name, item.pk) for item in groups if item.pk != group_pk
        ]
        self.app.push_screen(
            GroupDeleteReassignScreen(group_name, options),
            callback=lambda target_pk: self._on_delete_group_reassign(
                group_pk,
                target_pk,
            ),
        )

    def action_copy_idea_body(self) -> None:
        """Copy the selected idea's body to the system clipboard."""
        idea, had_target = self._resolve_copy_target_idea()
        if idea is None:
            if had_target:
                self.notify("Idea not found", severity="error")
            else:
                self.notify("No idea selected", severity="warning")
            return
        if not idea.body:
            self.notify("Idea has no body to copy", severity="warning")
            return

        selected_text = self._get_selected_rendered_body_text()
        text_to_copy = selected_text if selected_text is not None else idea.body
        if copy_to_clipboard(text_to_copy, self.app):
            if selected_text is not None:
                self.notify("Copied selection to clipboard")
            else:
                self.notify("Copied idea body to clipboard")
        else:
            self.notify("Clipboard unavailable", severity="warning")

    def _resolve_copy_target_idea(self) -> tuple[Idea | None, bool]:
        """Return the idea that copy-body actions should target."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        if panel.search_is_active():
            selected = panel.get_selected_idea()
            if selected is None:
                return None, False
            return self._service.get_idea(selected.pk), True
        if self._selected_idea_pk is None:
            return None, False
        return self._service.get_idea(self._selected_idea_pk), True

    def _get_selected_rendered_body_text(self) -> str | None:
        """Return selected text in rendered body, if any."""
        selected_text = self.get_selected_text()
        if selected_text:
            return selected_text

        view = self.query_one("#content-panel", IdeaView)
        return view.selected_body_text()

    def _on_delete_confirm(
        self,
        pk: int,
        *,
        confirmed: bool,
    ) -> None:
        """Handle delete confirmation result."""
        if confirmed:
            self._service.delete_idea(pk)
            self.notify("Idea deleted")
            self.refresh_ideas()

    def _on_form_dismiss(self, result: int | None) -> None:
        """Handle form dismiss — result is the idea pk."""
        if result is not None:
            self.refresh_ideas(select_pk=result)

    def _on_group_form_dismiss(self, result: int | None) -> None:
        """Refresh tree after group create."""
        if result is not None:
            self.notify("Group created")
            self.refresh_ideas()

    def _on_group_rename_dismiss(
        self,
        group_pk: int,
        name: str | None,
    ) -> None:
        """Handle group rename flow completion."""
        if name is None:
            return
        try:
            renamed = self._service.rename_group(group_pk, name)
        except ValueError as exc:
            self.notify(str(exc), severity="error")
            return
        if renamed is None:
            self.notify("Group not found", severity="error")
            return
        self.notify("Group renamed")
        self.refresh_ideas(select_group_pk=renamed.pk)

    def _on_idea_rename_dismiss(
        self,
        idea_pk: int,
        title: str | None,
    ) -> None:
        """Handle idea rename flow completion."""
        if title is None:
            return
        try:
            renamed = self._service.rename_idea(idea_pk, title)
        except ValueError as exc:
            self.notify(str(exc), severity="error")
            return
        if renamed is None:
            self.notify("Idea not found", severity="error")
            return
        self.notify("Idea renamed")
        self.refresh_ideas(select_pk=renamed.pk)

    def _on_delete_group_confirm(
        self,
        group_pk: int,
        *,
        confirmed: bool,
    ) -> None:
        """Handle delete-group confirmation for empty groups."""
        if confirmed:
            try:
                self._service.delete_group(group_pk)
            except ValueError as exc:
                self.notify(str(exc), severity="error")
                return
            self.notify("Group deleted")
            self.refresh_ideas()

    def _on_delete_group_reassign(
        self,
        group_pk: int,
        target_group_pk: int | None,
    ) -> None:
        """Handle group delete with reassignment."""
        if target_group_pk is None:
            return
        try:
            self._service.delete_group(
                group_pk,
                move_to_group_pk=target_group_pk,
            )
        except ValueError as exc:
            self.notify(str(exc), severity="error")
            return
        self.notify("Group deleted and ideas moved")
        self.refresh_ideas()

    def action_focus_search(self) -> None:
        """Focus the search input."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        if not panel.is_search_input_focused():
            self._focus_before_search = self._active_pane
        panel.focus_search()

    def action_cancel_search(self) -> None:
        """Clear search and return focus to the previous panel."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        cancel_result = panel.cancel_search_interaction()
        if cancel_result != "cleared_search":
            return
        if self._focus_before_search == "content":
            self.query_one("#content-panel", IdeaView).focus_content()
            self._active_pane = "content"
        else:
            panel.focus_preferred_list_widget()
            self._active_pane = "list"

    def action_show_help(self) -> None:
        """Show the help overlay."""
        self.app.push_screen(HelpScreen())

    def action_show_about(self) -> None:
        """Show the About overlay."""
        self.app.push_screen(AboutScreen(self._app_metadata))

    def action_toggle_focus(self) -> None:
        """Toggle focus between list and content panes."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        if panel.is_search_input_focused():
            return
        if panel.has_focus_within:
            content = self.query_one("#content-panel", IdeaView)
            content.focus_content()
            self._active_pane = "content"
        else:
            panel.focus_preferred_list_widget()
            self._active_pane = "list"

    def action_toggle_list_panel(self) -> None:
        """Collapse/expand the left list panel."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)
        content = self.query_one("#content-panel", IdeaView)
        if panel.has_class("collapsed"):
            panel.remove_class("collapsed")
            panel.focus_preferred_list_widget()
            self._active_pane = "list"
        else:
            panel.add_class("collapsed")
            content.focus_content()
            self._active_pane = "content"

    def action_quit_app(self) -> None:
        """Quit the application."""
        self.app.exit()

    def check_action(
        self,
        action: str,
        parameters: tuple[object, ...],
    ) -> bool | None:
        """Show search-mode footer hints only when they are relevant."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)

        if panel.search_is_active():
            if (
                panel.is_search_input_focused()
                or panel.is_search_results_focused()
            ) and action in self._SEARCH_MODE_DISABLED_ACTIONS:
                return False
            if panel.is_search_input_focused() and action in (
                self._SEARCH_INPUT_DISABLED_ACTIONS
            ):
                return False

        if action == "footer_search_results":
            return (
                panel.is_search_input_focused()
                and panel.search_is_active()
                and not panel.autocomplete_is_visible()
                and bool(panel.get_selected_idea())
            )
        if action == "footer_exit_search":
            return panel.is_search_input_focused()
        if action == "rename_selected":
            return self._can_rename_selection()
        return super().check_action(action, parameters)

    def _can_rename_selection(self) -> bool:
        """Return whether the current focus/selection supports rename."""
        panel = self.query_one("#idea-list-panel", IdeaListPanel)

        if panel.is_search_input_focused():
            return False
        group_pk = panel.get_selected_group_pk()
        if group_pk is not None:
            group = self._service.get_group(group_pk)
            return (
                group is not None
                and group.name != self._service.default_group_name
            )
        return panel.get_selected_idea() is not None

    @property
    def active_bindings(self) -> dict[str, ActiveBinding]:
        """Return bindings with search-mode footer filtering."""
        bindings = super().active_bindings
        panel = self.query_one("#idea-list-panel", IdeaListPanel)

        if panel.is_search_input_focused() or panel.search_is_active():
            bindings = {
                key: binding
                for key, binding in bindings.items()
                if binding.binding.action != "toggle_focus"
            }

        if panel.is_search_input_focused() and not panel.search_is_active():
            bindings = {
                key: binding
                for key, binding in bindings.items()
                if binding.binding.action == "footer_exit_search"
            }
        elif panel.is_search_input_focused() and panel.search_is_active():
            bindings = {
                key: binding
                for key, binding in bindings.items()
                if binding.binding.action in self._SEARCH_INPUT_FOOTER_ACTIONS
            }
        elif panel.is_search_results_focused() and panel.search_is_active():
            bindings = {
                key: binding
                for key, binding in bindings.items()
                if binding.binding.action in self._SEARCH_RESULTS_FOOTER_ACTIONS
            }

        up_binding = bindings.get("up")
        if (
            panel.is_search_results_focused()
            and panel.search_is_active()
            and up_binding is not None
            and up_binding.binding.action == "footer_previous_result"
        ):
            description = (
                "Search" if panel.is_first_result_selected() else "Prev Result"
            )
            bindings["up"] = ActiveBinding(
                up_binding.node,
                replace(up_binding.binding, description=description),
                up_binding.enabled,
                up_binding.tooltip,
            )
        return bindings

    def _set_selected_idea(self, idea_pk: int | None) -> None:
        """Track and publish the selected idea primary key."""
        self._selected_idea_pk = idea_pk
        if self._on_selected_idea_changed is not None:
            self._on_selected_idea_changed(idea_pk)
