///|
/// Derived benchmark statistics used in human-readable performance reports.
pub struct BenchmarkSummary {
  scenarios : Int
  total_operations : Int
  total_tokens : Int
  total_input_bytes : Int
  total_output_bytes : Int
  total_elapsed_units : UInt64
  fastest_scenario : String
  largest_scenario : String
} derive(Debug, Eq)

///|
pub fn BenchmarkReport::summary(self : BenchmarkReport) -> BenchmarkSummary {
  let mut operations = 0
  let mut tokens = 0
  let mut input = 0
  let mut output = 0
  let mut elapsed : UInt64 = 0
  let mut fastest = ""
  let mut fastest_units : UInt64 = 18446744073709551615
  let mut largest = ""
  let mut largest_bytes = 0
  for measurement in self.measurements {
    operations += measurement.operations
    tokens += measurement.token_count
    input += measurement.input_bytes
    output += measurement.output_bytes
    elapsed += measurement.elapsed_units
    if measurement.elapsed_units < fastest_units {
      fastest_units = measurement.elapsed_units
      fastest = measurement.name
    }
    if measurement.input_bytes > largest_bytes {
      largest_bytes = measurement.input_bytes
      largest = measurement.name
    }
  }
  {
    scenarios: self.measurements.length(),
    total_operations: operations,
    total_tokens: tokens,
    total_input_bytes: input,
    total_output_bytes: output,
    total_elapsed_units: elapsed,
    fastest_scenario: fastest,
    largest_scenario: largest,
  }
}

///|
pub fn BenchmarkSummary::average_tokens(self : BenchmarkSummary) -> Int {
  if self.scenarios == 0 {
    0
  } else {
    self.total_tokens / self.scenarios
  }
}

///|
pub fn BenchmarkSummary::average_input_bytes(self : BenchmarkSummary) -> Int {
  if self.scenarios == 0 {
    0
  } else {
    self.total_input_bytes / self.scenarios
  }
}

///|
pub fn BenchmarkSummary::average_output_bytes(self : BenchmarkSummary) -> Int {
  if self.scenarios == 0 {
    0
  } else {
    self.total_output_bytes / self.scenarios
  }
}

///|
pub fn BenchmarkSummary::output_expansion_percent(
  self : BenchmarkSummary,
) -> Int {
  if self.total_input_bytes == 0 {
    0
  } else {
    self.total_output_bytes * 100 / self.total_input_bytes
  }
}

///|
pub fn BenchmarkSummary::is_measured(self : BenchmarkSummary) -> Bool {
  self.scenarios > 0 && self.total_elapsed_units > 0
}

///|
pub fn BenchmarkSummary::to_markdown(self : BenchmarkSummary) -> String {
  "| Aggregate metric | Value |\n| --- | ---: |\n" +
  "| Scenarios | " +
  self.scenarios.to_string() +
  " |\n" +
  "| Operations | " +
  self.total_operations.to_string() +
  " |\n" +
  "| Tokens | " +
  self.total_tokens.to_string() +
  " |\n" +
  "| Input bytes | " +
  self.total_input_bytes.to_string() +
  " |\n" +
  "| Output bytes | " +
  self.total_output_bytes.to_string() +
  " |\n" +
  "| Elapsed units | " +
  self.total_elapsed_units.to_string() +
  " |\n" +
  "| Fastest scenario | " +
  self.fastest_scenario +
  " |\n" +
  "| Largest input | " +
  self.largest_scenario +
  " |"
}

///|
pub fn BenchmarkSummary::to_json(self : BenchmarkSummary) -> String {
  "{\"scenarios\":" +
  self.scenarios.to_string() +
  ",\"operations\":" +
  self.total_operations.to_string() +
  ",\"tokens\":" +
  self.total_tokens.to_string() +
  ",\"inputBytes\":" +
  self.total_input_bytes.to_string() +
  ",\"outputBytes\":" +
  self.total_output_bytes.to_string() +
  ",\"elapsedUnits\":" +
  self.total_elapsed_units.to_string() +
  ",\"fastest\":\"" +
  json_escape(self.fastest_scenario) +
  "\"}"
}

///|
pub fn BenchmarkReport::summary_markdown(self : BenchmarkReport) -> String {
  self.summary().to_markdown()
}

///|
pub fn BenchmarkReport::summary_json(self : BenchmarkReport) -> String {
  self.summary().to_json()
}

///|
pub fn BenchmarkReport::is_comparable(self : BenchmarkReport) -> Bool {
  self.measurements.length() >= 2 && self.summary().is_measured()
}

///|
pub fn BenchmarkReport::scenario_names(self : BenchmarkReport) -> Array[String] {
  self.measurements.map(fn(measurement) { measurement.name })
}

///|
pub fn BenchmarkReport::measurement_named(
  self : BenchmarkReport,
  name : String,
) -> BenchmarkMeasurement? {
  for measurement in self.measurements {
    if measurement.name == name {
      return Some(measurement)
    }
  }
  None
}

///|
pub fn BenchmarkReport::slowest(
  self : BenchmarkReport,
) -> BenchmarkMeasurement? {
  if self.measurements.length() == 0 {
    None
  } else {
    let mut result = self.measurements[0]
    for measurement in self.measurements {
      if measurement.elapsed_units > result.elapsed_units {
        result = measurement
      }
    }
    Some(result)
  }
}

///|
pub fn BenchmarkReport::largest_input(
  self : BenchmarkReport,
) -> BenchmarkMeasurement? {
  if self.measurements.length() == 0 {
    None
  } else {
    let mut result = self.measurements[0]
    for measurement in self.measurements {
      if measurement.input_bytes > result.input_bytes {
        result = measurement
      }
    }
    Some(result)
  }
}