// The `gql` generator: turn a `.api` spec's `type` blocks into a moongql
// code-first schema plus resolver stubs, the GraphQL counterpart of the moonapi
// (`generate`) and moonrpc (`generate_grpc`) generators. goctl has no GraphQL
// target; this is the moon-heke suite extending mctl to scaffold the whole stack.
// The output compiles against `Lfan-ke/moongql`.

///|
/// Lowercase the first character (the inverse of `cap_first`), naming a `Query`
/// lookup field after its type.
fn lower_first(s : String) -> String {
  if s.length() == 0 {
    return s
  }
  let c = s[0].to_int()
  if c >= 0x41 && c <= 0x5A {
    (c + 0x20).to_byte().to_char().to_string() + s[1:].to_owned()
  } else {
    s
  }
}

///|
/// Whether `s` begins with `prefix` (core ships no `String::starts_with`).
fn gql_starts_with(s : String, prefix : String) -> Bool {
  if s.length() < prefix.length() {
    return false
  }
  for i = 0; i < prefix.length(); i = i + 1 {
    if s[i] != prefix[i] {
      return false
    }
  }
  true
}

///|
/// A non-null scalar constructor expression.
fn nn_scalar(name : String) -> String {
  "@moongql.NonNull(@moongql.Scalar(\"" + name + "\"))"
}

///|
/// The moongql `GqlType` constructor expression for a field's MoonBit type (as the
/// `.api` parser emits it). MoonBit scalars map to GraphQL scalars, `Array[T]` to a
/// non-null list, a `Map[...]` to a JSON string (GraphQL has no map type), and any
/// other name to a `Named` reference. Every field is `NonNull` — the scaffold's
/// default, loosened per field as the schema is filled in.
fn gql_type_expr(mb_type : String) -> String {
  if gql_starts_with(mb_type, "Array[") {
    let inner = mb_type[6:mb_type.length() - 1].to_owned()
    "@moongql.NonNull(@moongql.ListOf(" + gql_type_expr(inner) + "))"
  } else if gql_starts_with(mb_type, "Map[") {
    nn_scalar("String")
  } else {
    match mb_type {
      "String" => nn_scalar("String")
      "Int" => nn_scalar("Int")
      "Int64" => nn_scalar("Int")
      "UInt" => nn_scalar("Int")
      "UInt64" => nn_scalar("Int")
      "Bool" => nn_scalar("Boolean")
      "Double" => nn_scalar("Float")
      "Float" => nn_scalar("Float")
      "ID" => nn_scalar("ID")
      other => "@moongql.NonNull(@moongql.Named(\"" + other + "\"))"
    }
  }
}

///|
/// A resolver stub line: `resolvers.field("", "", fn(info) { ... })`,
/// a null placeholder for the caller to replace with real logic.
fn resolver_stub(type_name : String, field_name : String) -> String {
  "  resolvers.field(\"" +
  type_name +
  "\", \"" +
  field_name +
  "\", fn(info) {\n    let _ = info\n    Json::null()\n  })\n"
}

///|
/// Generate a moongql code-first schema and resolver stubs from `spec`'s `type`
/// blocks. Each `type` becomes an object type with its fields mapped to GraphQL
/// types; a `Query` root gains a `(id: ID!): ` lookup per type; and
/// every field gets a resolver stub. The output compiles against
/// `Lfan-ke/moongql`; fill in the stub bodies to make it run.
pub fn generate_gql(spec : Spec) -> String {
  let mut out = "// Code generated by moonctl. DO NOT EDIT.\n\n"
  out = out +
    "///|\n" +
    "/// A moongql code-first schema built from the `.api` `type` blocks.\n" +
    "pub fn build_schema() -> @moongql.Schema {\n" +
    "  let schema = @moongql.Schema::new()\n"
  for t in spec.types {
    let v = lower_first(t.name)
    out = out + "  let " + v + " = schema.object(\"" + t.name + "\")\n"
    for f in t.fields {
      out = out +
        "  " +
        v +
        ".field(\"" +
        f.name +
        "\", " +
        gql_type_expr(f.type_) +
        ")\n"
    }
  }
  out = out + "  let query = schema.object(\"Query\")\n"
  for t in spec.types {
    out = out +
      "  query.field_args(\"" +
      lower_first(t.name) +
      "\", [(\"id\", @moongql.NonNull(@moongql.Scalar(\"ID\")))], @moongql.Named(\"" +
      t.name +
      "\"))\n"
  }
  out = out + "  schema\n}\n\n"
  out = out +
    "///|\n" +
    "/// Resolver stubs for the generated schema. Replace each body with real logic.\n" +
    "pub fn resolvers() -> @moongql.Resolvers {\n" +
    "  let resolvers = @moongql.Resolvers::new()\n"
  for t in spec.types {
    for f in t.fields {
      out = out + resolver_stub(t.name, f.name)
    }
  }
  for t in spec.types {
    out = out + resolver_stub("Query", lower_first(t.name))
  }
  out = out + "  resolvers\n}\n"
  out
}