///|
/// Immutable lib0 values. Arrays and objects use read-only storage so values
/// cannot be changed behind the CRDT's back after insertion.
pub(all) enum Any {
  Undefined
  Null
  Bool(Bool)
  Integer(Int64)
  Float(Double)
  String(String)
  Array(ReadOnlyArray[Any])
  Object(ReadOnlyArray[(String, Any)])
  Buffer(Bytes)
} derive(Debug)

///|
pub impl Eq for Any with fn equal(self, other) {
  fn number(integer : Int64, floating : Double) -> Bool {
    let converted = integer.to_double()
    if converted != floating {
      return false
    }
    // Match Rust's lossless integer conversion check, including its saturating
    // float-to-i64 conversion at the upper endpoint.
    if converted == 9223372036854775808.0 {
      return integer == 9223372036854775807L
    }
    converted.to_int64() == integer
  }
  match (self, other) {
    (Undefined, Undefined) | (Null, Null) => true
    (Bool(a), Bool(b)) => a == b
    (Integer(a), Integer(b)) => a == b
    (Float(a), Float(b)) => a == b
    (Integer(a), Float(b)) | (Float(b), Integer(a)) => number(a, b)
    (String(a), String(b)) => a == b
    (Buffer(a), Buffer(b)) => a == b
    (Array(a), Array(b)) => a == b
    (Object(a), Object(b)) => {
      let left = Map::from_array(a.iter().to_array())
      let right = Map::from_array(b.iter().to_array())
      left.length() == right.length() &&
      left.iter().all(fn(pair) { right.get(pair.0) == Some(pair.1) })
    }
    _ => false
  }
}

///|
pub extend Any with Eq::{equal, not_equal}

///|
pub extend Any with Debug::{to_repr}

///|
fn Writer::signed(self : Writer, number : Int64) -> Unit {
  let negative = number < 0L
  let mut magnitude = if negative {
    0UL - number.reinterpret_as_uint64()
  } else {
    number.reinterpret_as_uint64()
  }
  self.byte(
    (magnitude & 63UL).to_int() |
    (if negative { 64 } else { 0 }) |
    (if magnitude > 63UL { 128 } else { 0 }),
  )
  magnitude = magnitude >> 6
  while magnitude > 0UL {
    self.byte(
      (magnitude & 127UL).to_int() | (if magnitude > 127UL { 128 } else { 0 }),
    )
    magnitude = magnitude >> 7
  }
}

///|
fn Reader::signed(self : Reader) -> Int64 raise DecodeError {
  let first = self.byte()
  let negative = (first & 64) != 0
  let mut bits = (first & 63).to_uint64()
  if first >= 128 {
    for shift = 6; shift < 70; shift = shift + 7 {
      let b = self.byte()
      guard shift < 62 || b <= 3 else {
        raise InvalidData("signed integer overflow")
      }
      bits = bits | ((b & 127).to_uint64() << shift)
      if b < 128 {
        break
      }
    }
  }
  guard bits <=
    (if negative { 0x8000_0000_0000_0000UL } else { 0x7fff_ffff_ffff_ffffUL }) else {
    raise InvalidData("signed integer overflow")
  }
  (if negative { 0UL - bits } else { bits }).reinterpret_as_int64()
}

///|
fn Writer::big(self : Writer, bits : UInt64, width : Int32) -> Unit {
  for i = width - 1; i >= 0; i = i - 1 {
    self.byte(((bits >> (i * 8)) & 255UL).to_int())
  }
}

///|
fn Reader::big(self : Reader, width : Int32) -> UInt64 raise DecodeError {
  let mut bits = 0UL
  for _ in 0.. Unit {
  match value {
    Undefined => self.byte(127)
    Null => self.byte(126)
    Bool(b) => self.byte(if b { 120 } else { 121 })
    Integer(n) =>
      if n >= -2147483647L && n <= 2147483647L {
        self.byte(125)
        self.signed(n)
      } else {
        // BigInt preserves every bit of an Int64, including values outside the
        // exact floating-point range. This is a valid lib0 encoding even when a
        // producer might choose a shorter floating-point representation.
        self.byte(122)
        self.big(n.reinterpret_as_uint64(), 8)
      }
    Float(n) =>
      if Float::from_double(n).to_double() == n {
        self.byte(124)
        self.big(Float::from_double(n).reinterpret_as_uint().to_uint64(), 4)
      } else {
        self.byte(123)
        self.big(n.reinterpret_as_uint64(), 8)
      }
    String(s) => {
      self.byte(119)
      self.string(s)
    }
    Buffer(b) => {
      self.byte(116)
      self.buffer(b)
    }
    Array(values) => {
      self.byte(117)
      self.uint(values.length().to_uint64())
      for value in values {
        self.any(value)
      }
    }
    Object(entries) => {
      self.byte(118)
      self.uint(entries.length().to_uint64())
      for (key, value) in entries {
        self.string(key)
        self.any(value)
      }
    }
  }
}

///|
fn Reader::any(self : Reader, depth~ : Int32 = 0) -> Any raise DecodeError {
  guard depth <= 256 else {
    raise InvalidData("lib0 value nesting exceeds 256")
  }
  match self.byte() {
    127 => Undefined
    126 => Null
    125 => Integer(self.signed())
    124 =>
      Float(Float::reinterpret_from_uint(self.big(4).to_uint()).to_double())
    123 => Float(self.big(8).reinterpret_as_double())
    122 => Integer(self.big(8).reinterpret_as_int64())
    121 => Bool(false)
    120 => Bool(true)
    119 => String(self.string())
    116 => Buffer(self.buffer())
    117 => {
      let values = []
      let count = self.count()
      for _ in 0.. {
      let values : Map[String, Any] = Map([])
      let count = self.count()
      for _ in 0.. raise InvalidData("unknown lib0 value tag")
  }
}

///|
pub fn Any::encode(self : Any) -> Bytes {
  let writer : Writer = { bytes: [], }
  writer.any(self)
  writer.finish()
}

///|
pub fn Any::decode(bytes : Bytes) -> Any raise DecodeError {
  let reader : Reader = { bytes, offset: 0, }
  let value = reader.any()
  reader.finish()
  value
}

///|
pub fn Any::from_json(value : Json) -> Any {
  match value {
    Null => Null
    True => Bool(true)
    False => Bool(false)
    Number(n, ..) =>
      if n >= -9007199254740991.0 &&
        n <= 9007199254740991.0 &&
        n.to_int64().to_double() == n {
        Integer(n.to_int64())
      } else {
        Float(n)
      }
    String(s) => String(s)
    Array(values) =>
      Array(ReadOnlyArray::from_array(values.map(Any::from_json)[:]))
    Object(values) =>
      Object(
        ReadOnlyArray::from_array(
          values.to_array().map(fn(pair) { (pair.0, Any::from_json(pair.1)) })[:],
        ),
      )
  }
}

///|
/// JSON projection. Undefined becomes null and buffers become byte arrays.
/// Use the binary codec to preserve these distinctions and exact wide integers.
pub fn Any::to_json(self : Any) -> Json {
  match self {
    Undefined | Null => Json::null()
    Bool(b) => Json(b)
    Integer(n) => Json::number(n.to_double(), repr=n.to_string())
    Float(n) => Json(n)
    String(s) => Json(s)
    Buffer(b) => Json(b.to_array().map(fn(b) { b.to_int() }))
    Array(values) => Json(values.iter().map(Any::to_json).to_array())
    Object(entries) => {
      let values : Map[String, Json] = Map([])
      for (key, value) in entries {
        values[key] = value.to_json()
      }
      Json(values)
    }
  }
}