///|
pub struct TrendAlert {
  metric : String
  direction : Int
  slope : Double
  confidence : Double
  start_index : Int
  end_index : Int
  message : String
} derive(Debug, ToJson)

///|
pub fn trend_slope(values : Array[Double]) -> Double {
  if values.length() < 2 {
    0.0
  } else {
    let mut total = 0.0
    for value in values {
      total += value
    }
    let mean = total / values.length().to_double()
    let mut numerator = 0.0
    let mut denominator = 0.0
    for i, value in values {
      let x = i.to_double()
      numerator += (x - (values.length() - 1).to_double() / 2.0) *
        (value - mean)
      denominator += (x - (values.length() - 1).to_double() / 2.0) *
        (x - (values.length() - 1).to_double() / 2.0)
    }
    if denominator <= 0.0 {
      0.0
    } else {
      numerator / denominator
    }
  }
}

///|
pub fn trend_direction_code(slope : Double, tolerance : Double) -> Int {
  if slope > tolerance {
    1
  } else if slope < -tolerance {
    -1
  } else {
    0
  }
}

///|
pub fn trend_confidence(values : Array[Double], slope : Double) -> Double {
  if values.length() < 2 {
    0.0
  } else {
    let mut residual = 0.0
    let mean = if values.length() == 0 {
      0.0
    } else {
      let mut total = 0.0
      for value in values {
        total += value
      }
      total / values.length().to_double()
    }
    for value in values {
      let d = value - mean
      residual += d * d
    }
    let scale = residual.sqrt()
    if scale <= 0.0 {
      1.0
    } else {
      (slope.abs() * values.length().to_double() / scale).min(1.0)
    }
  }
}

///|
pub fn detect_trend(
  metric~ : String,
  values~ : Array[Double],
  tolerance~ : Double,
) -> TrendAlert? {
  let slope = trend_slope(values)
  let direction = trend_direction_code(slope, tolerance)
  if direction == 0 {
    None
  } else {
    Some({
      metric,
      direction,
      slope,
      confidence: trend_confidence(values, slope),
      start_index: 0,
      end_index: values.length() - 1,
      message: if direction > 0 {
        "upward trend"
      } else {
        "downward trend"
      },
    })
  }
}

///|
pub fn trend_severity(alert : TrendAlert) -> String {
  if alert.confidence >= 0.8 {
    "high"
  } else if alert.confidence >= 0.5 {
    "medium"
  } else {
    "low"
  }
}

///|
pub fn trend_alert_count(alerts : Array[TrendAlert]) -> Int {
  alerts.length()
}

///|
pub fn high_severity_trends(alerts : Array[TrendAlert]) -> Int {
  let mut count = 0
  for alert in alerts {
    if trend_severity(alert) == "high" {
      count += 1
    }
  }
  count
}

///|
pub fn trend_direction_name(direction : Int) -> String {
  if direction > 0 {
    "up"
  } else if direction < 0 {
    "down"
  } else {
    "flat"
  }
}

///|
pub fn trend_total_change(values : Array[Double]) -> Double {
  if values.length() < 2 {
    0.0
  } else {
    values[values.length() - 1] - values[0]
  }
}

///|
pub fn trend_relative_change(values : Array[Double]) -> Double {
  if values.length() < 2 || values[0] == 0.0 {
    0.0
  } else {
    trend_total_change(values) / values[0]
  }
}

///|
pub fn trend_turning_points(values : Array[Double]) -> Int {
  if values.length() < 3 {
    0
  } else {
    let mut count = 0
    let mut i = 1
    while i < values.length() - 1 {
      let left = values[i] - values[i - 1]
      let right = values[i + 1] - values[i]
      if (left > 0.0 && right < 0.0) || (left < 0.0 && right > 0.0) {
        count += 1
      }
      i += 1
    }
    count
  }
}