///|
/// Owns the readonly editor's primary cursor and the single transition spine
/// shared by API, pointer, keyboard, and model-content changes. This is the
/// single-cursor reduction of Monaco's `CursorsController` plus
/// `CursorCollection`: every accepted transition commits a paired model/view
/// state and returns one fact for the ViewModel's outgoing dispatcher.
struct CursorsController {
  model : @model.TextModel
  mut known_model_version_id : Int32
  mut context : CursorContext
  mut cursor : Cursor
  mut is_handling : Bool
}

///|
/// Builds a controller at the document origin and captures the model's
/// internal content version, never its caller-owned host metadata version.
pub fn CursorsController::CursorsController(
  model : @model.TextModel,
  context : CursorContext,
) -> CursorsController {
  {
    model,
    known_model_version_id: model.get_version_id(),
    context,
    cursor: Cursor(),
    is_handling: false,
  }
}

///|
/// Installs a changed converter and reprojects only from the validated model
/// side. The source's simultaneous model/view cross-validation surface is an
/// approved deferral; this local reduced seam therefore emits no public model
/// cursor event.
pub fn CursorsController::set_context(
  self : CursorsController,
  context : CursorContext,
) -> Unit {
  self.context = context
  if self.known_model_version_id != self.model.get_version_id() {
    // The content event's second loop will recover/reset the cursor after the
    // view-model structural update finishes.
    return
  }
  if self.is_handling {
    // A nested injected-text/mapping notification is covered by the outer
    // reprojection, matching the source recursion guard.
    return
  }
  self.is_handling = true
  self.cursor.reproject(context)
  self.is_handling = false
}

///|
/// Returns the paired primary state.
pub fn CursorsController::get_primary_cursor_state(
  self : CursorsController,
) -> CursorState {
  self.cursor.as_cursor_state()
}

///|
/// The current model-coordinate state.
pub fn CursorsController::model_state(
  self : CursorsController,
) -> SingleCursorState {
  self.cursor.model_state
}

///|
/// The current view-coordinate state.
pub fn CursorsController::view_state(
  self : CursorsController,
) -> SingleCursorState {
  self.cursor.view_state
}

///|
/// Captures internal version and paired cursor state as one equality unit.
fn CursorsController::snapshot(self : CursorsController) -> CursorModelState {
  {
    model_version_id: self.model.get_version_id(),
    cursor_state: self.cursor.as_cursor_state(),
  }
}

///|
/// Validates a supplied model state. Leftover columns survive only when the
/// corresponding anchor range or active position survives validation exactly.
fn CursorsController::validate_model_state(
  self : CursorsController,
  state : SingleCursorState,
) -> SingleCursorState {
  let validated_range = self.model.validate_range(
    Range(
      state.selection_start_start.line_number,
      state.selection_start_start.column,
      state.selection_start_end.line_number,
      state.selection_start_end.column,
    ),
  )
  let validated_start = @base_common.Position(
    validated_range.start_line_number,
    validated_range.start_column,
  )
  let validated_end = @base_common.Position(
    validated_range.end_line_number,
    validated_range.end_column,
  )
  let anchor_unchanged = validated_start == state.selection_start_start &&
    validated_end == state.selection_start_end
  let validated_position = self.model.validate_position(state.position)
  SingleCursorState(
    validated_start,
    validated_end,
    state.selection_start_kind,
    validated_position,
    selection_start_leftover_visible_columns=if anchor_unchanged {
      state.selection_start_leftover_visible_columns
    } else {
      0
    },
    leftover_visible_columns=if validated_position == state.position {
      state.leftover_visible_columns
    } else {
      0
    },
  )
}

///|
/// `CursorCollection.normalize`'s exact primary-only branch. This controller's
/// representation contains one `cursor` rather than a cursor array, so the
/// source condition `cursors.length === 1` is a structural invariant and the
/// method returns before allocating or sorting any normalization workspace.
fn CursorsController::normalize(self : CursorsController) -> Unit {
  ignore(self)
  return
}

