// Descriptor construction from the proto form.

///|
/// The edition feature values that change how a declaration is interpreted.
/// Zero means "inherit", which is what proto2 and proto3 files use.
pub(all) struct Features {
  /// 0 inherit, 1 EXPLICIT, 2 IMPLICIT, 3 LEGACY_REQUIRED.
  field_presence : Int32
  /// 0 inherit, 1 OPEN, 2 CLOSED.
  enum_type : Int32
  /// 0 inherit, 1 PACKED, 2 EXPANDED.
  repeated_field_encoding : Int32
  /// 0 inherit, 1 LENGTH_PREFIXED, 2 DELIMITED.
  message_encoding : Int32
  /// 0 inherit, 1 VERIFY, 2 NONE.
  utf8_validation : Int32
  /// 0 inherit, 1 ALLOW, 2 LEGACY_BEST_EFFORT.
  json_format : Int32
}

///|
pub let features_auto : Features = {
  field_presence: 0,
  enum_type: 0,
  repeated_field_encoding: 0,
  message_encoding: 0,
  utf8_validation: 0,
  json_format: 0,
}

///|
/// Set one `features.` value, leaving the name alone when it is not a
/// feature. `value` is the identifier or boolean as written.
pub fn Features::set(
  self : Features,
  name : String,
  value : String,
) -> Features {
  let code = match value {
    "EXPLICIT" | "true" => 1
    "IMPLICIT" | "false" => 2
    "LEGACY_REQUIRED" => 3
    "OPEN" => 1
    "CLOSED" => 2
    "PACKED" => 1
    "EXPANDED" => 2
    "LENGTH_PREFIXED" => 1
    "DELIMITED" => 2
    "VERIFY" => 1
    "NONE" => 2
    "ALLOW" => 1
    "LEGACY_BEST_EFFORT" => 2
    _ => 0
  }
  match name {
    "field_presence" => { ..self, field_presence: code, }
    "enum_type" => { ..self, enum_type: code, }
    "repeated_field_encoding" => { ..self, repeated_field_encoding: code, }
    "message_encoding" => { ..self, message_encoding: code, }
    "utf8_validation" => { ..self, utf8_validation: code, }
    "json_format" => { ..self, json_format: code, }
    _ => self
  }
}

///|

///|
/// A child's features on top of its parent's: anything the child sets wins,
/// anything it leaves at zero is inherited.
pub fn Features::merge(parent : Features, child : Features) -> Features {
  fn pick(a : Int32, b : Int32) -> Int32 {
    if b != 0 {
      b
    } else {
      a
    }
  }
  {
    field_presence: pick(parent.field_presence, child.field_presence),
    enum_type: pick(parent.enum_type, child.enum_type),
    repeated_field_encoding: pick(
      parent.repeated_field_encoding,
      child.repeated_field_encoding,
    ),
    message_encoding: pick(parent.message_encoding, child.message_encoding),
    utf8_validation: pick(parent.utf8_validation, child.utf8_validation),
    json_format: pick(parent.json_format, child.json_format),
  }
}

///|
pub(all) struct FieldDescriptorProto {
  name : String
  number : Int32
  /// 1 optional, 2 required, 3 repeated.
  label : Int32
  /// 1..18, the `descriptor.proto` type numbers.
  type_code : Int32
  /// The referenced message or enum, dotted and possibly leading-dot.
  type_name : String?
  json_name : String?
  default_value : String?
  oneof_index : Int32?
  proto3_optional : Bool
  /// `[packed = false]` on a repeated numeric field.
  packed_off : Bool
  /// The message an `extend` block extends; present only on an extension.
  extendee : String?
  /// `features.*` written on the field, already merged with its parents.
  features : Features
  /// Options written on the field that are not features, as (name, value).
  options : Array[(String, String)]
}

///|
pub(all) struct OneofDescriptorProto {
  name : String
}

///|
pub(all) struct EnumValueDescriptorProto {
  name : String
  number : Int32
}

///|
pub(all) struct EnumDescriptorProto {
  name : String
  value : Array[EnumValueDescriptorProto]
  features : Features
  options : Array[(String, String)]
}

