///|
/// Manual protobuf codec for the sqlc WASM plugin protocol.
/// Supports varint (LEB128), length-delimited, and embedded message encoding.
/// Decoder sets error flags on unsupported wire types (no abort on decode paths).
//
// Copyright 2026 Mairzzcllo
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

const WT_VARINT : Int = 0

///|
const WT_BITS64 : Int = 1

///|
const WT_LEN : Int = 2

///|
const WT_BITS32 : Int = 5

// === Encoder: mutable byte buffer ===

///|
struct Encoder {
  buf : Array[Byte]
}

///|
fn Encoder::new() -> Encoder {
  { buf: [] }
}

///|
fn Encoder::write_byte(self : Encoder, b : Byte) -> Unit {
  self.buf.push(b)
}

///|
fn Encoder::write_bytes(self : Encoder, bs : Bytes) -> Unit {
  let n = bs.length()
  for i = 0; i < n; i = i + 1 {
    self.write_byte(bs[i])
  }
}

///|
fn Encoder::finish(self : Encoder) -> Bytes {
  Bytes::from_array(self.buf)
}

///|
fn Encoder::write_varint(self : Encoder, v : Int) -> Unit {
  let mut val = v
  let mut bytes_written = 0
  while bytes_written < 5 && val >= 0x80 {
    self.write_byte(((val & 0x7F) | 0x80).to_byte())
    val = val >> 7
    bytes_written = bytes_written + 1
  }
  self.write_byte((val & 0x7F).to_byte())
}

///|
fn Encoder::write_tag(self : Encoder, fn_num : Int, wt : Int) -> Unit {
  self.write_varint((fn_num << 3) | wt)
}

///|
fn Encoder::write_string(self : Encoder, s : String) -> Unit {
  let bs = @utf8.encode(s)
  self.write_varint(bs.length())
  self.write_bytes(bs)
}

///|
fn Encoder::write_bool(self : Encoder, b : Bool) -> Unit {
  self.write_varint(if b { 1 } else { 0 })
}

///|
fn Encoder::write_embedded(self : Encoder, val : (Encoder) -> Unit) -> Unit {
  let sub = Encoder::new()
  val(sub)
  let bs = sub.finish()
  self.write_varint(bs.length())
  self.write_bytes(bs)
}

// === Decoder: reads from Bytes ===

///|
struct Decoder {
  buf : Bytes
  mut pos : Int
  end : Int
  mut error : Bool
}

///|
fn Decoder::new(buf : Bytes) -> Decoder {
  { buf, pos: 0, end: buf.length(), error: false }
}

///|
fn Decoder::remaining(self : Decoder) -> Int {
  self.end - self.pos
}

///|
fn Decoder::has_error(self : Decoder) -> Bool {
  self.error
}

///|
fn Decoder::read_byte(self : Decoder) -> Byte {
  if self.pos >= self.end {
    self.error = true
    return (0).to_byte()
  }
  let b = self.buf[self.pos]
  self.pos = self.pos + 1
  b
}

///|
fn Decoder::read_varint(self : Decoder) -> Int {
  let mut result : Int = 0
  let mut shift : Int = 0
  for i = 0; i < 10; i = i + 1 {
    if self.remaining() <= 0 {
      self.error = true
      break
    }
    let b = self.read_byte().to_int()
    result = result | ((b & 0x7F) << shift)
    if (b & 0x80) == 0 {
      return result
    }
    shift = shift + 7
  }
  // Protobuf varints are at most 10 bytes; trailing continuation bit is corrupt.
  self.error = true
  result
}

///|
fn Decoder::read_tag(self : Decoder) -> (Int, Int) {
  let n = self.read_varint()
  (n >> 3, n & 0x07)
}

///|
fn Decoder::read_string(self : Decoder) -> String {
  let len = self.read_varint()
  if self.has_error() || len <= 0 {
    return ""
  }
  let available = self.remaining()
  if len > available {
    self.error = true
    let start = self.pos
    self.pos = start
    return ""
  }
  let start = self.pos
  self.pos = start + len
  let sub = Bytes::makei(len, i => self.buf[start + i])
  @utf8.decode_lossy(sub[:])
}

///|
fn Decoder::read_bytes(self : Decoder) -> Bytes {
  let len = self.read_varint()
  if self.has_error() || len <= 0 {
    return Bytes::default()
  }
  let available = self.remaining()
  if len > available {
    self.error = true
    let start = self.pos
    self.pos = start
    return Bytes::default()
  }
  let start = self.pos
  self.pos = start + len
  Bytes::makei(len, i => self.buf[start + i])
}

///|
fn Decoder::read_bool(self : Decoder) -> Bool {
  self.read_varint() != 0
}

///|
/// Read 4 little-endian bytes as a 32-bit fixed-width unsigned integer.
/// Corresponds to protobuf wire type 5 (32-bit).
fn Decoder::read_fixed32(self : Decoder) -> Int {
  if self.has_error() {
    return 0
  }
  if self.remaining() < 4 {
    self.error = true
    return 0
  }
  let b0 = self.read_byte().to_int()
  let b1 = self.read_byte().to_int()
  let b2 = self.read_byte().to_int()
  let b3 = self.read_byte().to_int()
  b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}

///|
/// Read 8 little-endian bytes as a 64-bit fixed-width integer.
/// Corresponds to protobuf wire type 1 (64-bit).
fn Decoder::read_fixed64(self : Decoder) -> Int64 {
  if self.has_error() {
    return 0L
  }
  if self.remaining() < 8 {
    self.error = true
    return 0L
  }
  let b0 = self.read_byte().to_int()
  let b1 = self.read_byte().to_int()
  let b2 = self.read_byte().to_int()
  let b3 = self.read_byte().to_int()
  let b4 = self.read_byte().to_int()
  let b5 = self.read_byte().to_int()
  let b6 = self.read_byte().to_int()
  let b7 = self.read_byte().to_int()
  let low : Int64 = Int64::from_int(b0 | (b1 << 8) | (b2 << 16) | (b3 << 24))
  let high : Int64 = Int64::from_int(b4 | (b5 << 8) | (b6 << 16) | (b7 << 24))
  low | (high << 32)
}

///|
fn Decoder::skip_field(self : Decoder, fn_num : Int, wt : Int) -> Unit {
  match wt {
    WT_VARINT => {
      let _ = self.read_varint()
    }
    WT_LEN => {
      let len = self.read_varint()
      let new_pos = self.pos + len
      if new_pos > self.end || new_pos < self.pos {
        self.error = true
        self.pos = self.end
      } else {
        self.pos = new_pos
      }
    }
    WT_BITS64 => {
      let new_pos = self.pos + 8
      if new_pos > self.end {
        self.error = true
        self.pos = self.end
      } else {
        self.pos = new_pos
      }
    }
    WT_BITS32 => {
      let new_pos = self.pos + 4
      if new_pos > self.end {
        self.error = true
        self.pos = self.end
      } else {
        self.pos = new_pos
      }
    }
    3 => self.skip_group(fn_num)
    4 => {
      self.error = true
      self.pos = self.end
    }
    _ => {
      // Unknown wire type — cannot determine field boundary.
      // Skip rest of message and mark error.
      self.error = true
      self.pos = self.end
    }
  }
}

///|
fn Decoder::skip_group(self : Decoder, fn_num : Int) -> Unit {
  while self.remaining() > 0 {
    let (sub_fn, sub_wt) = self.read_tag()
    if sub_wt == 4 && sub_fn == fn_num {
      return
    }
    self.skip_field(sub_fn, sub_wt)
  }
}

///|
fn[T] decode_embedded(dec : Decoder, f : (Decoder) -> T) -> T {
  let len = dec.read_varint()
  if len < 0 || len > dec.remaining() {
    // Corrupt length — cannot determine sub-message boundary.
    // Setting pos = end loses sibling fields in this message, but
    // is safer than the alternative (misparsing payload bytes as sibling tags).
    dec.error = true
    dec.pos = dec.end
    return f({ buf: dec.buf, pos: dec.pos, end: dec.pos, error: true })
  }
  let start = dec.pos
  let end = start + len
  let sub : Decoder = { buf: dec.buf, pos: start, end, error: false }
  let result = f(sub)
  if sub.has_error() {
    dec.error = true
  }
  dec.pos = end
  result
}

// === File ===

///|
fn encode_file(enc : Encoder, val : File) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.name)
  enc.write_tag(2, WT_LEN)
  enc.write_varint(val.contents.length())
  enc.write_bytes(val.contents)
}

///|
fn decode_file(dec : Decoder) -> File {
  let mut name : String? = None
  let mut contents : Bytes? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => name = Some(dec.read_string())
      (2, WT_LEN) => contents = Some(dec.read_bytes())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  let contents_val = match contents {
    Some(v) => v
    None => Bytes::default()
  }
  { name: name_val, contents: contents_val }
}

// === Settings ===

