///|
using @rendering {type AnsiColor}

///|
using @editing {type HistoryConfiguration, type CompletionItem}

///|
pub struct CompletionSpan {
  start : Int
  length : Int
}

///|
pub fn CompletionSpan::new(start : Int, length : Int) -> CompletionSpan {
  {
    start: if start < 0 {
      0
    } else {
      start
    },
    length: if length < 0 {
      0
    } else {
      length
    },
  }
}

///|
pub fn CompletionSpan::end(self : CompletionSpan) -> Int {
  self.start + self.length
}

///|
pub fn CompletionSpan::contains_caret(
  self : CompletionSpan,
  caret : Int,
) -> Bool {
  caret >= self.start && caret <= self.end()
}

///|
pub struct PromptSnapshot {
  text : String
  caret : Int
}

///|
pub fn PromptSnapshot::new(text : String, caret : Int) -> PromptSnapshot {
  let text_length = text.length()
  let caret = if caret < 0 {
    0
  } else if caret > text_length {
    text_length
  } else {
    caret
  }
  { text, caret }
}

///|
pub struct KeyTransformContext {
  snapshot : PromptSnapshot
  key_press : KeyPress
}

///|
pub fn KeyTransformContext::new(
  snapshot : PromptSnapshot,
  key_press : KeyPress,
) -> KeyTransformContext {
  { snapshot, key_press }
}

///|
pub struct FormatInputContext {
  snapshot : PromptSnapshot
  key_press : KeyPress
}

///|
pub fn FormatInputContext::new(
  snapshot : PromptSnapshot,
  key_press : KeyPress,
) -> FormatInputContext {
  { snapshot, key_press }
}

///|
pub struct CompletionRequest {
  snapshot : PromptSnapshot
  span_to_replace : CompletionSpan
}

///|
pub fn CompletionRequest::new(
  snapshot : PromptSnapshot,
  span_to_replace : CompletionSpan,
) -> CompletionRequest {
  { snapshot, span_to_replace }
}

///|
pub struct CompletionTriggerContext {
  snapshot : PromptSnapshot
  key_press : KeyPress
}

///|
pub fn CompletionTriggerContext::new(
  snapshot : PromptSnapshot,
  key_press : KeyPress,
) -> CompletionTriggerContext {
  { snapshot, key_press }
}

///|
pub struct CompletionCommitContext {
  snapshot : PromptSnapshot
  key_press : KeyPress
  selected_item : CompletionItem
  span_to_replace : CompletionSpan
}

///|
pub fn CompletionCommitContext::new(
  snapshot : PromptSnapshot,
  key_press : KeyPress,
  selected_item : CompletionItem,
  span_to_replace : CompletionSpan,
) -> CompletionCommitContext {
  { snapshot, key_press, selected_item, span_to_replace }
}

///|
pub(all) enum KeyPressCallbackResult {
  StayOnPrompt
  SubmitPrompt(text~ : String)
}

///|
pub struct KeyPressCallback {
  pattern : KeyPressPattern
  callback : async (PromptSnapshot) -> KeyPressCallbackResult
}

///|
pub fn KeyPressCallback::new(
  pattern : KeyPressPattern,
  callback : async (PromptSnapshot) -> KeyPressCallbackResult,
) -> KeyPressCallback {
  { pattern, callback }
}

///|
pub struct PromptCallbacks {
  key_press_callbacks : Array[KeyPressCallback]
  highlight_callback : async (PromptSnapshot) -> Array[FormatSpan]
  completion_span_provider : async (PromptSnapshot) -> CompletionSpan
  completion_items_provider : async (CompletionRequest) -> Array[CompletionItem]
  should_open_completion_window : async (CompletionTriggerContext) -> Bool
  transform_key_press : async (KeyTransformContext) -> KeyPress
  confirm_completion_commit : async (CompletionCommitContext) -> Bool
  should_insert_soft_newline : async (PromptSnapshot, KeyPress) -> Bool
  format_input : async (FormatInputContext) -> (String, Int)
  overload_provider : async (PromptSnapshot) -> (Array[OverloadItem], Int)
}

///|
#warnings("-unused_async")
async fn default_completion_span(snapshot : PromptSnapshot) -> CompletionSpan {
  let text = snapshot.text
  let caret = snapshot.caret

  let mut start = caret
  while start > 0 {
    let ch = text.get_char(start - 1).unwrap()
    if ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '_' {
      start = start - 1
    } else {
      break
    }
  }

  let mut end = caret
  while end < text.length() {
    let ch = text.get_char(end).unwrap()
    if ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '_' {
      end = end + 1
    } else {
      break
    }
  }

  CompletionSpan::new(start, end - start)
}

