///|
pub struct LayerSummary {
  z_value : Double
  motion_count_value : Int
  cutting_distance_value : Double
} derive(Eq, Debug)

///|
pub fn LayerSummary::z(self : LayerSummary) -> Double {
  self.z_value
}

///|
pub fn LayerSummary::motion_count(self : LayerSummary) -> Int {
  self.motion_count_value
}

///|
pub fn LayerSummary::cutting_distance(self : LayerSummary) -> Double {
  self.cutting_distance_value
}

///|
/// Group motions by endpoint Z while retaining first-seen order.
pub fn summarize_layers(
  program : GCodeProgram,
  tolerance? : Double = 0.000001,
) -> Array[LayerSummary] {
  let layers : Array[LayerSummary] = []
  for motion in program.motions() {
    let z = motion.end().z()
    let mut found = -1
    for index, layer in layers {
      if absolute(layer.z() - z) <= tolerance {
        found = index
        break
      }
    }
    let length = if motion.kind() == Rapid {
      0.0
    } else {
      motion_path_length(motion)
    }
    if found < 0 {
      layers.push({
        z_value: z,
        motion_count_value: 1,
        cutting_distance_value: length,
      })
    } else {
      let old = layers[found]
      layers[found] = {
        ..old,
        motion_count_value: old.motion_count() + 1,
        cutting_distance_value: old.cutting_distance() + length,
      }
    }
  }
  layers
}