///|
pub fn decode_value(
  bytes : Bytes,
  limits~ : Limits = Limits::default(),
) -> Value raise AmqpError {
  let reader = wire_reader(bytes, limits)
  let result = read_value(reader, 0)
  if reader.pos != reader.end {
    raise Invalid("trailing value bytes", reader.pos)
  }
  result
}

///|
fn read_value(reader : WireReader, depth : Int32) -> Value raise AmqpError {
  reader.visit(depth)
  let code = reader.u8()
  if code == 0 {
    let descriptor = read_value(reader, depth + 1)
    if !descriptor_valid(descriptor) {
      raise Invalid("descriptor type", reader.pos)
    }
    Described(descriptor, read_value(reader, depth + 1))
  } else {
    read_body(reader, code, depth)
  }
}

///|
fn signed_byte(number : Int32) -> Int32 {
  if number >= 128 {
    number - 256
  } else {
    number
  }
}

///|
fn read_body(
  reader : WireReader,
  code : Int32,
  depth : Int32,
) -> Value raise AmqpError {
  match code {
    0x40 => Null
    0x41 => Boolean(true)
    0x42 => Boolean(false)
    0x43 => UInt(0U)
    0x44 => ULong(0UL)
    0x45 => List([])
    0x50 => UByte(reader.u8())
    0x51 => ByteValue(signed_byte(reader.u8()))
    0x52 => UInt(reader.u8().reinterpret_as_uint())
    0x53 => ULong(reader.u8().to_uint64())
    0x54 => IntValue(signed_byte(reader.u8()))
    0x55 => Long(signed_byte(reader.u8()).to_int64())
    0x56 => {
      let b = reader.u8()
      if b > 1 {
        raise Invalid("boolean must be zero or one", reader.pos - 1)
      }
      Boolean(b == 1)
    }
    0x60 => UShort(reader.number(2).to_int())
    0x61 => {
      let n = reader.number(2).to_int()
      Short(if n >= 32768 { n - 65536 } else { n })
    }
    0x70 => UInt(reader.number(4).to_uint())
    0x71 => IntValue(reader.number(4).to_uint().reinterpret_as_int())
    0x72 => FloatValue(Float::reinterpret_from_uint(reader.number(4).to_uint()))
    0x73 => {
      let n = reader.number(4).to_uint()
      if n > 0x10ffffU || (n >= 0xd800U && n <= 0xdfffU) {
        raise Invalid("Unicode scalar", reader.pos - 4)
      }
      Character(n)
    }
    0x74 => Decimal32(reader.take(4))
    0x80 => ULong(reader.number(8))
    0x81 => Long(reader.number(8).reinterpret_as_int64())
    0x82 => DoubleValue(reader.number(8).reinterpret_as_double())
    0x83 => Timestamp(reader.number(8).reinterpret_as_int64())
    0x84 => Decimal64(reader.take(8))
    0x94 => Decimal128(reader.take(16))
    0x98 => Uuid(reader.take(16))
    0xa0 | 0xb0 =>
      Binary(reader.take(reader.length(if code == 0xa0 { 1 } else { 4 })))
    0xa1 | 0xb1 | 0xa3 | 0xb3 => {
      let symbol = code == 0xa3 || code == 0xb3
      let width = if code == 0xa1 || code == 0xa3 { 1 } else { 4 }
      let text = reader.text(reader.length(width), symbol)
      if symbol {
        Symbol(text)
      } else {
        Text(text)
      }
    }
    0xc0 | 0xd0 | 0xc1 | 0xd1 => read_container(reader, code, depth)
    0xe0 | 0xf0 => read_array(reader, code, depth)
    _ => raise Unsupported("unknown AMQP type code {code}")
  }
}

