///|
/// The exact value of a number literal.
pub(all) enum NumberValue {
  Integer(BigInt)
  Decimal(@decimal.Decimal)
} derive(Eq, Debug)

///|
/// A number literal: the lexeme as written and its exact value.
pub(all) struct NumberLiteral {
  text : String
  value : NumberValue
} derive(Eq, Debug)

///|
/// The value as an `Int64` when it is an integer that fits.
pub fn NumberLiteral::as_int64(self : NumberLiteral) -> Int64? {
  match self.value {
    Integer(b) =>
      if b >= BigInt::from_int64(-9223372036854775807L - 1L) &&
        b <= BigInt::from_int64(9223372036854775807L) {
        Some(b.to_int64())
      } else {
        None
      }
    Decimal(_) => None
  }
}

///|
/// The value as a `Double` (lossy for large integers and long decimals).
pub fn NumberLiteral::as_double(self : NumberLiteral) -> Double {
  match self.value {
    Integer(b) => @decimal.Decimal::from_bigint(b).to_double()
    Decimal(d) => d.to_double()
  }
}

///|
/// The Smithy node keywords: `true`, `false` and `null`.
pub(all) enum Keyword {
  True
  False
  Null
} derive(Eq, Debug, Hash)

///|
/// `NodeObjectKvp`: a key (an identifier or a quoted string) and a value.
pub(all) struct NodeKvp {
  key : Node[String]
  value : NodeValue
  span : @span.Span
} derive(Eq, Debug)

///|
/// `NodeValue`. Strings hold their decoded value; `ShapeId` is a bare
/// identifier or shape ID in value position (`@foo(bar)`).
pub(all) enum NodeValue {
  Array(ArrayView[NodeValue], @span.Span)
  Object(ArrayView[NodeKvp], @span.Span)
  Number(NumberLiteral, @span.Span)
  Keyword(Keyword, @span.Span)
  String(String, @span.Span)
  TextBlock(String, @span.Span)
  ShapeId(ShapeId, @span.Span)
} derive(Eq, Debug)

///|
/// The span of the value, whatever its shape.
pub fn NodeValue::span(self : NodeValue) -> @span.Span {
  match self {
    Array(_, s) | Object(_, s) | Number(_, s) | Keyword(_, s) => s
    String(_, s) | TextBlock(_, s) | ShapeId(_, s) => s
  }
}

///|
/// The string value of a `String` or `TextBlock`, else `None`.
pub fn NodeValue::as_string(self : NodeValue) -> String? {
  match self {
    String(s, _) | TextBlock(s, _) => Some(s)
    _ => None
  }
}

///|
/// The value of `key` in an object, else `None`.
pub fn NodeValue::get(self : NodeValue, key : String) -> NodeValue? {
  guard self is Object(kvps, _) else { return None }
  for kvp in kvps {
    if kvp.key.value == key {
      return Some(kvp.value)
    }
  }
  None
}