// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
/// Internal Vi-style editor layer on top of `moon_cosmic.Editor`.
pub(all) enum ViMode {
  Normal
  Insert
  Replace
  Visual
  VisualLine
  Search
}

///|
pub(all) enum ViOperator {
  Delete
  Change
  Yank
  ShiftLeft
  ShiftRight
}

///|
fn vi_motion_for_key(key : Char) -> Motion? {
  if key == 'h' {
    Some(Left)
  } else if key == 'j' {
    Some(Down)
  } else if key == 'k' {
    Some(Up)
  } else if key == 'l' {
    Some(Right)
  } else if key == 'w' {
    Some(NextWord)
  } else if key == 'b' {
    Some(PreviousWord)
  } else if key == 'B' {
    Some(PreviousWord)
  } else if key == 'e' {
    Some(RightWord)
  } else if key == 'E' {
    Some(RightWord)
  } else if key == 'W' {
    Some(NextWord)
  } else if key == '0' {
    Some(Home)
  } else if key == '^' {
    Some(SoftHome)
  } else if key == '$' {
    Some(End)
  } else {
    None
  }
}

///|
fn vi_with_visual_selection(editor : Editor, mode : ViMode) -> Editor {
  match mode {
    Visual => editor.set_selection(Selection::Normal(editor.cursor()))
    VisualLine => editor.set_selection(Selection::Line(editor.cursor()))
    _ => editor
  }
}

///|
fn vi_delete_current_line(editor : Editor) -> Editor {
  let cursor = editor.cursor()
  let line = cursor.line
  let lines = editor.buffer().lines()
  if line < 0 || line >= lines.length() {
    return editor
  }
  let end = lines[line].text().length()
  editor.delete_range(Cursor::new(line, 0), Cursor::new(line, end))
}

///|
fn vi_copy_current_line(editor : Editor) -> String? {
  let cursor = editor.cursor()
  let line = cursor.line
  let lines = editor.buffer().lines()
  if line < 0 || line >= lines.length() {
    return None
  }
  let line0 = lines[line]
  Some(line0.text() + line0.ending().as_str())
}

///|
fn vi_open_line_below(editor : Editor) -> Editor {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return editor
  }
  let end = lines[cursor.line].text().length()
  let inserted = editor.insert_at(Cursor::new(cursor.line, end), "\n", None)
  inserted.0.set_cursor(inserted.1)
}

///|
fn vi_open_line_above(editor : Editor) -> Editor {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return editor
  }
  let inserted = editor.insert_at(Cursor::new(cursor.line, 0), "\n", None)
  inserted.0.set_cursor(Cursor::new(cursor.line, 0))
}

///|
fn string_append_char(s : String, ch : Char) -> String {
  let sb = StringBuilder::new(size_hint=s.length() + ch.utf16_len())
  sb.write_string(s)
  sb.write_char(ch)
  sb.to_string()
}

///|
fn vi_digit_value(ch : Char) -> Int? {
  if ch == '0' {
    Some(0)
  } else if ch == '1' {
    Some(1)
  } else if ch == '2' {
    Some(2)
  } else if ch == '3' {
    Some(3)
  } else if ch == '4' {
    Some(4)
  } else if ch == '5' {
    Some(5)
  } else if ch == '6' {
    Some(6)
  } else if ch == '7' {
    Some(7)
  } else if ch == '8' {
    Some(8)
  } else if ch == '9' {
    Some(9)
  } else {
    None
  }
}

///|
fn vi_count_value(count_opt : Int?) -> Int {
  match count_opt {
    None => 1
    Some(v) => if v <= 0 { 1 } else { v }
  }
}

///|
fn vi_repeat_motion(editor : Editor, motion : Motion, count : Int) -> Editor {
  let mut out = editor
  let mut i = 0
  while i < count {
    out = out.action(Motion(motion))
    i = i + 1
  }
  out
}

///|
fn vi_repeat_put(
  editor : Editor,
  selection : Selection,
  data : String,
  after : Bool,
  count : Int,
) -> Editor {
  let mut out = editor
  let mut i = 0
  while i < count {
    out = vi_put_from_register(out, selection, data, after)
    i = i + 1
  }
  out
}

///|
fn vi_repeat_action(editor : Editor, action : Action, count : Int) -> Editor {
  let mut out = editor
  let mut i = 0
  while i < count {
    out = out.action(action)
    i = i + 1
  }
  out
}

///|
fn vi_apply_indent_lines(
  editor : Editor,
  count : Int,
  unindent : Bool,
) -> Editor {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return editor
  }
  let count = if count <= 0 { 1 } else { count }
  let end_line = if cursor.line + count - 1 < lines.length() {
    cursor.line + count - 1
  } else {
    lines.length() - 1
  }
  let selected = editor.set_selection(Selection::Line(Cursor::new(end_line, 0)))
  let acted = if unindent {
    selected.action(Unindent)
  } else {
    selected.action(Indent)
  }
  acted.set_selection(Selection::None)
}

///|
fn vi_replace_one(editor : Editor, ch : Char) -> Editor {
  editor.action(Delete).action(Insert(ch)).action(Motion(Left))
}