///|
#warnings("-unused_async")
async fn default_should_open_completion_window(
  ctx : CompletionTriggerContext,
) -> Bool {
  let text = ctx.snapshot.text
  let caret = ctx.snapshot.caret
  if caret > 0 {
    let previous = text.get_char(caret - 1).unwrap()
    if previous == '.' || previous == '(' {
      return true
    }
  }

  if caret == 1 {
    let first = text.get_char(0).unwrap()
    let second_is_word = text.length() > 1 &&
      text.get_char(1).unwrap().is_ascii_alphabetic()
    return !first.is_whitespace() && !second_is_word
  }

  caret - 2 >= 0 &&
  text.get_char(caret - 2).unwrap().is_whitespace() &&
  text.get_char(caret - 1).unwrap().is_ascii_alphabetic()
}

///|
pub fn PromptCallbacks::new(
  key_press_callbacks? : Array[KeyPressCallback] = [],
  highlight_callback? : async (PromptSnapshot) -> Array[FormatSpan] = _ => [],
  completion_span_provider? : async (PromptSnapshot) -> CompletionSpan = default_completion_span,
  completion_items_provider? : async (CompletionRequest) -> Array[
    CompletionItem,
  ] = _ => [],
  should_open_completion_window? : async (CompletionTriggerContext) -> Bool = default_should_open_completion_window,
  transform_key_press? : async (KeyTransformContext) -> KeyPress = ctx => {
    ctx.key_press
  },
  confirm_completion_commit? : async (CompletionCommitContext) -> Bool = _ => {
    true
  },
  should_insert_soft_newline? : async (PromptSnapshot, KeyPress) -> Bool = (
    _,
    _,
  ) => false,
  format_input? : async (FormatInputContext) -> (String, Int) = ctx => {
    (ctx.snapshot.text, ctx.snapshot.caret)
  },
  overload_provider? : async (PromptSnapshot) -> (Array[OverloadItem], Int) = _ => {
    ([], 0)
  },
) -> PromptCallbacks {
  {
    key_press_callbacks,
    highlight_callback,
    completion_span_provider,
    completion_items_provider,
    should_open_completion_window,
    transform_key_press,
    confirm_completion_commit,
    should_insert_soft_newline,
    format_input,
    overload_provider,
  }
}

///|
pub impl Default for PromptCallbacks with default() {
  PromptCallbacks::new()
}

///|
struct KeyPressPattern {
  key : @console.ConsoleKey
  shift : Bool
  alt : Bool
  control : Bool
}

///|
pub fn KeyPressPattern::new(
  key : @console.ConsoleKey,
  shift? : Bool = false,
  alt? : Bool = false,
  control? : Bool = false,
) -> KeyPressPattern {
  { key, shift, alt, control }
}

///|
pub fn KeyPressPattern::matches(
  self : KeyPressPattern,
  key_info : @console.ConsoleKeyInfo,
) -> Bool {
  self.key == key_info.key &&
  self.shift == key_info.shift &&
  self.alt == key_info.alt &&
  self.control == key_info.control
}

///|
struct KeyPressPatterns {
  patterns : Array[KeyPressPattern]
}

///|
pub fn KeyPressPatterns::new(
  patterns : Array[KeyPressPattern],
) -> KeyPressPatterns {
  { patterns, }
}

///|
pub fn KeyPressPatterns::single(pattern : KeyPressPattern) -> KeyPressPatterns {
  { patterns: [pattern] }
}

///|
pub fn KeyPressPatterns::empty() -> KeyPressPatterns {
  { patterns: [] }
}

///|
pub fn KeyPressPatterns::has_any(self : KeyPressPatterns) -> Bool {
  self.patterns.length() > 0
}

///|
pub fn KeyPressPatterns::matches(
  self : KeyPressPatterns,
  key_info : @console.ConsoleKeyInfo,
) -> Bool {
  self.patterns.iter().any(pattern => pattern.matches(key_info))
}

