/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt;

use common::Location;
use common::Span;
use intern::string_key::StringKey;

use super::constant_value::*;
use super::directive::Directive;
use super::primitive::*;
use super::type_annotation::*;
use super::value::*;

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ExecutableDefinition {
    Operation(OperationDefinition),
    Fragment(FragmentDefinition),
}

impl ExecutableDefinition {
    pub fn location(&self) -> Location {
        match self {
            ExecutableDefinition::Operation(node) => node.location,
            ExecutableDefinition::Fragment(node) => node.location,
        }
    }

    pub fn name_identifier(&self) -> Option<&Identifier> {
        match self {
            ExecutableDefinition::Operation(node) => node.name.as_ref(),
            ExecutableDefinition::Fragment(node) => Some(&node.name),
        }
    }

    pub fn name(&self) -> Option<StringKey> {
        self.name_identifier().map(|identifier| identifier.value)
    }

    pub fn name_location(&self) -> Option<Location> {
        self.name_identifier()
            .map(|identifier| self.location().with_span(identifier.span))
    }

    pub fn has_directive(&self, directive_name: StringKey) -> bool {
        match self {
            ExecutableDefinition::Operation(node) => node
                .directives
                .iter()
                .any(|d| d.name.value == directive_name),
            ExecutableDefinition::Fragment(node) => node
                .directives
                .iter()
                .any(|d| d.name.value == directive_name),
        }
    }

    pub fn selections(&self) -> &[Selection] {
        match self {
            ExecutableDefinition::Operation(node) => &node.selections.items,
            ExecutableDefinition::Fragment(node) => &node.selections.items,
        }
    }
}

impl fmt::Debug for ExecutableDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ExecutableDefinition::Operation(node) => f.write_fmt(format_args!("{:#?}", node)),
            ExecutableDefinition::Fragment(node) => f.write_fmt(format_args!("{:#?}", node)),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct OperationDefinition {
    pub location: Location,
    pub operation: Option<(Token, OperationKind)>,
    pub name: Option<Identifier>,
    pub variable_definitions: Option<List<VariableDefinition>>,
    pub directives: Vec<Directive>,
    pub selections: List<Selection>,
}

impl OperationDefinition {
    pub fn operation_kind(&self) -> OperationKind {
        // The GraphQL spec defines anonymous operations as queries.
        // https://spec.graphql.org/June2018/#sec-Anonymous-Operation-Definitions
        self.operation
            .as_ref()
            .map(|(_, operation_kind)| *operation_kind)
            .unwrap_or(OperationKind::Query)
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum OperationKind {
    Query,
    Mutation,
    Subscription,
}

impl fmt::Display for OperationKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OperationKind::Query => f.write_str("query"),
            OperationKind::Mutation => f.write_str("mutation"),
            OperationKind::Subscription => f.write_str("subscription"),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FragmentDefinition {
    pub location: Location,
    pub fragment: Token,
    pub name: Identifier,
    pub variable_definitions: Option<List<VariableDefinition>>,
    pub type_condition: TypeCondition,
    pub directives: Vec<Directive>,
    pub selections: List<Selection>,
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct VariableDefinition {
    pub span: Span,
    pub name: VariableIdentifier,
    pub colon: Token,
    pub type_: TypeAnnotation,
    pub default_value: Option<DefaultValue>,
    pub directives: Vec<Directive>,
}

// Primitive Types

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Alias {
    pub span: Span,
    pub alias: Identifier,
    pub colon: Token,
}

impl fmt::Display for Alias {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{}", self.alias))
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct TypeCondition {
    pub span: Span,
    pub on: Token,
    pub type_: Identifier,
}

impl fmt::Display for TypeCondition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("on {}", self.type_))
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DefaultValue {
    pub span: Span,
    pub equals: Token,
    pub value: ConstantValue,
}

impl fmt::Display for DefaultValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{}", self.value))
    }
}

// Selections

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Selection {
    FragmentSpread(FragmentSpread),
    InlineFragment(InlineFragment),
    LinkedField(LinkedField),
    ScalarField(ScalarField),
}

impl Selection {
    pub fn span(&self) -> Span {
        match self {
            Selection::FragmentSpread(node) => node.span,
            Selection::InlineFragment(node) => node.span,
            Selection::LinkedField(node) => node.span,
            Selection::ScalarField(node) => node.span,
        }
    }