///|
fn container_bounds(
  reader : WireReader,
  width : Int32,
) -> (Int32, Int32) raise AmqpError {
  let size = reader.length(width)
  if size < width || size > reader.end - reader.pos {
    raise Invalid("container byte size", reader.pos)
  }
  let end = reader.pos + size
  let count = reader.length(width)
  if count > reader.limits.max_items - reader.nodes {
    raise Limit("container item count")
  }
  (end, count)
}

///|
fn read_container(
  reader : WireReader,
  code : Int32,
  depth : Int32,
) -> Value raise AmqpError {
  let (end, count) = container_bounds(
    reader,
    if code == 0xc0 || code == 0xc1 {
      1
    } else {
      4
    },
  )
  if count > end - reader.pos {
    raise Invalid("container count exceeds bytes", reader.pos)
  }
  let map = code == 0xc1 || code == 0xd1
  if map && count % 2 != 0 {
    raise Invalid("map requires even count", reader.pos)
  }
  let items : Array[Value] = []
  for i = 0; i < count; i = i + 1 {
    items.push(read_value(reader, depth + 1))
  }
  if reader.pos != end {
    raise Invalid("container size/count mismatch", reader.pos)
  }
  if map {
    let pairs : Array[(Value, Value)] = []
    let seen : Map[Bytes, Bool] = Map([])
    for i = 0; i < count; i = i + 2 {
      let key = encode_value(items[i], limits=reader.limits)
      if seen.contains(key) {
        raise Invalid("duplicate map key value", reader.pos)
      }
      seen[key] = true
      pairs.push((items[i], items[i + 1]))
    }
    MapValue(pairs)
  } else {
    List(items)
  }
}

///|
fn read_array(
  reader : WireReader,
  code : Int32,
  depth : Int32,
) -> Value raise AmqpError {
  let (end, count) = container_bounds(reader, if code == 0xe0 { 1 } else { 4 })
  let first = reader.u8()
  let (descriptor, element_code) = if first == 0 {
    let descriptor = read_value(reader, depth + 1)
    if !descriptor_valid(descriptor) {
      raise Invalid("array descriptor type", reader.pos)
    }
    (Some(descriptor), reader.u8())
  } else {
    (None, first)
  }
  if !valid_array_code(element_code) {
    raise Invalid("array constructor", reader.pos - 1)
  }
  let items : Array[Value] = []
  for i = 0; i < count; i = i + 1 {
    reader.visit(depth + 1)
    items.push(read_body(reader, element_code, depth + 1))
  }
  if reader.pos != end {
    raise Invalid("array size/count mismatch", reader.pos)
  }
  let normalized = if items.is_empty() {
    normalize_code(element_code)
  } else {
    value_code(items[0])
  }
  ArrayValue(normalized, descriptor, items)
}

///|
fn valid_array_code(code : Int32) -> Bool {
  canonical_array_code(normalize_code(code))
}

///|
fn normalize_code(code : Int32) -> Int32 {
  match code {
    0x41 | 0x42 => 0x56
    0x43 | 0x52 => 0x70
    0x44 | 0x53 => 0x80
    0x54 => 0x71
    0x55 => 0x81
    0x45 | 0xc0 => 0xd0
    0xc1 => 0xd1
    0xa0 => 0xb0
    0xa1 => 0xb1
    0xa3 => 0xb3
    0xe0 => 0xf0
    _ => code
  }
}

///|
test "compact scalar forms normalize without losing wire type" {
  assert_eq(encode_value(decode_value(b"\x53\xff")), encode_value(ULong(255UL)))
  assert_eq(encode_value(decode_value(b"\x54\xff")), encode_value(IntValue(-1)))
  assert_eq(encode_value(decode_value(b"\x41")), encode_value(Boolean(true)))
  assert_true(
    try {
      ignore(decode_value(b"\x56\x02"))
      false
    } catch {
      _ => true
    },
  )
  assert_true(
    try {
      ignore(decode_value(b"\xc1\x02\x01\x40"))
      false
    } catch {
      _ => true
    },
  )
}