///|
fn Generator::enum_type(
  self : Generator,
  wire_type : String,
  cases : Array[EnumCase],
) -> String raise SbeError {
  let name = type_identifier(wire_type)
  if !self.claim(name, "enum:\{wire_type}") {
    return name
  }
  let variants : Map[String, Bool] = Map([])
  let out = self.output
  out.write_string("///|\npub(all) enum \{name} {\n")
  for entry in cases {
    let variant = type_identifier(entry.name)
    if variants.contains(variant) {
      raise InvalidSchema("generated enum case collision", 0)
    }
    variants[variant] = true
    out.write_string("  \{variant}\n")
  }
  out.write_string(
    "} derive(Debug, Eq)\n\n///|\npub fn \{name}::to_value(self : \{name}) -> @sbe.Value {\n  match self {\n",
  )
  for entry in cases {
    out.write_string(
      "    \{type_identifier(entry.name)} => @sbe.Text(\{code_quote(entry.name)})\n",
    )
  }
  out.write_string(
    "  }\n}\n\n///|\npub fn \{name}::from_value(value : @sbe.Value) -> \{name} raise @sbe.SbeError {\n  match value.as_text() {\n",
  )
  for entry in cases {
    out.write_string(
      "    \{code_quote(entry.name)} => \{type_identifier(entry.name)}\n",
    )
  }
  out.write_string(
    "    _ => raise @sbe.value_error(\{code_quote(wire_type)}, \"unknown enum name\")\n  }\n}\n\n",
  )
  name
}

///|
fn Generator::adapter(
  self : Generator,
  wire_type : String,
  wire_name : String,
  depth : Int32,
) -> GenField raise SbeError {
  if depth > 64 {
    raise LimitExceeded("code generation type depth exceeds 64")
  }
  match self.schema.type_def(wire_type) {
    Encoded(encoding) => {
      let base = scalar_adapter(encoding.primitive, wire_name)
      if encoding.primitive != Char && encoding.length != 1 {
        array_adapter(base)
      } else {
        base
      }
    }
    Enumeration(_, cases, _) => {
      let name = self.enum_type(wire_type, cases)
      {
        wire_name,
        name: field_identifier(wire_name),
        type_name: name,
        to_value: source => "\{source}.to_value()",
        from_value: source => "\{name}::from_value(\{source})",
      }
    }
    BitSet(_, _) => array_adapter(scalar_adapter(Char, wire_name))
    Composite(entries, _) => {
      let name = type_identifier(wire_type)
      if self.claim(name, "composite:\{wire_type}") {
        let fields : Array[GenField] = []
        for entry in entries {
          let nested_type = self.schema.type_def(entry.type_name)
          if type_presence(nested_type) == Constant {
            continue
          }
          let field = self.adapter(entry.type_name, entry.name, depth + 1)
          fields.push(
            if type_presence(nested_type) == Optional {
              optional_adapter(field)
            } else {
              field
            },
          )
        }
        self.record(name, fields)
      }
      {
        wire_name,
        name: field_identifier(wire_name),
        type_name: name,
        to_value: source => "\{source}.to_value()",
        from_value: source => "\{name}::from_value(\{source})",
      }
    }
  }
}

///|
fn Generator::record(
  self : Generator,
  name : String,
  fields : Array[GenField],
) -> Unit raise SbeError {
  let used : Map[String, Bool] = Map([])
  let out = self.output
  if fields.is_empty() {
    // MoonBit does not permit zero-field records. A unit marker preserves an
    // empty fixed/group record without adding a wire field.
    out.write_string(
      "///|\npub(all) struct \{name} { unit : Unit } derive(Debug)\n\n",
    )
  } else {
    out.write_string("///|\npub(all) struct \{name} {\n")
    for field in fields {
      if used.contains(field.name) {
        raise InvalidSchema("generated field name collision in \{name}", 0)
      }
      used[field.name] = true
      out.write_string("  \{field.name} : \{field.type_name}\n")
    }
    out.write_string("} derive(Debug)\n\n")
  }
  out.write_string(
    "///|\npub fn \{name}::to_value(self : \{name}) -> @sbe.Value {\n",
  )
  if fields.is_empty() {
    out.write_string("  ignore(self)\n")
  }
  out.write_string("  let fields : Map[String, @sbe.Value] = Map([])\n")
  for field in fields {
    out.write_string(
      "  fields[\{code_quote(field.wire_name)}] = \{(field.to_value)("self." + field.name)}\n",
    )
  }
  out.write_string(
    "  @sbe.Record(fields)\n}\n\n///|\npub fn \{name}::from_value(value : @sbe.Value) -> \{name} raise @sbe.SbeError {\n",
  )
  if fields.is_empty() {
    out.write_string("  ignore(value.as_record())\n  { unit: () }\n")
  } else {
    out.write_string("  {\n")
    for field in fields {
      let expression = "value.field(\{code_quote(field.wire_name)})"
      out.write_string(
        "    \{field.name}: \{(field.from_value)(expression)},\n",
      )
    }
    out.write_string("  }\n")
  }
  out.write_string("}\n\n")
}

