///|
/// PackStream — the binary serialization format of the Neo4j Bolt protocol.
///
/// This module is a pure, dependency-free implementation of the PackStream
/// codec: a value model ([`PackStreamValue`]) plus an encoder
/// ([`packstream_encode`]) and a decoder ([`packstream_decode`]). No network,
/// no IO — everything here is unit-testable in isolation.
///
/// Marker summary (see https://neo4j.com/docs/bolt/current/packstream/):
///
/// * `0xC0` Null, `0xC1` Float64, `0xC2` False, `0xC3` True
/// * Integer: a single signed byte `0x00..0x7F` / `0xF0..0xFF` (tiny, -16..127),
///   then `0xC8` / `0xC9` / `0xCA` / `0xCB` (INT_8/16/32/64)
/// * String: `0x80..0x8F` (tiny, 0-15 bytes) or `0xD0` / `0xD1` / `0xD2` (8/16/32)
/// * List: `0x90..0x9F` or `0xD4` / `0xD5` / `0xD6`
/// * Map: `0xA0..0xAF` or `0xD8` / `0xD9` / `0xDA`
/// * Struct: `0xB0..0xBF` or `0xDC` / `0xDD` (tag byte + fields)
/// * Bytes: `0xCC` / `0xCD` / `0xCE` (8/16/32 length)

///|
/// A PackStream value. Maps are kept as an ordered list of `(String, value)`
/// pairs so insertion order is preserved exactly as sent on the wire.
pub enum PackStreamValue {
  Null
  Bool(Bool)
  Int(Int64)
  Float(Double)
  Str(String)
  List(Array[PackStreamValue])
  Map(Array[(String, PackStreamValue)])
  Struct(Int, Array[PackStreamValue])
  Bytes(Bytes)
} derive(Eq, @debug.Debug)

///|
pub fn PackStreamValue::null() -> PackStreamValue {
  PackStreamValue::Null
}

///|
pub fn PackStreamValue::bool(b : Bool) -> PackStreamValue {
  PackStreamValue::Bool(b)
}

///|
pub fn PackStreamValue::int(i : Int64) -> PackStreamValue {
  PackStreamValue::Int(i)
}

///|
/// Convenience constructor for a 32-bit integer value.
pub fn PackStreamValue::int32(i : Int) -> PackStreamValue {
  PackStreamValue::Int(i.to_int64())
}

///|
pub fn PackStreamValue::float(d : Double) -> PackStreamValue {
  PackStreamValue::Float(d)
}

///|
pub fn PackStreamValue::str(s : String) -> PackStreamValue {
  PackStreamValue::Str(s)
}

///|
pub fn PackStreamValue::list(items : Array[PackStreamValue]) -> PackStreamValue {
  PackStreamValue::List(items)
}

///|
pub fn PackStreamValue::map(
  entries : Array[(String, PackStreamValue)],
) -> PackStreamValue {
  PackStreamValue::Map(entries)
}

///|
/// `tag` is the single-byte struct signature; it must be in `0..=255`.
pub fn PackStreamValue::struct_(
  tag : Int,
  fields : Array[PackStreamValue],
) -> PackStreamValue {
  PackStreamValue::Struct(tag, fields)
}

///|
pub fn PackStreamValue::bytes(b : Bytes) -> PackStreamValue {
  PackStreamValue::Bytes(b)
}

///|
/// Render a value in a human-readable form: strings unquoted, numbers and
/// booleans as literals, lists/maps rendered recursively, and structs/bytes as
/// a short summary. This [`Show`] instance is what `println` uses.
pub impl Show for PackStreamValue with fn to_string(self) -> String {
  match self {
    Null => "null"
    Bool(b) => if b { "true" } else { "false" }
    Int(i) => i.to_string()
    Float(f) => f.to_string()
    Str(s) => s
    List(items) => {
      let parts = []
      for item in items {
        parts.push(item.to_string())
      }
      "[" + parts.join(", ") + "]"
    }
    Map(entries) => {
      let parts = []
      for (k, v) in entries {
        parts.push(k + ": " + v.to_string())
      }
      "{" + parts.join(", ") + "}"
    }
    Struct(tag, _) => "struct(" + tag.to_string() + ")"
    Bytes(b) => "bytes(" + b.length().to_string() + ")"
  }
}

///|
/// Promote `to_string` to a regular method so `value.to_string()` needs no
/// explicit `Show` qualification.
pub extend PackStreamValue with Show::{to_string}

