///|
/// Stable urgency ordering used by the constructive solver.
pub fn sort_visits_for_scheduling(values : Array[Visit]) -> Array[Visit] {
  let sorted = copy_array(values)
  for index = 1; index < sorted.length(); index = index + 1 {
    let current = sorted[index]
    let mut position = index
    while position > 0 {
      let previous = sorted[position - 1]
      if !visit_before(current, previous) {
        break
      }
      sorted[position] = previous
      position = position - 1
    }
    sorted[position] = current
  }
  sorted
}

///|
/// Compare visits by required status, priority, flexibility, duration and id.
pub fn visit_before(left : Visit, right : Visit) -> Bool {
  if left.required != right.required {
    return left.required
  }
  let left_priority = priority_value(left.priority)
  let right_priority = priority_value(right.priority)
  if left_priority != right_priority {
    return left_priority > right_priority
  }
  let left_slack = left.window.duration() - left.duration_minutes
  let right_slack = right.window.duration() - right.duration_minutes
  if left_slack != right_slack {
    return left_slack < right_slack
  }
  if left.duration_minutes != right.duration_minutes {
    return left.duration_minutes > right.duration_minutes
  }
  if left.window.start_minute != right.window.start_minute {
    return left.window.start_minute < right.window.start_minute
  }
  left.id < right.id
}

///|
/// Stable assignment comparison for serialized schedules.
pub fn assignment_before(left : Assignment, right : Assignment) -> Bool {
  if left.start_minute != right.start_minute {
    return left.start_minute < right.start_minute
  }
  if left.worker_id != right.worker_id {
    return left.worker_id < right.worker_id
  }
  left.visit_id < right.visit_id
}

///|
/// Sort schedule assignments for reproducible output.
pub fn sort_schedule_assignments(
  values : Array[Assignment],
) -> Array[Assignment] {
  let sorted = copy_array(values)
  for index = 1; index < sorted.length(); index = index + 1 {
    let current = sorted[index]
    let mut position = index
    while position > 0 && assignment_before(current, sorted[position - 1]) {
      sorted[position] = sorted[position - 1]
      position = position - 1
    }
    sorted[position] = current
  }
  sorted
}

///|
/// Sort strings lexicographically without mutating the caller's array.
pub fn sort_strings(values : Array[String]) -> Array[String] {
  let sorted = copy_array(values)
  for index = 1; index < sorted.length(); index = index + 1 {
    let current = sorted[index]
    let mut position = index
    while position > 0 && current < sorted[position - 1] {
      sorted[position] = sorted[position - 1]
      position = position - 1
    }
    sorted[position] = current
  }
  sorted
}