///|
pub(all) struct DescriptorProto {
  name : String
  field : Array[FieldDescriptorProto]
  nested_type : Array[DescriptorProto]
  enum_type : Array[EnumDescriptorProto]
  extension : Array[FieldDescriptorProto]
  oneof_decl : Array[OneofDescriptorProto]
  reserved_range : Array[(Int32, Int32)]
  reserved_name : Array[String]
  extension_range : Array[(Int32, Int32)]
  /// `options.map_entry`: this nested message is a map field's entry.
  map_entry : Bool
  features : Features
  options : Array[(String, String)]
}

///|
pub(all) struct MethodDescriptorProto {
  name : String
  input_type : String
  output_type : String
  client_streaming : Bool
  server_streaming : Bool
}

///|
pub(all) struct ServiceDescriptorProto {
  name : String
  methods : Array[MethodDescriptorProto]
}

///|
pub(all) struct FileDescriptorProto {
  name : String
  package_name : String
  /// "proto2", "proto3" or "editions".
  syntax : String
  message_type : Array[DescriptorProto]
  enum_type : Array[EnumDescriptorProto]
  service : Array[ServiceDescriptorProto]
  extension : Array[FieldDescriptorProto]
  dependency : Array[String]
  public_dependency : Array[Int32]
  /// `edition = "2023"` when the file declares one.
  edition : String?
  features : Features
  options : Array[(String, String)]
}

///|
/// A build refusal.
pub(all) suberror Refused {
  Name(String)
  Number(Int32)
  Type(String)
  Syntax(String)
}

///|
pub impl Show for Refused with fn output(self, logger) {
  match self {
    Name(name) => logger.write_string("bad name " + name)
    Number(number) => logger.write_string("bad number " + number.to_string())
    Type(name) => logger.write_string("unresolved type " + name)
    Syntax(text) => logger.write_string("bad syntax " + text)
  }
}

///|
pub extend Refused with Show::{to_string, output}

///|
/// Build a runtime file descriptor from the proto form.
pub fn build_file(
  proto : FileDescriptorProto,
) -> @reflect.FileDescriptor raise Refused {
  let syntax = parse_syntax(proto.syntax)
  let symbols = collect_symbols(proto)
  let messages : Array[@reflect.MessageDescriptor] = []
  for message in proto.message_type {
    messages.push(
      build_message(message, proto.package_name, proto.name, syntax, symbols),
    )
  }
  let enums : Array[@reflect.EnumDescriptor] = []
  for item in proto.enum_type {
    enums.push(
      build_enum(
        item,
        proto.package_name,
        proto.name,
        syntax == @reflect.Proto2,
      ),
    )
  }
  let services : Array[@reflect.ServiceDescriptor] = []
  for service in proto.service {
    services.push(build_service(service, proto.package_name, proto.name))
  }
  let extensions : Array[@reflect.FieldDescriptor] = []
  for field in proto.extension {
    extensions.push(
      build_field(
        field,
        proto.package_name,
        proto.package_name,
        proto.name,
        syntax,
        symbols,
        None,
      ),
    )
  }
  {
    path: proto.name,
    package_name: proto.package_name,
    syntax,
    messages,
    enums,
    services,
    extensions,
    dependencies: proto.dependency,
    public_dependencies: [],
    edition: proto.edition,
    option_map: collect_options(proto),
  }
}

///|
/// Every option written in the file, keyed by the declaration it sits on: the
/// file itself under the empty key, a message by its full name, a field as
/// `Message.field`.
fn collect_options(
  proto : FileDescriptorProto,
) -> Array[(String, Array[(String, String)])] {
  let out : Array[(String, Array[(String, String)])] = []
  if proto.options.length() > 0 {
    out.push(("", proto.options))
  }
  for message in proto.message_type {
    collect_message_options(message, proto.package_name, out)
  }
  for item in proto.enum_type {
    let full = @reflect.qualify(proto.package_name, item.name)
    if item.options.length() > 0 {
      out.push((full, item.options))
    }
  }
  out
}

///|
fn collect_message_options(
  message : DescriptorProto,
  scope : String,
  out : Array[(String, Array[(String, String)])],
) -> Unit {
  let full = @reflect.qualify(scope, message.name)
  if message.options.length() > 0 {
    out.push((full, message.options))
  }
  for field in message.field {
    if field.options.length() > 0 {
      out.push((full + "." + field.name, field.options))
    }
  }
  for nested in message.nested_type {
    collect_message_options(nested, full, out)
  }
  for item in message.enum_type {
    let enum_full = full + "." + item.name
    if item.options.length() > 0 {
      out.push((enum_full, item.options))
    }
  }
}

