///|
/// Aggregate facts about a batch of bitmap operands.
pub(all) struct BitmapBatchReport {
  operand_count : Int
  total_cardinality : Int
  union_cardinality : Int
  intersection_cardinality : Int
  duplicate_occurrences : Int
  empty_operands : Int
}

///|
/// Analyze a batch using exact set algebra. Empty input has empty union and
/// intersection by the library's documented aggregate identity.
pub fn analyze_batch(bitmaps : Array[RoaringBitmap]) -> BitmapBatchReport {
  let mut total = 0
  let mut empty = 0
  for bitmap in bitmaps {
    total += bitmap.cardinality()
    if bitmap.is_empty() {
      empty += 1
    }
  }
  let union = union_all(bitmaps)
  let intersection = intersection_all(bitmaps)
  let union_cardinality = union.cardinality()
  {
    operand_count: bitmaps.length(),
    total_cardinality: total,
    union_cardinality,
    intersection_cardinality: intersection.cardinality(),
    duplicate_occurrences: total - union_cardinality,
    empty_operands: empty,
  }
}

///|
/// Return the values that appear in at least `threshold` operands.
/// Threshold zero returns the union because no external universe is supplied.
pub fn values_at_least(
  bitmaps : Array[RoaringBitmap],
  threshold : Int,
) -> RoaringBitmap {
  if threshold <= 0 {
    return union_all(bitmaps)
  }
  if threshold > bitmaps.length() {
    return RoaringBitmap::new()
  }
  let values : Array[Int] = []
  for bitmap in bitmaps {
    for value in bitmap.to_array() {
      values.push(value)
    }
  }
  values.sort()
  let output : Array[Int] = []
  let mut index = 0
  while index < values.length() {
    let value = values[index]
    let mut count = 0
    while index < values.length() && values[index] == value {
      count += 1
      index += 1
    }
    if count >= threshold {
      output.push(value)
    }
  }
  from_sorted(output)
}

///|
/// Return values present in exactly one operand.
pub fn values_in_exactly_one(bitmaps : Array[RoaringBitmap]) -> RoaringBitmap {
  let values : Array[Int] = []
  for bitmap in bitmaps {
    for value in bitmap.to_array() {
      values.push(value)
    }
  }
  values.sort()
  let output : Array[Int] = []
  let mut index = 0
  while index < values.length() {
    let value = values[index]
    let mut count = 0
    while index < values.length() && values[index] == value {
      count += 1
      index += 1
    }
    if count == 1 {
      output.push(value)
    }
  }
  from_sorted(output)
}

///|
/// Return each operand's overlap count with the union of all other operands.
pub fn overlap_with_others(bitmaps : Array[RoaringBitmap]) -> Array[Int] {
  let output : Array[Int] = []
  for index in 0..