    pub fn directives(&self) -> &[Directive] {
        match self {
            Selection::FragmentSpread(node) => &node.directives,
            Selection::InlineFragment(node) => &node.directives,
            Selection::LinkedField(node) => &node.directives,
            Selection::ScalarField(node) => &node.directives,
        }
    }
}

impl fmt::Debug for Selection {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Selection::FragmentSpread(node) => f.write_fmt(format_args!("{:#?}", node)),
            Selection::InlineFragment(node) => f.write_fmt(format_args!("{:#?}", node)),
            Selection::LinkedField(node) => f.write_fmt(format_args!("{:#?}", node)),
            Selection::ScalarField(node) => f.write_fmt(format_args!("{:#?}", node)),
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct FragmentSpread {
    pub span: Span,
    pub spread: Token,
    pub name: Identifier,
    pub arguments: Option<List<Argument>>,
    pub directives: Vec<Directive>,
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct InlineFragment {
    pub span: Span,
    pub spread: Token,
    pub type_condition: Option<TypeCondition>,
    pub directives: Vec<Directive>,
    pub selections: List<Selection>,
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct LinkedField {
    pub span: Span,
    pub alias: Option<Alias>,
    pub name: Identifier,
    pub arguments: Option<List<Argument>>,
    pub directives: Vec<Directive>,
    pub selections: List<Selection>,
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ScalarField {
    pub span: Span,
    pub alias: Option<Alias>,
    pub name: Identifier,
    pub arguments: Option<List<Argument>>,
    pub directives: Vec<Directive>,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

mod constant_directive;
mod constant_value;
mod directive;
mod document;
mod executable;
mod primitive;
mod type_annotation;
mod type_system;
mod value;

pub use constant_directive::*;
pub use constant_value::*;
pub use directive::*;
pub use document::*;
pub use executable::*;
pub use primitive::*;
pub use type_annotation::*;
pub use type_system::*;
pub use value::*;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::cmp::Ordering;
use std::fmt;

use common::SourceLocationKey;
use common::Span;
use common::WithLocation;
use intern::string_key::StringKey;

use crate::lexer::TokenKind;

#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Token {
    pub span: Span,
    pub kind: TokenKind,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Identifier {
    pub span: Span,
    pub token: Token,
    pub value: StringKey,
}

impl fmt::Display for Identifier {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{}", self.value))
    }
}

impl Ord for Identifier {
    fn cmp(&self, other: &Self) -> Ordering {
        self.value.cmp(&other.value)
    }
}

impl PartialOrd for Identifier {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Identifier {
    pub fn name_with_location(&self, file: SourceLocationKey) -> WithLocation<StringKey> {
        WithLocation::from_span(file, self.span, self.value)
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct List<T> {
    pub span: Span,
    pub start: Token,
    pub items: Vec<T>,
    pub end: Token,
}

impl<T> List<T> {
    pub fn generated(items: Vec<T>) -> Self {
        Self {
            span: Span::empty(),
            start: Token {
                span: Span::empty(),
                kind: TokenKind::OpenBrace,
            },
            items,
            end: Token {
                span: Span::empty(),
                kind: TokenKind::CloseBrace,
            },
        }
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt;

use common::Span;

use super::primitive::*;

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum TypeAnnotation {
    Named(NamedTypeAnnotation),
    List(Box<ListTypeAnnotation>),
    NonNull(Box<NonNullTypeAnnotation>),
}

impl fmt::Display for TypeAnnotation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TypeAnnotation::Named(named) => named.fmt(f),
            TypeAnnotation::List(list) => f.write_fmt(format_args!("[{}]", list.type_)),
            TypeAnnotation::NonNull(non_null) => f.write_fmt(format_args!("{}!", non_null.type_)),
        }
    }
}

impl TypeAnnotation {
    pub fn inner(&self) -> &NamedTypeAnnotation {
        match self {
            TypeAnnotation::Named(named) => named,
            TypeAnnotation::List(of) => (*of).type_.inner(),
            TypeAnnotation::NonNull(of) => (*of).type_.inner(),
        }
    }

    pub fn span(&self) -> Span {
        match self {
            TypeAnnotation::Named(named) => named.name.span,
            TypeAnnotation::List(of) => (*of).span,
            TypeAnnotation::NonNull(of) => (*of).span,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NamedTypeAnnotation {
    pub name: Identifier,
}

impl fmt::Display for NamedTypeAnnotation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.name.fmt(f)
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ListTypeAnnotation {
    pub span: Span,
    pub open: Token,
    pub type_: TypeAnnotation,
    pub close: Token,
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NonNullTypeAnnotation {
    pub span: Span,
    pub type_: TypeAnnotation,
    pub exclamation: Token,
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt;

use common::Span;
use intern::string_key::StringKey;

use super::constant_directive::ConstantDirective;
use super::constant_value::ConstantValue;
use super::constant_value::StringNode;
use super::executable::OperationKind;
use super::primitive::*;
use super::type_annotation::TypeAnnotation;

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub enum TypeSystemDefinition {
    SchemaDefinition(SchemaDefinition),
    SchemaExtension(SchemaExtension),
    EnumTypeDefinition(EnumTypeDefinition),
    EnumTypeExtension(EnumTypeExtension),
    InterfaceTypeDefinition(InterfaceTypeDefinition),
    InterfaceTypeExtension(InterfaceTypeExtension),
    ObjectTypeDefinition(ObjectTypeDefinition),
    ObjectTypeExtension(ObjectTypeExtension),
    UnionTypeDefinition(UnionTypeDefinition),
    UnionTypeExtension(UnionTypeExtension),
    InputObjectTypeDefinition(InputObjectTypeDefinition),
    InputObjectTypeExtension(InputObjectTypeExtension),
    ScalarTypeDefinition(ScalarTypeDefinition),
    ScalarTypeExtension(ScalarTypeExtension),
    DirectiveDefinition(DirectiveDefinition),
}

impl TypeSystemDefinition {
    pub fn location(&self) -> Span {
        match self {
            TypeSystemDefinition::SchemaDefinition(_extension) => Span::empty(), // Not implemented
            TypeSystemDefinition::SchemaExtension(_extension) => Span::empty(),  // Not implemented
            TypeSystemDefinition::ObjectTypeExtension(extension) => extension.name.span,
            TypeSystemDefinition::ObjectTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::InterfaceTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::InterfaceTypeExtension(extension) => extension.name.span,
            TypeSystemDefinition::UnionTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::UnionTypeExtension(extension) => extension.name.span,
            TypeSystemDefinition::DirectiveDefinition(extension) => extension.name.span,
            TypeSystemDefinition::InputObjectTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::InputObjectTypeExtension(extension) => extension.name.span,
            TypeSystemDefinition::EnumTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::EnumTypeExtension(extension) => extension.name.span,
            TypeSystemDefinition::ScalarTypeDefinition(extension) => extension.name.span,
            TypeSystemDefinition::ScalarTypeExtension(extension) => extension.name.span,
        }
    }
}

impl fmt::Display for TypeSystemDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            TypeSystemDefinition::SchemaDefinition(SchemaDefinition {
                directives,
                operation_types,
            }) => write_schema_definition_helper(f, directives, &operation_types.items),
            TypeSystemDefinition::SchemaExtension(SchemaExtension {
                directives,
                operation_types,
            }) => write_schema_extension_helper(f, directives, operation_types),
            TypeSystemDefinition::ObjectTypeDefinition(ObjectTypeDefinition {
                name,
                interfaces,
                fields,
                directives,
            }) => write_object_helper(f, &name.value, interfaces, fields, directives, false),
            TypeSystemDefinition::ObjectTypeExtension(ObjectTypeExtension {
                name,
                interfaces,
                fields,
                directives,
            }) => write_object_helper(f, &name.value, interfaces, fields, directives, true),
            TypeSystemDefinition::InterfaceTypeDefinition(InterfaceTypeDefinition {
                name,
                fields,
                directives,
                ..
            }) => write_interface_helper(f, &name.value, fields, directives, false),
            TypeSystemDefinition::InterfaceTypeExtension(InterfaceTypeExtension {
                name,
                interfaces: _,
                fields,
                directives,
            }) => write_interface_helper(f, &name.value, fields, directives, true),
            TypeSystemDefinition::UnionTypeDefinition(UnionTypeDefinition {
                name,
                directives,
                members,
            }) => write_union_type_definition_helper(f, &name.value, directives, members, false),
            TypeSystemDefinition::UnionTypeExtension(UnionTypeExtension {
                name,
                directives,
                members,
            }) => write_union_type_definition_helper(f, &name.value, directives, members, true),
            TypeSystemDefinition::DirectiveDefinition(DirectiveDefinition {
                name,
                arguments,
                repeatable,
                locations,
                description,
                hack_source,
            }) => write_directive_definition_helper(
                f,
                &name.value,
                arguments,
                repeatable,
                locations,
                description,
                hack_source,
            ),
            TypeSystemDefinition::InputObjectTypeDefinition(InputObjectTypeDefinition {
                name,
                directives,
                fields,
            }) => {
                write_input_object_type_definition_helper(f, &name.value, directives, fields, false)
            }
            TypeSystemDefinition::InputObjectTypeExtension(InputObjectTypeExtension {
                name,
                directives,
                fields,
            }) => {
                write_input_object_type_definition_helper(f, &name.value, directives, fields, true)
            }
            TypeSystemDefinition::EnumTypeDefinition(EnumTypeDefinition {
                name,
                directives,
                values,
            }) => write_enum_type_definition_helper(f, &name.value, directives, values, false),
            TypeSystemDefinition::EnumTypeExtension(EnumTypeExtension {
                name,
                directives,
                values,
            }) => write_enum_type_definition_helper(f, &name.value, directives, values, true),
            TypeSystemDefinition::ScalarTypeDefinition(ScalarTypeDefinition {
                name,
                directives,
            }) => write_scalar_type_definition_helper(f, &name.value, directives, false),
            TypeSystemDefinition::ScalarTypeExtension(ScalarTypeExtension { name, directives }) => {
                write_scalar_type_definition_helper(f, &name.value, directives, true)
            }
        }
    }
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct SchemaDefinition {
    pub directives: Vec<ConstantDirective>,
    pub operation_types: List<OperationTypeDefinition>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct SchemaExtension {
    pub directives: Vec<ConstantDirective>,
    pub operation_types: Option<List<OperationTypeDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct OperationTypeDefinition {
    pub operation: OperationType,
    pub type_: Identifier,
}

impl fmt::Display for OperationTypeDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.operation, self.type_)
    }
}

#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Copy, Clone)]
pub enum OperationType {
    Query,
    Mutation,
    Subscription,
}

impl fmt::Display for OperationType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OperationType::Query => write!(f, "query"),
            OperationType::Mutation => write!(f, "mutation"),
            OperationType::Subscription => write!(f, "subscription"),
        }
    }
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ObjectTypeDefinition {
    pub name: Identifier,
    pub interfaces: Vec<Identifier>,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<FieldDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ObjectTypeExtension {
    pub name: Identifier,
    pub interfaces: Vec<Identifier>,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<FieldDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct InterfaceTypeDefinition {
    pub name: Identifier,
    pub interfaces: Vec<Identifier>,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<FieldDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct InterfaceTypeExtension {
    pub name: Identifier,
    pub interfaces: Vec<Identifier>,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<FieldDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct UnionTypeDefinition {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub members: Vec<Identifier>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct UnionTypeExtension {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub members: Vec<Identifier>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ScalarTypeDefinition {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct ScalarTypeExtension {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct EnumTypeDefinition {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub values: Option<List<EnumValueDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct EnumTypeExtension {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub values: Option<List<EnumValueDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct InputObjectTypeDefinition {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<InputValueDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct InputObjectTypeExtension {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
    pub fields: Option<List<InputValueDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct EnumValueDefinition {
    pub name: Identifier,
    pub directives: Vec<ConstantDirective>,
}

impl fmt::Display for EnumValueDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.name)?;
        write_directives(f, &self.directives)
    }
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct DirectiveDefinition {
    pub name: Identifier,
    pub arguments: Option<List<InputValueDefinition>>,
    pub repeatable: bool,
    pub locations: Vec<DirectiveLocation>,
    pub description: Option<StringNode>,
    pub hack_source: Option<StringNode>,
}

#[derive(PartialEq, Eq, Ord, PartialOrd, Hash, Debug, Clone, Copy)]
pub enum DirectiveLocation {
    Query,
    Mutation,
    Subscription,
    Field,
    FragmentDefinition,
    FragmentSpread,
    InlineFragment,
    Schema,
    Scalar,
    Object,
    FieldDefinition,
    ArgumentDefinition,
    Interface,
    Union,
    Enum,
    EnumValue,
    InputObject,
    InputFieldDefinition,
    VariableDefinition,
}

impl From<OperationKind> for DirectiveLocation {
    fn from(operation: OperationKind) -> Self {
        match operation {
            OperationKind::Query => Self::Query,
            OperationKind::Mutation => Self::Mutation,
            OperationKind::Subscription => Self::Subscription,
        }
    }
}

impl fmt::Display for DirectiveLocation {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            DirectiveLocation::Query => write!(f, "QUERY"),
            DirectiveLocation::Mutation => write!(f, "MUTATION"),
            DirectiveLocation::Subscription => write!(f, "SUBSCRIPTION"),
            DirectiveLocation::Field => write!(f, "FIELD"),
            DirectiveLocation::FragmentDefinition => write!(f, "FRAGMENT_DEFINITION"),
            DirectiveLocation::FragmentSpread => write!(f, "FRAGMENT_SPREAD"),
            DirectiveLocation::InlineFragment => write!(f, "INLINE_FRAGMENT"),
            DirectiveLocation::Schema => write!(f, "SCHEMA"),
            DirectiveLocation::Scalar => write!(f, "SCALAR"),
            DirectiveLocation::Object => write!(f, "OBJECT"),
            DirectiveLocation::FieldDefinition => write!(f, "FIELD_DEFINITION"),
            DirectiveLocation::ArgumentDefinition => write!(f, "ARGUMENT_DEFINITION"),
            DirectiveLocation::Interface => write!(f, "INTERFACE"),
            DirectiveLocation::Union => write!(f, "UNION"),
            DirectiveLocation::Enum => write!(f, "ENUM"),
            DirectiveLocation::EnumValue => write!(f, "ENUM_VALUE"),
            DirectiveLocation::InputObject => write!(f, "INPUT_OBJECT"),
            DirectiveLocation::InputFieldDefinition => write!(f, "INPUT_FIELD_DEFINITION"),
            DirectiveLocation::VariableDefinition => write!(f, "VARIABLE_DEFINITION"),
        }
    }
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct InputValueDefinition {
    pub name: Identifier,
    pub type_: TypeAnnotation,
    pub default_value: Option<ConstantValue>,
    pub directives: Vec<ConstantDirective>,
}

impl fmt::Display for InputValueDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}: {}", self.name, self.type_)?;
        if let Some(v) = &self.default_value {
            write!(f, " = {}", v)?;
        }

        if !self.directives.is_empty() {
            write!(f, " ")?;
            write_list(f, &self.directives, " ")?;
        }

        Ok(())
    }
}

/// A field definition which includes just the field name and arguments.
/// Used by Relay Resolvers.
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct FieldDefinitionStub {
    pub name: Identifier,
    pub arguments: Option<List<InputValueDefinition>>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug)]
pub struct FieldDefinition {
    pub name: Identifier,
    pub type_: TypeAnnotation,
    pub arguments: Option<List<InputValueDefinition>>,
    pub directives: Vec<ConstantDirective>,
    pub description: Option<StringNode>,
    pub hack_source: Option<StringNode>,
}

impl fmt::Display for FieldDefinition {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.name)?;
        if let Some(arguments) = self.arguments.as_ref() {
            write_arguments(f, &arguments.items)?;
        }
        write!(f, ": {}", self.type_)?;
        write_directives(f, &self.directives)
    }
}

fn write_list(
    f: &mut fmt::Formatter<'_>,
    list: &[impl fmt::Display],
    separator: &str,
) -> fmt::Result {
    let v = list
        .iter()
        .map(|elem| elem.to_string())
        .collect::<Vec<String>>()
        .join(separator);
    write!(f, "{}", v)
}

fn write_arguments(f: &mut fmt::Formatter<'_>, arguments: &[impl fmt::Display]) -> fmt::Result {
    if arguments.is_empty() {
        return Ok(());
    }

    write!(f, "(")?;
    write_list(f, arguments, ", ")?;
    write!(f, ")")
}

fn write_directives(f: &mut fmt::Formatter<'_>, directives: &[ConstantDirective]) -> fmt::Result {
    if directives.is_empty() {
        return Ok(());
    }

    write!(f, " ")?;
    write_list(f, directives, " ")
}

fn write_fields(f: &mut fmt::Formatter<'_>, fields: &[impl fmt::Display]) -> fmt::Result {
    if fields.is_empty() {
        return Ok(());
    }

    write!(f, " {{\n  ")?;
    write_list(f, fields, "\n  ")?;
    write!(f, "\n}}")
}

fn write_schema_definition_helper(
    f: &mut fmt::Formatter<'_>,
    directives: &[ConstantDirective],
    operation_types: &[OperationTypeDefinition],
) -> fmt::Result {
    write!(f, "schema")?;
    write_directives(f, directives)?;
    write_fields(f, operation_types)?;
    writeln!(f)
}

fn write_schema_extension_helper(
    f: &mut fmt::Formatter<'_>,
    directives: &[ConstantDirective],
    operation_types: &Option<List<OperationTypeDefinition>>,
) -> fmt::Result {
    write!(f, "extend schema")?;
    write_directives(f, directives)?;
    if let Some(operation_types) = operation_types.as_ref() {
        write_fields(f, &operation_types.items)?;
    }
    writeln!(f)
}

fn write_object_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    interfaces: &[Identifier],
    fields: &Option<List<FieldDefinition>>,
    directives: &[ConstantDirective],
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "type {}", name)?;
    if !interfaces.is_empty() {
        write!(f, " implements ")?;
        write_list(f, interfaces, " & ")?;
    }
    write_directives(f, directives)?;
    if let Some(fields) = fields.as_ref() {
        write_fields(f, &fields.items)?;
    }
    writeln!(f)
}

fn write_interface_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    fields: &Option<List<FieldDefinition>>,
    directives: &[ConstantDirective],
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "interface {}", name)?;
    write_directives(f, directives)?;
    if let Some(fields) = fields.as_ref() {
        write_fields(f, &fields.items)?;
    }
    writeln!(f)
}

fn write_union_type_definition_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    directives: &[ConstantDirective],
    members: &[Identifier],
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "union {}", name)?;
    write_directives(f, directives)?;
    if !members.is_empty() {
        write!(f, " = ")?;
        write_list(f, members, " | ")?;
    }
    writeln!(f)
}

fn write_directive_definition_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    arguments: &Option<List<InputValueDefinition>>,
    _repeatable: &bool,
    locations: &[DirectiveLocation],
    _description: &Option<StringNode>,
    _hack_source: &Option<StringNode>,
) -> fmt::Result {
    write!(f, "directive @{}", name)?;
    if let Some(arguments) = arguments.as_ref() {
        write_arguments(f, &arguments.items)?;
    }
    write!(f, " on ")?;
    write_list(f, locations, " | ")?;
    writeln!(f)
}

fn write_input_object_type_definition_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    directives: &[ConstantDirective],
    fields: &Option<List<InputValueDefinition>>,
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "input {}", name)?;
    write_directives(f, directives)?;
    if let Some(fields) = fields.as_ref() {
        write_fields(f, &fields.items)?;
    }
    writeln!(f)
}

fn write_enum_type_definition_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    directives: &[ConstantDirective],
    values: &Option<List<EnumValueDefinition>>,
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "enum {}", name)?;
    write_directives(f, directives)?;
    if let Some(values) = values.as_ref() {
        write_fields(f, &values.items)?;
    }

    writeln!(f)
}

