///|
/// Pure MoonBit CBOR implementation (Optimized)
/// Based on RFC 8949
pub suberror CborError String

// CBOR Major types (3-bit)
// 0: unsigned integer
// 1: negative integer
// 2: byte string
// 3: text string
// 4: array
// 5: map
// 6: tag
// 7: simple/float

///|
/// CBOR value representation supporting nested structures
pub enum CborValue {
  VUInt(UInt64)
  VInt(Int64)
  VBytes(Bytes)
  VString(String)
  VArray(Array[CborValue])
  VMap(Array[(CborValue, CborValue)])
  VTag(UInt64, CborValue)
  VBool(Bool)
  VNull
  VUndefined
  VDouble(Double)
} derive(Show, Eq)

// Helper functions to construct CborValue (for blackbox tests)

///|
pub fn vuint(v : UInt64) -> CborValue {
  VUInt(v)
}

///|
pub fn vint(v : Int64) -> CborValue {
  VInt(v)
}

///|
pub fn vbytes(v : Bytes) -> CborValue {
  VBytes(v)
}

///|
pub fn vstring(v : String) -> CborValue {
  VString(v)
}

///|
pub fn varray(v : Array[CborValue]) -> CborValue {
  VArray(v)
}

///|
pub fn vmap(v : Array[(CborValue, CborValue)]) -> CborValue {
  VMap(v)
}

///|
pub fn vtag(tag : UInt64, v : CborValue) -> CborValue {
  VTag(tag, v)
}

///|
pub fn vbool(v : Bool) -> CborValue {
  VBool(v)
}

///|
pub fn vnull() -> CborValue {
  VNull
}

///|
pub fn vundefined() -> CborValue {
  VUndefined
}

///|
pub fn vdouble(v : Double) -> CborValue {
  VDouble(v)
}

// Pre-allocated constants for common values

///|
let cbor_true : Bytes = Bytes::from_array([b'\xf5'])

///|
let cbor_false : Bytes = Bytes::from_array([b'\xf4'])

///|
let cbor_null : Bytes = Bytes::from_array([b'\xf6'])

///|
/// Encode unsigned integer
pub fn encode_uint(value : UInt64) -> Bytes {
  encode_uint_internal(0, value)
}

///|
/// Encode signed integer (Int64)
pub fn encode_int(value : Int64) -> Bytes {
  if value >= 0L {
    encode_uint_internal(0, value.reinterpret_as_uint64())
  } else {
    encode_uint_internal(1, (-1L - value).reinterpret_as_uint64())
  }
}

///|
/// Internal: encode uint with known size using Bytes::makei
fn encode_uint_internal(major : Int, value : UInt64) -> Bytes {
  let major_shifted = (major << 5).to_byte()
  if value < 24UL {
    let b0 = major_shifted | value.to_byte()
    Bytes::makei(1, fn(_i) { b0 })
  } else if value < 256UL {
    let b0 = major_shifted | b'\x18'
    let b1 = value.to_byte()
    Bytes::makei(2, fn(i) { if i == 0 { b0 } else { b1 } })
  } else if value < 65536UL {
    let b0 = major_shifted | b'\x19'
    let b1 = (value >> 8).to_byte()
    let b2 = value.to_byte()
    Bytes::makei(3, fn(i) {
      match i {
        0 => b0
        1 => b1
        _ => b2
      }
    })
  } else if value < 4294967296UL {
    let b0 = major_shifted | b'\x1a'
    let b1 = (value >> 24).to_byte()
    let b2 = (value >> 16).to_byte()
    let b3 = (value >> 8).to_byte()
    let b4 = value.to_byte()
    Bytes::makei(5, fn(i) {
      match i {
        0 => b0
        1 => b1
        2 => b2
        3 => b3
        _ => b4
      }
    })
  } else {
    let b0 = major_shifted | b'\x1b'
    let b1 = (value >> 56).to_byte()
    let b2 = (value >> 48).to_byte()
    let b3 = (value >> 40).to_byte()
    let b4 = (value >> 32).to_byte()
    let b5 = (value >> 24).to_byte()
    let b6 = (value >> 16).to_byte()
    let b7 = (value >> 8).to_byte()
    let b8 = value.to_byte()
    Bytes::makei(9, fn(i) {
      match i {
        0 => b0
        1 => b1
        2 => b2
        3 => b3
        4 => b4
        5 => b5
        6 => b6
        7 => b7
        _ => b8
      }
    })
  }
}

