///|
pub fn interpolate_array(
  xs : Array[Double],
  ys : Array[Double],
  query : Double,
) -> Double {
  if xs.length() == 0 || xs.length() != ys.length() {
    0.0
  } else if query <= xs[0] {
    ys[0]
  } else if query >= xs[xs.length() - 1] {
    ys[ys.length() - 1]
  } else {
    let mut value = ys[0]
    for i in 0..<(xs.length() - 1) {
      if query >= xs[i] && query <= xs[i + 1] {
        value = lerp_scalar(
          ys[i],
          ys[i + 1],
          inverse_lerp(xs[i], xs[i + 1], query),
        )
        break
      }
    }
    value
  }
}

///|
pub fn interpolate_angle(
  xs : Array[Double],
  ys : Array[Double],
  query : Double,
) -> Double {
  normalize_angle(interpolate_array(xs, ys.map(normalize_angle), query))
}

///|
pub fn nearest_index(values : Array[Double], query : Double) -> Int {
  if values.length() == 0 {
    -1
  } else {
    let mut index = 0
    let mut distance = (values[0] - query).abs()
    for i in 1.. Double {
  let index = nearest_index(values, query)
  if index < 0 {
    0.0
  } else {
    values[index]
  }
}

///|
pub fn resample_uniform(
  values : Array[Double],
  start : Double,
  step : Double,
  count : Int,
) -> Array[Double] {
  let result : Array[Double] = []
  let xs : Array[Double] = []
  for i in 0.. interpolate_array(xs, values, query))
}

///|
pub fn finite_difference(
  values : Array[Double],
  spacing : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  if spacing == 0.0 {
    return result
  }
  for i in 0.. Array[Double] {
  let result : Array[Double] = []
  if values.length() < 3 || spacing == 0.0 {
    return result
  }
  for i in 1..<(values.length() - 1) {
    result.push(
      (values[i + 1] - 2.0 * values[i] + values[i - 1]) / (spacing * spacing),
    )
  }
  result
}

///|
pub fn integrate_uniform(values : Array[Double], spacing : Double) -> Double {
  if values.length() < 2 {
    0.0
  } else {
    let mut total = 0.0
    for i in 0..<(values.length() - 1) {
      total += (values[i] + values[i + 1]) * spacing / 2.0
    }
    total
  }
}

///|
pub fn integrate_simpson(values : Array[Double], spacing : Double) -> Double {
  if values.length() < 3 || values.length() % 2 == 0 {
    integrate_uniform(values, spacing)
  } else {
    let mut total = values[0] + values[values.length() - 1]
    for i in 1..<(values.length() - 1) {
      total += if i % 2 == 0 { 2.0 * values[i] } else { 4.0 * values[i] }
    }
    total * spacing / 3.0
  }
}

///|
pub fn polynomial_fit_linear(
  xs : Array[Double],
  ys : Array[Double],
) -> (Double, Double) {
  let model = linear_regression(xs, ys)
  (model.slope, model.intercept)
}

///|
pub fn predict_linear(model : (Double, Double), x : Double) -> Double {
  model.0 * x + model.1
}

///|
pub fn residuals_linear(
  model : (Double, Double),
  xs : Array[Double],
  ys : Array[Double],
) -> Array[Double] {
  let result : Array[Double] = []
  for i in 0.. Array[Double] {
  moving_average(values, radius * 2 + 1)
}

///|
pub fn smooth_exponential(
  values : Array[Double],
  alpha : Double,
) -> Array[Double] {
  let result : Array[Double] = []
  if values.length() == 0 {
    return result
  }
  let weight = clamp(alpha, 0.0, 1.0)
  let mut previous = values[0]
  for value in values {
    previous = weight * value + (1.0 - weight) * previous
    result.push(previous)
  }
  result
}