///|
/// Explain the factors used to produce an ETA result.
pub fn explain_eta(
  result : EtaResult,
  route : RouteProfile,
  congestion~ : Double,
  config? : EtaConfig = default_eta_config(),
) -> EtaExplanation {
  let historical_speed = safe_div(
    route.total_distance(),
    route.historical_eta(congestion~),
    config.min_speed,
  )
  let live_weight = config.live_weight * result.confidence
  let history_weight = config.history_weight * (2.0 - result.confidence)
  let factors : Array[String] = []
  if result.confidence < 0.3 {
    factors.push("low confidence increases historical influence")
  }
  if result.raw_speed <= config.min_speed {
    factors.push("raw speed is at the configured floor")
  }
  if congestion > 0.65 {
    factors.push("congestion selects rush-hour profile")
  }
  if result.remaining_distance <= 0.0 {
    factors.push("route has no remaining distance")
  }
  {
    remaining_distance: result.remaining_distance,
    raw_speed: result.raw_speed,
    smoothed_speed: result.smoothed_speed,
    historical_speed,
    live_weight,
    history_weight,
    confidence: result.confidence,
    factors,
  }
}

///|
/// Return a concise explanation suitable for a tooltip.
pub fn format_eta_explanation(explanation : EtaExplanation) -> String {
  let builder = StringBuilder()
  builder.write_string(
    "remaining={explanation.remaining_distance} raw={explanation.raw_speed} smooth={explanation.smoothed_speed} history={explanation.historical_speed} confidence={explanation.confidence}\n",
  )
  for factor in explanation.factors {
    builder.write_string("- ")
    builder.write_string(factor)
    builder.write_char('\n')
  }
  builder.to_string()
}

///|
/// Explain every result in a batch.
pub fn explain_batch(
  batch : BatchForecast,
  route : RouteProfile,
  congestion~ : Double,
) -> Array[EtaExplanation] {
  let explanations : Array[EtaExplanation] = []
  for result in batch.results {
    explanations.push(explain_eta(result, route, congestion~))
  }
  explanations
}

///|
/// Return the dominant explanation factor, if any.
pub fn dominant_eta_factor(explanation : EtaExplanation) -> String? {
  if explanation.factors.length() == 0 {
    None
  } else {
    Some(explanation.factors[0])
  }
}

///|
/// Explain how a result differs from a historical-only baseline.
pub fn baseline_delta(
  result : EtaResult,
  route : RouteProfile,
  congestion~ : Double,
) -> Double {
  let baseline = safe_div(
    result.remaining_distance,
    safe_div(route.total_distance(), route.historical_eta(congestion~), 0.5),
    0.0,
  )
  result.eta_seconds - baseline
}