///|
fn encode_settings(enc : Encoder, val : Settings) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.version)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.engine)
  for i = 0; i < val.schema.length(); i = i + 1 {
    enc.write_tag(3, WT_LEN)
    enc.write_string(val.schema[i])
  }
  for i = 0; i < val.queries.length(); i = i + 1 {
    enc.write_tag(4, WT_LEN)
    enc.write_string(val.queries[i])
  }
  enc.write_tag(12, WT_LEN)
  enc.write_embedded(fn(enc) { encode_codegen(enc, val.codegen) })
}

///|
fn decode_settings(dec : Decoder) -> Settings {
  let mut version : String? = None
  let mut engine : String? = None
  let schema : Array[String] = []
  let queries : Array[String] = []
  let mut codegen : Codegen? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => version = Some(dec.read_string())
      (2, WT_LEN) => engine = Some(dec.read_string())
      (3, WT_LEN) => schema.push(dec.read_string())
      (4, WT_LEN) => queries.push(dec.read_string())
      (12, WT_LEN) => codegen = Some(decode_embedded(dec, decode_codegen))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let version_val = match version {
    Some(v) => v
    None => ""
  }
  let engine_val = match engine {
    Some(v) => v
    None => ""
  }
  let codegen_val = match codegen {
    Some(v) => v
    None =>
      { out: "", plugin: "", options: b"", env: [], process: None, wasm: None }
  }
  {
    version: version_val,
    engine: engine_val,
    schema,
    queries,
    codegen: codegen_val,
  }
}

// === Codegen ===

///|
fn encode_codegen(enc : Encoder, val : Codegen) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.out)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.plugin)
  enc.write_tag(3, WT_LEN)
  enc.write_varint(val.options.length())
  enc.write_bytes(val.options)
  for i = 0; i < val.env.length(); i = i + 1 {
    enc.write_tag(4, WT_LEN)
    enc.write_string(val.env[i])
  }
  match val.process {
    None => ()
    Some(v) => {
      enc.write_tag(5, WT_LEN)
      enc.write_embedded(fn(enc) { encode_codegen_process(enc, v) })
    }
  }
  match val.wasm {
    None => ()
    Some(v) => {
      enc.write_tag(6, WT_LEN)
      enc.write_embedded(fn(enc) { encode_codegen_wasm(enc, v) })
    }
  }
}

///|
fn decode_codegen(dec : Decoder) -> Codegen {
  let mut out : String? = None
  let mut plugin : String? = None
  let mut options : Bytes? = None
  let env : Array[String] = []
  let mut process : CodegenProcess? = None
  let mut wasm : CodegenWASM? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => out = Some(dec.read_string())
      (2, WT_LEN) => plugin = Some(dec.read_string())
      (3, WT_LEN) => options = Some(dec.read_bytes())
      (4, WT_LEN) => env.push(dec.read_string())
      (5, WT_LEN) =>
        process = Some(decode_embedded(dec, decode_codegen_process))
      (6, WT_LEN) => wasm = Some(decode_embedded(dec, decode_codegen_wasm))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let out_val = match out {
    Some(v) => v
    None => ""
  }
  let plugin_val = match plugin {
    Some(v) => v
    None => ""
  }
  let options_val = match options {
    Some(v) => v
    None => Bytes::default()
  }
  { out: out_val, plugin: plugin_val, options: options_val, env, process, wasm }
}

// === CodegenProcess ===

///|
fn encode_codegen_process(enc : Encoder, val : CodegenProcess) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.cmd)
}

///|
fn decode_codegen_process(dec : Decoder) -> CodegenProcess {
  let mut cmd : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => cmd = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let cmd_val = match cmd {
    Some(v) => v
    None => ""
  }
  { cmd: cmd_val }
}

// === CodegenWASM ===

///|
fn encode_codegen_wasm(enc : Encoder, val : CodegenWASM) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.url)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.sha256)
}

///|
fn decode_codegen_wasm(dec : Decoder) -> CodegenWASM {
  let mut url : String? = None
  let mut sha256 : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => url = Some(dec.read_string())
      (2, WT_LEN) => sha256 = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let url_val = match url {
    Some(v) => v
    None => ""
  }
  let sha256_val = match sha256 {
    Some(v) => v
    None => ""
  }
  { url: url_val, sha256: sha256_val }
}

// === Catalog ===

///|
fn encode_catalog(enc : Encoder, val : Catalog) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.comment)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.default_schema)
  enc.write_tag(3, WT_LEN)
  enc.write_string(val.name)
  for i = 0; i < val.schemas.length(); i = i + 1 {
    enc.write_tag(4, WT_LEN)
    enc.write_embedded(fn(enc) { encode_schema(enc, val.schemas[i]) })
  }
}

///|
fn decode_catalog(dec : Decoder) -> Catalog {
  let mut comment : String? = None
  let mut default_schema : String? = None
  let mut name : String? = None
  let schemas : Array[Schema] = []
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => comment = Some(dec.read_string())
      (2, WT_LEN) => default_schema = Some(dec.read_string())
      (3, WT_LEN) => name = Some(dec.read_string())
      (4, WT_LEN) => schemas.push(decode_embedded(dec, decode_schema))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  let default_schema_val = match default_schema {
    Some(v) => v
    None => ""
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  {
    comment: comment_val,
    default_schema: default_schema_val,
    name: name_val,
    schemas,
  }
}

// === Schema ===

///|
fn encode_schema(enc : Encoder, val : Schema) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.comment)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.name)
  for i = 0; i < val.tables.length(); i = i + 1 {
    enc.write_tag(3, WT_LEN)
    enc.write_embedded(fn(enc) { encode_table(enc, val.tables[i]) })
  }
  for i = 0; i < val.enums.length(); i = i + 1 {
    enc.write_tag(4, WT_LEN)
    enc.write_embedded(fn(enc) { encode_enum(enc, val.enums[i]) })
  }
  for i = 0; i < val.composite_types.length(); i = i + 1 {
    enc.write_tag(5, WT_LEN)
    enc.write_embedded(fn(enc) {
      encode_composite_type(enc, val.composite_types[i])
    })
  }
}

///|
fn decode_schema(dec : Decoder) -> Schema {
  let mut comment : String? = None
  let mut name : String? = None
  let tables : Array[Table] = []
  let enums : Array[Enum] = []
  let composite_types : Array[CompositeType] = []
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => comment = Some(dec.read_string())
      (2, WT_LEN) => name = Some(dec.read_string())
      (3, WT_LEN) => tables.push(decode_embedded(dec, decode_table))
      (4, WT_LEN) => enums.push(decode_embedded(dec, decode_enum))
      (5, WT_LEN) =>
        composite_types.push(decode_embedded(dec, decode_composite_type))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  { comment: comment_val, name: name_val, tables, enums, composite_types }
}

// === CompositeType ===

///|
fn encode_composite_type(enc : Encoder, val : CompositeType) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.name)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.comment)
}

///|
fn decode_composite_type(dec : Decoder) -> CompositeType {
  let mut name : String? = None
  let mut comment : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => name = Some(dec.read_string())
      (2, WT_LEN) => comment = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  { name: name_val, comment: comment_val }
}

// === Enum ===

///|
fn encode_enum(enc : Encoder, val : Enum) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.name)
  for i = 0; i < val.vals.length(); i = i + 1 {
    enc.write_tag(2, WT_LEN)
    enc.write_string(val.vals[i])
  }
  enc.write_tag(3, WT_LEN)
  enc.write_string(val.comment)
}

///|
fn decode_enum(dec : Decoder) -> Enum {
  let mut name : String? = None
  let vals : Array[String] = []
  let mut comment : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => name = Some(dec.read_string())
      (2, WT_LEN) => vals.push(dec.read_string())
      (3, WT_LEN) => comment = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  { name: name_val, vals, comment: comment_val }
}

// === Table ===

///|
fn encode_table(enc : Encoder, val : Table) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_embedded(fn(enc) { encode_identifier(enc, val.rel) })
  for i = 0; i < val.columns.length(); i = i + 1 {
    enc.write_tag(2, WT_LEN)
    enc.write_embedded(fn(enc) { encode_column(enc, val.columns[i]) })
  }
  enc.write_tag(3, WT_LEN)
  enc.write_string(val.comment)
}

///|
fn decode_table(dec : Decoder) -> Table {
  let mut rel : Identifier? = None
  let columns : Array[Column] = []
  let mut comment : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => rel = Some(decode_embedded(dec, decode_identifier))
      (2, WT_LEN) => columns.push(decode_embedded(dec, decode_column))
      (3, WT_LEN) => comment = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let rel_val = match rel {
    Some(v) => v
    None => empty_id
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  { rel: rel_val, columns, comment: comment_val }
}

// === Identifier ===

///|
fn encode_identifier(enc : Encoder, val : Identifier) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.catalog)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.schema)
  enc.write_tag(3, WT_LEN)
  enc.write_string(val.name)
}