fn write_scalar_type_definition_helper(
    f: &mut fmt::Formatter<'_>,
    name: &StringKey,
    directives: &[ConstantDirective],
    is_extension: bool,
) -> fmt::Result {
    if is_extension {
        write!(f, "extend ")?;
    }

    write!(f, "scalar {}", name)?;
    write_directives(f, directives)?;
    writeln!(f)
}

impl fmt::Display for ConstantDirective {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "@{}", self.name)?;
        if let Some(arguments) = self.arguments.as_ref() {
            write_arguments(f, &arguments.items)?;
        }
        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use core::fmt;

use common::Named;
use common::SourceLocationKey;
use common::Span;
use common::WithLocation;
use intern::string_key::StringKey;

use super::constant_value::*;
use super::primitive::*;

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct VariableIdentifier {
    pub span: Span,
    pub token: Token,
    pub name: StringKey,
}

impl fmt::Display for VariableIdentifier {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("${}", self.name))
    }
}

impl VariableIdentifier {
    pub fn name_with_location(&self, file: SourceLocationKey) -> WithLocation<StringKey> {
        WithLocation::from_span(file, self.span, self.name)
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Value {
    Constant(ConstantValue),
    Variable(VariableIdentifier),
    List(List<Value>),
    Object(List<Argument>),
}

impl fmt::Display for Value {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Value::Constant(value) => f.write_fmt(format_args!("{}", value)),
            Value::Variable(value) => f.write_fmt(format_args!("{}", value)),
            Value::List(value) => f.write_fmt(format_args!(
                "[{}]",
                value
                    .items
                    .iter()
                    .map(|item| item.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            )),
            Value::Object(value) => f.write_fmt(format_args!(
                "{{{}}}",
                value
                    .items
                    .iter()
                    .map(|item| item.to_string())
                    .collect::<Vec<String>>()
                    .join(", ")
            )),
        }
    }
}

impl Value {
    pub fn is_constant(&self) -> bool {
        matches!(self, Value::Constant(..))
    }

