///|
priv struct Formatter {
  buf : StringBuilder
  style : Style
  mut indent : Int32
}

///|
fn Formatter::new(style : Style) -> Formatter {
  { buf: StringBuilder(size_hint=1024), style, indent: 0, }
}

///|
fn Formatter::to_string(self : Formatter) -> String {
  self.buf.to_string()
}

///|
fn Formatter::write(self : Formatter, value : String) -> Unit {
  self.buf.write_string(value)
}

///|
fn Formatter::write_char(self : Formatter, value : Char) -> Unit {
  self.buf.write_char(value)
}

///|
fn Formatter::write_indent(self : Formatter) -> Unit {
  for _ in 0.. Unit {
  self.buf.write_char('\n')
}

///|
fn Formatter::inc_indent(self : Formatter) -> Unit {
  self.indent += self.style.indent
}

///|
fn Formatter::dec_indent(self : Formatter) -> Unit {
  self.indent -= self.style.indent
}

///|
fn Formatter::start_argument_block(self : Formatter, open_char : Char) -> Unit {
  self.buf.write_char(open_char)
  if self.style.multiline_arguments {
    self.inc_indent()
  }
}

///|
fn Formatter::end_argument_block(self : Formatter, close_char : Char) -> Unit {
  if self.style.multiline_arguments {
    self.endline()
    self.dec_indent()
    self.write_indent()
  }
  self.buf.write_char(close_char)
}

///|
fn Formatter::start_argument(self : Formatter) -> Unit {
  if self.style.multiline_arguments {
    self.endline()
    self.write_indent()
  }
}

///|
fn Formatter::delineate_argument(self : Formatter) -> Unit {
  self.buf.write_char(',')
  if !self.style.multiline_arguments {
    self.buf.write_char(' ')
  }
}

///|
fn Formatter::start_block(self : Formatter) -> Unit {
  self.write("{")
  self.endline()
  self.inc_indent()
}

///|
fn Formatter::end_block(self : Formatter) -> Unit {
  self.dec_indent()
  self.write_indent()
  self.write("}")
  self.endline()
}

///|
fn Formatter::margin(self : Formatter) -> Unit {
  if !self.buf.is_empty() {
    self.endline()
  }
}

///|
fn Formatter::write_quoted(self : Formatter, value : String) -> Unit {
  let mut has_newline = false
  let mut has_nonprintable = false
  for c in value.iter() {
    if c == '\n' {
      has_newline = true
    } else if c == '\r' ||
      c == '\t' ||
      (c.to_int() >= 0x20 && c.to_int() <= 0xffff) {
      ()
    } else {
      has_nonprintable = true
    }
  }
  if !has_newline || has_nonprintable {
    self.write("\"")
    for c in value.iter() {
      match c {
        '\r' => self.write("\\r")
        '\n' => self.write("\\n")
        '\t' => self.write("\\t")
        '"' => self.write("\\\"")
        '\\' => self.write("\\\\")
        _ if c.to_int() >= 0x20 && c.to_int() <= 0xffff => self.write_char(c)
        _ => {
          let hex = c.to_int().to_string(radix=16)
          self.write("\\u")
          for _ in 0..<(4 - hex.length()) {
            self.write("0")
          }
          self.write(hex)
        }
      }
    }
    self.write("\"")
  } else {
    self.write("\"\"\"")
    self.endline()
    self.inc_indent()
    for line in rust_lines(value) {
      if !line.trim().is_empty() {
        self.write_indent()
        self.write(line.replace_all(old="\"\"\"", new="\\\"\"\""))
      }
      self.endline()
    }
    self.dec_indent()
    self.write_indent()
    self.write("\"\"\"")
  }
}

///|
/// Format an executable GraphQL document with a stable canonical layout.
pub fn QueryDocument::format(
  self : QueryDocument,
  style~ : Style = Style::default(),
) -> String {
  let formatter = Formatter::new(style)
  self.display(formatter)
  formatter.to_string()
}

///|
/// Format an executable GraphQL document using the default style.
pub fn QueryDocument::to_string(self : QueryDocument) -> String {
  self.format()
}

///|
pub impl Show for QueryDocument with fn output(
  self : QueryDocument,
  logger : &Logger,
) -> Unit {
  logger.write_string(self.to_string())
}

///|
fn QueryDocument::display(self : QueryDocument, formatter : Formatter) -> Unit {
  for item in self.definitions {
    item.display(formatter)
  }
}

///|
fn QueryDefinition::display(
  self : QueryDefinition,
  formatter : Formatter,
) -> Unit {
  match self {
    OperationDefinition(op) => op.display(formatter)
    FragmentDefinition(fragment) => fragment.display(formatter)
  }
}

///|
fn OperationDefinition::display(
  self : OperationDefinition,
  formatter : Formatter,
) -> Unit {
  match self {
    SelectionSetOperation(set) => set.display(formatter)
    QueryOperation(query) => query.display(formatter)
    MutationOperation(mutation) => mutation.display(formatter)
    SubscriptionOperation(subscription) => subscription.display(formatter)
  }
}

///|
fn FragmentDefinition::display(
  self : FragmentDefinition,
  formatter : Formatter,
) -> Unit {
  formatter.margin()
  formatter.write_indent()
  formatter.write("fragment ")
  formatter.write(self.name)
  formatter.write(" ")
  self.type_condition.display(formatter)
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  for item in self.selection_set.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn SelectionSet::display(self : SelectionSet, formatter : Formatter) -> Unit {
  formatter.margin()
  formatter.write_indent()
  formatter.start_block()
  for item in self.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn Selection::display(self : Selection, formatter : Formatter) -> Unit {
  match self {
    FieldSelection(field) => field.display(formatter)
    FragmentSpreadSelection(fragment) => fragment.display(formatter)
    InlineFragmentSelection(fragment) => fragment.display(formatter)
  }
}

///|
fn display_query_arguments(
  arguments : Array[(String, Value)],
  formatter : Formatter,
) -> Unit {
  if !arguments.is_empty() {
    formatter.start_argument_block('(')
    formatter.start_argument()
    formatter.write(arguments[0].0)
    formatter.write(": ")
    arguments[0].1.display(formatter)
    for arg in arguments.exact_view(start=1) {
      formatter.delineate_argument()
      formatter.start_argument()
      formatter.write(arg.0)
      formatter.write(": ")
      arg.1.display(formatter)
    }
    formatter.end_argument_block(')')
  }
}

///|
fn Field::display(self : Field, formatter : Formatter) -> Unit {
  formatter.write_indent()
  match self.field_alias {
    Some(alias_value) => {
      formatter.write(alias_value)
      formatter.write(": ")
    }
    None => ()
  }
  formatter.write(self.name)
  display_query_arguments(self.arguments, formatter)
  display_directives(self.directives, formatter)
  if !self.selection_set.items.is_empty() {
    formatter.write(" ")
    formatter.start_block()
    for item in self.selection_set.items {
      item.display(formatter)
    }
    formatter.end_block()
  } else {
    formatter.endline()
  }
}

///|
fn Query::display(self : Query, formatter : Formatter) -> Unit {
  formatter.margin()
  formatter.write_indent()
  formatter.write("query")
  match self.name {
    Some(name) => {
      formatter.write(" ")
      formatter.write(name)
    }
    None => ()
  }
  display_variable_definitions(self.variable_definitions, formatter)
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  for item in self.selection_set.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn Mutation::display(self : Mutation, formatter : Formatter) -> Unit {
  formatter.margin()
  formatter.write_indent()
  formatter.write("mutation")
  match self.name {
    Some(name) => {
      formatter.write(" ")
      formatter.write(name)
    }
    None => ()
  }
  display_variable_definitions(self.variable_definitions, formatter)
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  for item in self.selection_set.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn Subscription::display(self : Subscription, formatter : Formatter) -> Unit {
  formatter.margin()
  formatter.write_indent()
  formatter.write("subscription")
  match self.name {
    Some(name) => {
      formatter.write(" ")
      formatter.write(name)
      display_variable_definitions(self.variable_definitions, formatter)
    }
    None => ()
  }
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  for item in self.selection_set.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn display_variable_definitions(
  variables : Array[VariableDefinition],
  formatter : Formatter,
) -> Unit {
  if !variables.is_empty() {
    formatter.write("(")
    variables[0].display(formatter)
    for variable in variables.exact_view(start=1) {
      formatter.write(", ")
      variable.display(formatter)
    }
    formatter.write(")")
  }
}

///|
fn VariableDefinition::display(
  self : VariableDefinition,
  formatter : Formatter,
) -> Unit {
  formatter.write("$")
  formatter.write(self.name)
  formatter.write(": ")
  self.var_type.display(formatter)
  match self.default_value {
    Some(value) => {
      formatter.write(" = ")
      value.display(formatter)
    }
    None => ()
  }
}

///|
fn GqlType::display(self : GqlType, formatter : Formatter) -> Unit {
  match self {
    NamedType(name) => formatter.write(name)
    ListType(typ) => {
      formatter.write("[")
      typ.display(formatter)
      formatter.write("]")
    }
    NonNullType(typ) => {
      typ.display(formatter)
      formatter.write("!")
    }
  }
}

///|
fn Value::display(self : Value, formatter : Formatter) -> Unit {
  match self {
    Variable(name) => {
      formatter.write("$")
      formatter.write(name)
    }
    IntValue(number) => formatter.write(number.value.to_string())
    FloatValue(value) => formatter.write(value.to_string())
    StringValue(value) => formatter.write_quoted(value)
    BooleanValue(true) => formatter.write("true")
    BooleanValue(false) => formatter.write("false")
    NullValue => formatter.write("null")
    EnumValue(name) => formatter.write(name)
    ListValue(items) => {
      formatter.start_argument_block('[')
      if !items.is_empty() {
        formatter.start_argument()
        items[0].display(formatter)
        for item in items.exact_view(start=1) {
          formatter.delineate_argument()
          formatter.start_argument()
          item.display(formatter)
        }
      }
      formatter.end_argument_block(']')
    }
    ObjectValue(items) => {
      formatter.start_argument_block('{')
      let sorted = items.copy()
      sorted.sort_by((left, right) => left.0.lexical_compare(right.0))
      let mut first = true
      for item in sorted {
        if first {
          first = false
        } else {
          formatter.delineate_argument()
        }
        formatter.start_argument()
        formatter.write(item.0)
        formatter.write(": ")
        item.1.display(formatter)
      }
      formatter.end_argument_block('}')
    }
  }
}

///|
fn InlineFragment::display(
  self : InlineFragment,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("...")
  match self.type_condition {
    Some(cond) => {
      formatter.write(" ")
      cond.display(formatter)
    }
    None => ()
  }
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  for item in self.selection_set.items {
    item.display(formatter)
  }
  formatter.end_block()
}

///|
fn TypeCondition::display(self : TypeCondition, formatter : Formatter) -> Unit {
  match self {
    On(name) => {
      formatter.write("on ")
      formatter.write(name)
    }
  }
}

///|
fn FragmentSpread::display(
  self : FragmentSpread,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("...")
  formatter.write(self.fragment_name)
  display_directives(self.directives, formatter)
  formatter.endline()
}

///|
fn Directive::display(self : Directive, formatter : Formatter) -> Unit {
  formatter.write("@")
  formatter.write(self.name)
  display_query_arguments(self.arguments, formatter)
}

///|
fn display_directives(
  directives : Array[Directive],
  formatter : Formatter,
) -> Unit {
  for directive in directives {
    formatter.write(" ")
    directive.display(formatter)
  }
}

///|
/// Format an SDL document with a stable canonical layout.
pub fn SchemaDocument::format(
  self : SchemaDocument,
  style~ : Style = Style::default(),
) -> String {
  let formatter = Formatter::new(style)
  self.display(formatter)
  formatter.to_string()
}

///|
/// Format an SDL document using the default style.
pub fn SchemaDocument::to_string(self : SchemaDocument) -> String {
  self.format()
}

///|
pub impl Show for SchemaDocument with fn output(
  self : SchemaDocument,
  logger : &Logger,
) -> Unit {
  logger.write_string(self.to_string())
}

///|
fn SchemaDocument::display(
  self : SchemaDocument,
  formatter : Formatter,
) -> Unit {
  for item in self.definitions {
    item.display(formatter)
  }
}

///|
fn SchemaDefinition::display(
  self : SchemaDefinition,
  formatter : Formatter,
) -> Unit {
  formatter.margin()
  match self {
    SchemaDef(schema) => schema.display(formatter)
    TypeDef(definition) => definition.display(formatter)
    TypeExt(extension) => extension.display(formatter)
    DirectiveDef(directive) => directive.display(formatter)
  }
}

///|
fn SchemaDefinitionData::display(
  self : SchemaDefinitionData,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("schema")
  display_directives(self.directives, formatter)
  formatter.write(" ")
  formatter.start_block()
  match self.query {
    Some(name) => {
      formatter.write_indent()
      formatter.write("query: ")
      formatter.write(name)
      formatter.endline()
    }
    None => ()
  }
  match self.mutation {
    Some(name) => {
      formatter.write_indent()
      formatter.write("mutation: ")
      formatter.write(name)
      formatter.endline()
    }
    None => ()
  }
  match self.subscription {
    Some(name) => {
      formatter.write_indent()
      formatter.write("subscription: ")
      formatter.write(name)
      formatter.endline()
    }
    None => ()
  }
  formatter.end_block()
}

///|
fn TypeDefinition::display(
  self : TypeDefinition,
  formatter : Formatter,
) -> Unit {
  match self {
    ScalarTypeDef(scalar) => scalar.display(formatter)
    ObjectTypeDef(object) => object.display(formatter)
    InterfaceTypeDef(interface_) => interface_.display(formatter)
    UnionTypeDef(union_) => union_.display(formatter)
    EnumTypeDef(enum_) => enum_.display(formatter)
    InputObjectTypeDef(input) => input.display(formatter)
  }
}

///|
fn display_description(description : String?, formatter : Formatter) -> Unit {
  match description {
    Some(value) => {
      formatter.write_indent()
      formatter.write_quoted(value)
      formatter.endline()
    }
    None => ()
  }
}

///|
fn ScalarType::display(self : ScalarType, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("scalar ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  formatter.endline()
}

///|
fn ScalarTypeExtension::display(
  self : ScalarTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend scalar ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  formatter.endline()
}

///|
fn display_schema_fields(
  fields : Array[SchemaField],
  formatter : Formatter,
) -> Unit {
  if !fields.is_empty() {
    formatter.write(" ")
    formatter.start_block()
    for field in fields {
      field.display(formatter)
    }
    formatter.end_block()
  } else {
    formatter.endline()
  }
}

///|
fn display_implements(
  interfaces : Array[String],
  formatter : Formatter,
) -> Unit {
  if !interfaces.is_empty() {
    formatter.write(" implements ")
    formatter.write(interfaces[0])
    for name in interfaces.exact_view(start=1) {
      formatter.write(" & ")
      formatter.write(name)
    }
  }
}

///|
fn ObjectType::display(self : ObjectType, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("type ")
  formatter.write(self.name)
  display_implements(self.implements_interfaces, formatter)
  display_directives(self.directives, formatter)
  display_schema_fields(self.fields, formatter)
}

///|
fn ObjectTypeExtension::display(
  self : ObjectTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend type ")
  formatter.write(self.name)
  display_implements(self.implements_interfaces, formatter)
  display_directives(self.directives, formatter)
  display_schema_fields(self.fields, formatter)
}

///|
fn InputValue::display(self : InputValue, formatter : Formatter) -> Unit {
  match self.description {
    Some(value) => {
      formatter.write_quoted(value)
      formatter.write(" ")
    }
    None => ()
  }
  formatter.write(self.name)
  formatter.write(": ")
  self.value_type.display(formatter)
  match self.default_value {
    Some(value) => {
      formatter.write(" = ")
      value.display(formatter)
    }
    None => ()
  }
  display_directives(self.directives, formatter)
}

///|
fn display_input_arguments(
  arguments : Array[InputValue],
  formatter : Formatter,
) -> Unit {
  if !arguments.is_empty() {
    formatter.write("(")
    arguments[0].display(formatter)
    for arg in arguments.exact_view(start=1) {
      formatter.write(", ")
      arg.display(formatter)
    }
    formatter.write(")")
  }
}

///|
fn SchemaField::display(self : SchemaField, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write(self.name)
  display_input_arguments(self.arguments, formatter)
  formatter.write(": ")
  self.field_type.display(formatter)
  display_directives(self.directives, formatter)
  formatter.endline()
}

///|
fn InterfaceType::display(self : InterfaceType, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("interface ")
  formatter.write(self.name)
  display_implements(self.implements_interfaces, formatter)
  display_directives(self.directives, formatter)
  display_schema_fields(self.fields, formatter)
}

///|
fn InterfaceTypeExtension::display(
  self : InterfaceTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend interface ")
  formatter.write(self.name)
  display_implements(self.implements_interfaces, formatter)
  display_directives(self.directives, formatter)
  display_schema_fields(self.fields, formatter)
}

///|
fn UnionType::display(self : UnionType, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("union ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_union_members(self.types, formatter)
  formatter.endline()
}

///|
fn UnionTypeExtension::display(
  self : UnionTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend union ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_union_members(self.types, formatter)
  formatter.endline()
}

///|
fn display_union_members(types : Array[String], formatter : Formatter) -> Unit {
  if !types.is_empty() {
    formatter.write(" = ")
    formatter.write(types[0])
    for typ in types.exact_view(start=1) {
      formatter.write(" | ")
      formatter.write(typ)
    }
  }
}

///|
fn EnumType::display(self : EnumType, formatter : Formatter) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("enum ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_enum_values(self.values, formatter, false)
}

///|
fn EnumTypeExtension::display(
  self : EnumTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend enum ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_enum_values(self.values, formatter, false)
}

///|
fn display_enum_values(
  values : Array[EnumValue],
  formatter : Formatter,
  _input_style : Bool,
) -> Unit {
  if !values.is_empty() {
    formatter.write(" ")
    formatter.start_block()
    for value in values {
      formatter.write_indent()
      match value.description {
        Some(description) => {
          formatter.write_quoted(description)
          formatter.write(" ")
        }
        None => ()
      }
      formatter.write(value.name)
      display_directives(value.directives, formatter)
      formatter.endline()
    }
    formatter.end_block()
  } else {
    formatter.endline()
  }
}

///|
fn display_input_fields(
  fields : Array[InputValue],
  formatter : Formatter,
) -> Unit {
  if !fields.is_empty() {
    formatter.write(" ")
    formatter.start_block()
    for field in fields {
      formatter.write_indent()
      field.display(formatter)
      formatter.endline()
    }
    formatter.end_block()
  } else {
    formatter.endline()
  }
}

///|
fn InputObjectType::display(
  self : InputObjectType,
  formatter : Formatter,
) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("input ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_input_fields(self.fields, formatter)
}

///|
fn InputObjectTypeExtension::display(
  self : InputObjectTypeExtension,
  formatter : Formatter,
) -> Unit {
  formatter.write_indent()
  formatter.write("extend input ")
  formatter.write(self.name)
  display_directives(self.directives, formatter)
  display_input_fields(self.fields, formatter)
}

///|
fn TypeExtension::display(self : TypeExtension, formatter : Formatter) -> Unit {
  match self {
    ScalarTypeExt(scalar) => scalar.display(formatter)
    ObjectTypeExt(object) => object.display(formatter)
    InterfaceTypeExt(interface_) => interface_.display(formatter)
    UnionTypeExt(union_) => union_.display(formatter)
    EnumTypeExt(enum_) => enum_.display(formatter)
    InputObjectTypeExt(input) => input.display(formatter)
  }
}

///|
fn DirectiveDefinition::display(
  self : DirectiveDefinition,
  formatter : Formatter,
) -> Unit {
  display_description(self.description, formatter)
  formatter.write_indent()
  formatter.write("directive @")
  formatter.write(self.name)
  display_input_arguments(self.arguments, formatter)
  if self.repeatable {
    formatter.write(" repeatable")
  }
  if !self.locations.is_empty() {
    formatter.write(" on ")
    formatter.write(self.locations[0].as_string())
    for location in self.locations.exact_view(start=1) {
      formatter.write(" | ")
      formatter.write(location.as_string())
    }
  }
  formatter.endline()
}