///|
/// Result of converting a dynamic message to protobuf-style JSON.
pub(all) enum JsonEncodeResult {
  JsonOk(String)
  JsonErr(DecodeError)
} derive(Debug, Eq)

///|
/// Result of parsing protobuf-style JSON into a dynamic message.
pub(all) enum JsonDecodeResult {
  JsonDecodeOk(MessageValue)
  JsonDecodeErr(DecodeError)
} derive(Debug, Eq)

///|
priv enum JsonStringResult {
  JsonStringOk(String)
  JsonStringErr(DecodeError)
}

///|
priv enum JsonScalarDecodeResult {
  JsonScalarOk(ProtoValue)
  JsonScalarErr(DecodeError)
}

///|
priv struct JsonParser {
  src : String
  chars : Array[Char]
  mut i : Int
}

///|
priv struct JsonIntegerText {
  negative : Bool
  digits : String
}

///|
fn json_join(parts : Array[String], sep : String) -> String {
  let mut out = ""
  for i = 0; i < parts.length(); i = i + 1 {
    if i > 0 {
      out = out + sep
    }
    out = out + parts[i]
  }
  out
}

///|
fn json_hex_digit(n : Int) -> String {
  match n {
    0 => "0"
    1 => "1"
    2 => "2"
    3 => "3"
    4 => "4"
    5 => "5"
    6 => "6"
    7 => "7"
    8 => "8"
    9 => "9"
    10 => "a"
    11 => "b"
    12 => "c"
    13 => "d"
    14 => "e"
    _ => "f"
  }
}

///|
fn json_unicode_escape(code : Int) -> String {
  "\\u" +
  json_hex_digit((code >> 12) & 15) +
  json_hex_digit((code >> 8) & 15) +
  json_hex_digit((code >> 4) & 15) +
  json_hex_digit(code & 15)
}

///|
/// Escape a MoonBit string as a JSON string literal.
pub fn json_escape_string(value : String) -> String {
  let mut out = "\""
  for c in value.to_array() {
    if c == '"' {
      out = out + "\\\""
    } else if c == '\\' {
      out = out + "\\\\"
    } else if c == '\n' {
      out = out + "\\n"
    } else if c == '\r' {
      out = out + "\\r"
    } else if c == '\t' {
      out = out + "\\t"
    } else if c.to_int() < 32 {
      out = out + json_unicode_escape(c.to_int())
    } else {
      out = out + c.to_string()
    }
  }
  out + "\""
}

///|
fn base64_char(index : Int) -> String {
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[index:index +
  1].to_owned()
}

///|
/// Encode bytes with the standard base64 alphabet used by protobuf JSON.
pub fn base64_encode(input : Bytes) -> String {
  let bytes = input.to_array()
  let mut out = ""
  let mut i = 0
  while i < bytes.length() {
    let b0 = bytes[i].to_uint().reinterpret_as_int()
    let b1 = if i + 1 < bytes.length() {
      bytes[i + 1].to_uint().reinterpret_as_int()
    } else {
      0
    }
    let b2 = if i + 2 < bytes.length() {
      bytes[i + 2].to_uint().reinterpret_as_int()
    } else {
      0
    }
    let triple = (b0 << 16) | (b1 << 8) | b2
    out = out + base64_char((triple >> 18) & 63)
    out = out + base64_char((triple >> 12) & 63)
    if i + 1 < bytes.length() {
      out = out + base64_char((triple >> 6) & 63)
    } else {
      out = out + "="
    }
    if i + 2 < bytes.length() {
      out = out + base64_char(triple & 63)
    } else {
      out = out + "="
    }
    i = i + 3
  }
  out
}

///|
fn base64_value(c : Char) -> Int {
  if c >= 'A' && c <= 'Z' {
    c.to_int() - 'A'.to_int()
  } else if c >= 'a' && c <= 'z' {
    c.to_int() - 'a'.to_int() + 26
  } else if c >= '0' && c <= '9' {
    c.to_int() - '0'.to_int() + 52
  } else if c == '+' {
    62
  } else if c == '/' {
    63
  } else if c == '-' {
    62
  } else if c == '_' {
    63
  } else {
    -1
  }
}

///|
/// Decode base64 used by protobuf JSON bytes fields.  In addition to the
/// standard alphabet, protobuf JSON parsers accept URL-safe `-`/`_` variants
/// and missing final padding.
pub fn base64_decode(input : String) -> DecodeBytesResult {
  let chars = input.to_array()
  if chars.length() == 0 {
    return BytesOk(b"", 0)
  }
  if chars.length() % 4 == 1 {
    return BytesErr(Unsupported("invalid base64 length"))
  }
  let out : Array[Byte] = []
  let mut i = 0
  while i < chars.length() {
    let c0 = chars[i]
    let c1 = chars[i + 1]
    let c2 = if i + 2 < chars.length() { chars[i + 2] } else { '=' }
    let c3 = if i + 3 < chars.length() { chars[i + 3] } else { '=' }
    let v0 = base64_value(c0)
    let v1 = base64_value(c1)
    let pad2 = c2 == '='
    let pad3 = c3 == '='
    if v0 < 0 || v1 < 0 {
      return BytesErr(Unsupported("invalid base64 alphabet"))
    }
    if pad2 && !pad3 {
      return BytesErr(Unsupported("invalid base64 padding"))
    }
    let v2 = if pad2 { 0 } else { base64_value(c2) }
    let v3 = if pad3 { 0 } else { base64_value(c3) }
    if v2 < 0 || v3 < 0 {
      return BytesErr(Unsupported("invalid base64 alphabet"))
    }
    let triple = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3
    out.push(((triple >> 16) & 255).reinterpret_as_uint().to_byte())
    if !pad2 {
      out.push(((triple >> 8) & 255).reinterpret_as_uint().to_byte())
    }
    if !pad3 {
      out.push((triple & 255).reinterpret_as_uint().to_byte())
    }
    if (pad2 || pad3) && i + 4 < chars.length() {
      return BytesErr(Unsupported("base64 padding must be final"))
    }
    i = i + 4
  }
  BytesOk(Bytes::from_array(out), chars.length())
}

