///|
pub fn diagnose_run(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
) -> String {
  let validation = validate_dataset(qrels, run)
  let profile = profile_dataset(qrels, run)
  let quality = run_quality(qrels, run)
  let lines : Array[String] = [
    "validation_errors=\{validation.error_count}",
    "validation_warnings=\{validation.warning_count}",
    "run_query_coverage=\{format_metric(profile.run_query_coverage)}",
    "qrels_query_coverage=\{format_metric(profile.qrels_query_coverage)}",
    "duplicate_rate=\{format_metric(quality.duplicate_rate)}",
    "unjudged_rate=\{format_metric(quality.unjudged_rate)}",
    "score_monotonicity=\{format_metric(quality.score_monotonicity)}",
    "finite_score_rate=\{format_metric(quality.finite_score_rate)}",
  ]
  for issue in validation.issues {
    lines.push("\{issue.code}:\{issue.message}")
  }
  lines.join("\n")
}

///|
pub fn render_metric_matrix(
  reports : Array[(String, BenchmarkReport)],
) -> String {
  let metric_names : Map[String, Unit] = Map([])
  for pair in reports {
    let report = pair.1
    for metric in report.summary {
      metric_names[metric.name] = ()
    }
  }
  let names : Array[String] = []
  for name, _ in metric_names {
    names.push(name)
  }
  names.sort()
  let header : Array[String] = ["experiment"]
  for name in names {
    header.push(name)
  }
  let rows : Array[String] = [header.join(",")]
  for pair in reports {
    let label = pair.0
    let report = pair.1
    let row : Array[String] = [label]
    let values : Map[String, Double] = Map([])
    for metric in report.summary {
      values[metric.name] = metric.mean
    }
    for name in names {
      row.push(values.get_or_default(name, 0.0).to_string())
    }
    rows.push(row.join(","))
  }
  rows.join("\n")
}

///|
pub fn report_digest(report : BenchmarkReport) -> String {
  let text = render_compact_report(report)
  let mut checksum = 0
  for char in text {
    checksum = (checksum * 31 + char.to_int()) % 1000000007
  }
  "mrbench-\{checksum}"
}

///|
pub fn compare_metric_means(
  left : BenchmarkReport,
  right : BenchmarkReport,
  metric : String,
) -> Double {
  summarize_report_metric(right, metric).mean -
  summarize_report_metric(left, metric).mean
}

///|
pub fn report_has_finite_metrics(report : BenchmarkReport) -> Bool {
  for metric in report.summary {
    if metric.mean.is_nan() || metric.mean.is_inf() {
      return false
    }
  }
  true
}

///|
pub fn report_metric_names(report : BenchmarkReport) -> Array[String] {
  let names : Array[String] = []
  for metric in report.summary {
    if !names.contains(metric.name) {
      names.push(metric.name)
    }
  }
  names.sort()
  names
}