///|
/// Subscribes to the ViewModel's outgoing cursor event alias.
pub fn ViewModel::on_cursor_state_changed(
  self : ViewModel,
  listener : (CursorStateChangedEvent) -> Unit,
) -> @base_common.Disposable {
  self.cursor_event_dispatcher.on_event(listener)
}

///|
/// Sends an accepted controller transition through the outgoing-only
/// dispatcher and reports whether the full model/view state changed. A
/// view-only change returns true but remains silent publicly.
fn ViewModel::emit_cursor_state_change(
  self : ViewModel,
  change : @cursor.CursorStateChange?,
) -> Bool {
  match change {
    None => false
    Some(change) => {
      if change.has_outgoing_event {
        self.cursor_event_dispatcher.emit_outgoing_event(
          CursorStateChangedEvent::from_change(change),
        )
      }
      true
    }
  }
}

///|
/// Validates a view-side cursor state with `PositionAffinity.None`, preserving
/// source leftover arithmetic by adding each column correction to its residue.
fn ViewModel::validate_view_cursor_state(
  self : ViewModel,
  state : @cursor.SingleCursorState,
) -> @cursor.SingleCursorState {
  let valid_position = self.normalize_position(state.position, None)
  let valid_start = if state.selection_start_start == state.position {
    valid_position
  } else {
    self.normalize_position(state.selection_start_start, None)
  }
  let valid_end = if state.selection_start_end == state.selection_start_start {
    valid_start
  } else {
    self.normalize_position(state.selection_start_end, None)
  }
  if valid_position == state.position &&
    valid_start == state.selection_start_start &&
    valid_end == state.selection_start_end {
    return state
  }
  SingleCursorState(
    valid_start,
    valid_end,
    state.selection_start_kind,
    valid_position,
    selection_start_leftover_visible_columns=state.selection_start_leftover_visible_columns +
      state.selection_start_start.column -
      valid_start.column,
    leftover_visible_columns=state.leftover_visible_columns +
      state.position.column -
      valid_position.column,
  )
}

///|
/// Applies one source-shaped partial state through the common controller and
/// outgoing-dispatcher spine. `None` preserves the source's both-null no-op.
pub fn ViewModel::set_cursor_state(
  self : ViewModel,
  state : @cursor.PartialCursorState?,
  source : String,
  reason : @editor_api.CursorChangeReason,
) -> Bool {
  self.emit_cursor_state_change(
    self.cursor.set_cursor_state(state, source~, reason~),
  )
}

///|
/// Applies a model-side state through the common transition/dispatcher spine.
pub fn ViewModel::set_model_cursor_state(
  self : ViewModel,
  state : @cursor.SingleCursorState,
  source : String,
  reason : @editor_api.CursorChangeReason,
) -> Bool {
  self.set_cursor_state(
    Some(@cursor.CursorState::from_model_state(state)),
    source,
    reason,
  )
}

///|
/// Applies a view-side state after source-shaped normalization, then emits the
/// resulting outgoing cursor fact when its model side changed.
pub fn ViewModel::set_view_cursor_state(
  self : ViewModel,
  state : @cursor.SingleCursorState,
  source : String,
  reason : @editor_api.CursorChangeReason,
) -> Bool {
  let state = self.validate_view_cursor_state(state)
  self.set_cursor_state(
    Some(@cursor.CursorState::from_view_state(state)),
    source,
    reason,
  )
}