///|
/// Whether contacting the origin is currently possible.
pub(all) enum OriginState {
  OriginAvailable
  OriginUnavailable
} derive(Debug, Eq)

///|
/// Stable terminal reuse decisions.
pub(all) enum ReuseVerdict {
  ReuseFresh
  Revalidate
  ServeStale
  FetchFromOrigin
  OnlyIfCachedMiss
  NotReusable
} derive(Debug, Eq)

///|
pub(all) struct ReuseDecision {
  verdict : ReuseVerdict
  current_age : Int64
  freshness_lifetime : Int64
  trace : Array[TraceStep]
  diagnostics : Array[Diagnostic]
} derive(Debug, Eq)

///|
/// Decide whether one candidate stored response can satisfy a request.
pub fn evaluate_reuse(
  stored : StoredResponse?,
  presented : RequestMetadata,
  mode : CacheMode,
  now : Timestamp,
  origin? : OriginState = OriginAvailable,
  policy? : CachePolicy = default_policy(),
) -> ReuseDecision {
  let request_control = effective_request_control(presented.headers)
  let diagnostics = request_control.diagnostics.copy()
  guard stored is Some(candidate) else {
    return reuse_without_candidate(request_control, diagnostics)
  }
  let key = evaluate_cache_key(candidate, presented)
  append_diagnostics(diagnostics, key.diagnostics)
  let trace = key.trace.copy()
  if !key.matches {
    return reuse_terminal(
      if request_control.contains("only-if-cached") {
        OnlyIfCachedMiss
      } else {
        NotReusable
      },
      "REUSE_CACHE_KEY_MISMATCH",
      "candidate does not match the presented cache key",
      "RFC 9111 4, 5.2.1.7",
      0L,
      0L,
      trace,
      diagnostics,
    )
  }
  let response_control = parse_cache_control(candidate.response.headers)
  append_diagnostics(diagnostics, response_control.diagnostics)
  let freshness = calculate_freshness(candidate, mode, now, policy~)
  append_diagnostics(diagnostics, freshness.diagnostics)
  for item in freshness.trace {
    trace.push(item)
  }
  let age = freshness.current_age
  let lifetime = constrained_lifetime(
    freshness.freshness_lifetime,
    request_control,
    diagnostics,
  )
  if request_forces_validation(request_control) ||
    response_control.contains("no-cache") {
    return network_required(
      candidate, request_control, origin, "REUSE_NO_CACHE_REVALIDATION", "request or response requires successful validation before reuse",
      "RFC 9111 4, 5.2.1.4, 5.2.2.4", age, lifetime, trace, diagnostics,
    )
  }
  let minimum_fresh = request_delta_or_zero(
    request_control, "min-fresh", diagnostics,
  )
  let fresh_enough = saturated_add(age, minimum_fresh) < lifetime
  if fresh_enough {
    return reuse_terminal(
      ReuseFresh,
      "REUSE_FRESH",
      "candidate is fresh after applying request constraints",
      "RFC 9111 4.2, 5.2.1.3",
      age,
      lifetime,
      trace,
      diagnostics,
    )
  }
  let stale_by = if age > lifetime { age - lifetime } else { 0L }
  let stale_forbidden = response_control.contains("must-revalidate") ||
    (mode == SharedCache && response_control.contains("proxy-revalidate")) ||
    request_control.contains("no-cache")
  let max_stale = max_stale_allowance(request_control, diagnostics)
  let request_allows_stale = match max_stale {
    Some(None) => true
    Some(Some(limit)) => stale_by <= limit
    None => false
  }
  let disconnected_policy = origin == OriginUnavailable &&
    policy.allow_disconnected_stale
  if !stale_forbidden && (request_allows_stale || disconnected_policy) {
    return reuse_terminal(
      ServeStale,
      if request_allows_stale {
        "REUSE_STALE_REQUEST_ALLOWED"
      } else {
        "REUSE_STALE_DISCONNECTED_POLICY"
      },
      "stale candidate is permitted by explicit request or disconnected policy",
      "RFC 9111 4.2.4, 5.2.1.2",
      age,
      lifetime,
      trace,
      diagnostics,
    )
  }
  network_required(
    candidate,
    request_control,
    origin,
    if stale_forbidden {
      "REUSE_STALE_FORBIDDEN"
    } else {
      "REUSE_STALE_REVALIDATE"
    },
    "stale candidate requires successful validation or a new response",
    "RFC 9111 4.2.4, 4.3",
    age,
    lifetime,
    trace,
    diagnostics,
  )
}

///|
fn reuse_without_candidate(
  request_control : CacheControl,
  diagnostics : Array[Diagnostic],
) -> ReuseDecision {
  let trace : Array[TraceStep] = []
  if request_control.contains("only-if-cached") {
    reuse_terminal(
      OnlyIfCachedMiss,
      "REUSE_ONLY_IF_CACHED_MISS",
      "only-if-cached forbids contacting the origin and no candidate exists",
      "RFC 9111 5.2.1.7",
      0L,
      0L,
      trace,
      diagnostics,
    )
  } else {
    reuse_terminal(
      FetchFromOrigin,
      "REUSE_CACHE_MISS",
      "no stored response candidate exists",
      "RFC 9111 4",
      0L,
      0L,
      trace,
      diagnostics,
    )
  }
}

