///|
/// Maximal spans satisfying every typed fact condition at one knowledge cutoff.
/// Repeated keys are ANDed; an empty condition list is the whole bounds.
pub fn Ledger::matching_conditions(
  self : Ledger,
  conditions : Array[(String, SpanFilter)],
  bounds : Span,
  known_at : Int64,
  max_conditions~ : Int32 = 64,
  max_segments~ : Int32 = 4096,
  max_input_segments~ : Int32 = 16384,
) -> Array[Span] raise TemporalError {
  if max_conditions < 0 ||
    max_conditions > 1024 ||
    conditions.length() > max_conditions {
    raise TooManyKeys(maximum=max_conditions)
  }
  if max_segments <= 0 {
    raise TooManySegments(maximum=max_segments)
  }
  let grouped : Map[String, Array[SpanFilter]] = Map([])
  let keys : Array[String] = []
  for (key, filter) in conditions {
    if key.is_empty() {
      raise EmptyKey
    }
    match grouped.get(key) {
      Some(filters) => filters.push(filter)
      None => {
        grouped[key] = [filter]
        keys.push(key)
      }
    }
  }
  let rows = self.aligned_timeline(
    keys,
    bounds,
    known_at,
    max_keys=max_conditions,
    max_segments~,
    max_cells=1048576,
    max_input_segments~,
  )
  let result : Array[Span] = []
  for row in rows {
    let mut matches = true
    for fact in row.facts {
      for filter in grouped[fact.key] {
        let segment : Segment = { valid: row.valid, source: fact.source, }
        if !filter.accepts(segment) {
          matches = false
          break
        }
      }
      if !matches {
        break
      }
    }
    if matches {
      if !result.is_empty() && result[result.length() - 1].touches(row.valid) {
        let i = result.length() - 1
        result[i] = { start: result[i].start, end: row.valid.end, }
      } else {
        result.push(row.valid)
      }
    }
  }
  result
}