///|
pub struct KeyBindings {
  submit_prompt : KeyPressPatterns
  new_line : KeyPressPatterns
  cancel_prompt : KeyPressPatterns
  history_previous : KeyPressPatterns
  history_next : KeyPressPatterns
  backward_delete_character : KeyPressPatterns
  delete_character : KeyPressPatterns
  move_left : KeyPressPatterns
  move_right : KeyPressPatterns
  move_home : KeyPressPatterns
  move_end : KeyPressPatterns
  move_word_left : KeyPressPatterns
  move_word_right : KeyPressPatterns
  backward_delete_word : KeyPressPatterns
  delete_word : KeyPressPatterns
  trigger_completion : KeyPressPatterns
  dismiss_completion : KeyPressPatterns
  undo : KeyPressPatterns
  redo : KeyPressPatterns
  move_left_selection : KeyPressPatterns
  move_right_selection : KeyPressPatterns
  move_home_selection : KeyPressPatterns
  move_end_selection : KeyPressPatterns
  copy_selection : KeyPressPatterns
  cut_selection : KeyPressPatterns
  kill_line_tail : KeyPressPatterns
  kill_line_head : KeyPressPatterns
  yank : KeyPressPatterns
  yank_pop : KeyPressPatterns
  reverse_search : KeyPressPatterns
  paste : KeyPressPatterns
}

///|
let default_submit_prompt_patterns : KeyPressPatterns = KeyPressPatterns::new([
  KeyPressPattern::new(Enter),
  KeyPressPattern::new(Enter, control=true),
  KeyPressPattern::new(Enter, alt=true, control=true),
])

///|
let default_new_line_patterns : KeyPressPatterns = KeyPressPatterns::single(
  KeyPressPattern::new(Enter, control=true),
)

///|
let default_cancel_prompt_patterns : KeyPressPatterns = KeyPressPatterns::single(
  KeyPressPattern::new(C, control=true),
)

///|
let default_backward_delete_character_patterns : KeyPressPatterns = @console.Backspace
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_history_previous_patterns : KeyPressPatterns = @console.UpArrow
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_history_next_patterns : KeyPressPatterns = @console.DownArrow
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_delete_character_patterns : KeyPressPatterns = @console.Delete
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_move_left_patterns : KeyPressPatterns = @console.LeftArrow
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_move_right_patterns : KeyPressPatterns = @console.RightArrow
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_move_home_patterns : KeyPressPatterns = @console.Home
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_move_end_patterns : KeyPressPatterns = @console.End
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_move_word_left_patterns : KeyPressPatterns = KeyPressPatterns::new([
  KeyPressPattern::new(LeftArrow, control=true),
  KeyPressPattern::new(B, alt=true),
])

///|
let default_move_word_right_patterns : KeyPressPatterns = KeyPressPatterns::new([
    KeyPressPattern::new(RightArrow, control=true),
    KeyPressPattern::new(F, alt=true),
  ],
)

///|
let default_backward_delete_word_patterns : KeyPressPatterns = KeyPressPatterns::new([
    KeyPressPattern::new(W, control=true),
    KeyPressPattern::new(Backspace, alt=true),
  ],
)

///|
let default_delete_word_patterns : KeyPressPatterns = KeyPressPatterns::new([
  KeyPressPattern::new(D, alt=true),
  KeyPressPattern::new(Delete, control=true),
])

///|
let default_trigger_completion_patterns : KeyPressPatterns = @console.Tab
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_dismiss_completion_patterns : KeyPressPatterns = @console.Escape
  |> KeyPressPattern::new
  |> KeyPressPatterns::single

///|
let default_undo_patterns : KeyPressPatterns = @console.Z
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_redo_patterns : KeyPressPatterns = @console.Y
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_move_left_selection_patterns : KeyPressPatterns = @console.LeftArrow
  |> KeyPressPattern::new(shift=true)
  |> KeyPressPatterns::single

///|
let default_move_right_selection_patterns : KeyPressPatterns = @console.RightArrow
  |> KeyPressPattern::new(shift=true)
  |> KeyPressPatterns::single

///|
let default_move_home_selection_patterns : KeyPressPatterns = @console.Home
  |> KeyPressPattern::new(shift=true)
  |> KeyPressPatterns::single

///|
let default_move_end_selection_patterns : KeyPressPatterns = @console.End
  |> KeyPressPattern::new(shift=true)
  |> KeyPressPatterns::single

///|
let default_copy_selection_patterns : KeyPressPatterns = @console.C
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_cut_selection_patterns : KeyPressPatterns = @console.X
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_kill_line_tail_patterns : KeyPressPatterns = @console.K
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_kill_line_head_patterns : KeyPressPatterns = @console.U
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_yank_patterns : KeyPressPatterns = @console.Y
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_yank_pop_patterns : KeyPressPatterns = @console.Y
  |> KeyPressPattern::new(alt=true)
  |> KeyPressPatterns::single

///|
let default_reverse_search_patterns : KeyPressPatterns = @console.R
  |> KeyPressPattern::new(control=true)
  |> KeyPressPatterns::single