///|
/// Decode signed integer
pub fn decode_int(input : Bytes) -> Int64 raise CborError {
  let (major, value, _) = decode_head(input, 0)
  if major == 0 {
    value.reinterpret_as_int64()
  } else if major == 1 {
    -1L - value.reinterpret_as_int64()
  } else {
    raise CborError("Expected integer type")
  }
}

///|
/// Encode double (64-bit float) using Bytes::makei
pub fn encode_double(value : Double) -> Bytes {
  let bits = value.reinterpret_as_uint64()
  Bytes::makei(9, fn(i) {
    match i {
      0 => b'\xfb'
      1 => ((bits >> 56) & 0xFFUL).to_byte()
      2 => ((bits >> 48) & 0xFFUL).to_byte()
      3 => ((bits >> 40) & 0xFFUL).to_byte()
      4 => ((bits >> 32) & 0xFFUL).to_byte()
      5 => ((bits >> 24) & 0xFFUL).to_byte()
      6 => ((bits >> 16) & 0xFFUL).to_byte()
      7 => ((bits >> 8) & 0xFFUL).to_byte()
      _ => (bits & 0xFFUL).to_byte()
    }
  })
}

///|
/// Decode double
pub fn decode_double(input : Bytes) -> Double raise CborError {
  if input.length() < 9 {
    raise CborError("Not enough bytes for double")
  }
  let header = input[0].to_int()
  if header != 0xfb {
    raise CborError("Expected 64-bit float")
  }
  let bits = (input[1].to_uint64() << 56) |
    (input[2].to_uint64() << 48) |
    (input[3].to_uint64() << 40) |
    (input[4].to_uint64() << 32) |
    (input[5].to_uint64() << 24) |
    (input[6].to_uint64() << 16) |
    (input[7].to_uint64() << 8) |
    input[8].to_uint64()
  bits.reinterpret_as_double()
}

///|
/// Encode string (UTF-8 text string) - optimized with FixedArray
pub fn encode_string(value : String) -> Bytes {
  // First pass: calculate UTF-8 length
  let mut utf8_len = 0
  for c in value {
    let code = c.to_int()
    if code < 0x80 {
      utf8_len = utf8_len + 1
    } else if code < 0x800 {
      utf8_len = utf8_len + 2
    } else if code < 0x10000 {
      utf8_len = utf8_len + 3
    } else {
      utf8_len = utf8_len + 4
    }
  }
  // Calculate header size
  let header_size = if utf8_len < 24 {
    1
  } else if utf8_len < 256 {
    2
  } else if utf8_len < 65536 {
    3
  } else {
    5
  }
  // Allocate exact size and build in place
  let result = FixedArray::make(header_size + utf8_len, b'\x00')
  // Write header
  let mut pos = write_header(result, 0, 3, utf8_len.to_uint64())
  // Write UTF-8 data directly
  for c in value {
    let code = c.to_int()
    if code < 0x80 {
      result[pos] = code.to_byte()
      pos = pos + 1
    } else if code < 0x800 {
      result[pos] = ((code >> 6) | 0xC0).to_byte()
      result[pos + 1] = ((code & 0x3F) | 0x80).to_byte()
      pos = pos + 2
    } else if code < 0x10000 {
      result[pos] = ((code >> 12) | 0xE0).to_byte()
      result[pos + 1] = (((code >> 6) & 0x3F) | 0x80).to_byte()
      result[pos + 2] = ((code & 0x3F) | 0x80).to_byte()
      pos = pos + 3
    } else {
      result[pos] = ((code >> 18) | 0xF0).to_byte()
      result[pos + 1] = (((code >> 12) & 0x3F) | 0x80).to_byte()
      result[pos + 2] = (((code >> 6) & 0x3F) | 0x80).to_byte()
      result[pos + 3] = ((code & 0x3F) | 0x80).to_byte()
      pos = pos + 4
    }
  }
  // Convert FixedArray to Bytes using makei
  Bytes::makei(result.length(), fn(i) { result[i] })
}