///|
fn parse_syntax(text : String) -> @reflect.Syntax {
  match text {
    "proto2" => @reflect.Proto2
    "editions" => @reflect.Editions
    _ => @reflect.Proto3
  }
}

///|
fn build_message(
  proto : DescriptorProto,
  package_name : String,
  file_path : String,
  syntax : @reflect.Syntax,
  symbols : Array[String],
) -> @reflect.MessageDescriptor raise Refused {
  let full_name = @reflect.qualify(package_name, proto.name)
  let nested : Array[@reflect.MessageDescriptor] = []
  for item in proto.nested_type {
    nested.push(
      build_message(
        item,
        package_name + "." + proto.name,
        file_path,
        syntax,
        symbols,
      ),
    )
  }
  let nested_extensions : Array[@reflect.FieldDescriptor] = []
  for item in proto.extension {
    nested_extensions.push(
      build_field(
        item,
        full_name,
        package_name + "." + proto.name,
        file_path,
        syntax,
        symbols,
        None,
      ),
    )
  }
  let enums : Array[@reflect.EnumDescriptor] = []
  for item in proto.enum_type {
    enums.push(
      build_enum(
        item,
        package_name + "." + proto.name,
        file_path,
        syntax == @reflect.Proto2,
      ),
    )
  }
  let oneofs : Array[@reflect.OneofDescriptor] = []
  for oneof in proto.oneof_decl {
    oneofs.push({ name: oneof.name, synthetic: false, field_numbers: [], })
  }
  let fields : Array[@reflect.FieldDescriptor] = []
  for field in proto.field {
    let built = build_field(
      field,
      full_name,
      package_name + "." + proto.name,
      file_path,
      syntax,
      symbols,
      Some(nested),
    )
    fields.push(built)
  }
  // Mark synthetic oneofs: a oneof whose only field carries proto3_optional.
  for i in 0..
          if field_oneof == i {
            count = count + 1
            if !proto3_optional_of(proto, field.number) {
              synthetic = false
            }
          }
        None => ()
      }
    }
    oneofs[i] = {
      name: oneofs[i].name,
      synthetic: synthetic && count == 1,
      field_numbers: oneofs[i].field_numbers,
    }
  }
  // Fill oneof membership.
  for field in fields {
    match field.oneof_index {
      Some(field_oneof) =>
        if field_oneof >= 0 && field_oneof < oneofs.length() {
          let current = oneofs[field_oneof]
          let numbers = current.field_numbers.copy()
          numbers.push(field.number)
          oneofs[field_oneof] = {
            name: current.name,
            synthetic: current.synthetic,
            field_numbers: numbers,
          }
        }
      None => ()
    }
  }
  let required : Array[Int32] = []
  for field in fields {
    if field.cardinality == @reflect.Required {
      required.push(field.number)
    }
  }
  let map_entry_field = match proto.map_entry {
    true => {
      let key = find_field(fields, 1)
      let value = find_field(fields, 2)
      (key, value)
    }
    false => (None, None)
  }
  {
    name: proto.name,
    full_name,
    syntax,
    fields,
    oneofs,
    is_map_entry: proto.map_entry,
    reserved_names: proto.reserved_name,
    reserved_ranges: proto.reserved_range,
    required_numbers: required,
    extension_ranges: proto.extension_range,
    nested_messages: nested,
    nested_enums: enums,
    nested_extensions,
    file_path,
    map_key: map_entry_field.0,
    map_value: map_entry_field.1,
  }
}

///|
fn proto3_optional_of(proto : DescriptorProto, number : Int32) -> Bool {
  for field in proto.field {
    if field.number == number {
      return field.proto3_optional
    }
  }
  false
}

///|
fn find_field(
  fields : Array[@reflect.FieldDescriptor],
  number : Int32,
) -> @reflect.FieldDescriptor? {
  for field in fields {
    if field.number == number {
      return Some(field)
    }
  }
  None
}

