///|
/// Validate invariants of a manually constructed JsonValue tree. Parsed input
/// already satisfies these checks, but the API protects callers constructing
/// values from another decoder or an in-memory transformation.
pub enum TreeIssueKind {
  DuplicateKey
  InvalidNumber
  DepthLimit
}

///|
pub(all) struct TreeIssue {
  path : String
  kind : TreeIssueKind
  detail : String
}

///|
pub fn TreeIssueKind::to_string(self : TreeIssueKind) -> String {
  match self {
    DuplicateKey => "duplicate-key"
    InvalidNumber => "invalid-number"
    DepthLimit => "depth-limit"
  }
}

///|
pub fn validate_tree(value : JsonValue, max_depth : Int) -> Array[TreeIssue] {
  let issues : Array[TreeIssue] = []
  validate_tree_at(value, "", 0, max_depth, issues)
  issues
}

///|
pub fn TreeIssue::to_text(self : TreeIssue) -> String {
  self.kind.to_string() + " " + self.path + ": " + self.detail
}

///|
fn validate_tree_at(
  value : JsonValue,
  path : String,
  depth : Int,
  max_depth : Int,
  issues : Array[TreeIssue],
) -> Unit {
  if depth > max_depth {
    issues.push({ path, kind: DepthLimit, detail: max_depth.to_string() })
    return
  }
  match value {
    Number(number) =>
      match canonical_number(number) {
        Ok(_) => ()
        Err(error) =>
          issues.push({ path, kind: InvalidNumber, detail: error.message() })
      }
    Array(values) => {
      let mut index = 0
      while index < values.length() {
        validate_tree_at(
          values[index],
          path + "/" + index.to_string(),
          depth + 1,
          max_depth,
          issues,
        )
        index = index + 1
      }
    }
    Object(entries) => {
      let seen : Array[String] = []
      for entry in entries {
        if contains_string(seen, entry.0) {
          issues.push({
            path: path + "/" + entry.0,
            kind: DuplicateKey,
            detail: entry.0,
          })
        } else {
          seen.push(entry.0)
        }
        validate_tree_at(
          entry.1,
          path + "/" + entry.0,
          depth + 1,
          max_depth,
          issues,
        )
      }
    }
    _ => ()
  }
}