use reovim_driver_input::KeymapQuery;

use super::*;

fn key(c: char) -> KeyEvent {
    KeyEvent::new(KeyCode::Char(c))
}

fn test_state() -> ModeState {
    ModeState::new(MicroscopeMode::PICKER_ID)
}

/// Mock keymap that always returns `NotFound`.
struct NotFoundKeymap;

#[cfg_attr(coverage_nightly, coverage(off))]
impl KeymapQuery for NotFoundKeymap {
    fn query(&self, _mode: &ModeId, _keys: &KeySequence) -> KeyLookupState {
        KeyLookupState::NotFound
    }
}

fn resolve_input(keymap: &impl KeymapQuery) -> ResolveInput<'_> {
    static EMPTY_KEYS: KeySequence = KeySequence::new();
    static MODE: ModeId = MicroscopeMode::PICKER_ID;
    ResolveInput::new(&EMPTY_KEYS, &MODE, keymap)
}

#[test]
fn resolver_new() {
    let resolver = MicroscopeResolver::new();
    assert_eq!(resolver.mode_id(), &MicroscopeMode::PICKER_ID);
}

#[test]
fn resolver_default() {
    let resolver = MicroscopeResolver::default();
    assert_eq!(resolver.mode_id(), &MicroscopeMode::PICKER_ID);
}

#[test]
fn insert_printable_chars() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = NotFoundKeymap;
    let input = resolve_input(&keymap);

    let result = resolver.resolve_with_keymap(&key('m'), &mut state, &input);
    assert!(matches!(result, ResolveResult::InsertChar { char: 'm', .. }));

    let result = resolver.resolve_with_keymap(&key(' '), &mut state, &input);
    assert!(matches!(result, ResolveResult::InsertChar { char: ' ', .. }));
}

#[test]
fn ctrl_char_not_insertable() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = NotFoundKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::with_modifiers(KeyCode::Char('n'), Modifiers::CTRL);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    // C-n not in keymap → NotHandled
    assert!(matches!(result, ResolveResult::NotHandled));
}

#[test]
fn alt_char_not_insertable() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = NotFoundKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::with_modifiers(KeyCode::Char('x'), Modifiers::ALT);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::NotHandled));
}

#[test]
fn escape_not_handled_without_binding() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = NotFoundKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::new(KeyCode::Escape);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::NotHandled));
}

#[test]
fn backspace_not_handled_without_binding() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = NotFoundKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::new(KeyCode::Backspace);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::NotHandled));
}

#[test]
fn no_inheritance() {
    let resolver = MicroscopeResolver::new();
    assert!(resolver.inherits_from().is_none());
}

#[test]
fn reset_is_noop() {
    let mut resolver = MicroscopeResolver::new();
    resolver.reset();
    assert_eq!(resolver.mode_id(), &MicroscopeMode::PICKER_ID);
}

/// Mock keymap that returns `ExactOnly` for Escape.
struct EscapeKeymap;

#[cfg_attr(coverage_nightly, coverage(off))]
impl KeymapQuery for EscapeKeymap {
    fn query(&self, _mode: &ModeId, keys: &KeySequence) -> KeyLookupState {
        if keys.len() == 1 && matches!(keys.as_slice()[0].code, KeyCode::Escape) {
            KeyLookupState::ExactOnly(crate::ids::CLOSE)
        } else {
            KeyLookupState::NotFound
        }
    }
}

#[test]
fn escape_executes_with_binding() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = EscapeKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::new(KeyCode::Escape);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::Execute(..)));
}

/// Mock keymap that returns `PrefixOnly` for certain keys.
struct PrefixKeymap;

#[cfg_attr(coverage_nightly, coverage(off))]
impl KeymapQuery for PrefixKeymap {
    fn query(&self, _mode: &ModeId, _keys: &KeySequence) -> KeyLookupState {
        KeyLookupState::PrefixOnly
    }
}

#[test]
fn prefix_returns_pending() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = PrefixKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::new(KeyCode::Escape);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::Pending));
}

/// Mock keymap that returns `ExactWithLonger`.
struct ExactWithLongerKeymap;

#[cfg_attr(coverage_nightly, coverage(off))]
impl KeymapQuery for ExactWithLongerKeymap {
    fn query(&self, _mode: &ModeId, _keys: &KeySequence) -> KeyLookupState {
        KeyLookupState::ExactWithLonger {
            exact: crate::ids::CLOSE,
        }
    }
}

#[test]
fn exact_with_longer_executes() {
    let resolver = MicroscopeResolver::new();
    let mut state = test_state();
    let keymap = ExactWithLongerKeymap;
    let input = resolve_input(&keymap);

    let k = KeyEvent::new(KeyCode::Enter);
    let result = resolver.resolve_with_keymap(&k, &mut state, &input);
    assert!(matches!(result, ResolveResult::Execute(..)));
}
