///|
priv enum KeyProcessingResult {
  Handled
  NotHandled
  Return(PromptResult)
}

///|
priv enum KeyPhase {
  KeyDown
  KeyUp
}

///|
priv struct InputState {
  left : Array[Char]
  right : Array[Char]
  mut last_was_yank : Bool
  mut render_requested : Bool
}

///|
fn InputState::new() -> InputState {
  { left: [], right: [], last_was_yank: false, render_requested: false }
}

///|
fn InputState::request_render(self : InputState) -> Unit {
  self.render_requested = true
}

///|
fn InputState::take_render_requested(self : InputState) -> Bool {
  let requested = self.render_requested
  self.render_requested = false
  requested
}

///|
priv enum PaneEntry {
  SearchModeHandler
  NavigationModesHandler
  SearchTriggerHandler
  SelectionClipboardHandler
  PasteHandler
  KillRingHandler
  SelectionMovementHandler
  UndoRedoHandler
  CompletionAndHistoryHandler
  SubmitOrCancelHandler
  EditKeysHandler
  CharacterInputHandler
}

///|
priv struct PaneContext {
  runtime : Prompt
  prompt : FormattedString
  state : InputState
  key_bindings : KeyBindings
  key_press : KeyPress
}

///|
fn PaneContext::new(
  runtime : Prompt,
  prompt : FormattedString,
  state : InputState,
  key_bindings : KeyBindings,
  key_press : KeyPress,
) -> PaneContext {
  { runtime, prompt, state, key_bindings, key_press }
}

///|
priv trait Pane {
  supports_phase(Self, KeyPhase) -> Bool
  async on_key(Self, PaneContext, KeyPhase) -> KeyProcessingResult
}

///|
impl Pane for PaneEntry with supports_phase(self, phase) {
  match phase {
    KeyDown => true
    KeyUp => self is CharacterInputHandler
  }
}

///|
impl Pane for PaneEntry with on_key(self, context, phase) {
  let runtime = context.runtime
  let prompt = context.prompt
  let state = context.state
  let key_bindings = context.key_bindings
  let key_press = context.key_press
  let info = key_press.console_key_info
  match phase {
    KeyDown =>
      match self {
        SearchModeHandler =>
          SearchPane::new().handle_search_mode(
            runtime, state, key_bindings, info,
          )
        NavigationModesHandler =>
          CompletionPane::new().handle_navigation_modes(
            runtime, state, key_bindings, info,
          )
        SearchTriggerHandler =>
          SearchPane::new().handle_search_trigger(
            runtime, state, key_bindings, info,
          )
        SelectionClipboardHandler =>
          runtime.handle_selection_clipboard(state, key_bindings, info)
        PasteHandler => runtime.handle_paste(state, key_bindings, info)
        KillRingHandler => runtime.handle_kill_ring(state, key_bindings, info)
        SelectionMovementHandler =>
          runtime.handle_selection_movement_keys(state, key_bindings, info)
        UndoRedoHandler => runtime.handle_undo_redo(state, key_bindings, info)
        CompletionAndHistoryHandler =>
          CompletionPane::new().handle_completion_and_history(
            runtime, state, key_bindings, key_press,
          )
        SubmitOrCancelHandler =>
          runtime.handle_submit_or_cancel(state, key_bindings, key_press)
        EditKeysHandler =>
          runtime.handle_edit_keys(state, key_bindings, key_press)
        CharacterInputHandler =>
          runtime.handle_character_input(state, key_press)
      }
    KeyUp => runtime.commit_requested_render(prompt, state)
  }
}

///|
fn default_pane_entries() -> Array[PaneEntry] {
  [
    SearchModeHandler,
    NavigationModesHandler,
    SearchTriggerHandler,
    SelectionClipboardHandler,
    PasteHandler,
    KillRingHandler,
    SelectionMovementHandler,
    UndoRedoHandler,
    CompletionAndHistoryHandler,
    SubmitOrCancelHandler,
    EditKeysHandler,
    CharacterInputHandler,
  ]
}

///|
priv struct PanePipeline {
  panes : Array[PaneEntry]
}

///|
fn PanePipeline::new(panes : Array[PaneEntry]) -> PanePipeline {
  { panes, }
}

///|
fn default_pane_pipeline() -> PanePipeline {
  PanePipeline::new(default_pane_entries())
}

///|
async fn Prompt::run_pane_pipeline(
  self : Prompt,
  prompt : FormattedString,
  state : InputState,
  key_bindings : KeyBindings,
  key_press : KeyPress,
  pipeline : PanePipeline,
) -> KeyProcessingResult {
  let down = self.run_pane_phase(
    prompt,
    state,
    key_bindings,
    key_press,
    pipeline,
    KeyDown,
  )
  if down is Return(_) {
    return down
  }

  let up = self.run_pane_phase(
    prompt,
    state,
    key_bindings,
    key_press,
    pipeline,
    KeyUp,
  )
  if up is Return(_) {
    return up
  }

  if down is Handled || up is Handled {
    Handled
  } else {
    NotHandled
  }
}

///|
async fn Prompt::commit_requested_render(
  self : Prompt,
  prompt : FormattedString,
  state : InputState,
) -> KeyProcessingResult {
  if state.take_render_requested() {
    self.render_line(prompt, state.left, state.right)
    Handled
  } else {
    NotHandled
  }
}

///|
async fn Prompt::run_pane_phase(
  self : Prompt,
  prompt : FormattedString,
  state : InputState,
  key_bindings : KeyBindings,
  key_press : KeyPress,
  pipeline : PanePipeline,
  phase : KeyPhase,
) -> KeyProcessingResult {
  for pane in pipeline.panes {
    if !pane.supports_phase(phase) {
      continue
    }
    let context = PaneContext::new(self, prompt, state, key_bindings, key_press)
    let result = pane.on_key(context, phase)
    if !(result is NotHandled) {
      return result
    }
  }
  NotHandled
}