///|
/// HTTP response metadata supplied by the caller; this library sends no HTTP.
pub struct ProblemContext {
  actual_status : Int?
  content_type : String?
  base_uri : String?
} derive(Eq, Debug)

///|
pub fn problem_context(
  actual_status : Int?,
  content_type : String?,
  base_uri : String?,
) -> ProblemContext {
  { actual_status, content_type, base_uri }
}

///|
pub(all) enum ContextIssueKind {
  InvalidContentType
  StatusMismatch
  MissingBaseUri
  InvalidBaseUri
} derive(Eq, Debug)

///|
pub struct ContextIssue {
  kind : ContextIssueKind
  context : String
} derive(Eq, Debug)

///|
pub fn ContextIssue::kind(self : ContextIssue) -> ContextIssueKind {
  self.kind
}

///|
pub fn ContextIssue::context(self : ContextIssue) -> String {
  self.context
}

///|
pub fn ContextIssueKind::name(self : ContextIssueKind) -> String {
  match self {
    InvalidContentType => "InvalidContentType"
    StatusMismatch => "StatusMismatch"
    MissingBaseUri => "MissingBaseUri"
    InvalidBaseUri => "InvalidBaseUri"
  }
}

///|
pub fn validate_context(
  problem : ProblemDetails,
  context : ProblemContext,
) -> Array[ContextIssue] {
  let issues : Array[ContextIssue] = []
  match context.content_type {
    Some(value) if !is_problem_content_type(value) =>
      issues.push({
        kind: InvalidContentType,
        context: "Content-Type is not application/problem+json",
      })
    _ => ()
  }
  match (problem.status(), context.actual_status) {
    (Some(declared), Some(actual)) if declared != actual =>
      issues.push({
        kind: StatusMismatch,
        context: "problem status differs from HTTP response status",
      })
    _ => ()
  }
  let needs_base = is_relative_optional(problem.type_raw()) ||
    is_relative_optional(problem.instance())
  if needs_base {
    match context.base_uri {
      None =>
        issues.push({
          kind: MissingBaseUri,
          context: "relative URI-reference has no base URI",
        })
      Some(base) =>
        match validate_uri_reference(base) {
          Ok(info) if info.is_absolute() => ()
          _ =>
            issues.push({
              kind: InvalidBaseUri,
              context: "base URI must be an absolute URI-reference",
            })
        }
    }
  }
  issues
}

///|
fn is_problem_content_type(value : String) -> Bool {
  let normalized = ascii_lower(value)
  normalized == "application/problem+json" ||
  normalized.has_prefix("application/problem+json;")
}

///|
fn is_relative_optional(value : String?) -> Bool {
  match value {
    None => false
    Some(uri) =>
      match validate_uri_reference(uri) {
        Ok(info) => info.is_relative()
        Err(_) => false
      }
  }
}

///|
fn ascii_lower(value : String) -> String {
  let builder = StringBuilder()
  for c in value {
    if c >= 'A' && c <= 'Z' {
      builder.write_char((c.to_int() + 32).unsafe_to_char())
    } else {
      builder.write_char(c)
    }
  }
  builder.to_string()
}