///|
fn is_i64_json_string_type(typ : ScalarType) -> Bool {
  match typ {
    Int64Type | UInt64Type | SInt64Type | Fixed64Type | SFixed64Type => true
    _ => false
  }
}

///|
fn json_upper_ascii(c : Char) -> Char {
  match c {
    'a' => 'A'
    'b' => 'B'
    'c' => 'C'
    'd' => 'D'
    'e' => 'E'
    'f' => 'F'
    'g' => 'G'
    'h' => 'H'
    'i' => 'I'
    'j' => 'J'
    'k' => 'K'
    'l' => 'L'
    'm' => 'M'
    'n' => 'N'
    'o' => 'O'
    'p' => 'P'
    'q' => 'Q'
    'r' => 'R'
    's' => 'S'
    't' => 'T'
    'u' => 'U'
    'v' => 'V'
    'w' => 'W'
    'x' => 'X'
    'y' => 'Y'
    'z' => 'Z'
    other => other
  }
}

///|
fn json_lower_camel_field_name(name : String) -> String {
  let chars = name.to_array()
  let out : Array[Char] = []
  let mut capitalize_next = false
  for c in chars {
    if c == '_' {
      capitalize_next = true
    } else if capitalize_next {
      out.push(json_upper_ascii(c))
      capitalize_next = false
    } else {
      out.push(c)
    }
  }
  String::from_iter(out.iter())
}

///|
fn json_find_descriptor(
  desc : MessageDescriptor,
  name : String,
) -> FieldDescriptor? {
  for field in desc.fields {
    if field.name == name {
      return Some(field)
    }
  }
  for field in desc.fields {
    if json_lower_camel_field_name(field.name) == name {
      return Some(field)
    }
  }
  None
}

///|
fn json_find_message_field(
  value : MessageValue,
  name : String,
) -> MessageField? {
  for field in value.fields {
    if field.name == name {
      return Some(field)
    }
  }
  None
}

///|
fn json_find_message_descriptor(
  descriptors : Array[MessageDescriptor],
  name : String,
) -> MessageDescriptor? {
  for desc in descriptors {
    if desc.name == name {
      return Some(desc)
    }
  }
  None
}

///|
fn json_find_enum_descriptor(
  descriptors : Array[EnumDescriptor],
  name : String,
) -> EnumDescriptor? {
  for desc in descriptors {
    if desc.name == name {
      return Some(desc)
    }
  }
  None
}

///|
fn json_enum_name_for_number(desc : EnumDescriptor, number : Int64) -> String? {
  for value in desc.values {
    if value.number.to_int64() == number {
      return Some(value.name)
    }
  }
  None
}

///|
fn json_enum_number_for_name(desc : EnumDescriptor, name : String) -> Int64? {
  for value in desc.values {
    if value.name == name {
      return Some(value.number.to_int64())
    }
  }
  None
}

///|
fn json_oneof_group(label : FieldLabel) -> String? {
  match label {
    Oneof(group) => Some(group)
    Singular | Optional | Repeated => None
  }
}

///|
fn validate_json_message(
  desc : MessageDescriptor,
  value : MessageValue,
) -> DecodeError? {
  let seen_oneofs : Array[String] = []
  for field in value.fields {
    match json_find_descriptor(desc, field.name) {
      None =>
        return Some(Unsupported("unknown field in JSON mapping: " + field.name))
      Some(fd) => {
        if fd.label != Repeated && field.values.length() > 1 {
          return Some(
            Unsupported(
              "singular field has multiple JSON values: " + field.name,
            ),
          )
        }
        match json_oneof_group(fd.label) {
          None => ()
          Some(group) =>
            if field.values.length() > 0 {
              for seen in seen_oneofs {
                if seen == group {
                  return Some(
                    Unsupported("multiple fields set for oneof group: " + group),
                  )
                }
              }
              seen_oneofs.push(group)
            }
        }
      }
    }
  }
  None
}

///|
fn json_find_entry_field(entry : MessageValue, name : String) -> ProtoValue? {
  for field in entry.fields {
    if field.name == name && field.values.length() > 0 {
      return Some(field.values[0])
    }
  }
  None
}

///|
fn map_key_to_json_property(
  typ : ScalarType,
  value : ProtoValue,
) -> JsonEncodeResult {
  match (typ, value) {
    (StringType, StringValue(v)) => JsonOk(v)
    (BoolType, BoolValue(v)) => JsonOk(if v { "true" } else { "false" })
    (UInt32Type | Fixed32Type, UInt64Value(v)) =>
      if v <= 0xffffffffUL {
        JsonOk(v.to_string())
      } else {
        JsonErr(Unsupported("uint32 map key out of range"))
      }
    (UInt64Type | Fixed64Type, UInt64Value(v)) => JsonOk(v.to_string())
    (Int32Type | SInt32Type | SFixed32Type, Int64Value(v)) =>
      if v >= -2147483647L - 1L && v <= 2147483647L {
        JsonOk(v.to_string())
      } else {
        JsonErr(Unsupported("int32 map key out of range"))
      }
    (Int64Type | SInt64Type | SFixed64Type, Int64Value(v)) =>
      JsonOk(v.to_string())
    _ => JsonErr(Unsupported("value does not match map key type"))
  }
}

