///|
/// Online moments and extrema for long-running RR ingestion.
pub(all) struct OnlineStats {
  mut count : Int
  mut mean : Double
  mut m2 : Double
  mut minimum : Double
  mut maximum : Double
  mut last : Double
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Create an empty streaming accumulator.
pub fn OnlineStats::new() -> OnlineStats {
  { count: 0, mean: 0.0, m2: 0.0, minimum: 0.0, maximum: 0.0, last: 0.0 }
}

///|
/// Add one observation using Welford's stable update.
pub fn OnlineStats::push(self : OnlineStats, value : Double) -> Unit {
  if self.count == 0 {
    self.count = 1
    self.mean = value
    self.minimum = value
    self.maximum = value
    self.last = value
  } else {
    self.count += 1
    let delta = value - self.mean
    self.mean += delta / self.count.to_double()
    let delta2 = value - self.mean
    self.m2 += delta * delta2
    if value < self.minimum {
      self.minimum = value
    }
    if value > self.maximum {
      self.maximum = value
    }
    self.last = value
  }
}

///|
/// Add all observations in order.
pub fn OnlineStats::push_all(
  self : OnlineStats,
  values : Array[Double],
) -> Unit {
  for value in values {
    self.push(value)
  }
}

///|
/// Return sample variance from an online accumulator.
pub fn OnlineStats::variance(self : OnlineStats) -> Double {
  if self.count <= 1 {
    0.0
  } else {
    self.m2 / (self.count - 1).to_double()
  }
}

///|
/// Return sample standard deviation from an online accumulator.
pub fn OnlineStats::standard_deviation(self : OnlineStats) -> Double {
  self.variance().sqrt()
}

///|
/// Merge another accumulator using Chan's parallel moments formula.
pub fn OnlineStats::merge(self : OnlineStats, other : OnlineStats) -> Unit {
  if other.count == 0 {
    return
  }
  if self.count == 0 {
    self.count = other.count
    self.mean = other.mean
    self.m2 = other.m2
    self.minimum = other.minimum
    self.maximum = other.maximum
    self.last = other.last
    return
  }
  let total = self.count + other.count
  let delta = other.mean - self.mean
  self.m2 += other.m2 +
    delta *
    delta *
    self.count.to_double() *
    other.count.to_double() /
    total.to_double()
  self.mean += delta * other.count.to_double() / total.to_double()
  self.count = total
  if other.minimum < self.minimum {
    self.minimum = other.minimum
  }
  if other.maximum > self.maximum {
    self.maximum = other.maximum
  }
  self.last = other.last
}

///|
/// Return an immutable snapshot of the current accumulator.
pub fn OnlineStats::snapshot(self : OnlineStats) -> DistributionStats {
  {
    count: self.count,
    sum: self.mean * self.count.to_double(),
    mean: self.mean,
    median: self.mean,
    variance: self.variance(),
    standard_deviation: self.standard_deviation(),
    minimum: self.minimum,
    maximum: self.maximum,
    q1: self.minimum,
    q3: self.maximum,
    interquartile_range: self.maximum - self.minimum,
    median_absolute_deviation: 0.0,
  }
}

///|
/// Calculate an exponentially weighted moving average.
pub fn exponential_moving_average(
  values : Array[Double],
  alpha : Double,
) -> Array[Double] {
  let result = []
  if values.length() == 0 {
    return result
  }
  let weight = alpha.clamp(min=0.0, max=1.0)
  let mut current = values[0]
  result.push(current)
  for i in 1.. Array[Double] {
  let result = []
  if values.length() == 0 {
    return result
  }
  let weight = alpha.clamp(min=0.0, max=1.0)
  let mut baseline = values[0]
  let mut variance = 0.0
  result.push(0.0)
  for i in 1.. Double {
  let mut last = 0.0
  for value in values {
    if !value.is_nan() && !value.is_inf() {
      last = value
    }
  }
  last
}