use crate::update::action::{Action, InputTarget};
use crate::update::input::keybindings;
use crate::update::input::keybindings::{Key, KeyCombo};
use crate::update::input::keymap;

pub fn handle_cell_edit_keys(combo: KeyCombo) -> Action {
    use crate::update::action::CursorMove;
    if let Some(action) = keymap::resolve(&combo, keybindings::CELL_EDIT_KEYS) {
        return action;
    }
    match combo.key {
        Key::Backspace => Action::TextBackspace {
            target: InputTarget::ResultCellEdit,
        },
        Key::Delete => Action::TextDelete {
            target: InputTarget::ResultCellEdit,
        },
        Key::Left => Action::TextMoveCursor {
            target: InputTarget::ResultCellEdit,
            direction: CursorMove::Left,
        },
        Key::Right => Action::TextMoveCursor {
            target: InputTarget::ResultCellEdit,
            direction: CursorMove::Right,
        },
        Key::Home => Action::TextMoveCursor {
            target: InputTarget::ResultCellEdit,
            direction: CursorMove::Home,
        },
        Key::End => Action::TextMoveCursor {
            target: InputTarget::ResultCellEdit,
            direction: CursorMove::End,
        },
        Key::Char(c) => Action::TextInput {
            target: InputTarget::ResultCellEdit,
            ch: c,
        },
        _ => Action::None,
    }
}

pub fn handle_command_line_mode(combo: KeyCombo) -> Action {
    use crate::update::action::CursorMove;
    if let Some(action) = keymap::resolve(&combo, keybindings::COMMAND_LINE_KEYS) {
        return action;
    }
    match combo.key {
        Key::Backspace => Action::TextBackspace {
            target: InputTarget::CommandLine,
        },
        Key::Left => Action::TextMoveCursor {
            target: InputTarget::CommandLine,
            direction: CursorMove::Left,
        },
        Key::Right => Action::TextMoveCursor {
            target: InputTarget::CommandLine,
            direction: CursorMove::Right,
        },
        Key::Home => Action::TextMoveCursor {
            target: InputTarget::CommandLine,
            direction: CursorMove::Home,
        },
        Key::End => Action::TextMoveCursor {
            target: InputTarget::CommandLine,
            direction: CursorMove::End,
        },
        Key::Char(c) => Action::TextInput {
            target: InputTarget::CommandLine,
            ch: c,
        },
        _ => Action::None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::update::input::keybindings::{Key, KeyCombo};
    use rstest::rstest;

    fn combo(k: Key) -> KeyCombo {
        KeyCombo::plain(k)
    }

    mod cell_edit_mode {
        use super::*;
        use crate::update::action::CursorMove;

        #[test]
        fn esc_in_cell_edit_returns_cancel_not_discard() {
            let result = handle_cell_edit_keys(combo(Key::Esc));

            assert!(matches!(result, Action::ResultCancelCellEdit));
        }

        #[test]
        fn char_input_returns_cell_edit_input() {
            let result = handle_cell_edit_keys(combo(Key::Char('x')));

            assert!(matches!(
                result,
                Action::TextInput {
                    target: InputTarget::ResultCellEdit,
                    ch: 'x'
                }
            ));
        }

        #[test]
        fn backspace_returns_cell_edit_backspace() {
            let result = handle_cell_edit_keys(combo(Key::Backspace));

            assert!(matches!(
                result,
                Action::TextBackspace {
                    target: InputTarget::ResultCellEdit
                }
            ));
        }

        #[test]
        fn delete_returns_cell_edit_delete() {
            let result = handle_cell_edit_keys(combo(Key::Delete));

            assert!(matches!(
                result,
                Action::TextDelete {
                    target: InputTarget::ResultCellEdit
                }
            ));
        }

        #[test]
        fn left_returns_move_cursor_left() {
            let result = handle_cell_edit_keys(combo(Key::Left));

            assert!(matches!(
                result,
                Action::TextMoveCursor {
                    target: InputTarget::ResultCellEdit,
                    direction: CursorMove::Left
                }
            ));
        }

        #[test]
        fn right_returns_move_cursor_right() {
            let result = handle_cell_edit_keys(combo(Key::Right));

            assert!(matches!(
                result,
                Action::TextMoveCursor {
                    target: InputTarget::ResultCellEdit,
                    direction: CursorMove::Right
                }
            ));
        }

        #[test]
        fn home_returns_move_cursor_home() {
            let result = handle_cell_edit_keys(combo(Key::Home));

            assert!(matches!(
                result,
                Action::TextMoveCursor {
                    target: InputTarget::ResultCellEdit,
                    direction: CursorMove::Home
                }
            ));
        }

        #[test]
        fn end_returns_move_cursor_end() {
            let result = handle_cell_edit_keys(combo(Key::End));

            assert!(matches!(
                result,
                Action::TextMoveCursor {
                    target: InputTarget::ResultCellEdit,
                    direction: CursorMove::End
                }
            ));
        }
    }

    mod command_line {
        use super::*;

        enum Expected {
            Submit,
            Exit,
            Backspace,
            Input(char),
            None,
        }

        #[rstest]
        #[case(Key::Enter, Expected::Submit)]
        #[case(Key::Esc, Expected::Exit)]
        #[case(Key::Backspace, Expected::Backspace)]
        #[case(Key::Char('s'), Expected::Input('s'))]
        #[case(Key::Tab, Expected::None)]
        fn handles_keys(#[case] code: Key, #[case] expected: Expected) {
            let result = handle_command_line_mode(combo(code));

            match expected {
                Expected::Submit => assert!(matches!(result, Action::CommandLineSubmit)),
                Expected::Exit => assert!(matches!(result, Action::ExitCommandLine)),
                Expected::Backspace => assert!(matches!(
                    result,
                    Action::TextBackspace {
                        target: InputTarget::CommandLine
                    }
                )),
                Expected::Input(ch) => {
                    assert!(
                        matches!(result, Action::TextInput { target: InputTarget::CommandLine, ch: c } if c == ch)
                    );
                }
                Expected::None => assert!(matches!(result, Action::None)),
            }
        }
    }
}