///|
fn Generator::block_record(
  self : Generator,
  name : String,
  block : BlockDef,
  depth : Int32,
  identity : String,
) -> Unit raise SbeError {
  if depth > 32 {
    raise LimitExceeded("code generation group depth exceeds 32")
  }
  if !self.claim(name, "block:\{identity}") {
    return
  }
  let fields : Array[GenField] = []
  for field in block.fields {
    if field.presence == Constant {
      continue
    }
    let adapter = self.adapter(field.type_name, field.name, 0)
    fields.push(
      if field.presence == Optional || field.since_version > 0 {
        optional_adapter(adapter)
      } else {
        adapter
      },
    )
  }
  for group in block.groups {
    let group_type = "\{name}\{type_identifier(group.name)}"
    self.block_record(
      group_type,
      group.block,
      depth + 1,
      "\{identity}/\{group.id}",
    )
    let adapter = GenField::{
      wire_name: group.name,
      name: field_identifier(group.name),
      type_name: group_type,
      to_value: source => "\{source}.to_value()",
      from_value: source => "\{group_type}::from_value(\{source})",
    }
    let array = array_adapter(adapter)
    fields.push(
      if group.since_version > 0 {
        optional_adapter(array)
      } else {
        array
      },
    )
  }
  for data in block.data {
    let adapter = if data.character_encoding == "" {
      GenField::{
        wire_name: data.name,
        name: field_identifier(data.name),
        type_name: "Bytes",
        to_value: source => "@sbe.Binary(\{source})",
        from_value: source => "\{source}.as_binary()",
      }
    } else {
      scalar_adapter(Char, data.name)
    }
    fields.push(
      if data.since_version > 0 {
        optional_adapter(adapter)
      } else {
        adapter
      },
    )
  }
  self.record(name, fields)
}

///|
/// Generate typed MoonBit adapters from a compiled standard schema. Generated
/// encoders use the shared checked runtime; no zero-copy performance claim is
/// made. The result imports this module under the alias `sbe`.
pub fn generate_moonbit(
  xml : String,
  includes~ : Map[String, String] = Map([]),
) -> String raise SbeError {
  let schema = parse_schema(xml, includes~)
  let output = StringBuilder()
  output.write_string(
    "// Generated by liyun6666/moonsbe 0.1.0. Do not edit.\n// Schema ID \{schema.id}, schema version \{schema.version}.\n\n",
  )
  output.write_string(
    "///|\nlet compiled_schema : @sbe.Schema = try! @sbe.parse_schema(\n  \{code_quote(xml)},\n  includes=Map([\n",
  )
  for name, source in includes {
    output.write_string("    (\{code_quote(name)}, \{code_quote(source)}),\n")
  }
  output.write_string("  ]),\n)\n\n")
  let generator = Generator::{ schema, output, emitted: Map([]), }
  for message in schema.messages {
    let name = type_identifier(message.name)
    generator.block_record(name, message.block, 0, "\{message.id}")
    output.write_string(
      "///|\npub fn \{name}::encode(self : \{name}, version? : Int = \{schema.version}) -> Bytes raise @sbe.SbeError {\n  @sbe.encode(compiled_schema, \{message.id}, self.to_value(), version~)\n}\n\n",
    )
    output.write_string(
      "///|\npub fn \{name}::decode(bytes : Bytes) -> \{name} raise @sbe.SbeError {\n  let message = @sbe.decode(compiled_schema, bytes)\n  if message.header.template_id != \{message.id} {\n    raise @sbe.value_error(\"templateId\", \"wrong generated message type\")\n  }\n  \{name}::from_value(message.value)\n}\n\n",
    )
  }
  output.to_string()
}