///|
fn string_pop_last_char(s : String) -> String {
  if s.length() == 0 {
    return s
  }
  let mut last = 0
  for p in s.iter2() {
    last = p.0
  }
  let sb = StringBuilder::new(size_hint=last)
  sb.write_view(s[:].view(start_offset=0, end_offset=last))
  sb.to_string()
}

///|
fn find_substring_forward(text : String, pattern : String, start : Int) -> Int? {
  let plen = pattern.length()
  if plen == 0 || start < 0 || start > text.length() || plen > text.length() {
    return None
  }
  let end = text.length() - plen
  for i in start..<(end + 1) {
    let mut ok = true
    for j in 0.. Int? {
  let plen = pattern.length()
  if plen == 0 || plen > text.length() {
    return None
  }
  let end = if end_exclusive > text.length() {
    text.length()
  } else {
    end_exclusive
  }
  if end < plen {
    return None
  }
  let mut i = end - plen
  while true {
    let mut ok = true
    for j in 0.. Editor {
  if query.length() == 0 {
    return editor
  }
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if lines.length() == 0 {
    return editor
  }
  if forwards {
    for line_i in cursor.line.. ()
        Some(index) =>
          return editor.set_cursor(
            Cursor::new_with_affinity(line_i, index, cursor.affinity),
          )
      }
    }
  } else {
    let mut line_i = cursor.line + 1
    while line_i > 0 {
      line_i = line_i - 1
      let text = lines[line_i].text()
      let end = if line_i == cursor.line { cursor.index } else { text.length() }
      match find_substring_backward(text, query, end) {
        None => ()
        Some(index) =>
          return editor.set_cursor(
            Cursor::new_with_affinity(line_i, index, cursor.affinity),
          )
      }
    }
  }
  editor
}

///|
fn vi_find_in_line(
  editor : Editor,
  target : Char,
  forwards : Bool,
  till : Bool,
) -> Editor {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return editor
  }
  let text = lines[cursor.line].text()
  let target_len = target.utf16_len()
  if forwards {
    for p in text.iter2() {
      let idx = p.0
      let ch = p.1
      if idx > cursor.index && ch == target {
        let hit = if till { if idx > 0 { idx - 1 } else { idx } } else { idx }
        return editor.set_cursor(
          Cursor::new_with_affinity(cursor.line, hit, cursor.affinity),
        )
      }
    }
  } else {
    let mut found_opt : Int? = None
    for p in text.iter2() {
      let idx = p.0
      let ch = p.1
      if idx < cursor.index && ch == target {
        found_opt = Some(idx)
      }
    }
    match found_opt {
      None => ()
      Some(idx) => {
        let hit = if till { idx + target_len } else { idx }
        return editor.set_cursor(
          Cursor::new_with_affinity(cursor.line, hit, cursor.affinity),
        )
      }
    }
  }
  editor
}

///|
fn is_word_char(ch : Char) -> Bool {
  ch.is_ascii_alphabetic() || ch.is_numeric() || ch == '_'
}

///|
fn find_word_bounds(
  text : String,
  cursor_index : Int,
  with_delims : Bool,
) -> (Int, Int)? {
  if cursor_index < 0 || cursor_index >= text.length() {
    return None
  }
  for p in text.iter2() {
    let idx = p.0
    let ch = p.1
    let end = idx + ch.utf16_len()
    if cursor_index < idx || cursor_index >= end || !is_word_char(ch) {
      continue
    }

    let mut start = idx
    let mut stop = end

    let prevs : Array[(Int, Char)] = []
    for p0 in text.iter2() {
      if p0.0 >= idx {
        break
      }
      prevs.push(p0)
    }
    let mut i = prevs.length()
    while i > 0 {
      i = i - 1
      let p0 = prevs[i]
      if is_word_char(p0.1) {
        start = p0.0
      } else {
        break
      }
    }

    for p0 in text.iter2() {
      if p0.0 < end {
        continue
      }
      if is_word_char(p0.1) {
        stop = p0.0 + p0.1.utf16_len()
      } else {
        break
      }
    }

    if with_delims {
      let mut with_stop = stop
      let mut has_tail_space = false
      for p0 in text.iter2() {
        if p0.0 < stop {
          continue
        }
        if p0.1.is_whitespace() {
          has_tail_space = true
          with_stop = p0.0 + p0.1.utf16_len()
        } else {
          break
        }
      }

      let mut with_start = start
      if !has_tail_space {
        let prev_ws : Array[(Int, Char)] = []
        for p0 in text.iter2() {
          if p0.0 >= start {
            break
          }
          prev_ws.push(p0)
        }
        let mut j = prev_ws.length()
        while j > 0 {
          j = j - 1
          let p0 = prev_ws[j]
          if p0.1.is_whitespace() {
            with_start = p0.0
          } else {
            break
          }
        }
      }
      start = with_start
      stop = with_stop
    }

    return Some((start, stop))
  }
  None
}

///|
fn vi_select_word_text_object(
  editor : Editor,
  with_delims : Bool,
) -> (Editor, Bool) {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return (editor, false)
  }
  let text = lines[cursor.line].text()
  match find_word_bounds(text, cursor.index, with_delims) {
    None => (editor, false)
    Some((start, end)) => {
      let start_cursor = Cursor::new_with_affinity(
        cursor.line,
        start,
        cursor.affinity,
      )
      let end_cursor = Cursor::new_with_affinity(
        cursor.line,
        end,
        cursor.affinity,
      )
      (
        editor
        .set_selection(Selection::Normal(start_cursor))
        .set_cursor(end_cursor),
        true,
      )
    }
  }
}

