///|
/// How a caller should respond when valid JSON is not already canonical.
pub enum CanonicalPolicy {
  RequireCanonical
  Normalize
  ReportOnly
}

///|
pub fn policy_require() -> CanonicalPolicy {
  CanonicalPolicy::RequireCanonical
}

///|
pub fn policy_normalize() -> CanonicalPolicy {
  CanonicalPolicy::Normalize
}

///|
pub fn policy_report() -> CanonicalPolicy {
  CanonicalPolicy::ReportOnly
}

///|
pub fn CanonicalPolicy::to_string(self : CanonicalPolicy) -> String {
  match self {
    RequireCanonical => "require-canonical"
    Normalize => "normalize"
    ReportOnly => "report-only"
  }
}

///|
pub fn CanonicalPolicy::to_json(self : CanonicalPolicy) -> String {
  canonical_string(self.to_string())
}

///|
pub(all) struct PolicyDecision {
  accepted : Bool
  canonical : String?
  digest : String?
  issues : Array[CanonicalIssue]
  error : String?
}

///|
/// Evaluate syntax, resource limits, canonical form, and semantic issues as a
/// single policy decision suitable for a CI gate.
pub fn evaluate_policy(
  input : String,
  limits : DocumentLimits,
  policy : CanonicalPolicy,
) -> PolicyDecision {
  let value = match parse_with_limits(input, limits) {
    Ok(value) => value
    Err(error) =>
      return {
        accepted: false,
        canonical: None,
        digest: None,
        issues: [],
        error: Some(error),
      }
  }
  let canonical = match canonicalize(value) {
    Ok(canonical) => canonical
    Err(error) =>
      return {
        accepted: false,
        canonical: None,
        digest: None,
        issues: [],
        error: Some(error.message()),
      }
  }
  let issues = canonical_issues(value)
  let digest = match canonical_digest(value) {
    Ok(digest) => digest
    Err(error) =>
      return {
        accepted: false,
        canonical: Some(canonical),
        digest: None,
        issues,
        error: Some(error.message()),
      }
  }
  let exact = check_canonical(input).canonical
  let accepted = match policy {
    RequireCanonical => exact
    Normalize | ReportOnly => true
  }
  {
    accepted,
    canonical: Some(canonical),
    digest: Some(digest),
    issues,
    error: if accepted || policy is ReportOnly {
      None
    } else {
      Some("document is valid JSON but not in canonical form")
    },
  }
}

///|
pub fn enforce_canonical(
  input : String,
  limits : DocumentLimits,
) -> Result[String, String] {
  let decision = evaluate_policy(input, limits, RequireCanonical)
  if !decision.accepted {
    return Err(
      match decision.error {
        Some(error) => error
        None => "canonical policy rejected document"
      },
    )
  }
  match decision.canonical {
    Some(canonical) => Ok(canonical)
    None => Err("canonical policy produced no output")
  }
}

///|
pub fn PolicyDecision::to_text(self : PolicyDecision) -> String {
  let mut output = "accepted: " +
    (if self.accepted { "true" } else { "false" }) +
    "\n"
  match self.canonical {
    Some(canonical) => output = output + "canonical: " + canonical + "\n"
    None => ()
  }
  match self.digest {
    Some(digest) => output = output + "sha256: " + digest + "\n"
    None => ()
  }
  if self.issues.length() > 0 {
    output = output + "issues: " + self.issues.length().to_string() + "\n"
  }
  match self.error {
    Some(error) => output = output + "error: " + error + "\n"
    None => ()
  }
  output
}