    pub fn span(&self) -> Span {
        match self {
            Value::Constant(value) => value.span(),
            Value::Variable(value) => value.span,
            Value::List(value) => value.span,
            Value::Object(value) => value.span,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Argument {
    pub span: Span,
    pub name: Identifier,
    pub colon: Token,
    pub value: Value,
}

impl fmt::Display for Argument {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{}: {}", self.name, self.value))
    }
}

impl Named for Argument {
    type Name = StringKey;
    fn name(&self) -> StringKey {
        self.name.value
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<abc8378151c6180b46a82e4d45bd0129>>
 */

mod parse_document;

use parse_document::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn invalid_definition_invalid() {
    let input = include_str!("parse_document/fixtures/invalid_definition.invalid.graphql");
    let expected = include_str!("parse_document/fixtures/invalid_definition.invalid.expected");
    test_fixture(transform_fixture, "invalid_definition.invalid.graphql", "parse_document/fixtures/invalid_definition.invalid.expected", input, expected);
}

#[test]
fn mixed() {
    let input = include_str!("parse_document/fixtures/mixed.graphql");
    let expected = include_str!("parse_document/fixtures/mixed.expected");
    test_fixture(transform_fixture, "mixed.graphql", "parse_document/fixtures/mixed.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<467adf22a47715eb35bf37efa6f69f7f>>
 */

mod parse_document_with_features;

use parse_document_with_features::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn fragment_with_empty_vardefs_invalid() {
    let input = include_str!("parse_document_with_features/fixtures/fragment_with_empty_vardefs.invalid.graphql");
    let expected = include_str!("parse_document_with_features/fixtures/fragment_with_empty_vardefs.invalid.expected");
    test_fixture(transform_fixture, "fragment_with_empty_vardefs.invalid.graphql", "parse_document_with_features/fixtures/fragment_with_empty_vardefs.invalid.expected", input, expected);
}

#[test]
fn fragment_with_variable_defs() {
    let input = include_str!("parse_document_with_features/fixtures/fragment_with_variable_defs.graphql");
    let expected = include_str!("parse_document_with_features/fixtures/fragment_with_variable_defs.expected");
    test_fixture(transform_fixture, "fragment_with_variable_defs.graphql", "parse_document_with_features/fixtures/fragment_with_variable_defs.expected", input, expected);
}

#[test]
fn spread_with_arguments() {
    let input = include_str!("parse_document_with_features/fixtures/spread_with_arguments.graphql");
    let expected = include_str!("parse_document_with_features/fixtures/spread_with_arguments.expected");
    test_fixture(transform_fixture, "spread_with_arguments.graphql", "parse_document_with_features/fixtures/spread_with_arguments.expected", input, expected);
}

#[test]
fn spread_with_empty_arguments_invalid() {
    let input = include_str!("parse_document_with_features/fixtures/spread_with_empty_arguments.invalid.graphql");
    let expected = include_str!("parse_document_with_features/fixtures/spread_with_empty_arguments.invalid.expected");
    test_fixture(transform_fixture, "spread_with_empty_arguments.invalid.graphql", "parse_document_with_features/fixtures/spread_with_empty_arguments.invalid.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<dd4791b79658f50a23574eb6f4548bf9>>
 */

mod parse_executable_document;

use parse_executable_document::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn block_string() {
    let input = include_str!("parse_executable_document/fixtures/block_string.graphql");
    let expected = include_str!("parse_executable_document/fixtures/block_string.expected");
    test_fixture(transform_fixture, "block_string.graphql", "parse_executable_document/fixtures/block_string.expected", input, expected);
}

#[test]
fn fragment_with_variable_defs_invalid() {
    let input = include_str!("parse_executable_document/fixtures/fragment_with_variable_defs.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/fragment_with_variable_defs.invalid.expected");
    test_fixture(transform_fixture, "fragment_with_variable_defs.invalid.graphql", "parse_executable_document/fixtures/fragment_with_variable_defs.invalid.expected", input, expected);
}

#[test]
fn incomplete_field_alias() {
    let input = include_str!("parse_executable_document/fixtures/incomplete_field_alias.graphql");
    let expected = include_str!("parse_executable_document/fixtures/incomplete_field_alias.expected");
    test_fixture(transform_fixture, "incomplete_field_alias.graphql", "parse_executable_document/fixtures/incomplete_field_alias.expected", input, expected);
}

#[test]
fn incorrect_variable_name_invalid() {
    let input = include_str!("parse_executable_document/fixtures/incorrect_variable_name.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/incorrect_variable_name.invalid.expected");
    test_fixture(transform_fixture, "incorrect_variable_name.invalid.graphql", "parse_executable_document/fixtures/incorrect_variable_name.invalid.expected", input, expected);
}

#[test]
fn invalid_number() {
    let input = include_str!("parse_executable_document/fixtures/invalid_number.graphql");
    let expected = include_str!("parse_executable_document/fixtures/invalid_number.expected");
    test_fixture(transform_fixture, "invalid_number.graphql", "parse_executable_document/fixtures/invalid_number.expected", input, expected);
}

#[test]
fn keyword_as_name() {
    let input = include_str!("parse_executable_document/fixtures/keyword_as_name.graphql");
    let expected = include_str!("parse_executable_document/fixtures/keyword_as_name.expected");
    test_fixture(transform_fixture, "keyword_as_name.graphql", "parse_executable_document/fixtures/keyword_as_name.expected", input, expected);
}

#[test]
fn kitchen_sink() {
    let input = include_str!("parse_executable_document/fixtures/kitchen-sink.graphql");
    let expected = include_str!("parse_executable_document/fixtures/kitchen-sink.expected");
    test_fixture(transform_fixture, "kitchen-sink.graphql", "parse_executable_document/fixtures/kitchen-sink.expected", input, expected);
}

#[test]
fn list_of_enum() {
    let input = include_str!("parse_executable_document/fixtures/list_of_enum.graphql");
    let expected = include_str!("parse_executable_document/fixtures/list_of_enum.expected");
    test_fixture(transform_fixture, "list_of_enum.graphql", "parse_executable_document/fixtures/list_of_enum.expected", input, expected);
}

#[test]
fn missing_zero_on_float_invalid() {
    let input = include_str!("parse_executable_document/fixtures/missing_zero_on_float.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/missing_zero_on_float.invalid.expected");
    test_fixture(transform_fixture, "missing_zero_on_float.invalid.graphql", "parse_executable_document/fixtures/missing_zero_on_float.invalid.expected", input, expected);
}

#[test]
fn multiple_parse_errors_invalid() {
    let input = include_str!("parse_executable_document/fixtures/multiple_parse_errors.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/multiple_parse_errors.invalid.expected");
    test_fixture(transform_fixture, "multiple_parse_errors.invalid.graphql", "parse_executable_document/fixtures/multiple_parse_errors.invalid.expected", input, expected);
}

#[test]
fn space_in_variable() {
    let input = include_str!("parse_executable_document/fixtures/space_in_variable.graphql");
    let expected = include_str!("parse_executable_document/fixtures/space_in_variable.expected");
    test_fixture(transform_fixture, "space_in_variable.graphql", "parse_executable_document/fixtures/space_in_variable.expected", input, expected);
}

#[test]
fn spread_with_arguments_invalid() {
    let input = include_str!("parse_executable_document/fixtures/spread_with_arguments.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/spread_with_arguments.invalid.expected");
    test_fixture(transform_fixture, "spread_with_arguments.invalid.graphql", "parse_executable_document/fixtures/spread_with_arguments.invalid.expected", input, expected);
}

#[test]
fn unterminated_string_invalid() {
    let input = include_str!("parse_executable_document/fixtures/unterminated_string.invalid.graphql");
    let expected = include_str!("parse_executable_document/fixtures/unterminated_string.invalid.expected");
    test_fixture(transform_fixture, "unterminated_string.invalid.graphql", "parse_executable_document/fixtures/unterminated_string.invalid.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<74f4b149daa4ad7e9564404219116b8d>>
 */

mod parse_executable_document_with_error_recovery;

use parse_executable_document_with_error_recovery::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn argument_missing_identifier() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier.expected");
    test_fixture(transform_fixture, "argument-missing-identifier.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier.expected", input, expected);
}

#[test]
fn argument_missing_identifier_2() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier-2.grahql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier-2.expected");
    test_fixture(transform_fixture, "argument-missing-identifier-2.grahql", "parse_executable_document_with_error_recovery/fixtures/argument-missing-identifier-2.expected", input, expected);
}

#[test]
fn argument_missing_value() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-value.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-value.expected");
    test_fixture(transform_fixture, "argument-missing-value.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-missing-value.expected", input, expected);
}

#[test]
fn argument_missing_value_2() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-value-2.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-missing-value-2.expected");
    test_fixture(transform_fixture, "argument-missing-value-2.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-missing-value-2.expected", input, expected);
}

#[test]
fn argument_name_only() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-name-only.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-name-only.expected");
    test_fixture(transform_fixture, "argument-name-only.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-name-only.expected", input, expected);
}

#[test]
fn argument_name_only_2() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-name-only-2.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-name-only-2.expected");
    test_fixture(transform_fixture, "argument-name-only-2.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-name-only-2.expected", input, expected);
}

#[test]
fn argument_value_only() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only.expected");
    test_fixture(transform_fixture, "argument-value-only.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-value-only.expected", input, expected);
}

#[test]
fn argument_value_only_2() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only-2.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only-2.expected");
    test_fixture(transform_fixture, "argument-value-only-2.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-value-only-2.expected", input, expected);
}

#[test]
fn argument_value_only_3() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only-3.grahql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-value-only-3.expected");
    test_fixture(transform_fixture, "argument-value-only-3.grahql", "parse_executable_document_with_error_recovery/fixtures/argument-value-only-3.expected", input, expected);
}

#[test]
fn argument_without_closing_paren() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-without-closing-paren.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/argument-without-closing-paren.expected");
    test_fixture(transform_fixture, "argument-without-closing-paren.graphql", "parse_executable_document_with_error_recovery/fixtures/argument-without-closing-paren.expected", input, expected);
}

#[test]
fn directive_without_name() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/directive-without-name.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/directive-without-name.expected");
    test_fixture(transform_fixture, "directive-without-name.graphql", "parse_executable_document_with_error_recovery/fixtures/directive-without-name.expected", input, expected);
}

