///|
/// A one-step or multi-step forecast with uncertainty metadata.
pub(all) struct ForecastResult {
  predictions : Array[Double]
  slope : Double
  intercept : Double
  residual_sd : Double
  lower_95 : Array[Double]
  upper_95 : Array[Double]
} derive(FromJson, ToJson, Debug, Eq)

///|
/// Fit a linear forecast and return approximate 95% prediction bounds.
pub fn forecast_linear(values : Array[Double], horizon : Int) -> ForecastResult {
  let trend = fit_linear_trend(values)
  let predictions = []
  let lower = []
  let upper = []
  let residual = trend.residual_standard_error
  let horizon_count = if horizon < 0 { 0 } else { horizon }
  for step in 0.. ForecastResult {
  let values = []
  for measurement in history {
    values.push(measurement.rmssd)
  }
  forecast_linear(values, horizon)
}

///|
/// Calculate a rolling standardized deviation from a local baseline.
pub fn rolling_anomaly_scores(
  values : Array[Double],
  window_size : Int,
) -> Array[Double] {
  let result = []
  if window_size <= 0 {
    return result
  }
  for i in 0.. Double {
  let n = if actual.length() < forecast.predictions.length() {
    actual.length()
  } else {
    forecast.predictions.length()
  }
  if n == 0 {
    0.0
  } else {
    let mut covered = 0
    for i in 0..= forecast.lower_95[i] && actual[i] <= forecast.upper_95[i] {
        covered += 1
      }
    }
    covered.to_double() / n.to_double()
  }
}

///|
/// Calculate mean absolute forecast error.
pub fn forecast_mae(
  actual : Array[Double],
  predicted : Array[Double],
) -> Double {
  let n = if actual.length() < predicted.length() {
    actual.length()
  } else {
    predicted.length()
  }
  if n == 0 {
    0.0
  } else {
    let mut error = 0.0
    for i in 0.. Bool {
  score.abs() >= (if threshold < 0.0 { 0.0 } else { threshold })
}