///|
/// Write CBOR header to FixedArray, returns new position
fn write_header(
  buf : FixedArray[Byte],
  pos : Int,
  major : Int,
  value : UInt64,
) -> Int {
  let major_shifted = (major << 5).to_byte()
  if value < 24UL {
    buf[pos] = major_shifted | value.to_byte()
    pos + 1
  } else if value < 256UL {
    buf[pos] = major_shifted | b'\x18'
    buf[pos + 1] = value.to_byte()
    pos + 2
  } else if value < 65536UL {
    buf[pos] = major_shifted | b'\x19'
    buf[pos + 1] = (value >> 8).to_byte()
    buf[pos + 2] = value.to_byte()
    pos + 3
  } else {
    buf[pos] = major_shifted | b'\x1a'
    buf[pos + 1] = (value >> 24).to_byte()
    buf[pos + 2] = (value >> 16).to_byte()
    buf[pos + 3] = (value >> 8).to_byte()
    buf[pos + 4] = value.to_byte()
    pos + 5
  }
}

///|
/// Decode string - optimized UTF-8 decoding
pub fn decode_string(input : Bytes) -> String raise CborError {
  let (major, len, offset) = decode_head(input, 0)
  if major != 3 {
    raise CborError("Expected text string")
  }
  let str_len = len.to_int()
  if input.length() < offset + str_len {
    raise CborError("Not enough bytes for string")
  }
  // Optimized UTF-8 decoding
  utf8_to_string_optimized(input, offset, str_len)
}

///|
/// Optimized UTF-8 decoding - processes 4 ASCII bytes at once when possible
fn utf8_to_string_optimized(bytes : Bytes, start : Int, len : Int) -> String {
  let buf = StringBuilder::new(size_hint=len)
  let end = start + len
  let mut i = start
  // Main loop
  while i < end {
    let b0 = bytes[i].to_int()
    if b0 < 0x80 {
      // Fast path: ASCII
      buf.write_char(b0.unsafe_to_char())
      i = i + 1
    } else if b0 < 0xE0 {
      // 2-byte sequence
      let b1 = bytes[i + 1].to_int()
      let code = ((b0 & 0x1F) << 6) | (b1 & 0x3F)
      buf.write_char(code.unsafe_to_char())
      i = i + 2
    } else if b0 < 0xF0 {
      // 3-byte sequence
      let b1 = bytes[i + 1].to_int()
      let b2 = bytes[i + 2].to_int()
      let code = ((b0 & 0x0F) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F)
      buf.write_char(code.unsafe_to_char())
      i = i + 3
    } else {
      // 4-byte sequence
      let b1 = bytes[i + 1].to_int()
      let b2 = bytes[i + 2].to_int()
      let b3 = bytes[i + 3].to_int()
      let code = ((b0 & 0x07) << 18) |
        ((b1 & 0x3F) << 12) |
        ((b2 & 0x3F) << 6) |
        (b3 & 0x3F)
      buf.write_char(code.unsafe_to_char())
      i = i + 4
    }
  }
  buf.to_string()
}

///|
/// Encode bytes (byte string) - optimized
pub fn encode_bytes(value : Bytes) -> Bytes {
  let len = value.length()
  let header_size = if len < 24 {
    1
  } else if len < 256 {
    2
  } else if len < 65536 {
    3
  } else {
    5
  }
  let result = FixedArray::make(header_size + len, b'\x00')
  let pos = write_header(result, 0, 2, len.to_uint64())
  // Copy bytes directly
  for i = 0; i < len; i = i + 1 {
    result[pos + i] = value[i]
  }
  Bytes::makei(result.length(), fn(i) { result[i] })
}

///|
/// Decode bytes - optimized with direct copy
pub fn decode_bytes(input : Bytes) -> Bytes raise CborError {
  let (major, len, offset) = decode_head(input, 0)
  if major != 2 {
    raise CborError("Expected byte string")
  }
  let byte_len = len.to_int()
  if input.length() < offset + byte_len {
    raise CborError("Not enough bytes")
  }
  // Direct copy without intermediate Array
  let result = FixedArray::make(byte_len, b'\x00')
  for i = 0; i < byte_len; i = i + 1 {
    result[i] = input[offset + i]
  }
  Bytes::makei(result.length(), fn(i) { result[i] })
}

