use anyhow::Result;
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use ratatui::layout::{Rect, Size};
use std::time::Instant;

use crate::tui::app::{
    App, DetailNavigationState, Focus, FooterChoiceMode, TASK_ROW_DOUBLE_CLICK, TaskRefKind,
};
use crate::tui::authoring::AddTaskStep;
use crate::tui::conflict_flow::ConflictResolutionChoice;
use crate::tui::event::{
    Action, CommandCompletion, CommandLookup, command_cycle_options, complete_command,
    lookup_command,
};
use crate::tui::navigation::{
    detail_scroll_with_delta, detail_task_delta, handle_detail_overlay_key, next_index,
    scroll_with_delta,
};
use crate::tui::overlay::{CommandState, OverlayOutcome, OverlayRoute, OverlayState};
use crate::tui::platform::is_editor_prefix_key;
use crate::tui::shortcut_buffer::{DetailShortcutResolution, NormalShortcutResolution};
use crate::tui::store::TaskView;
use crate::tui::ui::{
    database_stats_scroll_cap, detail_child_task_at_position, detail_help_scroll_cap,
    help_scroll_cap, prefix_hint_scroll_cap, recent_action_at_position, task_at_position,
    task_status_at_position, text_panel_scroll_cap,
};

impl App {
    pub(super) async fn dispatch_paste(&mut self, text: &str) -> Result<()> {
        let Some(overlay) = self.overlay.take() else {
            return Ok(());
        };
        let mut overlay = crate::tui::overlay::handle_generic_overlay_paste(text, overlay);
        if let OverlayState::Search(state) = &mut overlay {
            self.handle_search_paste(state).await?;
        }
        self.overlay = Some(overlay);
        Ok(())
    }

