///|
/// Validator selected for a conditional origin request.
pub(all) enum ValidatorKind {
  EntityTagValidator
  LastModifiedValidator
  NoValidator
} derive(Debug, Eq)

///|
pub(all) struct ValidationRequest {
  headers : Headers
  kind : ValidatorKind
  trace : Array[TraceStep]
} derive(Debug, Eq)

///|
/// Result of attempting to apply a 304 response to stored metadata.
pub(all) struct ValidationMergeResult {
  merged : StoredResponse?
  trace : Array[TraceStep]
  diagnostics : Array[Diagnostic]
} derive(Debug, Eq)

///|
/// Prepare conditional request fields from the preferred stored validator.
pub fn prepare_validation(
  stored : StoredResponse,
  presented : RequestMetadata,
) -> ValidationRequest {
  let trace : Array[TraceStep] = []
  match stored.response.headers.first("etag") {
    Some(value) if value.trim().length() > 0 => {
      trace.push(
        step(
          "VALIDATION_IF_NONE_MATCH", "selected stored ETag for If-None-Match", "RFC 9111 4.3.1",
        ),
      )
      {
        headers: presented.headers.replace("if-none-match", value),
        kind: EntityTagValidator,
        trace,
      }
    }
    _ =>
      match stored.response.headers.first("last-modified") {
        Some(value) if value.trim().length() > 0 => {
          trace.push(
            step(
              "VALIDATION_IF_MODIFIED_SINCE", "selected Last-Modified for If-Modified-Since",
              "RFC 9111 4.3.1",
            ),
          )
          {
            headers: presented.headers.replace("if-modified-since", value),
            kind: LastModifiedValidator,
            trace,
          }
        }
        _ => {
          trace.push(
            step(
              "VALIDATION_NO_VALIDATOR", "stored response has no usable validator",
              "RFC 9111 4.3.1",
            ),
          )
          { headers: presented.headers, kind: NoValidator, trace, }
        }
      }
  }
}

///|
/// Apply metadata from a valid 304 response without changing the stored status
/// or body ownership. A conflicting ETag fails closed.
pub fn merge_not_modified(
  stored : StoredResponse,
  not_modified : ResponseMetadata,
  request_time : Timestamp,
  response_time : Timestamp,
) -> ValidationMergeResult {
  let trace : Array[TraceStep] = []
  let diagnostics = not_modified.headers.diagnostics()
  if not_modified.status != 304 {
    return merge_rejected(
      "VALIDATION_STATUS_NOT_304", "validation merge requires status 304", trace,
      diagnostics,
    )
  }
  if has_error(diagnostics) {
    return merge_rejected(
      "VALIDATION_FIELDS_INVALID", "304 response contains unsafe HTTP fields", trace,
      diagnostics,
    )
  }
  let old_etag = stored.response.headers.first("etag")
  let new_etag = not_modified.headers.first("etag")
  if old_etag is Some(old_value) &&
    new_etag is Some(new_value) &&
    old_value.trim() != new_value.trim() {
    return merge_rejected(
      "VALIDATION_ETAG_CONFLICT", "304 ETag does not identify the stored response",
      trace, diagnostics,
    )
  }
  let connection_fields = connection_named_fields(not_modified.headers)
  let mut updated = stored.response.headers
  let processed : Array[String] = []
  for entry in not_modified.headers.entries {
    if array_contains(processed, entry.name) {
      continue
    }
    processed.push(entry.name)
    if should_skip_validation_update(entry.name, connection_fields) {
      trace.push(
        step(
          "VALIDATION_FIELD_SKIPPED",
          "excluded field was not copied from 304: " + entry.name,
          "RFC 9111 3.2, 4.3.4",
        ),
      )
      continue
    }
    updated = updated.without(entry.name)
    for value in not_modified.headers.values(entry.name) {
      updated = updated.add(entry.name, value)
    }
  }
  trace.push(
    step(
      "VALIDATION_METADATA_MERGED", "stored response metadata was freshened from 304",
      "RFC 9111 4.3.4",
    ),
  )
  {
    merged: Some({
      request: stored.request,
      response: { status: stored.response.status, headers: updated, },
      request_time,
      response_time,
    }),
    trace,
    diagnostics,
  }
}

///|
fn merge_rejected(
  code : String,
  message : String,
  trace : Array[TraceStep],
  diagnostics : Array[Diagnostic],
) -> ValidationMergeResult {
  trace.push(step(code, message, "RFC 9111 4.3.4"))
  { merged: None, trace, diagnostics, }
}

///|
fn connection_named_fields(headers : Headers) -> Array[String] {
  let fields : Array[String] = []
  for value in headers.values("connection") {
    let split = @lex.split_quoted_list(value)
    for item in split.parts {
      let name = @lex.lower(@lex.trim_ows(item))
      if @lex.is_token(name) && !array_contains(fields, name) {
        fields.push(name)
      }
    }
  }
  fields
}

///|
fn should_skip_validation_update(
  name : String,
  connection_fields : Array[String],
) -> Bool {
  name == "connection" ||
  name == "content-length" ||
  name == "content-range" ||
  name == "keep-alive" ||
  name == "proxy-authenticate" ||
  name == "proxy-authentication-info" ||
  name == "proxy-authorization" ||
  name == "proxy-connection" ||
  name == "te" ||
  name == "trailer" ||
  name == "transfer-encoding" ||
  name == "upgrade" ||
  array_contains(connection_fields, name)
}