///|
fn network_required(
  candidate : StoredResponse,
  request_control : CacheControl,
  origin : OriginState,
  code : String,
  message : String,
  section : String,
  age : Int64,
  lifetime : Int64,
  trace : Array[TraceStep],
  diagnostics : Array[Diagnostic],
) -> ReuseDecision {
  if request_control.contains("only-if-cached") || origin == OriginUnavailable {
    return reuse_terminal(
      OnlyIfCachedMiss,
      "REUSE_NETWORK_FORBIDDEN",
      "validation or retrieval is required but the origin cannot be contacted",
      "RFC 9111 5.2.1.7",
      age,
      lifetime,
      trace,
      diagnostics,
    )
  }
  reuse_terminal(
    if has_validator(candidate.response.headers) {
      Revalidate
    } else {
      FetchFromOrigin
    },
    code,
    message,
    section,
    age,
    lifetime,
    trace,
    diagnostics,
  )
}

///|
fn reuse_terminal(
  verdict : ReuseVerdict,
  code : String,
  message : String,
  section : String,
  age : Int64,
  lifetime : Int64,
  trace : Array[TraceStep],
  diagnostics : Array[Diagnostic],
) -> ReuseDecision {
  trace.push(step(code, message, section))
  {
    verdict,
    current_age: age,
    freshness_lifetime: lifetime,
    trace,
    diagnostics,
  }
}

///|
fn effective_request_control(headers : Headers) -> CacheControl {
  let parsed = parse_cache_control(headers)
  if headers.contains("cache-control") {
    return parsed
  }
  for value in headers.values("pragma") {
    if @lex.lower(@lex.trim_ows(value)) == "no-cache" {
      let directives = parsed.directives.copy()
      directives.push({
        name: "no-cache",
        value: None,
        quoted: false,
        raw: "no-cache",
      })
      let diagnostics = parsed.diagnostics.copy()
      diagnostics.push({
        level: Info,
        code: "REQUEST_PRAGMA_NO_CACHE",
        message: "legacy Pragma no-cache was applied because Cache-Control is absent",
        field_name: Some("pragma"),
      })
      return { directives, diagnostics, }
    }
  }
  parsed
}

///|
fn request_forces_validation(control : CacheControl) -> Bool {
  if control.contains("no-cache") {
    return true
  }
  match control.delta("max-age") {
    DeltaValid(0L) => true
    _ => false
  }
}

///|
fn constrained_lifetime(
  lifetime : Int64,
  control : CacheControl,
  diagnostics : Array[Diagnostic],
) -> Int64 {
  match control.delta("max-age") {
    DeltaValid(limit) => if limit < lifetime { limit } else { lifetime }
    DeltaInvalid | DeltaRepeated => {
      diagnostics.push({
        level: Warning,
        code: "REQUEST_MAX_AGE_INVALID",
        message: "invalid request max-age was ignored",
        field_name: Some("cache-control"),
      })
      lifetime
    }
    DeltaMissing => lifetime
  }
}

///|
fn request_delta_or_zero(
  control : CacheControl,
  name : String,
  diagnostics : Array[Diagnostic],
) -> Int64 {
  match control.delta(name) {
    DeltaValid(value) => value
    DeltaInvalid | DeltaRepeated => {
      diagnostics.push({
        level: Warning,
        code: "REQUEST_DELTA_INVALID",
        message: "invalid request delta directive was ignored: " + name,
        field_name: Some("cache-control"),
      })
      0L
    }
    DeltaMissing => 0L
  }
}

///|
/// Outer Some means max-stale is present; inner None means unlimited.
fn max_stale_allowance(
  control : CacheControl,
  diagnostics : Array[Diagnostic],
) -> Int64?? {
  let matches = control.occurrences("max-stale")
  if matches.length() == 0 {
    return None
  }
  if matches.length() > 1 {
    diagnostics.push({
      level: Warning,
      code: "REQUEST_MAX_STALE_REPEATED",
      message: "repeated max-stale directive was ignored",
      field_name: Some("cache-control"),
    })
    return None
  }
  match matches[0].value {
    None => Some(None)
    Some(value) =>
      match @lex.parse_saturating_decimal(value, 2147483648L) {
        Some(seconds) => Some(Some(seconds))
        None => {
          diagnostics.push({
            level: Warning,
            code: "REQUEST_MAX_STALE_INVALID",
            message: "invalid max-stale value was ignored",
            field_name: Some("cache-control"),
          })
          None
        }
      }
  }
}

///|
fn has_validator(headers : Headers) -> Bool {
  (headers.first("etag") is Some(value) && value.trim().length() > 0) ||
  (headers.first("last-modified") is Some(value) && value.trim().length() > 0)
}