///|
/// Scalar interpolation primitive.
pub fn interpolate_scalar(start : Double, end : Double, t : Double) -> Double {
  start + (end - start) * t
}

///|
pub fn interpolate_point2(start : Point2, end : Point2, t : Double) -> Point2 {
  start.lerp(end, t)
}

///|
pub fn interpolate_point3(start : Point3, end : Point3, t : Double) -> Point3 {
  start.lerp(end, t)
}

///|
/// Interpolate straight in RGBA space, optionally preserving premultiplied alpha.
pub fn interpolate_rgba(
  start : Rgba,
  end : Rgba,
  t : Double,
  premultiplied? : Bool = false,
) -> Rgba {
  let alpha = interpolate_scalar(start.a, end.a, t)
  if premultiplied && alpha > 0.0 {
    let r = interpolate_scalar(start.r * start.a, end.r * end.a, t) / alpha
    let g = interpolate_scalar(start.g * start.a, end.g * end.a, t) / alpha
    let b = interpolate_scalar(start.b * start.a, end.b * end.a, t) / alpha
    rgba(r~, g~, b~, a=alpha)
  } else {
    rgba(
      r=interpolate_scalar(start.r, end.r, t),
      g=interpolate_scalar(start.g, end.g, t),
      b=interpolate_scalar(start.b, end.b, t),
      a=alpha,
    )
  }
}

///|
/// Interpolate an angle in radians along the shortest path.
pub fn interpolate_angle(start : Double, end : Double, t : Double) -> Double {
  let turn = 2.0 * @math.PI
  let mut delta = (end - start) % turn
  if delta > @math.PI {
    delta = delta - turn
  } else if delta < -@math.PI {
    delta = delta + turn
  }
  start + delta * t
}

///|
pub fn interpolate_transform(
  start : Transform2D,
  end : Transform2D,
  t : Double,
  shortest_rotation? : Bool = true,
) -> Transform2D {
  let rotation = if shortest_rotation {
    interpolate_angle(start.rotation, end.rotation, t)
  } else {
    interpolate_scalar(start.rotation, end.rotation, t)
  }
  {
    position: start.position.lerp(end.position, t),
    scale: start.scale.lerp(end.scale, t),
    rotation,
    skew: interpolate_scalar(start.skew, end.skew, t),
  }
}

///|
/// Cubic Hermite interpolation. Tangents use the same units as values.
pub fn hermite(
  start : Double,
  end : Double,
  tangent_start : Double,
  tangent_end : Double,
  t : Double,
) -> Double {
  let t2 = t * t
  let t3 = t2 * t
  let h00 = 2.0 * t3 - 3.0 * t2 + 1.0
  let h10 = t3 - 2.0 * t2 + t
  let h01 = -2.0 * t3 + 3.0 * t2
  let h11 = t3 - t2
  h00 * start + h10 * tangent_start + h01 * end + h11 * tangent_end
}

///|
/// Catmull-Rom interpolation across four scalar samples.
pub fn catmull_rom(
  previous : Double,
  start : Double,
  end : Double,
  next : Double,
  t : Double,
  tension? : Double = 0.0,
) -> Double {
  let tangent_scale = (1.0 - tension) / 2.0
  let tangent_start = (end - previous) * tangent_scale
  let tangent_end = (next - start) * tangent_scale
  hermite(start, end, tangent_start, tangent_end, t)
}

///|
/// Cubic interpolation over a value array using clamped endpoint samples.
pub fn catmull_rom_array(values : Array[Double], position : Double) -> Double {
  if values.length() == 0 {
    0.0
  } else if values.length() == 1 {
    values[0]
  } else {
    let max_index = values.length() - 1
    let x = clamp(position, 0.0, max_index.to_double())
    let index = x.to_int()
    let t = x - index.to_double()
    let previous = values[(index - 1).max(0)]
    let start = values[index]
    let end = values[(index + 1).min(max_index)]
    let next = values[(index + 2).min(max_index)]
    catmull_rom(previous, start, end, next, t)
  }
}