use crate::config::AiMode;
use egui::Key;

/// Keybinding actions that can be triggered globally
#[derive(Debug, Clone, PartialEq)]
pub enum KeyAction {
    /// Accept/Allow a pending permission request
    AcceptPermission,
    /// Deny a pending permission request
    DenyPermission,
    /// Tentatively accept, waiting for message (Shift+1)
    TentativeAccept,
    /// Tentatively deny, waiting for message (Shift+2)
    TentativeDeny,
    /// Allow always — add to session allowlist and accept (3)
    AllowAlways,
    /// Tentatively allow always, waiting for message (Shift+3)
    TentativeAllowAlways,
    /// Cancel tentative state (Escape when tentative)
    CancelTentative,
    /// Switch to agent by number (0-indexed)
    SwitchToAgent(usize),
    /// Cycle to next agent
    NextAgent,
    /// Cycle to previous agent
    PreviousAgent,
    /// Spawn a new agent (Ctrl+T)
    NewAgent,
    /// Interrupt/stop the current AI operation
    Interrupt,
    /// Toggle between scene view and classic view
    ToggleView,
    /// Cycle permission mode: Default → Plan → AcceptEdits (Ctrl+M)
    CyclePermissionMode,
    /// Toggle dave-side "Auto Accept All" on/off (Ctrl+Shift+M). Deliberately a
    /// distinct chord from the Ctrl+M mode cycle.
    ToggleAutoAcceptAll,
    /// Delete the active session
    DeleteActiveSession,
    /// Navigate to next item in focus queue (Ctrl+N)
    FocusQueueNext,
    /// Navigate to previous item in focus queue (Ctrl+P)
    FocusQueuePrev,
    /// Toggle Done status for current focus queue item (Ctrl+D)
    FocusQueueToggleDone,
    /// Toggle auto-steal focus mode (Ctrl+\)
    ToggleAutoSteal,
    /// Open external editor for composing input (Ctrl+G)
    OpenExternalEditor,
    /// Open a new terminal window (Ctrl+`)
    OpenTerminal,
    /// Clone the active agent with the same working directory (Ctrl+Shift+T)
    CloneAgent,
    /// Clear the active agent (Ctrl+Shift+C)
    ClearAgent,
    /// Rename the active agent (Ctrl+Shift+R)
    RenameAgent,
}