    pub(crate) async fn dispatch_key(&mut self, key: KeyEvent, terminal_size: Size) -> Result<()> {
        if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
            self.handle(Action::Quit).await
        } else if self.footer_choice_mode.is_some() {
            self.handle_footer_choice_key(key).await
        } else if key.code == KeyCode::Esc && self.pending_shortcut.cancel() {
            self.pending_shortcut_scroll = 0;
            Ok(())
        } else if self.overlay_captures_input() {
            if key.code == KeyCode::Char('?')
                && matches!(self.overlay, Some(OverlayState::Detail { .. }))
            {
                self.toggle_help_at_height(terminal_size.height);
                Ok(())
            } else if self.dispatch_prefix_hint_key_scroll(key, terminal_size) {
                Ok(())
            } else {
                self.handle_overlay_key_at_size(key, terminal_size).await
            }
        } else if key.code == KeyCode::Char('?') {
            self.toggle_help_at_height(terminal_size.height);
            Ok(())
        } else if self.dispatch_prefix_hint_key_scroll(key, terminal_size) {
            Ok(())
        } else if key.modifiers.is_empty() || key.modifiers == KeyModifiers::SHIFT {
            self.handle_normal_key(key.code).await
        } else {
            Ok(())
        }
    }

    async fn handle_footer_choice_key(&mut self, key: KeyEvent) -> Result<()> {
        let Some(mode) = self.footer_choice_mode else {
            return Ok(());
        };
        if !key.modifiers.is_empty() {
            return Ok(());
        }
        match (mode, key.code) {
            (_, KeyCode::Esc) => {
                self.footer_choice_mode = None;
                Ok(())
            }
            (FooterChoiceMode::Status, KeyCode::Char('i')) => {
                self.submit_footer_status("inbox").await
            }
            (FooterChoiceMode::Status, KeyCode::Char('b')) => {
                self.submit_footer_status("backlog").await
            }
            (FooterChoiceMode::Status, KeyCode::Char('t')) => {
                self.submit_footer_status("todo").await
            }
            (FooterChoiceMode::Status, KeyCode::Char('a')) => {
                self.submit_footer_status("active").await
            }
            (FooterChoiceMode::Status, KeyCode::Char('d')) => {
                self.submit_footer_status("done").await
            }
            (FooterChoiceMode::Status, KeyCode::Char('x')) => {
                self.submit_footer_status("canceled").await
            }
            (FooterChoiceMode::Priority, KeyCode::Char('n')) => {
                self.submit_footer_priority("none").await
            }
            (FooterChoiceMode::Priority, KeyCode::Char('l')) => {
                self.submit_footer_priority("low").await
            }
            (FooterChoiceMode::Priority, KeyCode::Char('m')) => {
                self.submit_footer_priority("medium").await
            }
            (FooterChoiceMode::Priority, KeyCode::Char('h')) => {
                self.submit_footer_priority("high").await
            }
            (FooterChoiceMode::Priority, KeyCode::Char('u')) => {
                self.submit_footer_priority("urgent").await
            }
            _ => Ok(()),
        }
    }

    async fn submit_footer_status(&mut self, status: &'static str) -> Result<()> {
        self.footer_choice_mode = None;
        self.update_status(status).await?;
        if self.detail_context && self.overlay.is_none() {
            self.restore_detail_overlay(true);
        }
        Ok(())
    }

    async fn submit_footer_priority(&mut self, priority: &'static str) -> Result<()> {
        self.footer_choice_mode = None;
        self.set_exact_priority(priority).await?;
        if self.detail_context && self.overlay.is_none() {
            self.restore_detail_overlay(true);
        }
        Ok(())
    }

    pub(crate) async fn dispatch_mouse(
        &mut self,
        mouse: MouseEvent,
        terminal_size: Size,
    ) -> Result<()> {
        match mouse.kind {
            MouseEventKind::ScrollDown => {
                if self.dispatch_prefix_hint_scroll(1, terminal_size) {
                    return Ok(());
                }
                if self.dispatch_mouse_scroll(mouse.kind, terminal_size) {
                    return Ok(());
                }
                return self.handle_task_list_wheel(1, terminal_size).await;
            }
            MouseEventKind::ScrollUp => {
                if self.dispatch_prefix_hint_scroll(-1, terminal_size) {
                    return Ok(());
                }
                if self.dispatch_mouse_scroll(mouse.kind, terminal_size) {
                    return Ok(());
                }
                return self.handle_task_list_wheel(-1, terminal_size).await;
            }
            MouseEventKind::Down(MouseButton::Left) => {}
            MouseEventKind::Moved => {
                self.handle_detail_mouse_move(mouse, terminal_size);
                return Ok(());
            }
            MouseEventKind::Down(MouseButton::Right) => {
                return self
                    .handle_task_status_right_click(mouse, terminal_size)
                    .await;
            }
            _ => return Ok(()),
        }

        self.last_task_click = self
            .last_task_click
            .as_ref()
            .filter(|last| last.at.elapsed() <= TASK_ROW_DOUBLE_CLICK)
            .cloned();

        let header = ratatui::layout::Rect {
            x: 0,
            y: 0,
            width: terminal_size.width,
            height: 2,
        };
        if terminal_size.width >= 70
            && terminal_size.height >= 18
            && self.detail_underlay()
            && matches!(
                crate::tui::ui::header_target_at(&self.store, header, mouse.column, mouse.row),
                Some(crate::tui::ui::HeaderTarget::Home)
            )
        {
            self.last_task_click = None;
            self.cancel_overlay();
            return Ok(());
        }

        if matches!(self.overlay, Some(OverlayState::HeaderMenu(_))) {
            let Some(OverlayState::HeaderMenu(state)) = self.overlay.take() else {
                return Ok(());
            };
            self.submit_header_menu_at(state, mouse.column, mouse.row, terminal_size)
                .await?;
            if self.detail_context && self.overlay.is_none() {
                self.restore_detail_overlay(true);
            }
            return Ok(());
        }
        if matches!(self.overlay, Some(OverlayState::OrderMenu(_))) {
            let Some(OverlayState::OrderMenu(state)) = self.overlay.take() else {
                return Ok(());
            };
            self.submit_order_menu_at(state, mouse.column, mouse.row, terminal_size)
                .await?;
            if self.detail_context && self.overlay.is_none() {
                self.restore_detail_overlay(true);
            }
            return Ok(());
        }
        if let Some(OverlayState::Detail { scroll }) = self.overlay
            && self.handle_detail_mouse_click(mouse, terminal_size, scroll)
        {
            return Ok(());
        }
        if matches!(
            self.overlay,
            Some(OverlayState::Picker(_) | OverlayState::Confirm(_) | OverlayState::TextPanel(_))
        ) {
            self.last_task_click = None;
            let Some(overlay) = self.overlay.take() else {
                return Ok(());
            };
            self.handle_overlay_mouse(overlay, mouse, terminal_size)
                .await?;
            return Ok(());
        }
        if self.overlay.is_some() || terminal_size.width < 70 || terminal_size.height < 18 {
            return Ok(());
        }
        if let Some(target) =
            crate::tui::ui::header_target_at(&self.store, header, mouse.column, mouse.row)
        {
            self.last_task_click = None;
            return match target {
                crate::tui::ui::HeaderTarget::Home => Ok(()),
                crate::tui::ui::HeaderTarget::Workspace { column } => {
                    self.show_workspace_menu(column, mouse.row).await?;
                    Ok(())
                }
                crate::tui::ui::HeaderTarget::Scope { column } => {
                    self.show_scope_menu(column, mouse.row);
                    Ok(())
                }
                crate::tui::ui::HeaderTarget::View { column } => {
                    self.show_view_menu(column, mouse.row);
                    Ok(())
                }
                crate::tui::ui::HeaderTarget::MetricView(view) => self.show_view(view).await,
                crate::tui::ui::HeaderTarget::Order { column } => {
                    self.show_order_menu(column, mouse.row);
                    Ok(())
                }
                crate::tui::ui::HeaderTarget::SyncStatus => self.show_config_status(),
            };
        }

        if !self.sidebar_contains_mouse(terminal_size, mouse.column, mouse.row)
            && self.store.view_state.view == TaskView::RecentActions
            && let Some(hit) = recent_action_at_position(
                &self.store,
                &self.widgets.table,
                self.task_area_for_mouse(terminal_size),
                mouse.column,
                mouse.row,
            )
        {
            self.focus = Focus::Tasks;
            self.widgets.table.select(Some(hit.action_index));
            self.last_task_click = None;
            return Ok(());
        }

        if !self.sidebar_contains_mouse(terminal_size, mouse.column, mouse.row)
            && let Some(hit) = task_status_at_position(
                &self.store,
                &self.widgets.table,
                self.task_area_for_mouse(terminal_size),
                mouse.column,
                mouse.row,
            )
        {
            self.last_task_click = None;
            self.focus = Focus::Tasks;
            self.widgets.table.select(Some(hit.task_index));
            self.footer_choice_mode = Some(FooterChoiceMode::Status);
            return Ok(());
        }

        if !self.sidebar_contains_mouse(terminal_size, mouse.column, mouse.row)
            && let Some(hit) = task_at_position(
                &self.store,
                &self.widgets.table,
                self.task_area_for_mouse(terminal_size),
                mouse.column,
                mouse.row,
            )
        {
            self.focus = Focus::Tasks;
            self.widgets.table.select(Some(hit.task_index));
            let now = Instant::now();
            let is_double_click = self.last_task_click.as_ref().is_some_and(|previous| {
                previous.task_id == hit.task_id
                    && previous.viewport_row == hit.viewport_row
                    && now.duration_since(previous.at) <= TASK_ROW_DOUBLE_CLICK
            });
            if is_double_click {
                self.last_task_click = None;
                self.overlay = Some(OverlayState::Detail { scroll: 0 });
            } else {
                self.last_task_click = Some(crate::tui::app::TaskRowClick {
                    task_id: hit.task_id,
                    viewport_row: hit.viewport_row,
                    at: now,
                });
            }
            return Ok(());
        }

        self.last_task_click = None;
        if let Some(click) = crate::tui::ui::sidebar_click_at_for(
            &self.store.sidebar_entries,
            &self.widgets.sidebar,
            self.focus,
            self.sidebar_visible,
            ratatui::layout::Rect::new(0, 0, terminal_size.width, terminal_size.height),
            mouse.column,
            mouse.row,
        ) {
            self.widgets.sidebar.select(Some(click.entry_index));
            self.apply_sidebar_selection().await?;
        }

        Ok(())
    }

    fn task_area_for_mouse(&self, terminal_size: Size) -> Rect {
        let body_height = terminal_size.height.saturating_sub(4);
        let body = Rect::new(0, 2, terminal_size.width, body_height);
        if !self.sidebar_visible || body.width < 100 {
            body
        } else {
            let sidebar_width = body.width.min(26);
            Rect::new(
                sidebar_width,
                body.y,
                body.width.saturating_sub(sidebar_width),
                body.height,
            )
        }
    }

    async fn handle_task_status_right_click(
        &mut self,
        mouse: MouseEvent,
        terminal_size: Size,
    ) -> Result<()> {
        self.last_task_click = None;
        if self.overlay.is_some() || terminal_size.width < 70 || terminal_size.height < 18 {
            return Ok(());
        }
        if self.sidebar_contains_mouse(terminal_size, mouse.column, mouse.row) {
            return Ok(());
        }
        let Some(hit) = task_status_at_position(
            &self.store,
            &self.widgets.table,
            self.task_area_for_mouse(terminal_size),
            mouse.column,
            mouse.row,
        ) else {
            return Ok(());
        };

        self.focus = Focus::Tasks;
        self.widgets.table.select(Some(hit.task_index));
        self.footer_choice_mode = Some(FooterChoiceMode::Status);
        Ok(())
    }

    fn sidebar_contains_mouse(&self, terminal_size: Size, column: u16, row: u16) -> bool {
        let terminal = Rect::new(0, 0, terminal_size.width, terminal_size.height);
        crate::tui::ui::sidebar_layout_for(terminal, self.focus, self.sidebar_visible).is_some_and(
            |layout| {
                column >= layout.sidebar.x
                    && column < layout.sidebar.x.saturating_add(layout.sidebar.width)
                    && row >= layout.sidebar.y
                    && row < layout.sidebar.y.saturating_add(layout.sidebar.height)
            },
        )
    }

    fn handle_detail_mouse_click(
        &mut self,
        mouse: MouseEvent,
        terminal_size: Size,
        scroll: u16,
    ) -> bool {
        if let Some(item) = self.store.selected_task(self.widgets.table.selected()) {
            let current_task_id = item.task.id.clone();
            if let Some(hit) = detail_child_task_at_position(
                item,
                terminal_size.width,
                terminal_size.height,
                mouse.column,
                mouse.row,
                scroll,
            ) {
                self.last_task_click = None;
                self.detail_context = true;
                self.detail_context_scroll = 0;
                self.hovered_detail_child_task_id = Some(hit.task_id.clone());
                if let Some(index) = self
                    .store
                    .tasks
                    .iter()
                    .position(|item| item.task.id == hit.task_id)
                {
                    self.push_detail_navigation_state(DetailNavigationState {
                        task_id: current_task_id,
                        scroll,
                    });
                    self.widgets.table.select(Some(index));
                    self.overlay = Some(OverlayState::Detail { scroll: 0 });
                } else {
                    self.set_warning("child task is hidden by the current view");
                    if self.overlay.is_none() {
                        self.overlay = Some(OverlayState::Detail { scroll });
                    }
                }
                return true;
            }
        }

        let Some((target, _column, _row)) = crate::tui::ui::detail_metadata_target_at(
            terminal_size.width,
            terminal_size.height,
            mouse.column,
            mouse.row,
        ) else {
            return false;
        };
        self.last_task_click = None;
        self.detail_context = true;
        self.detail_context_scroll = scroll;
        match target {
            crate::tui::ui::DetailMetadataTarget::Status => {
                self.footer_choice_mode = Some(FooterChoiceMode::Status)
            }
            crate::tui::ui::DetailMetadataTarget::Priority => {
                self.footer_choice_mode = Some(FooterChoiceMode::Priority)
            }
        }
        if self.overlay.is_none() {
            self.overlay = Some(OverlayState::Detail { scroll });
        }
        true
    }

    fn handle_detail_mouse_move(&mut self, mouse: MouseEvent, terminal_size: Size) {
        let Some(OverlayState::Detail { scroll }) = self.overlay else {
            self.hovered_detail_child_task_id = None;
            return;
        };
        self.hovered_detail_child_task_id = self
            .store
            .selected_task(self.widgets.table.selected())
            .and_then(|item| {
                detail_child_task_at_position(
                    item,
                    terminal_size.width,
                    terminal_size.height,
                    mouse.column,
                    mouse.row,
                    scroll,
                )
            })
            .map(|hit| hit.task_id);
    }

    async fn handle_task_list_wheel(&mut self, delta: isize, terminal_size: Size) -> Result<()> {
        if self.overlay.is_some()
            || terminal_size.width < 70
            || terminal_size.height < 18
            || self.detail_underlay()
            || self.focus != Focus::Tasks
        {
            return Ok(());
        }

        let next = next_index(
            self.widgets.table.selected(),
            self.store.main_row_count(),
            delta,
            false,
        );
        self.widgets.table.select(next);
        Ok(())
    }

    fn dispatch_prefix_hint_key_scroll(&mut self, key: KeyEvent, terminal_size: Size) -> bool {
        if !key.modifiers.is_empty() && key.modifiers != KeyModifiers::SHIFT {
            return false;
        }
        let delta = match key.code {
            KeyCode::Down => 1,
            KeyCode::Up => -1,
            KeyCode::PageDown => terminal_size.height.saturating_sub(4).max(1) as isize,
            KeyCode::PageUp => -(terminal_size.height.saturating_sub(4).max(1) as isize),
            _ => return false,
        };
        self.dispatch_prefix_hint_scroll(delta, terminal_size)
    }

    fn dispatch_prefix_hint_scroll(&mut self, delta: isize, terminal_size: Size) -> bool {
        if !self.prefix_hints_active() {
            return false;
        }
        let cap = prefix_hint_scroll_cap(
            terminal_size.height,
            self.detail_underlay(),
            &self.pending_shortcut.labels(),
        );
        self.pending_shortcut_scroll = scroll_with_delta(self.pending_shortcut_scroll, delta, cap);
        true
    }

    fn prefix_hints_active(&self) -> bool {
        if self.pending_shortcut.is_empty() {
            return false;
        }
        !matches!(
            &self.overlay,
            Some(OverlayState::AddTask(_))
                if self.pending_shortcut.has_add_task_status_prefix()
                    || self.pending_shortcut.has_add_task_priority_prefix()
        )
    }

    fn dispatch_mouse_scroll(&mut self, kind: MouseEventKind, terminal_size: Size) -> bool {
        let delta = match kind {
            MouseEventKind::ScrollDown => 1,
            MouseEventKind::ScrollUp => -1,
            _ => return false,
        };

        match &mut self.overlay {
            Some(OverlayState::Help { scroll }) => {
                let cap = help_scroll_cap(terminal_size.height);
                *scroll = scroll_with_delta(*scroll, delta, cap);
                true
            }
            Some(OverlayState::DetailHelp { scroll }) => {
                let cap = detail_help_scroll_cap(terminal_size.height);
                *scroll = scroll_with_delta(*scroll, delta, cap);
                true
            }
            Some(OverlayState::Detail { scroll }) => {
                let task = self.store.selected_task(self.widgets.table.selected());
                *scroll = detail_scroll_with_delta(
                    *scroll,
                    delta,
                    terminal_size.width,
                    terminal_size.height,
                    task,
                );
                true
            }
            Some(OverlayState::TextPanel(state)) => {
                let cap = text_panel_scroll_cap(&state.lines);
                state.scroll = scroll_with_delta(state.scroll, delta, cap);
                true
            }
            _ => false,
        }
    }

    fn overlay_captures_input(&self) -> bool {
        self.overlay
            .as_ref()
            .is_some_and(OverlayState::captures_input)
    }

    pub(crate) async fn handle_normal_key(&mut self, code: KeyCode) -> Result<()> {
        if self.overlay_captures_input()
            && (code != KeyCode::Esc || self.pending_shortcut.is_empty())
        {
            return self
                .handle_overlay_key(KeyEvent::new(code, KeyModifiers::NONE))
                .await;
        }

        if code == KeyCode::Esc {
            if !self.pending_shortcut.cancel() {
                self.handle(Action::CancelOverlay).await?;
            }
            self.pending_shortcut_scroll = 0;
            return Ok(());
        }

        match self.pending_shortcut.resolve_normal(code) {
            NormalShortcutResolution::Action(action) => {
                self.pending_shortcut_scroll = 0;
                self.handle(action).await?;
            }
            NormalShortcutResolution::Prefix => {
                self.pending_shortcut_scroll = 0;
            }
            NormalShortcutResolution::Missing(label) => {
                self.pending_shortcut_scroll = 0;
                self.set_warning(format!("invalid shortcut: {label}"));
            }
        }
        Ok(())
    }

    pub(crate) async fn handle_overlay_key(&mut self, key: KeyEvent) -> Result<()> {
        self.handle_overlay_key_at_size(key, Size::new(80, 24))
            .await
    }

    async fn handle_overlay_key_at_size(
        &mut self,
        key: KeyEvent,
        terminal_size: Size,
    ) -> Result<()> {
        let Some(overlay) = self.overlay.take() else {
            return Ok(());
        };

        match overlay {
            OverlayState::Search(state) => self.handle_search_key(state, key).await?,
            OverlayState::Command { mut state } => match key.code {
                KeyCode::Esc => {}
                KeyCode::Enter => {
                    if let Some(action) = self.accept_command_input(state.input.as_str()) {
                        self.execute(action).await?;
                    } else {
                        self.overlay = Some(OverlayState::Command { state });
                    }
                }
                KeyCode::Tab | KeyCode::BackTab => {
                    self.complete_command_input(&mut state, key.code == KeyCode::BackTab);
                    self.overlay = Some(OverlayState::Command { state });
                }
                _ => {
                    state.input.handle_key(key);
                    state.reset_cycle();
                    self.overlay = Some(OverlayState::Command { state });
                }
            },
            overlay => {
                self.handle_generic_overlay_key(key, overlay, terminal_size)
                    .await?
            }
        }

        if self.detail_context && self.overlay.is_none() {
            self.restore_detail_overlay(true);
        }

        Ok(())
    }

    async fn handle_overlay_mouse(
        &mut self,
        overlay: OverlayState,
        mouse: MouseEvent,
        terminal_size: Size,
    ) -> Result<()> {
        let was_add_task_picker = matches!(
            &overlay,
            OverlayState::Picker(state)
                if matches!(
                    state.route,
                    OverlayRoute::AddTaskTitleProject | OverlayRoute::AddTaskTitlePriority
                )
        ) || matches!(
            &overlay,
            OverlayState::TagCombobox(state) if state.route == OverlayRoute::AddTaskTitleLabels
        );
        let outcome =
            crate::tui::overlay::handle_generic_overlay_mouse(overlay, mouse, terminal_size);
        self.apply_generic_overlay_outcome(outcome, false, false, was_add_task_picker)
            .await?;
        if self.detail_context && self.overlay.is_none() {
            self.restore_detail_overlay(true);
        }
        Ok(())
    }

    async fn handle_generic_overlay_key(
        &mut self,
        key: KeyEvent,
        overlay: OverlayState,
        terminal_size: Size,
    ) -> Result<()> {
        if let OverlayState::Detail { scroll } = overlay {
            if let Some(outcome) = self.handle_detail_shortcut(key, scroll).await? {
                self.overlay = outcome;
                return Ok(());
            }

            if let Some(delta) = detail_task_delta(key) {
                self.select_detail_task(delta);
                self.overlay = Some(OverlayState::Detail { scroll: 0 });
                return Ok(());
            }

            let overlay = OverlayState::Detail { scroll };
            let task = self.store.selected_task(self.widgets.table.selected());
            let outcome = handle_detail_overlay_key(
                key,
                overlay,
                terminal_size.width,
                terminal_size.height,
                task,
            );
            match outcome {
                OverlayOutcome::None(overlay) => self.overlay = Some(overlay),
                OverlayOutcome::Cancelled => self.cancel_authoring_overlay(),
                OverlayOutcome::Submitted(submit) => self.handle_overlay_submit(submit).await?,
            }
            return Ok(());
        }

        let had_add_task_status_prefix = self.pending_shortcut.has_add_task_status_prefix();
        if let Some(status) = self.pending_shortcut.take_add_task_status_request(key) {
            if let OverlayState::AddTask(state) = &overlay {
                if self.capture_add_task_state(state) {
                    self.overlay = Some(overlay);
                    self.set_add_task_status(status);
                }
            } else {
                self.overlay = Some(overlay);
            }
            return Ok(());
        }
        if had_add_task_status_prefix {
            self.pending_shortcut.clear();
            self.overlay = Some(overlay);
            if key.code != KeyCode::Esc {
                self.set_warning("invalid status shortcut");
            }
            return Ok(());
        }

        let had_add_task_priority_prefix = self.pending_shortcut.has_add_task_priority_prefix();
        if let Some(priority) = self.pending_shortcut.take_add_task_priority_request(key) {
            if let OverlayState::AddTask(state) = &overlay {
                if self.capture_add_task_state(state) {
                    self.overlay = Some(overlay);
                    self.set_add_task_priority(priority);
                }
            } else {
                self.overlay = Some(overlay);
            }
            return Ok(());
        }
        if had_add_task_priority_prefix {
            self.pending_shortcut.clear();
            self.overlay = Some(overlay);
            if key.code != KeyCode::Esc {
                self.set_warning("invalid priority shortcut");
            }
            return Ok(());
        }

        if self.pending_shortcut.take_editor_open_request(key) {
            match &overlay {
                OverlayState::MultilineInput(state)
                    if state.route == OverlayRoute::EditDescription =>
                {
                    self.open_description_external_editor(state.clone());
                }
                OverlayState::AddTask(state) if state.focus == AddTaskStep::Description => {
                    if self.capture_add_task_state(state) {
                        self.open_add_task_description_editor();
                    }
                }
                _ => self.overlay = Some(overlay),
            }
            return Ok(());
        }

        if is_editor_prefix_key(key)
            && matches!(
                &overlay,
                OverlayState::MultilineInput(state)
                    if state.route == OverlayRoute::EditDescription
            )
        {
            self.pending_shortcut.begin_editor_prefix();
            self.overlay = Some(overlay);
            return Ok(());
        }

        if let OverlayState::AddTask(state) = &overlay {
            if is_editor_prefix_key(key) {
                if state.focus == AddTaskStep::Description {
                    self.pending_shortcut.begin_editor_prefix();
                }
                self.overlay = Some(overlay);
                return Ok(());
            }
            if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('p') {
                if self.capture_add_task_state(state) {
                    self.begin_add_task_title_project();
                }
                return Ok(());
            }
            if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('l') {
                if self.capture_add_task_state(state) {
                    self.begin_add_task_title_labels();
                }
                return Ok(());
            }
            if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('r') {
                self.pending_shortcut.begin_add_task_priority_prefix();
                self.overlay = Some(overlay);
                return Ok(());
            }
            if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('t') {
                self.pending_shortcut.begin_add_task_status_prefix();
                self.overlay = Some(overlay);
                return Ok(());
            }
            if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('n') {
                let title = state.title.text.clone();
                let description = state.description.lines.join("\n");
                if self.capture_add_task_state(state) {
                    self.submit_add_task_title_natural(title, description)
                        .await?;
                }
                return Ok(());
            }
        }

        let scroll_cap = match overlay {
            OverlayState::DetailHelp { .. } => detail_help_scroll_cap(terminal_size.height),
            OverlayState::DatabaseStats { .. } => database_stats_scroll_cap(terminal_size.height),
            _ => help_scroll_cap(terminal_size.height),
        };
        let was_detail_help = matches!(overlay, OverlayState::DetailHelp { .. });
        let was_add_task_description_editor = matches!(
            &overlay,
            OverlayState::MultilineInput(state) if state.route == OverlayRoute::AddTaskDescription
        );
        let was_add_task_picker = matches!(
            &overlay,
            OverlayState::Picker(state)
                if matches!(
                    state.route,
                    OverlayRoute::AddTaskTitleProject | OverlayRoute::AddTaskTitlePriority
                )
        ) || matches!(
            &overlay,
            OverlayState::TagCombobox(state) if state.route == OverlayRoute::AddTaskTitleLabels
        );
        let outcome = crate::tui::overlay::handle_generic_overlay_key(key, overlay, scroll_cap);
        self.apply_generic_overlay_outcome(
            outcome,
            was_detail_help,
            was_add_task_description_editor,
            was_add_task_picker,
        )
        .await
    }

    async fn apply_generic_overlay_outcome(
        &mut self,
        outcome: OverlayOutcome,
        was_detail_help: bool,
        was_add_task_description_editor: bool,
        was_add_task_picker: bool,
    ) -> Result<()> {
        match outcome {
            OverlayOutcome::None(overlay) => self.overlay = Some(overlay),
            OverlayOutcome::Cancelled if was_detail_help => {
                self.overlay = Some(OverlayState::Detail { scroll: 0 })
            }
            OverlayOutcome::Cancelled if was_add_task_description_editor || was_add_task_picker => {
                self.begin_add_task_step()
            }
            OverlayOutcome::Cancelled if self.add_task_only => self.should_quit = true,
            OverlayOutcome::Cancelled => self.cancel_authoring_overlay(),
            OverlayOutcome::Submitted(submit) => self.handle_overlay_submit(submit).await?,
        }
        Ok(())
    }

    async fn handle_detail_shortcut(
        &mut self,
        key: KeyEvent,
        scroll: u16,
    ) -> Result<Option<Option<OverlayState>>> {
        if !key.modifiers.is_empty() && key.modifiers != KeyModifiers::SHIFT {
            return Ok(None);
        }

        match self.pending_shortcut.resolve_detail(key) {
            DetailShortcutResolution::Action(Action::GoBack) => {
                self.pending_shortcut_scroll = 0;
                if self.go_back_in_detail() {
                    return Ok(Some(self.overlay.take()));
                }
                self.detail_context = false;
                self.detail_context_scroll = 0;
                Ok(Some(None))
            }
            DetailShortcutResolution::Action(action) => {
                self.pending_shortcut_scroll = 0;
                self.detail_context = true;
                self.detail_context_scroll = scroll;
                self.execute(action).await?;
                if self.detail_context && self.overlay.is_none() {
                    self.restore_detail_overlay_at_scroll(true, scroll);
                }
                Ok(Some(self.overlay.take()))
            }
            DetailShortcutResolution::Prefix => {
                self.pending_shortcut_scroll = 0;
                Ok(Some(Some(OverlayState::Detail { scroll })))
            }
            DetailShortcutResolution::MissingAfterPrefix(label) => {
                self.pending_shortcut_scroll = 0;
                self.set_warning(format!("invalid shortcut: {label}"));
                Ok(Some(Some(OverlayState::Detail { scroll })))
            }
            DetailShortcutResolution::PassThrough => Ok(None),
        }
    }

    async fn handle(&mut self, action: Action) -> Result<()> {
        self.execute(action).await
    }

    pub(super) async fn execute(&mut self, action: Action) -> Result<()> {
        match action {
            Action::Quit => self.should_quit = true,
            Action::CancelOverlay => self.cancel_overlay(),
            Action::MoveDown => self.move_selection(1).await?,
            Action::MoveUp => self.move_selection(-1).await?,
            Action::MoveLeft => self.move_left(),
            Action::MoveRight => self.move_right(),
            Action::PreviousItem => self.previous_item(),
            Action::NextItem => self.next_item(),
            Action::First => self.select_edge(false).await?,
            Action::Last => self.select_edge(true).await?,
            Action::ToggleFocus => self.toggle_focus(),
            Action::ToggleSidebar => self.toggle_sidebar(),
            Action::ToggleDetail => self.activate_or_toggle_detail().await?,
            Action::GoBack => self.go_back().await?,
            Action::ToggleHelp => self.toggle_help_at_height(24),
            Action::BeginSearch => self.begin_search(),
            Action::BeginCommand => self.begin_command(),
            Action::Refresh => self.refresh().await?,
            Action::SetOrder(order) => self.set_sort(order).await?,
            Action::ReverseSort => self.reverse_sort().await?,
            Action::SetStatus(status) => self.update_status(status).await?,
            Action::SetPriority(priority) => self.set_exact_priority(priority).await?,
            Action::CyclePriority(reverse) => self.update_priority(reverse).await?,
            Action::CopyShortRef => self.copy_selected_ref(TaskRefKind::Short),
            Action::CopyDurableRef => self.copy_selected_ref(TaskRefKind::Durable),
            Action::BeginEditTitle => self.begin_edit_title(),
            Action::BeginEditDescription => self.begin_edit_description(),
            Action::BeginEditProject => self.begin_edit_project(),
            Action::BeginEditPriority => self.begin_edit_priority(),
            Action::BeginEditLabels => self.begin_edit_labels(),
            Action::Delete => self.begin_delete_task(),
            Action::Restore => self.update_deleted(false).await?,
            Action::BeginStatusPicker => self.begin_status_picker(),
            Action::BeginRenameProject => self.begin_rename_project(),
            Action::BeginDeleteProject => self.begin_delete_project(),
            Action::BeginAddProject => self.begin_add_project(),
            Action::BeginAddLabel => self.begin_add_label(),
            Action::BeginAddTask => self.begin_add_task().await?,
            Action::BeginAddNote => self.begin_add_note(),
            Action::BeginFilterLabel => self.begin_filter_label(),
            Action::BeginFilterPriority => self.begin_filter_priority(),
            Action::BeginScopeProject => self.begin_scope_project(),
            Action::BeginSwitchWorkspace => self.begin_switch_workspace().await?,
            Action::ClearFilters => self.clear_filters().await?,
            Action::ToggleDeletedFilter => self.toggle_deleted_filter().await?,
            Action::ShowView(view) => self.show_view(view).await?,
            Action::ShowWorkspaceScope => {
                self.show_scope(crate::tui::store::TaskScopeTarget::Workspace)
                    .await?
            }
            Action::BeginConflictList => self.open_conflict_list().await?,
            Action::ShowConflictDetails => self.show_conflict_details().await?,
            Action::NextConflict => self.move_to_conflict(1),
            Action::PreviousConflict => self.move_to_conflict(-1),
            Action::AcceptConflictLocal => {
                self.begin_conflict_resolution(ConflictResolutionChoice::Local)
                    .await?
            }
            Action::AcceptConflictRemote => {
                self.begin_conflict_resolution(ConflictResolutionChoice::Remote)
                    .await?
            }
            Action::BeginManualConflictMerge => self.begin_manual_conflict_merge().await?,
            Action::ShowConfigStatus => self.show_config_status()?,
            Action::ShowConfigInfo => self.show_config_info()?,
            Action::ShowConfigPaths => self.show_config_paths()?,
            Action::ShowDatabaseStats => self.show_database_stats().await?,
            Action::BeginConfigInit => self.begin_config_init()?,
            Action::BeginAddDependency => self.begin_add_dependency().await?,
            Action::BeginRemoveDependency => self.begin_remove_dependency(),
            Action::Undo => self.undo_last().await?,
            Action::ToggleMarkSelected => self.toggle_mark_selected(),
            Action::ToggleMarkAllInView => self.toggle_mark_all_in_view(),
            Action::ClearMarks => self.clear_marks(),
            Action::ToggleEpicExpanded => {
                let index = self.widgets.table.selected();
                if let Some(message) = self.store.toggle_selected_epic(index).await? {
                    self.set_info(message.message);
                    self.widgets.table.select(message.selected);
                }
            }
            Action::DetachEpicChild => {
                let index = self.widgets.table.selected();
                if let Some(message) = self.store.detach_selected_epic_child(index).await? {
                    self.set_info(message.message);
                    self.widgets.table.select(message.selected);
                }
            }
            Action::PromoteEpicChild => {
                let index = self.widgets.table.selected();
                if let Some(message) = self.store.promote_selected_epic_child(index).await? {
                    self.set_info(message.message);
                    self.widgets.table.select(message.selected);
                }
            }
            Action::Planned { name, reason } => {
                self.set_warning(format!(":{name} is not yet implemented: {reason}"));
            }
            Action::Disabled { name, reason } => {
                self.set_warning(format!(":{name} is disabled: {reason}"));
            }
            Action::AcceptCommand
            | Action::CancelCommand
            | Action::BackspaceCommand
            | Action::CommandChar(_)
            | Action::AcceptSearch
            | Action::CancelSearch
            | Action::BackspaceSearch
            | Action::SearchChar(_)
            | Action::None => {}
        }
        Ok(())
    }

    fn accept_command_input(&mut self, input: &str) -> Option<Action> {
        match lookup_command(input) {
            CommandLookup::Found(action) => {
                self.pending_shortcut.clear();
                Some(action)
            }
            CommandLookup::Empty => {
                self.set_info("empty command");
                None
            }
            CommandLookup::Ambiguous => {
                self.set_warning(format!("ambiguous command: {}", input.trim()));
                None
            }
            CommandLookup::Missing => {
                self.set_warning(format!("unknown command: {}", input.trim()));
                None
            }
        }
    }

    fn complete_command_input(&mut self, state: &mut CommandState, reverse: bool) {
        let cycle_input = state
            .cycle_input
            .clone()
            .unwrap_or_else(|| state.input.text.clone());
        let options = command_cycle_options(&cycle_input);
        if options.len() > 1 {
            state.cycle_index = if state.cycle_input.is_some() {
                if reverse {
                    state
                        .cycle_index
                        .checked_sub(1)
                        .unwrap_or(options.len().saturating_sub(1))
                } else {
                    (state.cycle_index + 1) % options.len()
                }
            } else if reverse {
                options.len().saturating_sub(1)
            } else {
                0
            };
            state.cycle_input = Some(cycle_input);
            let completion = options[state.cycle_index].to_string();
            state.input.text = completion;
            state.input.cursor = state.input.text.len();
            state.highlighted = Some(state.input.text.clone());
            self.set_info(format!(
                "command {} of {}",
                state.cycle_index + 1,
                options.len()
            ));
            return;
        }

        let highlighted = state.highlighted.clone();
        state.reset_cycle();
        state.highlighted = highlighted;
        match complete_command(state.input.as_str()) {
            CommandCompletion::Completed(completion) => {
                state.input.text = completion;
                state.input.cursor = state.input.text.len();
                state.highlighted = Some(state.input.text.clone());
            }
            CommandCompletion::Empty => self.set_info("type a command prefix to complete"),
            CommandCompletion::Missing => self.set_warning(format!(
                "no command matches: {}",
                state.input.as_str().trim()
            )),
            CommandCompletion::Unchanged => self.set_info("no further completion"),
        }
    }

    pub(super) fn toggle_help_at_height(&mut self, _terminal_height: u16) {
        match self.overlay {
            Some(OverlayState::Help { .. }) => self.overlay = None,
            Some(OverlayState::DetailHelp { .. }) => {
                self.overlay = Some(OverlayState::Detail { scroll: 0 })
            }
            Some(OverlayState::Detail { .. }) => {
                self.overlay = Some(OverlayState::DetailHelp { scroll: 0 })
            }
            _ => self.overlay = Some(OverlayState::Help { scroll: 0 }),
        }
    }
}