///|
fn decode_identifier(dec : Decoder) -> Identifier {
  let mut catalog : String? = None
  let mut schema : String? = None
  let mut name : String? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => catalog = Some(dec.read_string())
      (2, WT_LEN) => schema = Some(dec.read_string())
      (3, WT_LEN) => name = Some(dec.read_string())
      (_, _) => dec.skip_field(fn_num, wt)
    }
  }
  let catalog_val = match catalog {
    Some(v) => v
    None => ""
  }
  let schema_val = match schema {
    Some(v) => v
    None => ""
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  { catalog: catalog_val, schema: schema_val, name: name_val }
}

// === Column ===

///|
fn encode_column(enc : Encoder, val : Column) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.name)
  // field 2 was removed from the proto definition (was "type" — redundant with field 12 ty identifier)
  if val.not_null {
    enc.write_tag(3, WT_VARINT)
    enc.write_bool(val.not_null)
  }
  if val.is_array {
    enc.write_tag(4, WT_VARINT)
    enc.write_bool(val.is_array)
  }
  enc.write_tag(5, WT_LEN)
  enc.write_string(val.comment)
  if val.length != 0 {
    enc.write_tag(6, WT_VARINT)
    enc.write_varint(val.length)
  }
  if val.is_named_param {
    enc.write_tag(7, WT_VARINT)
    enc.write_bool(val.is_named_param)
  }
  if val.is_func_call {
    enc.write_tag(8, WT_VARINT)
    enc.write_bool(val.is_func_call)
  }
  enc.write_tag(9, WT_LEN)
  enc.write_string(val.scope)
  enc.write_tag(10, WT_LEN)
  enc.write_embedded(fn(enc) { encode_identifier(enc, val.table) })
  enc.write_tag(11, WT_LEN)
  enc.write_string(val.table_alias)
  enc.write_tag(12, WT_LEN)
  enc.write_embedded(fn(enc) { encode_identifier(enc, val.ty) })
  if val.is_sqlc_slice {
    enc.write_tag(13, WT_VARINT)
    enc.write_bool(val.is_sqlc_slice)
  }
  enc.write_tag(14, WT_LEN)
  enc.write_embedded(fn(enc) { encode_identifier(enc, val.embed_table) })
  enc.write_tag(15, WT_LEN)
  enc.write_string(val.original_name)
  if val.unsigned {
    enc.write_tag(16, WT_VARINT)
    enc.write_bool(val.unsigned)
  }
  if val.array_dims != 0 {
    enc.write_tag(17, WT_VARINT)
    enc.write_varint(val.array_dims)
  }
}

///|
fn decode_column(dec : Decoder) -> Column {
  let mut name : String? = None
  let mut not_null : Bool? = None
  let mut is_array : Bool? = None
  let mut comment : String? = None
  let mut length : Int? = None
  let mut is_named_param : Bool? = None
  let mut is_func_call : Bool? = None
  let mut scope : String? = None
  let mut table : Identifier? = None
  let mut table_alias : String? = None
  let mut ty : Identifier? = None
  let mut is_sqlc_slice : Bool? = None
  let mut embed_table : Identifier? = None
  let mut original_name : String? = None
  let mut unsigned : Bool? = None
  let mut array_dims : Int? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => name = Some(dec.read_string())
      // field 2 was removed from the proto definition
      (3, WT_VARINT) => not_null = Some(dec.read_bool())
      (4, WT_VARINT) => is_array = Some(dec.read_bool())
      (5, WT_LEN) => comment = Some(dec.read_string())
      (6, WT_VARINT) => length = Some(dec.read_varint())
      (7, WT_VARINT) => is_named_param = Some(dec.read_bool())
      (8, WT_VARINT) => is_func_call = Some(dec.read_bool())
      (9, WT_LEN) => scope = Some(dec.read_string())
      (10, WT_LEN) => table = Some(decode_embedded(dec, decode_identifier))
      (11, WT_LEN) => table_alias = Some(dec.read_string())
      (12, WT_LEN) => ty = Some(decode_embedded(dec, decode_identifier))
      (13, WT_VARINT) => is_sqlc_slice = Some(dec.read_bool())
      (14, WT_LEN) =>
        embed_table = Some(decode_embedded(dec, decode_identifier))
      (15, WT_LEN) => original_name = Some(dec.read_string())
      (16, WT_VARINT) => unsigned = Some(dec.read_bool())
      (17, WT_VARINT) => array_dims = Some(dec.read_varint())
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  let not_null_val = match not_null {
    Some(v) => v
    None => false
  }
  let is_array_val = match is_array {
    Some(v) => v
    None => false
  }
  let comment_val = match comment {
    Some(v) => v
    None => ""
  }
  let length_val = match length {
    Some(v) => v
    None => 0
  }
  let is_named_param_val = match is_named_param {
    Some(v) => v
    None => false
  }
  let is_func_call_val = match is_func_call {
    Some(v) => v
    None => false
  }
  let scope_val = match scope {
    Some(v) => v
    None => ""
  }
  let table_val = match table {
    Some(v) => v
    None => empty_id
  }
  let table_alias_val = match table_alias {
    Some(v) => v
    None => ""
  }
  let ty_val = match ty {
    Some(v) => v
    None => empty_id
  }
  let is_sqlc_slice_val = match is_sqlc_slice {
    Some(v) => v
    None => false
  }
  let embed_table_val = match embed_table {
    Some(v) => v
    None => empty_id
  }
  let original_name_val = match original_name {
    Some(v) => v
    None => ""
  }
  let unsigned_val = match unsigned {
    Some(v) => v
    None => false
  }
  let array_dims_val = match array_dims {
    Some(v) => v
    None => 0
  }
  {
    name: name_val,
    not_null: not_null_val,
    is_array: is_array_val,
    comment: comment_val,
    length: length_val,
    is_named_param: is_named_param_val,
    is_func_call: is_func_call_val,
    scope: scope_val,
    table: table_val,
    table_alias: table_alias_val,
    ty: ty_val,
    is_sqlc_slice: is_sqlc_slice_val,
    embed_table: embed_table_val,
    original_name: original_name_val,
    unsigned: unsigned_val,
    array_dims: array_dims_val,
  }
}

// === Query ===

///|
fn encode_query(enc : Encoder, val : Query) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_string(val.text)
  enc.write_tag(2, WT_LEN)
  enc.write_string(val.name)
  enc.write_tag(3, WT_LEN)
  enc.write_string(val.cmd)
  for i = 0; i < val.columns.length(); i = i + 1 {
    enc.write_tag(4, WT_LEN)
    enc.write_embedded(fn(enc) { encode_column(enc, val.columns[i]) })
  }
  for i = 0; i < val.params.length(); i = i + 1 {
    enc.write_tag(5, WT_LEN)
    enc.write_embedded(fn(enc) { encode_parameter(enc, val.params[i]) })
  }
  for i = 0; i < val.comments.length(); i = i + 1 {
    enc.write_tag(6, WT_LEN)
    enc.write_string(val.comments[i])
  }
  enc.write_tag(7, WT_LEN)
  enc.write_string(val.filename)
  enc.write_tag(8, WT_LEN)
  enc.write_embedded(fn(enc) { encode_identifier(enc, val.insert_into_table) })
}

///|
fn decode_query(dec : Decoder) -> Query {
  let mut text : String? = None
  let mut name : String? = None
  let mut cmd : String? = None
  let columns : Array[Column] = []
  let params : Array[Parameter] = []
  let comments : Array[String] = []
  let mut filename : String? = None
  let mut insert_into_table : Identifier? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => text = Some(dec.read_string())
      (2, WT_LEN) => name = Some(dec.read_string())
      (3, WT_LEN) => cmd = Some(dec.read_string())
      (4, WT_LEN) => columns.push(decode_embedded(dec, decode_column))
      (5, WT_LEN) => params.push(decode_embedded(dec, decode_parameter))
      (6, WT_LEN) => comments.push(dec.read_string())
      (7, WT_LEN) => filename = Some(dec.read_string())
      (8, WT_LEN) =>
        insert_into_table = Some(decode_embedded(dec, decode_identifier))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let text_val = match text {
    Some(v) => v
    None => ""
  }
  let name_val = match name {
    Some(v) => v
    None => ""
  }
  let cmd_val = match cmd {
    Some(v) => v
    None => ""
  }
  let filename_val = match filename {
    Some(v) => v
    None => ""
  }
  let insert_val = match insert_into_table {
    Some(v) => v
    None => { catalog: "", schema: "", name: "" }
  }
  {
    text: text_val,
    name: name_val,
    cmd: cmd_val,
    columns,
    params,
    comments,
    filename: filename_val,
    insert_into_table: insert_val,
  }
}

// === Parameter ===

///|
fn encode_parameter(enc : Encoder, val : Parameter) -> Unit {
  enc.write_tag(1, WT_VARINT)
  enc.write_varint(val.number)
  enc.write_tag(2, WT_LEN)
  enc.write_embedded(fn(enc) { encode_column(enc, val.column) })
}