///|
fn map_field_to_json(
  desc : FieldDescriptor,
  field : MessageField,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  lower_camel : Bool,
) -> JsonEncodeResult {
  match desc.typ {
    MapType(key_typ, value_typ) => {
      let props : Array[String] = []
      for item in field.values {
        match item {
          MapEntryValue(entry) => {
            let key = match json_find_entry_field(entry, "key") {
              None => return JsonErr(Unsupported("map entry missing key"))
              Some(k) => k
            }
            let value = match json_find_entry_field(entry, "value") {
              None => return JsonErr(Unsupported("map entry missing value"))
              Some(v) => v
            }
            let key_text = match map_key_to_json_property(key_typ, key) {
              JsonErr(e) => return JsonErr(e)
              JsonOk(text) => text
            }
            let value_text = match
              scalar_to_json_with_options(
                value_typ, value, descriptors, enum_descriptors, lower_camel,
              ) {
              JsonErr(e) => return JsonErr(e)
              JsonOk(text) => text
            }
            props.push(json_escape_string(key_text) + ":" + value_text)
          }
          _ =>
            return JsonErr(Unsupported("value does not match map entry type"))
        }
      }
      JsonOk("{" + json_join(props, ",") + "}")
    }
    _ => JsonErr(Unsupported("field is not a map"))
  }
}

///|
fn scalar_to_json(typ : ScalarType, value : ProtoValue) -> JsonEncodeResult {
  match (typ, value) {
    (StringType, StringValue(v)) => JsonOk(json_escape_string(v))
    (BytesType, BytesValue(v)) => JsonOk(json_escape_string(base64_encode(v)))
    (BoolType, BoolValue(v)) => JsonOk(if v { "true" } else { "false" })
    (EnumType(_), Int64Value(v)) => JsonOk(v.to_string())
    (UInt32Type | Fixed32Type, UInt64Value(v)) =>
      if v > 0xffffffffUL {
        JsonErr(Unsupported("uint32 value out of range"))
      } else {
        JsonOk(v.to_string())
      }
    (UInt64Type | Fixed64Type, UInt64Value(v)) =>
      if is_i64_json_string_type(typ) {
        JsonOk(json_escape_string(v.to_string()))
      } else {
        JsonOk(v.to_string())
      }
    (Int32Type | SInt32Type | SFixed32Type, Int64Value(v)) =>
      if v < -2147483647L - 1L || v > 2147483647L {
        JsonErr(Unsupported("int32 value out of range"))
      } else {
        JsonOk(v.to_string())
      }
    (Int64Type | SInt64Type | SFixed64Type, Int64Value(v)) =>
      if is_i64_json_string_type(typ) {
        JsonOk(json_escape_string(v.to_string()))
      } else {
        JsonOk(v.to_string())
      }
    (FloatType, FloatValue(v)) => JsonOk(float_double_to_json(v.to_double()))
    (DoubleType, DoubleValue(v)) => JsonOk(float_double_to_json(v))
    (NamedType(name), NestedMessageValue(_)) =>
      JsonErr(Unsupported("missing descriptor for nested message: " + name))
    (NamedType(name), _) =>
      JsonErr(Unsupported("value does not match nested message: " + name))
    (MapType(_, _), _) =>
      JsonErr(Unsupported("map value must be encoded as a JSON object"))
    (FloatType, _) => JsonErr(Unsupported("value does not match float type"))
    (DoubleType, _) => JsonErr(Unsupported("value does not match double type"))
    _ => JsonErr(Unsupported("value does not match JSON scalar type"))
  }
}

///|
fn float_double_to_json(v : Double) -> String {
  if v.is_nan() {
    json_escape_string("NaN")
  } else if v.is_pos_inf() {
    json_escape_string("Infinity")
  } else if v.is_neg_inf() {
    json_escape_string("-Infinity")
  } else {
    v.to_string()
  }
}

///|
fn field_to_json(
  desc : FieldDescriptor,
  field : MessageField,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  lower_camel : Bool,
) -> JsonEncodeResult {
  match desc.typ {
    MapType(_, _) =>
      return map_field_to_json(
        desc, field, descriptors, enum_descriptors, lower_camel,
      )
    _ => ()
  }
  if desc.label == Repeated {
    let items : Array[String] = []
    for value in field.values {
      match
        scalar_to_json_with_options(
          desc.typ,
          value,
          descriptors,
          enum_descriptors,
          lower_camel,
        ) {
        JsonErr(e) => return JsonErr(e)
        JsonOk(item) => items.push(item)
      }
    }
    JsonOk("[" + json_join(items, ",") + "]")
  } else if field.values.length() == 0 {
    JsonErr(Unsupported("singular field has no JSON value: " + field.name))
  } else {
    scalar_to_json_with_options(
      desc.typ,
      field.values[0],
      descriptors,
      enum_descriptors,
      lower_camel,
    )
  }
}

///|
fn scalar_to_json_with_options(
  typ : ScalarType,
  value : ProtoValue,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  lower_camel : Bool,
) -> JsonEncodeResult {
  match (typ, value) {
    (EnumType(name), Int64Value(v)) =>
      match json_find_enum_descriptor(enum_descriptors, name) {
        Some(desc) =>
          match json_enum_name_for_number(desc, v) {
            Some(enum_name) => JsonOk(json_escape_string(enum_name))
            None => JsonOk(v.to_string())
          }
        None => scalar_to_json(typ, value)
      }
    (NamedType(name), NestedMessageValue(v)) =>
      match json_find_message_descriptor(descriptors, name) {
        None =>
          JsonErr(Unsupported("missing descriptor for nested message: " + name))
        Some(desc) =>
          message_to_json_with_options(
            desc, descriptors, enum_descriptors, v, lower_camel,
          )
      }
    _ => scalar_to_json(typ, value)
  }
}

///|
fn json_output_field_name(
  field : FieldDescriptor,
  lower_camel : Bool,
) -> String {
  if lower_camel {
    json_lower_camel_field_name(field.name)
  } else {
    field.name
  }
}

///|
/// Convert a dynamic message to deterministic protobuf-style JSON. Known
/// fields are emitted in descriptor order; absent fields are omitted.
pub fn message_to_json(
  desc : MessageDescriptor,
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_with_descriptors(desc, [], value)
}

