//! Built-in default keybindings.
//!
//! These are Sioyek/zathura/vim-inspired defaults. Every binding refers to an
//! action constant from [`crate::actions`].

use crate::{actions as a, binding::BindingSet};

/// Return a [`BindingSet`] populated with the built-in default bindings.
///
/// Multiple key sequences may map to the same action (e.g. both `j` and
/// `<Down>` trigger `nav.down`).
pub fn default_bindings() -> BindingSet {
    let mut set = BindingSet::new();

    // Helper to register a binding, panicking if the sequence is invalid.
    // Invalid sequences in defaults are a programming error, not a user error.
    macro_rules! bind {
        ($seq:expr => $action:expr) => {
            set.add_parsed($seq, $action)
                .unwrap_or_else(|e| panic!("invalid default binding '{}': {}", $seq, e));
        };
    }

    // ── Navigation ────────────────────────────────────────────────────────────
    bind!("j"         => a::NAV_DOWN);
    bind!("<Down>"    => a::NAV_DOWN);
    bind!("k"         => a::NAV_UP);
    bind!("<Up>"      => a::NAV_UP);
    bind!("gg"        => a::NAV_TOP);
    bind!("G"         => a::NAV_BOTTOM);
    bind!("<C-d>"     => a::NAV_PAGE_DOWN);
    bind!("<C-u>"     => a::NAV_PAGE_UP);
    bind!("<PageDown>" => a::NAV_PAGE_DOWN);
    bind!("<PageUp>"  => a::NAV_PAGE_UP);

    // ── Pane focus ────────────────────────────────────────────────────────────
    bind!("<Tab>"     => a::FOCUS_NEXT);
    bind!("<S-Tab>"   => a::FOCUS_PREV);
    bind!("H"         => a::FOCUS_LEFT);
    bind!("M"         => a::FOCUS_CENTER);
    bind!("L"         => a::FOCUS_RIGHT);

    // ── Library tree ──────────────────────────────────────────────────────────
    bind!("zo"        => a::TREE_EXPAND);
    bind!("zc"        => a::TREE_COLLAPSE);
    bind!("za"        => a::TREE_TOGGLE);

    // ── Pane navigation ───────────────────────────────────────────────────────
    bind!("h"         => a::FOCUS_PREV);
    bind!("l"         => a::FOCUS_NEXT);

    // ── PDF viewer ────────────────────────────────────────────────────────────
    bind!("J"         => a::PDF_PAGE_NEXT);
    bind!("K"         => a::PDF_PAGE_PREV);

    // ── Item actions ──────────────────────────────────────────────────────────
    bind!("<Enter>"   => a::ACTION_OPEN);
    bind!("e"         => a::ACTION_EDIT);
    bind!("d"         => a::ACTION_DELETE);
    bind!("n"         => a::ACTION_NEW);

    // ── Tabs ──────────────────────────────────────────────────────────────────
    // vim-style gt/gT; g is already a prefix from gg (nav.top).
    bind!("gt"        => a::TAB_NEXT);
    bind!("gT"        => a::TAB_PREV);
    bind!("q"         => a::TAB_CLOSE);
    bind!("<C-w>"     => a::TAB_CLOSE);

    // ── Input modes ───────────────────────────────────────────────────────────
    bind!(":"         => a::MODE_COMMAND);
    bind!("/"         => a::MODE_SEARCH);
    bind!("<Esc>"     => a::MODE_NORMAL);
    bind!("b"         => a::MODE_BIBTEX);

    set
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{
        binding::LookupResult,
        key::{Key, KeyCode},
    };

    fn defaults() -> BindingSet {
        default_bindings()
    }

    #[test]
    fn j_maps_to_nav_down() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('j')]),
            LookupResult::Exact(a::NAV_DOWN.into())
        );
    }

    #[test]
    fn arrow_down_maps_to_nav_down() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::plain(KeyCode::ArrowDown)]),
            LookupResult::Exact(a::NAV_DOWN.into())
        );
    }

    #[test]
    fn gg_maps_to_nav_top() {
        let d = defaults();
        // 'g' alone is a prefix.
        assert_eq!(d.lookup(&[Key::char('g')]), LookupResult::Prefix);
        // 'gg' is the full binding.
        assert_eq!(
            d.lookup(&[Key::char('g'), Key::char('g')]),
            LookupResult::Exact(a::NAV_TOP.into())
        );
    }

    #[test]
    fn capital_g_maps_to_nav_bottom() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('G')]),
            LookupResult::Exact(a::NAV_BOTTOM.into())
        );
    }

    #[test]
    fn zo_maps_to_tree_expand() {
        let d = defaults();
        assert_eq!(d.lookup(&[Key::char('z')]), LookupResult::Prefix);
        assert_eq!(
            d.lookup(&[Key::char('z'), Key::char('o')]),
            LookupResult::Exact(a::TREE_EXPAND.into())
        );
    }

    #[test]
    fn zc_maps_to_tree_collapse() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('z'), Key::char('c')]),
            LookupResult::Exact(a::TREE_COLLAPSE.into())
        );
    }

    #[test]
    fn h_maps_to_focus_prev() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('h')]),
            LookupResult::Exact(a::FOCUS_PREV.into())
        );
    }

    #[test]
    fn l_maps_to_focus_next() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('l')]),
            LookupResult::Exact(a::FOCUS_NEXT.into())
        );
    }

    #[test]
    fn colon_maps_to_mode_command() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char(':')]),
            LookupResult::Exact(a::MODE_COMMAND.into())
        );
    }

    #[test]
    fn escape_maps_to_mode_normal() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::plain(KeyCode::Escape)]),
            LookupResult::Exact(a::MODE_NORMAL.into())
        );
    }

    #[test]
    fn tab_maps_to_focus_next() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::plain(KeyCode::Tab)]),
            LookupResult::Exact(a::FOCUS_NEXT.into())
        );
    }

    #[test]
    fn shift_tab_maps_to_focus_prev() {
        use crate::key::parse_sequence;
        let d = defaults();
        let shift_tab = &parse_sequence("<S-Tab>").unwrap()[0];
        assert_eq!(
            d.lookup(&[shift_tab.clone()]),
            LookupResult::Exact(a::FOCUS_PREV.into())
        );
    }

    #[test]
    fn gt_maps_to_tab_next() {
        let d = defaults();
        // g is still a prefix (gg, gt, gT all share it)
        assert_eq!(d.lookup(&[Key::char('g')]), LookupResult::Prefix);
        assert_eq!(
            d.lookup(&[Key::char('g'), Key::char('t')]),
            LookupResult::Exact(a::TAB_NEXT.into())
        );
    }

    #[test]
    fn capital_gt_maps_to_tab_prev() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('g'), Key::char('T')]),
            LookupResult::Exact(a::TAB_PREV.into())
        );
    }

    #[test]
    fn q_maps_to_tab_close() {
        let d = defaults();
        assert_eq!(
            d.lookup(&[Key::char('q')]),
            LookupResult::Exact(a::TAB_CLOSE.into())
        );
    }

    #[test]
    fn all_default_sequences_are_valid() {
        // Constructing the defaults panics on any invalid sequence, so this
        // test implicitly validates every binding in default_bindings().
        let _ = default_bindings();
    }
}