///|
/// Applies one source-shaped partial state and returns a change fact only when
/// the full paired state or internal version changed. `None` is the source's
/// both-sides-null early return. A full state follows the model-authoritative
/// reprojection seam documented by ONE-028; live callers otherwise provide
/// exactly one side.
pub fn CursorsController::set_cursor_state(
  self : CursorsController,
  state : PartialCursorState?,
  source~ : String = "keyboard",
  reason~ : @editor_api.CursorChangeReason = NotSet,
) -> CursorStateChange? {
  let state = match state {
    None => return None
    Some(state) => state
  }
  let old_state = self.snapshot()
  let supplied_model_state = state.model_state()
  let supplied_view_state = state.view_state()
  let validated_model_state = match supplied_model_state {
    Some(model_state) => Some(self.validate_model_state(model_state))
    None => None
  }
  let state_changed = self.cursor.set_state(
    self.context,
    validated_model_state,
    supplied_view_state,
  )
  self.normalize()
  if !state_changed {
    return None
  }
  if supplied_model_state is None {
    // The supplied view state is authoritative under wrap/injected text. The
    // converter derives its model side and the model validates that side, but
    // the approved reduced seam must not round-trip it back to a potentially
    // different view position. Source dual-side cross-validation remains the
    // explicit ONE-028 deferral.
    let derived_model_state = self.cursor.model_state
    let validated_model_state = self.validate_model_state(derived_model_state)
    // In the source's missing-model branch the view state's two residues are
    // retained after view->model conversion/validation; the zero-on-clamp rule
    // applies only when callers supplied a model state directly.
    self.cursor.model_state = {
      ..validated_model_state,
      selection_start_leftover_visible_columns: derived_model_state.selection_start_leftover_visible_columns,
      leftover_visible_columns: derived_model_state.leftover_visible_columns,
    }
  }
  self.finish_transition(Some(old_state), source, reason)
}

///|
/// Completes an accepted mutation. Full equality is the first gate; public
/// outgoing delivery is a second gate that ignores view-only reprojections.
fn CursorsController::finish_transition(
  self : CursorsController,
  old_state : CursorModelState?,
  source : String,
  reason : @editor_api.CursorChangeReason,
) -> CursorStateChange? {
  let new_state = self.snapshot()
  match old_state {
    Some(old) if old == new_state => return None
    _ => ()
  }
  let source = if source == "" { "keyboard" } else { source }
  let (old_selections, old_model_version_id, has_outgoing_event) = match
    old_state {
    None => (None, 0, true)
    Some(old) =>
      (
        Some([old.cursor_state.model_state.selection()]),
        old.model_version_id,
        old.cursor_state.model_state != new_state.cursor_state.model_state,
      )
  }
  Some({
    old_selections,
    selections: [new_state.cursor_state.model_state.selection()],
    old_model_version_id,
    model_version_id: new_state.model_version_id,
    source,
    reason,
    reached_max_cursor_count: false,
    has_outgoing_event,
  })
}

///|
/// Model-content transition. A flush updates the known internal version,
/// rebuilds the cursor at the origin, and deliberately compares against a null
/// old snapshot so it emits even when the visible cursor was already there.
pub fn CursorsController::on_model_content_changed(
  self : CursorsController,
  event : @model.InternalModelContentChangeEvent,
) -> CursorStateChange? {
  self.known_model_version_id = event.raw_content_changed_event.version_id
  if self.is_handling {
    return None
  }
  if !event.raw_content_changed_event.contains_event(Flush) {
    return None
  }
  self.cursor = Cursor()
  self.finish_transition(None, "model", ContentFlush)
}

///|
/// The selection in model coordinates.
pub fn CursorsController::get_model_selection(
  self : CursorsController,
) -> @core.Selection {
  self.cursor.model_state.selection()
}

///|
/// The selection in view coordinates.
pub fn CursorsController::get_view_selection(
  self : CursorsController,
) -> @core.Selection {
  self.cursor.view_state.selection()
}