///|
/// Encode boolean - uses pre-allocated constants
pub fn encode_bool(value : Bool) -> Bytes {
  if value {
    cbor_true
  } else {
    cbor_false
  }
}

///|
/// Decode boolean
pub fn decode_bool(input : Bytes) -> Bool raise CborError {
  if input.length() < 1 {
    raise CborError("Empty input")
  }
  let b = input[0].to_int()
  if b == 0xf5 {
    true
  } else if b == 0xf4 {
    false
  } else {
    raise CborError("Expected boolean")
  }
}

///|
/// Encode null - uses pre-allocated constant
pub fn encode_null() -> Bytes {
  cbor_null
}

// Internal helper functions

///|
/// Decode CBOR head, returns (major_type, value, bytes_consumed)
fn decode_head(
  input : Bytes,
  offset : Int,
) -> (Int, UInt64, Int) raise CborError {
  if offset >= input.length() {
    raise CborError("Unexpected end of input")
  }
  let first = input[offset].to_int()
  let major = first >> 5
  let additional = first & 0x1f
  if additional < 24 {
    (major, additional.to_uint64(), offset + 1)
  } else if additional == 24 {
    if offset + 1 >= input.length() {
      raise CborError("Not enough bytes")
    }
    (major, input[offset + 1].to_uint64(), offset + 2)
  } else if additional == 25 {
    if offset + 2 >= input.length() {
      raise CborError("Not enough bytes")
    }
    let v = (input[offset + 1].to_uint64() << 8) | input[offset + 2].to_uint64()
    (major, v, offset + 3)
  } else if additional == 26 {
    if offset + 4 >= input.length() {
      raise CborError("Not enough bytes")
    }
    let v = (input[offset + 1].to_uint64() << 24) |
      (input[offset + 2].to_uint64() << 16) |
      (input[offset + 3].to_uint64() << 8) |
      input[offset + 4].to_uint64()
    (major, v, offset + 5)
  } else if additional == 27 {
    if offset + 8 >= input.length() {
      raise CborError("Not enough bytes")
    }
    let v = (input[offset + 1].to_uint64() << 56) |
      (input[offset + 2].to_uint64() << 48) |
      (input[offset + 3].to_uint64() << 40) |
      (input[offset + 4].to_uint64() << 32) |
      (input[offset + 5].to_uint64() << 24) |
      (input[offset + 6].to_uint64() << 16) |
      (input[offset + 7].to_uint64() << 8) |
      input[offset + 8].to_uint64()
    (major, v, offset + 9)
  } else {
    raise CborError("Invalid additional info: \{additional}")
  }
}

///|
/// Encode CborValue to CBOR bytes (supports nested structures)
pub fn encode(value : CborValue) -> Bytes {
  match value {
    VUInt(v) => encode_uint(v)
    VInt(v) => encode_int(v)
    VBytes(v) => encode_bytes(v)
    VString(v) => encode_string(v)
    VArray(arr) => {
      // Encode array header
      let header = encode_uint_internal(4, arr.length().to_uint64())
      // Encode each element
      let parts : Array[Bytes] = [header]
      for item in arr {
        parts.push(encode(item))
      }
      concat_bytes(parts)
    }
    VMap(pairs) => {
      // Encode map header
      let header = encode_uint_internal(5, pairs.length().to_uint64())
      // Encode each key-value pair
      let parts : Array[Bytes] = [header]
      for pair in pairs {
        let (k, v) = pair
        parts.push(encode(k))
        parts.push(encode(v))
      }
      concat_bytes(parts)
    }
    VTag(tag, val) => {
      // Encode tag header
      let header = encode_uint_internal(6, tag)
      // Encode tagged value
      let value_bytes = encode(val)
      concat_bytes([header, value_bytes])
    }
    VBool(v) => encode_bool(v)
    VNull => encode_null()
    VUndefined => Bytes::from_array([b'\xf7'])
    VDouble(v) => encode_double(v)
  }
}