///|
fn vi_select_in(
  editor : Editor,
  start_c : Char,
  end_c : Char,
  with_delims : Bool,
) -> (Editor, Bool) {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return (editor, false)
  }

  let mut end_line = cursor.line
  let mut end_index = cursor.index
  let mut starts = 0
  let mut ends = 0
  let mut found_end = false
  while !found_end {
    if end_line < 0 || end_line >= lines.length() {
      break
    }
    let text = lines[end_line].text()
    for p in text.iter2() {
      let i = p.0
      let c = p.1
      if i < end_index {
        continue
      }
      if c == end_c {
        ends = ends + 1
      } else if c == start_c {
        starts = starts + 1
      }
      if ends > starts {
        end_index = if with_delims { i + c.utf16_len() } else { i }
        found_end = true
        break
      }
    }
    if found_end {
      break
    }
    if end_line + 1 < lines.length() {
      end_line = end_line + 1
      end_index = 0
    } else {
      break
    }
  }
  if !found_end {
    return (editor, false)
  }

  let mut start_line = cursor.line
  let mut start_index = cursor.index
  let mut found_start = false
  while !found_start {
    if start_line < 0 || start_line >= lines.length() {
      break
    }
    let text = lines[start_line].text()
    let prevs : Array[(Int, Char)] = []
    for p in text.iter2() {
      if p.0 >= start_index {
        break
      }
      prevs.push(p)
    }
    let mut i = prevs.length()
    while i > 0 {
      i = i - 1
      let p = prevs[i]
      let c = p.1
      if c == start_c {
        starts = starts + 1
      } else if c == end_c {
        ends = ends + 1
      }
      if starts >= ends {
        start_index = if with_delims { p.0 } else { p.0 + c.utf16_len() }
        found_start = true
        break
      }
    }
    if found_start {
      break
    }
    if start_line > 0 {
      start_line = start_line - 1
      start_index = lines[start_line].text().length()
    } else {
      break
    }
  }
  if !found_start {
    return (editor, false)
  }

  let start_cursor = Cursor::new_with_affinity(
    start_line,
    start_index,
    cursor.affinity,
  )
  let end_cursor = Cursor::new_with_affinity(
    end_line,
    end_index,
    cursor.affinity,
  )
  (
    editor.set_selection(Selection::Normal(start_cursor)).set_cursor(end_cursor),
    true,
  )
}

///|
fn vi_select_text_object(
  editor : Editor,
  key : Char,
  with_delims : Bool,
) -> (Editor, Bool) {
  if key == 'w' || key == 'W' {
    return vi_select_word_text_object(editor, with_delims)
  }
  if key == '<' || key == '>' {
    return vi_select_in(editor, '<', '>', with_delims)
  }
  if key == '{' || key == '}' {
    return vi_select_in(editor, '{', '}', with_delims)
  }
  if key == '(' || key == ')' {
    return vi_select_in(editor, '(', ')', with_delims)
  }
  if key == '[' || key == ']' {
    return vi_select_in(editor, '[', ']', with_delims)
  }
  if key == '"' {
    return vi_select_in(editor, '"', '"', with_delims)
  }
  if key == '\'' {
    return vi_select_in(editor, '\'', '\'', with_delims)
  }
  if key == '`' {
    return vi_select_in(editor, '`', '`', with_delims)
  }
  (editor, false)
}

///|
fn vi_apply_text_object_operator(
  editor : Editor,
  op : ViOperator,
  key : Char,
  with_delims : Bool,
) -> (Editor, ViMode, (Selection, String)?, Bool) {
  let selected = vi_select_text_object(editor, key, with_delims)
  if !selected.1 {
    return (editor, Normal, None, false)
  }
  let selected_editor = selected.0
  let selection = selected_editor.selection()
  match op {
    Delete => {
      let captured : (Selection, String)? = match
        selected_editor.copy_selection() {
        None => None
        Some(data) => Some((selection, data))
      }
      let deleted = selected_editor.delete_selection()
      (deleted.0, Normal, captured, deleted.1)
    }
    Change => {
      let captured : (Selection, String)? = match
        selected_editor.copy_selection() {
        None => None
        Some(data) => Some((selection, data))
      }
      let deleted = selected_editor.delete_selection()
      (deleted.0, Insert, captured, deleted.1)
    }
    Yank => {
      let captured : (Selection, String)? = match
        selected_editor.copy_selection() {
        None => None
        Some(data) => Some((selection, data))
      }
      (selected_editor.set_selection(Selection::None), Normal, captured, true)
    }
    ShiftLeft =>
      (
        selected_editor.action(Unindent).set_selection(Selection::None),
        Normal,
        None,
        true,
      )
    ShiftRight =>
      (
        selected_editor.action(Indent).set_selection(Selection::None),
        Normal,
        None,
        true,
      )
  }
}