///|
fn decode_parameter(dec : Decoder) -> Parameter {
  let mut number : Int? = None
  let mut column : Column? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_VARINT) => number = Some(dec.read_varint())
      (2, WT_LEN) => column = Some(decode_embedded(dec, decode_column))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  let number_val = match number {
    Some(v) => v
    None => 0
  }
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let column_val = match column {
    Some(v) => v
    None =>
      {
        name: "",
        not_null: false,
        is_array: false,
        comment: "",
        length: 0,
        is_named_param: false,
        is_func_call: false,
        scope: "",
        table: empty_id,
        table_alias: "",
        ty: empty_id,
        is_sqlc_slice: false,
        embed_table: empty_id,
        original_name: "",
        unsigned: false,
        array_dims: 0,
      }
  }
  { number: number_val, column: column_val }
}

// === GenerateRequest ===

///|
fn decode_generate_request(dec : Decoder) -> GenerateRequest raise {
  let mut settings : Settings? = None
  let mut catalog : Catalog? = None
  let queries : Array[Query] = []
  let mut sqlc_version : String? = None
  let mut plugin_options : Bytes? = None
  let mut global_options : Bytes? = None
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => settings = Some(decode_embedded(dec, decode_settings))
      (2, WT_LEN) => catalog = Some(decode_embedded(dec, decode_catalog))
      (3, WT_LEN) => queries.push(decode_embedded(dec, decode_query))
      (4, WT_LEN) => sqlc_version = Some(dec.read_string())
      (5, WT_LEN) => plugin_options = Some(dec.read_bytes())
      (6, WT_LEN) => global_options = Some(dec.read_bytes())
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  if dec.has_error() {
    raise Failure::Failure("codec: corrupt protobuf")
  }
  let empty_codegen : Codegen = {
    out: "",
    plugin: "",
    options: b"",
    env: [],
    process: None,
    wasm: None,
  }
  let settings_val = match settings {
    Some(v) => v
    None =>
      {
        version: "",
        engine: "",
        schema: [],
        queries: [],
        codegen: empty_codegen,
      }
  }
  let catalog_val = match catalog {
    Some(v) => v
    None => { comment: "", default_schema: "", name: "", schemas: [] }
  }
  let sqlc_version_val = match sqlc_version {
    Some(v) => v
    None => ""
  }
  let plugin_options_val = match plugin_options {
    Some(v) => v
    None => Bytes::default()
  }
  let global_options_val = match global_options {
    Some(v) => v
    None => Bytes::default()
  }
  {
    settings: settings_val,
    catalog: catalog_val,
    queries,
    sqlc_version: sqlc_version_val,
    plugin_options: plugin_options_val,
    global_options: global_options_val,
  }
}

// === GenerateResponse ===

///|
fn encode_generate_response(enc : Encoder, val : GenerateResponse) -> Unit {
  for i = 0; i < val.files.length(); i = i + 1 {
    enc.write_tag(1, WT_LEN)
    enc.write_embedded(fn(enc) { encode_file(enc, val.files[i]) })
  }
}

///|
fn decode_generate_response(dec : Decoder) -> GenerateResponse {
  let files : Array[File] = []
  while dec.remaining() > 0 {
    let (fn_num, wt) = dec.read_tag()
    match (fn_num, wt) {
      (1, WT_LEN) => files.push(decode_embedded(dec, decode_file))
      (_, _) => dec.skip_field(fn_num, wt)
    }
    if dec.has_error() {
      break
    }
  }
  { files, }
}

///|
fn encode_generate_request(enc : Encoder, val : GenerateRequest) -> Unit {
  enc.write_tag(1, WT_LEN)
  enc.write_embedded(fn(enc) { encode_settings(enc, val.settings) })
  enc.write_tag(2, WT_LEN)
  enc.write_embedded(fn(enc) { encode_catalog(enc, val.catalog) })
  for i = 0; i < val.queries.length(); i = i + 1 {
    enc.write_tag(3, WT_LEN)
    enc.write_embedded(fn(enc) { encode_query(enc, val.queries[i]) })
  }
  enc.write_tag(4, WT_LEN)
  enc.write_string(val.sqlc_version)
  enc.write_tag(5, WT_LEN)
  enc.write_varint(val.plugin_options.length())
  enc.write_bytes(val.plugin_options)
  enc.write_tag(6, WT_LEN)
  enc.write_varint(val.global_options.length())
  enc.write_bytes(val.global_options)
}

// === Public API ===

///|
pub fn decode_request(data : Bytes) -> GenerateRequest raise {
  let dec = Decoder::new(data)
  decode_generate_request(dec)
}

///|
pub fn encode_request(req : GenerateRequest) -> Bytes {
  let enc = Encoder::new()
  encode_generate_request(enc, req)
  enc.finish()
}

///|
pub fn encode_response(resp : GenerateResponse) -> Bytes {
  let enc = Encoder::new()
  encode_generate_response(enc, resp)
  enc.finish()
}

///|
pub fn decode_response(data : Bytes) -> GenerateResponse {
  let dec = Decoder::new(data)
  decode_generate_response(dec)
}

// === Tests ===

///|
test "decode_request: empty request" {
  let data : Bytes = b""
  let req = decode_request(data)
  inspect(req.settings.version, content="")
  inspect(req.queries.length(), content="0")
}

///|
test "decode_request: minimal request with sqlc_version" {
  let enc = Encoder::new()
  enc.write_tag(4, WT_LEN)
  enc.write_string("1.0.0")
  let data = enc.finish()
  let req = decode_request(data)
  inspect(req.sqlc_version, content="1.0.0")
  inspect(req.settings.version, content="")
}

///|
test "roundtrip: encode + decode GenerateResponse" {
  let resp : GenerateResponse = {
    files: [{ name: "test.txt", contents: b"hello" }],
  }
  let data = encode_response(resp)
  let dec = Decoder::new(data)
  let decoded = decode_generate_response(dec)
  inspect(decoded.files.length(), content="1")
  inspect(decoded.files[0].name, content="test.txt")
  inspect(decoded.files[0].contents, content="b\"hello\"")
}

///|
test "roundtrip: encode + decode File" {
  let f : File = { name: "query/users.go", contents: b"package db\n" }
  let enc = Encoder::new()
  encode_file(enc, f)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_file(dec)
  inspect(decoded.name, content="query/users.go")
  inspect(decoded.contents, content="b\"package db\\x0a\"")
}

///|
test "roundtrip: encode + decode Identifier" {
  let id : Identifier = { catalog: "cat", schema: "pub", name: "users" }
  let enc = Encoder::new()
  encode_identifier(enc, id)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_identifier(dec)
  inspect(decoded.catalog, content="cat")
  inspect(decoded.schema, content="pub")
  inspect(decoded.name, content="users")
}

///|
test "roundtrip: encode + decode CodegenProcess" {
  let p : CodegenProcess = { cmd: "protoc" }
  let enc = Encoder::new()
  encode_codegen_process(enc, p)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_codegen_process(dec)
  inspect(decoded.cmd, content="protoc")
}

///|
test "roundtrip: encode + decode CodegenWASM" {
  let w : CodegenWASM = {
    url: "https://example.com/plugin.wasm",
    sha256: "abc123",
  }
  let enc = Encoder::new()
  encode_codegen_wasm(enc, w)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_codegen_wasm(dec)
  inspect(decoded.url, content="https://example.com/plugin.wasm")
  inspect(decoded.sha256, content="abc123")
}

///|
test "roundtrip: encode + decode Column" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let col : Column = {
    name: "id",
    not_null: true,
    is_array: false,
    comment: "",
    length: 0,
    is_named_param: false,
    is_func_call: false,
    scope: "",
    table: empty_id,
    table_alias: "",
    ty: empty_id,
    is_sqlc_slice: false,
    embed_table: empty_id,
    original_name: "id",
    unsigned: false,
    array_dims: 0,
  }
  let enc = Encoder::new()
  encode_column(enc, col)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_column(dec)
  inspect(decoded.name, content="id")
  inspect(decoded.not_null, content="true")
}

///|
test "varint: roundtrip" {
  let values : Array[Int] = [0, 1, 127, 128, 255, 300, 1000000]
  for i = 0; i < values.length(); i = i + 1 {
    let enc = Encoder::new()
    enc.write_varint(values[i])
    let data = enc.finish()
    let dec = Decoder::new(data)
    let decoded = dec.read_varint()
    inspect(decoded, content="\{values[i]}")
  }
}

///|
test "string: roundtrip ASCII" {
  let s = "hello world"
  let enc = Encoder::new()
  enc.write_string(s)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_string()
  inspect(decoded, content="hello world")
}

///|
test "string: roundtrip UTF-8 non-ASCII" {
  let s = "你好世界"
  let enc = Encoder::new()
  enc.write_string(s)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_string()
  inspect(decoded, content="你好世界")
}

///|
test "bytes: roundtrip" {
  let bs : Bytes = b"\x00\x01\x02\xFF\xFE"
  let enc = Encoder::new()
  enc.write_varint(bs.length())
  enc.write_bytes(bs)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_bytes()
  inspect(decoded.length(), content="5")
  inspect(decoded[0], content="b'\\x00'")
}