///|
let default_paste_patterns : KeyPressPatterns = KeyPressPatterns::new([
  KeyPressPattern::new(V, control=true),
  KeyPressPattern::new(Insert, shift=true),
  KeyPressPattern::new(V, control=true, shift=true),
])

///|
pub fn KeyBindings::new(
  submit_prompt? : KeyPressPatterns = default_submit_prompt_patterns,
  new_line? : KeyPressPatterns = default_new_line_patterns,
  cancel_prompt? : KeyPressPatterns = default_cancel_prompt_patterns,
  history_previous? : KeyPressPatterns = default_history_previous_patterns,
  history_next? : KeyPressPatterns = default_history_next_patterns,
  backward_delete_character? : KeyPressPatterns = default_backward_delete_character_patterns,
  delete_character? : KeyPressPatterns = default_delete_character_patterns,
  move_left? : KeyPressPatterns = default_move_left_patterns,
  move_right? : KeyPressPatterns = default_move_right_patterns,
  move_home? : KeyPressPatterns = default_move_home_patterns,
  move_end? : KeyPressPatterns = default_move_end_patterns,
  move_word_left? : KeyPressPatterns = default_move_word_left_patterns,
  move_word_right? : KeyPressPatterns = default_move_word_right_patterns,
  backward_delete_word? : KeyPressPatterns = default_backward_delete_word_patterns,
  delete_word? : KeyPressPatterns = default_delete_word_patterns,
  trigger_completion? : KeyPressPatterns = default_trigger_completion_patterns,
  dismiss_completion? : KeyPressPatterns = default_dismiss_completion_patterns,
  undo? : KeyPressPatterns = default_undo_patterns,
  redo? : KeyPressPatterns = default_redo_patterns,
  move_left_selection? : KeyPressPatterns = default_move_left_selection_patterns,
  move_right_selection? : KeyPressPatterns = default_move_right_selection_patterns,
  move_home_selection? : KeyPressPatterns = default_move_home_selection_patterns,
  move_end_selection? : KeyPressPatterns = default_move_end_selection_patterns,
  copy_selection? : KeyPressPatterns = default_copy_selection_patterns,
  cut_selection? : KeyPressPatterns = default_cut_selection_patterns,
  kill_line_tail? : KeyPressPatterns = default_kill_line_tail_patterns,
  kill_line_head? : KeyPressPatterns = default_kill_line_head_patterns,
  yank? : KeyPressPatterns = default_yank_patterns,
  yank_pop? : KeyPressPatterns = default_yank_pop_patterns,
  reverse_search? : KeyPressPatterns = default_reverse_search_patterns,
  paste? : KeyPressPatterns = default_paste_patterns,
) -> KeyBindings {
  {
    submit_prompt,
    new_line,
    cancel_prompt,
    history_previous,
    history_next,
    backward_delete_character,
    delete_character,
    move_left,
    move_right,
    move_home,
    move_end,
    move_word_left,
    move_word_right,
    backward_delete_word,
    delete_word,
    trigger_completion,
    dismiss_completion,
    undo,
    redo,
    move_left_selection,
    move_right_selection,
    move_home_selection,
    move_end_selection,
    copy_selection,
    cut_selection,
    kill_line_tail,
    kill_line_head,
    yank,
    yank_pop,
    reverse_search,
    paste,
  }
}

///|
impl Default for KeyBindings with default() {
  KeyBindings::new()
}

///|
pub struct PromptConfiguration {
  prompt : FormattedString
  key_bindings : KeyBindings
  history : HistoryConfiguration
  callbacks : PromptCallbacks
  selection_background : AnsiColor
  use_colors : Bool
  max_completion_items_count : Int
  kill_ring_max_size : Int
}

///|
pub fn PromptConfiguration::new(
  prompt? : FormattedString = FormattedString::new("> "),
  key_bindings? : KeyBindings = KeyBindings::default(),
  history? : HistoryConfiguration = HistoryConfiguration::default(),
  callbacks? : PromptCallbacks = PromptCallbacks::default(),
  selection_background? : AnsiColor = Blue,
  use_colors? : Bool = true,
  max_completion_items_count? : Int = 5,
  kill_ring_max_size? : Int = 20,
) -> PromptConfiguration {
  {
    prompt,
    key_bindings,
    history,
    callbacks,
    selection_background,
    use_colors,
    max_completion_items_count,
    kill_ring_max_size,
  }
}

///|
pub impl Default for PromptConfiguration with default() {
  PromptConfiguration::new()
}