///|
/// Return the centralized, replaceable demonstration configuration for M3.
///
/// Values are integer milliseconds and are illustrative teaching assumptions,
/// not official F1 data for any circuit, tyre, team, or driver.
pub fn default_pace_model_config() -> PaceModelConfig {
  {
    tyre_profiles: [
      {
        compound: Soft,
        fresh_deltas: { dry_ms: -700, damp_ms: 6500, wet_ms: 24000, },
        degradation_ms_per_lap: 120,
        cliff_age_laps: 12,
        cliff_extra_ms_per_lap: 240,
      },
      {
        compound: Medium,
        fresh_deltas: { dry_ms: 0, damp_ms: 7000, wet_ms: 25000, },
        degradation_ms_per_lap: 80,
        cliff_age_laps: 20,
        cliff_extra_ms_per_lap: 180,
      },
      {
        compound: Hard,
        fresh_deltas: { dry_ms: 500, damp_ms: 7500, wet_ms: 26000, },
        degradation_ms_per_lap: 55,
        cliff_age_laps: 30,
        cliff_extra_ms_per_lap: 120,
      },
      {
        compound: Intermediate,
        fresh_deltas: { dry_ms: 4000, damp_ms: 0, wet_ms: 3500, },
        degradation_ms_per_lap: 95,
        cliff_age_laps: 22,
        cliff_extra_ms_per_lap: 180,
      },
      {
        compound: Wet,
        fresh_deltas: { dry_ms: 9000, damp_ms: 3000, wet_ms: 500, },
        degradation_ms_per_lap: 70,
        cliff_age_laps: 28,
        cliff_extra_ms_per_lap: 140,
      },
    ],
    pit_loss_profile: {
      green_pit_loss_ms: 22000,
      safety_car_pit_loss_ms: 12000,
    },
  }
}

///|
fn fresh_delta_ms(deltas : WeatherPaceDeltas, weather : Weather) -> Int {
  match weather {
    Dry => deltas.dry_ms
    Damp => deltas.damp_ms
    Wet => deltas.wet_ms
  }
}

///|
fn validated_profile(
  config : PaceModelConfig,
  compound : Compound,
) -> Result[TyreProfile, ModelError] {
  match validate_pace_model_config(config) {
    Err(error) => Err(error)
    Ok(_) =>
      match find_tyre_profile(config, compound) {
        Some(profile) => Ok(profile)
        None =>
          Err(
            model_error(
              MissingTyreProfile,
              "missing tyre profile for requested compound",
            ),
          )
      }
  }
}

///|
/// Calculate fresh pace plus linear and cliff degradation in integer milliseconds.
///
/// `tyre_age_laps` is the completed-lap age and must be at least one. A
/// positive result makes the lap slower than the neutral anchor; a negative
/// result makes it faster. Returns `ModelError` for an invalid configuration,
/// missing profile, or invalid tyre age.
pub fn tyre_pace_delta_ms(
  config : PaceModelConfig,
  compound : Compound,
  tyre_age_laps : Int,
  weather : Weather,
) -> Result[Int, ModelError] {
  let profile = match validated_profile(config, compound) {
    Ok(profile) => profile
    Err(error) => return Err(error)
  }
  if tyre_age_laps < 1 {
    return Err(model_error(InvalidTyreAge, "tyre age must be at least 1 lap"))
  }
  let linear_degradation_ms = (tyre_age_laps - 1) *
    profile.degradation_ms_per_lap
  let cliff_degradation_ms = if tyre_age_laps > profile.cliff_age_laps {
    (tyre_age_laps - profile.cliff_age_laps) * profile.cliff_extra_ms_per_lap
  } else {
    0
  }
  Ok(
    fresh_delta_ms(profile.fresh_deltas, weather) +
    linear_degradation_ms +
    cliff_degradation_ms,
  )
}

///|
/// Return the configured pit loss for a track status after validating the config.
///
/// The returned non-negative integer is milliseconds added on a pit lap.
/// Returns `ModelError` when `config` is invalid.
pub fn pit_loss_ms(
  config : PaceModelConfig,
  track_status : TrackStatus,
) -> Result[Int, ModelError] {
  match validate_pace_model_config(config) {
    Err(error) => Err(error)
    Ok(_) =>
      match track_status {
        Green => Ok(config.pit_loss_profile.green_pit_loss_ms)
        SafetyCar => Ok(config.pit_loss_profile.safety_car_pit_loss_ms)
      }
  }
}

///|
fn modeled_pit_loss_ms(
  config : PaceModelConfig,
  track_status : TrackStatus,
  pit : Bool,
) -> Result[Int, ModelError] {
  if pit {
    pit_loss_ms(config, track_status)
  } else {
    Ok(0)
  }
}

///|
/// Estimate observed lap time from a positive neutral anchor and model conditions.
///
/// Adds the tyre pace delta and, when `pit` is true, the configured track-status
/// pit loss. Returns a positive integer-millisecond lap time or `ModelError`
/// for invalid model input or a non-positive result.
pub fn estimate_lap_time_ms(
  config : PaceModelConfig,
  neutral_lap_time_ms : Int,
  compound : Compound,
  tyre_age_laps : Int,
  weather : Weather,
  track_status : TrackStatus,
  pit : Bool,
) -> Result[Int, ModelError] {
  if neutral_lap_time_ms < 1 {
    return Err(
      model_error(InvalidLapTime, "neutral lap time must be greater than zero"),
    )
  }
  let tyre_delta = match
    tyre_pace_delta_ms(config, compound, tyre_age_laps, weather) {
    Ok(delta) => delta
    Err(error) => return Err(error)
  }
  let pit_loss = match modeled_pit_loss_ms(config, track_status, pit) {
    Ok(loss) => loss
    Err(error) => return Err(error)
  }
  let predicted = neutral_lap_time_ms + tyre_delta + pit_loss
  if predicted < 1 {
    return Err(
      model_error(
        NonPositiveResult,
        "estimated lap time must be greater than zero",
      ),
    )
  }
  Ok(predicted)
}

///|
/// Derive a positive neutral anchor by removing modeled pace and pit effects.
///
/// This is the inverse of `estimate_lap_time_ms` for the same model inputs.
/// It preserves unmodeled observed effects in the anchor and returns
/// `ModelError` for invalid input or a non-positive derived time.
pub fn derive_neutral_lap_time_ms(
  config : PaceModelConfig,
  observed_lap_time_ms : Int,
  compound : Compound,
  tyre_age_laps : Int,
  weather : Weather,
  track_status : TrackStatus,
  pit : Bool,
) -> Result[Int, ModelError] {
  if observed_lap_time_ms < 1 {
    return Err(
      model_error(InvalidLapTime, "observed lap time must be greater than zero"),
    )
  }
  let tyre_delta = match
    tyre_pace_delta_ms(config, compound, tyre_age_laps, weather) {
    Ok(delta) => delta
    Err(error) => return Err(error)
  }
  let pit_loss = match modeled_pit_loss_ms(config, track_status, pit) {
    Ok(loss) => loss
    Err(error) => return Err(error)
  }
  let neutral = observed_lap_time_ms - tyre_delta - pit_loss
  if neutral < 1 {
    return Err(
      model_error(
        NonPositiveResult,
        "derived neutral lap time must be greater than zero",
      ),
    )
  }
  Ok(neutral)
}