///|
fn build_field(
  proto : FieldDescriptorProto,
  containing : String,
  scope : String,
  file_path : String,
  syntax : @reflect.Syntax,
  symbols : Array[String],
  siblings : Array[@reflect.MessageDescriptor]?,
) -> @reflect.FieldDescriptor raise Refused {
  let _ = file_path
  let mut kind = match @reflect.Kind::of(proto.type_code) {
    Some(kind) => kind
    None => raise Type(proto.name)
  }
  // `features.message_encoding = DELIMITED` is the editions spelling of a
  // group: the same start/end tags, a different declaration.
  if proto.features.message_encoding == 2 && kind == @reflect.Message {
    kind = @reflect.Group
  }
  let cardinality = match proto.label {
    2 => @reflect.Required
    3 => @reflect.Repeated
    _ => @reflect.Optional
  }
  let resolved = match proto.type_name {
    Some(name) => resolve_type(name, scope, symbols)
    None => None
  }
  let is_extension = proto.extendee is Some(_)
  let mut is_map = false
  let mut map_key_field = None
  let mut map_value_field = None
  if cardinality == @reflect.Repeated && kind == @reflect.Message {
    match resolved {
      Some(entry_name) =>
        match find_nested(siblings, entry_name) {
          Some(entry) =>
            if entry.is_map_entry {
              is_map = true
              map_key_field = entry.map_key
              map_value_field = entry.map_value
            }
          None => ()
        }
      None => ()
    }
  }
  let json_name = match proto.json_name {
    Some(name) =>
      if name.length() > 0 {
        name
      } else {
        to_json_name(proto.name)
      }
    None => to_json_name(proto.name)
  }
  let text_name = if kind == @reflect.Group {
    @reflect.short_name(
      match resolved {
        Some(name) => name
        None => proto.name
      },
    )
  } else {
    proto.name
  }
  let packed = if cardinality == @reflect.Repeated && kind.is_numeric() {
    match proto.features.repeated_field_encoding {
      1 => true
      2 => false
      _ =>
        match syntax {
          @reflect.Proto3 | @reflect.Editions => !proto.packed_off
          @reflect.Proto2 => false
        }
    }
  } else {
    false
  }
  let mut has_default = false
  if syntax == @reflect.Proto2 {
    match proto.default_value {
      Some(_) => has_default = true
      None => ()
    }
  }
  let default_value = if has_default {
    parse_default(kind, proto.default_value, resolved)
  } else {
    @reflect.zero_value(kind)
  }
  let has_presence = match proto.features.field_presence {
    1 | 3 => true
    2 => false
    _ =>
      syntax == @reflect.Proto2 ||
      // Editions default to explicit presence, unlike proto3.
      syntax == @reflect.Editions ||
      cardinality == @reflect.Required ||
      proto.oneof_index is Some(_) ||
      proto.proto3_optional ||
      kind.is_message()
  }
  let containing_name = match proto.extendee {
    Some(name) =>
      match resolve_type(name, scope, symbols) {
        Some(full) => full
        None => containing
      }
    None => containing
  }
  let full_name = if is_extension {
    @reflect.qualify(scope, proto.name)
  } else {
    containing + "." + proto.name
  }
  {
    name: proto.name,
    full_name,
    number: proto.number,
    kind,
    cardinality,
    json_name,
    text_name,
    has_presence,
    is_extension,
    is_map,
    packed,
    oneof_index: proto.oneof_index,
    message_type: if kind.is_message() {
      resolved
    } else {
      None
    },
    enum_type: if kind == @reflect.Enum {
      resolved
    } else {
      None
    },
    has_default,
    default_value,
    default_enum_name: proto.default_value,
    containing_message: containing_name,
    map_key_field,
    map_value_field,
  }
}

///|
fn resolve_type(
  name : String,
  scope : String,
  symbols : Array[String],
) -> String? {
  if name.length() == 0 {
    return None
  }
  if char_at(name, 0) == '.' {
    return Some(name[1:].to_owned())
  }
  // Try the innermost scope outward, which is how proto resolves a relative name.
  let mut prefix = scope
  while true {
    let candidate = if prefix.length() == 0 {
      name
    } else {
      prefix + "." + name
    }
    if symbols.contains(candidate) {
      return Some(candidate)
    }
    if !prefix.contains(".") {
      break
    }
    prefix = @reflect.parent_name(prefix)
  }
  if symbols.contains(name) {
    return Some(name)
  }
  if name.contains(".") {
    Some(name)
  } else if scope.length() == 0 {
    Some(name)
  } else {
    Some(scope + "." + name)
  }
}

///|
fn collect_symbols(proto : FileDescriptorProto) -> Array[String] {
  let out : Array[String] = []
  for message in proto.message_type {
    collect_message_symbols(message, proto.package_name, out)
  }
  for item in proto.enum_type {
    out.push(@reflect.qualify(proto.package_name, item.name))
  }
  out
}

