///|
/// The broad lexical shape of a JSON number, before IEEE-754 normalization.
pub enum NumberKind {
  Zero
  Integer
  Fraction
  Exponent
}

///|
pub(all) struct NumberInfo {
  original : String
  canonical : String
  kind : NumberKind
  negative_zero : Bool
}

///|
pub fn NumberKind::to_string(self : NumberKind) -> String {
  match self {
    Zero => "zero"
    Integer => "integer"
    Fraction => "fraction"
    Exponent => "exponent"
  }
}

///|
/// Parse and normalize one JSON number token while retaining useful audit
/// metadata about the original spelling.
pub fn inspect_number(token : String) -> Result[NumberInfo, CanonicalError] {
  let canonical = canonical_number(token)
  let normalized = match canonical {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let kind = if token.contains("e") || token.contains("E") {
    Exponent
  } else if token.contains(".") {
    Fraction
  } else if normalized == "0" {
    Zero
  } else {
    Integer
  }
  Ok({
    original: token,
    canonical: normalized,
    kind,
    negative_zero: token == "-0" ||
    token == "-0.0" ||
    token == "-0e0" ||
    token == "-0E0",
  })
}

///|
pub fn canonical_number_token(token : String) -> Result[String, CanonicalError] {
  match inspect_number(token) {
    Ok(info) => Ok(info.canonical)
    Err(error) => Err(error)
  }
}

///|
pub fn NumberInfo::summary(self : NumberInfo) -> String {
  "original=" +
  self.original +
  ",canonical=" +
  self.canonical +
  ",kind=" +
  self.kind.to_string() +
  ",negative_zero=" +
  (if self.negative_zero { "true" } else { "false" })
}

///|
pub fn NumberInfo::to_json(self : NumberInfo) -> String {
  "{\"original\":" +
  canonical_string(self.original) +
  ",\"canonical\":" +
  canonical_string(self.canonical) +
  ",\"kind\":" +
  canonical_string(self.kind.to_string()) +
  ",\"negative_zero\":" +
  (if self.negative_zero { "true" } else { "false" }) +
  "}"
}