///|
/// The same schema written without annotations: types built by hand, data
/// handed to the executor as JSON, and resolvers reading JSON back. The
/// annotated schema must print and answer exactly as this one does.
fn hand_register(s : @moongql.Schema, r : @moongql.Resolvers) -> Unit {
  s.scalar("Int64")
  let author = s.object("Author", description="Someone who writes books")
  author.field("id", NonNull(Scalar("Int64")))
  author.field("name", NonNull(Scalar("String")))
  s.enum_("Status", ["Draft", "Published"], description="Where a book stands")
  let book = s.object("Book")
  book.field("id", NonNull(Scalar("Int64")))
  book.field("title", NonNull(Scalar("String")))
  book.field("status", NonNull(Named("Status")))
  book.field("authorId", NonNull(Scalar("Int64")))
  book.field(
    "isbn",
    Scalar("String"),
    deprecation_reason="ISBNs are going away",
  )
  let input = s.input("NewBook")
  input.field("title", NonNull(Scalar("String")))
  input.field("authorId", NonNull(Scalar("Int64")))
  input.field("status", Named("Status"))
  let magazine = s.object("Magazine")
  magazine.field("id", NonNull(Scalar("Int64")))
  magazine.field("title", NonNull(Scalar("String")))
  magazine.field("issue", NonNull(Scalar("Int")))
  let work = s.interface("Work", description="Anything on the shelf")
  work.field("id", NonNull(Scalar("Int64")))
  work.field("title", NonNull(Scalar("String")))
  book.implements("Work")
  magazine.implements("Work")
  s.union("Hit", ["Author", "Book"])
  let query = s.object("Query")
  s.set_mutation("Mutation")
  let mutation = s.object("Mutation")
  author.field_args(
    "books",
    [("first", Scalar("Int"))],
    NonNull(ListOf(NonNull(Named("Book")))),
    description="Their books, at most `first`",
  )
  r.field("Author", "books", info => {
    let id = key_of(info.parent, "id")
    let mine = shelf.filter(b => Json::string(b.author_id.to_string()) == id)
    let mine = match info.arg("first") {
      Number(n, ..) if n.to_int() < mine.length() =>
        mine[:n.to_int()].to_owned()
      _ => mine
    }
    Json::array(mine.map(book_json))
  })
  author.field("contact", NonNull(Scalar("String")))
  r.field("Author", "contact", info => key_of(info.parent, "email"))
  book.field("author", Named("Author"))
  r.field("Book", "author", info => {
    let id = key_of(info.parent, "authorId")
    match writers.search_by(a => Json::string(a.id.to_string()) == id) {
      Some(i) => author_json(writers[i])
      None => Json::null()
    }
  })
  query.field_args(
    "author",
    [("id", NonNull(Scalar("Int64")))],
    Named("Author"),
  )
  r.field("Query", "author", info => {
    let id = info.arg("id")
    match writers.search_by(a => Json::string(a.id.to_string()) == id) {
      Some(i) => author_json(writers[i])
      None => Json::null()
    }
  })
  query.field_args(
    "books",
    [("status", Named("Status"))],
    NonNull(ListOf(NonNull(Named("Book")))),
    description="Every book, or those in one state",
  )
  r.field("Query", "books", info => {
    let picked = match info.arg("status") {
      String(s) => shelf.filter(b => status_name(b.status) == s)
      _ => shelf
    }
    Json::array(picked.map(book_json))
  })
  query.field("works", NonNull(ListOf(NonNull(Named("Work")))))
  r.field("Query", "works", _ => {
    Json::array(shelf.map(book_json) + issues.map(magazine_json))
  })
  query.field_args(
    "search",
    [("text", NonNull(Scalar("String")))],
    NonNull(ListOf(NonNull(Named("Hit")))),
    description="Authors and titles that contain `text`",
  )
  r.field("Query", "search", info => {
    let text = match info.arg("text") {
      String(t) => t
      _ => ""
    }
    Json::array(
      writers.filter(a => a.name.contains(text)).map(author_json) +
      shelf.filter(b => b.title.contains(text)).map(book_json),
    )
  })
  mutation.field_args(
    "addBook",
    [("input", NonNull(Named("NewBook")))],
    NonNull(Named("Book")),
  )
  r.field("Mutation", "addBook", info => {
    let input = info.arg("input")
    let title = match key_of(input, "title") {
      String(t) => t
      _ => ""
    }
    if title == "" {
      raise @exec.ResolverError("a book needs a title")
    }
    let author_id = match key_of(input, "authorId") {
      String(t) => @string.parse_int64(t) catch { _ => 0L }
      _ => 0L
    }
    let status = match key_of(input, "status") {
      String("Published") => Published
      _ => Draft
    }
    let created : Book = {
      id: (shelf.length() + 1).to_int64(),
      title,
      status,
      author_id,
      code: None,
    }
    shelf.push(created)
    book_json(created)
  })
}

///|
fn key_of(v : Json, key : String) -> Json {
  match v {
    Object(m) => m.get(key).unwrap_or(Json::null())
    _ => Json::null()
  }
}

///|
fn status_name(s : Status) -> String {
  match s {
    Draft => "Draft"
    Published => "Published"
  }
}

///|
fn author_json(a : Author) -> Json {
  {
    "__typename": "Author",
    "id": Json::string(a.id.to_string()),
    "name": Json::string(a.name),
    "email": Json::string(a.email),
  }
}

///|
fn book_json(b : Book) -> Json {
  {
    "__typename": "Book",
    "id": Json::string(b.id.to_string()),
    "title": Json::string(b.title),
    "status": Json::string(status_name(b.status)),
    "authorId": Json::string(b.author_id.to_string()),
    "isbn": match b.code {
      Some(c) => Json::string(c)
      None => Json::null()
    },
  }
}

///|
fn magazine_json(m : Magazine) -> Json {
  {
    "__typename": "Magazine",
    "id": Json::string(m.id.to_string()),
    "title": Json::string(m.title),
    "issue": Json::number(m.issue.to_double()),
  }
}