///|
fn collect_message_symbols(
  message : DescriptorProto,
  scope : String,
  out : Array[String],
) -> Unit {
  let full = @reflect.qualify(scope, message.name)
  out.push(full)
  for nested in message.nested_type {
    collect_message_symbols(nested, full, out)
  }
  for item in message.enum_type {
    out.push(full + "." + item.name)
  }
}

///|
fn find_nested(
  siblings : Array[@reflect.MessageDescriptor]?,
  name : String,
) -> @reflect.MessageDescriptor? {
  match siblings {
    Some(items) => {
      for item in items {
        if item.full_name == name || item.name == name {
          return Some(item)
        }
      }
      None
    }
    None => None
  }
}

///|
fn parse_default(
  kind : @reflect.Kind,
  text : String?,
  resolved : String?,
) -> @reflect.Value {
  let _ = resolved
  match text {
    Some(value) =>
      match kind {
        @reflect.Bool => @reflect.Bool(value == "true")
        @reflect.Int32 | @reflect.Sint32 | @reflect.Sfixed32 =>
          @reflect.Int32(parse_int_text(value).to_int())
        @reflect.Int64 | @reflect.Sint64 | @reflect.Sfixed64 =>
          @reflect.Int64(parse_int_text(value))
        @reflect.Uint32 | @reflect.Fixed32 =>
          @reflect.Uint32(parse_int_text(value).to_int().reinterpret_as_uint())
        @reflect.Uint64 | @reflect.Fixed64 =>
          @reflect.Uint64(parse_int_text(value).reinterpret_as_uint64())
        @reflect.Float =>
          @reflect.Float(@float.Float::from_double(parse_double_text(value)))
        @reflect.Double => @reflect.Double(parse_double_text(value))
        @reflect.String => @reflect.String(value)
        @reflect.Bytes => @reflect.Bytes(@utf8.encode(value))
        _ => @reflect.Invalid
      }
    None => @reflect.zero_value(kind)
  }
}

///|
fn build_enum(
  proto : EnumDescriptorProto,
  scope : String,
  file_path : String,
  closed_by_default : Bool,
) -> @reflect.EnumDescriptor {
  let full_name = @reflect.qualify(scope, proto.name)
  let values : Array[@reflect.EnumValueDescriptor] = []
  for value in proto.value {
    values.push({
      name: value.name,
      number: value.number,
      full_name: @reflect.qualify(scope, value.name),
    })
  }
  let closed = if proto.features.enum_type == 0 {
    closed_by_default
  } else {
    proto.features.enum_type == 2
  }
  { name: proto.name, full_name, values, closed, file_path, }
}

///|
fn build_service(
  proto : ServiceDescriptorProto,
  scope : String,
  file_path : String,
) -> @reflect.ServiceDescriptor {
  let full_name = @reflect.qualify(scope, proto.name)
  let methods : Array[@reflect.MethodDescriptor] = []
  for m in proto.methods {
    methods.push({
      name: m.name,
      full_name: full_name + "." + m.name,
      input_type: m.input_type,
      output_type: m.output_type,
      streaming_client: m.client_streaming,
      streaming_server: m.server_streaming,
    })
  }
  { name: proto.name, full_name, methods, file_path, }
}

///|
/// `foo_bar` becomes `fooBar`, the JSON name default.
pub fn to_json_name(name : String) -> String {
  let out = Buffer()
  let mut upper = false
  for i in 0.. name
  }
}

///|
fn char_at(text : String, index : Int32) -> Char {
  text[index].to_int().to_char().unwrap_or('?')
}

///|
fn parse_int_text(text : String) -> Int64 {
  let mut i = 0
  let negative = text.length() > 0 && char_at(text, 0) == '-'
  if negative || (text.length() > 0 && char_at(text, 0) == '+') {
    i = 1
  }
  let mut value = 0L
  while i < text.length() {
    let ch = char_at(text, i)
    if ch < '0' || ch > '9' {
      return 0L
    }
    value = value * 10L + (ch.to_int() - '0'.to_int()).to_int64()
    i = i + 1
  }
  if negative {
    -value
  } else {
    value
  }
}

