// Mapping from ATD to JSON: interpretation of `` annotations.

///|
/// JSON representation of an ATD `int`.
pub(all) enum JsonInt {
  Int
  String
} derive(Eq, Debug)

///|
/// JSON representation of an ATD `float`.
pub(all) enum JsonFloat {
  /// Number with an optional maximum number of decimal places
  Float(Int?)
  Int
} derive(Eq, Debug)

///|
/// OCaml-specific JSON adapter.
pub(all) struct OcamlAdapter {
  normalize : String
  restore : String
} derive(Eq, Debug)

///|
/// JSON adapters for various target languages.
pub(all) struct JsonAdapter {
  ocaml_adapter : OcamlAdapter?
  java_adapter : String?
} derive(Eq, Debug)

///|
/// JSON representation of lists: JSON arrays, or JSON objects for lists of
/// pairs whose first element is a string.
pub(all) enum JsonList {
  Array
  Object
} derive(Eq, Debug)

///|
/// Representation of the variants of a sum type with a payload:
/// `["Cons", payload]` (Array, default) or `{"Cons": payload}` (Object).
pub(all) enum JsonSumRepr {
  Array
  Object
} derive(Eq, Debug)

///|
/// Record options.
pub(all) struct JsonRecord {
  json_keep_nulls : Bool
  json_record_adapter : JsonAdapter
} derive(Eq, Debug)

///|
/// Sum type options.
pub(all) struct JsonSum {
  json_sum_adapter : JsonAdapter
  json_open_enum : Bool
  json_lowercase_tags : Bool
  json_sum_repr : JsonSumRepr
} derive(Eq, Debug)

///|
/// All the valid annotations of the form ``.
pub let json_annot_schema : Schema = [
  {
    section: "json",
    fields: [
      (TypeExpr, "adapter.ocaml"),
      (TypeExpr, "adapter.to_ocaml"),
      (TypeExpr, "adapter.from_ocaml"),
      (TypeExpr, "keep_nulls"),
      (TypeExpr, "open_enum"),
      (TypeExpr, "precision"),
      (TypeExpr, "repr"),
      (Variant, "name"),
      (Field, "name"),
      (Field, "tag_field"),
    ],
  },
  {
    section: "ocaml_json",
    fields: [
      (TypeDef, "from"),
      (TypeDef, "module"),
      (TypeDef, "predef"),
      (TypeDef, "t"),
    ],
  },
]

///|
/// Representation of an `int`: ``.
pub fn get_json_int(an : Annot) -> JsonInt raise AtdError {
  annot_get_field(
    an,
    parse=s => {
      match s {
        "int" => Some(JsonInt::Int)
        "string" => Some(JsonInt::String)
        _ => None
      }
    },
    default=JsonInt::Int,
    sections=["json"],
    field="repr",
  )
}

///|
/// ``
pub fn get_json_precision(an : Annot) -> Int? raise AtdError {
  annot_get_opt_field(
    an,
    parse=s => {
      try @string.parse_int(s) catch {
        _ => None
      } noraise {
        n => Some(n)
      }
    },
    sections=["json"],
    field="precision",
  )
}

///|
/// Representation of a `float`: ``.
pub fn get_json_float(an : Annot) -> JsonFloat raise AtdError {
  let is_float = annot_get_field(
    an,
    parse=s => {
      match s {
        "float" => Some(true)
        "int" => Some(false)
        _ => None
      }
    },
    default=true,
    sections=["json"],
    field="repr",
  )
  if is_float {
    Float(get_json_precision(an))
  } else {
    Int
  }
}

///|
/// Representation of a list: ``.
pub fn get_json_list(an : Annot) -> JsonList raise AtdError {
  annot_get_field(
    an,
    parse=s => {
      match s {
        "array" => Some(JsonList::Array)
        "object" => Some(JsonList::Object)
        _ => None
      }
    },
    default=JsonList::Array,
    sections=["json"],
    field="repr",
  )
}

///|
/// JSON adapters: `` etc.
pub fn get_json_adapter(an : Annot) -> JsonAdapter raise AtdError {
  let get_field = field => annot_get_string(an, sections=["json"], field~)
  let get_loc = field => {
    match annot_get_loc(an, sections=["json"], field~) {
      Some(loc) => loc
      None => dummy_loc
    }
  }
  let field_module = "adapter.ocaml"
  let field_normalize = "adapter.to_ocaml"
  let field_restore = "adapter.from_ocaml"
  let ocaml_adapter = match
    (
      get_field(field_module),
      get_field(field_normalize),
      get_field(field_restore),
    ) {
    (None, None, None) => None
    (Some(m), None, None) =>
      Some({ normalize: m + ".normalize", restore: m + ".restore", })
    (None, Some(normalize), Some(restore)) =>
      Some({ normalize: "(" + normalize + ")", restore: "(" + restore + ")", })
    (Some(_), _, _) =>
      error_at(
        get_loc(field_module),
        "Cannot use \{ocaml_quote(field_module)} field together with \{ocaml_quote(field_normalize)} or \{ocaml_quote(field_restore)}",
      )
    (None, Some(_), None) =>
      error_at(
        get_loc(field_normalize),
        "\{ocaml_quote(field_normalize)} is used without required counterpart \{ocaml_quote(field_restore)}",
      )
    (None, None, Some(_)) =>
      error_at(
        get_loc(field_restore),
        "\{ocaml_quote(field_restore)} is used without required counterpart \{ocaml_quote(field_normalize)}",
      )
  }
  let java_adapter = get_field("adapter.java")
  { ocaml_adapter, java_adapter, }
}

///|
/// ``
pub fn get_json_open_enum(an : Annot) -> Bool raise AtdError {
  annot_get_flag(an, sections=["json"], field="open_enum")
}

///|
/// ``
pub fn get_json_lowercase_tags(an : Annot) -> Bool raise AtdError {
  annot_get_flag(an, sections=["json"], field="lowercase_tags")
}

///|
/// Representation of sum types: ``.
pub fn get_json_sum_repr(an : Annot) -> JsonSumRepr raise AtdError {
  annot_get_field(
    an,
    parse=s => {
      match s {
        "object" => Some(JsonSumRepr::Object)
        _ => None
      }
    },
    default=JsonSumRepr::Array,
    sections=["json"],
    field="repr",
  )
}

///|
/// All the options of a sum type.
pub fn get_json_sum(an : Annot) -> JsonSum raise AtdError {
  {
    json_sum_adapter: get_json_adapter(an),
    json_open_enum: get_json_open_enum(an),
    json_lowercase_tags: get_json_lowercase_tags(an),
    json_sum_repr: get_json_sum_repr(an),
  }
}

///|
/// JSON name of a variant: ``.
pub fn get_json_cons(default : String, an : Annot) -> String raise AtdError {
  annot_get_field(
    an,
    parse=s => Some(s),
    default~,
    sections=["json"],
    field="name",
  )
}

///|
/// JSON name of a field: ``.
pub fn get_json_fname(default : String, an : Annot) -> String raise AtdError {
  annot_get_field(
    an,
    parse=s => Some(s),
    default~,
    sections=["json"],
    field="name",
  )
}

///|
/// ``
pub fn get_json_keep_nulls(an : Annot) -> Bool raise AtdError {
  annot_get_flag(an, sections=["json"], field="keep_nulls")
}

///|
/// All the options of a record type.
pub fn get_json_record(an : Annot) -> JsonRecord raise AtdError {
  {
    json_keep_nulls: get_json_keep_nulls(an),
    json_record_adapter: get_json_adapter(an),
  }
}