///|
/// Parse a complete executable GraphQL document.
///
/// The document may contain operations and fragment definitions. Empty input and
/// trailing tokens raise `ParseError`.
pub fn parse_query(source : String) -> QueryDocument raise ParseError {
  let parser = Parser::new(source)
  let definitions : Array[QueryDefinition] = []
  if parser.at_eof() {
    parse_error("expected query definition", parser.peek().position)
  }
  while true {
    if parser.at_eof() {
      break
    }
    definitions.push(parser.parse_query_definition())
    continue
  }
  parser.expect_eof()
  { definitions, }
}

///|
/// Parse the first executable GraphQL definition and return the unconsumed tail.
///
/// This is useful for stream-like tooling that receives concatenated GraphQL
/// definitions and wants to consume them one at a time.
pub fn consume_definition(
  source : String,
) -> (QueryDefinition, String) raise ParseError {
  let (tokens, rest_offset) = tokenize_first_query_definition(source)
  let parser : Parser = { tokens, index: 0, }
  let definition = parser.parse_query_definition()
  parser.expect_eof()
  let rest = source.unsafe_substring(start=rest_offset, end=source.length())
  (definition, rest)
}

///|
fn Parser::parse_query_definition(
  self : Parser,
) -> QueryDefinition raise ParseError {
  if self.check_punct("{") ||
    self.check_ident("query") ||
    self.check_ident("mutation") ||
    self.check_ident("subscription") {
    OperationDefinition(self.parse_operation_definition())
  } else if self.check_ident("fragment") {
    FragmentDefinition(self.parse_fragment_definition())
  } else {
    let token = self.peek()
    parse_error(
      "expected {, query, mutation, subscription or fragment",
      token.position,
    )
  }
}

///|
fn Parser::parse_operation_definition(
  self : Parser,
) -> OperationDefinition raise ParseError {
  if self.check_punct("{") {
    SelectionSetOperation(self.parse_selection_set())
  } else if self.check_ident("query") {
    QueryOperation(self.parse_query_operation())
  } else if self.check_ident("mutation") {
    MutationOperation(self.parse_mutation_operation())
  } else if self.check_ident("subscription") {
    SubscriptionOperation(self.parse_subscription_operation())
  } else {
    let token = self.peek()
    parse_error("expected operation definition", token.position)
  }
}

///|
fn Parser::parse_query_operation(self : Parser) -> Query raise ParseError {
  let position = self.expect_ident("query").position
  let (name, variable_definitions, directives, selection_set) = self.parse_operation_common()
  { position, name, variable_definitions, directives, selection_set, }
}

///|
fn Parser::parse_mutation_operation(self : Parser) -> Mutation raise ParseError {
  let position = self.expect_ident("mutation").position
  let (name, variable_definitions, directives, selection_set) = self.parse_operation_common()
  { position, name, variable_definitions, directives, selection_set, }
}

///|
fn Parser::parse_subscription_operation(
  self : Parser,
) -> Subscription raise ParseError {
  let position = self.expect_ident("subscription").position
  let (name, variable_definitions, directives, selection_set) = self.parse_operation_common()
  { position, name, variable_definitions, directives, selection_set, }
}

///|
fn Parser::parse_operation_common(
  self : Parser,
) -> (String?, Array[VariableDefinition], Array[Directive], SelectionSet) raise ParseError {
  let name = if self.check_name() {
    let (name, _) = self.expect_name()
    Some(name)
  } else {
    None
  }
  let variable_definitions = self.parse_variable_definitions()
  let directives = self.parse_directives()
  let selection_set = self.parse_selection_set()
  (name, variable_definitions, directives, selection_set)
}

///|
fn Parser::parse_variable_definitions(
  self : Parser,
) -> Array[VariableDefinition] raise ParseError {
  if !self.eat_punct("(") {
    return []
  }
  let variables : Array[VariableDefinition] = []
  if self.check_punct(")") {
    let token = self.peek()
    parse_error("expected variable definition", token.position)
  }
  while true {
    if self.check_punct(")") {
      ignore(self.take())
      break
    }
    let position = self.expect_punct("$").position
    let (name, _) = self.expect_name()
    ignore(self.expect_punct(":"))
    let var_type = self.parse_type()
    let default_value = if self.eat_punct("=") {
      Some(self.parse_default_value())
    } else {
      None
    }
    variables.push({ position, name, var_type, default_value, })
    continue
  }
  variables
}

///|
fn Parser::parse_fragment_definition(
  self : Parser,
) -> FragmentDefinition raise ParseError {
  let position = self.expect_ident("fragment").position
  let (name, _) = self.expect_name()
  ignore(self.expect_ident("on"))
  let (type_name, _) = self.expect_name()
  let type_condition = On(type_name)
  let directives = self.parse_directives()
  let selection_set = self.parse_selection_set()
  { position, name, type_condition, directives, selection_set, }
}

///|
fn Parser::parse_selection_set(self : Parser) -> SelectionSet raise ParseError {
  let start = self.expect_punct("{").position
  let items : Array[Selection] = []
  if self.check_punct("}") {
    let token = self.peek()
    parse_error("expected selection", token.position)
  }
  while true {
    if self.check_punct("}") {
      let end = self.take().position
      return { span: (start, end), items, }
    }
    items.push(self.parse_selection())
    continue
  }
  parse_error("unterminated selection set", start)
}

///|
fn Parser::parse_selection(self : Parser) -> Selection raise ParseError {
  if self.eat_punct("...") {
    self.parse_spread()
  } else {
    FieldSelection(self.parse_field())
  }
}

///|
fn Parser::parse_spread(self : Parser) -> Selection raise ParseError {
  let position = self.peek().position
  if self.check_ident("on") || self.check_punct("@") || self.check_punct("{") {
    let type_condition = if self.eat_ident("on") {
      let (name, _) = self.expect_name()
      Some(On(name))
    } else {
      None
    }
    let directives = self.parse_directives()
    let selection_set = self.parse_selection_set()
    InlineFragmentSelection({
      position,
      type_condition,
      directives,
      selection_set,
    })
  } else {
    let (fragment_name, fragment_pos) = self.expect_name()
    let directives = self.parse_directives()
    FragmentSpreadSelection({
      position: fragment_pos,
      fragment_name,
      directives,
    })
  }
}

///|
fn Parser::parse_field(self : Parser) -> Field raise ParseError {
  let (name_or_alias, position) = self.expect_name()
  let (name, field_alias) = if self.eat_punct(":") {
    let (name, _) = self.expect_name()
    (name, Some(name_or_alias))
  } else {
    (name_or_alias, None)
  }
  let arguments = self.parse_arguments()
  let directives = self.parse_directives()
  let selection_set = if self.check_punct("{") {
    self.parse_selection_set()
  } else {
    empty_selection_set(position)
  }
  { position, field_alias, name, arguments, directives, selection_set, }
}

///|
/// Minify an executable GraphQL document while preserving token boundaries.
///
/// The result removes ignored whitespace and comments, inserting a single space
/// only when two adjacent non-punctuator tokens would otherwise merge.
pub fn minify_query(source : String) -> String raise ParseError {
  let tokens = tokenize(source)
  let pieces : Array[String] = []
  let mut prev_was_non_punctuator = false
  for token in tokens {
    if token.kind == TkEof {
      break
    }
    let is_non_punctuator = token.kind != TkPunctuator
    if prev_was_non_punctuator && is_non_punctuator {
      pieces.push(" ")
    }
    pieces.push(token.value)
    prev_was_non_punctuator = is_non_punctuator
  }
  pieces.join("")
}