#[test]
fn empty_argument_list() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/empty-argument-list.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/empty-argument-list.expected");
    test_fixture(transform_fixture, "empty-argument-list.graphql", "parse_executable_document_with_error_recovery/fixtures/empty-argument-list.expected", input, expected);
}

#[test]
fn empty_linked_field() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/empty-linked-field.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/empty-linked-field.expected");
    test_fixture(transform_fixture, "empty-linked-field.graphql", "parse_executable_document_with_error_recovery/fixtures/empty-linked-field.expected", input, expected);
}

#[test]
fn inline_fragment_without_selection() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/inline-fragment-without-selection.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/inline-fragment-without-selection.expected");
    test_fixture(transform_fixture, "inline-fragment-without-selection.graphql", "parse_executable_document_with_error_recovery/fixtures/inline-fragment-without-selection.expected", input, expected);
}

#[test]
fn type_in_argument_value() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/type-in-argument-value.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/type-in-argument-value.expected");
    test_fixture(transform_fixture, "type-in-argument-value.graphql", "parse_executable_document_with_error_recovery/fixtures/type-in-argument-value.expected", input, expected);
}

#[test]
fn variable_definition_with_directive() {
    let input = include_str!("parse_executable_document_with_error_recovery/fixtures/variable-definition-with-directive.graphql");
    let expected = include_str!("parse_executable_document_with_error_recovery/fixtures/variable-definition-with-directive.expected");
    test_fixture(transform_fixture, "variable-definition-with-directive.graphql", "parse_executable_document_with_error_recovery/fixtures/variable-definition-with-directive.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<5807697a03122f4474b041010ef2dc34>>
 */

mod parse_schema_document;

use parse_schema_document::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn directive_description() {
    let input = include_str!("parse_schema_document/fixtures/directive_description.graphql");
    let expected = include_str!("parse_schema_document/fixtures/directive_description.expected");
    test_fixture(transform_fixture, "directive_description.graphql", "parse_schema_document/fixtures/directive_description.expected", input, expected);
}

#[test]
fn field_description() {
    let input = include_str!("parse_schema_document/fixtures/field_description.graphql");
    let expected = include_str!("parse_schema_document/fixtures/field_description.expected");
    test_fixture(transform_fixture, "field_description.graphql", "parse_schema_document/fixtures/field_description.expected", input, expected);
}

#[test]
fn schema_kitchen_sink() {
    let input = include_str!("parse_schema_document/fixtures/schema_kitchen_sink.graphql");
    let expected = include_str!("parse_schema_document/fixtures/schema_kitchen_sink.expected");
    test_fixture(transform_fixture, "schema_kitchen_sink.graphql", "parse_schema_document/fixtures/schema_kitchen_sink.expected", input, expected);
}

#[test]
fn schema_with_leading_comment() {
    let input = include_str!("parse_schema_document/fixtures/schema_with_leading_comment.graphql");
    let expected = include_str!("parse_schema_document/fixtures/schema_with_leading_comment.expected");
    test_fixture(transform_fixture, "schema_with_leading_comment.graphql", "parse_schema_document/fixtures/schema_with_leading_comment.expected", input, expected);
}

#[test]
fn type_definition() {
    let input = include_str!("parse_schema_document/fixtures/type_definition.graphql");
    let expected = include_str!("parse_schema_document/fixtures/type_definition.expected");
    test_fixture(transform_fixture, "type_definition.graphql", "parse_schema_document/fixtures/type_definition.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @generated SignedSource<<e754c9b986474e80e9bc346aa6c77fcd>>
 */

mod print;

use print::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn schema() {
    let input = include_str!("print/fixtures/schema.graphql");
    let expected = include_str!("print/fixtures/schema.expected");
    test_fixture(transform_fixture, "schema.graphql", "print/fixtures/schema.expected", input, expected);
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_syntax::parse_document;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    parse_document(
        fixture.content,
        SourceLocationKey::standalone(fixture.file_name),
    )
    .map(|x| format!("{:#?}", x))
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_syntax::parse_document_with_features;
use graphql_syntax::FragmentArgumentSyntaxKind;
use graphql_syntax::ParserFeatures;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    parse_document_with_features(
        fixture.content,
        SourceLocationKey::standalone(fixture.file_name),
        ParserFeatures {
            fragment_argument_capability:
                FragmentArgumentSyntaxKind::SpreadArgumentsAndFragmentVariableDefinitions,
        },
    )
    .map(|x| format!("{:#?}", x))
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_syntax::parse_executable;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    parse_executable(
        fixture.content,
        SourceLocationKey::standalone(fixture.file_name),
    )
    .map(|x| format!("{:#?}", x))
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_syntax::parse_executable_with_error_recovery;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let result = parse_executable_with_error_recovery(
        fixture.content,
        SourceLocationKey::standalone(fixture.file_name),
    );
    Ok(format!(
        "{:#?}\nErrors:\n{}",
        result.item,
        diagnostics_to_sorted_string(fixture.content, &result.diagnostics)
    ))
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::Diagnostic;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_syntax::parse_schema_document;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    parse_schema_document(
        fixture.content,
        SourceLocationKey::standalone(fixture.file_name),
    )
    .map(|x| format!("{:#?}", x))
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))
}

// NOTE: copied from graphql-test-helpers to avoid cyclic dependency breaking Rust Analyzer
fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_syntax::parse_schema_document;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let type_system_definitions =
        parse_schema_document(fixture.content, SourceLocationKey::generated())
            .expect("Failed to parse definitions")
            .definitions;

    let result = type_system_definitions
        .iter()
        .map(|node| format!("{}", node))
        .collect::<Vec<String>>()
        .join("\n");

    Ok(result)
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::collections::HashMap;
use std::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::SourceLocationKey;
use common::TextSource;
use fixture_tests::Fixture;
use graphql_cli::DiagnosticPrinter;
use graphql_cli::Sources as DiagnosticPrinterSources;
use graphql_ir::build_ir_with_extra_features;
use graphql_ir::BuilderOptions;
use graphql_ir::FragmentVariablesSemantic;
use graphql_ir::Program;
use graphql_ir::RelayMode;
use graphql_syntax::parse_executable;
use graphql_text_printer::print_fragment;
use graphql_text_printer::print_operation;
use graphql_text_printer::PrinterOptions;
use relay_test_schema::get_test_schema;
use relay_test_schema::get_test_schema_with_located_extensions;

pub fn apply_transform_for_test<T>(fixture: &Fixture<'_>, transform: T) -> Result<String, String>
where
    T: Fn(&Program) -> DiagnosticsResult<Program>,
{
    let parts: Vec<_> = fixture.content.split("%extensions%").collect();
    let source_location = SourceLocationKey::embedded(fixture.file_name, 0);
    let source_text = parts[0];

    let mut sources_map: HashMap<SourceLocationKey, String> = Default::default();

    sources_map.insert(source_location, source_text.to_string());
    let ast = parse_executable(source_text, source_location).unwrap();
    let schema = if let Some(extensions_text) = parts.get(1) {
        let extension_location = SourceLocationKey::embedded(fixture.file_name, 1);
        sources_map.insert(extension_location, extensions_text.to_string());

        get_test_schema_with_located_extensions(*extensions_text, extension_location)
    } else {
        get_test_schema()
    };

    let ir_result = build_ir_with_extra_features(
        &schema,
        &ast.definitions,
        &BuilderOptions {
            allow_undefined_fragment_spreads: false,
            fragment_variables_semantic: FragmentVariablesSemantic::PassedValue,
            relay_mode: Some(RelayMode),
            default_anonymous_operation_name: None,
        },
    );
    let ir = ir_result.map_err(|diagnostics| {
        diagnostics_to_sorted_strings_with_sources_map(&sources_map, &diagnostics)
    })?;

    let program = Program::from_definitions(Arc::clone(&schema), ir);
    let next_program = transform(&program).map_err(|diagnostics| {
        diagnostics_to_sorted_strings_with_sources_map(&sources_map, &diagnostics)
    })?;

    let printer_options = PrinterOptions {
        debug_directive_data: true,
        ..Default::default()
    };

    let mut printed = next_program
        .operations()
        .map(|def| print_operation(&schema, def, printer_options.clone()))
        .collect::<Vec<_>>();
    printed.sort();

    let mut printed_fragments = next_program
        .fragments()
        .map(|def| print_fragment(&schema, def, printer_options.clone()))
        .collect::<Vec<_>>();
    printed_fragments.sort();
    printed.extend(printed_fragments);

    Ok(printed.join("\n\n"))
}

pub fn diagnostics_to_sorted_string(source: &str, diagnostics: &[Diagnostic]) -> String {
    let printer =
        DiagnosticPrinter::new(|_| Some(TextSource::from_whole_document(source.to_string())));

    print_diagnostics_to_sorted_string(&printer, diagnostics)
}

pub fn diagnostics_to_sorted_strings_with_sources_map(
    sources: &HashMap<SourceLocationKey, String>,
    diagnostics: &[Diagnostic],
) -> String {
    let printer = DiagnosticPrinter::new(|source_location| {
        sources
            .get(&source_location)
            .map(TextSource::from_whole_document)
    });
    print_diagnostics_to_sorted_string(&printer, diagnostics)
}

fn print_diagnostics_to_sorted_string<T: DiagnosticPrinterSources>(
    printer: &DiagnosticPrinter<T>,
    diagnostics: &[Diagnostic],
) -> String {
    let mut printed = diagnostics
        .iter()
        .map(|diagnostic| printer.diagnostic_to_string(diagnostic))
        .collect::<Vec<_>>();
    printed.sort();
    printed.join("\n\n")
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#![deny(warnings)]
#![deny(rust_2018_idioms)]
#![deny(clippy::all)]

mod print_ast_to_text;
mod print_full_operation;
mod print_to_text;

pub use print_ast_to_text::print_executable_definition_ast;
pub use print_ast_to_text::print_fragment_ast;
pub use print_ast_to_text::print_operation_ast;
pub use print_full_operation::print_full_operation;
pub use print_full_operation::OperationPrinter;
pub use print_to_text::print_arguments;
pub use print_to_text::print_definition;
pub use print_to_text::print_directives;
pub use print_to_text::print_fragment;
pub use print_to_text::print_ir;
pub use print_to_text::print_operation;
pub use print_to_text::print_selection;
pub use print_to_text::print_selections;
pub use print_to_text::print_value;
pub use print_to_text::write_arguments;
pub use print_to_text::write_directives;
pub use print_to_text::write_value;
pub use print_to_text::PrinterOptions;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt::Result as FmtResult;
use std::fmt::Write;

use graphql_syntax::Argument;
use graphql_syntax::Directive;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::FragmentDefinition;
use graphql_syntax::FragmentSpread;
use graphql_syntax::InlineFragment;
use graphql_syntax::LinkedField;
use graphql_syntax::List;
use graphql_syntax::OperationDefinition;
use graphql_syntax::ScalarField;
use graphql_syntax::Selection;
use graphql_syntax::VariableDefinition;

pub fn print_operation_ast(node: &OperationDefinition) -> String {
    let mut printer: Printer = Default::default();
    printer.print_operation(node).unwrap();

    printer.output
}

pub fn print_fragment_ast(node: &FragmentDefinition) -> String {
    let mut printer: Printer = Default::default();
    printer.print_fragment(node).unwrap();

    printer.output
}

pub fn print_executable_definition_ast(node: &ExecutableDefinition) -> String {
    match node {
        ExecutableDefinition::Operation(operation) => print_operation_ast(operation),
        ExecutableDefinition::Fragment(fragment) => print_fragment_ast(fragment),
    }
}

#[derive(Default)]
struct Printer {
    output: String,
}

impl Printer {
    fn print_operation(&mut self, operation: &OperationDefinition) -> FmtResult {
        if let Some((_, operation_kind)) = operation.operation {
            write!(self.output, "{}", operation_kind)?;
        };
        if let Some(name) = &operation.name {
            write!(self.output, " {}", name.value)?;
        }
        if let Some(variable_definitions) = &operation.variable_definitions {
            self.print_variable_definitions(variable_definitions)?;
        }
        self.print_directives(&operation.directives)?;
        writeln!(self.output, " {{")?;
        self.print_selections(&operation.selections, "  ")?;
        write!(self.output, "}}")?;

        Ok(())
    }

    fn print_fragment(&mut self, fragment: &FragmentDefinition) -> FmtResult {
        write!(self.output, "fragment {}", fragment.name)?;
        if let Some(variable_definitions) = &fragment.variable_definitions {
            self.print_variable_definitions(variable_definitions)?;
        }
        write!(self.output, " {}", fragment.type_condition)?;
        self.print_directives(&fragment.directives)?;
        writeln!(self.output, " {{")?;
        self.print_selections(&fragment.selections, "  ")?;
        write!(self.output, "}}")?;

        Ok(())
    }

    fn print_variable_definitions(
        &mut self,
        variable_definitions: &List<VariableDefinition>,
    ) -> FmtResult {
        write!(self.output, "(")?;
        let last = variable_definitions.items.last();
        for variable_definition in &variable_definitions.items {
            self.print_variable_definition(variable_definition)?;
            if let Some(last) = last {
                if last != variable_definition {
                    write!(self.output, ", ")?;
                }
            }
        }
        write!(self.output, ")")?;

        Ok(())
    }

    fn print_variable_definition(&mut self, variable_definition: &VariableDefinition) -> FmtResult {
        write!(
            self.output,
            "{}: {}",
            variable_definition.name, variable_definition.type_
        )?;
        if let Some(default_value) = &variable_definition.default_value {
            write!(self.output, " = {}", default_value)?;
        }
        self.print_directives(&variable_definition.directives)?;

        Ok(())
    }

    fn print_directives(&mut self, directives: &[Directive]) -> FmtResult {
        for directive in directives {
            self.print_directive(directive)?;
        }

        Ok(())
    }

    fn print_directive(&mut self, directive: &Directive) -> FmtResult {
        write!(self.output, " @{}", directive.name)?;

        if let Some(arguments) = &directive.arguments {
            self.print_arguments(arguments)?;
        }

        Ok(())
    }

    fn print_arguments(&mut self, arguments: &List<Argument>) -> FmtResult {
        write!(self.output, "(")?;
        let last_arg = arguments.items.last();
        for argument in &arguments.items {
            write!(self.output, "{}", argument)?;
            if let Some(last_arg) = last_arg {
                if last_arg != argument {
                    write!(self.output, ", ")?;
                }
            }
        }
        write!(self.output, ")")?;
        Ok(())
    }

    fn print_selections(&mut self, selections: &List<Selection>, indent: &str) -> FmtResult {
        for selection in &selections.items {
            self.print_selection(selection, indent)?;
            writeln!(self.output)?;
        }

        Ok(())
    }

    fn print_selection(&mut self, selection: &Selection, indent: &str) -> FmtResult {
        write!(self.output, "{}", indent)?;
        match selection {
            Selection::FragmentSpread(node) => self.print_fragment_spread(node),
            Selection::InlineFragment(node) => self.print_inline_fragment(node, indent),
            Selection::LinkedField(node) => self.print_linked_field(node, indent),
            Selection::ScalarField(node) => self.print_scalar_field(node),
        }
    }

    fn print_fragment_spread(&mut self, node: &FragmentSpread) -> FmtResult {
        write!(self.output, "...{}", node.name)?;
        if let Some(arguments) = &node.arguments {
            self.print_arguments(arguments)?;
        }
        self.print_directives(&node.directives)?;

        Ok(())
    }

    fn print_inline_fragment(&mut self, node: &InlineFragment, indent: &str) -> FmtResult {
        write!(self.output, "...")?;
        if let Some(type_condition) = &node.type_condition {
            write!(self.output, " {}", type_condition)?;
        }
        self.print_directives(&node.directives)?;
        writeln!(self.output, " {{")?;
        self.print_selections(&node.selections, &format!("  {}", indent))?;
        write!(self.output, "{}}}", indent)?;
        Ok(())
    }

    fn print_linked_field(&mut self, node: &LinkedField, indent: &str) -> FmtResult {
        if let Some(alias) = &node.alias {
            write!(self.output, "{}: ", alias)?;
        }

        write!(self.output, "{}", node.name)?;
        if let Some(arguments) = &node.arguments {
            self.print_arguments(arguments)?;
        }
        self.print_directives(&node.directives)?;
        writeln!(self.output, " {{")?;
        self.print_selections(&node.selections, &format!("  {}", indent))?;
        write!(self.output, "{}}}", indent)?;
        Ok(())
    }

    fn print_scalar_field(&mut self, node: &ScalarField) -> FmtResult {
        if let Some(alias) = &node.alias {
            write!(self.output, "{}: ", alias)?;
        }

        write!(self.output, "{}", node.name)?;
        if let Some(arguments) = &node.arguments {
            self.print_arguments(arguments)?;
        }
        self.print_directives(&node.directives)?;

        Ok(())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::sync::Arc;

use fnv::FnvHashMap;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentSpread;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Visitor;

use crate::print_fragment;
use crate::print_operation;
use crate::PrinterOptions;

pub fn print_full_operation(
    program: &Program,
    operation: &OperationDefinition,
    options: PrinterOptions,
) -> String {
    let mut printer = OperationPrinter::new(program, options);
    printer.print(operation)
}

pub struct OperationPrinter<'s> {
    fragment_result: FnvHashMap<FragmentDefinitionName, String>,
    reachable_fragments: FnvHashMap<FragmentDefinitionName, Arc<FragmentDefinition>>,
    program: &'s Program,
    options: PrinterOptions,
}

impl<'s> OperationPrinter<'s> {
    pub fn new(program: &'s Program, options: PrinterOptions) -> Self {
        Self {
            fragment_result: Default::default(),
            reachable_fragments: Default::default(),
            program,
            options,
        }
    }

    pub fn print(&mut self, operation: &OperationDefinition) -> String {
        let mut result = print_operation(&self.program.schema, operation, self.options);
        self.visit_operation(operation);
        let mut fragments: Vec<(FragmentDefinitionName, Arc<FragmentDefinition>)> =
            self.reachable_fragments.drain().collect();
        fragments.sort_unstable_by_key(|(name, _)| *name);
        for (_, fragment) in fragments {
            if !self.options.compact {
                result.push_str("\n\n");
            }
            result.push_str(self.print_fragment(&fragment));
        }
        if !self.options.compact {
            result.push('\n');
        }
        result
    }

    fn print_fragment(&mut self, fragment: &FragmentDefinition) -> &str {
        let schema = &self.program.schema;
        self.fragment_result
            .entry(fragment.name.item)
            .or_insert_with(|| print_fragment(schema, fragment, self.options))
    }
}

impl<'s, 'ir> Visitor for OperationPrinter<'s> {
    const NAME: &'static str = "OperationPrinter";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn visit_fragment_spread(&mut self, spread: &FragmentSpread) {
        if self.reachable_fragments.contains_key(&spread.fragment.item) {
            return;
        }

        let fragment = self.program.fragment(spread.fragment.item).unwrap();
        self.reachable_fragments
            .insert(spread.fragment.item, Arc::clone(fragment));
        self.visit_fragment(fragment);
    }

    fn visit_scalar_field(&mut self, _field: &ScalarField) {
        // Stop
    }
}