///|
fn vi_register_get(
  registers : Array[(Char, Selection, String)],
  key : Char,
) -> (Selection, String)? {
  for entry in registers {
    if entry.0 == key {
      return Some((entry.1, entry.2))
    }
  }
  None
}

///|
fn vi_register_set(
  registers : Array[(Char, Selection, String)],
  key : Char,
  selection : Selection,
  data : String,
) -> Array[(Char, Selection, String)] {
  let out : Array[(Char, Selection, String)] = []
  let mut replaced = false
  for entry in registers {
    if entry.0 == key {
      out.push((key, selection, data))
      replaced = true
    } else {
      out.push(entry)
    }
  }
  if !replaced {
    out.push((key, selection, data))
  }
  out
}

///|
fn vi_target_register_key(key_opt : Char?) -> Char {
  match key_opt {
    None => '"'
    Some(key) => key
  }
}

///|
fn vi_put_from_register(
  editor : Editor,
  selection : Selection,
  data : String,
  after : Bool,
) -> Editor {
  match selection {
    Selection::Line(_) => {
      let cursor = editor.cursor()
      let line = if after { cursor.line + 1 } else { cursor.line }
      let inserted = editor.insert_at(Cursor::new(line, 0), data, None)
      inserted.0.set_cursor(inserted.1)
    }
    _ => {
      let editor = if after {
        let moved = editor.action(Motion(Right))
        if moved.cursor().line == editor.cursor().line {
          moved
        } else {
          editor
        }
      } else {
        editor
      }
      editor.insert_string(data, None)
    }
  }
}

///|
fn vi_capture_current_line(editor : Editor) -> (Selection, String)? {
  match vi_copy_current_line(editor) {
    None => None
    Some(data) => Some((Selection::Line(editor.cursor()), data))
  }
}

///|
fn vi_capture_lines(
  editor : Editor,
  start_line : Int,
  count : Int,
) -> (Selection, String)? {
  let lines = editor.buffer().lines()
  if start_line < 0 || start_line >= lines.length() {
    return None
  }
  let end_exclusive = if start_line + count < lines.length() {
    start_line + count
  } else {
    lines.length()
  }
  let sb = StringBuilder::new()
  let mut i = start_line
  while i < end_exclusive {
    let line = lines[i]
    sb.write_string(line.text())
    sb.write_string(line.ending().as_str())
    i = i + 1
  }
  let data = sb.to_string()
  if data == "" {
    None
  } else {
    Some((Selection::Line(editor.cursor()), data))
  }
}

///|
fn vi_delete_count_lines(
  editor : Editor,
  count : Int,
) -> (Editor, (Selection, String)?) {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return (editor, None)
  }
  let count = if count <= 0 { 1 } else { count }
  let start_line = cursor.line
  let last_line = lines.length() - 1
  let end_line = if start_line + count - 1 < last_line {
    start_line + count - 1
  } else {
    last_line
  }
  let captured = vi_capture_lines(editor, start_line, count)
  let start = Cursor::new(start_line, 0)
  let end = if end_line < last_line {
    Cursor::new(end_line + 1, 0)
  } else {
    Cursor::new(end_line, lines[end_line].text().length())
  }
  let deleted = editor.delete_range(start, end)
  (deleted.set_cursor(Cursor::new(start_line, 0)), captured)
}

///|
fn vi_delete_to_line_end(editor : Editor) -> (Editor, (Selection, String)?) {
  let cursor = editor.cursor()
  let lines = editor.buffer().lines()
  if cursor.line < 0 || cursor.line >= lines.length() {
    return (editor, None)
  }
  let end_index = lines[cursor.line].text().length()
  if cursor.index >= end_index {
    return (editor, None)
  }
  let end = Cursor::new(cursor.line, end_index)
  let selected = editor.set_selection(Selection::Normal(cursor)).set_cursor(end)
  let captured : (Selection, String)? = match selected.copy_selection() {
    None => None
    Some(data) => Some((selected.selection(), data))
  }
  let deleted = selected.delete_selection()
  if deleted.1 {
    (deleted.0, captured)
  } else {
    (editor, None)
  }
}

///|
pub struct ViEditor {
  editor : Editor
  mode : ViMode
  pending_operator : ViOperator?
  passthrough : Bool
  history : Array[Change]
  history_index : Int
  save_pivot_opt : Int?
  changed : Bool
  search_opt : (String, Bool)?
  search_input_opt : String?
  search_input_forwards : Bool
  pending_find_opt : (Bool, Bool, Int)?
  last_find_opt : (Bool, Bool, Char)?
  pending_replace_char : Bool
  pending_text_object_opt : (ViOperator, Bool)?
  pending_g : Bool
  pending_count_opt : Int?
  pending_register_prefix : Bool
  active_register_opt : Char?
  registers : Array[(Char, Selection, String)]
  register_opt : String?
}

