use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};

use super::render::TaskOverlayAction;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum KeyContext {
    Global,
    Main,
    Composer,
    SlashMenu,
    AgentsOverlay,
    EventsOverlay,
    ScrollOverlay,
    TasksOverlay,
    ModelPicker,
    ModelEffortPicker,
    DebugPromptInput,
}

impl KeyContext {
    pub(super) const fn label(self) -> &'static str {
        match self {
            KeyContext::Global => "global",
            KeyContext::Main => "main",
            KeyContext::Composer => "composer",
            KeyContext::SlashMenu => "slash menu",
            KeyContext::AgentsOverlay => "agents overlay",
            KeyContext::EventsOverlay => "events overlay",
            KeyContext::ScrollOverlay => "scroll overlays",
            KeyContext::TasksOverlay => "tasks overlay",
            KeyContext::ModelPicker => "model picker",
            KeyContext::ModelEffortPicker => "reasoning effort picker",
            KeyContext::DebugPromptInput => "debug prompt",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum TuiKeyAction {
    Quit,
    OpenHelp,
    HistoryPrevious,
    HistoryNext,
    ChatScroll(ScrollAction),
    Composer(ComposerAction),
    SlashMenu(SlashMenuAction),
    OverlayClose,
    OverlayAccept,
    OverlayMoveUp,
    OverlayMoveDown,
    OverlayScroll(ScrollAction),
    Task(TaskOverlayAction),
    ModelFilterBackspace,
    InsertChar(char),
    Ignore,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ComposerAction {
    Submit,
    Cancel,
    InsertNewline,
    Backspace,
    Delete,
    MoveLeft,
    MoveRight,
    MoveHome,
    MoveEnd,
    InsertTab,
    MoveToStart,
    MoveToEnd,
    DeleteToEnd,
    DeleteToStart,
    DeleteWord,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum SlashMenuAction {
    Dismiss,
    Previous,
    Next,
    Complete,
    Submit,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ScrollAction {
    Up,
    Down,
    PageUp,
    PageDown,
    Home,
    End,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct DefaultBindingHint {
    pub(super) context: KeyContext,
    pub(super) action: &'static str,
    pub(super) keys: &'static str,
}

pub(super) const DEFAULT_BINDING_HINTS: &[DefaultBindingHint] = &[
    DefaultBindingHint {
        context: KeyContext::Global,
        action: "quit",
        keys: "Ctrl+C",
    },
    DefaultBindingHint {
        context: KeyContext::Main,
        action: "help when composer is empty",
        keys: "?",
    },
    DefaultBindingHint {
        context: KeyContext::Main,
        action: "history when composer is empty",
        keys: "Up/Down",
    },
    DefaultBindingHint {
        context: KeyContext::Main,
        action: "scroll chat when composer has content",
        keys: "Up/Down",
    },
    DefaultBindingHint {
        context: KeyContext::Main,
        action: "scroll chat",
        keys: "PgUp/PgDn",
    },
    DefaultBindingHint {
        context: KeyContext::Composer,
        action: "submit",
        keys: "Enter",
    },
    DefaultBindingHint {
        context: KeyContext::Composer,
        action: "newline",
        keys: "Shift+Enter",
    },
    DefaultBindingHint {
        context: KeyContext::Composer,
        action: "readline edit",
        keys: "Ctrl+A/E/B/F/K/U/W/H/D",
    },
    DefaultBindingHint {
        context: KeyContext::SlashMenu,
        action: "select/complete/run/close",
        keys: "Up/Down Tab Enter Esc",
    },
    DefaultBindingHint {
        context: KeyContext::AgentsOverlay,
        action: "select/close",
        keys: "Up/Down Enter Esc",
    },
    DefaultBindingHint {
        context: KeyContext::TasksOverlay,
        action: "select/actions/close",
        keys: "Up/Down f/l/x/i Esc",
    },
    DefaultBindingHint {
        context: KeyContext::ScrollOverlay,
        action: "scroll/close",
        keys: "Up/Down PgUp/PgDn Home/End Esc",
    },
];

pub(super) fn resolve_key(context: KeyContext, key: KeyEvent) -> TuiKeyAction {
    match context {
        KeyContext::Global => resolve_global_key(key),
        KeyContext::Main => resolve_main_key(key),
        KeyContext::Composer | KeyContext::DebugPromptInput => resolve_composer_key(key),
        KeyContext::SlashMenu => resolve_slash_menu_key(key),
        KeyContext::AgentsOverlay => resolve_list_overlay_key(key),
        KeyContext::EventsOverlay => resolve_events_overlay_key(key),
        KeyContext::ScrollOverlay => resolve_scroll_overlay_key(key),
        KeyContext::TasksOverlay => resolve_tasks_overlay_key(key),
        KeyContext::ModelPicker => resolve_model_picker_key(key),
        KeyContext::ModelEffortPicker => resolve_list_overlay_key(key),
    }
}

pub(super) fn status_hint(context: KeyContext, slash_menu_visible: bool) -> &'static str {
    if slash_menu_visible {
        return "Slash: Up/Down select  Tab complete  Enter run  Esc close";
    }
    match context {
        KeyContext::Global | KeyContext::Main | KeyContext::Composer => {
            "/help commands  /state agent state  /transcript  PgUp/PgDn scroll  Ctrl+A/E edit  Ctrl+C quit"
        }
        KeyContext::SlashMenu => "Slash: Up/Down select  Tab complete  Enter run  Esc close",
        KeyContext::AgentsOverlay => "Agents: Up/Down, Enter select, Esc",
        KeyContext::EventsOverlay => "Events: Up/Down, PgUp/PgDn, Home/End, Esc",
        KeyContext::ScrollOverlay => "Up/Down, PgUp/PgDn, Home/End, Esc",
        KeyContext::TasksOverlay => "Tasks: Up/Down, PgUp/PgDn, Home/End, f/l/x/i actions, Esc",
        KeyContext::ModelPicker => {
            "Model: type filter, Backspace edit, Up/Down move, Enter select, Esc cancel"
        }
        KeyContext::ModelEffortPicker => "Reasoning effort: Up/Down move, Enter select, Esc back",
        KeyContext::DebugPromptInput => "Debug prompt: Enter confirm, Esc cancel",
    }
}

fn resolve_global_key(key: KeyEvent) -> TuiKeyAction {
    if key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('c') {
        TuiKeyAction::Quit
    } else {
        TuiKeyAction::Ignore
    }
}

fn resolve_main_key(key: KeyEvent) -> TuiKeyAction {
    match key.code {
        KeyCode::Char('?') => TuiKeyAction::OpenHelp,
        KeyCode::Up => TuiKeyAction::HistoryPrevious,
        KeyCode::Down => TuiKeyAction::HistoryNext,
        KeyCode::PageUp => TuiKeyAction::ChatScroll(ScrollAction::PageUp),
        KeyCode::PageDown => TuiKeyAction::ChatScroll(ScrollAction::PageDown),
        _ => resolve_composer_key(key),
    }
}

fn resolve_composer_key(key: KeyEvent) -> TuiKeyAction {
    if key.modifiers.contains(KeyModifiers::CONTROL) {
        return match key.code {
            KeyCode::Char('a') => TuiKeyAction::Composer(ComposerAction::MoveToStart),
            KeyCode::Char('e') => TuiKeyAction::Composer(ComposerAction::MoveToEnd),
            KeyCode::Char('b') => TuiKeyAction::Composer(ComposerAction::MoveLeft),
            KeyCode::Char('f') => TuiKeyAction::Composer(ComposerAction::MoveRight),
            KeyCode::Char('k') => TuiKeyAction::Composer(ComposerAction::DeleteToEnd),
            KeyCode::Char('u') => TuiKeyAction::Composer(ComposerAction::DeleteToStart),
            KeyCode::Char('w') => TuiKeyAction::Composer(ComposerAction::DeleteWord),
            KeyCode::Char('h') => TuiKeyAction::Composer(ComposerAction::Backspace),
            KeyCode::Char('d') => TuiKeyAction::Composer(ComposerAction::Delete),
            _ => TuiKeyAction::Ignore,
        };
    }

    match key.code {
        KeyCode::Enter if key.modifiers.contains(KeyModifiers::SHIFT) => {
            TuiKeyAction::Composer(ComposerAction::InsertNewline)
        }
        KeyCode::Enter => TuiKeyAction::Composer(ComposerAction::Submit),
        KeyCode::Esc => TuiKeyAction::Composer(ComposerAction::Cancel),
        KeyCode::Backspace => TuiKeyAction::Composer(ComposerAction::Backspace),
        KeyCode::Delete => TuiKeyAction::Composer(ComposerAction::Delete),
        KeyCode::Left => TuiKeyAction::Composer(ComposerAction::MoveLeft),
        KeyCode::Right => TuiKeyAction::Composer(ComposerAction::MoveRight),
        KeyCode::Home => TuiKeyAction::Composer(ComposerAction::MoveHome),
        KeyCode::End => TuiKeyAction::Composer(ComposerAction::MoveEnd),
        KeyCode::Tab => TuiKeyAction::Composer(ComposerAction::InsertTab),
        KeyCode::Char(ch) => TuiKeyAction::InsertChar(ch),
        _ => TuiKeyAction::Ignore,
    }
}

fn resolve_slash_menu_key(key: KeyEvent) -> TuiKeyAction {
    if key.code == KeyCode::Enter && key.modifiers.contains(KeyModifiers::SHIFT) {
        return TuiKeyAction::Ignore;
    }
    match key.code {
        KeyCode::Esc => TuiKeyAction::SlashMenu(SlashMenuAction::Dismiss),
        KeyCode::Up => TuiKeyAction::SlashMenu(SlashMenuAction::Previous),
        KeyCode::Down => TuiKeyAction::SlashMenu(SlashMenuAction::Next),
        KeyCode::Char('p') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            TuiKeyAction::SlashMenu(SlashMenuAction::Previous)
        }
        KeyCode::Char('n') if key.modifiers.contains(KeyModifiers::CONTROL) => {
            TuiKeyAction::SlashMenu(SlashMenuAction::Next)
        }
        KeyCode::Tab => TuiKeyAction::SlashMenu(SlashMenuAction::Complete),
        KeyCode::Enter => TuiKeyAction::SlashMenu(SlashMenuAction::Submit),
        _ => TuiKeyAction::Ignore,
    }
}

fn resolve_list_overlay_key(key: KeyEvent) -> TuiKeyAction {
    match key.code {
        KeyCode::Esc => TuiKeyAction::OverlayClose,
        KeyCode::Enter => TuiKeyAction::OverlayAccept,
        KeyCode::Up | KeyCode::Char('k') => TuiKeyAction::OverlayMoveUp,
        KeyCode::Down | KeyCode::Char('j') => TuiKeyAction::OverlayMoveDown,
        _ => TuiKeyAction::Ignore,
    }
}

fn resolve_events_overlay_key(key: KeyEvent) -> TuiKeyAction {
    match key.code {
        KeyCode::Esc => TuiKeyAction::OverlayClose,
        KeyCode::Up => TuiKeyAction::OverlayMoveUp,
        KeyCode::Down => TuiKeyAction::OverlayMoveDown,
        _ => scroll_action_for_key(key.code)
            .map(TuiKeyAction::OverlayScroll)
            .unwrap_or(TuiKeyAction::Ignore),
    }
}

fn resolve_scroll_overlay_key(key: KeyEvent) -> TuiKeyAction {
    if key.code == KeyCode::Esc {
        return TuiKeyAction::OverlayClose;
    }
    scroll_action_for_key(key.code)
        .map(TuiKeyAction::OverlayScroll)
        .unwrap_or(TuiKeyAction::Ignore)
}

fn resolve_tasks_overlay_key(key: KeyEvent) -> TuiKeyAction {
    match key.code {
        KeyCode::Esc => TuiKeyAction::OverlayClose,
        KeyCode::Up => TuiKeyAction::OverlayMoveUp,
        KeyCode::Down => TuiKeyAction::OverlayMoveDown,
        KeyCode::Char('f') | KeyCode::Char('F') => {
            TuiKeyAction::Task(TaskOverlayAction::FullOutput)
        }
        KeyCode::Char('l') | KeyCode::Char('L') => {
            TuiKeyAction::Task(TaskOverlayAction::FollowOutput)
        }
        KeyCode::Char('x') | KeyCode::Char('X') => TuiKeyAction::Task(TaskOverlayAction::Stop),
        KeyCode::Char('i') | KeyCode::Char('I') => TuiKeyAction::Task(TaskOverlayAction::Input),
        _ => scroll_action_for_key(key.code)
            .map(TuiKeyAction::OverlayScroll)
            .unwrap_or(TuiKeyAction::Ignore),
    }
}

fn resolve_model_picker_key(key: KeyEvent) -> TuiKeyAction {
    match key.code {
        KeyCode::Esc => TuiKeyAction::OverlayClose,
        KeyCode::Enter => TuiKeyAction::OverlayAccept,
        KeyCode::Up | KeyCode::Char('k') => TuiKeyAction::OverlayMoveUp,
        KeyCode::Down | KeyCode::Char('j') => TuiKeyAction::OverlayMoveDown,
        KeyCode::Backspace => TuiKeyAction::ModelFilterBackspace,
        KeyCode::Char(ch) if !key.modifiers.contains(KeyModifiers::CONTROL) => {
            TuiKeyAction::InsertChar(ch)
        }
        _ => TuiKeyAction::Ignore,
    }
}

pub(super) fn scroll_action_for_key(key: KeyCode) -> Option<ScrollAction> {
    match key {
        KeyCode::Up => Some(ScrollAction::Up),
        KeyCode::Down => Some(ScrollAction::Down),
        KeyCode::PageUp => Some(ScrollAction::PageUp),
        KeyCode::PageDown => Some(ScrollAction::PageDown),
        KeyCode::Home => Some(ScrollAction::Home),
        KeyCode::End => Some(ScrollAction::End),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn key(code: KeyCode) -> KeyEvent {
        KeyEvent::new(code, KeyModifiers::NONE)
    }

    fn ctrl(ch: char) -> KeyEvent {
        KeyEvent::new(KeyCode::Char(ch), KeyModifiers::CONTROL)
    }

    #[test]
    fn composer_readline_bindings_resolve_to_actions() {
        assert_eq!(
            resolve_key(KeyContext::Composer, ctrl('a')),
            TuiKeyAction::Composer(ComposerAction::MoveToStart)
        );
        assert_eq!(
            resolve_key(KeyContext::Composer, ctrl('k')),
            TuiKeyAction::Composer(ComposerAction::DeleteToEnd)
        );
        assert_eq!(
            resolve_key(KeyContext::Composer, key(KeyCode::Enter)),
            TuiKeyAction::Composer(ComposerAction::Submit)
        );
        assert_eq!(
            resolve_key(
                KeyContext::Composer,
                KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)
            ),
            TuiKeyAction::Composer(ComposerAction::InsertNewline)
        );
    }

    #[test]
    fn slash_menu_conflicts_resolve_inside_slash_context() {
        assert_eq!(
            resolve_key(KeyContext::SlashMenu, key(KeyCode::Enter)),
            TuiKeyAction::SlashMenu(SlashMenuAction::Submit)
        );
        assert_eq!(
            resolve_key(
                KeyContext::SlashMenu,
                KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)
            ),
            TuiKeyAction::Ignore
        );
        assert_eq!(
            resolve_key(KeyContext::SlashMenu, ctrl('n')),
            TuiKeyAction::SlashMenu(SlashMenuAction::Next)
        );
    }

    #[test]
    fn task_overlay_keys_resolve_to_selection_scroll_and_task_actions() {
        assert_eq!(
            resolve_key(KeyContext::TasksOverlay, key(KeyCode::Up)),
            TuiKeyAction::OverlayMoveUp
        );
        assert_eq!(
            resolve_key(KeyContext::TasksOverlay, key(KeyCode::PageDown)),
            TuiKeyAction::OverlayScroll(ScrollAction::PageDown)
        );
        assert_eq!(
            resolve_key(KeyContext::TasksOverlay, key(KeyCode::Char('x'))),
            TuiKeyAction::Task(TaskOverlayAction::Stop)
        );
    }

    #[test]
    fn default_binding_hints_cover_required_contexts() {
        assert!(DEFAULT_BINDING_HINTS
            .iter()
            .any(|hint| hint.context == KeyContext::Composer));
        assert!(DEFAULT_BINDING_HINTS
            .iter()
            .any(|hint| hint.context == KeyContext::SlashMenu));
        assert!(DEFAULT_BINDING_HINTS
            .iter()
            .any(|hint| hint.context == KeyContext::TasksOverlay));
    }
}
