///|
fn ElementNode::dispatch_keyboard(
  self : ElementNode,
  target : ElementId,
  event : KeyboardEvent,
) -> ElementEventResult {
  if self.spec is ModifiedSpec({ modifier: DisabledModifier(true), .. }) {
    return { changed: false, activated: false, focused: None }
  }
  if self.id == target {
    match self.spec {
      TextFieldSpec(data) => handle_text_field_keyboard(self, data, event)
      SearchBarSpec(data) => handle_searchbar_keyboard(self, data, event)
      RichTextEditorSpec(data) =>
        handle_rich_text_editor_keyboard(self, data, event)
      ButtonSpec(data) => handle_button_keyboard(self, data, event)
      CheckboxSpec(data) => handle_checkbox_keyboard(self, data, event)
      ModifiedSpec(data) => handle_focused_modified_keyboard(self, data, event)
      _ => { changed: false, activated: false, focused: None }
    }
  } else {
    let mut changed = false
    let mut activated = false
    for child in self.children {
      let result = child.dispatch_keyboard(target, event)
      changed = changed || result.changed
      activated = activated || result.activated
    }
    if changed {
      self.dirty.mark_paint()
    }
    { changed, activated, focused: None }
  }
}

///|
fn ElementNode::dispatch_keyboard_shortcut(
  self : ElementNode,
  event : KeyboardEvent,
) -> ElementEventResult {
  match self.spec {
    ModifiedSpec(data) =>
      match data.modifier {
        DisabledModifier(true) =>
          { changed: false, activated: false, focused: None }
        KeyboardShortcutModifier(shortcut) =>
          if shortcut.matches(event) {
            self.activate_primary()
          } else {
            self.dispatch_keyboard_shortcut_to_children(event)
          }
        _ => self.dispatch_keyboard_shortcut_to_children(event)
      }
    _ => self.dispatch_keyboard_shortcut_to_children(event)
  }
}

///|
fn ElementNode::dispatch_keyboard_shortcut_to_children(
  self : ElementNode,
  event : KeyboardEvent,
) -> ElementEventResult {
  let mut changed = false
  let mut activated = false
  for child in self.children {
    let result = child.dispatch_keyboard_shortcut(event)
    changed = changed || result.changed
    activated = activated || result.activated
    if result.activated {
      break
    }
  }
  if changed {
    self.dirty.mark_paint()
  }
  { changed, activated, focused: None }
}

///|
fn ElementNode::activate_primary(self : ElementNode) -> ElementEventResult {
  match self.spec {
    ButtonSpec(data) => {
      match data.on_click {
        Some(handler) => handler()
        None => ()
      }
      self.dirty.mark_paint()
      { changed: true, activated: true, focused: None }
    }
    CheckboxSpec(data) => {
      data.checked.set(!data.checked.get())
      self.dirty.mark_paint()
      { changed: true, activated: true, focused: None }
    }
    ModifiedSpec(data) =>
      match data.modifier {
        DisabledModifier(true) =>
          { changed: false, activated: false, focused: None }
        OnTapModifier(handler) => {
          handler()
          self.dirty.mark_paint()
          { changed: true, activated: true, focused: None }
        }
        _ => self.activate_first_child()
      }
    _ => self.activate_first_child()
  }
}

///|
fn ElementNode::activate_first_child(self : ElementNode) -> ElementEventResult {
  for child in self.children {
    let result = child.activate_primary()
    if result.activated || result.changed {
      return result
    }
  }
  { changed: false, activated: false, focused: None }
}

///|
fn handle_text_field_keyboard(
  element : ElementNode,
  data : TextFieldViewSpec,
  event : KeyboardEvent,
) -> ElementEventResult {
  if !event.pressed {
    return { changed: false, activated: false, focused: None }
  }
  if event.key == "Enter" {
    match data.on_submit {
      Some(handler) => handler()
      None => ()
    }
    element.dirty.mark_paint()
    return { changed: true, activated: true, focused: None }
  }
  if is_select_all_shortcut(event) {
    return select_text_range(element, data.value.get())
  }
  if is_undo_shortcut(event) {
    let result = handle_text_field_command(element, data, Undo, paste_text=None)
    return { changed: result.changed, activated: false, focused: None }
  }
  if is_redo_shortcut(event) {
    let result = handle_text_field_command(element, data, Redo, paste_text=None)
    return { changed: result.changed, activated: false, focused: None }
  }
  if event.key == "Backspace" {
    match active_text_selection(element.control.selection) {
      Some(selection) =>
        return handle_text_field_input(element, data, DeleteRange(selection))
      None =>
        return handle_text_field_input(element, data, DeleteSurrounding(1, 0))
    }
  }
  if event.key == "Delete" {
    match active_text_selection(element.control.selection) {
      Some(selection) =>
        return handle_text_field_input(element, data, DeleteRange(selection))
      None =>
        return handle_text_field_input(element, data, DeleteSurrounding(0, 1))
    }
  }
  if event.key == "ArrowLeft" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.value.get(),
        element.control.caret,
        element.control.selection,
        MoveLeft,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "ArrowRight" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.value.get(),
        element.control.caret,
        element.control.selection,
        MoveRight,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "Home" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.value.get(),
        element.control.caret,
        element.control.selection,
        MoveStart,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "End" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.value.get(),
        element.control.caret,
        element.control.selection,
        MoveEnd,
        extend=event.modifiers.shift,
      ),
    )
  }
  match event.text {
    Some(text) =>
      if text != "" {
        handle_text_field_input(element, data, InsertText(text))
      } else {
        { changed: false, activated: false, focused: None }
      }
    None => { changed: false, activated: false, focused: None }
  }
}

