///|
pub fn segment_bounds(length : Int, segments : Int) -> Array[Array[Int]] {
  if segments <= 0 {
    abort("segments must be positive")
  }
  let result = []
  for segment = 0; segment < segments; segment = segment + 1 {
    result.push([segment * length / segments, (segment + 1) * length / segments])
  }
  result
}

///|
pub fn segment_values(
  data : Array[Double],
  segments : Int,
) -> Array[Array[Double]] {
  let result = []
  for bounds in segment_bounds(data.length(), segments) {
    let values = []
    for index = bounds[0]; index < bounds[1]; index = index + 1 {
      values.push(data[index])
    }
    result.push(values)
  }
  result
}

///|
pub fn segment_means(data : Array[Double], segments : Int) -> Array[Double] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(mean(values))
  }
  result
}

///|
pub fn segment_medians(data : Array[Double], segments : Int) -> Array[Double] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(median(values))
  }
  result
}

///|
pub fn segment_mads(data : Array[Double], segments : Int) -> Array[Double] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(mad(values))
  }
  result
}

///|
pub fn segment_trimmed_means(
  data : Array[Double],
  segments : Int,
  trim_percent : Double,
) -> Array[Double] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(trimmed_mean(values, trim_percent))
  }
  result
}

///|
pub fn cumulative_mean(data : Array[Double]) -> Array[Double] {
  let result = []
  let mut total = 0.0
  for index = 0; index < data.length(); index = index + 1 {
    total += data[index]
    result.push(total / (index + 1).to_double())
  }
  result
}

///|
pub fn cumulative_weighted_mean(
  data : Array[Double],
  weights : Array[Double],
) -> Array[Double] {
  if data.length() != weights.length() {
    return []
  }
  let result = []
  let mut numerator = 0.0
  let mut denominator = 0.0
  for index = 0; index < data.length(); index = index + 1 {
    if weights[index] < 0.0 {
      abort("weights must be non-negative")
    }
    numerator += data[index] * weights[index]
    denominator += weights[index]
    result.push(if denominator == 0.0 { 0.0 } else { numerator / denominator })
  }
  result
}

///|
pub fn cumulative_quantile(
  data : Array[Double],
  probability : Double,
) -> Array[Double] {
  let result = []
  let prefix = []
  for value in data {
    prefix.push(value)
    result.push(quantile(prefix, probability))
  }
  result
}

///|
pub fn cumulative_trimmed_mean(
  data : Array[Double],
  trim_percent : Double,
) -> Array[Double] {
  let result = []
  let prefix = []
  for value in data {
    prefix.push(value)
    result.push(trimmed_mean(prefix, trim_percent))
  }
  result
}

///|
pub fn cumulative_winsorized_mean(
  data : Array[Double],
  trim_percent : Double,
) -> Array[Double] {
  let result = []
  let prefix = []
  for value in data {
    prefix.push(value)
    result.push(winsorized_mean(prefix, trim_percent))
  }
  result
}

///|
pub fn running_minimum(data : Array[Double]) -> Array[Double] {
  let result = []
  if data.length() == 0 {
    return result
  }
  let mut current = data[0]
  for value in data {
    if value < current {
      current = value
    }
    result.push(current)
  }
  result
}

///|
pub fn running_maximum(data : Array[Double]) -> Array[Double] {
  let result = []
  if data.length() == 0 {
    return result
  }
  let mut current = data[0]
  for value in data {
    if value > current {
      current = value
    }
    result.push(current)
  }
  result
}

///|
pub fn running_range(data : Array[Double]) -> Array[Double] {
  let minimum = running_minimum(data)
  let maximum = running_maximum(data)
  let result = []
  for index = 0; index < minimum.length(); index = index + 1 {
    result.push(maximum[index] - minimum[index])
  }
  result
}

///|
pub fn aggregate_by_quantile(
  data : Array[Double],
  groups : Int,
) -> Array[Array[Double]] {
  if groups <= 0 {
    abort("groups must be positive")
  }
  let result = []
  for group = 0; group < groups; group = group + 1 {
    let lower = group.to_double() / groups.to_double()
    let upper = (group + 1).to_double() / groups.to_double()
    let values = []
    for value in data {
      let probability = empirical_cdf(data, value)
      if probability > lower && probability <= upper {
        values.push(value)
      }
    }
    result.push(values)
  }
  result
}

///|
pub fn quantile_group_means(
  data : Array[Double],
  groups : Int,
) -> Array[Double] {
  let result = []
  for values in aggregate_by_quantile(data, groups) {
    result.push(mean(values))
  }
  result
}

///|
pub fn quantile_group_mads(data : Array[Double], groups : Int) -> Array[Double] {
  let result = []
  for values in aggregate_by_quantile(data, groups) {
    result.push(mad(values))
  }
  result
}

///|
pub fn aggregate_error_by_segment(
  actual : Array[Double],
  predicted : Array[Double],
  segments : Int,
) -> Array[Double] {
  if actual.length() != predicted.length() {
    return []
  }
  let errors = []
  for index = 0; index < actual.length(); index = index + 1 {
    errors.push(actual[index] - predicted[index])
  }
  segment_mads(errors, segments)
}

///|
pub fn segment_outlier_counts(
  data : Array[Double],
  segments : Int,
  threshold? : Double = 3.5,
) -> Array[Int] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(outlier_indices_z(values, threshold~).length())
  }
  result
}

///|
pub fn segment_quality_scores(
  data : Array[Double],
  segments : Int,
) -> Array[Double] {
  let result = []
  for values in segment_values(data, segments) {
    result.push(robust_signal_quality(values))
  }
  result
}