///|
pub fn ViEditor::new(editor : Editor) -> ViEditor {
  ViEditor::{
    editor,
    mode: Normal,
    pending_operator: None,
    passthrough: false,
    history: [],
    history_index: 0,
    save_pivot_opt: None,
    changed: false,
    search_opt: None,
    search_input_opt: None,
    search_input_forwards: true,
    pending_find_opt: None,
    last_find_opt: None,
    pending_replace_char: false,
    pending_text_object_opt: None,
    pending_g: false,
    pending_count_opt: None,
    pending_register_prefix: false,
    active_register_opt: None,
    registers: [],
    register_opt: None,
  }
}

///|
pub fn ViEditor::editor(self : ViEditor) -> Editor {
  self.editor
}

///|
pub fn ViEditor::mode(self : ViEditor) -> ViMode {
  self.mode
}

///|
pub fn ViEditor::changed(self : ViEditor) -> Bool {
  self.changed
}

///|
pub fn ViEditor::set_changed(self : ViEditor, changed : Bool) -> ViEditor {
  ViEditor::{ ..self, changed, }
}

///|
pub fn ViEditor::save_point(self : ViEditor) -> ViEditor {
  ViEditor::{ ..self, save_pivot_opt: Some(self.history_index), changed: false }
}

///|
pub fn ViEditor::set_passthrough(
  self : ViEditor,
  passthrough : Bool,
) -> ViEditor {
  ViEditor::{ ..self, passthrough, }
}

///|
pub fn ViEditor::set_editor(self : ViEditor, editor : Editor) -> ViEditor {
  ViEditor::{ ..self, editor, }
}

///|
pub fn ViEditor::escape(self : ViEditor) -> ViEditor {
  ViEditor::{
    ..self,
    editor: self.editor.set_selection(Selection::None),
    mode: Normal,
    pending_operator: None,
    search_input_opt: None,
    search_input_forwards: true,
    pending_find_opt: None,
    pending_replace_char: false,
    pending_text_object_opt: None,
    pending_g: false,
    pending_count_opt: None,
    pending_register_prefix: false,
    active_register_opt: None,
  }
}

///|
fn vi_eval_changed(history_index : Int, save_pivot_opt : Int?) -> Bool {
  match save_pivot_opt {
    Some(pivot) => history_index != pivot
    None => history_index != 0
  }
}

