///|
pub fn build_matrix_timeline(
  matrices : Array[ScenarioMatrix],
) -> MatrixTimeline {
  let snapshots : Array[MatrixSnapshot] = []
  let mut improved = 0
  let mut regressed = 0
  let mut stable = 0
  let mut index = 0
  while index < matrices.length() {
    let matrix = matrices[index]
    snapshots.push(MatrixSnapshot::{
      label: matrix.name + "@" + matrix.version,
      digest: digest_for_matrix(matrix),
      required_count: matrix.required_count,
      passed_count: matrix.passed_count,
      failed_count: matrix.failed_count,
      skipped_count: matrix.skipped_count,
      ready: matrix.passes(),
    })
    if index > 0 {
      let before = snapshots[index - 1]
      let current = snapshots[index]
      let before_score = snapshot_score(before)
      let current_score = snapshot_score(current)
      if current_score > before_score {
        improved += 1
      } else if current_score < before_score {
        regressed += 1
      } else {
        stable += 1
      }
    }
    index += 1
  }
  MatrixTimeline::{ snapshots, improved, regressed, stable }
}

///|
pub fn MatrixTimeline::passes(self : MatrixTimeline) -> Bool {
  self.regressed == 0 && self.snapshots_all_ready()
}

///|
pub fn MatrixTimeline::snapshots_all_ready(self : MatrixTimeline) -> Bool {
  let mut index = 0
  while index < self.snapshots.length() {
    if !self.snapshots[index].ready {
      return false
    }
    index += 1
  }
  true
}

///|
pub fn MatrixTimeline::summary(self : MatrixTimeline) -> String {
  "snapshots=" +
  self.snapshots.length().to_string() +
  ", improved=" +
  self.improved.to_string() +
  ", regressed=" +
  self.regressed.to_string() +
  ", stable=" +
  self.stable.to_string() +
  ", passes=" +
  bool_text(self.passes())
}

///|
pub fn MatrixTimeline::to_markdown(self : MatrixTimeline) -> String {
  let mut out = "# Scenario Matrix Timeline\n\n"
  out = out + "- Passes: " + bool_text(self.passes()) + "\n"
  out = out + "- Improved: " + self.improved.to_string() + "\n"
  out = out + "- Regressed: " + self.regressed.to_string() + "\n"
  out = out + "- Stable: " + self.stable.to_string() + "\n\n"
  out = out +
    "| Snapshot | Required | Passed | Failed | Skipped | Ready | Digest |\n"
  out = out + "| --- | ---: | ---: | ---: | ---: | --- | --- |\n"
  let mut index = 0
  while index < self.snapshots.length() {
    let snapshot = self.snapshots[index]
    out = out + "| `" + snapshot.label + "` | "
    out = out + snapshot.required_count.to_string() + " | "
    out = out + snapshot.passed_count.to_string() + " | "
    out = out + snapshot.failed_count.to_string() + " | "
    out = out + snapshot.skipped_count.to_string() + " | "
    out = out + bool_text(snapshot.ready) + " | `" + snapshot.digest + "` |\n"
    index += 1
  }
  out
}

///|
pub fn MatrixTimeline::to_json(self : MatrixTimeline) -> String {
  let mut out = "{\"passes\":" + bool_text(self.passes()) + ","
  out = out + "\"improved\":" + self.improved.to_string() + ","
  out = out + "\"regressed\":" + self.regressed.to_string() + ","
  out = out + "\"stable\":" + self.stable.to_string() + ",\"snapshots\":["
  let mut index = 0
  while index < self.snapshots.length() {
    if index > 0 {
      out = out + ","
    }
    let snapshot = self.snapshots[index]
    out = out + "{\"label\":\"" + json_escape(snapshot.label) + "\","
    out = out + "\"required\":" + snapshot.required_count.to_string() + ","
    out = out + "\"passed\":" + snapshot.passed_count.to_string() + ","
    out = out + "\"failed\":" + snapshot.failed_count.to_string() + ","
    out = out + "\"skipped\":" + snapshot.skipped_count.to_string() + ","
    out = out + "\"ready\":" + bool_text(snapshot.ready) + ","
    out = out + "\"digest\":\"" + snapshot.digest + "\"}"
    index += 1
  }
  out + "]}"
}

///|
pub fn timeline_ready_count(timeline : MatrixTimeline) -> Int {
  let mut count = 0
  let mut index = 0
  while index < timeline.snapshots.length() {
    if timeline.snapshots[index].ready {
      count += 1
    }
    index += 1
  }
  count
}

///|
pub fn timeline_failed_case_count(timeline : MatrixTimeline) -> Int {
  let mut count = 0
  let mut index = 0
  while index < timeline.snapshots.length() {
    count += timeline.snapshots[index].failed_count
    index += 1
  }
  count
}

///|
fn snapshot_score(snapshot : MatrixSnapshot) -> Int {
  snapshot.passed_count * 3 -
  snapshot.failed_count * 5 -
  snapshot.skipped_count * 2
}