///|
fn CodeGenerator::field_json_shape(
  self : CodeGenerator,
  field : Field,
  parent_path : ImportPath,
) -> FieldJsonShape raise {
  guard field.type_ is Some(type_) else {
    raise @model.UnexpectedType(
      "Field type is not set: \{field.import_path.name()}",
    )
  }
  let field_name = field.import_path.name() |> pascal_to_snake
  let field_type = self.get_moonbit_type(field, parent_path~)
  let has_default_value = field.default_value() is Some(_)
  let default_expr = if field.default_value() is Some(default_value) {
    if field.type_ is Some(TYPE_STRING) {
      "\"\{default_value}\""
    } else if field.is_enum() {
      "\{field_type}::\{default_value}"
    } else {
      "\{default_value}"
    }
  } else {
    "Default::default()"
  }
  let cardinality = if field.map_key_value() is Some(_) {
    FieldJsonCardinality::MapJsonField
  } else if field.is_list() {
    FieldJsonCardinality::RepeatedJsonField
  } else if field.is_optional() {
    FieldJsonCardinality::OptionalJsonField
  } else {
    FieldJsonCardinality::SingularJsonField
  }
  {
    field_name,
    self_expr: "self.\{field_name}",
    to_json_name: field.json_name.unwrap_or(field.name),
    from_json_name: field.json_name.unwrap(),
    type_,
    default_expr,
    has_default_value,
    cardinality,
  }
}

///|
fn FieldJsonShape::to_json_expr(
  self : FieldJsonShape,
  value_expr : String,
) -> String {
  if self.type_ is TYPE_BYTES {
    "@lib.base64_encode(\{value_expr}).to_json()"
  } else {
    "ToJson::to_json(\{value_expr})"
  }
}

///|
fn CodeGenerator::gen_field_json_to(
  _ : CodeGenerator,
  shape : FieldJsonShape,
  content : StringBuilder,
) -> Unit {
  match shape.cardinality {
    OptionalJsonField => {
      content.write_string("  match \{shape.self_expr} {\n")
      let optional_match = if shape.has_default_value {
        "Some(v) if v != \{shape.default_expr}"
      } else {
        "Some(v)"
      }
      content.write_string(
        "      \{optional_match} => json[\"\{shape.to_json_name}\"] = \{shape.to_json_expr("v")}\n",
      )
      content.write_string(
        (
          #|      _ => ()
          #|    }
          #|
        ),
      )
    }
    SingularJsonField | RepeatedJsonField | MapJsonField => {
      content.write_string(
        "  if \{shape.self_expr} != \{shape.default_expr} {\n",
      )
      content.write_string(
        "  json[\"\{shape.to_json_name}\"] = \{shape.to_json_expr(shape.self_expr)}\n",
      )
      content.write_string("  }\n")
    }
  }
}

///|
fn CodeGenerator::gen_field_json_from(
  _ : CodeGenerator,
  shape : FieldJsonShape,
  content : StringBuilder,
) -> Unit {
  match shape.cardinality {
    OptionalJsonField =>
      match shape.type_ {
        TYPE_BYTES =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", String(value)) => message.\{shape.field_name} = Some(try @lib.base64_decode(value) catch { _ => raise @json.JsonDecodeError((path, \"Invalid base64 encoding\")) })\n",
          )
        TYPE_FLOAT =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", Number(value, ..)) => message.\{shape.field_name} = Some(Float::from_double(value))\n",
          )
        _ =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", value) => message.\{shape.field_name} = Some(@json.from_json(value, path~))\n",
          )
      }
    MapJsonField =>
      content.write_string(
        "      (\"\{shape.from_json_name}\", _) => message.\{shape.field_name} = @json.from_json(value, path~)\n",
      )
    RepeatedJsonField => {
      content.write_string(
        "      (\"\{shape.from_json_name}\", Array(value)) => message.\{shape.field_name} = value.map(v => \n",
      )
      if shape.type_ is TYPE_FLOAT {
        content.write_string("Float::from_double(@json.from_json(v, path~)))\n")
      } else {
        content.write_string("@json.from_json(v, path~))\n")
      }
    }
    SingularJsonField =>
      match shape.type_ {
        TYPE_FLOAT =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", Number(value, ..)) => message.\{shape.field_name} = Float::from_double(value)\n",
          )
        TYPE_BYTES =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", String(value)) => message.\{shape.field_name} = try @lib.base64_decode(value) catch { _ => raise @json.JsonDecodeError((path, \"Invalid base64 encoding\")) }\n",
          )
        _ =>
          content.write_string(
            "      (\"\{shape.from_json_name}\", value) => message.\{shape.field_name} = @json.from_json(value, path~)\n",
          )
      }
  }
}