///|
/// IR serialization: ApiIr -> canonical JSON.
///
/// Produces the JSON format that the codegen backends consume.

///|
/// Convert HttpMethod to string.
fn method_to_string(m : HttpMethod) -> String {
  match m {
    Get => "GET"
    Post => "POST"
    Put => "PUT"
    Patch => "PATCH"
    Delete => "DELETE"
  }
}

///|
/// Convert ParamLocation to string.
fn location_to_string(l : ParamLocation) -> String {
  match l {
    Path => "path"
    Query => "query"
    Header => "header"
  }
}

///|
/// Convert a json array from array of strings.
fn strings_to_json(xs : Array[String]) -> Json {
  let out : Array[Json] = []
  for s in xs {
    out.push(Json::string(s))
  }
  Json::array(out)
}

///|
/// Serialize model members to JSON.
fn members_to_json(members : Array[(String, String)]) -> Json {
  let out : Array[Json] = []
  for pair in members {
    let (name, wire) = pair
    out.push(Json::array([Json::string(name), Json::string(wire)]))
  }
  Json::array(out)
}

///|
/// Serialize a FieldIr to JSON.
fn field_to_json(f : FieldIr) -> Json {
  let required = is_required_presence(f.presence)
  let nullable = is_nullable_presence(f.presence)
  Json::object(
    Map([
      ("name", Json::string(f.name)),
      ("wire_name", Json::string(f.wire_name)),
      ("type", type_ref_to_json(f.type_ref)),
      ("required", Json::boolean(required)),
      ("nullable", Json::boolean(nullable)),
      ("presence", Json::string(presence_to_string(f.presence))),
    ]),
  )
}

///|
/// Serialize fields array to JSON.
fn fields_to_json(fields : Array[FieldIr]) -> Json {
  let out : Array[Json] = []
  for f in fields {
    out.push(field_to_json(f))
  }
  Json::array(out)
}

///|
/// Serialize a model to JSON.
fn model_to_json(m : ModelIr) -> Json {
  match m {
    EnumModel(e) =>
      Json::object(
        Map([
          ("kind", Json::string("enum")),
          ("name", Json::string(e.name)),
          ("members", members_to_json(e.members)),
        ]),
      )
    StructModel(s) => {
      let additional_field = match s.additional_properties_field {
        Some(name) => Json::string(name)
        None => Json::null()
      }
      Json::object(
        Map([
          ("kind", Json::string("struct")),
          ("name", Json::string(s.name)),
          ("additional_properties", Json::boolean(s.additional_properties)),
          ("additional_properties_field", additional_field),
          ("fields", fields_to_json(s.fields)),
        ]),
      )
    }
  }
}

///|
/// Serialize models array to JSON.
fn models_to_json(models : Array[ModelIr]) -> Json {
  let out : Array[Json] = []
  for m in models {
    out.push(model_to_json(m))
  }
  Json::array(out)
}

///|
/// Serialize a ParameterIr to JSON.
fn parameter_to_json(p : ParameterIr) -> Json {
  Json::object(
    Map([
      ("name", Json::string(p.name)),
      ("wire_name", Json::string(p.wire_name)),
      ("location", Json::string(location_to_string(p.location))),
      ("type", type_ref_to_json(p.type_ref)),
      ("required", Json::boolean(p.required)),
      ("nullable", Json::boolean(p.nullable)),
      (
        "presence",
        Json::string(
          presence_to_string(compute_presence(p.required, p.nullable)),
        ),
      ),
    ]),
  )
}

///|
/// Serialize parameters array to JSON.
fn parameters_to_json(params : Array[ParameterIr]) -> Json {
  let out : Array[Json] = []
  for p in params {
    out.push(parameter_to_json(p))
  }
  Json::array(out)
}

///|
/// Serialize a RequestBodyIr to JSON.
fn request_body_to_json(rb : RequestBodyIr?) -> Json {
  match rb {
    Some(body) =>
      Json::object(
        Map([
          (
            "type",
            match body.type_ref {
              Some(t) => type_ref_to_json(t)
              None => Json::null()
            },
          ),
          ("required", Json::boolean(body.required)),
          (
            "media_type",
            match body.media_type {
              Some(s) => Json::string(s)
              None => Json::null()
            },
          ),
        ]),
      )
    None => Json::null()
  }
}

///|
/// Serialize a ResponseIr to JSON.
fn response_to_json(r : ResponseIr) -> Json {
  Json::object(
    Map([
      ("status", Json::number(r.status.to_double())),
      (
        "type",
        match r.type_ref {
          Some(t) => type_ref_to_json(t)
          None => Json::null()
        },
      ),
      (
        "media_type",
        match r.media_type {
          Some(s) => Json::string(s)
          None => Json::null()
        },
      ),
    ]),
  )
}

