///|
/// Result of checking whether a JSON document is already in JCS form.
pub(all) struct CanonicalCheck {
  valid_json : Bool
  canonical : Bool
  normalized : String?
  reason : String?
}

///|
/// Check syntax and compare the source byte-for-byte with its canonical form.
/// This deliberately reports non-canonical but valid JSON separately from
/// malformed JSON so formatters and CI can choose different policies.
pub fn check_canonical(input : String) -> CanonicalCheck {
  let value = match parse(input) {
    Ok(value) => value
    Err(error) =>
      return {
        valid_json: false,
        canonical: false,
        normalized: None,
        reason: Some(error.message()),
      }
  }
  let normalized = match canonicalize(value) {
    Ok(serialized) => serialized
    Err(error) =>
      return {
        valid_json: true,
        canonical: false,
        normalized: None,
        reason: Some(error.message()),
      }
  }
  if normalized == input {
    {
      valid_json: true,
      canonical: true,
      normalized: Some(normalized),
      reason: None,
    }
  } else {
    {
      valid_json: true,
      canonical: false,
      normalized: Some(normalized),
      reason: Some("source differs from its RFC 8785 canonical form"),
    }
  }
}

///|
pub fn is_canonical_json(input : String) -> Bool {
  check_canonical(input).canonical
}

///|
/// Canonicalize a complete JSON document in one call.
pub fn canonicalize_json(input : String) -> Result[String, String] {
  let check = check_canonical(input)
  if !check.valid_json {
    return Err(
      match check.reason {
        Some(reason) => reason
        None => "invalid JSON"
      },
    )
  }
  match check.normalized {
    Some(normalized) => Ok(normalized)
    None =>
      Err(
        match check.reason {
          Some(reason) => reason
          None => "canonicalization failed"
        },
      )
  }
}

///|
/// Canonicalize untrusted input only after enforcing the caller's resource
/// budget. The parsed tree is reused, so the input is not parsed twice.
pub fn canonicalize_json_with_limits(
  input : String,
  limits : DocumentLimits,
) -> Result[String, String] {
  let value = match parse_with_limits(input, limits) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match canonicalize(value) {
    Ok(canonical) => Ok(canonical)
    Err(error) => Err(error.message())
  }
}