///|
fn parse_double_text(text : String) -> Double {
  // Only the forms a proto default takes: an optional sign, digits, a decimal
  // point, and an optional exponent.
  let mut i = 0
  let negative = text.length() > 0 && char_at(text, 0) == '-'
  if negative || (text.length() > 0 && char_at(text, 0) == '+') {
    i = 1
  }
  let mut value = 0.0
  while i < text.length() && char_at(text, i) >= '0' && char_at(text, i) <= '9' {
    value = value * 10.0 +
      (char_at(text, i).to_int() - '0'.to_int()).to_double()
    i = i + 1
  }
  if i < text.length() && char_at(text, i) == '.' {
    i = i + 1
    let mut scale = 0.1
    while i < text.length() &&
          char_at(text, i) >= '0' &&
          char_at(text, i) <= '9' {
      value = value +
        (char_at(text, i).to_int() - '0'.to_int()).to_double() * scale
      scale = scale * 0.1
      i = i + 1
    }
  }
  if negative {
    -value
  } else {
    value
  }
}

///|
fn upper_char(ch : Char) -> Char {
  if ch >= 'a' && ch <= 'z' {
    (ch.to_int() - 32).to_char().unwrap_or(ch)
  } else {
    ch
  }
}

///|
/// The proto form of a runtime file descriptor.
pub fn to_file_descriptor_proto(
  file : @reflect.FileDescriptor,
) -> FileDescriptorProto {
  let messages : Array[DescriptorProto] = []
  for message in file.messages {
    messages.push(to_message_proto(message))
  }
  let enums : Array[EnumDescriptorProto] = []
  for item in file.enums {
    enums.push(to_enum_proto(item))
  }
  let services : Array[ServiceDescriptorProto] = []
  for service in file.services {
    let methods : Array[MethodDescriptorProto] = []
    for m in service.methods {
      methods.push({
        name: m.name,
        input_type: "." + m.input_type,
        output_type: "." + m.output_type,
        client_streaming: m.streaming_client,
        server_streaming: m.streaming_server,
      })
    }
    services.push({ name: service.name, methods, })
  }
  {
    name: file.path,
    package_name: file.package_name,
    syntax: match file.syntax {
      @reflect.Proto2 => "proto2"
      @reflect.Proto3 => "proto3"
      @reflect.Editions => "editions"
    },
    message_type: messages,
    enum_type: enums,
    service: services,
    extension: [],
    dependency: file.dependencies,
    public_dependency: [],
    edition: file.edition,
    features: features_auto,
    options: [],
  }
}

///|
fn to_message_proto(message : @reflect.MessageDescriptor) -> DescriptorProto {
  let fields : Array[FieldDescriptorProto] = []
  for field in message.fields {
    fields.push(to_field_proto(field))
  }
  let nested : Array[DescriptorProto] = []
  for item in message.nested_messages {
    nested.push(to_message_proto(item))
  }
  let enums : Array[EnumDescriptorProto] = []
  for item in message.nested_enums {
    enums.push(to_enum_proto(item))
  }
  let oneofs : Array[OneofDescriptorProto] = []
  for oneof in message.oneofs {
    oneofs.push({ name: oneof.name, })
  }
  {
    name: message.name,
    field: fields,
    nested_type: nested,
    enum_type: enums,
    extension: [],
    oneof_decl: oneofs,
    reserved_range: message.reserved_ranges,
    reserved_name: message.reserved_names,
    extension_range: message.extension_ranges,
    map_entry: message.is_map_entry,
    features: features_auto,
    options: [],
  }
}

///|
fn to_field_proto(field : @reflect.FieldDescriptor) -> FieldDescriptorProto {
  {
    name: field.name,
    number: field.number,
    label: field.cardinality.code(),
    type_code: field.kind.code(),
    type_name: match field.kind {
      @reflect.Message | @reflect.Group =>
        match field.message_type {
          Some(name) => Some("." + name)
          None => None
        }
      @reflect.Enum =>
        match field.enum_type {
          Some(name) => Some("." + name)
          None => None
        }
      _ => None
    },
    json_name: Some(field.json_name),
    default_value: None,
    oneof_index: field.oneof_index,
    proto3_optional: false,
    packed_off: !field.packed,
    extendee: None,
    features: features_auto,
    options: [],
  }
}

///|
fn to_enum_proto(item : @reflect.EnumDescriptor) -> EnumDescriptorProto {
  let values : Array[EnumValueDescriptorProto] = []
  for value in item.values {
    values.push({ name: value.name, number: value.number, })
  }
  { name: item.name, value: values, features: features_auto, options: [], }
}