///|
/// Helper: concatenate multiple Bytes
fn concat_bytes(parts : Array[Bytes]) -> Bytes {
  let mut total_len = 0
  for part in parts {
    total_len = total_len + part.length()
  }
  let result = FixedArray::make(total_len, b'\x00')
  let mut pos = 0
  for part in parts {
    for i = 0; i < part.length(); i = i + 1 {
      result[pos] = part[i]
      pos = pos + 1
    }
  }
  Bytes::makei(total_len, fn(i) { result[i] })
}

///|
/// Decode CBOR bytes to CborValue (supports nested structures)
pub fn decode(input : Bytes) -> CborValue raise CborError {
  let (value, _consumed) = decode_value(input, 0)
  value
}

///|
/// Internal: decode CBOR value at given offset, returns (value, bytes_consumed)
fn decode_value(input : Bytes, offset : Int) -> (CborValue, Int) raise CborError {
  let (major, value, new_offset) = decode_head(input, offset)
  match major {
    // Major type 0: unsigned integer
    0 => {
      // If the value fits in Int64 range, return as VInt for convenience
      if value <= 0x7FFFFFFFFFFFFFFFUL {
        (VInt(value.reinterpret_as_int64()), new_offset)
      } else {
        (VUInt(value), new_offset)
      }
    }
    // Major type 1: negative integer
    1 => (VInt(-1L - value.reinterpret_as_int64()), new_offset)
    // Major type 2: byte string
    2 => {
      let len = value.to_int()
      if input.length() < new_offset + len {
        raise CborError("Not enough bytes for byte string")
      }
      let bytes_data = FixedArray::make(len, b'\x00')
      for i = 0; i < len; i = i + 1 {
        bytes_data[i] = input[new_offset + i]
      }
      (VBytes(Bytes::makei(len, fn(i) { bytes_data[i] })), new_offset + len)
    }
    // Major type 3: text string
    3 => {
      let len = value.to_int()
      if input.length() < new_offset + len {
        raise CborError("Not enough bytes for text string")
      }
      let str = utf8_to_string_optimized(input, new_offset, len)
      (VString(str), new_offset + len)
    }
    // Major type 4: array
    4 => {
      let len = value.to_int()
      let arr : Array[CborValue] = []
      let mut pos = new_offset
      for _i = 0; _i < len; _i = _i + 1 {
        let (item, consumed) = decode_value(input, pos)
        arr.push(item)
        pos = consumed
      }
      (VArray(arr), pos)
    }
    // Major type 5: map
    5 => {
      let len = value.to_int()
      let map : Array[(CborValue, CborValue)] = []
      let mut pos = new_offset
      for _i = 0; _i < len; _i = _i + 1 {
        let (key, consumed1) = decode_value(input, pos)
        let (val, consumed2) = decode_value(input, consumed1)
        map.push((key, val))
        pos = consumed2
      }
      (VMap(map), pos)
    }
    // Major type 6: tag
    6 => {
      let (tagged_value, consumed) = decode_value(input, new_offset)
      (VTag(value, tagged_value), consumed)
    }
    // Major type 7: simple values and floats
    7 => {
      let additional = input[offset].to_int() & 0x1f
      if additional == 20 {
        (VBool(false), new_offset)
      } else if additional == 21 {
        (VBool(true), new_offset)
      } else if additional == 22 {
        (VNull, new_offset)
      } else if additional == 23 {
        (VUndefined, new_offset)
      } else if additional == 27 {
        // 64-bit float
        if input.length() < new_offset + 8 {
          raise CborError("Not enough bytes for double")
        }
        let bits = (input[new_offset].to_uint64() << 56) |
          (input[new_offset + 1].to_uint64() << 48) |
          (input[new_offset + 2].to_uint64() << 40) |
          (input[new_offset + 3].to_uint64() << 32) |
          (input[new_offset + 4].to_uint64() << 24) |
          (input[new_offset + 5].to_uint64() << 16) |
          (input[new_offset + 6].to_uint64() << 8) |
          input[new_offset + 7].to_uint64()
        (VDouble(bits.reinterpret_as_double()), new_offset + 8)
      } else {
        raise CborError("Unsupported simple value: \{additional}")
      }
    }
    _ => raise CborError("Invalid major type: \{major}")
  }
}