/// Check for keybinding actions.
/// Most keybindings use Ctrl modifier to avoid conflicts with text input.
/// Exception: 1/2 for permission responses work without Ctrl but only when no text input has focus.
/// In Chat mode, agentic-specific keybindings (scene view, plan mode, focus queue) are disabled.
pub fn check_keybindings(
    ctx: &egui::Context,
    has_pending_permission: bool,
    has_pending_question: bool,
    in_tentative_state: bool,
    ai_mode: AiMode,
) -> Option<KeyAction> {
    let is_agentic = ai_mode == AiMode::Agentic;

    // Escape in tentative state cancels the tentative mode (agentic only)
    if is_agentic
        && in_tentative_state
        && ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::Escape))
    {
        return Some(KeyAction::CancelTentative);
    }

    // Escape otherwise works to interrupt AI (even when text input has focus)
    if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, Key::Escape)) {
        return Some(KeyAction::Interrupt);
    }

    let ctrl = egui::Modifiers::CTRL;
    let ctrl_shift = egui::Modifiers::CTRL | egui::Modifiers::SHIFT;

    // Ctrl+J / Ctrl+K for cycling through agents/chats.
    // Previously Ctrl+Tab / Ctrl+Shift+Tab, but the chrome now consumes those
    // for app-level tab switching (see notedeck_chrome::chrome::cycle_app), so
    // they never reach dave. Works even with text input focus since the Ctrl
    // modifier makes them unambiguous.
    // Use matches_exact so Ctrl+Shift+K (ClearAgent) isn't swallowed here.
    if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::J)) {
        return Some(KeyAction::NextAgent);
    }
    if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::K)) {
        return Some(KeyAction::PreviousAgent);
    }

    // Focus queue navigation - agentic only
    if is_agentic {
        // Ctrl+N for higher priority (toward NeedsInput)
        if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::N)) {
            return Some(KeyAction::FocusQueueNext);
        }

        // Ctrl+P for lower priority (toward Done)
        if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::P)) {
            return Some(KeyAction::FocusQueuePrev);
        }
    }

    // Ctrl+Shift+T to clone the active agent (check before Ctrl+T) - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::T)) {
        return Some(KeyAction::CloneAgent);
    }

    // Ctrl+Shift+K to clear the active agent - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::K)) {
        return Some(KeyAction::ClearAgent);
    }

    // Ctrl+Shift+R to rename the active agent
    if ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::R)) {
        return Some(KeyAction::RenameAgent);
    }

    // Ctrl+T to spawn a new agent/chat
    if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::T)) {
        return Some(KeyAction::NewAgent);
    }

    // Ctrl+L to toggle between scene view and list view - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::L)) {
        return Some(KeyAction::ToggleView);
    }

    // Ctrl+G to open external editor for composing input
    if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::G)) {
        return Some(KeyAction::OpenExternalEditor);
    }

    // Ctrl+` to open a new terminal window
    if ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Backtick)) {
        return Some(KeyAction::OpenTerminal);
    }

    // Ctrl+M to cycle permission mode - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::M)) {
        return Some(KeyAction::CyclePermissionMode);
    }

    // Ctrl+Shift+M to toggle dave-side Auto Accept All - agentic only.
    // A distinct chord so it can't be reached by accidental cycling.
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl_shift) && i.key_pressed(Key::M)) {
        return Some(KeyAction::ToggleAutoAcceptAll);
    }

    // Ctrl+D to toggle Done status for current focus queue item - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::D)) {
        return Some(KeyAction::FocusQueueToggleDone);
    }

    // Ctrl+\ to toggle auto-steal focus mode (Ctrl+Space conflicts with macOS input source switching) - agentic only
    if is_agentic && ctx.input(|i| i.modifiers.matches_exact(ctrl) && i.key_pressed(Key::Backslash))
    {
        return Some(KeyAction::ToggleAutoSteal);
    }

    // Delete key to delete active session (only when no text input has focus)
    if !ctx.wants_keyboard_input() && ctx.input(|i| i.key_pressed(Key::Delete)) {
        return Some(KeyAction::DeleteActiveSession);
    }

    // Ctrl+1-9 for switching agents/chats (works even with text input focus)
    // Check this BEFORE permission bindings so Ctrl+number always switches agents
    if let Some(action) = ctx.input(|i| {
        if !i.modifiers.matches_exact(ctrl) {
            return None;
        }

        for (idx, key) in [
            Key::Num1,
            Key::Num2,
            Key::Num3,
            Key::Num4,
            Key::Num5,
            Key::Num6,
            Key::Num7,
            Key::Num8,
            Key::Num9,
        ]
        .iter()
        .enumerate()
        {
            if i.key_pressed(*key) {
                return Some(KeyAction::SwitchToAgent(idx));
            }
        }

        None
    }) {
        return Some(action);
    }

    // Permission keybindings - agentic only
    // When there's a pending permission (but NOT an AskUserQuestion):
    // - 1 = accept, 2 = deny (no modifiers)
    // - Shift+1 = tentative accept, Shift+2 = tentative deny (for adding message)
    // This is checked AFTER Ctrl+number so Ctrl bindings take precedence
    // IMPORTANT: Only handle these when no text input has focus, to avoid
    // capturing keypresses when user is typing a message in tentative state
    // AskUserQuestion uses number keys for option selection, so we skip these bindings
    if is_agentic && has_pending_permission && !has_pending_question && !ctx.wants_keyboard_input()
    {
        // Shift+1 = tentative accept, Shift+2 = tentative deny
        // Note: egui may report shifted keys as their symbol (e.g., Shift+1 as Exclamationmark)
        // We check for both the symbol key and Shift+Num key to handle different behaviors
        if let Some(action) = ctx.input_mut(|i| {
            // Shift+1: check for '!' (Exclamationmark) which egui reports on some systems
            if i.key_pressed(Key::Exclamationmark) {
                return Some(KeyAction::TentativeAccept);
            }
            // Shift+2: check with shift modifier (egui may report Num2 with shift held)
            if i.modifiers.shift && i.key_pressed(Key::Num2) {
                return Some(KeyAction::TentativeDeny);
            }
            // Shift+3: tentative allow always
            if i.modifiers.shift && i.key_pressed(Key::Num3) {
                return Some(KeyAction::TentativeAllowAlways);
            }
            None
        }) {
            return Some(action);
        }

        // Bare keypresses (no modifiers) for immediate accept/deny/always
        if let Some(action) = ctx.input(|i| {
            if !i.modifiers.any() {
                if i.key_pressed(Key::Num1) {
                    return Some(KeyAction::AcceptPermission);
                } else if i.key_pressed(Key::Num2) {
                    return Some(KeyAction::DenyPermission);
                } else if i.key_pressed(Key::Num3) {
                    return Some(KeyAction::AllowAlways);
                }
            }
            None
        }) {
            return Some(action);
        }
    }

    None
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::config::AiMode;
    use egui::{Key, Modifiers};
    use egui_kittest::Harness;

    /// Press `modifiers`+`key` in a headless egui frame and return whatever
    /// `check_keybindings` detects (agentic mode, no pending prompts).
    fn detect(modifiers: Modifiers, key: Key) -> Option<KeyAction> {
        // Accumulate: `press_key_modifiers` runs the key-down frame internally
        // and then a key-up frame, so we must not clobber the detection with the
        // later (keys-released) frame's `None`.
        let mut harness = Harness::new_ui_state(
            |ui, action: &mut Option<KeyAction>| {
                if let Some(a) = check_keybindings(ui.ctx(), false, false, false, AiMode::Agentic) {
                    *action = Some(a);
                }
            },
            None,
        );
        harness.run();
        harness.press_key_modifiers(modifiers, key);
        harness.state().clone()
    }

    #[test]
    fn ctrl_m_cycles_but_ctrl_shift_m_toggles_auto_accept_all() {
        // The safe cycle stays on Ctrl+M...
        assert_eq!(
            detect(Modifiers::CTRL, Key::M),
            Some(KeyAction::CyclePermissionMode),
        );
        // ...and the dangerous Auto Accept All needs the distinct Ctrl+Shift+M
        // chord, so it can't be triggered by the cycle key.
        assert_eq!(
            detect(Modifiers::CTRL | Modifiers::SHIFT, Key::M),
            Some(KeyAction::ToggleAutoAcceptAll),
        );
    }
}