///|
fn is_select_all_shortcut(event : KeyboardEvent) -> Bool {
  event.pressed &&
  (event.key == "a" || event.key == "A") &&
  (event.modifiers.meta || event.modifiers.control)
}

///|
fn is_undo_shortcut(event : KeyboardEvent) -> Bool {
  event.pressed &&
  (event.key == "z" || event.key == "Z") &&
  !event.modifiers.shift &&
  (event.modifiers.meta || event.modifiers.control)
}

///|
fn is_redo_shortcut(event : KeyboardEvent) -> Bool {
  event.pressed &&
  (event.key == "z" || event.key == "Z") &&
  event.modifiers.shift &&
  (event.modifiers.meta || event.modifiers.control)
}

///|
fn select_text_range(
  element : ElementNode,
  text : String,
) -> ElementEventResult {
  let length = text_length(text)
  element.control.caret = length
  element.control.selection = Some(TextRange::new(start=0, end=length))
  element.control.composition = None
  element.control.composition_cursor = None
  element.dirty.mark_paint()
  { changed: true, activated: false, focused: None }
}

///|
fn handle_searchbar_keyboard(
  element : ElementNode,
  data : SearchBarViewSpec,
  event : KeyboardEvent,
) -> ElementEventResult {
  if !event.pressed {
    return { changed: false, activated: false, focused: None }
  }
  if event.key == "Enter" {
    match data.on_submit {
      Some(handler) => handler()
      None => ()
    }
    element.dirty.mark_paint()
    return { changed: true, activated: true, focused: None }
  }
  if is_select_all_shortcut(event) {
    return select_text_range(element, data.query.get())
  }
  if is_undo_shortcut(event) {
    let result = handle_searchbar_command(element, data, Undo, paste_text=None)
    return { changed: result.changed, activated: false, focused: None }
  }
  if is_redo_shortcut(event) {
    let result = handle_searchbar_command(element, data, Redo, paste_text=None)
    return { changed: result.changed, activated: false, focused: None }
  }
  if event.key == "Backspace" {
    match active_text_selection(element.control.selection) {
      Some(selection) =>
        return handle_searchbar_input(element, data, DeleteRange(selection))
      None =>
        return handle_searchbar_input(element, data, DeleteSurrounding(1, 0))
    }
  }
  if event.key == "Delete" {
    match active_text_selection(element.control.selection) {
      Some(selection) =>
        return handle_searchbar_input(element, data, DeleteRange(selection))
      None =>
        return handle_searchbar_input(element, data, DeleteSurrounding(0, 1))
    }
  }
  if event.key == "ArrowLeft" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.query.get(),
        element.control.caret,
        element.control.selection,
        MoveLeft,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "ArrowRight" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.query.get(),
        element.control.caret,
        element.control.selection,
        MoveRight,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "Home" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.query.get(),
        element.control.caret,
        element.control.selection,
        MoveStart,
        extend=event.modifiers.shift,
      ),
    )
  }
  if event.key == "End" {
    return apply_text_caret_move(
      element,
      move_text_caret(
        data.query.get(),
        element.control.caret,
        element.control.selection,
        MoveEnd,
        extend=event.modifiers.shift,
      ),
    )
  }
  match event.text {
    Some(text) =>
      if text != "" {
        handle_searchbar_input(element, data, InsertText(text))
      } else {
        { changed: false, activated: false, focused: None }
      }
    None => { changed: false, activated: false, focused: None }
  }
}

///|
fn handle_button_keyboard(
  element : ElementNode,
  data : ButtonViewSpec,
  event : KeyboardEvent,
) -> ElementEventResult {
  if event.pressed &&
    (event.key == "Enter" || event.key == "Space" || event.key == " ") {
    match data.on_click {
      Some(handler) => handler()
      None => ()
    }
    element.dirty.mark_paint()
    { changed: true, activated: true, focused: None }
  } else {
    { changed: false, activated: false, focused: None }
  }
}

///|
fn handle_focused_modified_keyboard(
  element : ElementNode,
  data : ModifiedViewSpec,
  event : KeyboardEvent,
) -> ElementEventResult {
  match data.modifier {
    DisabledModifier(true) =>
      { changed: false, activated: false, focused: None }
    FocusableModifier(true) =>
      if event.pressed &&
        (event.key == "Enter" || event.key == "Space" || event.key == " ") {
        element.activate_first_child()
      } else {
        { changed: false, activated: false, focused: None }
      }
    _ => { changed: false, activated: false, focused: None }
  }
}

///|
fn handle_checkbox_keyboard(
  element : ElementNode,
  data : CheckboxViewSpec,
  event : KeyboardEvent,
) -> ElementEventResult {
  if event.pressed &&
    (event.key == "Enter" || event.key == "Space" || event.key == " ") {
    data.checked.set(!data.checked.get())
    element.dirty.mark_paint()
    { changed: true, activated: true, focused: None }
  } else {
    { changed: false, activated: false, focused: None }
  }
}