///|
/// Duration for one distinct assigned value inside a finite query window.
pub struct ValueDuration {
  value : String
  duration : Int64
}

///|
pub fn ValueDuration::value(self : ValueDuration) -> String {
  self.value
}

///|
pub fn ValueDuration::duration(self : ValueDuration) -> Int64 {
  self.duration
}

///|
/// Duration totals distinguish a never-claimed gap from an explicit deletion.
pub struct Coverage {
  total : Int64
  assigned : Int64
  withdrawn : Int64
  unclaimed : Int64
  values : Array[ValueDuration]
}

///|
pub fn Coverage::total(self : Coverage) -> Int64 {
  self.total
}

///|
pub fn Coverage::assigned(self : Coverage) -> Int64 {
  self.assigned
}

///|
pub fn Coverage::withdrawn(self : Coverage) -> Int64 {
  self.withdrawn
}

///|
pub fn Coverage::unclaimed(self : Coverage) -> Int64 {
  self.unclaimed
}

///|
pub fn Coverage::values(self : Coverage) -> Array[ValueDuration] {
  self.values.copy()
}

///|
fn checked_span_duration(span : Span) -> Int64 raise TemporalError {
  let end = match span.end {
    Some(end) => end
    None => raise UnboundedDuration
  }
  if span.start < 0L && end > 9223372036854775807L + span.start {
    raise DurationOverflow
  }
  end - span.start
}

///|
/// Aggregate the amount of valid time assigned to each value. Instants are
/// abstract units; the caller decides whether one unit means a day, second or
/// another scale. No floating-point arithmetic or calendar rules are used.
pub fn Ledger::coverage(
  self : Ledger,
  key : String,
  bounds : Span,
  known_at : Int64,
  max_segments~ : Int32 = 4096,
) -> Coverage raise TemporalError {
  let total = checked_span_duration(bounds)
  let timeline = self.timeline(key, bounds, known_at, max_segments~)
  let mut assigned = 0L
  let mut withdrawn = 0L
  let mut unclaimed = 0L
  let durations : Map[String, Int64] = Map([])
  for segment in timeline {
    let length = checked_span_duration(segment.valid)
    match segment.source {
      None => unclaimed = unclaimed + length
      Some(source) =>
        match source.value {
          None => withdrawn = withdrawn + length
          Some(value) => {
            assigned = assigned + length
            let previous = match durations.get(value) {
              Some(previous) => previous
              None => 0L
            }
            durations[value] = previous + length
          }
        }
    }
  }
  let values : Array[ValueDuration] = []
  for value, duration in durations {
    values.push({ value, duration, })
  }
  values.sort_by(fn(a, b) { a.value.lexical_compare(b.value) })
  { total, assigned, withdrawn, unclaimed, values, }
}