///|
/// Summary metrics for comparing or previewing a motion curve.
pub(all) struct CurveReport {
  samples : Int
  minimum : Double
  maximum : Double
  endpoint_error : Double
  monotonic : Bool
  peak_velocity : Double
  peak_acceleration : Double
} derive(Debug)

///|
pub fn CurveReport::samples(self : CurveReport) -> Int {
  self.samples
}

///|
pub fn CurveReport::minimum(self : CurveReport) -> Double {
  self.minimum
}

///|
pub fn CurveReport::maximum(self : CurveReport) -> Double {
  self.maximum
}

///|
pub fn CurveReport::endpoint_error(self : CurveReport) -> Double {
  self.endpoint_error
}

///|
pub fn CurveReport::is_monotonic(self : CurveReport) -> Bool {
  self.monotonic
}

///|
pub fn CurveReport::peak_velocity(self : CurveReport) -> Double {
  self.peak_velocity
}

///|
pub fn CurveReport::peak_acceleration(self : CurveReport) -> Double {
  self.peak_acceleration
}

///|
fn build_report(func : (Double) -> Double, samples : Int) -> CurveReport {
  let count = if samples < 2 { 2 } else { samples }
  let first = func(0.0)
  let mut minimum = first
  let mut maximum = first
  let mut previous = first
  let mut monotonic = true
  let mut peak_velocity = 0.0
  let mut peak_acceleration = 0.0
  let step = 1.0 / (count - 1).to_double()
  for i in 1.. maximum {
      maximum = value
    }
    if value + 0.000000001 < previous {
      monotonic = false
    }
    let velocity = (value - previous) / step
    let acceleration = if i == 1 {
      0.0
    } else {
      (value - 2.0 * previous + func((i - 2).to_double() * step)) /
      (step * step)
    }
    if velocity.abs() > peak_velocity {
      peak_velocity = velocity.abs()
    }
    if acceleration.abs() > peak_acceleration {
      peak_acceleration = acceleration.abs()
    }
    previous = value
  }
  {
    samples: count,
    minimum,
    maximum,
    endpoint_error: (first - 0.0).abs() + (previous - 1.0).abs(),
    monotonic,
    peak_velocity,
    peak_acceleration,
  }
}

///|
/// Inspect an arbitrary easing function using a fixed normalized grid.
pub fn analyze_easing(func : (Double) -> Double, samples : Int) -> CurveReport {
  build_report(func, samples)
}

///|
pub fn analyze_easing_id(id : EasingId, samples : Int) -> CurveReport {
  build_report(easing(id), samples)
}

///|
pub fn analyze_bezier(curve : Bezier, samples : Int) -> CurveReport {
  build_report(curve.as_easing(), samples)
}

///|
/// Produce value, velocity, and acceleration samples for a normalized curve.
pub fn motion_profile(
  func : (Double) -> Double,
  samples : Int,
) -> Array[MotionSample] raise MotionError {
  if samples < 2 {
    raise MotionError::InvalidSampleCount(samples)
  }
  let result : Array[MotionSample] = []
  let step = 1.0 / (samples - 1).to_double()
  for i in 0.. Double, samples : Int) -> Double {
  if samples < 2 {
    0.0
  } else {
    let step = 1.0 / (samples - 1).to_double()
    let mut total = 0.0
    let mut previous = func(0.0)
    for i in 1..