///|
/// Encode a [`PackStreamValue`] to its PackStream byte representation.
pub fn packstream_encode(value : PackStreamValue) -> Bytes {
  let w = Writer::new()
  w.write(value)
  w.to_bytes()
}

///|
/// Decode a single PackStream value from `bytes`.
///
/// Returns `None` when the bytes do not hold exactly one well-formed value
/// (truncated input, an unknown marker, or trailing garbage).
pub fn packstream_decode(bytes : Bytes) -> PackStreamValue? {
  let r = Reader::new(bytes)
  match r.read() {
    None => None
    Some(value) => if r.eof() { Some(value) } else { None }
  }
}

///|
/// Write `value` into `buf`.
fn write_value(buf : Buffer, value : PackStreamValue) -> Unit {
  match value {
    Null => buf.write_byte(b'\xc0')
    Bool(b) => buf.write_byte(if b { b'\xc3' } else { b'\xc2' })
    Int(i) => write_int(buf, i)
    Float(d) => {
      buf.write_byte(b'\xc1')
      buf.write_double_be(d)
    }
    Str(s) => write_string(buf, s)
    List(items) => write_list(buf, items)
    Map(entries) => write_map(buf, entries)
    Struct(tag, fields) => write_struct(buf, tag, fields)
    Bytes(b) => write_bytes_value(buf, b)
  }
}

///|
/// Write a 64-bit integer using the most compact marker.
fn write_int(buf : Buffer, i : Int64) -> Unit {
  if i >= -16L && i <= 127L {
    buf.write_byte(i.to_byte())
  } else if i >= -128L && i <= 127L {
    buf.write_byte(b'\xc8')
    buf.write_byte(i.to_byte())
  } else if i >= -32768L && i <= 32767L {
    buf.write_byte(b'\xc9')
    buf.write_int16_be(Int16::from_int64(i))
  } else if i >= -2147483648L && i <= 2147483647L {
    buf.write_byte(b'\xca')
    buf.write_int_be(i.to_int())
  } else {
    buf.write_byte(b'\xcb')
    buf.write_int64_be(i)
  }
}

///|
/// Write the tiny/8/16/32-bit length marker shared by String, List and Map.
fn write_sized_marker(
  buf : Buffer,
  n : Int,
  tiny : Int,
  m8 : Int,
  m16 : Int,
  m32 : Int,
) -> Unit {
  if n <= 15 {
    buf.write_byte((tiny + n).to_byte())
  } else if n <= 255 {
    buf.write_byte(m8.to_byte())
    buf.write_byte(n.to_byte())
  } else if n <= 65535 {
    buf.write_byte(m16.to_byte())
    buf.write_int16_be(Int16::from_int(n))
  } else {
    buf.write_byte(m32.to_byte())
    buf.write_int_be(n)
  }
}

///|
/// Write a Bytes length marker (`0xCC` / `0xCD` / `0xCE`; Bytes has no tiny form).
fn write_bytes_marker(buf : Buffer, n : Int) -> Unit {
  if n <= 255 {
    buf.write_byte(b'\xcc')
    buf.write_byte(n.to_byte())
  } else if n <= 65535 {
    buf.write_byte(b'\xcd')
    buf.write_int16_be(Int16::from_int(n))
  } else {
    buf.write_byte(b'\xce')
    buf.write_int_be(n)
  }
}

///|
/// Write a Struct length marker (`0xB0..0xBF` / `0xDC` / `0xDD`).
fn write_struct_marker(buf : Buffer, n : Int) -> Unit {
  if n <= 15 {
    buf.write_byte((0xB0 + n).to_byte())
  } else if n <= 255 {
    buf.write_byte(b'\xdc')
    buf.write_byte(n.to_byte())
  } else {
    buf.write_byte(b'\xdd')
    buf.write_int16_be(Int16::from_int(n))
  }
}

///|
fn write_string(buf : Buffer, s : String) -> Unit {
  let utf8 = @utf8.encode(s.to_string_view())
  write_sized_marker(buf, utf8.length(), 0x80, 0xD0, 0xD1, 0xD2)
  buf.write_bytes(utf8.exact_view())
}

///|
fn write_list(buf : Buffer, items : Array[PackStreamValue]) -> Unit {
  write_sized_marker(buf, items.length(), 0x90, 0xD4, 0xD5, 0xD6)
  for item in items {
    write_value(buf, item)
  }
}

