///|
fn problem_from_json(
  value : Json,
  stage : String,
) -> AcmeProblem raise AcmeError {
  let fields = object_at(value, stage)
  let subproblems : Array[AcmeSubproblem] = []
  match fields.get("subproblems") {
    Some(Array(values)) =>
      for item in values {
        let sub = object_at(item, "\{stage}.subproblems")
        let identifier = match sub.get("identifier") {
          Some(value) => Some(decode_identifier(value, "\{stage}.identifier"))
          None => None
        }
        subproblems.push({
          kind: required_string(sub, "type", "\{stage}.subproblem"),
          detail: match optional_string(sub, "detail", "\{stage}.subproblem") {
            Some(text) => text
            None => ""
          },
          identifier,
        })
      }
    Some(_) => raise malformed(stage, "field `subproblems` must be an array")
    None => ()
  }
  let status = match fields.get("status") {
    Some(Number(number, ..)) => Some(number.to_int())
    Some(Null) | None => None
    Some(_) => raise malformed(stage, "field `status` must be a number")
  }
  AcmeProblem::{
    kind: required_string(fields, "type", stage),
    title: optional_string(fields, "title", stage),
    detail: match optional_string(fields, "detail", stage) {
      Some(text) => text
      None => ""
    },
    status,
    instance: optional_string(fields, "instance", stage),
    subproblems,
  }
}

///|
pub fn AcmeProblem::decode(input : String) -> AcmeProblem raise AcmeError {
  problem_from_json(parse_json(input, "problem"), "problem")
}

///|
fn optional_problem(
  fields : Map[String, Json],
  name : String,
  stage : String,
) -> AcmeProblem? raise AcmeError {
  match fields.get(name) {
    Some(Null) | None => None
    Some(value) => Some(problem_from_json(value, "\{stage}.\{name}"))
  }
}

///|
fn challenge_from_json(
  value : Json,
  stage : String,
) -> Challenge raise AcmeError {
  let fields = object_at(value, stage)
  Challenge::{
    kind: ChallengeKind::parse(required_string(fields, "type", stage)),
    url: required_string(fields, "url", stage),
    status: ResourceStatus::parse(required_string(fields, "status", stage)),
    token: required_string(fields, "token", stage),
    validated: optional_string(fields, "validated", stage),
    problem: optional_problem(fields, "error", stage),
  }
}

///|
pub fn Challenge::decode(input : String) -> Challenge raise AcmeError {
  challenge_from_json(parse_json(input, "challenge"), "challenge")
}

///|
pub fn Authorization::decode(
  input : String,
  url~ : String,
) -> Authorization raise AcmeError {
  let fields = object_at(parse_json(input, "authorization"), "authorization")
  let challenges : Array[Challenge] = []
  match fields.get("challenges") {
    Some(Array(values)) =>
      for value in values {
        challenges.push(challenge_from_json(value, "authorization.challenges"))
      }
    Some(_) =>
      raise malformed("authorization", "field `challenges` must be an array")
    None => raise malformed("authorization", "missing array field `challenges`")
  }
  Authorization::{
    url,
    identifier: match fields.get("identifier") {
      Some(value) => decode_identifier(value, "authorization.identifier")
      None => raise malformed("authorization", "missing `identifier`")
    },
    status: ResourceStatus::parse(
      required_string(fields, "status", "authorization"),
    ),
    expires: optional_string(fields, "expires", "authorization"),
    wildcard: optional_bool(fields, "wildcard", "authorization", false),
    challenges,
  }
}

///|
pub fn Authorization::find_challenge(
  self : Authorization,
  kind : ChallengeKind,
) -> Challenge? {
  for challenge in self.challenges {
    if challenge.kind == kind {
      return Some(challenge)
    }
  }
  None
}