///|
test "roundtrip: CompositeType" {
  let ct : CompositeType = { name: "address", comment: "A physical address" }
  let enc = Encoder::new()
  encode_composite_type(enc, ct)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_composite_type(dec)
  inspect(decoded.name, content="address")
  inspect(decoded.comment, content="A physical address")
}

///|
test "roundtrip: Enum" {
  let e : Enum = {
    name: "user_role",
    vals: ["admin", "user", "guest"],
    comment: "User roles",
  }
  let enc = Encoder::new()
  encode_enum(enc, e)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_enum(dec)
  inspect(decoded.name, content="user_role")
  inspect(decoded.vals.length(), content="3")
  inspect(decoded.vals[0], content="admin")
  inspect(decoded.vals[1], content="user")
  inspect(decoded.vals[2], content="guest")
  inspect(decoded.comment, content="User roles")
}

///|
test "roundtrip: Parameter" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let p : Parameter = {
    number: 1,
    column: {
      name: "id",
      not_null: true,
      is_array: false,
      comment: "",
      length: 0,
      is_named_param: false,
      is_func_call: false,
      scope: "",
      table: empty_id,
      table_alias: "",
      ty: empty_id,
      is_sqlc_slice: false,
      embed_table: empty_id,
      original_name: "",
      unsigned: false,
      array_dims: 0,
    },
  }
  let enc = Encoder::new()
  encode_parameter(enc, p)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_parameter(dec)
  inspect(decoded.number, content="1")
  inspect(decoded.column.name, content="id")
}

///|
test "roundtrip: Query" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let q : Query = {
    text: "SELECT * FROM users WHERE id = $1",
    name: "GetUser",
    cmd: ":one",
    columns: [
      {
        name: "id",
        not_null: true,
        is_array: false,
        comment: "",
        length: 0,
        is_named_param: false,
        is_func_call: false,
        scope: "",
        table: empty_id,
        table_alias: "",
        ty: empty_id,
        is_sqlc_slice: false,
        embed_table: empty_id,
        original_name: "",
        unsigned: false,
        array_dims: 0,
      },
    ],
    params: [
      {
        number: 1,
        column: {
          name: "id",
          not_null: true,
          is_array: false,
          comment: "",
          length: 0,
          is_named_param: false,
          is_func_call: false,
          scope: "",
          table: empty_id,
          table_alias: "",
          ty: empty_id,
          is_sqlc_slice: false,
          embed_table: empty_id,
          original_name: "",
          unsigned: false,
          array_dims: 0,
        },
      },
    ],
    comments: ["fetch a user by ID"],
    filename: "query.sql",
    insert_into_table: empty_id,
  }
  let enc = Encoder::new()
  encode_query(enc, q)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_query(dec)
  inspect(decoded.name, content="GetUser")
  inspect(decoded.cmd, content=":one")
  inspect(decoded.columns.length(), content="1")
  inspect(decoded.params.length(), content="1")
}

///|
test "roundtrip: Codegen with process and wasm" {
  let cg : Codegen = {
    out: "gen/",
    plugin: "moonbit",
    options: b"--package db",
    env: ["PATH=/usr/bin"],
    process: Some({ cmd: "protoc" }),
    wasm: Some({ url: "https://example.com/plugin.wasm", sha256: "abc" }),
  }
  let enc = Encoder::new()
  encode_codegen(enc, cg)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_codegen(dec)
  inspect(decoded.out, content="gen/")
  inspect(decoded.plugin, content="moonbit")
  inspect(
    match decoded.process {
      Some(v) => v.cmd
      None => ""
    },
    content="protoc",
  )
  inspect(
    match decoded.wasm {
      Some(v) => v.url
      None => ""
    },
    content="https://example.com/plugin.wasm",
  )
}

///|
test "roundtrip: Schema with tables, enums, composite_types" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let schema : Schema = {
    comment: "public schema",
    name: "public",
    tables: [
      {
        rel: { catalog: "db", schema: "public", name: "users" },
        columns: [
          {
            name: "id",
            not_null: true,
            is_array: false,
            comment: "",
            length: 0,
            is_named_param: false,
            is_func_call: false,
            scope: "",
            table: empty_id,
            table_alias: "",
            ty: { catalog: "", schema: "", name: "int4" },
            is_sqlc_slice: false,
            embed_table: empty_id,
            original_name: "",
            unsigned: false,
            array_dims: 0,
          },
        ],
        comment: "users table",
      },
    ],
    enums: [{ name: "mood", vals: ["happy", "sad"], comment: "" }],
    composite_types: [{ name: "address", comment: "" }],
  }
  let enc = Encoder::new()
  encode_schema(enc, schema)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_schema(dec)
  inspect(decoded.name, content="public")
  inspect(decoded.tables.length(), content="1")
  inspect(decoded.tables[0].rel.name, content="users")
  inspect(decoded.enums.length(), content="1")
  inspect(decoded.enums[0].name, content="mood")
  inspect(decoded.composite_types.length(), content="1")
  inspect(decoded.composite_types[0].name, content="address")
}

///|
test "roundtrip: Settings" {
  let settings : Settings = {
    version: "1",
    engine: "postgresql",
    schema: ["CREATE TABLE users (id int4)"],
    queries: ["-- name: GetUser :one\nSELECT * FROM users"],
    codegen: {
      out: "gen/",
      plugin: "moonbit",
      options: b"",
      env: [],
      process: None,
      wasm: None,
    },
  }
  let enc = Encoder::new()
  encode_settings(enc, settings)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_settings(dec)
  inspect(decoded.version, content="1")
  inspect(decoded.engine, content="postgresql")
  inspect(decoded.schema.length(), content="1")
  inspect(decoded.queries.length(), content="1")
}

///|
test "roundtrip: Catalog" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let cat : Catalog = {
    comment: "main catalog",
    default_schema: "public",
    name: "testdb",
    schemas: [
      {
        comment: "",
        name: "public",
        tables: [
          {
            rel: { catalog: "testdb", schema: "public", name: "users" },
            columns: [
              {
                name: "id",
                not_null: true,
                is_array: false,
                comment: "",
                length: 0,
                is_named_param: false,
                is_func_call: false,
                scope: "",
                table: empty_id,
                table_alias: "",
                ty: { catalog: "", schema: "", name: "int4" },
                is_sqlc_slice: false,
                embed_table: empty_id,
                original_name: "",
                unsigned: false,
                array_dims: 0,
              },
            ],
            comment: "",
          },
        ],
        enums: [],
        composite_types: [],
      },
    ],
  }
  let enc = Encoder::new()
  encode_catalog(enc, cat)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = decode_catalog(dec)
  inspect(decoded.name, content="testdb")
  inspect(decoded.default_schema, content="public")
  inspect(decoded.schemas.length(), content="1")
  inspect(decoded.schemas[0].tables.length(), content="1")
}

///|
test "decode_request: full request with all fields" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  // Build a CodeGenRequest manually through the encoder
  let enc = Encoder::new()
  // settings (field 1)
  enc.write_tag(1, WT_LEN)
  enc.write_embedded(fn(enc) {
    encode_settings(enc, {
      version: "1",
      engine: "postgresql",
      schema: ["CREATE TABLE users"],
      queries: ["-- name: GetUser :one\nSELECT * FROM users"],
      codegen: {
        out: "gen/",
        plugin: "moonbit",
        options: b"",
        env: [],
        process: None,
        wasm: None,
      },
    })
  })
  // catalog (field 2)
  enc.write_tag(2, WT_LEN)
  enc.write_embedded(fn(enc) {
    encode_catalog(enc, {
      comment: "",
      default_schema: "public",
      name: "testdb",
      schemas: [
        {
          comment: "",
          name: "public",
          tables: [
            {
              rel: { catalog: "testdb", schema: "public", name: "users" },
              columns: [
                {
                  name: "id",
                  not_null: true,
                  is_array: false,
                  comment: "",
                  length: 0,
                  is_named_param: false,
                  is_func_call: false,
                  scope: "",
                  table: empty_id,
                  table_alias: "",
                  ty: empty_id,
                  is_sqlc_slice: false,
                  embed_table: empty_id,
                  original_name: "",
                  unsigned: false,
                  array_dims: 0,
                },
              ],
              comment: "",
            },
          ],
          enums: [],
          composite_types: [],
        },
      ],
    })
  })
  // query (field 3)
  enc.write_tag(3, WT_LEN)
  enc.write_embedded(fn(enc) {
    encode_query(enc, {
      text: "SELECT * FROM users WHERE id = $1",
      name: "GetUser",
      cmd: ":one",
      columns: [
        {
          name: "id",
          not_null: true,
          is_array: false,
          comment: "",
          length: 0,
          is_named_param: false,
          is_func_call: false,
          scope: "",
          table: empty_id,
          table_alias: "",
          ty: empty_id,
          is_sqlc_slice: false,
          embed_table: empty_id,
          original_name: "",
          unsigned: false,
          array_dims: 0,
        },
      ],
      params: [
        {
          number: 1,
          column: {
            name: "id",
            not_null: true,
            is_array: false,
            comment: "",
            length: 0,
            is_named_param: false,
            is_func_call: false,
            scope: "",
            table: empty_id,
            table_alias: "",
            ty: empty_id,
            is_sqlc_slice: false,
            embed_table: empty_id,
            original_name: "",
            unsigned: false,
            array_dims: 0,
          },
        },
      ],
      comments: [],
      filename: "query.sql",
      insert_into_table: empty_id,
    })
  })
  // sqlc_version (field 4)
  enc.write_tag(4, WT_LEN)
  enc.write_string("1.53.0")
  let data = enc.finish()
  let decoded = decode_request(data)
  inspect(decoded.sqlc_version, content="1.53.0")
  inspect(decoded.queries.length(), content="1")
  inspect(decoded.queries[0].name, content="GetUser")
  inspect(decoded.settings.engine, content="postgresql")
  inspect(decoded.catalog.name, content="testdb")
}

