///|
/// Compact coverage summary for dashboards and reproducibility reports.
pub(all) struct DatasetSummary {
  sample_count : Int
  annotation_count : Int
  annotator_count : Int
  split_counts : Map[String, Int]
  labeled_sample_count : Int
  unlabeled_sample_count : Int
} derive(Eq, Debug)

///|
/// Calculate counts without changing the source dataset.
pub fn summarize_dataset(dataset : Dataset) -> DatasetSummary {
  let splits : Map[String, Int] = Map([])
  let mut annotation_count = 0
  let mut labeled = 0
  for sample in dataset.samples {
    let old = match splits.get(sample.split) {
      Some(value) => value
      None => 0
    }
    splits[sample.split] = old + 1
    annotation_count += sample.annotations.length()
    if sample.annotations.length() > 0 {
      labeled += 1
    }
  }
  {
    sample_count: dataset.samples.length(),
    annotation_count,
    annotator_count: dataset.get_all_annotators().length(),
    split_counts: splits,
    labeled_sample_count: labeled,
    unlabeled_sample_count: dataset.samples.length() - labeled,
  }
}

///|
/// Fraction of samples that have at least one annotation.
pub fn annotation_coverage(summary : DatasetSummary) -> Double {
  if summary.sample_count == 0 {
    0.0
  } else {
    summary.labeled_sample_count.to_double() / summary.sample_count.to_double()
  }
}

///|
/// Whether every requested split exists and contains data.
pub fn has_required_splits(
  summary : DatasetSummary,
  required : Array[String],
) -> Bool {
  for split in required {
    match summary.split_counts.get(split) {
      Some(value) => if value == 0 { return false }
      None => return false
    }
  }
  true
}

///|
/// Number of samples in a named split, returning zero for an absent split.
pub fn split_count(summary : DatasetSummary, split : String) -> Int {
  match summary.split_counts.get(split) {
    Some(value) => value
    None => 0
  }
}

///|
/// Average number of annotations per sample, including zero-annotation rows.
pub fn annotation_density(summary : DatasetSummary) -> Double {
  if summary.sample_count == 0 {
    0.0
  } else {
    summary.annotation_count.to_double() / summary.sample_count.to_double()
  }
}

///|
/// Empty datasets cannot be released as training data.
pub fn is_empty(summary : DatasetSummary) -> Bool {
  summary.sample_count == 0
}

///|
/// Whether all samples have at least one annotation.
pub fn is_fully_annotated(summary : DatasetSummary) -> Bool {
  summary.sample_count > 0 && summary.unlabeled_sample_count == 0
}

///|
/// Ratio of distinct text values to samples; lower values indicate repetition.
pub fn text_uniqueness_ratio(
  summary : DatasetSummary,
  unique_texts : Int,
) -> Double {
  if summary.sample_count == 0 {
    0.0
  } else {
    unique_texts.to_double() / summary.sample_count.to_double()
  }
}

///|
/// A compact release predicate combining coverage and non-empty input checks.
pub fn passes_basic_coverage(summary : DatasetSummary) -> Bool {
  !is_empty(summary) &&
  is_fully_annotated(summary) &&
  annotation_coverage(summary) == 1.0
}

///|
/// Whether a split has at least one sample.
pub fn split_exists(summary : DatasetSummary, split : String) -> Bool {
  split_count(summary, split) > 0
}