///|
fn write_map(buf : Buffer, entries : Array[(String, PackStreamValue)]) -> Unit {
  write_sized_marker(buf, entries.length(), 0xA0, 0xD8, 0xD9, 0xDA)
  for (k, v) in entries {
    write_string(buf, k)
    write_value(buf, v)
  }
}

///|
fn write_struct(
  buf : Buffer,
  tag : Int,
  fields : Array[PackStreamValue],
) -> Unit {
  write_struct_marker(buf, fields.length())
  buf.write_byte(tag.to_byte())
  for field in fields {
    write_value(buf, field)
  }
}

///|
fn write_bytes_value(buf : Buffer, b : Bytes) -> Unit {
  write_bytes_marker(buf, b.length())
  buf.write_bytes(b.exact_view())
}

///|
/// Read a single value starting at `pos`; returns the value and the position
/// just past it, or `None` if the bytes are malformed or truncated.
fn read_value(bytes : Bytes, pos : Int) -> (PackStreamValue, Int)? {
  if pos >= bytes.length() {
    return None
  }
  let marker = bytes[pos].to_int()
  let p = pos + 1
  if marker == 0xC0 {
    Some((PackStreamValue::Null, p))
  } else if marker == 0xC2 {
    Some((PackStreamValue::Bool(false), p))
  } else if marker == 0xC3 {
    Some((PackStreamValue::Bool(true), p))
  } else if marker == 0xC1 {
    if p + 8 > bytes.length() {
      None
    } else {
      Some((PackStreamValue::Float(read_double_be(bytes, p)), p + 8))
    }
  } else if marker <= 0x7F || marker >= 0xF0 {
    Some((PackStreamValue::Int(read_tiny_int(marker)), p))
  } else if marker >= 0x80 && marker <= 0x8F {
    read_string(bytes, p, marker - 0x80)
  } else if marker >= 0x90 && marker <= 0x9F {
    read_list(bytes, p, marker - 0x90)
  } else if marker >= 0xA0 && marker <= 0xAF {
    read_map(bytes, p, marker - 0xA0)
  } else if marker >= 0xB0 && marker <= 0xBF {
    read_struct(bytes, p, marker - 0xB0)
  } else if marker == 0xC8 {
    read_int_sized(bytes, p, 1)
  } else if marker == 0xC9 {
    read_int_sized(bytes, p, 2)
  } else if marker == 0xCA {
    read_int_sized(bytes, p, 4)
  } else if marker == 0xCB {
    read_int_sized(bytes, p, 8)
  } else if marker >= 0xCC && marker <= 0xCE {
    match read_sized_header(bytes, marker, p) {
      None => None
      Some((n, data)) => read_bytes_value(bytes, data, n)
    }
  } else if marker >= 0xD0 && marker <= 0xD2 {
    match read_sized_header(bytes, marker, p) {
      None => None
      Some((n, data)) => read_string(bytes, data, n)
    }
  } else if marker >= 0xD4 && marker <= 0xD6 {
    match read_sized_header(bytes, marker, p) {
      None => None
      Some((n, data)) => read_list(bytes, data, n)
    }
  } else if marker >= 0xD8 && marker <= 0xDA {
    match read_sized_header(bytes, marker, p) {
      None => None
      Some((n, data)) => read_map(bytes, data, n)
    }
  } else if marker == 0xDC || marker == 0xDD {
    match read_sized_header(bytes, marker, p) {
      None => None
      Some((n, data)) => read_struct(bytes, data, n)
    }
  } else {
    None
  }
}

///|
/// Read the length field of a sized marker (1/2/4 bytes depending on the
/// marker), returning `(length, data_start)`.
fn read_sized_header(bytes : Bytes, marker : Int, pos : Int) -> (Int, Int)? {
  if marker == 0xCC ||
    marker == 0xD0 ||
    marker == 0xD4 ||
    marker == 0xD8 ||
    marker == 0xDC {
    match read_len8(bytes, pos) {
      None => None
      Some(n) => Some((n, pos + 1))
    }
  } else if marker == 0xCD ||
    marker == 0xD1 ||
    marker == 0xD5 ||
    marker == 0xD9 ||
    marker == 0xDD {
    match read_len16(bytes, pos) {
      None => None
      Some(n) => Some((n, pos + 2))
    }
  } else {
    match read_len32(bytes, pos) {
      None => None
      Some(n) => Some((n, pos + 4))
    }
  }
}