///|
test "varint: single byte values 0..127" {
  for i = 0; i < 128; i = i + 1 {
    let enc = Encoder::new()
    enc.write_varint(i)
    let data = enc.finish()
    let dec = Decoder::new(data)
    let decoded = dec.read_varint()
    inspect(decoded, content="\{i}")
  }
}

///|
test "varint: multi-byte positive values" {
  let values : Array[Int] = [128, 255, 300, 1000000, 2147483647]
  for i = 0; i < values.length(); i = i + 1 {
    let enc = Encoder::new()
    enc.write_varint(values[i])
    let data = enc.finish()
    let dec = Decoder::new(data)
    let decoded = dec.read_varint()
    inspect(decoded, content="\{values[i]}")
  }
}

///|
test "string: empty" {
  let enc = Encoder::new()
  enc.write_string("")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_string()
  inspect(decoded, content="")
}

///|
test "bytes: empty" {
  let bs : Bytes = b""
  let enc = Encoder::new()
  enc.write_varint(bs.length())
  enc.write_bytes(bs)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_bytes()
  inspect(decoded.length(), content="0")
}

///|
test "bytes: single byte" {
  let bs : Bytes = b"\xAB"
  let enc = Encoder::new()
  enc.write_varint(bs.length())
  enc.write_bytes(bs)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let decoded = dec.read_bytes()
  inspect(decoded.length(), content="1")
}

///|
test "skip_field: varint" {
  let enc = Encoder::new()
  enc.write_tag(1, WT_VARINT)
  enc.write_varint(42)
  enc.write_tag(2, WT_LEN)
  enc.write_string("hello")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (field_num1, wt1) = dec.read_tag()
  inspect(field_num1, content="1")
  inspect(wt1, content="0")
  dec.skip_field(field_num1, wt1)
  let (field_num2, wt2) = dec.read_tag()
  inspect(field_num2, content="2")
  inspect(wt2, content="2")
}

///|
test "skip_field: length-delimited" {
  let enc = Encoder::new()
  enc.write_tag(1, WT_LEN)
  enc.write_string("skip_this")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (field_num, wt) = dec.read_tag()
  dec.skip_field(field_num, wt)
  inspect(dec.remaining(), content="0")
}

///|
test "roundtrip: null vs not-null Column" {
  let empty_id : Identifier = { catalog: "", schema: "", name: "" }
  let col_nullable : Column = {
    name: "email",
    not_null: false,
    is_array: false,
    comment: "",
    length: 0,
    is_named_param: false,
    is_func_call: false,
    scope: "",
    table: empty_id,
    table_alias: "",
    ty: empty_id,
    is_sqlc_slice: false,
    embed_table: empty_id,
    original_name: "",
    unsigned: false,
    array_dims: 0,
  }
  let col_notnull : Column = {
    name: "id",
    not_null: true,
    is_array: false,
    comment: "",
    length: 0,
    is_named_param: false,
    is_func_call: false,
    scope: "",
    table: empty_id,
    table_alias: "",
    ty: empty_id,
    is_sqlc_slice: false,
    embed_table: empty_id,
    original_name: "",
    unsigned: false,
    array_dims: 0,
  }
  // Wrap each column in an embedded (length-delimited) message for boundary
  let enc = Encoder::new()
  enc.write_tag(1, WT_LEN)
  enc.write_embedded(fn(enc) { encode_column(enc, col_nullable) })
  enc.write_tag(2, WT_LEN)
  enc.write_embedded(fn(enc) { encode_column(enc, col_notnull) })
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (_fn1, _wt1) = dec.read_tag()
  let decoded1 = decode_embedded(dec, decode_column)
  let (_fn2, _wt2) = dec.read_tag()
  let decoded2 = decode_embedded(dec, decode_column)
  inspect(decoded1.not_null, content="false")
  inspect(decoded2.not_null, content="true")
}

///|
test "varint: near-max int32 value roundtrip" {
  let enc = Encoder::new()
  enc.write_varint(2147483647)
  let data = enc.finish()
  let dec = Decoder::new(data)
  inspect(dec.read_varint(), content="2147483647")
}

///|
test "varint: encoding with 5-byte max prevents infinite loop" {
  // 5 continuation bytes forces the encoder's max-iteration guard to activate
  // Instead of looping forever with arithmetic right-shift on negative values,
  // the encoder stops after 5 bytes and emits the masked final byte.
  let enc = Encoder::new()
  // Use a value that triggers arithmetic shift edge case on wasm32
  enc.write_varint(-1)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let _ = dec.read_varint()
  inspect(data.length() > 0, content="true")
}

///|
test "varint: 11 continuation bytes without terminator sets error" {
  // Protobuf max varint is 10 bytes; an 11th continuation byte is corrupt.
  let buf = Bytes::from_array([
    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00,
  ])
  let dec = Decoder::new(buf)
  let _ = dec.read_varint()
  inspect(dec.has_error(), content="true")
}

///|
test "read_string: negative len returns empty" {
  // 5 continuation bytes produce a negative value; read_string treats <=0 as empty
  let buf = Bytes::from_array([0x80, 0x80, 0x80, 0x80, 0x80])
  let dec = Decoder::new(buf)
  let s = dec.read_string()
  inspect(s, content="")
}

///|
test "read_bytes: exact boundary succeeds" {
  let buf = Bytes::from_array([0x03, 0x01, 0x02, 0x03])
  let dec = Decoder::new(buf)
  // read_varint = 3, exactly 3 bytes available
  let bs = dec.read_bytes()
  inspect(bs.length(), content="3")
}

///|
test "read_bytes: normal 5 bytes" {
  let buf = Bytes::from_array([0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F])
  let dec = Decoder::new(buf)
  // read_varint = 5, 5 bytes available
  let bs = dec.read_bytes()
  inspect(bs.length(), content="5")
  inspect(bs[0].to_int(), content="72")
  inspect(bs[4].to_int(), content="111")
}

///|
test "decode_embedded: len 0 creates empty sub-decoder" {
  let buf = Bytes::from_array([0x00])
  let dec = Decoder::new(buf)
  let result = decode_embedded(dec, fn(sub) { sub.remaining() })
  inspect(result, content="0")
}

///|
test "decode_embedded: negative len returns empty sub-decoder" {
  // 5 continuation bytes that decode to a negative value
  let buf = Bytes::from_array([0x80, 0x80, 0x80, 0x80, 0x80, 0x00])
  let dec = Decoder::new(buf)
  let result = decode_embedded(dec, fn(sub) { sub.remaining() })
  inspect(result, content="0")
}

///|
test "decode_embedded: len exceeds buffer returns empty sub-decoder" {
  // Varint value 999 with only 1 byte of buffer data — length exceeds buffer
  let b : Int = 0x41
  let enc = Encoder::new()
  enc.write_varint(999)
  enc.write_byte(b.to_byte())
  let data = enc.finish()
  let dec = Decoder::new(data)
  let result = decode_embedded(dec, fn(sub) { sub.remaining() })
  inspect(result, content="0")
}

///|
test "roundtrip: encode_request with empty plugin_options" {
  let req : GenerateRequest = {
    settings: {
      version: "1",
      engine: "postgresql",
      schema: [],
      queries: [],
      codegen: {
        out: "",
        plugin: "",
        options: b"",
        env: [],
        process: None,
        wasm: None,
      },
    },
    catalog: { comment: "", default_schema: "", name: "", schemas: [] },
    queries: [],
    sqlc_version: "1.53.0",
    plugin_options: b"",
    global_options: b"",
  }
  let data = encode_request(req)
  let decoded = decode_request(data)
  inspect(decoded.plugin_options.length(), content="0")
  inspect(decoded.global_options.length(), content="0")
  inspect(decoded.sqlc_version, content="1.53.0")
}

///|
test "roundtrip: encode_request with non-empty plugin_options" {
  let req : GenerateRequest = {
    settings: {
      version: "1",
      engine: "postgresql",
      schema: [],
      queries: [],
      codegen: {
        out: "",
        plugin: "",
        options: b"",
        env: [],
        process: None,
        wasm: None,
      },
    },
    catalog: { comment: "", default_schema: "", name: "", schemas: [] },
    queries: [],
    sqlc_version: "1.53.0",
    plugin_options: b"--package db",
    global_options: b"",
  }
  let data = encode_request(req)
  let decoded = decode_request(data)
  inspect(decoded.plugin_options.length(), content="12")
}