///|
/// Convert a dynamic message to deterministic protobuf-style JSON using
/// lowerCamelCase JSON field names, matching the protobuf JSON default.
pub fn message_to_json_lower_camel(
  desc : MessageDescriptor,
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_lower_camel_with_descriptors(desc, [], value)
}

///|
/// Convert a dynamic message to deterministic protobuf-style JSON, resolving
/// `NamedType` fields through `descriptors` for nested message values.
pub fn message_to_json_with_descriptors(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_with_options(desc, descriptors, [], value, false)
}

///|
/// Convert protobuf-style JSON with lowerCamelCase field names while resolving
/// `NamedType` fields through `descriptors`.
pub fn message_to_json_lower_camel_with_descriptors(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_with_options(desc, descriptors, [], value, true)
}

///|
/// Convert protobuf-style JSON using enum value names when matching
/// `EnumDescriptor`s are available.
pub fn message_to_json_with_schema(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_with_options(
    desc, descriptors, enum_descriptors, value, false,
  )
}

///|
/// Convert lowerCamelCase protobuf-style JSON using enum value names when
/// matching `EnumDescriptor`s are available.
pub fn message_to_json_lower_camel_with_schema(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  value : MessageValue,
) -> JsonEncodeResult {
  message_to_json_with_options(desc, descriptors, enum_descriptors, value, true)
}

///|
fn message_to_json_with_options(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  value : MessageValue,
  lower_camel : Bool,
) -> JsonEncodeResult {
  match validate_json_message(desc, value) {
    Some(e) => return JsonErr(e)
    None => ()
  }
  let props : Array[String] = []
  for fd in desc.fields {
    match json_find_message_field(value, fd.name) {
      None => ()
      Some(field) =>
        match
          field_to_json(fd, field, descriptors, enum_descriptors, lower_camel) {
          JsonErr(e) => return JsonErr(e)
          JsonOk(encoded) =>
            props.push(
              json_escape_string(json_output_field_name(fd, lower_camel)) +
              ":" +
              encoded,
            )
        }
    }
  }
  JsonOk("{" + json_join(props, ",") + "}")
}

///|
fn JsonParser::skip_ws(self : JsonParser) -> Unit {
  while self.i < self.chars.length() {
    let c = self.chars[self.i]
    if c == ' ' || c == '\n' || c == '\r' || c == '\t' {
      self.i = self.i + 1
    } else {
      return
    }
  }
}

///|
fn JsonParser::peek(self : JsonParser) -> Char? {
  if self.i < self.chars.length() {
    Some(self.chars[self.i])
  } else {
    None
  }
}

///|
fn JsonParser::consume(self : JsonParser, wanted : Char) -> Bool {
  self.skip_ws()
  match self.peek() {
    Some(c) =>
      if c == wanted {
        self.i = self.i + 1
        true
      } else {
        false
      }
    None => false
  }
}

///|
fn JsonParser::consume_literal(self : JsonParser, wanted : String) -> Bool {
  self.skip_ws()
  let chars = wanted.to_array()
  if self.i + chars.length() > self.chars.length() {
    return false
  }
  for j = 0; j < chars.length(); j = j + 1 {
    if self.chars[self.i + j] != chars[j] {
      return false
    }
  }
  self.i = self.i + chars.length()
  true
}

///|
fn json_hex_value(c : Char) -> Int {
  if c >= '0' && c <= '9' {
    c.to_int() - '0'.to_int()
  } else if c >= 'a' && c <= 'f' {
    c.to_int() - 'a'.to_int() + 10
  } else if c >= 'A' && c <= 'F' {
    c.to_int() - 'A'.to_int() + 10
  } else {
    -1
  }
}

///|
fn json_byte_from_int(value : Int) -> Byte {
  (value & 255).reinterpret_as_uint().to_byte()
}

///|
fn json_utf8_from_codepoint(code : Int) -> String {
  let out : Array[Byte] = []
  if code <= 0x7F {
    out.push(json_byte_from_int(code))
  } else if code <= 0x7FF {
    out.push(json_byte_from_int(0xC0 | (code >> 6)))
    out.push(json_byte_from_int(0x80 | (code & 0x3F)))
  } else if code <= 0xFFFF {
    out.push(json_byte_from_int(0xE0 | (code >> 12)))
    out.push(json_byte_from_int(0x80 | ((code >> 6) & 0x3F)))
    out.push(json_byte_from_int(0x80 | (code & 0x3F)))
  } else {
    out.push(json_byte_from_int(0xF0 | (code >> 18)))
    out.push(json_byte_from_int(0x80 | ((code >> 12) & 0x3F)))
    out.push(json_byte_from_int(0x80 | ((code >> 6) & 0x3F)))
    out.push(json_byte_from_int(0x80 | (code & 0x3F)))
  }
  @utf8.decode_lossy(Bytes::from_array(out)[:])
}

///|
fn JsonParser::read_json_hex_quad(self : JsonParser) -> Int? {
  if self.i + 4 > self.chars.length() {
    return None
  }
  let mut code = 0
  for j = 0; j < 4; j = j + 1 {
    let d = json_hex_value(self.chars[self.i + j])
    if d < 0 {
      return None
    }
    code = code * 16 + d
  }
  self.i = self.i + 4
  Some(code)
}

