use crate::commands::{self, Command};
use helix_core::{hashmap, state};
use std::collections::HashMap;

// Kakoune-inspired:
// mode = {
//      normal = {
//          q = record_macro
//          w = (next) word
//          W = next WORD
//          e = end of word
//          E = end of WORD
//          r =
//          t = 'till char
//          y = yank
//          u = undo
//          U = redo
//          i = insert
//          I = INSERT (start of line)
//          o = open below (insert on new line below)
//          O = open above (insert on new line above)
//          p = paste (before cursor)
//          P = PASTE (after cursor)
//          ` =
//          [ = select to text object start (alt = select whole object)
//          ] = select to text object end
//          { = extend to inner object start
//          } = extend to inner object end
//          a = append
//          A = APPEND (end of line)
//          s = split
//          S = select
//          d = delete()
//          f = find_char()
//          g = goto (gg, G, gc, gd, etc)
//
//          h = move_char_left(n)
//          j = move_line_down(n)
//          k = move_line_up(n)
//          l = move_char_right(n)
//          : = command line
//          ; = collapse selection to cursor
//          " = use register
//          ` = convert case? (to lower) (alt = swap case)
//          ~ = convert to upper case
//          . = repeat last command
//          \ = disable hook?
//          / = search
//          > = indent
//          < = deindent
//          % = select whole buffer (in vim = jump to matching bracket)
//          * = search pattern in selection
//          ( = rotate main selection backward
//          ) = rotate main selection forward
//          - = trim selections? (alt = merge contiguous sel together)
//          @ = convert tabs to spaces
//          & = align cursor
//          ? = extend to next given regex match (alt = to prev)
//
//          in kakoune these are alt-h alt-l / gh gl
//                              select from curs to begin end / move curs to begin end
//          0 = start of line
//          ^ = start of line (first non blank char)
//          $ = end of line
//
//          z = save selections
//          Z = restore selections
//          x = select line
//          X = extend line
//          c = change selected text
//          C = copy selection?
//          v = view menu (viewport manipulation)
//          b = select to previous word start
//          B = select to previous WORD start
//
//
//
//
//
//
//          = = align?
//          + =
//      }
// }

#[cfg(feature = "term")]
pub use crossterm::event::{KeyCode, KeyEvent as Key, KeyModifiers as Modifiers};

// TODO: could be trie based
type Keymap = HashMap<Vec<Key>, Command>;
type Keymaps = HashMap<state::Mode, Keymap>;

macro_rules! key {
    ($ch:expr) => {
        Key {
            code: KeyCode::Char($ch),
            modifiers: Modifiers::NONE,
        }
    };
}

macro_rules! shift {
    ($ch:expr) => {
        Key {
            code: KeyCode::Char($ch),
            modifiers: Modifiers::SHIFT,
        }
    };
}

macro_rules! ctrl {
    ($ch:expr) => {
        Key {
            code: KeyCode::Char($ch),
            modifiers: Modifiers::CONTROL,
        }
    };
}

pub fn default() -> Keymaps {
    hashmap!(
        state::Mode::Normal =>
            // as long as you cast the first item, rust is able to infer the other cases
            hashmap!(
                vec![key!('h')] => commands::move_char_left as Command,
                vec![key!('j')] => commands::move_line_down,
                vec![key!('k')] => commands::move_line_up,
                vec![key!('0')] => commands::move_line_start,
                vec![key!('$')] => commands::move_line_end,
                vec![key!('l')] => commands::move_char_right,
                vec![shift!('H')] => commands::extend_char_left,
                vec![shift!('J')] => commands::extend_line_down,
                vec![shift!('K')] => commands::extend_line_up,
                vec![shift!('L')] => commands::extend_char_right,
                vec![key!('w')] => commands::move_next_word_start,
                vec![key!('b')] => commands::move_prev_word_start,
                vec![key!('e')] => commands::move_next_word_end,
                vec![key!('g')] => commands::goto_mode,
                vec![key!('i')] => commands::insert_mode,
                vec![shift!('I')] => commands::prepend_to_line,
                vec![key!('a')] => commands::append_mode,
                vec![shift!('A')] => commands::append_to_line,
                vec![key!('o')] => commands::open_below,
                vec![key!('d')] => commands::delete_selection,
                vec![key!('c')] => commands::change_selection,
                vec![key!('s')] => commands::split_selection_on_newline,
                vec![key!(';')] => commands::collapse_selection,
                vec![Key {
                    code: KeyCode::Esc,
                    modifiers: Modifiers::NONE
                }] => commands::normal_mode,
                vec![Key {
                    code: KeyCode::PageUp,
                    modifiers: Modifiers::NONE
                }] => commands::page_up,
                vec![Key {
                    code: KeyCode::PageDown,
                    modifiers: Modifiers::NONE
                }] => commands::page_down,
                vec![ctrl!('u')] => commands::half_page_up,
                vec![ctrl!('d')] => commands::half_page_down,
            ),
            state::Mode::Insert => hashmap!(
                vec![Key {
                    code: KeyCode::Esc,
                    modifiers: Modifiers::NONE
                }] => commands::normal_mode as Command,
                vec![Key {
                    code: KeyCode::Backspace,
                    modifiers: Modifiers::NONE
                }] => commands::delete_char_backward,
                vec![Key {
                    code: KeyCode::Delete,
                    modifiers: Modifiers::NONE
                }] => commands::delete_char_forward,
                vec![Key {
                    code: KeyCode::Enter,
                    modifiers: Modifiers::NONE
                }] => commands::insert_newline,
                vec![Key {
                    code: KeyCode::Tab,
                    modifiers: Modifiers::NONE
                }] => commands::insert_tab,
            ),
            state::Mode::Goto => hashmap!(
                vec![Key {
                    code: KeyCode::Esc,
                    modifiers: Modifiers::NONE
                }] => commands::normal_mode as Command,
                vec![key!('g')] => commands::move_file_start as Command,
                vec![key!('e')] => commands::move_file_end as Command,
            )
    )
}