///|
/// A group of steps executed in one lifecycle phase.
pub(all) struct ExecutionPhase {
  phase : PlanPhase
  steps : Array[MeasurementStep]
  device_id : String
}

///|
fn phase_for_step(step : MeasurementStep) -> PlanPhase {
  if step.direction == "datum-aligned" {
    PlanPhase::EstablishDatum
  } else {
    PlanPhase::Measure
  }
}

///|
/// Group plan steps into stable datum and measurement phases.
pub fn execution_phases(plan : MeasurementPlan) -> Array[ExecutionPhase] {
  let datum_steps = []
  let measurement_steps = []
  for step in plan.steps {
    if phase_for_step(step) == PlanPhase::EstablishDatum {
      datum_steps.push(step)
    } else {
      measurement_steps.push(step)
    }
  }
  let result = []
  if datum_steps.length() > 0 {
    result.push({
      phase: PlanPhase::EstablishDatum,
      steps: datum_steps,
      device_id: datum_steps[0].device_id,
    })
  }
  if measurement_steps.length() > 0 {
    result.push({
      phase: PlanPhase::Measure,
      steps: measurement_steps,
      device_id: measurement_steps[0].device_id,
    })
  }
  result
}

///|
/// Return a stable datum-first sequence while grouping adjacent device steps.
pub fn grouped_execution_order(
  plan : MeasurementPlan,
) -> Array[MeasurementStep] {
  let ordered = execution_order(plan)
  let result = []
  let device_ids = []
  for step in ordered {
    let mut seen = false
    for device_id in device_ids {
      if device_id == step.device_id {
        seen = true
      }
    }
    if !seen {
      device_ids.push(step.device_id)
    }
  }
  for device_id in device_ids {
    for step in ordered {
      if step.device_id == device_id {
        result.push(step)
      }
    }
  }
  result
}

///|
/// Count device changes in an already ordered sequence.
pub fn device_switch_count(steps : Array[MeasurementStep]) -> Int {
  if steps.length() < 2 {
    0
  } else {
    let mut result = 0
    for i in 1..