///|
/// Additional audit metrics beyond the structural counters in `JsonStats`.
pub(all) struct JsonProfile {
  stats : JsonStats
  key_count : Int
  unique_key_count : Int
  max_key_length : Int
  max_string_length : Int
  negative_zero_count : Int
  canonical_issue_count : Int
}

///|
priv struct ProfileState {
  keys : Array[String]
  mut max_key_length : Int
  mut max_string_length : Int
  mut negative_zero_count : Int
}

///|
pub fn profile_value(value : JsonValue) -> JsonProfile {
  let stats = JsonStats::empty()
  collect_stats(value, 0, stats)
  let state = {
    keys: [],
    max_key_length: 0,
    max_string_length: 0,
    negative_zero_count: 0,
  }
  collect_profile_values(value, state)
  {
    stats,
    key_count: state.keys.length(),
    unique_key_count: count_unique_keys(state.keys),
    max_key_length: state.max_key_length,
    max_string_length: state.max_string_length,
    negative_zero_count: state.negative_zero_count,
    canonical_issue_count: canonical_issues(value).length(),
  }
}

///|
pub fn profile_document(input : String) -> Result[JsonProfile, String] {
  let value = match parse(input) {
    Ok(value) => value
    Err(error) => return Err(error.message())
  }
  Ok(profile_value(value))
}

///|
fn collect_profile_values(value : JsonValue, state : ProfileState) -> Unit {
  match value {
    String(string) =>
      if string.length() > state.max_string_length {
        state.max_string_length = string.length()
      }
    Number(number) =>
      if number == "-0" ||
        number == "-0.0" ||
        number == "-0e0" ||
        number == "-0E0" {
        state.negative_zero_count = state.negative_zero_count + 1
      }
    Array(values) =>
      for child in values {
        collect_profile_values(child, state)
      }
    Object(entries) =>
      for entry in entries {
        state.keys.push(entry.0)
        if entry.0.length() > state.max_key_length {
          state.max_key_length = entry.0.length()
        }
        collect_profile_values(entry.1, state)
      }
    _ => ()
  }
}

///|
fn count_unique_keys(keys : Array[String]) -> Int {
  let unique : Array[String] = []
  for key in keys {
    if !contains_string(unique, key) {
      unique.push(key)
    }
  }
  unique.length()
}

///|
fn contains_string(values : Array[String], target : String) -> Bool {
  for value in values {
    if value == target {
      return true
    }
  }
  false
}

///|
pub fn JsonProfile::to_json(self : JsonProfile) -> String {
  "{\"stats\":" +
  self.stats.to_json() +
  ",\"key_count\":" +
  self.key_count.to_string() +
  ",\"unique_key_count\":" +
  self.unique_key_count.to_string() +
  ",\"max_key_length\":" +
  self.max_key_length.to_string() +
  ",\"max_string_length\":" +
  self.max_string_length.to_string() +
  ",\"negative_zero_count\":" +
  self.negative_zero_count.to_string() +
  ",\"canonical_issue_count\":" +
  self.canonical_issue_count.to_string() +
  "}"
}