///|
fn JsonParser::parse_unicode_escape(self : JsonParser) -> JsonStringResult {
  if self.i + 4 > self.chars.length() {
    return JsonStringErr(UnexpectedEof)
  }
  let high = match self.read_json_hex_quad() {
    Some(code) => code
    None => return JsonStringErr(Unsupported("invalid JSON unicode escape"))
  }
  if high >= 0xD800 && high <= 0xDBFF {
    if self.i + 2 > self.chars.length() {
      return JsonStringErr(Unsupported("missing JSON low surrogate"))
    }
    if self.chars[self.i] != '\\' || self.chars[self.i + 1] != 'u' {
      return JsonStringErr(Unsupported("missing JSON low surrogate"))
    }
    self.i = self.i + 2
    let low = match self.read_json_hex_quad() {
      Some(code) => code
      None => return JsonStringErr(Unsupported("invalid JSON unicode escape"))
    }
    if low < 0xDC00 || low > 0xDFFF {
      return JsonStringErr(Unsupported("invalid JSON low surrogate"))
    }
    let codepoint = 0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00)
    JsonStringOk(json_utf8_from_codepoint(codepoint))
  } else if high >= 0xDC00 && high <= 0xDFFF {
    JsonStringErr(Unsupported("unexpected JSON low surrogate"))
  } else {
    JsonStringOk(json_utf8_from_codepoint(high))
  }
}

///|
fn JsonParser::parse_string(self : JsonParser) -> JsonStringResult {
  self.skip_ws()
  if self.i >= self.chars.length() || self.chars[self.i] != '"' {
    return JsonStringErr(Unsupported("expected JSON string"))
  }
  self.i = self.i + 1
  let mut out = ""
  while self.i < self.chars.length() {
    let c = self.chars[self.i]
    self.i = self.i + 1
    if c == '"' {
      return JsonStringOk(out)
    } else if c == '\\' {
      if self.i >= self.chars.length() {
        return JsonStringErr(UnexpectedEof)
      }
      let e = self.chars[self.i]
      self.i = self.i + 1
      if e == '"' {
        out = out + "\""
      } else if e == '\\' {
        out = out + "\\"
      } else if e == '/' {
        out = out + "/"
      } else if e == 'b' {
        out = out + "\b"
      } else if e == 'f' {
        out = out + "\f"
      } else if e == 'n' {
        out = out + "\n"
      } else if e == 'r' {
        out = out + "\r"
      } else if e == 't' {
        out = out + "\t"
      } else if e == 'u' {
        match self.parse_unicode_escape() {
          JsonStringErr(err) => return JsonStringErr(err)
          JsonStringOk(text) => out = out + text
        }
      } else {
        return JsonStringErr(Unsupported("unsupported JSON escape"))
      }
    } else if c.to_int() < 32 {
      return JsonStringErr(Unsupported("unescaped JSON control character"))
    } else {
      out = out + c.to_string()
    }
  }
  JsonStringErr(UnexpectedEof)
}

///|
fn is_json_digit(c : Char) -> Bool {
  c >= '0' && c <= '9'
}

///|
fn is_json_nonzero_digit(c : Char) -> Bool {
  c >= '1' && c <= '9'
}

///|
fn JsonParser::parse_number_token(self : JsonParser) -> JsonStringResult {
  self.skip_ws()
  let start = self.i
  if self.i >= self.chars.length() {
    return JsonStringErr(UnexpectedEof)
  }
  if self.chars[self.i] == '-' {
    self.i = self.i + 1
  }
  if self.i >= self.chars.length() {
    return JsonStringErr(Unsupported("expected JSON number"))
  }
  if self.chars[self.i] == '0' {
    self.i = self.i + 1
    if self.i < self.chars.length() && is_json_digit(self.chars[self.i]) {
      return JsonStringErr(Unsupported("invalid JSON number leading zero"))
    }
  } else if is_json_nonzero_digit(self.chars[self.i]) {
    while self.i < self.chars.length() && is_json_digit(self.chars[self.i]) {
      self.i = self.i + 1
    }
  } else {
    return JsonStringErr(Unsupported("expected JSON number"))
  }
  if self.i < self.chars.length() && self.chars[self.i] == '.' {
    self.i = self.i + 1
    if self.i >= self.chars.length() || !is_json_digit(self.chars[self.i]) {
      return JsonStringErr(Unsupported("invalid JSON number fraction"))
    }
    while self.i < self.chars.length() && is_json_digit(self.chars[self.i]) {
      self.i = self.i + 1
    }
  }
  if self.i < self.chars.length() &&
    (self.chars[self.i] == 'e' || self.chars[self.i] == 'E') {
    self.i = self.i + 1
    if self.i < self.chars.length() &&
      (self.chars[self.i] == '-' || self.chars[self.i] == '+') {
      self.i = self.i + 1
    }
    if self.i >= self.chars.length() || !is_json_digit(self.chars[self.i]) {
      return JsonStringErr(Unsupported("invalid JSON number exponent"))
    }
    while self.i < self.chars.length() && is_json_digit(self.chars[self.i]) {
      self.i = self.i + 1
    }
  }
  JsonStringOk(self.src[start:self.i].to_owned())
}

///|
fn JsonParser::parse_number_or_string_token(
  self : JsonParser,
) -> JsonStringResult {
  self.skip_ws()
  match self.peek() {
    Some('"') => self.parse_string()
    _ => self.parse_number_token()
  }
}

///|
fn json_digits_all_zero(digits : String) -> Bool {
  for c in digits.to_array() {
    if c != '0' {
      return false
    }
  }
  true
}

///|
fn json_strip_leading_zeroes(digits : String) -> String {
  let chars = digits.to_array()
  if chars.length() == 0 {
    return "0"
  }
  let mut i = 0
  while i + 1 < chars.length() && chars[i] == '0' {
    i = i + 1
  }
  digits[i:chars.length()].to_owned()
}

///|
fn json_digits_in_range(digits : String, max : String) -> Bool {
  let normalized = json_strip_leading_zeroes(digits)
  let ds = normalized.to_array()
  let ms = max.to_array()
  if ds.length() < ms.length() {
    return true
  }
  if ds.length() > ms.length() {
    return false
  }
  for i = 0; i < ds.length(); i = i + 1 {
    if ds[i] < ms[i] {
      return true
    }
    if ds[i] > ms[i] {
      return false
    }
  }
  true
}