///|
/// Serialize responses array to JSON.
fn responses_to_json(responses : Array[ResponseIr]) -> Json {
  let out : Array[Json] = []
  for r in responses {
    out.push(response_to_json(r))
  }
  Json::array(out)
}

///| Serialize an OperationIr to JSON.

///|
/// Serialize a ResponseVariant to JSON.
fn variant_to_json(v : ResponseVariant) -> Json {
  Json::object(
    Map([
      ("status", Json::number(v.status.to_double())),
      ("type", type_ref_to_json(v.type_ref)),
    ]),
  )
}

///|
/// Serialize ResponseStrategy to JSON.
fn strategy_to_json(s : ResponseStrategy) -> Json {
  match s {
    SingleResult(tr) =>
      Json::object(
        Map([
          ("kind", Json::string("single_result")),
          ("type", type_ref_to_json(tr)),
        ]),
      )
    UnitResult => Json::object(Map([("kind", Json::string("unit_result"))]))
    ResponseEnum(variants) => {
      let out : Array[Json] = []
      for v in variants {
        out.push(variant_to_json(v))
      }
      Json::object(
        Map([
          ("kind", Json::string("response_enum")),
          ("variants", Json::array(out)),
        ]),
      )
    }
    NoContent => Json::object(Map([("kind", Json::string("no_content"))]))
    UnsupportedMediaType(mt) =>
      Json::object(
        Map([
          ("kind", Json::string("unsupported_media_type")),
          ("media_type", Json::string(mt)),
        ]),
      )
  }
}

///|
fn body_name_to_json(name : String?) -> Json {
  match name {
    Some(n) => Json::string(n)
    None => Json::null()
  }
}

///|
/// Serialize an OperationIr to JSON.
fn operation_to_json(op : OperationIr) -> Json {
  Json::object(
    Map([
      ("operation_id", Json::string(op.operation_id)),
      ("http_method", Json::string(method_to_string(op.http_method))),
      ("path", Json::string(op.path)),
      ("fn_name", Json::string(op.fn_name)),
      ("tags", strings_to_json(op.tags)),
      ("parameters", parameters_to_json(op.parameters)),
      ("request_body", request_body_to_json(op.request_body)),
      ("body_name", body_name_to_json(op.body_name)),
      ("success_responses", responses_to_json(op.success_responses)),
      ("error_responses", responses_to_json(op.error_responses)),
      ("response_strategy", strategy_to_json(op.response_strategy)),
      ("security", strings_to_json(op.security)),
      ("source_pointer", Json::string(op.source.pointer)),
    ]),
  )
}

///|
/// Serialize AuthSchemeIr to JSON.
fn auth_scheme_to_json(s : AuthSchemeIr) -> Json {
  Json::object(
    Map([
      ("name", Json::string(s.name)),
      ("kind", Json::string(s.kind)),
      (
        "location",
        match s.location {
          Some(l) => Json::string(l)
          None => Json::null()
        },
      ),
      (
        "key_name",
        match s.key_name {
          Some(k) => Json::string(k)
          None => Json::null()
        },
      ),
    ]),
  )
}

///|
/// Serialize auth schemes array to JSON.
fn auth_schemes_to_json(schemes : Array[AuthSchemeIr]) -> Json {
  let out : Array[Json] = []
  for s in schemes {
    out.push(auth_scheme_to_json(s))
  }
  Json::array(out)
}

///|
/// Serialize operations array to JSON.
fn operations_to_json(ops : Array[OperationIr]) -> Json {
  let out : Array[Json] = []
  for op in ops {
    out.push(operation_to_json(op))
  }
  Json::array(out)
}

///|
/// Serialize the full ApiIr to canonical JSON.
pub fn api_to_json(api : ApiIr) -> Json {
  Json::object(
    Map([
      ("module", Json::string(api.module_name)),
      ("title", Json::string(api.title)),
      ("version", Json::string(api.version)),
      ("servers", strings_to_json(api.servers)),
      ("auth_schemes", auth_schemes_to_json(api.auth_schemes)),
      ("models", models_to_json(api.models)),
      ("operations", operations_to_json(api.operations)),
    ]),
  )
}

///|
/// Serialize ApiIr to a canonical JSON string.
pub fn api_to_json_string(api : ApiIr) -> String {
  api_to_json(api).stringify() + "\n"
}