use log::debug;
use super::Result;
use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
use crate::tty::{self, RawReader, Term, Terminal};
use crate::{Config, EditMode};
#[cfg(feature = "custom-bindings")]
use crate::{Event, EventContext, EventHandler};
pub type RepeatCount = usize;
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum Cmd {
    Abort, AcceptLine,
    BeginningOfHistory,
    CapitalizeWord,
    ClearScreen,
    #[cfg(windows)]
    PasteFromClipboard,
    Complete,
    CompleteBackward,
    CompleteHint,
    Dedent(Movement),
    DowncaseWord,
    EndOfFile,
    EndOfHistory,
    ForwardSearchHistory,
    HistorySearchBackward,
    HistorySearchForward,
    Indent(Movement),
    Insert(RepeatCount, String),
    Interrupt,
    Kill(Movement),
    Move(Movement),
    NextHistory,
    Noop,
    Overwrite(char),
    PreviousHistory,
    QuotedInsert,
    ReplaceChar(RepeatCount, char),
    Replace(Movement, Option<String>),
    ReverseSearchHistory,
    SelfInsert(RepeatCount, char),
    Suspend,
    TransposeChars,
    TransposeWords(RepeatCount),
    Undo(RepeatCount),
    Unknown,
    UpcaseWord,
    ViYankTo(Movement),
    Yank(RepeatCount, Anchor),
    YankPop,
    LineUpOrPreviousHistory(RepeatCount),
    LineDownOrNextHistory(RepeatCount),
    Newline,
    AcceptOrInsertLine {
        accept_in_the_middle: bool,
    },
}
impl Cmd {
    #[must_use]
    pub const fn should_reset_kill_ring(&self) -> bool {
        #[allow(clippy::match_same_arms)]
        match *self {
            Cmd::Kill(Movement::BackwardChar(_) | Movement::ForwardChar(_)) => true,
            Cmd::ClearScreen
            | Cmd::Kill(_)
            | Cmd::Replace(..)
            | Cmd::Noop
            | Cmd::Suspend
            | Cmd::Yank(..)
            | Cmd::YankPop => false,
            _ => true,
        }
    }
    const fn is_repeatable_change(&self) -> bool {
        matches!(
            *self,
            Cmd::Dedent(..)
                | Cmd::Indent(..)
                | Cmd::Insert(..)
                | Cmd::Kill(_)
                | Cmd::ReplaceChar(..)
                | Cmd::Replace(..)
                | Cmd::SelfInsert(..)
                | Cmd::ViYankTo(_)
                | Cmd::Yank(..) )
    }
    const fn is_repeatable(&self) -> bool {
        match *self {
            Cmd::Move(_) => true,
            _ => self.is_repeatable_change(),
        }
    }
    fn redo(&self, new: Option<RepeatCount>, wrt: &dyn Refresher) -> Self {
        match *self {
            Cmd::Dedent(ref mvt) => Cmd::Dedent(mvt.redo(new)),
            Cmd::Indent(ref mvt) => Cmd::Indent(mvt.redo(new)),
            Cmd::Insert(previous, ref text) => {
                Cmd::Insert(repeat_count(previous, new), text.clone())
            }
            Cmd::Kill(ref mvt) => Cmd::Kill(mvt.redo(new)),
            Cmd::Move(ref mvt) => Cmd::Move(mvt.redo(new)),
            Cmd::ReplaceChar(previous, c) => Cmd::ReplaceChar(repeat_count(previous, new), c),
            Cmd::Replace(ref mvt, ref text) => {
                if text.is_none() {
                    let last_insert = wrt.last_insert();
                    if let Movement::ForwardChar(0) = mvt {
                        Cmd::Replace(
                            Movement::ForwardChar(last_insert.as_ref().map_or(0, String::len)),
                            last_insert,
                        )
                    } else {
                        Cmd::Replace(mvt.redo(new), last_insert)
                    }
                } else {
                    Cmd::Replace(mvt.redo(new), text.clone())
                }
            }
            Cmd::SelfInsert(previous, c) => {
                if let Some(text) = wrt.last_insert() {
                    Cmd::Insert(repeat_count(previous, new), text)
                } else {
                    Cmd::SelfInsert(repeat_count(previous, new), c)
                }
            }
            Cmd::ViYankTo(ref mvt) => Cmd::ViYankTo(mvt.redo(new)),
            Cmd::Yank(previous, anchor) => Cmd::Yank(repeat_count(previous, new), anchor),
            _ => unreachable!(),
        }
    }
}
const fn repeat_count(previous: RepeatCount, new: Option<RepeatCount>) -> RepeatCount {
    match new {
        Some(n) => n,
        None => previous,
    }
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
pub enum Word {
    Big,
    Emacs,
    Vi,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
pub enum At {
    Start,
    BeforeEnd,
    AfterEnd,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
pub enum Anchor {
    After,
    Before,
}
#[derive(Debug, Clone, Eq, PartialEq, Copy)]
pub enum CharSearch {
    Forward(char),
    ForwardBefore(char),
    Backward(char),
    BackwardAfter(char),
}
impl CharSearch {
    const fn opposite(self) -> Self {
        match self {
            CharSearch::Forward(c) => CharSearch::Backward(c),
            CharSearch::ForwardBefore(c) => CharSearch::BackwardAfter(c),
            CharSearch::Backward(c) => CharSearch::Forward(c),
            CharSearch::BackwardAfter(c) => CharSearch::ForwardBefore(c),
        }
    }
}
#[derive(Debug, Clone, Eq, PartialEq)]
#[non_exhaustive]
pub enum Movement {
    WholeLine,
    BeginningOfLine,
    EndOfLine,
    BackwardWord(RepeatCount, Word), ForwardWord(RepeatCount, At, Word), ViCharSearch(RepeatCount, CharSearch),
    ViFirstPrint,
    BackwardChar(RepeatCount),
    ForwardChar(RepeatCount),
    LineUp(RepeatCount),
    LineDown(RepeatCount),
    WholeBuffer,
    BeginningOfBuffer,
    EndOfBuffer,
}
impl Movement {
    const fn redo(&self, new: Option<RepeatCount>) -> Self {
        match *self {
            Movement::WholeLine => Movement::WholeLine,
            Movement::BeginningOfLine => Movement::BeginningOfLine,
            Movement::ViFirstPrint => Movement::ViFirstPrint,
            Movement::EndOfLine => Movement::EndOfLine,
            Movement::BackwardWord(previous, word) => {
                Movement::BackwardWord(repeat_count(previous, new), word)
            }
            Movement::ForwardWord(previous, at, word) => {
                Movement::ForwardWord(repeat_count(previous, new), at, word)
            }
            Movement::ViCharSearch(previous, char_search) => {
                Movement::ViCharSearch(repeat_count(previous, new), char_search)
            }
            Movement::BackwardChar(previous) => Movement::BackwardChar(repeat_count(previous, new)),
            Movement::ForwardChar(previous) => Movement::ForwardChar(repeat_count(previous, new)),
            Movement::LineUp(previous) => Movement::LineUp(repeat_count(previous, new)),
            Movement::LineDown(previous) => Movement::LineDown(repeat_count(previous, new)),
            Movement::WholeBuffer => Movement::WholeBuffer,
            Movement::BeginningOfBuffer => Movement::BeginningOfBuffer,
            Movement::EndOfBuffer => Movement::EndOfBuffer,
        }
    }
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub enum InputMode {
    Command,
    Insert,
    Replace,
}
pub struct InputState<'b> {
    pub(crate) mode: EditMode,
    #[cfg_attr(not(feature = "custom-bindings"), allow(dead_code))]
    custom_bindings: &'b Bindings,
    pub(crate) input_mode: InputMode, num_args: i16,
    last_cmd: Cmd,                        last_char_search: Option<CharSearch>, }
pub trait Invoke {
    fn input(&self) -> &str;
    }
impl Invoke for &str {
    fn input(&self) -> &str {
        self
    }
}
pub trait Refresher {
    fn refresh_line(&mut self) -> Result<()>;
    fn refresh_line_with_msg(&mut self, msg: Option<&str>) -> Result<()>;
    fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
    fn doing_insert(&mut self);
    fn done_inserting(&mut self);
    fn last_insert(&self) -> Option<String>;
    fn is_cursor_at_end(&self) -> bool;
    fn has_hint(&self) -> bool;
    fn hint_text(&self) -> Option<&str>;
    fn line(&self) -> &str;
    fn pos(&self) -> usize;
    fn external_print(&mut self, msg: String) -> Result<()>;
}
impl<'b> InputState<'b> {
    pub fn new(config: &Config, custom_bindings: &'b Bindings) -> Self {
        Self {
            mode: config.edit_mode(),
            custom_bindings,
            input_mode: InputMode::Insert,
            num_args: 0,
            last_cmd: Cmd::Noop,
            last_char_search: None,
        }
    }
    pub fn is_emacs_mode(&self) -> bool {
        self.mode == EditMode::Emacs
    }
    pub fn next_cmd(
        &mut self,
        rdr: &mut <Terminal as Term>::Reader,
        wrt: &mut dyn Refresher,
        single_esc_abort: bool,
        ignore_external_print: bool,
    ) -> Result<Cmd> {
        let single_esc_abort = self.single_esc_abort(single_esc_abort);
        let key;
        if ignore_external_print {
            key = rdr.next_key(single_esc_abort)?;
        } else {
            loop {
                let event = rdr.wait_for_input(single_esc_abort)?;
                match event {
                    tty::Event::KeyPress(k) => {
                        key = k;
                        break;
                    }
                    tty::Event::ExternalPrint(msg) => {
                        wrt.external_print(msg)?;
                    }
                }
            }
        }
        match self.mode {
            EditMode::Emacs => self.emacs(rdr, wrt, key),
            EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr, wrt, key),
            EditMode::Vi => self.vi_command(rdr, wrt, key),
        }
    }
    fn single_esc_abort(&self, single_esc_abort: bool) -> bool {
        match self.mode {
            EditMode::Emacs => single_esc_abort,
            EditMode::Vi => false,
        }
    }
    fn term_binding<R: RawReader>(
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        key: &KeyEvent,
    ) -> Option<Cmd> {
        let cmd = rdr.find_binding(key);
        if cmd == Some(Cmd::EndOfFile) && !wrt.line().is_empty() {
            None } else {
            cmd
        }
    }
    fn emacs_digit_argument<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        digit: char,
    ) -> Result<KeyEvent> {
        #[allow(clippy::cast_possible_truncation)]
        match digit {
            '0'..='9' => {
                self.num_args = digit.to_digit(10).unwrap() as i16;
            }
            '-' => {
                self.num_args = -1;
            }
            _ => unreachable!(),
        }
        loop {
            wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
            let key = rdr.next_key(true)?;
            #[allow(clippy::cast_possible_truncation)]
            match key {
                E(K::Char(digit @ '0'..='9'), m) if m == M::NONE || m == M::ALT => {
                    if self.num_args == -1 {
                        self.num_args *= digit.to_digit(10).unwrap() as i16;
                    } else if self.num_args.abs() < 1000 {
                        self.num_args = self
                            .num_args
                            .saturating_mul(10)
                            .saturating_add(digit.to_digit(10).unwrap() as i16);
                    }
                }
                E(K::Char('-'), m) if m == M::NONE || m == M::ALT => {}
                _ => {
                    wrt.refresh_line()?;
                    return Ok(key);
                }
            };
        }
    }
    fn emacs<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        mut key: KeyEvent,
    ) -> Result<Cmd> {
        if let E(K::Char(digit @ '-'), M::ALT) = key {
            key = self.emacs_digit_argument(rdr, wrt, digit)?;
        } else if let E(K::Char(digit @ '0'..='9'), M::ALT) = key {
            key = self.emacs_digit_argument(rdr, wrt, digit)?;
        }
        let (n, positive) = self.emacs_num_args(); let mut evt = key.into();
        if let Some(cmd) = self.custom_binding(wrt, &evt, n, positive) {
            return Ok(if cmd.is_repeatable() {
                cmd.redo(Some(n), wrt)
            } else {
                cmd
            });
        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
            return Ok(cmd);
        }
        let cmd = match key {
            E(K::Char(c), M::NONE) => {
                if positive {
                    Cmd::SelfInsert(n, c)
                } else {
                    Cmd::Unknown
                }
            }
            E(K::Char('A'), M::CTRL) => Cmd::Move(Movement::BeginningOfLine),
            E(K::Char('B'), M::CTRL) => Cmd::Move(if positive {
                Movement::BackwardChar(n)
            } else {
                Movement::ForwardChar(n)
            }),
            E(K::Char('E'), M::CTRL) => Cmd::Move(Movement::EndOfLine),
            E(K::Char('F'), M::CTRL) => Cmd::Move(if positive {
                Movement::ForwardChar(n)
            } else {
                Movement::BackwardChar(n)
            }),
            E(K::Char('G'), M::CTRL | M::CTRL_ALT) | E::ESC => Cmd::Abort,
            E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(if positive {
                Movement::BackwardChar(n)
            } else {
                Movement::ForwardChar(n)
            }),
            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
            E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => {
                if positive {
                    Cmd::Complete
                } else {
                    Cmd::CompleteBackward
                }
            }
            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
            E(K::Char('K'), M::CTRL) => Cmd::Kill(if positive {
                Movement::EndOfLine
            } else {
                Movement::BeginningOfLine
            }),
            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
            E(K::Char('X'), M::CTRL) => {
                if let Some(cmd) = self.custom_seq_binding(rdr, wrt, &mut evt, n, positive)? {
                    cmd
                } else {
                    let snd_key = match evt {
                        Event::KeySeq(ref key_seq) if key_seq.len() > 1 => key_seq[1],
                        _ => rdr.next_key(true)?,
                    };
                    match snd_key {
                        E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
                        E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
                        E(K::Backspace, M::NONE) => Cmd::Kill(if positive {
                            Movement::BeginningOfLine
                        } else {
                            Movement::EndOfLine
                        }),
                        _ => Cmd::Unknown,
                    }
                }
            }
            E(K::Char(']'), m @ (M::CTRL | M::CTRL_ALT)) => {
                let ch = rdr.next_key(false)?;
                match ch {
                    E(K::Char(ch), M::NONE) => Cmd::Move(Movement::ViCharSearch(
                        n,
                        if positive {
                            if m.contains(M::ALT) {
                                CharSearch::Backward(ch)
                            } else {
                                CharSearch::ForwardBefore(ch)
                            }
                        } else if m.contains(M::ALT) {
                            CharSearch::ForwardBefore(ch)
                        } else {
                            CharSearch::Backward(ch)
                        },
                    )),
                    _ => Cmd::Unknown,
                }
            }
            E(K::Backspace, M::ALT) => Cmd::Kill(if positive {
                Movement::BackwardWord(n, Word::Emacs)
            } else {
                Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
            }),
            E(K::Char('<'), M::ALT) => Cmd::BeginningOfHistory,
            E(K::Char('>'), M::ALT) => Cmd::EndOfHistory,
            E(K::Char('B' | 'b') | K::Left, M::ALT) | E(K::Left, M::CTRL) => {
                Cmd::Move(if positive {
                    Movement::BackwardWord(n, Word::Emacs)
                } else {
                    Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
                })
            }
            E(K::Char('C' | 'c'), M::ALT) => Cmd::CapitalizeWord,
            E(K::Char('D' | 'd'), M::ALT) => Cmd::Kill(if positive {
                Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
            } else {
                Movement::BackwardWord(n, Word::Emacs)
            }),
            E(K::Char('F' | 'f') | K::Right, M::ALT) | E(K::Right, M::CTRL) => {
                Cmd::Move(if positive {
                    Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
                } else {
                    Movement::BackwardWord(n, Word::Emacs)
                })
            }
            E(K::Char('L' | 'l'), M::ALT) => Cmd::DowncaseWord,
            E(K::Char('T' | 't'), M::ALT) => Cmd::TransposeWords(n),
            E(K::Char('U' | 'u'), M::ALT) => Cmd::UpcaseWord,
            E(K::Char('Y' | 'y'), M::ALT) => Cmd::YankPop,
            _ => self.common(rdr, wrt, evt, key, n, positive)?,
        };
        debug!(target: "rustyline", "Emacs command: {:?}", cmd);
        Ok(cmd)
    }
    #[allow(clippy::cast_possible_truncation)]
    fn vi_arg_digit<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        digit: char,
    ) -> Result<KeyEvent> {
        self.num_args = digit.to_digit(10).unwrap() as i16;
        loop {
            wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
            let key = rdr.next_key(false)?;
            if let E(K::Char(digit @ '0'..='9'), M::NONE) = key {
                if self.num_args.abs() < 1000 {
                    self.num_args = self
                        .num_args
                        .saturating_mul(10)
                        .saturating_add(digit.to_digit(10).unwrap() as i16);
                }
            } else {
                wrt.refresh_line()?;
                return Ok(key);
            };
        }
    }
    fn vi_command<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        mut key: KeyEvent,
    ) -> Result<Cmd> {
        if let E(K::Char(digit @ '1'..='9'), M::NONE) = key {
            key = self.vi_arg_digit(rdr, wrt, digit)?;
        }
        let no_num_args = self.num_args == 0;
        let n = self.vi_num_args(); let evt = key.into();
        if let Some(cmd) = self.custom_binding(wrt, &evt, n, true) {
            return Ok(if cmd.is_repeatable() {
                if no_num_args {
                    cmd.redo(None, wrt)
                } else {
                    cmd.redo(Some(n), wrt)
                }
            } else {
                cmd
            });
        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
            return Ok(cmd);
        }
        let cmd = match key {
            E(K::Char('$') | K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
            E(K::Char('.'), M::NONE) => {
                if no_num_args {
                    self.last_cmd.redo(None, wrt)
                } else {
                    self.last_cmd.redo(Some(n), wrt)
                }
            }
            E(K::Char('0'), M::NONE) => Cmd::Move(Movement::BeginningOfLine),
            E(K::Char('^'), M::NONE) => Cmd::Move(Movement::ViFirstPrint),
            E(K::Char('a'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                wrt.doing_insert();
                Cmd::Move(Movement::ForwardChar(n))
            }
            E(K::Char('A'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                wrt.doing_insert();
                Cmd::Move(Movement::EndOfLine)
            }
            E(K::Char('b'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), E(K::Char('B'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
            E(K::Char('c'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                match self.vi_cmd_motion(rdr, wrt, key, n)? {
                    Some(mvt) => Cmd::Replace(mvt, None),
                    None => Cmd::Unknown,
                }
            }
            E(K::Char('C'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                Cmd::Replace(Movement::EndOfLine, None)
            }
            E(K::Char('d'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
                Some(mvt) => Cmd::Kill(mvt),
                None => Cmd::Unknown,
            },
            E(K::Char('D'), M::NONE) | E(K::Char('K'), M::CTRL) => Cmd::Kill(Movement::EndOfLine),
            E(K::Char('e'), M::NONE) => {
                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi))
            }
            E(K::Char('E'), M::NONE) => {
                Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big))
            }
            E(K::Char('i'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                wrt.doing_insert();
                Cmd::Noop
            }
            E(K::Char('I'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                wrt.doing_insert();
                Cmd::Move(Movement::BeginningOfLine)
            }
            E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
                let cs = self.vi_char_search(rdr, c)?;
                match cs {
                    Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
                    None => Cmd::Unknown,
                }
            }
            E(K::Char(';'), M::NONE) => match self.last_char_search {
                Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
                None => Cmd::Noop,
            },
            E(K::Char(','), M::NONE) => match self.last_char_search {
                Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())),
                None => Cmd::Noop,
            },
            E(K::Char('p'), M::NONE) => Cmd::Yank(n, Anchor::After), E(K::Char('P'), M::NONE) => Cmd::Yank(n, Anchor::Before), E(K::Char('r'), M::NONE) => {
                let ch = rdr.next_key(false)?;
                match ch {
                    E(K::Char(c), M::NONE) => Cmd::ReplaceChar(n, c),
                    E::ESC => Cmd::Noop,
                    _ => Cmd::Unknown,
                }
            }
            E(K::Char('R'), M::NONE) => {
                self.input_mode = InputMode::Replace;
                Cmd::Replace(Movement::ForwardChar(0), None)
            }
            E(K::Char('s'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                Cmd::Replace(Movement::ForwardChar(n), None)
            }
            E(K::Char('S'), M::NONE) => {
                self.input_mode = InputMode::Insert;
                Cmd::Replace(Movement::WholeLine, None)
            }
            E(K::Char('u'), M::NONE) => Cmd::Undo(n),
            E(K::Char('w'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), E(K::Char('W'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), E(K::Char('x'), M::NONE) => Cmd::Kill(Movement::ForwardChar(n)), E(K::Char('X'), M::NONE) => Cmd::Kill(Movement::BackwardChar(n)), E(K::Char('y'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
                Some(mvt) => Cmd::ViYankTo(mvt),
                None => Cmd::Unknown,
            },
            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
                Cmd::Move(Movement::BackwardChar(n))
            }
            E(K::Char('G'), M::CTRL) => Cmd::Abort,
            E(K::Char('l' | ' '), M::NONE) => Cmd::Move(Movement::ForwardChar(n)),
            E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
            E(K::Char('+' | 'j'), M::NONE) => Cmd::LineDownOrNextHistory(n),
            E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
            E(K::Char('-' | 'k'), M::NONE) => Cmd::LineUpOrPreviousHistory(n),
            E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
            E(K::Char('R'), M::CTRL) => {
                self.input_mode = InputMode::Insert; Cmd::ReverseSearchHistory
            }
            E(K::Char('S'), M::CTRL) => {
                self.input_mode = InputMode::Insert; Cmd::ForwardSearchHistory
            }
            E(K::Char('<'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
                Some(mvt) => Cmd::Dedent(mvt),
                None => Cmd::Unknown,
            },
            E(K::Char('>'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
                Some(mvt) => Cmd::Indent(mvt),
                None => Cmd::Unknown,
            },
            E::ESC => Cmd::Noop,
            _ => self.common(rdr, wrt, evt, key, n, true)?,
        };
        debug!(target: "rustyline", "Vi command: {:?}", cmd);
        if cmd.is_repeatable_change() {
            self.last_cmd = cmd.clone();
        }
        Ok(cmd)
    }
    fn vi_insert<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        key: KeyEvent,
    ) -> Result<Cmd> {
        let evt = key.into();
        if let Some(cmd) = self.custom_binding(wrt, &evt, 0, true) {
            return Ok(if cmd.is_repeatable() {
                cmd.redo(None, wrt)
            } else {
                cmd
            });
        } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
            return Ok(cmd);
        }
        let cmd = match key {
            E(K::Char(c), M::NONE) => {
                if self.input_mode == InputMode::Replace {
                    Cmd::Overwrite(c)
                } else {
                    Cmd::SelfInsert(1, c)
                }
            }
            E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(Movement::BackwardChar(1)),
            E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
            E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => Cmd::Complete,
            E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
            E(K::Char(k), M::ALT) => {
                debug!(target: "rustyline", "Vi fast command mode: {}", k);
                self.input_mode = InputMode::Command;
                wrt.done_inserting();
                self.vi_command(rdr, wrt, E(K::Char(k), M::NONE))?
            }
            E::ESC => {
                self.input_mode = InputMode::Command;
                wrt.done_inserting();
                Cmd::Move(Movement::BackwardChar(1))
            }
            _ => self.common(rdr, wrt, evt, key, 1, true)?,
        };
        debug!(target: "rustyline", "Vi insert: {:?}", cmd);
        if cmd.is_repeatable_change() {
            #[allow(clippy::if_same_then_else)]
            if let (Cmd::Replace(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
                } else if let (Cmd::SelfInsert(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
                } else {
                self.last_cmd = cmd.clone();
            }
        }
        Ok(cmd)
    }
    fn vi_cmd_motion<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        key: KeyEvent,
        n: RepeatCount,
    ) -> Result<Option<Movement>> {
        let mut mvt = rdr.next_key(false)?;
        if mvt == key {
            return Ok(Some(Movement::WholeLine));
        }
        let mut n = n;
        if let E(K::Char(digit @ '1'..='9'), M::NONE) = mvt {
            mvt = self.vi_arg_digit(rdr, wrt, digit)?;
            n = self.vi_num_args().saturating_mul(n);
        }
        Ok(match mvt {
            E(K::Char('$'), M::NONE) => Some(Movement::EndOfLine),
            E(K::Char('0'), M::NONE) => Some(Movement::BeginningOfLine),
            E(K::Char('^'), M::NONE) => Some(Movement::ViFirstPrint),
            E(K::Char('b'), M::NONE) => Some(Movement::BackwardWord(n, Word::Vi)),
            E(K::Char('B'), M::NONE) => Some(Movement::BackwardWord(n, Word::Big)),
            E(K::Char('e'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
            E(K::Char('E'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
            E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
                let cs = self.vi_char_search(rdr, c)?;
                cs.map(|cs| Movement::ViCharSearch(n, cs))
            }
            E(K::Char(';'), M::NONE) => self
                .last_char_search
                .map(|cs| Movement::ViCharSearch(n, cs)),
            E(K::Char(','), M::NONE) => self
                .last_char_search
                .map(|cs| Movement::ViCharSearch(n, cs.opposite())),
            E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
                Some(Movement::BackwardChar(n))
            }
            E(K::Char('l' | ' '), M::NONE) => Some(Movement::ForwardChar(n)),
            E(K::Char('j' | '+'), M::NONE) => Some(Movement::LineDown(n)),
            E(K::Char('k' | '-'), M::NONE) => Some(Movement::LineUp(n)),
            E(K::Char('w'), M::NONE) => {
                if key == E(K::Char('c'), M::NONE) {
                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
                } else {
                    Some(Movement::ForwardWord(n, At::Start, Word::Vi))
                }
            }
            E(K::Char('W'), M::NONE) => {
                if key == E(K::Char('c'), M::NONE) {
                    Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
                } else {
                    Some(Movement::ForwardWord(n, At::Start, Word::Big))
                }
            }
            _ => None,
        })
    }
    fn vi_char_search<R: RawReader>(
        &mut self,
        rdr: &mut R,
        cmd: char,
    ) -> Result<Option<CharSearch>> {
        let ch = rdr.next_key(false)?;
        Ok(match ch {
            E(K::Char(ch), M::NONE) => {
                let cs = match cmd {
                    'f' => CharSearch::Forward(ch),
                    't' => CharSearch::ForwardBefore(ch),
                    'F' => CharSearch::Backward(ch),
                    'T' => CharSearch::BackwardAfter(ch),
                    _ => unreachable!(),
                };
                self.last_char_search = Some(cs);
                Some(cs)
            }
            _ => None,
        })
    }
    fn common<R: RawReader>(
        &mut self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        mut evt: Event,
        key: KeyEvent,
        n: RepeatCount,
        positive: bool,
    ) -> Result<Cmd> {
        Ok(match key {
            E(K::Home, M::NONE) => Cmd::Move(Movement::BeginningOfLine),
            E(K::Left, M::NONE) => Cmd::Move(if positive {
                Movement::BackwardChar(n)
            } else {
                Movement::ForwardChar(n)
            }),
            #[cfg(any(windows, test))]
            E(K::Char('C'), M::CTRL) => Cmd::Interrupt,
            E(K::Char('D'), M::CTRL) => {
                if self.is_emacs_mode() && !wrt.line().is_empty() {
                    Cmd::Kill(if positive {
                        Movement::ForwardChar(n)
                    } else {
                        Movement::BackwardChar(n)
                    })
                } else if cfg!(windows) || cfg!(test) || !wrt.line().is_empty() {
                    Cmd::EndOfFile
                } else {
                    Cmd::Unknown
                }
            }
            E(K::Delete, M::NONE) => Cmd::Kill(if positive {
                Movement::ForwardChar(n)
            } else {
                Movement::BackwardChar(n)
            }),
            E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
            E(K::Right, M::NONE) => Cmd::Move(if positive {
                Movement::ForwardChar(n)
            } else {
                Movement::BackwardChar(n)
            }),
            E(K::Char('J' | 'M'), M::CTRL) | E::ENTER => Cmd::AcceptOrInsertLine {
                accept_in_the_middle: true,
            },
            E(K::Down, M::NONE) => Cmd::LineDownOrNextHistory(1),
            E(K::Up, M::NONE) => Cmd::LineUpOrPreviousHistory(1),
            E(K::Char('R'), M::CTRL) => Cmd::ReverseSearchHistory,
            E(K::Char('S'), M::CTRL) => Cmd::ForwardSearchHistory,
            E(K::Char('T'), M::CTRL) => Cmd::TransposeChars,
            E(K::Char('U'), M::CTRL) => Cmd::Kill(if positive {
                Movement::BeginningOfLine
            } else {
                Movement::EndOfLine
            }),
            E(K::Char('Q'), M::CTRL) => Cmd::QuotedInsert,
            #[cfg(not(windows))]
            E(K::Char('V'), M::CTRL) => Cmd::QuotedInsert,
            #[cfg(windows)]
            E(K::Char('V'), M::CTRL) => Cmd::PasteFromClipboard,
            E(K::Char('W'), M::CTRL) => Cmd::Kill(if positive {
                Movement::BackwardWord(n, Word::Big)
            } else {
                Movement::ForwardWord(n, At::AfterEnd, Word::Big)
            }),
            E(K::Char('Y'), M::CTRL) => {
                if positive {
                    Cmd::Yank(n, Anchor::Before)
                } else {
                    Cmd::Unknown }
            }
            E(K::Char('_'), M::CTRL) => Cmd::Undo(n),
            E(K::UnknownEscSeq, M::NONE) => Cmd::Noop,
            E(K::BracketedPasteStart, M::NONE) => {
                let paste = rdr.read_pasted_text()?;
                Cmd::Insert(1, paste)
            }
            _ => self
                .custom_seq_binding(rdr, wrt, &mut evt, n, positive)?
                .unwrap_or(Cmd::Unknown),
        })
    }
    fn num_args(&mut self) -> i16 {
        let num_args = match self.num_args {
            0 => 1,
            _ => self.num_args,
        };
        self.num_args = 0;
        num_args
    }
    #[allow(clippy::cast_sign_loss)]
    fn emacs_num_args(&mut self) -> (RepeatCount, bool) {
        let num_args = self.num_args();
        if num_args < 0 {
            if let (n, false) = num_args.overflowing_abs() {
                (n as RepeatCount, false)
            } else {
                (RepeatCount::MAX, false)
            }
        } else {
            (num_args as RepeatCount, true)
        }
    }
    #[allow(clippy::cast_sign_loss)]
    fn vi_num_args(&mut self) -> RepeatCount {
        let num_args = self.num_args();
        if num_args < 0 {
            unreachable!()
        } else {
            num_args.unsigned_abs() as RepeatCount
        }
    }
}
#[cfg(feature = "custom-bindings")]
impl<'b> InputState<'b> {
    fn custom_binding(
        &self,
        wrt: &mut dyn Refresher,
        evt: &Event,
        n: RepeatCount,
        positive: bool,
    ) -> Option<Cmd> {
        let bindings = self.custom_bindings;
        let handler = bindings.get(evt).or_else(|| bindings.get(&Event::Any));
        if let Some(handler) = handler {
            match handler {
                EventHandler::Simple(cmd) => Some(cmd.clone()),
                EventHandler::Conditional(handler) => {
                    let ctx = EventContext::new(self, wrt);
                    handler.handle(evt, n, positive, &ctx)
                }
            }
        } else {
            None
        }
    }
    fn custom_seq_binding<R: RawReader>(
        &self,
        rdr: &mut R,
        wrt: &mut dyn Refresher,
        evt: &mut Event,
        n: RepeatCount,
        positive: bool,
    ) -> Result<Option<Cmd>> {
        while let Some(subtrie) = self.custom_bindings.get_raw_descendant(evt) {
            let snd_key = rdr.next_key(true)?;
            if let Event::KeySeq(ref mut key_seq) = evt {
                key_seq.push(snd_key);
            } else {
                break;
            }
            let handler = subtrie.get(evt).unwrap();
            if let Some(handler) = handler {
                let cmd = match handler {
                    EventHandler::Simple(cmd) => Some(cmd.clone()),
                    EventHandler::Conditional(handler) => {
                        let ctx = EventContext::new(self, wrt);
                        handler.handle(evt, n, positive, &ctx)
                    }
                };
                if cmd.is_some() {
                    return Ok(cmd);
                }
            }
        }
        Ok(None)
    }
}
#[cfg(not(feature = "custom-bindings"))]
impl<'b> InputState<'b> {
    fn custom_binding(
        &self,
        _: &mut dyn Refresher,
        _: &Event,
        _: RepeatCount,
        _: bool,
    ) -> Option<Cmd> {
        None
    }
    fn custom_seq_binding<R: RawReader>(
        &self,
        _: &mut R,
        _: &mut dyn Refresher,
        _: &mut Event,
        _: RepeatCount,
        _: bool,
    ) -> Result<Option<Cmd>> {
        Ok(None)
    }
}
cfg_if::cfg_if! {
    if #[cfg(feature = "custom-bindings")] {
pub type Bindings = radix_trie::Trie<Event, EventHandler>;
    } else {
enum Event {
   KeySeq([KeyEvent; 1]),
}
impl From<KeyEvent> for Event {
    fn from(k: KeyEvent) -> Event {
        Event::KeySeq([k])
    }
}
pub struct Bindings {}
impl Bindings {
    pub fn new() -> Bindings {
        Bindings {}
    }
}
    }
}