///|
fn json_append_zeroes(digits : String, count : Int) -> String {
  let mut out = digits
  let mut remaining = count
  if remaining > 80 {
    remaining = 80
  }
  while remaining > 0 {
    out = out + "0"
    remaining = remaining - 1
  }
  out
}

///|
fn parse_json_integer_text(text : String) -> JsonIntegerText? {
  let chars = text.to_array()
  if chars.length() == 0 {
    return None
  }
  let mut i = 0
  let mut negative = false
  if chars[i] == '-' {
    negative = true
    i = i + 1
  }
  if i >= chars.length() || !is_json_digit(chars[i]) {
    return None
  }
  let mut digits = ""
  while i < chars.length() && is_json_digit(chars[i]) {
    digits = digits + chars[i].to_string()
    i = i + 1
  }
  let mut fractional_digits = 0
  if i < chars.length() && chars[i] == '.' {
    i = i + 1
    if i >= chars.length() || !is_json_digit(chars[i]) {
      return None
    }
    while i < chars.length() && is_json_digit(chars[i]) {
      digits = digits + chars[i].to_string()
      fractional_digits = fractional_digits + 1
      i = i + 1
    }
  }
  let mut exponent = 0
  if i < chars.length() && (chars[i] == 'e' || chars[i] == 'E') {
    i = i + 1
    let mut exponent_negative = false
    if i < chars.length() && chars[i] == '-' {
      exponent_negative = true
      i = i + 1
    } else if i < chars.length() && chars[i] == '+' {
      i = i + 1
    }
    if i >= chars.length() || !is_json_digit(chars[i]) {
      return None
    }
    let mut value = 0
    while i < chars.length() && is_json_digit(chars[i]) {
      if value <= 1000 {
        value = value * 10 + (chars[i].to_int() - '0'.to_int())
      }
      i = i + 1
    }
    exponent = if exponent_negative { -value } else { value }
  }
  if i != chars.length() {
    return None
  }
  let shift = exponent - fractional_digits
  if shift >= 0 {
    digits = json_append_zeroes(digits, shift)
  } else {
    let remove = -shift
    let ds = digits.to_array()
    if remove > ds.length() {
      if json_digits_all_zero(digits) {
        digits = "0"
      } else {
        return None
      }
    } else {
      let keep = ds.length() - remove
      for j = keep; j < ds.length(); j = j + 1 {
        if ds[j] != '0' {
          return None
        }
      }
      if keep == 0 {
        digits = "0"
      } else {
        digits = digits[0:keep].to_owned()
      }
    }
  }
  Some(JsonIntegerText::{ negative, digits: json_strip_leading_zeroes(digits) })
}

///|
fn parse_u64_digits(text : String) -> UInt64? {
  let integer = match parse_json_integer_text(text) {
    Some(v) => v
    None => return None
  }
  if integer.negative && !json_digits_all_zero(integer.digits) {
    return None
  }
  if !json_digits_in_range(integer.digits, "18446744073709551615") {
    return None
  }
  let mut value = 0UL
  for c in integer.digits.to_array() {
    value = value * 10UL + (c.to_int() - '0'.to_int()).to_uint64()
  }
  Some(value)
}

///|
fn parse_i64_digits(text : String) -> Int64? {
  let integer = match parse_json_integer_text(text) {
    Some(v) => v
    None => return None
  }
  let max = if integer.negative {
    "9223372036854775808"
  } else {
    "9223372036854775807"
  }
  if !json_digits_in_range(integer.digits, max) {
    return None
  }
  if integer.negative && integer.digits == "9223372036854775808" {
    return Some(-9223372036854775807L - 1L)
  }
  let mut value = 0L
  for c in integer.digits.to_array() {
    value = value * 10L + (c.to_int() - '0'.to_int()).to_int64()
  }
  if integer.negative {
    Some(-value)
  } else {
    Some(value)
  }
}

///|
fn parse_json_double_value(text : String) -> Double? {
  match text {
    "NaN" => Some(0x7FF8000000000001UL.reinterpret_as_double())
    "Infinity" => Some(0x7FF0000000000000UL.reinterpret_as_double())
    "-Infinity" => Some(0xFFF0000000000000UL.reinterpret_as_double())
    _ =>
      try @string.parse_double(text) catch {
        _ => None
      } noraise {
        v => Some(v)
      }
  }
}

