///|
/// Compiles one supported Kaitai Struct description to self-contained MoonBit source.
pub fn generate(source : String) -> String raise SchemaError {
  let schema = load_schema(source)
  let output = StringBuilder()
  let id = schema.root.name
  let root_type = pascal_case(id)
  let root_record_type = root_type + "Record"
  let records = [schema.root, ..schema.records]
  let mut has_numeric_fields = false
  let mut has_uint64 = false
  let mut has_repeated_fields = false
  let mut has_contents = false
  let mut has_validation = false
  for record in records {
    for field in record.fields {
      has_numeric_fields = has_numeric_fields ||
        field.kind is Unsigned(_, _) ||
        field.kind is Signed(_, _) ||
        field.kind is Float32(_)
      has_uint64 = has_uint64 || field.kind is Unsigned64(_)
      has_repeated_fields = has_repeated_fields || field.count is Some(_)
      has_contents = has_contents || field.kind is Contents(_)
      has_validation = has_validation || field.validation is Some(_)
    }
  }
  output.write_string("// Generated by KsyMoon from \{id}.ksy.\n\n")
  for record in schema.records {
    emit_record_type(
      output,
      nested_type(root_type, record.name),
      record,
      root_type,
    )
  }
  emit_record_type(output, root_record_type, schema.root, root_type)
  emit_reader(
    output,
    root_type,
    has_numeric_fields~,
    has_uint64~,
    has_repeated_fields~,
    has_contents~,
    has_validation~,
  )
  for record in schema.records {
    let name = nested_reader(id, record.name)
    emit_record_reader(
      output,
      name,
      nested_type(root_type, record.name),
      record,
      id,
      root_type,
    )
  }
  emit_record_reader(
    output,
    "read_\{id}_root",
    root_record_type,
    schema.root,
    id,
    root_type,
  )
  output.write_string(
    "///|\npub fn parse_\{id}(input : Bytes) -> \{root_record_type} raise \{root_type}ParseError {\n",
  )
  let repeat_budget = if has_repeated_fields {
    ", repeats_left: 1_000_000"
  } else {
    ""
  }
  output.write_string(
    "  let reader : \{root_type}Reader = { input, offset: 0\{repeat_budget} }\n",
  )
  output.write_string("  read_\{id}_root(reader, \"\{id}\")\n}\n")
  output.to_string()
}

///|
fn pascal_case(name : String) -> String {
  let output = StringBuilder()
  for segment in name.split("_") {
    let segment = segment.to_owned()
    output.write_string(segment[0:1].to_owned().to_upper())
    output.write_stringview(segment[1:])
  }
  output.to_string()
}

///|
fn nested_type(root_type : String, name : String) -> String {
  "\{root_type}_\{name}"
}

///|
fn nested_reader(id : String, name : String) -> String {
  "read_\{id}_type_\{name}"
}

///|
fn field_type(kind : FieldKind, root_type : String) -> String {
  match kind {
    Unsigned(_, _) => "UInt"
    Unsigned64(_) => "UInt64"
    Signed(_, _) => "Int"
    Float32(_) => "Float"
    RawBytes(_) | Contents(_) => "BytesView"
    Nested(name) => nested_type(root_type, name)
  }
}

///|
fn emit_record_type(
  output : StringBuilder,
  type_name : String,
  record : RecordDef,
  root_type : String,
) -> Unit {
  output.write_string("///|\npub struct \{type_name} {\n")
  for field in record.fields {
    let element_type = field_type(field.kind, root_type)
    let type_name = if field.count is Some(_) {
      "Array[\{element_type}]"
    } else {
      element_type
    }
    output.write_string("  \{field.name} : \{type_name}\n")
  }
  output.write_string("}\n\n")
}

///|
fn length_code(expression : LengthExpr) -> String {
  match expression {
    Constant(value) => "\{value}L"
    EarlierField(name, 0L) => "v_\{name}.to_int64()"
    EarlierField(name, subtract) => "v_\{name}.to_int64() - \{subtract}L"
  }
}

///|
fn endian_flag(endian : Endian) -> String {
  match endian {
    Little => "true"
    Big => "false"
  }
}

///|
fn read_code(kind : FieldKind, id : String, path : String) -> String {
  match kind {
    Unsigned(width, endian) =>
      "reader.read_bits(\{width.to_int()}, \{endian_flag(endian)}, \{path})"
    Unsigned64(endian) =>
      "reader.read_uint64_bits(8, \{endian_flag(endian)}, \{path})"
    Signed(width, endian) => {
      let bits = "reader.read_bits(\{width.to_int()}, \{endian_flag(endian)}, \{path})"
      let shift = (4 - width.to_int()) * 8
      if shift == 0 {
        "\{bits}.reinterpret_as_int()"
      } else {
        "((\{bits} << \{shift}).reinterpret_as_int() >> \{shift})"
      }
    }
    Float32(endian) =>
      "Float::reinterpret_from_uint(reader.read_bits(4, \{endian_flag(endian)}, \{path}))"
    RawBytes(length) => "reader.take(\{length_code(length)}, \{path})"
    Contents(bytes) => {
      let values = bytes
        .iter()
        .map(fn(byte) { byte.to_int().to_string() })
        .collect()
      "reader.read_contents(Bytes::from_array([\{values.join(", ")}]), \{path})"
    }
    Nested(name) => "\{nested_reader(id, name)}(reader, \{path})"
  }
}

///|
fn emit_record_reader(
  output : StringBuilder,
  reader_name : String,
  type_name : String,
  record : RecordDef,
  id : String,
  root_type : String,
) -> Unit {
  output.write_string(
    "///|\nfn \{reader_name}(reader : \{root_type}Reader, path : String) -> \{type_name} raise \{root_type}ParseError {\n",
  )
  for field in record.fields {
    let name = field.name
    let field_path = "path_\{name}"
    output.write_string("  let \{field_path} = path + \".\{name}\"\n")
    match field.count {
      None => {
        output.write_string(
          "  let v_\{name} = \{read_code(field.kind, id, field_path)}\n",
        )
        if field.validation is Some(check) {
          output.write_string(
            "  reader.check_scalar(v_\{name}.to_int64(), \{check.width.to_int()}, \{check.minimum}L, \{check.maximum}L, \{field_path})\n",
          )
        }
      }
      Some(count) => {
        output.write_string(
          "  let count_\{name} = reader.reserve_repeats(\{length_code(count)}, \{field_path})\n",
        )
        output.write_string("  let v_\{name} = []\n")
        output.write_string("  for index in 0..