///|
/// Intermediate values from RFC 9111 section 4.2.3 age calculation.
pub(all) struct AgeBreakdown {
  date_value : Timestamp?
  age_value : Int64
  apparent_age : Int64
  response_delay : Int64
  corrected_age_value : Int64
  corrected_initial_age : Int64
  resident_time : Int64
  current_age : Int64
} derive(Debug, Eq)

///|
pub(all) struct AgeResult {
  breakdown : AgeBreakdown
  trace : Array[TraceStep]
  diagnostics : Array[Diagnostic]
} derive(Debug, Eq)

///|
/// Calculate current response age with saturating arithmetic. Reversed clocks
/// are clamped to zero-duration intervals and reported diagnostically.
pub fn calculate_current_age(
  stored : StoredResponse,
  now : Timestamp,
) -> AgeResult {
  let diagnostics : Array[Diagnostic] = []
  let trace : Array[TraceStep] = []
  let date_value = header_date(stored.response.headers, "date", now)
  if stored.response.headers.contains("date") && date_value is None {
    diagnostics.push({
      level: Warning,
      code: "AGE_DATE_INVALID",
      message: "Date is invalid or repeated and was ignored",
      field_name: Some("date"),
    })
  }
  let age_value = parse_age_value(stored.response.headers, diagnostics)
  let apparent_age = match date_value {
    Some(date) => nonnegative_difference(stored.response_time, date)
    None => 0L
  }
  let response_delay = if stored.response_time >= stored.request_time {
    stored.response_time - stored.request_time
  } else {
    diagnostics.push({
      level: Warning,
      code: "AGE_RESPONSE_TIME_BEFORE_REQUEST",
      message: "response time precedes request time; delay was clamped to zero",
      field_name: None,
    })
    0L
  }
  let corrected_age_value = saturated_add(age_value, response_delay)
  let corrected_initial_age = if apparent_age > corrected_age_value {
    apparent_age
  } else {
    corrected_age_value
  }
  let resident_time = if now >= stored.response_time {
    now - stored.response_time
  } else {
    diagnostics.push({
      level: Warning,
      code: "AGE_NOW_BEFORE_RESPONSE",
      message: "current time precedes response time; resident time was clamped to zero",
      field_name: None,
    })
    0L
  }
  let current_age = saturated_add(corrected_initial_age, resident_time)
  trace.push(
    step(
      "AGE_CALCULATED", "current age calculated from apparent age, corrected Age, and resident time",
      "RFC 9111 4.2.3",
    ),
  )
  {
    breakdown: {
      date_value,
      age_value,
      apparent_age,
      response_delay,
      corrected_age_value,
      corrected_initial_age,
      resident_time,
      current_age,
    },
    trace,
    diagnostics,
  }
}

///|
fn parse_age_value(headers : Headers, diagnostics : Array[Diagnostic]) -> Int64 {
  let values = headers.values("age")
  if values.length() == 0 {
    return 0L
  }
  if values.length() > 1 {
    diagnostics.push({
      level: Warning,
      code: "AGE_REPEATED",
      message: "repeated Age fields were ignored",
      field_name: Some("age"),
    })
    return 0L
  }
  match @lex.parse_saturating_decimal(values[0], 2147483648L) {
    Some(value) => value
    None => {
      diagnostics.push({
        level: Warning,
        code: "AGE_VALUE_INVALID",
        message: "invalid Age field was treated as zero",
        field_name: Some("age"),
      })
      0L
    }
  }
}

///|
fn nonnegative_difference(later : Int64, earlier : Int64) -> Int64 {
  if later <= earlier {
    0L
  } else if later - earlier > 2147483648L {
    2147483648L
  } else {
    later - earlier
  }
}

///|
fn saturated_add(left : Int64, right : Int64) -> Int64 {
  if left >= 2147483648L || right >= 2147483648L - left {
    2147483648L
  } else {
    left + right
  }
}