///|
fn read_len8(bytes : Bytes, pos : Int) -> Int? {
  if pos >= bytes.length() {
    None
  } else {
    Some(bytes[pos].to_int())
  }
}

///|
fn read_len16(bytes : Bytes, pos : Int) -> Int? {
  if pos + 2 > bytes.length() {
    None
  } else {
    Some(bytes[pos].to_int() * 256 + bytes[pos + 1].to_int())
  }
}

///|
fn read_len32(bytes : Bytes, pos : Int) -> Int? {
  if pos + 4 > bytes.length() {
    return None
  }
  let n : Int64 = (bytes[pos].to_int64() << 24)
    .lor(bytes[pos + 1].to_int64() << 16)
    .lor(bytes[pos + 2].to_int64() << 8)
    .lor(bytes[pos + 3].to_int64())
  if n > 2147483647L {
    None
  } else {
    Some(n.to_int())
  }
}

///|
/// Decode a tiny integer marker byte (`0x00..0x7F` / `0xF0..0xFF`) into an `Int64`.
fn read_tiny_int(marker : Int) -> Int64 {
  if marker >= 0xF0 {
    (marker - 256).to_int64()
  } else {
    marker.to_int64()
  }
}

///|
/// Read an `n`-byte (`n` in 1/2/4/8) big-endian signed integer, sign-extended.
fn read_int_be(bytes : Bytes, pos : Int, n : Int) -> Int64 {
  let u = read_uint_be(bytes, pos, n)
  let shift : Int = (8 - n) * 8
  (u << shift).reinterpret_as_int64() >> shift
}

///|
/// Assemble `n` bytes at `pos` into a big-endian `UInt64` (zero-padded above).
fn read_uint_be(bytes : Bytes, pos : Int, n : Int) -> UInt64 {
  let mut u : UInt64 = 0UL
  for i in 0.. Double {
  read_uint_be(bytes, pos, 8).reinterpret_as_double()
}

///|
fn read_int_sized(bytes : Bytes, pos : Int, n : Int) -> (PackStreamValue, Int)? {
  if pos + n > bytes.length() {
    None
  } else {
    Some((PackStreamValue::Int(read_int_be(bytes, pos, n)), pos + n))
  }
}

///|
fn read_string(bytes : Bytes, pos : Int, n : Int) -> (PackStreamValue, Int)? {
  if n < 0 || n > bytes.length() - pos {
    None
  } else {
    let view = bytes.exact_view(start=pos, end=pos + n)
    try @utf8.decode(view) catch {
      _ => None
    } noraise {
      s => Some((PackStreamValue::Str(s), pos + n))
    }
  }
}

///|
fn read_bytes_value(
  bytes : Bytes,
  pos : Int,
  n : Int,
) -> (PackStreamValue, Int)? {
  if n < 0 || n > bytes.length() - pos {
    None
  } else {
    Some(
      (
        PackStreamValue::Bytes(
          bytes.exact_view(start=pos, end=pos + n).to_owned(),
        ),
        pos + n,
      ),
    )
  }
}

///|
fn read_list(bytes : Bytes, pos : Int, n : Int) -> (PackStreamValue, Int)? {
  if n < 0 {
    return None
  }
  let items : Array[PackStreamValue] = []
  let mut p = pos
  for _ in 0.. return None
      Some((v, np)) => {
        items.push(v)
        p = np
      }
    }
  }
  Some((PackStreamValue::List(items), p))
}

///|
fn read_map(bytes : Bytes, pos : Int, n : Int) -> (PackStreamValue, Int)? {
  if n < 0 {
    return None
  }
  let entries : Array[(String, PackStreamValue)] = []
  let mut p = pos
  for _ in 0.. return None
      Some((PackStreamValue::Str(k), p1)) =>
        match read_value(bytes, p1) {
          None => return None
          Some((v, p2)) => {
            entries.push((k, v))
            p = p2
          }
        }
      Some((_, _)) => return None
    }
  }
  Some((PackStreamValue::Map(entries), p))
}

///|
fn read_struct(bytes : Bytes, pos : Int, n : Int) -> (PackStreamValue, Int)? {
  if n < 0 || pos >= bytes.length() {
    return None
  }
  let tag = bytes[pos].to_int()
  let fields : Array[PackStreamValue] = []
  let mut p = pos + 1
  for _ in 0.. return None
      Some((v, np)) => {
        fields.push(v)
        p = np
      }
    }
  }
  Some((PackStreamValue::Struct(tag, fields), p))
}