///|
fn ViEditor::push_change(self : ViEditor, change : Change) -> ViEditor {
  let history : Array[Change] = []
  for i in 0.. ViEditor {
  let target = vi_target_register_key(self.active_register_opt)
  let registers0 = vi_register_set(self.registers, '"', selection, data)
  let registers = if target == '"' {
    registers0
  } else {
    vi_register_set(registers0, target, selection, data)
  }
  ViEditor::{
    ..self,
    registers,
    register_opt: Some(data),
    active_register_opt: None,
    pending_register_prefix: false,
  }
}

///|
fn ViEditor::register_for_put(self : ViEditor) -> (Selection, String)? {
  let target = vi_target_register_key(self.active_register_opt)
  match vi_register_get(self.registers, target) {
    Some(value) => Some(value)
    None =>
      if target == '"' {
        match self.register_opt {
          None => None
          Some(data) => Some((Selection::None, data))
        }
      } else {
        None
      }
  }
}

///|
fn ViEditor::finalize_change(self : ViEditor) -> ViEditor {
  let finished = self.editor.finish_change()
  let editor = finished.0
  let with_editor = ViEditor::{ ..self, editor, }
  match finished.1 {
    None => with_editor
    Some(change) =>
      if change.items.length() == 0 {
        with_editor
      } else {
        with_editor.push_change(change)
      }
  }
}

///|
fn ViEditor::feed_char_inner(self : ViEditor, key : Char) -> ViEditor {
  match self.search_input_opt {
    None => ()
    Some(query) =>
      if key == '\n' {
        let forwards = self.search_input_forwards
        let editor = vi_search(self.editor, query, forwards)
        return ViEditor::{
          ..self,
          editor,
          mode: Normal,
          search_opt: Some((query, forwards)),
          search_input_opt: None,
          search_input_forwards: true,
        }
      } else if key == '\u{08}' || key == '\u{7F}' {
        return ViEditor::{
          ..self,
          search_input_opt: Some(string_pop_last_char(query)),
        }
      } else {
        return ViEditor::{
          ..self,
          mode: Search,
          search_input_opt: Some(string_append_char(query, key)),
        }
      }
  }

  match self.pending_find_opt {
    None => ()
    Some((forwards, till, find_count)) => {
      let mut editor = self.editor
      let mut i = 0
      while i < find_count {
        editor = vi_find_in_line(editor, key, forwards, till)
        i = i + 1
      }
      return ViEditor::{
        ..self,
        editor,
        pending_find_opt: None,
        last_find_opt: Some((forwards, till, key)),
      }
    }
  }

  if self.pending_replace_char {
    return ViEditor::{
      ..self,
      editor: vi_replace_one(self.editor, key),
      pending_replace_char: false,
      mode: Normal,
      pending_count_opt: None,
    }
  }

  match self.pending_text_object_opt {
    None => ()
    Some((op, with_delims)) => {
      let applied = vi_apply_text_object_operator(
        self.editor,
        op,
        key,
        with_delims,
      )
      let with_state = ViEditor::{
        ..self,
        editor: applied.0,
        mode: applied.1,
        pending_operator: None,
        pending_text_object_opt: None,
      }
      match applied.2 {
        None => return with_state
        Some(captured) => return with_state.set_register(captured.0, captured.1)
      }
    }
  }

  if self.pending_register_prefix {
    return ViEditor::{
      ..self,
      pending_register_prefix: false,
      active_register_opt: Some(key),
    }
  }

  if self.pending_g {
    if key == 'g' {
      let editor = match self.pending_count_opt {
        None => self.editor.action(Motion(BufferStart))
        Some(count) =>
          self.editor.action(
            Motion(GotoLine(if count <= 0 { 0 } else { count - 1 })),
          )
      }
      return ViEditor::{
        ..self,
        editor,
        pending_g: false,
        pending_count_opt: None,
      }
    }
    if key == 'e' || key == 'E' {
      return ViEditor::{
        ..self,
        editor: self.editor
        .action(Motion(PreviousWord))
        .action(Motion(RightWord)),
        pending_g: false,
        pending_count_opt: None,
      }
    }
    if key == 'n' || key == 'N' {
      match self.search_opt {
        None =>
          return ViEditor::{ ..self, pending_g: false, pending_count_opt: None }
        Some((query, forwards0)) => {
          let forwards = if key == 'n' { forwards0 } else { !forwards0 }
          let searched = vi_search(self.editor, query, forwards)
          let cursor = searched.cursor()
          let end = Cursor::new(cursor.line, cursor.index + query.length())
          return ViEditor::{
            ..self,
            editor: searched
            .set_selection(Selection::Normal(cursor))
            .set_cursor(end),
            pending_g: false,
            pending_count_opt: None,
          }
        }
      }
    }
    return ViEditor::{ ..self, pending_g: false }.feed_char_inner(key)
  }

  if self.passthrough {
    if key == '\n' {
      return ViEditor::{ ..self, editor: self.editor.action(Enter) }
    }
    return ViEditor::{ ..self, editor: self.editor.action(Insert(key)) }
  }
  match self.mode {
    Insert =>
      if key == '\n' {
        ViEditor::{ ..self, editor: self.editor.action(Enter) }
      } else {
        ViEditor::{ ..self, editor: self.editor.action(Insert(key)) }
      }
    Replace =>
      if key == '\n' {
        ViEditor::{ ..self, editor: self.editor.action(Enter) }
      } else {
        ViEditor::{
          ..self,
          editor: self.editor.action(Delete).action(Insert(key)),
        }
      }
    Visual | VisualLine => {
      if key == 'v' && self.mode is Visual {
        return self.escape()
      }
      if key == 'V' && self.mode is VisualLine {
        return self.escape()
      }
      match vi_motion_for_key(key) {
        None => self
        Some(motion) =>
          ViEditor::{ ..self, editor: self.editor.action(Motion(motion)) }
      }
    }
    Search => self
    Normal => {
      match vi_digit_value(key) {
        None => ()
        Some(digit) =>
          if key != '0' || self.pending_count_opt is Some(_) {
            let base = match self.pending_count_opt {
              None => 0
              Some(v) => v
            }
            return ViEditor::{
              ..self,
              pending_count_opt: Some(base * 10 + digit),
            }
          }
      }
      let count = vi_count_value(self.pending_count_opt)
      match self.pending_operator {
        Some(Delete) =>
          if key == 'i' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Delete, false)),
            }
          } else if key == 'a' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Delete, true)),
            }
          } else if key == 'd' {
            let deleted = vi_delete_count_lines(self.editor, count)
            let with_editor = ViEditor::{
              ..self,
              editor: deleted.0,
              pending_operator: None,
              pending_count_opt: None,
            }
            match deleted.1 {
              None => with_editor
              Some(captured) => with_editor.set_register(captured.0, captured.1)
            }
          } else {
            let after_op = ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
            match vi_motion_for_key(key) {
              None => after_op
              Some(motion) =>
                ViEditor::{
                  ..after_op,
                  editor: vi_repeat_motion(after_op.editor, motion, count).action(
                    Delete,
                  ),
                }
            }
          }
        Some(Change) =>
          if key == 'i' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Change, false)),
            }
          } else if key == 'a' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Change, true)),
            }
          } else if key == 'c' {
            let deleted = vi_delete_count_lines(self.editor, count)
            let with_editor = ViEditor::{
              ..self,
              editor: deleted.0,
              mode: Insert,
              pending_operator: None,
              pending_count_opt: None,
            }
            match deleted.1 {
              None => with_editor
              Some(captured) => with_editor.set_register(captured.0, captured.1)
            }
          } else {
            let after_op = ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
            match vi_motion_for_key(key) {
              None => after_op
              Some(motion) =>
                ViEditor::{
                  ..after_op,
                  editor: vi_repeat_motion(after_op.editor, motion, count).action(
                    Delete,
                  ),
                  mode: Insert,
                }
            }
          }
        Some(Yank) =>
          if key == 'i' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Yank, false)),
            }
          } else if key == 'a' {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_text_object_opt: Some((Yank, true)),
            }
          } else if key == 'y' {
            let with_state = ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
            match
              vi_capture_lines(self.editor, self.editor.cursor().line, count) {
              None => with_state
              Some(captured) => with_state.set_register(captured.0, captured.1)
            }
          } else {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
          }
        Some(ShiftLeft) =>
          if key == '<' {
            ViEditor::{
              ..self,
              editor: vi_apply_indent_lines(self.editor, count, true),
              pending_operator: None,
              pending_count_opt: None,
            }
          } else {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
          }
        Some(ShiftRight) =>
          if key == '>' {
            ViEditor::{
              ..self,
              editor: vi_apply_indent_lines(self.editor, count, false),
              pending_operator: None,
              pending_count_opt: None,
            }
          } else {
            ViEditor::{
              ..self,
              pending_operator: None,
              pending_count_opt: None,
            }
          }
        None =>
          if key == 'i' {
            ViEditor::{ ..self, mode: Insert, pending_count_opt: None }
          } else if key == 'I' {
            ViEditor::{
              ..self,
              editor: self.editor.action(Motion(Home)),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'a' {
            ViEditor::{
              ..self,
              editor: self.editor.action(Motion(Right)),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'A' {
            ViEditor::{
              ..self,
              editor: self.editor.action(Motion(End)),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'v' {
            let mode = Visual
            ViEditor::{
              ..self,
              mode,
              editor: vi_with_visual_selection(self.editor, mode),
              pending_count_opt: None,
            }
          } else if key == 'V' {
            let mode = VisualLine
            ViEditor::{
              ..self,
              mode,
              editor: vi_with_visual_selection(self.editor, mode),
              pending_count_opt: None,
            }
          } else if key == 'x' {
            ViEditor::{
              ..self,
              editor: vi_repeat_action(self.editor, Delete, count),
              pending_count_opt: None,
            }
          } else if key == 'X' {
            ViEditor::{
              ..self,
              editor: vi_repeat_action(self.editor, Backspace, count),
              pending_count_opt: None,
            }
          } else if key == 's' {
            ViEditor::{
              ..self,
              editor: vi_repeat_action(self.editor, Delete, count),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'r' {
            ViEditor::{
              ..self,
              pending_replace_char: true,
              pending_count_opt: None,
            }
          } else if key == 'R' {
            ViEditor::{ ..self, mode: Replace, pending_count_opt: None }
          } else if key == 'o' {
            ViEditor::{
              ..self,
              editor: vi_open_line_below(self.editor),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'O' {
            ViEditor::{
              ..self,
              editor: vi_open_line_above(self.editor),
              mode: Insert,
              pending_count_opt: None,
            }
          } else if key == 'D' {
            let deleted = vi_delete_to_line_end(self.editor)
            let with_editor = ViEditor::{
              ..self,
              editor: deleted.0,
              pending_count_opt: None,
            }
            match deleted.1 {
              None => with_editor
              Some(captured) => with_editor.set_register(captured.0, captured.1)
            }
          } else if key == 'C' {
            let deleted = vi_delete_to_line_end(self.editor)
            let with_editor = ViEditor::{
              ..self,
              editor: deleted.0,
              mode: Insert,
              pending_count_opt: None,
            }
            match deleted.1 {
              None => with_editor
              Some(captured) => with_editor.set_register(captured.0, captured.1)
            }
          } else if key == 'S' {
            let with_editor = ViEditor::{
              ..self,
              editor: vi_delete_current_line(self.editor),
              mode: Insert,
              pending_count_opt: None,
            }
            match vi_capture_current_line(self.editor) {
              None => with_editor
              Some(captured) => with_editor.set_register(captured.0, captured.1)
            }
          } else if key == 'Y' {
            match
              vi_capture_lines(self.editor, self.editor.cursor().line, count) {
              None => ViEditor::{ ..self, pending_count_opt: None }
              Some(captured) =>
                ViEditor::{ ..self, pending_count_opt: None }.set_register(
                  captured.0,
                  captured.1,
                )
            }
          } else if key == 'g' {
            ViEditor::{ ..self, pending_g: true }
          } else if key == 'G' {
            let editor = match self.pending_count_opt {
              None => self.editor.action(Motion(BufferEnd))
              Some(n) =>
                self.editor.action(
                  Motion(GotoLine(if n <= 0 { 0 } else { n - 1 })),
                )
            }
            ViEditor::{ ..self, editor, pending_count_opt: None }
          } else if key == '"' {
            ViEditor::{ ..self, pending_register_prefix: true }
          } else if key == '/' {
            ViEditor::{
              ..self,
              mode: Search,
              search_input_opt: Some(""),
              search_input_forwards: true,
              pending_count_opt: None,
            }
          } else if key == '?' {
            ViEditor::{
              ..self,
              mode: Search,
              search_input_opt: Some(""),
              search_input_forwards: false,
              pending_count_opt: None,
            }
          } else if key == 'n' {
            match self.search_opt {
              None => self
              Some((query, forwards)) =>
                ViEditor::{
                  ..self,
                  editor: vi_search(self.editor, query, forwards),
                  pending_count_opt: None,
                }
            }
          } else if key == 'N' {
            match self.search_opt {
              None => self
              Some((query, forwards)) =>
                ViEditor::{
                  ..self,
                  editor: vi_search(self.editor, query, !forwards),
                  pending_count_opt: None,
                }
            }
          } else if key == 'f' {
            ViEditor::{
              ..self,
              pending_find_opt: Some((true, false, count)),
              pending_count_opt: None,
            }
          } else if key == 'F' {
            ViEditor::{
              ..self,
              pending_find_opt: Some((false, false, count)),
              pending_count_opt: None,
            }
          } else if key == 't' {
            ViEditor::{
              ..self,
              pending_find_opt: Some((true, true, count)),
              pending_count_opt: None,
            }
          } else if key == 'T' {
            ViEditor::{
              ..self,
              pending_find_opt: Some((false, true, count)),
              pending_count_opt: None,
            }
          } else if key == ';' {
            match self.last_find_opt {
              None => ViEditor::{ ..self, pending_count_opt: None }
              Some(last) =>
                ViEditor::{
                  ..self,
                  editor: vi_find_in_line(self.editor, last.2, last.0, last.1),
                  pending_count_opt: None,
                }
            }
          } else if key == ',' {
            match self.last_find_opt {
              None => ViEditor::{ ..self, pending_count_opt: None }
              Some(last) =>
                ViEditor::{
                  ..self,
                  editor: vi_find_in_line(self.editor, last.2, !last.0, last.1),
                  pending_count_opt: None,
                }
            }
          } else if key == '-' {
            ViEditor::{
              ..self,
              editor: self.editor.action(Motion(Up)).action(Motion(SoftHome)),
              pending_count_opt: None,
            }
          } else if key == '+' {
            ViEditor::{
              ..self,
              editor: self.editor.action(Motion(Down)).action(Motion(SoftHome)),
              pending_count_opt: None,
            }
          } else if key == '<' {
            ViEditor::{ ..self, pending_operator: Some(ShiftLeft) }
          } else if key == '>' {
            ViEditor::{ ..self, pending_operator: Some(ShiftRight) }
          } else if key == 'p' {
            match self.register_for_put() {
              None => self
              Some(register) =>
                ViEditor::{
                  ..self,
                  editor: vi_repeat_put(
                    self.editor,
                    register.0,
                    register.1,
                    true,
                    count,
                  ),
                  active_register_opt: None,
                  pending_count_opt: None,
                }
            }
          } else if key == 'P' {
            match self.register_for_put() {
              None => self
              Some(register) =>
                ViEditor::{
                  ..self,
                  editor: vi_repeat_put(
                    self.editor,
                    register.0,
                    register.1,
                    false,
                    count,
                  ),
                  active_register_opt: None,
                  pending_count_opt: None,
                }
            }
          } else if key == 'd' {
            ViEditor::{ ..self, pending_operator: Some(Delete) }
          } else if key == 'c' {
            ViEditor::{ ..self, pending_operator: Some(Change) }
          } else if key == 'y' {
            ViEditor::{ ..self, pending_operator: Some(Yank) }
          } else if key == 'u' {
            self.undo()
          } else if key == '\u{12}' {
            self.redo()
          } else {
            match vi_motion_for_key(key) {
              None => ViEditor::{ ..self, pending_count_opt: None }
              Some(motion) =>
                ViEditor::{
                  ..self,
                  editor: vi_repeat_motion(self.editor, motion, count),
                  pending_count_opt: None,
                }
            }
          }
      }
    }
  }
}

///|
pub fn ViEditor::feed_char(self : ViEditor, key : Char) -> ViEditor {
  if key == '\u{1B}' {
    return self.escape()
  }
  let started = ViEditor::{ ..self, editor: self.editor.start_change() }
  started.feed_char_inner(key).finalize_change()
}

///|
pub fn ViEditor::undo(self : ViEditor) -> ViEditor {
  if self.history_index == 0 {
    return self
  }
  let change = self.history[self.history_index - 1].reverse()
  let applied = self.editor.apply_change(change)
  if !applied.1 {
    return self
  }
  let history_index = self.history_index - 1
  ViEditor::{
    ..self,
    editor: applied.0,
    history_index,
    changed: vi_eval_changed(history_index, self.save_pivot_opt),
    mode: Normal,
    pending_operator: None,
    pending_text_object_opt: None,
    pending_g: false,
    pending_count_opt: None,
    pending_register_prefix: false,
    active_register_opt: None,
    pending_replace_char: false,
  }
}

///|
pub fn ViEditor::redo(self : ViEditor) -> ViEditor {
  if self.history_index >= self.history.length() {
    return self
  }
  let change = self.history[self.history_index]
  let applied = self.editor.apply_change(change)
  if !applied.1 {
    return self
  }
  let history_index = self.history_index + 1
  ViEditor::{
    ..self,
    editor: applied.0,
    history_index,
    changed: vi_eval_changed(history_index, self.save_pivot_opt),
    mode: Normal,
    pending_operator: None,
    pending_text_object_opt: None,
    pending_g: false,
    pending_count_opt: None,
    pending_register_prefix: false,
    active_register_opt: None,
    pending_replace_char: false,
  }
}

///|
pub fn ViEditor::feed_string(self : ViEditor, keys : String) -> ViEditor {
  let mut out = self
  for key in keys {
    out = out.feed_char(key)
  }
  out
}