///|
/// A self-describing value: the data model made concrete.
///
/// `Serialize` is polymorphic in its serializer and therefore has no trait
/// object form, so a heterogeneous collection cannot be built out of
/// `Serialize` values directly. `Value` fills that gap, and doubles as the
/// result type of `Deserializer::deserialize_any`.
///
/// Strings are held as UTF-8 `BytesView`s, not as MoonBit `String`s. The data model
/// describes what a document *contains*, and a document contains text in some
/// encoding — turning that into UTF-16 is a service to one particular consumer,
/// so the consumer that wants it pays for it. It also means a document can be
/// read and written again without ever being transcoded.
///
/// Structs and enum variants collapse into `Map` here, using serde's default
/// externally-tagged representation: a unit variant becomes `Str(name)`, and
/// any variant with a payload becomes a single-entry `Map` from the variant
/// name to its payload.
///
/// `Seq` and `Map` hold `ReadOnlyArray`, which makes a value a tree by
/// construction: a cycle needs an already-built node to be pointed back at one
/// of its ancestors, and nothing reachable from a `Value` can be written to
/// after it is built. That is not only about traversals terminating. The
/// native and LLVM backends are reference-counted, so a cycle would be a leak
/// that no collector ever comes to clear. It also means a value never aliases
/// the array it was built from.
pub(all) enum Value {
  Null
  Boolean(Bool)
  /// Any signed integer in the data model, widened.
  Int(Int64)
  /// Any unsigned integer in the data model, widened.
  UInt(UInt64)
  Double(Double)
  Str(BytesView)
  Bytes(Bytes)
  Seq(ReadOnlyArray[Value])
  /// Order-preserving, and keys need not be strings.
  Map(ReadOnlyArray[(Value, Value)])
} derive(Eq, Debug)

///|
/// A short name for this value's shape, used in `InvalidType` messages.
pub fn Value::type_name(self : Value) -> String {
  match self {
    Null => "unit"
    Boolean(_) => "boolean"
    Int(_) => "integer"
    UInt(_) => "unsigned integer"
    Double(_) => "float"
    Str(_) => "string"
    Bytes(_) => "byte string"
    Seq(_) => "sequence"
    Map(_) => "map"
  }
}

///|
/// Looks up a key in a `Map` value. Returns `None` for any other shape.
///
/// Takes a `String` because that is how a key is written in source; it is
/// encoded once, not once per entry.
pub fn Value::get(self : Value, key : String) -> Value? {
  guard self is Map(entries) else { return None }
  let needle = @utf8.encode(key)[:]
  for entry in entries {
    if entry.0 is Str(k) && k == needle {
      return Some(entry.1)
    }
  }
  None
}

///|
/// The text of a `Str`, decoded. `None` for any other shape.
///
/// The explicit step from the data model's bytes to a MoonBit `String`.
pub fn Value::text(self : Value) -> String? {
  guard self is Str(bytes) else { return None }
  Some(@utf8.decode(bytes)) catch {
    _ => None
  }
}

///|
/// Copies every borrowed string, so the value no longer keeps the document it
/// was parsed from alive.
///
/// Parsing borrows by default, which is free and right when a whole document
/// is being processed. Keeping a handful of fields out of a large one is the
/// case that wants this: without it, a five-byte key can hold ten megabytes
/// reachable.
pub fn Value::detach(self : Value) -> Value {
  match self {
    Str(view) => Str(view.to_owned()[:])
    Seq(items) => Seq(items.map(v => v.detach()))
    Map(entries) => Map(entries.map(e => (e.0.detach(), e.1.detach())))
    other => other
  }
}

///|
pub impl Show for Value with fn output(self, logger) {
  match self {
    Null => logger.write_string("null")
    Boolean(b) => logger.write_string(b.to_string())
    Int(i) => logger.write_string(i.to_string())
    UInt(u) => logger.write_string(u.to_string())
    Double(d) => logger.write_string(d.to_string())
    Str(s) => {
      logger.write_string("\"")
      logger.write_string(@utf8.decode_lossy(s))
      logger.write_string("\"")
    }
    Bytes(b) => {
      logger.write_string("b\"")
      for byte in b {
        logger.write_string(byte.to_hex())
      }
      logger.write_string("\"")
    }
    Seq(items) => {
      logger.write_string("[")
      for i, item in items {
        if i > 0 {
          logger.write_string(", ")
        }
        Show::output(item, logger)
      }
      logger.write_string("]")
    }
    Map(entries) => {
      logger.write_string("{")
      for i, entry in entries {
        if i > 0 {
          logger.write_string(", ")
        }
        Show::output(entry.0, logger)
        logger.write_string(": ")
        Show::output(entry.1, logger)
      }
      logger.write_string("}")
    }
  }
}