///|
/// Source selected for a response's freshness lifetime.
pub(all) enum FreshnessSource {
  SharedMaxAge
  MaxAge
  ExpiresDate
  LastModifiedHeuristic
  NoFreshnessInformation
} derive(Debug, Eq)

///|
pub(all) struct FreshnessResult {
  source : FreshnessSource
  freshness_lifetime : Int64
  current_age : Int64
  remaining_freshness : Int64
  fresh : Bool
  trace : Array[TraceStep]
  diagnostics : Array[Diagnostic]
} derive(Debug, Eq)

///|
/// Calculate response freshness lifetime and compare it with current age.
pub fn calculate_freshness(
  stored : StoredResponse,
  mode : CacheMode,
  now : Timestamp,
  policy? : CachePolicy = default_policy(),
) -> FreshnessResult {
  let age = calculate_current_age(stored, now)
  let diagnostics = age.diagnostics.copy()
  let trace = age.trace.copy()
  let control = parse_cache_control(stored.response.headers)
  append_diagnostics(diagnostics, control.diagnostics)
  let (source, lifetime) = select_freshness_lifetime(
    stored, mode, now, policy, control, diagnostics, trace,
  )
  let current_age = age.breakdown.current_age
  let remaining = if lifetime > current_age {
    lifetime - current_age
  } else {
    0L
  }
  let fresh = current_age < lifetime
  trace.push(
    step(
      if fresh {
        "FRESHNESS_FRESH"
      } else {
        "FRESHNESS_STALE"
      },
      if fresh {
        "stored response is fresh"
      } else {
        "stored response is stale"
      },
      "RFC 9111 4.2",
    ),
  )
  {
    source,
    freshness_lifetime: lifetime,
    current_age,
    remaining_freshness: remaining,
    fresh,
    trace,
    diagnostics,
  }
}

///|
fn select_freshness_lifetime(
  stored : StoredResponse,
  mode : CacheMode,
  now : Timestamp,
  policy : CachePolicy,
  control : CacheControl,
  diagnostics : Array[Diagnostic],
  trace : Array[TraceStep],
) -> (FreshnessSource, Int64) {
  if mode == SharedCache {
    match control.delta("s-maxage") {
      DeltaValid(value) => {
        trace.push(
          step(
            "FRESHNESS_SHARED_MAX_AGE", "shared cache selected s-maxage", "RFC 9111 5.2.2.10",
          ),
        )
        return (SharedMaxAge, value)
      }
      DeltaInvalid | DeltaRepeated =>
        diagnostics.push({
          level: Warning,
          code: "FRESHNESS_S_MAXAGE_INVALID",
          message: "invalid or repeated s-maxage was ignored",
          field_name: Some("cache-control"),
        })
      DeltaMissing => ()
    }
  }
  match control.delta("max-age") {
    DeltaValid(value) => {
      trace.push(
        step(
          "FRESHNESS_MAX_AGE", "cache selected response max-age", "RFC 9111 5.2.2.1",
        ),
      )
      return (MaxAge, value)
    }
    DeltaInvalid | DeltaRepeated =>
      diagnostics.push({
        level: Warning,
        code: "FRESHNESS_MAX_AGE_INVALID",
        message: "invalid or repeated max-age was ignored",
        field_name: Some("cache-control"),
      })
    DeltaMissing => ()
  }
  let expires = header_date(stored.response.headers, "expires", now)
  if expires is Some(expiration) {
    let date = header_date(stored.response.headers, "date", now).unwrap_or(
      stored.response_time,
    )
    let lifetime = nonnegative_difference(expiration, date)
    trace.push(
      step(
        "FRESHNESS_EXPIRES", "cache selected Expires relative to Date or response time",
        "RFC 9111 4.2.1",
      ),
    )
    return (ExpiresDate, lifetime)
  }
  if stored.response.headers.contains("expires") {
    diagnostics.push({
      level: Warning,
      code: "FRESHNESS_EXPIRES_INVALID",
      message: "invalid or repeated Expires was treated as already expired",
      field_name: Some("expires"),
    })
    return (ExpiresDate, 0L)
  }
  if policy.allow_heuristic_freshness {
    let last_modified = header_date(
      stored.response.headers,
      "last-modified",
      now,
    )
    if last_modified is Some(modified) {
      let date = header_date(stored.response.headers, "date", now).unwrap_or(
        stored.response_time,
      )
      let resource_age = nonnegative_difference(date, modified)
      let fraction = clamp_int(policy.heuristic_fraction_percent, 0, 100)
      let cap = clamp_int64(policy.heuristic_max_seconds, 0L, 2147483648L)
      let candidate = resource_age / 100L * fraction.to_int64() +
        resource_age % 100L * fraction.to_int64() / 100L
      let lifetime = if candidate > cap { cap } else { candidate }
      trace.push(
        step(
          "FRESHNESS_HEURISTIC_LAST_MODIFIED", "heuristic freshness derived from Last-Modified age",
          "RFC 9111 4.2.2",
        ),
      )
      return (LastModifiedHeuristic, lifetime)
    }
  }
  trace.push(
    step(
      "FRESHNESS_NONE", "response contains no usable explicit or heuristic freshness information",
      "RFC 9111 4.2",
    ),
  )
  (NoFreshnessInformation, 0L)
}

///|
fn clamp_int(value : Int, low : Int, high : Int) -> Int {
  if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}

///|
fn clamp_int64(value : Int64, low : Int64, high : Int64) -> Int64 {
  if value < low {
    low
  } else if value > high {
    high
  } else {
    value
  }
}