// The Ion value model.
//
// Every Ion value is an ordered list of annotation symbol tokens plus one of
// the core value kinds. Keeping annotations on the value (instead of in every
// kind) lets the reader, the writer, and a schema validator reach them
// uniformly.

///|
/// The core kind of an Ion value, without annotations.
pub(all) enum IonValueKind {
  /// A null of the given type; `Null(IonType::Null)` is untyped `null`.
  Null(IonType)
  Bool(Bool)
  Int(BigInt)
  Float(Double)
  Decimal(Decimal)
  Timestamp(Timestamp)
  Symbol(SymbolToken)
  String(String)
  Clob(Bytes)
  Blob(Bytes)
  List(Array[IonValue])
  Sexp(Array[IonValue])
  Struct(Array[Field])
} derive(Debug)

///|
/// A field of an Ion struct: a symbol *token* name and a value.
///
/// The name is a token, not a value, so it cannot carry annotations.
pub struct Field {
  name : SymbolToken
  value : IonValue
} derive(Debug)

///|
/// An Ion value: zero or more annotations followed by the core value.
pub struct Ion {
  annotations : Array[SymbolToken]
  kind : IonValueKind
} derive(Debug)

///|
/// The core kind of this value.
pub fn IonValue::kind(self : IonValue) -> IonValueKind {
  self.kind
}

///|
/// The annotations on this value, in order.
pub fn IonValue::annotations(self : IonValue) -> Array[SymbolToken] {
  self.annotations
}

///|
/// Whether this value carries at least one annotation.
pub fn IonValue::has_annotations(self : IonValue) -> Bool {
  !self.annotations.is_empty()
}

///|
/// A copy of this value with `annotation` appended to its annotations.
pub fn IonValue::with_annotation(
  self : IonValue,
  annotation : SymbolToken,
) -> IonValue {
  { annotations: [..self.annotations, annotation], kind: self.kind, }
}

///|
/// A copy of this value with the given annotations, replacing any existing
/// ones.
pub fn IonValue::with_annotations(
  self : IonValue,
  annotations : Array[SymbolToken],
) -> IonValue {
  { annotations, kind: self.kind, }
}

///|
/// The Ion type of this value. For a typed null such as `null.int` this is the
/// null's type (`int`), not `null`.
pub fn IonValue::ion_type(self : IonValue) -> IonType {
  match self.kind {
    IonValueKind::Null(t) => t
    IonValueKind::Bool(_) => IonType::Bool
    IonValueKind::Int(_) => IonType::Int
    IonValueKind::Float(_) => IonType::Float
    IonValueKind::Decimal(_) => IonType::Decimal
    IonValueKind::Timestamp(_) => IonType::Timestamp
    IonValueKind::Symbol(_) => IonType::Symbol
    IonValueKind::String(_) => IonType::String
    IonValueKind::Clob(_) => IonType::Clob
    IonValueKind::Blob(_) => IonType::Blob
    IonValueKind::List(_) => IonType::List
    IonValueKind::Sexp(_) => IonType::Sexp
    IonValueKind::Struct(_) => IonType::Struct
  }
}

///|
/// Whether this value is a null of any type.
pub fn IonValue::is_null(self : IonValue) -> Bool {
  self.kind is IonValueKind::Null(_)
}

///|
/// Whether this value is the untyped `null` (`null.null`).
pub fn IonValue::is_untyped_null(self : IonValue) -> Bool {
  match self.kind {
    IonValueKind::Null(IonType::Null) => true
    _ => false
  }
}

///|
/// The untyped `null` value.
pub fn IonValue::untyped_null() -> IonValue {
  Ion(IonValueKind::Null(IonType::Null))
}

///|
/// A typed null such as `null.int`.
pub fn IonValue::typed_null(t : IonType) -> IonValue {
  Ion(IonValueKind::Null(t))
}

///|
/// A `Bool` value.
pub fn IonValue::bool(value : Bool) -> IonValue {
  Ion(IonValueKind::Bool(value))
}

///|
/// An `int` value from an `Int`.
pub fn IonValue::int(value : Int32) -> IonValue {
  Ion(IonValueKind::Int(BigInt::from_int(value)))
}

///|
/// An `int` value from an arbitrary-precision `BigInt`.
pub fn IonValue::big_int(value : BigInt) -> IonValue {
  Ion(IonValueKind::Int(value))
}

///|
/// A `float` value.
pub fn IonValue::float(value : Double) -> IonValue {
  Ion(IonValueKind::Float(value))
}

///|
/// A `decimal` value.
pub fn IonValue::decimal(value : Decimal) -> IonValue {
  Ion(IonValueKind::Decimal(value))
}

///|
/// A `timestamp` value.
pub fn IonValue::timestamp(value : Timestamp) -> IonValue {
  Ion(IonValueKind::Timestamp(value))
}

///|
/// A `symbol` value with known text.
pub fn IonValue::symbol(value : String) -> IonValue {
  Ion(IonValueKind::Symbol(SymbolToken::SymbolToken(value)))
}

///|
/// A `symbol` value from a symbol token.
pub fn IonValue::symbol_token(value : SymbolToken) -> IonValue {
  Ion(IonValueKind::Symbol(value))
}

///|
/// A `string` value.
pub fn IonValue::string(value : String) -> IonValue {
  Ion(IonValueKind::String(value))
}

///|
/// A `clob` value.
pub fn IonValue::clob(value : Bytes) -> IonValue {
  Ion(IonValueKind::Clob(value))
}

///|
/// A `blob` value.
pub fn IonValue::blob(value : Bytes) -> IonValue {
  Ion(IonValueKind::Blob(value))
}

///|
/// A `list` value.
pub fn IonValue::list(values : Array[IonValue]) -> IonValue {
  Ion(IonValueKind::List(values))
}

///|
/// An `sexp` value.
pub fn IonValue::sexp(values : Array[IonValue]) -> IonValue {
  Ion(IonValueKind::Sexp(values))
}

///|
/// A `struct` value.
pub fn IonValue::from_fields(fields : Array[Field]) -> IonValue {
  Ion(IonValueKind::Struct(fields))
}

///|
/// Build a struct field from a name literal.
#as_free_fn(field)
#alias(new, deprecated="Use `@ion.field(...)` instead")
pub fn Field::Field(name : String, value : IonValue) -> Field {
  { name: SymbolToken::SymbolToken(name), value, }
}

///|
/// Build a struct field from a name symbol token.
pub fn Field::from_token(name : SymbolToken, value : IonValue) -> Field {
  { name, value, }
}

///|
/// The field's name token.
pub fn Field::name(self : Field) -> SymbolToken {
  self.name
}

///|
/// The field's value.
pub fn Field::value(self : Field) -> IonValue {
  self.value
}