///|
fn parse_scalar_from_json(
  parser : JsonParser,
  typ : ScalarType,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
) -> JsonScalarDecodeResult {
  match typ {
    StringType =>
      match parser.parse_string() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(v) => JsonScalarOk(StringValue(v))
      }
    BytesType =>
      match parser.parse_string() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match base64_decode(text) {
            BytesErr(e) => JsonScalarErr(e)
            BytesOk(bytes, _) => JsonScalarOk(BytesValue(bytes))
          }
      }
    BoolType =>
      if parser.consume_literal("true") {
        JsonScalarOk(BoolValue(true))
      } else if parser.consume_literal("false") {
        JsonScalarOk(BoolValue(false))
      } else {
        JsonScalarErr(Unsupported("expected JSON bool"))
      }
    UInt32Type | Fixed32Type =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_u64_digits(text) {
            Some(v) =>
              if v <= 0xffffffffUL {
                JsonScalarOk(UInt64Value(v))
              } else {
                JsonScalarErr(Unsupported("uint32 JSON out of range"))
              }
            None => JsonScalarErr(Unsupported("invalid unsigned integer JSON"))
          }
      }
    UInt64Type | Fixed64Type =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_u64_digits(text) {
            Some(v) => JsonScalarOk(UInt64Value(v))
            None => JsonScalarErr(Unsupported("invalid unsigned integer JSON"))
          }
      }
    Int32Type | SInt32Type | SFixed32Type =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_i64_digits(text) {
            Some(v) =>
              if v >= -2147483647L - 1L && v <= 2147483647L {
                JsonScalarOk(Int64Value(v))
              } else {
                JsonScalarErr(Unsupported("int32 JSON out of range"))
              }
            None => JsonScalarErr(Unsupported("invalid signed integer JSON"))
          }
      }
    Int64Type | SInt64Type | SFixed64Type =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_i64_digits(text) {
            Some(v) => JsonScalarOk(Int64Value(v))
            None => JsonScalarErr(Unsupported("invalid signed integer JSON"))
          }
      }
    EnumType(name) =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_i64_digits(text) {
            Some(v) => JsonScalarOk(Int64Value(v))
            None =>
              match json_find_enum_descriptor(enum_descriptors, name) {
                Some(desc) =>
                  match json_enum_number_for_name(desc, text) {
                    Some(number) => JsonScalarOk(Int64Value(number))
                    None => JsonScalarErr(Unsupported("unknown enum JSON name"))
                  }
                None => JsonScalarErr(Unsupported("invalid enum integer JSON"))
              }
          }
      }
    FloatType =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_json_double_value(text) {
            Some(v) => JsonScalarOk(FloatValue(Float::from_double(v)))
            None => JsonScalarErr(Unsupported("invalid float JSON"))
          }
      }
    DoubleType =>
      match parser.parse_number_or_string_token() {
        JsonStringErr(e) => JsonScalarErr(e)
        JsonStringOk(text) =>
          match parse_json_double_value(text) {
            Some(v) => JsonScalarOk(DoubleValue(v))
            None => JsonScalarErr(Unsupported("invalid double JSON"))
          }
      }
    NamedType(name) =>
      match json_find_message_descriptor(descriptors, name) {
        None =>
          JsonScalarErr(
            Unsupported("missing descriptor for nested message: " + name),
          )
        Some(desc) =>
          match
            parse_json_message_object(
              parser, desc, descriptors, enum_descriptors,
            ) {
            JsonDecodeErr(e) => JsonScalarErr(e)
            JsonDecodeOk(message) => JsonScalarOk(NestedMessageValue(message))
          }
      }
    MapType(_, _) =>
      JsonScalarErr(Unsupported("map JSON value must be parsed as object"))
  }
}

///|
fn json_string_array_contains(values : Array[String], value : String) -> Bool {
  for item in values {
    if item == value {
      return true
    }
  }
  false
}

///|
fn parse_map_key_from_json_property(
  key_typ : ScalarType,
  key : String,
) -> JsonScalarDecodeResult {
  match key_typ {
    StringType => JsonScalarOk(StringValue(key))
    BoolType =>
      if key == "true" {
        JsonScalarOk(BoolValue(true))
      } else if key == "false" {
        JsonScalarOk(BoolValue(false))
      } else {
        JsonScalarErr(Unsupported("invalid bool map key JSON"))
      }
    UInt32Type | Fixed32Type =>
      match parse_u64_digits(key) {
        Some(v) =>
          if v <= 0xffffffffUL {
            JsonScalarOk(UInt64Value(v))
          } else {
            JsonScalarErr(Unsupported("uint32 map key JSON out of range"))
          }
        None => JsonScalarErr(Unsupported("invalid unsigned map key JSON"))
      }
    UInt64Type | Fixed64Type =>
      match parse_u64_digits(key) {
        Some(v) => JsonScalarOk(UInt64Value(v))
        None => JsonScalarErr(Unsupported("invalid unsigned map key JSON"))
      }
    Int32Type | SInt32Type | SFixed32Type =>
      match parse_i64_digits(key) {
        Some(v) =>
          if v >= -2147483647L - 1L && v <= 2147483647L {
            JsonScalarOk(Int64Value(v))
          } else {
            JsonScalarErr(Unsupported("int32 map key JSON out of range"))
          }
        None => JsonScalarErr(Unsupported("invalid signed map key JSON"))
      }
    Int64Type | SInt64Type | SFixed64Type =>
      match parse_i64_digits(key) {
        Some(v) => JsonScalarOk(Int64Value(v))
        None => JsonScalarErr(Unsupported("invalid signed map key JSON"))
      }
    _ => JsonScalarErr(Unsupported("unsupported map key JSON type"))
  }
}

///|
fn parse_json_map_field(
  parser : JsonParser,
  fd : FieldDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
) -> JsonDecodeResult {
  match fd.typ {
    MapType(key_typ, value_typ) => {
      if !parser.consume('{') {
        return JsonDecodeErr(Unsupported("expected map JSON object"))
      }
      let values : Array[ProtoValue] = []
      let seen : Array[String] = []
      parser.skip_ws()
      if parser.consume('}') {
        return JsonDecodeOk(
          message_value([repeated_message_field(fd.name, values)]),
        )
      }
      let mut done = false
      while !done {
        let key_text = match parser.parse_string() {
          JsonStringErr(e) => return JsonDecodeErr(e)
          JsonStringOk(k) => k
        }
        if !parser.consume(':') {
          return JsonDecodeErr(Unsupported("expected map JSON colon"))
        }
        let key = match parse_map_key_from_json_property(key_typ, key_text) {
          JsonScalarErr(e) => return JsonDecodeErr(e)
          JsonScalarOk(v) => v
        }
        let canonical_key = match map_key_to_json_property(key_typ, key) {
          JsonErr(e) => return JsonDecodeErr(e)
          JsonOk(v) => v
        }
        if json_string_array_contains(seen, canonical_key) {
          return JsonDecodeErr(
            Unsupported("duplicate map JSON key: " + canonical_key),
          )
        }
        seen.push(canonical_key)
        let value = match
          parse_scalar_from_json(
            parser, value_typ, descriptors, enum_descriptors,
          ) {
          JsonScalarErr(e) => return JsonDecodeErr(e)
          JsonScalarOk(v) => v
        }
        values.push(map_entry_value(key, value))
        parser.skip_ws()
        if parser.consume(',') {
          ()
        } else if parser.consume('}') {
          done = true
        } else {
          return JsonDecodeErr(Unsupported("expected comma or map object end"))
        }
      }
      JsonDecodeOk(message_value([repeated_message_field(fd.name, values)]))
    }
    _ => JsonDecodeErr(Unsupported("field is not a map"))
  }
}

