///|
/// Aggregate diagnostics over a replay trace without changing detector state.
pub struct TraceSummary {
  points : Int
  event_count : Int
  mean_change_probability : Double
  maximum_change_probability : Double
  maximum_step : Int
  final_run_length : Int
}

///|
pub fn TraceSummary::points(self : TraceSummary) -> Int {
  self.points
}

///|
pub fn TraceSummary::event_count(self : TraceSummary) -> Int {
  self.event_count
}

///|
pub fn TraceSummary::maximum_change_probability(self : TraceSummary) -> Double {
  self.maximum_change_probability
}

///|
pub fn TraceSummary::maximum_step(self : TraceSummary) -> Int {
  self.maximum_step
}

///|
/// Summarize a trace under a caller-supplied probability threshold.
pub fn summarize(
  updates : Array[Update],
  threshold : Double,
) -> Result[TraceSummary, DetectError] {
  if !finite(threshold) || threshold <= 0.0 || threshold >= 1.0 {
    return Err(InvalidHazard)
  }
  if updates.length() == 0 {
    return Ok({
      points: 0,
      event_count: 0,
      mean_change_probability: 0.0,
      maximum_change_probability: 0.0,
      maximum_step: 0,
      final_run_length: 0,
    })
  }
  let mut total = 0.0
  let mut event_count = 0
  let mut maximum = updates[0].change_probability
  let mut maximum_step = updates[0].step
  for update in updates {
    total = total + update.change_probability
    if update.change_probability >= threshold {
      event_count = event_count + 1
    }
    if update.change_probability > maximum {
      maximum = update.change_probability
      maximum_step = update.step
    }
  }
  Ok({
    points: updates.length(),
    event_count,
    mean_change_probability: total / updates.length().to_double(),
    maximum_change_probability: maximum,
    maximum_step,
    final_run_length: updates[updates.length() - 1].most_likely_run_length,
  })
}

///|
/// Return the first update whose probability reaches a threshold.
pub fn first_crossing(updates : Array[Update], threshold : Double) -> Update? {
  for update in updates {
    if update.change_probability >= threshold {
      return Some(update)
    }
  }
  None
}