///|
test "skip_field: unknown wire type skips to end" {
  // Unknown wire type 7 with data byte 0xFF — skip_field should jump to end
  let val : Int = 0xFF
  let enc = Encoder::new()
  enc.write_tag(1, 7)
  enc.write_byte(val.to_byte())
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (fn_num, wt) = dec.read_tag()
  dec.skip_field(fn_num, wt)
  inspect(dec.remaining(), content="0")
}

///|
test "read_fixed32: decodes 4 LE bytes" {
  let buf = Bytes::from_array([0x78, 0x56, 0x34, 0x12])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  inspect(val, content="305419896")
}

///|
test "read_fixed32: zero value" {
  let buf = Bytes::from_array([0x00, 0x00, 0x00, 0x00])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  inspect(val, content="0")
}

///|
test "read_fixed32: all-ones value (wraps to -1 as Int)" {
  let buf = Bytes::from_array([0xFF, 0xFF, 0xFF, 0xFF])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  // 0xFFFFFFFF as 32-bit signed Int = -1
  inspect(val, content="-1")
}

///|
test "read_fixed64: decodes 8 LE bytes" {
  let buf = Bytes::from_array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  // 0x0807060504030201 = 578437695752307201
  inspect(val, content="578437695752307201")
}

///|
test "read_fixed64: zero value" {
  let buf = Bytes::from_array([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  inspect(val, content="0")
}

///|
test "read_fixed64: small value (42)" {
  let buf = Bytes::from_array([0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  inspect(val, content="42")
}

///|
test "skip_field: WT_BITS64 (wire type 1) skips 8 bytes" {
  let enc = Encoder::new()
  enc.write_tag(1, WT_BITS64)
  enc.write_byte((0x01).to_byte())
  enc.write_byte((0x02).to_byte())
  enc.write_byte((0x03).to_byte())
  enc.write_byte((0x04).to_byte())
  enc.write_byte((0x05).to_byte())
  enc.write_byte((0x06).to_byte())
  enc.write_byte((0x07).to_byte())
  enc.write_byte((0x08).to_byte())
  enc.write_tag(2, WT_LEN)
  enc.write_string("ok")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (fn_num, wt) = dec.read_tag()
  dec.skip_field(fn_num, wt)
  let (fn2, wt2) = dec.read_tag()
  inspect(fn2, content="2")
  inspect(wt2, content="2")
  let s = dec.read_string()
  inspect(s, content="ok")
}

///|
test "skip_field: WT_BITS32 (wire type 5) skips 4 bytes" {
  let enc = Encoder::new()
  enc.write_tag(1, WT_BITS32)
  enc.write_byte((0xDE).to_byte())
  enc.write_byte((0xAD).to_byte())
  enc.write_byte((0xBE).to_byte())
  enc.write_byte((0xEF).to_byte())
  enc.write_tag(2, WT_LEN)
  enc.write_string("ok")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let (fn_num, wt) = dec.read_tag()
  dec.skip_field(fn_num, wt)
  let (fn2, wt2) = dec.read_tag()
  inspect(fn2, content="2")
  inspect(wt2, content="2")
  let s = dec.read_string()
  inspect(s, content="ok")
}

// === P1-039: Codec error path boundary tests ===

///|
/// P1-039: read_string with len exactly equal to available bytes (boundary, should succeed).
test "P1-039: read_string len == available (boundary success)" {
  let p039_buf = Bytes::from_array([0x05, 0x48, 0x65, 0x6C, 0x6C, 0x6F])
  let p039_dec = Decoder::new(p039_buf)
  let p039_s = p039_dec.read_string()
  inspect(p039_s, content="Hello")
  inspect(p039_dec.remaining(), content="0")
}

///|
/// P1-039: read_string with len == remaining on exact boundary.
test "P1-039: read_string len == remaining on exact boundary" {
  // Varint=1 with 1 data byte
  let p039_buf2 = Bytes::from_array([0x01, 0x41])
  let p039_dec2 = Decoder::new(p039_buf2)
  let p039_s2 = p039_dec2.read_string()
  inspect(p039_s2, content="A")
  inspect(p039_dec2.remaining(), content="0")
}

///|
/// P1-039: decode_embedded with len == remaining (exact boundary).
test "P1-039: decode_embedded len == remaining (exact boundary)" {
  let p039_enc = Encoder::new()
  p039_enc.write_tag(1, WT_LEN)
  p039_enc.write_string("A")
  let p039_data = p039_enc.finish()
  let p039_dec3 = Decoder::new(p039_data)
  let (fnum039, wt039) = p039_dec3.read_tag()
  inspect(fnum039, content="1")
  inspect(wt039, content="2")
  let p039_str = p039_dec3.read_string()
  inspect(p039_str, content="A")
  inspect(p039_dec3.remaining(), content="0")
}

///|
/// P1-039: skip_field WT_LEN with length that exceeds buffer — pos clamped to end.
test "P1-039: skip_field WT_LEN exceeds buffer clamps pos to end" {
  // Write tag (field=1, WT_LEN=2), varint len=100, plus 1 payload byte
  let p039_enc5 = Encoder::new()
  p039_enc5.write_tag(1, WT_LEN)
  p039_enc5.write_varint(100)
  p039_enc5.write_byte((0x41).to_byte())
  let p039_data5 = p039_enc5.finish()
  let p039_dec16 = Decoder::new(p039_data5)
  let (fnum039g, wt039g) = p039_dec16.read_tag()
  p039_dec16.skip_field(fnum039g, wt039g)
  inspect(p039_dec16.remaining(), content="0")
}

///|
/// P1-039: skip_field WT_LEN with large negative wrapping varint.
/// 5 continuation bytes produce a large value; guard clamps to end.
test "P1-039: skip_field WT_LEN negative varint clamps pos" {
  let p039_buf5 = Bytes::from_array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00])
  let p039_dec5 = Decoder::new(p039_buf5)
  let (fnum039b, wt039b) = p039_dec5.read_tag()
  p039_dec5.skip_field(fnum039b, wt039b)
  inspect(p039_dec5.remaining() >= 0, content="true")
}

///|
/// P0-061: read_byte with valid input returns correct byte.
test "P0-061: read_byte with valid input returns correct byte" {
  let buf = Bytes::from_array([0x42])
  let dec = Decoder::new(buf)
  let b = dec.read_byte()
  inspect(b.to_int(), content="66") // 'B' = 66
  inspect(dec.remaining(), content="0")
}

///|
/// P0-061: read_byte on empty buffer sets error flag (P0-070).
test "P0-061: read_byte with zero-length buffer does not return value" {
  let dec = Decoder::new(b"")
  let _b = dec.read_byte()
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="0")
}

///|
/// P1-039: read_varint on empty buffer returns 0.
test "P1-039: read_varint on empty buffer returns 0" {
  let p039_dec8 = Decoder::new(b"")
  inspect(p039_dec8.read_varint(), content="0")
}

///|
/// P1-039: read_varint with all continuation bytes (max 10) followed by terminator.
test "P1-039: read_varint max continuation bytes with terminator" {
  // 9 continuation bytes (0x80) + 1 terminator (0x01) = valid 10-byte varint (sqlc length=-1)
  let p039_buf9 = Bytes::from_array([
    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01,
  ])
  let p039_dec9 = Decoder::new(p039_buf9)
  let _ = p039_dec9.read_varint()
  inspect(p039_dec9.has_error(), content="false")
  inspect(p039_dec9.remaining(), content="0")
}

///|
/// P1-039: decode_embedded with zero bytes (len=0 varint only).
test "P1-039: decode_embedded zero-length returns empty sub-decoder" {
  let p039_buf10 = Bytes::from_array([0x00])
  let p039_dec10 = Decoder::new(p039_buf10)
  let p039_result = decode_embedded(p039_dec10, fn(sub) { sub.remaining() })
  inspect(p039_result, content="0")
}

///|
/// P1-039: skip_field wire type 3 (start group) triggers skip_group.
test "P1-039: skip_field wire type 3 triggers skip_group" {
  let p039_enc2 = Encoder::new()
  p039_enc2.write_tag(1, 3) // SGROUP
  p039_enc2.write_byte((0x00).to_byte())
  p039_enc2.write_tag(2, WT_LEN)
  p039_enc2.write_string("ok")
  let p039_data2 = p039_enc2.finish()
  let p039_dec11 = Decoder::new(p039_data2)
  let (fnum039c, wt039c) = p039_dec11.read_tag()
  inspect(wt039c, content="3")
  p039_dec11.skip_field(fnum039c, wt039c)
  inspect(p039_dec11.remaining(), content="0")
}

///|
/// P1-056: skip_field wire type 4 (EndGroup) outside group context sets error.
test "P1-056: skip_field wire type 4 sets error" {
  let p056_enc = Encoder::new()
  p056_enc.write_tag(1, 4) // orphaned EndGroup
  let p056_data = p056_enc.finish()
  let p056_dec = Decoder::new(p056_data)
  let (fnum056, wt056) = p056_dec.read_tag()
  inspect(wt056, content="4")
  p056_dec.skip_field(fnum056, wt056)
  inspect(p056_dec.has_error(), content="true")
  inspect(p056_dec.remaining(), content="0")
}

///|
/// P1-039: read_fixed32 at buffer boundary (exactly 4 bytes).
test "P1-039: read_fixed32 at exact buffer boundary" {
  let p039_buf13 = Bytes::from_array([0x01, 0x00, 0x00, 0x00])
  let p039_dec13 = Decoder::new(p039_buf13)
  inspect(p039_dec13.read_fixed32(), content="1")
  inspect(p039_dec13.remaining(), content="0")
}

///|
/// P1-039: read_fixed64 at buffer boundary (exactly 8 bytes).
test "P1-039: read_fixed64 at exact buffer boundary" {
  let p039_buf14 = Bytes::from_array([
    0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  ])
  let p039_dec14 = Decoder::new(p039_buf14)
  inspect(p039_dec14.read_fixed64(), content="42")
  inspect(p039_dec14.remaining(), content="0")
}

///|
/// P1-039: skip_field unknown wire type (6) should also skip to end.
test "P1-039: skip_field unknown wire type 6 skips to end" {
  let p039_enc4 = Encoder::new()
  p039_enc4.write_tag(1, 6)
  p039_enc4.write_byte((0xFF).to_byte())
  let p039_data4 = p039_enc4.finish()
  let p039_dec15 = Decoder::new(p039_data4)
  let (fnum039f, wt039f) = p039_dec15.read_tag()
  p039_dec15.skip_field(fnum039f, wt039f)
  inspect(p039_dec15.remaining(), content="0")
}

// === P0-061: Codec bounds hardening tests ===

///|
/// P0-061: read_fixed32 with exactly 4 bytes succeeds (boundary).
test "P0-061: read_fixed32 exact boundary 4 bytes" {
  let buf = Bytes::from_array([0x01, 0x02, 0x03, 0x04])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  // LE: 0x04030201 = 67305985
  inspect(val, content="67305985")
  inspect(dec.remaining(), content="0")
}

///|
/// P0-061: read_fixed32 with more than 4 bytes only consumes 4.
test "P0-061: read_fixed32 with 5 bytes only consumes 4" {
  let buf = Bytes::from_array([0x01, 0x00, 0x00, 0x00, 0xFF])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  inspect(val, content="1")
  inspect(dec.remaining(), content="1")
  // The remaining byte is 0xFF, not consumed by read_fixed32
  let b = dec.read_byte()
  inspect(b.to_int(), content="255")
}

///|
/// P0-061: read_fixed64 with exactly 8 bytes succeeds (boundary).
test "P0-061: read_fixed64 exact boundary 8 bytes" {
  let buf = Bytes::from_array([0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  inspect(val, content="1")
  inspect(dec.remaining(), content="0")
}

///|
/// P0-061: read_fixed64 with more than 8 bytes only consumes 8.
test "P0-061: read_fixed64 with 10 bytes only consumes 8" {
  let buf = Bytes::from_array([
    0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xAA, 0xBB,
  ])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  // 0x0807060504030201 = 578437695752307201
  inspect(val, content="578437695752307201")
  inspect(dec.remaining(), content="2")
  // Remaining bytes are 0xAA, 0xBB
  let b1 = dec.read_byte()
  let b2 = dec.read_byte()
  inspect(b1.to_int(), content="170") // 0xAA
  inspect(b2.to_int(), content="187") // 0xBB
}

///|
/// P0-061: read_fixed32 with 3 bytes sets error flag (P0-070).
test "P0-061: read_fixed32 4 bytes boundary correct value" {
  // Test the exact boundary case: 4 bytes, fixed32 works correctly
  let buf = Bytes::from_array([0x78, 0x56, 0x34, 0x12])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed32()
  inspect(val, content="305419896")
  inspect(dec.remaining(), content="0")
}

///|
/// P0-061: read_fixed64 with 7 bytes sets error flag (P0-070).
test "P0-061: read_fixed64 8 bytes boundary correct value" {
  let buf = Bytes::from_array([0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00])
  let dec = Decoder::new(buf)
  let val = dec.read_fixed64()
  // 0x0000000100000000 = 4294967296
  inspect(val, content="4294967296")
  inspect(dec.remaining(), content="0")
}

///|
/// P0-061: read_varint never calls read_byte past end of buffer.
/// read_varint has its own remaining() check on each iteration.
/// This test validates that read_varint with exactly 1-byte non-continuation varint works.
test "P0-061: read_varint guard prevents OOB read_byte" {
  let buf = Bytes::from_array([0x01])
  let dec = Decoder::new(buf)
  let val = dec.read_varint()
  inspect(val, content="1")
  inspect(dec.remaining(), content="0")
}

// === P0-070: Codec abort → error flag ===

///|
test "P0-070: read_byte on empty buffer sets error flag" {
  let dec = Decoder::new(b"")
  let _b = dec.read_byte()
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="0")
}

///|
test "P0-070: read_string len exceeds available sets error" {
  // varint len=5 (0x05), only 1 payload byte available
  let dec = Decoder::new(Bytes::from_array([0x05, 0x41]))
  let s = dec.read_string()
  inspect(s, content="")
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="1")
}

///|
test "P0-070: read_bytes len exceeds available sets error" {
  let dec = Decoder::new(Bytes::from_array([0x05, 0x41]))
  let bs = dec.read_bytes()
  inspect(bs.length(), content="0")
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="1")
}

