///|
/// Linear interpolation and clamped interpolation helpers.
pub fn lerp2(left : Double, right : Double, amount : Double) -> Double {
  left + (right - left) * amount
}

///|
pub fn lerp2_clamped(left : Double, right : Double, amount : Double) -> Double {
  lerp2(left, right, amount.max(0.0).min(1.0))
}

///|
pub fn inverse_lerp2(left : Double, right : Double, value : Double) -> Double {
  if (right - left).abs() <= 0.000000000001 {
    0.0
  } else {
    (value - left) / (right - left)
  }
}

///|
pub fn remap2(
  value : Double,
  source_min : Double,
  source_max : Double,
  target_min : Double,
  target_max : Double,
) -> Double {
  lerp2(target_min, target_max, inverse_lerp2(source_min, source_max, value))
}

///|
pub fn smoothstep2(amount : Double) -> Double {
  let t = amount.max(0.0).min(1.0)
  t * t * (3.0 - 2.0 * t)
}

///|
pub fn smootherstep2(amount : Double) -> Double {
  let t = amount.max(0.0).min(1.0)
  t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
}

///|
pub fn cosine_interpolation2(
  left : Double,
  right : Double,
  amount : Double,
) -> Double {
  let t = (1.0 - @math.cos(amount * @math.PI)) / 2.0
  lerp2(left, right, t)
}

///|
pub fn cubic_interpolation2(
  a : Double,
  b : Double,
  c : Double,
  d : Double,
  amount : Double,
) -> Double {
  let t2 = amount * amount
  let t3 = t2 * amount
  0.5 *
  (
    2.0 * b +
    (-a + c) * amount +
    (2.0 * a - 5.0 * b + 4.0 * c - d) * t2 +
    (-a + 3.0 * b - 3.0 * c + d) * t3
  )
}

///|
pub fn cubic_hermite2(
  p0 : Double,
  p1 : Double,
  tangent0 : Double,
  tangent1 : Double,
  amount : Double,
) -> Double {
  let t2 = amount * amount
  let t3 = t2 * amount
  (2.0 * t3 - 3.0 * t2 + 1.0) * p0 +
  (t3 - 2.0 * t2 + amount) * tangent0 +
  (-2.0 * t3 + 3.0 * t2) * p1 +
  (t3 - t2) * tangent1
}

///|
pub fn cubic_hermite_sample(
  p0 : Double,
  p1 : Double,
  tangent0 : Double,
  tangent1 : Double,
  count~ : Int,
) -> Array[Double] {
  let result : Array[Double] = []
  if count <= 0 {
    return result
  }
  for i in 0.. Array[Double] {
  let result : Array[Double] = []
  if points.length() == 0 {
    return result
  }
  if points.length() == 1 {
    result.push(points[0])
    return result
  }
  for i in 0..<(points.length() - 1) {
    let a = if i == 0 { points[i] } else { points[i - 1] }
    let b = points[i]
    let c = points[i + 1]
    let d = if i + 2 >= points.length() { c } else { points[i + 2] }
    for step in 0.. Double {
  lerp2(lerp2(a, b, x), lerp2(c, d, x), y)
}

///|
pub fn trilerp2(
  corners : ArrayView[Double],
  x : Double,
  y : Double,
  z : Double,
) -> Double {
  if corners.length() < 8 {
    0.0
  } else {
    let lower = bilerp2(corners[0], corners[1], corners[2], corners[3], x, y)
    let upper = bilerp2(corners[4], corners[5], corners[6], corners[7], x, y)
    lerp2(lower, upper, z)
  }
}

///|
pub fn interpolate_array(values : ArrayView[Double], amount : Double) -> Double {
  if values.length() == 0 {
    0.0
  } else if values.length() == 1 {
    values[0]
  } else {
    let position = amount.max(0.0).min(1.0) * (values.length() - 1).to_double()
    let lower = position.floor().to_int()
    let upper = position.ceil().to_int().min(values.length() - 1)
    lerp2(values[lower], values[upper], position - lower.to_double())
  }
}

///|
pub fn resample_array(values : ArrayView[Double], count : Int) -> Array[Double] {
  let result : Array[Double] = []
  for i in 0.. Array[Double] {
  let result : Array[Double] = []
  if values.length() < 2 {
    return result
  }
  for i in 0..<(values.length() - 1) {
    result.push(values[i + 1] - values[i])
  }
  result
}

///|
pub fn second_difference2(values : ArrayView[Double]) -> Array[Double] {
  finite_difference2(finite_difference2(values))
}

///|
pub fn interpolation_monotonic(values : ArrayView[Double]) -> Bool {
  for i in 1.. Double {
  if values.length() == 0 {
    0.0
  } else {
    let mut minimum = values[0]
    let mut maximum = values[0]
    for value in values {
      minimum = minimum.min(value)
      maximum = maximum.max(value)
    }
    maximum - minimum
  }
}