///|
/// Default speed policy for combining route profile evidence.
pub fn default_speed_policy() -> SpeedPolicy {
  {
    free_flow_weight: 0.15,
    typical_weight: 0.25,
    rush_hour_weight: 0.15,
    live_weight: 0.45,
    minimum_speed: 0.5,
    maximum_speed: 60.0,
  }
}

///|
/// Return a policy with bounded non-negative weights.
pub fn sanitize_speed_policy(policy : SpeedPolicy) -> SpeedPolicy {
  {
    free_flow_weight: if policy.free_flow_weight < 0.0 {
      0.0
    } else {
      policy.free_flow_weight
    },
    typical_weight: if policy.typical_weight < 0.0 {
      0.0
    } else {
      policy.typical_weight
    },
    rush_hour_weight: if policy.rush_hour_weight < 0.0 {
      0.0
    } else {
      policy.rush_hour_weight
    },
    live_weight: if policy.live_weight < 0.0 {
      0.0
    } else {
      policy.live_weight
    },
    minimum_speed: if policy.minimum_speed <= 0.0 {
      0.1
    } else {
      policy.minimum_speed
    },
    maximum_speed: if policy.maximum_speed < policy.minimum_speed {
      policy.minimum_speed
    } else {
      policy.maximum_speed
    },
  }
}

///|
/// Map congestion to a coarse traffic regime.
pub fn regime_for_congestion(congestion : Double) -> TrafficRegime {
  let c = clamp(congestion, 0.0, 1.0)
  if c < 0.25 {
    FreeFlow
  } else if c < 0.65 {
    Typical
  } else {
    RushHour
  }
}

///|
/// Return the profile speed associated with a regime.
pub fn SegmentProfile::speed_for_regime(
  self : SegmentProfile,
  regime : TrafficRegime,
) -> Double {
  match regime {
    FreeFlow => self.free_flow_speed
    Typical => self.typical_speed
    RushHour => self.rush_hour_speed
    Live => self.typical_speed
  }
}

///|
/// Blend historical and live speed according to policy and reliability.
pub fn predict_segment_speed(
  segment : SegmentProfile,
  live_speed : Double,
  congestion : Double,
  policy? : SpeedPolicy = default_speed_policy(),
) -> Double {
  let safe = sanitize_speed_policy(policy)
  let regime = regime_for_congestion(congestion)
  let historical = segment.expected_speed(congestion~)
  let regime_speed = segment.speed_for_regime(regime)
  let live = if live_speed <= 0.0 { historical } else { live_speed }
  let regime_weight = if regime == FreeFlow {
    safe.free_flow_weight
  } else if regime == RushHour {
    safe.rush_hour_weight
  } else {
    safe.typical_weight
  }
  let live_weight = safe.live_weight * clamp(segment.reliability, 0.0, 1.0)
  let historical_weight = regime_weight + (1.0 - live_weight) * 0.5
  let total = live_weight + historical_weight
  clamp(
    safe_div(
      live * live_weight + (historical + regime_speed) * historical_weight * 0.5,
      total,
      historical,
    ),
    safe.minimum_speed,
    safe.maximum_speed,
  )
}

///|
/// Estimate confidence from segment reliability and speed agreement.
pub fn speed_confidence(
  segment : SegmentProfile,
  live_speed : Double,
  predicted_speed : Double,
) -> Double {
  let agreement = 1.0 -
    safe_div(abs_double(live_speed - predicted_speed), predicted_speed, 1.0)
  clamp(
    clamp(segment.reliability, 0.0, 1.0) * clamp(agreement, 0.0, 1.0),
    0.0,
    1.0,
  )
}

///|
/// Convert a live route speed into segment-local speed with a floor.
pub fn allocate_live_speed(
  route : RouteProfile,
  live_speed : Double,
  segment : SegmentProfile,
) -> Double {
  let average = safe_div(
    route.total_distance(),
    route.historical_eta(congestion=0.4),
    live_speed,
  )
  clamp(
    live_speed * safe_div(segment.expected_speed(congestion=0.4), average, 1.0),
    0.1,
    60.0,
  )
}

///|
/// Return weighted speed candidates for diagnostics and benchmark reports.
pub fn speed_candidates(
  segment : SegmentProfile,
  congestion~ : Double,
) -> Array[Double] {
  [
    segment.free_flow_speed,
    segment.typical_speed,
    segment.rush_hour_speed,
    segment.expected_speed(congestion~),
  ]
}

///|
/// Measure how far live speed is from a segment's historical envelope.
pub fn historical_deviation(
  segment : SegmentProfile,
  live_speed : Double,
) -> Double {
  let candidates = speed_candidates(segment, congestion=0.5)
  let center = median(candidates)
  safe_div(abs_double(live_speed - center), center, 0.0)
}