///|
test "P0-070: read_fixed32 insufficient bytes sets error" {
  let dec = Decoder::new(Bytes::from_array([0x01, 0x02, 0x03]))
  let val = dec.read_fixed32()
  inspect(val, content="0")
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="3")
}

///|
test "P0-070: read_fixed64 insufficient bytes sets error" {
  let dec = Decoder::new(
    Bytes::from_array([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]),
  )
  let val = dec.read_fixed64()
  inspect(val, content="0")
  inspect(dec.has_error(), content="true")
  inspect(dec.remaining(), content="7")
}

///|
test "P0-070: decode_request corrupt protobuf raises" {
  let enc = Encoder::new()
  enc.write_tag(4, WT_LEN)
  enc.write_varint(100)
  enc.write_byte((0x41).to_byte())
  let data = enc.finish()
  let result = try {
    let _ = decode_request(data)
    "unexpected_ok"
  } catch {
    _ => "raised"
  }
  inspect(result, content="raised")
}

// === P0-072: decode_embedded field-level error propagation ===

///|
/// P0-072: corrupt embedded length in settings sets error and stops sibling fields.
test "P0-072: decode_settings corrupt codegen length sets error" {
  let enc = Encoder::new()
  enc.write_tag(12, WT_LEN)
  enc.write_varint(999)
  let data = enc.finish()
  let dec = Decoder::new(data)
  let _ = decode_settings(dec)
  inspect(dec.has_error(), content="true")
}

///|
/// P0-072: sub-decoder error inside embedded message stops parsing sibling fields.
test "P0-072: decode_settings breaks after embedded sub-decoder error" {
  let inner = Encoder::new()
  inner.write_tag(1, WT_LEN)
  inner.write_varint(50)
  inner.write_byte((0x41).to_byte())
  let inner_bytes = inner.finish()

  let enc = Encoder::new()
  enc.write_tag(1, WT_LEN)
  enc.write_string("v1")
  enc.write_tag(12, WT_LEN)
  enc.write_varint(inner_bytes.length())
  enc.write_bytes(inner_bytes)
  enc.write_tag(2, WT_LEN)
  enc.write_string("postgres")
  let data = enc.finish()
  let dec = Decoder::new(data)
  let settings = decode_settings(dec)
  inspect(dec.has_error(), content="true")
  inspect(settings.version, content="v1")
  inspect(settings.engine, content="")
}

///|
/// P0-072: corrupt nested embedded propagates to decode_request raise.
test "P0-072: decode_request raises on nested embedded error" {
  let inner = Encoder::new()
  inner.write_tag(1, WT_LEN)
  inner.write_varint(50)
  inner.write_byte((0x41).to_byte())
  let inner_bytes = inner.finish()

  let settings_enc = Encoder::new()
  settings_enc.write_tag(12, WT_LEN)
  settings_enc.write_varint(inner_bytes.length())
  settings_enc.write_bytes(inner_bytes)
  let settings_bytes = settings_enc.finish()

  let enc = Encoder::new()
  enc.write_tag(1, WT_LEN)
  enc.write_varint(settings_bytes.length())
  enc.write_bytes(settings_bytes)
  let data = enc.finish()
  let result = try {
    let _ = decode_request(data)
    "unexpected_ok"
  } catch {
    _ => "raised"
  }
  inspect(result, content="raised")
}