///|
pub fn corpus_profile(
  qrels : Array[JudgedDoc],
  run : Array[RetrievedDoc],
) -> CorpusProfile {
  let qrels_by_query = group_qrels(qrels)
  let runs_by_query = group_runs(run)
  let query_ids = sorted_query_ids(qrels_by_query, runs_by_query)
  let relevance = build_relevance_map(qrels)
  let mut relevant = 0
  let mut maximum = 0
  for _, value in relevance {
    maximum = Int::max(maximum, value)
    if value > 0 {
      relevant += 1
    }
  }
  let mut unjudged = 0
  for item in run {
    if !relevance.contains(item.doc_id) {
      unjudged += 1
    }
  }
  let unique_docs : Map[String, Unit] = Map([])
  for item in qrels {
    unique_docs[item.doc_id] = ()
  }
  for item in run {
    unique_docs[item.doc_id] = ()
  }
  let mut judgment_total = 0
  for _, items in qrels_by_query {
    judgment_total += items.length()
  }
  {
    query_count: query_ids.length(),
    qrels_rows: qrels.length(),
    run_rows: run.length(),
    unique_document_count: unique_docs.length(),
    relevant_document_count: relevant,
    max_relevance: maximum,
    unjudged_retrievals: unjudged,
    mean_judgments_per_query: if query_ids.is_empty() {
      0.0
    } else {
      Double::from_int(judgment_total) / Double::from_int(query_ids.length())
    },
    mean_run_length: if query_ids.is_empty() {
      0.0
    } else {
      Double::from_int(run.length()) / Double::from_int(query_ids.length())
    },
    mean_score: score_mean(run),
    score_stddev: score_stddev(run),
  }
}

///|
pub fn relevance_histogram(qrels : Array[JudgedDoc]) -> Map[Int, Int] {
  let histogram : Map[Int, Int] = Map([])
  for item in qrels {
    histogram[item.relevance] = histogram.get_or_default(item.relevance, 0) + 1
  }
  histogram
}

///|
pub fn render_corpus_profile(profile : CorpusProfile) -> String {
  let lines : Array[String] = [
    "Corpus profile",
    "queries=\{profile.query_count}",
    "qrels_rows=\{profile.qrels_rows}",
    "run_rows=\{profile.run_rows}",
    "unique_documents=\{profile.unique_document_count}",
    "relevant_documents=\{profile.relevant_document_count}",
    "max_relevance=\{profile.max_relevance}",
    "unjudged_retrievals=\{profile.unjudged_retrievals}",
    "mean_judgments_per_query=\{format_metric(profile.mean_judgments_per_query)}",
    "mean_run_length=\{format_metric(profile.mean_run_length)}",
    "mean_score=\{format_metric(profile.mean_score)}",
    "score_stddev=\{format_metric(profile.score_stddev)}",
  ]
  lines.join("\n")
}

///|
pub fn corpus_profile_json(profile : CorpusProfile) -> String {
  ToJson::to_json(profile).stringify(indent=2)
}

///|
pub fn document_relevance_levels(qrels : Array[JudgedDoc]) -> Array[Int] {
  let levels : Map[Int, Unit] = Map([])
  for item in qrels {
    levels[item.relevance] = ()
  }
  let result : Array[Int] = []
  for level, _ in levels {
    result.push(level)
  }
  result.sort()
  result
}