///|
/// Return whether a breakdown's totals match its segment contributions.
pub fn breakdown_is_consistent(breakdown : RouteBreakdown) -> Bool {
  let summed_distance = breakdown.segments.fold(init=0.0, (total, segment) => {
    total + segment.distance
  })
  let summed_eta = sum_segment_eta(breakdown.segments)
  abs_double(summed_distance - breakdown.total_distance) < 0.000001 &&
  abs_double(summed_eta - breakdown.total_eta_seconds) < 0.000001 &&
  breakdown.confidence >= 0.0 &&
  breakdown.confidence <= 1.0
}

///|
/// Check a detailed breakdown including segment IDs and ordering.
pub fn detailed_breakdown_is_consistent(detailed : DetailedBreakdown) -> Bool {
  if !breakdown_is_consistent(detailed.total) ||
    detailed.forecasts.length() != detailed.total.segments.length() {
    false
  } else {
    let mut valid = true
    for index, forecast in detailed.forecasts {
      if forecast.index != index ||
        forecast.eta.segment_id != forecast.segment.segment_id ||
        !validate_calendar_bucket({
          start_second: 0.0,
          end_second: 1.0,
          congestion: forecast.congestion,
          label: "check",
        }) {
        valid = false
      }
    }
    valid
  }
}

///|
/// Return a bounded remaining distance for a route observation.
pub fn route_remaining_distance(
  route : RouteProfile,
  observation : Observation,
) -> Double {
  clamp(
    route.total_distance() - observation.distance,
    0.0,
    route.total_distance(),
  )
}

///|
/// Return whether ETA results have finite non-negative fields.
pub fn results_are_finite(results : Array[EtaResult]) -> Bool {
  results.all(result => check_eta_result(result))
}

///|
/// Return a normalized route progress fraction.
pub fn route_progress(route : RouteProfile, distance : Double) -> Double {
  safe_div(
    clamp(distance, 0.0, route.total_distance()),
    route.total_distance(),
    0.0,
  )
}

///|
/// Return progress values for a trace.
pub fn trace_progress(
  route : RouteProfile,
  observations : Array[Observation],
) -> Array[Double] {
  let values : Array[Double] = []
  for observation in observations {
    values.push(route_progress(route, observation.distance))
  }
  values
}

///|
/// Return the first index at which route completion is reached.
pub fn completion_index(
  route : RouteProfile,
  observations : Array[Observation],
) -> Int? {
  let distance = route.total_distance()
  for index, observation in observations {
    if observation.distance >= distance {
      return Some(index)
    }
  }
  None
}

///|
/// Return a route's total length after removing invalid negative segments.
pub fn safe_route_distance(route : RouteProfile) -> Double {
  let mut total = 0.0
  for segment in route.segments {
    if segment.distance > 0.0 && is_finite_value(segment.distance) {
      total += segment.distance
    }
  }
  total
}

///|
/// Return route segments whose reliability meets a floor.
pub fn reliable_segments(
  route : RouteProfile,
  floor? : Double = 0.5,
) -> Array[SegmentProfile] {
  let result : Array[SegmentProfile] = []
  let threshold = clamp(floor, 0.0, 1.0)
  for segment in route.segments {
    if segment.reliability >= threshold {
      result.push(segment)
    }
  }
  result
}

///|
/// Return the fraction of a route covered by reliable segments.
pub fn reliable_distance_fraction(
  route : RouteProfile,
  floor? : Double = 0.5,
) -> Double {
  let reliable = reliable_segments(route, floor~)
  let distance = reliable.fold(init=0.0, (total, segment) => {
    total + segment.distance
  })
  safe_div(distance, route.total_distance(), 0.0)
}