///|
fn parse_json_field(
  parser : JsonParser,
  fd : FieldDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
) -> JsonDecodeResult {
  if parser.consume_literal("null") {
    return JsonDecodeOk(message_value([]))
  }
  match fd.typ {
    MapType(_, _) =>
      return parse_json_map_field(parser, fd, descriptors, enum_descriptors)
    _ => ()
  }
  if fd.label == Repeated {
    if !parser.consume('[') {
      return JsonDecodeErr(Unsupported("expected repeated JSON array"))
    }
    let values : Array[ProtoValue] = []
    parser.skip_ws()
    if parser.consume(']') {
      return JsonDecodeOk(
        message_value([repeated_message_field(fd.name, values)]),
      )
    }
    let mut done = false
    while !done {
      match
        parse_scalar_from_json(parser, fd.typ, descriptors, enum_descriptors) {
        JsonScalarErr(e) => return JsonDecodeErr(e)
        JsonScalarOk(value) => values.push(value)
      }
      parser.skip_ws()
      if parser.consume(',') {
        ()
      } else if parser.consume(']') {
        done = true
      } else {
        return JsonDecodeErr(Unsupported("expected comma or array end"))
      }
    }
    JsonDecodeOk(message_value([repeated_message_field(fd.name, values)]))
  } else {
    match
      parse_scalar_from_json(parser, fd.typ, descriptors, enum_descriptors) {
      JsonScalarErr(e) => JsonDecodeErr(e)
      JsonScalarOk(value) =>
        JsonDecodeOk(message_value([message_field(fd.name, value)]))
    }
  }
}

///|
fn append_json_field(
  fields : Array[MessageField],
  field : MessageField,
) -> Unit {
  fields.push(field)
}

///|
fn order_json_fields(
  desc : MessageDescriptor,
  parsed : Array[MessageField],
) -> Array[MessageField] {
  let ordered : Array[MessageField] = []
  for fd in desc.fields {
    for f in parsed {
      if f.name == fd.name {
        ordered.push(f)
      }
    }
  }
  ordered
}

///|
fn parse_json_message_object(
  parser : JsonParser,
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
) -> JsonDecodeResult {
  if !parser.consume('{') {
    return JsonDecodeErr(Unsupported("expected JSON object"))
  }
  let parsed : Array[MessageField] = []
  let seen_field_names : Array[String] = []
  let seen_oneofs : Array[String] = []
  parser.skip_ws()
  if parser.consume('}') {
    return JsonDecodeOk(message_value([]))
  }
  let mut done = false
  while !done {
    let key = match parser.parse_string() {
      JsonStringErr(e) => return JsonDecodeErr(e)
      JsonStringOk(k) => k
    }
    if !parser.consume(':') {
      return JsonDecodeErr(Unsupported("expected JSON object colon"))
    }
    let fd = match json_find_descriptor(desc, key) {
      None => return JsonDecodeErr(Unsupported("unknown JSON field: " + key))
      Some(found) => found
    }
    if json_string_array_contains(seen_field_names, fd.name) {
      return JsonDecodeErr(Unsupported("duplicate JSON field: " + key))
    }
    seen_field_names.push(fd.name)
    match parse_json_field(parser, fd, descriptors, enum_descriptors) {
      JsonDecodeErr(e) => return JsonDecodeErr(e)
      JsonDecodeOk(one) =>
        if one.fields.length() > 0 {
          match json_oneof_group(fd.label) {
            None => ()
            Some(group) => {
              for seen in seen_oneofs {
                if seen == group {
                  return JsonDecodeErr(
                    Unsupported(
                      "multiple JSON fields for oneof group: " + group,
                    ),
                  )
                }
              }
              seen_oneofs.push(group)
            }
          }
          append_json_field(parsed, one.fields[0])
        }
    }
    parser.skip_ws()
    if parser.consume(',') {
      ()
    } else if parser.consume('}') {
      done = true
    } else {
      return JsonDecodeErr(Unsupported("expected comma or object end"))
    }
  }
  JsonDecodeOk(message_value(order_json_fields(desc, parsed)))
}

///|
/// Parse a protobuf-style JSON object into a dynamic message. Known fields are
/// accepted in any JSON order and returned in descriptor order.
pub fn json_to_message(
  desc : MessageDescriptor,
  input : String,
) -> JsonDecodeResult {
  json_to_message_with_descriptors(desc, [], input)
}

///|
/// Parse protobuf-style JSON, resolving `NamedType` fields through
/// `descriptors` for nested message values.
pub fn json_to_message_with_descriptors(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  input : String,
) -> JsonDecodeResult {
  json_to_message_with_schema(desc, descriptors, [], input)
}

///|
/// Parse protobuf-style JSON while resolving both nested message descriptors
/// and enum value names.
pub fn json_to_message_with_schema(
  desc : MessageDescriptor,
  descriptors : Array[MessageDescriptor],
  enum_descriptors : Array[EnumDescriptor],
  input : String,
) -> JsonDecodeResult {
  let parser = JsonParser::{ src: input, chars: input.to_array(), i: 0 }
  match parse_json_message_object(parser, desc, descriptors, enum_descriptors) {
    JsonDecodeErr(e) => JsonDecodeErr(e)
    JsonDecodeOk(message) => {
      parser.skip_ws()
      if parser.i != parser.chars.length() {
        JsonDecodeErr(Unsupported("trailing JSON input"))
      } else {
        JsonDecodeOk(message)
      }
    }
  }
}