///|
pub fn sliding_windows(
  values : ArrayView[Double],
  width : Int,
  stride : Int,
) -> Array[Array[Double]] {
  let output : Array[Array[Double]] = []
  if width > 0 && stride > 0 {
    let mut start = 0
    while start + width <= values.length() {
      let window = Array::makei(width, fn(i) { values[start + i] })
      output.push(window)
      start = start + stride
    }
  }
  output
}

///|
pub fn window_means(
  values : ArrayView[Double],
  width : Int,
  stride : Int,
) -> Array[Double] {
  sliding_windows(values, width, stride).map(fn(window) { mean(window) })
}

///|
pub fn moving_average(values : ArrayView[Double], width : Int) -> Array[Double] {
  let output = zeros(values.length())
  if width > 0 {
    for i in 0.. Array[Double] {
  let output = zeros(values.length())
  if values.length() > 0 {
    let mut current = values[0]
    for i in 0.. Array[Double] {
  let output = zeros(values.length())
  if values.length() > 0 {
    let mut current = values[0]
    for i in 0.. Array[Double] {
  let minimum = min_value(values, 0.0)
  let span = max_value(values, 0.0) - minimum
  if span == 0.0 {
    values.map(fn(_) { 0.0 })
  } else {
    values.map(fn(value) { (value - minimum) / span })
  }
}

///|
pub fn covariance(
  first : ArrayView[Double],
  second : ArrayView[Double],
) -> Double {
  let count = if first.length() < second.length() {
    first.length()
  } else {
    second.length()
  }
  if count == 0 {
    0.0
  } else {
    let first_mean = mean(first)
    let second_mean = mean(second)
    let mut total = 0.0
    for i in 0.. Double {
  let denominator = sample_variance(first).sqrt() *
    sample_variance(second).sqrt()
  safe_ratio(covariance(first, second), denominator, 0.0)
}

///|
pub fn downsample_mean(
  values : ArrayView[Double],
  factor : Int,
) -> Array[Double] {
  if factor <= 0 {
    []
  } else {
    let count = values.length() / factor
    Array::makei(count, fn(i) {
      mean(Array::makei(factor, fn(j) { values[i * factor + j] }))
    })
  }
}

///|
pub fn interpolate_series(
  values : ArrayView[Double],
  count : Int,
) -> Array[Double] {
  if count <= 0 || values.length() == 0 {
    []
  } else if values.length() == 1 {
    Array::make(count, values[0])
  } else {
    Array::makei(count, fn(i) {
      let position = i.to_double() *
        (values.length() - 1).to_double() /
        (count - 1).max(1).to_double()
      let left = position.to_int()
      let fraction = position - left.to_double()
      lerp(values[left], values[(left + 1).min(values.length() - 1)], fraction)
    })
  }
}

///|
pub fn analysis_summary(values : ArrayView[Double]) -> String {
  let summary = summarize_series("values", values)
  summary_to_csv(summary)
}