///|
pub(all) enum TemporalJoinMode {
  BothAssigned
  LeftAssigned
  AllStates
}

///|
/// A pair of independent ledger witnesses over one common valid-time span.
/// None is an unclaimed gap; Some(tombstone) remains explicit withdrawal evidence.
pub struct JoinedSegment {
  valid : Span
  left : Revision?
  right : Revision?
}

///|
pub fn JoinedSegment::valid(self : JoinedSegment) -> Span {
  self.valid
}

///|
pub fn JoinedSegment::left(self : JoinedSegment) -> Revision? {
  self.left
}

///|
pub fn JoinedSegment::right(self : JoinedSegment) -> Revision? {
  self.right
}

///|
/// Join two independently versioned facts using each side's knowledge cutoff.
/// No arithmetic on time endpoints, no automatic business-value comparison.
pub fn Ledger::join_timeline(
  self : Ledger,
  other : Ledger,
  left_key : String,
  right_key : String,
  bounds : Span,
  left_known : Int64,
  right_known : Int64,
  mode~ : TemporalJoinMode = BothAssigned,
  max_segments~ : Int32 = 4096,
) -> Array[JoinedSegment] raise TemporalError {
  if max_segments <= 0 {
    raise TooManySegments(maximum=max_segments)
  }
  if left_key.is_empty() || right_key.is_empty() {
    raise EmptyKey
  }
  let left = self.timeline(left_key, bounds, left_known, max_segments~)
  let right = other.timeline(right_key, bounds, right_known, max_segments~)
  let result : Array[JoinedSegment] = []
  let mut a = 0
  let mut b = 0
  while a < left.length() && b < right.length() {
    let emit_row = match mode {
      BothAssigned => left[a].value() is Some(_) && right[b].value() is Some(_)
      LeftAssigned => left[a].value() is Some(_)
      AllStates => true
    }
    if emit_row && left[a].valid.intersection(right[b].valid) is Some(valid) {
      if result.length() >= max_segments {
        raise TooManySegments(maximum=max_segments)
      }
      result.push({ valid, left: left[a].source, right: right[b].source, })
    }
    if end_before(left[a].valid.end, right[b].valid.end) {
      a += 1
    } else if end_before(right[b].valid.end, left[a].valid.end) {
      b += 1
    } else {
      a += 1
      b += 1
    }
  }
  result
}