/*
 * 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 common::Span;
use graphql_syntax::parse_executable_with_features;
use graphql_syntax::FragmentArgumentSyntaxKind;
use graphql_syntax::ParserFeatures;

use super::*;

pub(super) fn test_resolution(source: &str, sub_str: &str, cb: impl Fn(&ResolutionPath<'_>)) {
    let document = parse_executable_with_features(
        source,
        SourceLocationKey::standalone("/test/file"),
        ParserFeatures {
            fragment_argument_capability:
                FragmentArgumentSyntaxKind::SpreadArgumentsAndFragmentVariableDefinitions,
        },
    )
    .unwrap();

    let pos = source.find(sub_str).unwrap() as u32;

    // Select the `uri` field
    let position_span = Span {
        start: pos,
        end: pos,
    };

    let resolved = document.resolve((), position_span);

    cb(&resolved);
}

#[test]
fn operation_definition_type_condition() {
    let source = r#"
            query Foo {
                me {
                    id
                }
            }
        "#;
    test_resolution(source, "Foo", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::OperationDefinitionName(_),
            })
        );
    })
}

#[test]
fn operation_definition_operation() {
    let source = r#"
            query Foo {
                me {
                    id
                }
            }
        "#;
    test_resolution(source, "query", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Operation(OperationPath {
                inner: (_, OperationKind::Query),
                parent: _,
            })
        );
    })
}

#[test]
fn linked_field_name() {
    let source = r#"
            query Foo {
                me {
                    id
                }
            }
        "#;
    test_resolution(source, "me", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::LinkedFieldName(_),
            })
        );
    })
}

#[test]
fn linked_field_alias() {
    let source = r#"
            query Foo {
                mario: me {
                    id
                }
            }
        "#;
    test_resolution(source, "mario", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::LinkedFieldAlias(_),
            })
        );
    })
}

#[test]
fn scalar_field_name() {
    let source = r#"
            query Foo {
                me {
                    id
                }
            }
        "#;
    test_resolution(source, "id", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::ScalarFieldName(_),
            })
        );
    })
}

#[test]
fn scalar_field_alias() {
    let source = r#"
            query Foo {
                me {
                    identity: id
                }
            }
        "#;
    test_resolution(source, "identity", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::ScalarFieldAlias(_),
            })
        );
    })
}

#[test]
fn inline_fragment() {
    let source = r#"
            query Foo {
                me {
                    ... on User {
                        id
                    }
                }
            }
        "#;
    test_resolution(source, "...", |resolved| {
        assert_matches!(resolved, ResolutionPath::InlineFragment(_));
    })
}

#[test]
fn inline_fragment_type_condition() {
    let source = r#"
            query Foo {
                me {
                    ... on User {
                        id
                    }
                }
            }
        "#;
    test_resolution(source, "User", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::TypeConditionType(_),
            })
        );
    })
}
#[test]
fn fragment_definition_name() {
    let source = r#"
            fragment Foo on User {
                id
            }
        "#;
    test_resolution(source, "Foo", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::FragmentDefinitionName(_),
            })
        );
    })
}

#[test]
fn fragment_definition_type() {
    let source = r#"
            fragment Foo on User {
                id
            }
        "#;
    test_resolution(source, "User", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::TypeConditionType(_),
            })
        );
    })
}

#[test]
fn fragment_spread_name() {
    let source = r#"
            fragment Foo on Node {
                ...someFragment
            }
        "#;
    test_resolution(source, "someFragment", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::FragmentSpreadName(_),
            })
        );
    })
}

#[test]
fn directive_name() {
    let source = r#"
            fragment Foo on Node {
                id @required(action: LOG)
            }
        "#;
    test_resolution(source, "required", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::DirectiveName(_),
            })
        );
    })
}

#[test]
fn argument_name() {
    let source = r#"
            fragment Foo on Node {
                id @required(action: LOG)
            }
        "#;
    test_resolution(source, "action", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::ArgumentName(_),
            })
        );
    })
}

#[test]
fn argument_value() {
    let source = r#"
            fragment Foo on Node {
                id @required(action: LOG)
            }
        "#;
    test_resolution(source, "LOG", |resolved| {
        assert_matches!(resolved, ResolutionPath::ConstantEnum(_));
    })
}

#[test]
fn list_literal() {
    let source = r#"
            query Foo {
                checkinSearchQuery(query: {
                    query: "Hello",
                    inputs: [{query: "Goodbye", inputs: []}]
                })
            }
        "#;
    test_resolution(source, "Goodbye", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::ConstantString(ConstantStringPath {
                inner: _,
                parent: ConstantValuePath {
                    inner: _,
                    parent: ConstantValueParent::ConstantArgValue(_),
                },
            })
        );
    })
}

#[test]
fn fragment_argument_name() {
    let source = r#"
        fragment Foo($localId: ID!) on User {
            id
          }
        "#;
    test_resolution(source, "localId", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::VariableIdentifier(VariableIdentifierPath {
                inner: _,
                parent: VariableIdentifierParent::VariableDefinition(VariableDefinitionPath {
                    inner: _,
                    parent: VariableDefinitionListPath {
                        inner: _,
                        parent: VariableDefinitionListParent::FragmentDefinition(_),
                    },
                }),
            })
        );
    })
}

#[test]
fn fragment_argument_type() {
    let source = r#"
        fragment Foo($localId: ID!) on User {
            id
          }
        "#;
    test_resolution(source, "ID!", |resolved| {
        assert_matches!(
            resolved,
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent: IdentParent::NamedTypeAnnotation(NamedTypeAnnotationPath {
                    inner: _,
                    parent: TypeAnnotationPath {
                        inner: _,
                        parent: TypeAnnotationParent::NonNullTypeAnnotation(_),
                    }
                }),
            })
        )
    })
}

#[test]
fn fragment_argument_default_value() {
    let source = r#"
        fragment Foo($localId: ID! = "1") on User {
            id
          }
        "#;
    test_resolution(source, r#""1""#, |resolved| {
        assert_matches!(resolved, ResolutionPath::ConstantString(_));
    })
}
#[test]
fn fragment_argument_directive() {
    let source = r#"
        fragment Foo($localId: ID! = "1") on User {
            id
          }
        "#;
    test_resolution(source, r#""1""#, |resolved| {
        assert_matches!(resolved, ResolutionPath::ConstantString(_));
    })
}


/*
 * 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 super::*;

impl<'a> TypeAnnotationParent<'a> {
    pub fn find_variable_definition_path(&'a self) -> &'a VariableDefinitionPath<'a> {
        match self {
            TypeAnnotationParent::VariableDefinition(variable_definition_path) => {
                variable_definition_path
            }
            TypeAnnotationParent::ListTypeAnnotation(ListTypeAnnotationPath {
                inner: _,
                parent: list_type_annotation_parent,
            }) => list_type_annotation_parent
                .parent
                .find_variable_definition_path(),
            TypeAnnotationParent::NonNullTypeAnnotation(NonNullTypeAnnotationPath {
                inner: _,
                parent: non_null_type_annotation_parent,
            }) => non_null_type_annotation_parent
                .parent
                .find_variable_definition_path(),
        }
    }
}

#[cfg(test)]
mod tests {
    use intern::Lookup;

    use super::*;
    use crate::test::test_resolution;

    #[test]
    fn variable_definition_path_named_type_annotation() {
        let source = r#"
            query Foo($isCool: Boolean) {
                me
            }
        "#;
        test_resolution(source, "Boolean", |resolved| {
            if let ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::NamedTypeAnnotation(NamedTypeAnnotationPath {
                        inner: _,
                        parent:
                            TypeAnnotationPath {
                                inner: _,
                                parent: type_annotation_parent,
                            },
                    }),
            }) = resolved
            {
                let variable_definition_path =
                    type_annotation_parent.find_variable_definition_path();
                assert!(variable_definition_path.inner.name.name.lookup() == "isCool");
            } else {
                panic!(
                    "Should resolve to Ident with parent NamedTypedAnnotation, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn variable_definition_path_list() {
        let source = r#"
            query Foo($isCool: [Boolean]) {
                me
            }
        "#;
        test_resolution(source, "Boolean", |resolved| {
            if let ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::NamedTypeAnnotation(NamedTypeAnnotationPath {
                        inner: _,
                        parent:
                            TypeAnnotationPath {
                                inner: _,
                                parent: type_annotation_parent,
                            },
                    }),
            }) = resolved
            {
                let variable_definition_path =
                    type_annotation_parent.find_variable_definition_path();
                assert!(variable_definition_path.inner.name.name.lookup() == "isCool");
            } else {
                panic!(
                    "Should resolve to Ident with parent NamedTypedAnnotation, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn variable_definition_path_non_null() {
        let source = r#"
            query Foo($isCool: Boolean!) {
                me
            }
        "#;
        test_resolution(source, "Boolean", |resolved| {
            if let ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::NamedTypeAnnotation(NamedTypeAnnotationPath {
                        inner: _,
                        parent:
                            TypeAnnotationPath {
                                inner: _,
                                parent: type_annotation_parent,
                            },
                    }),
            }) = resolved
            {
                let variable_definition_path =
                    type_annotation_parent.find_variable_definition_path();
                assert!(variable_definition_path.inner.name.name.lookup() == "isCool");
            } else {
                panic!(
                    "Should resolve to Ident with parent NamedTypedAnnotation, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn variable_definition_path_non_null_list_of_non_null() {
        let source = r#"
            query Foo($isCool: [Boolean!]!) {
                me
            }
        "#;
        test_resolution(source, "Boolean", |resolved| {
            if let ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::NamedTypeAnnotation(NamedTypeAnnotationPath {
                        inner: _,
                        parent:
                            TypeAnnotationPath {
                                inner: _,
                                parent: type_annotation_parent,
                            },
                    }),
            }) = resolved
            {
                let variable_definition_path =
                    type_annotation_parent.find_variable_definition_path();
                assert!(variable_definition_path.inner.name.name.lookup() == "isCool");
            } else {
                panic!(
                    "Should resolve to Ident with parent NamedTypedAnnotation, instead got {:?}",
                    resolved
                );
            }
        })
    }
}


/*
 * 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 graphql_syntax::OperationType;
use intern::string_key::StringKey;
use thiserror::Error;

use crate::definitions::Type;

pub type Result<T> = std::result::Result<T, SchemaError>;

#[derive(Debug, Error)]
pub enum SchemaError {
    #[error("Duplicate {0:?} type definition, got '{1}' and '{2}'.")]
    DuplicateOperationDefinition(OperationType, StringKey, StringKey),

    #[error("Duplicate directive definition '{0}'.")]
    DuplicateDirectiveDefinition(StringKey),

    #[error("Cannot extend type '{0}', the type is not defined on the server schema.")]
    ExtendUndefinedType(StringKey),

    #[error("Expected an object type for name '{0}', got '{1:?}'.")]
    ExpectedObjectReference(StringKey, Type),

    #[error("Expected an interface type for name '{0}', got '{1:?}'.")]
    ExpectedInterfaceReference(StringKey, Type),

    #[error("Reference to undefined type '{0}'.")]
    UndefinedType(StringKey),

    #[error("Duplicate field definition '{0}' found.")]
    DuplicateField(StringKey),

    #[error("Duplicate definition for type '{0}'.")]
    DuplicateType(StringKey),

    #[error("Invalid ID '{0}' provided for type '{1}'")]
    UnknownTypeID(usize, String),

    // TODO: These should be replaced with error codes or by unifying the parsers.
    #[error("Parse Error '{0}' at {1}.\nError line:'{2}'")]
    Syntax(String, String, String),

    #[error(
        "Expected a field of the input object to be a valid input object, scalar, or enum. Got '{0}'."
    )]
    ExpectedInputType(StringKey),

    #[error("Reference to undefined directive '{0}'.")]
    UndefinedDirective(StringKey),
}


/*
 * 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 intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;

lazy_static! {
    pub static ref CLIENT_ID_DESCRIPTION: StringKey = "Relay's cache key for this object.".intern();
    pub static ref TYPENAME_DESCRIPTION: StringKey =
        "This object's GraphQL type. Provided by GraphQL type name introspection.".intern();
}


/*
 * 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 common::DirectiveName;
use intern::string_key::StringKey;
use intern::Lookup;

use crate::definitions::Directive;
use crate::definitions::*;

pub trait Schema {
    fn query_type(&self) -> Option<Type>;

    fn mutation_type(&self) -> Option<Type>;

    fn subscription_type(&self) -> Option<Type>;

    fn clientid_field(&self) -> FieldID;

    fn strongid_field(&self) -> FieldID;

    fn typename_field(&self) -> FieldID;

    fn fetch_token_field(&self) -> FieldID;

    fn is_fulfilled_field(&self) -> FieldID;

    fn get_type(&self, type_name: StringKey) -> Option<Type>;

    fn get_directive(&self, name: DirectiveName) -> Option<&Directive>;

    fn input_object(&self, id: InputObjectID) -> &InputObject;
    fn input_objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a InputObject> + 'a>;

    fn enum_(&self, id: EnumID) -> &Enum;
    fn enums<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Enum> + 'a>;

    fn scalar(&self, id: ScalarID) -> &Scalar;
    fn scalars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Scalar> + 'a>;

    fn field(&self, id: FieldID) -> &Field;
    fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Field> + 'a>;

    fn object(&self, id: ObjectID) -> &Object;
    fn objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Object> + 'a>;

    fn union(&self, id: UnionID) -> ⋃
    fn unions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Union> + 'a>;

    fn interface(&self, id: InterfaceID) -> &Interface;
    fn interfaces<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Interface> + 'a>;

    fn get_type_name(&self, type_: Type) -> StringKey;

    fn is_extension_type(&self, type_: Type) -> bool;

    fn is_string(&self, type_: Type) -> bool;

    fn is_id(&self, type_: Type) -> bool;

    fn named_field(&self, parent_type: Type, name: StringKey) -> Option<FieldID>;

    fn unchecked_argument_type_sentinel(&self) -> &TypeReference<Type>;

    fn snapshot_print(&self) -> String;

    // Is `maybe_subtype` usable as â€” but not equal to â€” `super_type`.
    // Examples:
    // is_type_strict_subtype_of(Int!, Int) => true, Int! is usable where an Int is expected
    // is_type_strict_subtype_of(Int, Int) => false, same type
    // is_type_strict_subtype_of(InterfaceThatExtendsNode, Node) => true
    // is_type_strict_subtype_of(Node, Node) => false, same type
    fn is_type_strict_subtype_of(
        &self,
        maybe_subtype: &TypeReference<Type>,
        super_type: &TypeReference<Type>,
    ) -> bool {
        match (maybe_subtype, super_type) {
            (TypeReference::NonNull(of_sub), TypeReference::NonNull(of_super)) => {
                self.is_type_strict_subtype_of(of_sub, of_super)
            }
            // If supertype is non-null, maybeSubType must be non-nullable too
            (_, TypeReference::NonNull(_)) => false,
            // If supertype is nullable, maybeSubType may be non-nullable or nullable
            (TypeReference::NonNull(of_sub), _) => {
                // `T!` is a strict subtype of `U` so long as T is a (non-strict) subtype of U
                self.is_type_subtype_of(of_sub, super_type)
            }
            (TypeReference::List(of_sub), TypeReference::List(of_super)) => {
                self.is_type_strict_subtype_of(of_sub, of_super)
            }
            // If supertype is a list, maybeSubType must be a list too
            (_, TypeReference::List(_)) => false,
            // If supertype is not a list, maybeSubType must also not be a list
            (TypeReference::List(_), _) => false,
            (TypeReference::Named(named_subtype), TypeReference::Named(named_supertype)) => {
                self.is_named_type_strict_subtype_of(*named_subtype, *named_supertype)
            }
        }
    }

    fn is_type_subtype_of(
        &self,
        maybe_subtype: &TypeReference<Type>,
        super_type: &TypeReference<Type>,
    ) -> bool {
        match (maybe_subtype, super_type) {
            (TypeReference::NonNull(of_sub), TypeReference::NonNull(of_super)) => {
                self.is_type_subtype_of(of_sub, of_super)
            }
            // If supertype is non-null, maybeSubType must be non-nullable too
            (_, TypeReference::NonNull(_)) => false,
            // If supertype is nullable, maybeSubType may be non-nullable or nullable
            (TypeReference::NonNull(of_sub), _) => self.is_type_subtype_of(of_sub, super_type),
            (TypeReference::List(of_sub), TypeReference::List(of_super)) => {
                self.is_type_subtype_of(of_sub, of_super)
            }
            // If supertype is a list, maybeSubType must be a list too
            (_, TypeReference::List(_)) => false,
            // If supertype is not a list, maybeSubType must also not be a list
            (TypeReference::List(_), _) => false,
            (TypeReference::Named(named_subtype), TypeReference::Named(named_supertype)) => {
                self.is_named_type_subtype_of(*named_subtype, *named_supertype)
            }
        }
    }

    // Is `maybe_subtype` usable as â€” but not equal to â€” `super_type`.
    // Examples:
    // is_type_strict_subtype_of(InterfaceThatExtendsNode, Node) => true
    // is_type_strict_subtype_of(Node, Node) => false, same type
    fn is_named_type_strict_subtype_of(&self, maybe_subtype: Type, super_type: Type) -> bool {
        match (maybe_subtype, super_type) {
            (Type::Object(sub_id), Type::Interface(super_id)) => {
                // does object implement the interface
                let object = self.object(sub_id);
                object.interfaces.contains(&super_id)
            }
            (Type::Object(sub_id), Type::Union(super_id)) => {
                // is object a member of the union
                let union = self.union(super_id);
                union.members.contains(&sub_id)
            }
            (Type::Interface(sub_id), Type::Interface(super_id)) => {
                // does sub interface implement the super interface
                let interface = self.interface(sub_id);
                interface.interfaces.contains(&super_id)
            }
            _ => false,
        }
    }

    fn is_named_type_subtype_of(&self, maybe_subtype: Type, super_type: Type) -> bool {
        match (maybe_subtype, super_type) {
            (Type::Object(sub_id), Type::Interface(super_id)) => {
                // does object implement the interface
                let object = self.object(sub_id);
                object.interfaces.contains(&super_id)
            }
            (Type::Object(sub_id), Type::Union(super_id)) => {
                // is object a member of the union
                let union = self.union(super_id);
                union.members.contains(&sub_id)
            }
            (Type::Interface(sub_id), Type::Interface(super_id)) => {
                // does interface implement the interface
                let interface = self.interface(sub_id);
                sub_id == super_id || interface.interfaces.contains(&super_id)
            }
            _ => maybe_subtype == super_type,
        }
    }

    fn are_overlapping_types(&self, a: Type, b: Type) -> bool {
        fn overlapping_objects(a: &[ObjectID], b: &[ObjectID]) -> bool {
            a.iter().any(|item| b.contains(item))
        }

        fn overlapping_interfaces(a: &[InterfaceID], b: &[InterfaceID]) -> bool {
            a.iter().any(|item| b.contains(item))
        }

        if a == b {
            return true;
        };
        match (a, b) {
            (Type::Interface(a), Type::Interface(b)) => {
                overlapping_objects(
                    &self.interface(a).implementing_objects,
                    &self.interface(b).implementing_objects,
                ) || overlapping_interfaces(
                    &self.interface(a).implementing_interfaces,
                    &self.interface(b).implementing_interfaces,
                )
            }

            (Type::Union(a), Type::Union(b)) => {
                overlapping_objects(&self.union(a).members, &self.union(b).members)
            }

            (Type::Union(union_id), Type::Interface(interface_id))
            | (Type::Interface(interface_id), Type::Union(union_id)) => overlapping_objects(
                &self.union(union_id).members,
                &self.interface(interface_id).implementing_objects,
            ),

            (Type::Interface(interface_id), Type::Object(object_id))
            | (Type::Object(object_id), Type::Interface(interface_id)) => self
                .interface(interface_id)
                .implementing_objects
                .contains(&object_id),

            (Type::Union(union_id), Type::Object(object_id))
            | (Type::Object(object_id), Type::Union(union_id)) => {
                self.union(union_id).members.contains(&object_id)
            }

            _ => false,
        }
    }

    fn write_type_string(&self, writer: &mut String, type_: &TypeReference<Type>) -> FmtResult {
        match type_ {
            TypeReference::Named(inner) => {
                write!(writer, "{}", self.get_type_name(*inner).lookup())
            }
            TypeReference::NonNull(of) => {
                self.write_type_string(writer, of)?;
                write!(writer, "!")
            }
            TypeReference::List(of) => {
                write!(writer, "[")?;
                self.write_type_string(writer, of)?;
                write!(writer, "]")
            }
        }
    }

    fn get_type_string(&self, type_: &TypeReference<Type>) -> String {
        let mut result = String::new();
        self.write_type_string(&mut result, type_).unwrap();
        result
    }

    fn is_extension_directive(&self, name: DirectiveName) -> bool {
        if let Some(directive) = self.get_directive(name) {
            directive.is_extension
        } else {
            panic!("Unknown directive {}.", name.0.lookup())
        }
    }

    fn directives_for_type(&self, type_: Type) -> &Vec<DirectiveValue> {
        match type_ {
            Type::Enum(id) => &self.enum_(id).directives,
            Type::InputObject(id) => &self.input_object(id).directives,
            Type::Interface(id) => &self.interface(id).directives,
            Type::Object(id) => &self.object(id).directives,
            Type::Scalar(id) => &self.scalar(id).directives,
            Type::Union(id) => &self.union(id).directives,
        }
    }
}


/*
 * 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.
 */

//! This crate contains a GraphQL schema representation.

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

pub mod definitions;
mod errors;
mod field_descriptions;
mod flatbuffer;
mod graphql_schema;
mod in_memory;
mod schema;
pub mod suggestion_list;

use common::DiagnosticsResult;
use common::SourceLocationKey;
pub use definitions::Argument;
pub use definitions::ArgumentDefinitions;
pub use definitions::ArgumentValue;
pub use definitions::Directive;
pub use definitions::DirectiveValue;
pub use definitions::Enum;
pub use definitions::EnumID;
pub use definitions::EnumValue;
pub use definitions::Field;
pub use definitions::FieldID;
pub use definitions::InputObject;
pub use definitions::InputObjectID;
pub use definitions::Interface;
pub use definitions::InterfaceID;
pub use definitions::Object;
pub use definitions::ObjectID;
pub use definitions::Scalar;
pub use definitions::ScalarID;
pub use definitions::Type;
pub use definitions::TypeReference;
pub use definitions::TypeWithFields;
pub use definitions::Union;
pub use definitions::UnionID;
pub use errors::Result;
pub use errors::SchemaError;
use flatbuffer::FlatBufferSchema;
pub use flatbuffer::SchemaWrapper;
pub use graphql_schema::Schema;
pub use graphql_syntax::DirectiveLocation;
use graphql_syntax::SchemaDocument;
pub use graphql_syntax::TypeSystemDefinition;
pub use in_memory::InMemorySchema;

pub use crate::schema::SDLSchema;

const BUILTINS: &str = include_str!("./builtins.graphql");

pub use flatbuffer::serialize_as_flatbuffer;

pub fn build_schema(sdl: &str) -> DiagnosticsResult<SDLSchema> {
    build_schema_with_extensions::<_, &str>(&[(sdl, SourceLocationKey::generated())], &[])
}

pub fn build_schema_with_extensions<T: AsRef<str>, U: AsRef<str>>(
    server_sdls: &[(T, SourceLocationKey)],
    extension_sdls: &[(U, SourceLocationKey)],
) -> DiagnosticsResult<SDLSchema> {
    let mut server_documents = vec![builtins()?];

    let server_schema_document = match server_sdls {
        [(sdl, source_location)] => {
            graphql_syntax::parse_schema_document(sdl.as_ref(), *source_location)?
        }
        _ => {
            // When the schema is split across multiple files, the individual
            // files may not be syntactically complete, so we join them together
            // before parsing.

            // Note that this requires us to use a generates source location key which
            // means click to definition for schema files will not work.
            let mut combined_sdl: String = String::new();
            for (sdl, _) in server_sdls {
                combined_sdl.push_str(sdl.as_ref());
                combined_sdl.push('\n');
            }
            graphql_syntax::parse_schema_document(&combined_sdl, SourceLocationKey::Generated)?
        }
    };

    server_documents.push(server_schema_document);

    let mut client_schema_documents = Vec::new();
    for (extension_sdl, location_key) in extension_sdls {
        client_schema_documents.push(graphql_syntax::parse_schema_document(
            extension_sdl.as_ref(),
            *location_key,
        )?);
    }

    SDLSchema::build(&server_documents, &client_schema_documents)
}

pub fn build_schema_with_flat_buffer(bytes: Vec<u8>) -> SDLSchema {
    SDLSchema::FlatBuffer(SchemaWrapper::from_vec(bytes))
}

pub fn build_schema_from_flat_buffer(bytes: &[u8]) -> DiagnosticsResult<FlatBufferSchema<'_>> {
    Ok(FlatBufferSchema::build(bytes))
}

pub fn builtins() -> DiagnosticsResult<SchemaDocument> {
    graphql_syntax::parse_schema_document(BUILTINS, SourceLocationKey::generated())
}


/*
 * 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::DiagnosticsResult;
use common::DirectiveName;
use common::SourceLocationKey;
use graphql_syntax::*;
use intern::string_key::StringKey;

use crate::definitions::Directive;
use crate::definitions::*;
use crate::flatbuffer::SchemaWrapper;
use crate::graphql_schema::Schema;
use crate::in_memory::InMemorySchema;

#[derive(Debug)]
pub enum SDLSchema {
    InMemory(InMemorySchema),
    FlatBuffer(SchemaWrapper),
}

impl Schema for SDLSchema {
    fn query_type(&self) -> Option<Type> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.query_type(),
            SDLSchema::InMemory(schema) => schema.query_type(),
        }
    }

    fn mutation_type(&self) -> Option<Type> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.mutation_type(),
            SDLSchema::InMemory(schema) => schema.mutation_type(),
        }
    }

    fn subscription_type(&self) -> Option<Type> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.subscription_type(),
            SDLSchema::InMemory(schema) => schema.subscription_type(),
        }
    }

    fn clientid_field(&self) -> FieldID {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.clientid_field(),
            SDLSchema::InMemory(schema) => schema.clientid_field(),
        }
    }

    fn strongid_field(&self) -> FieldID {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.strongid_field(),
            SDLSchema::InMemory(schema) => schema.strongid_field(),
        }
    }

    fn typename_field(&self) -> FieldID {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.typename_field(),
            SDLSchema::InMemory(schema) => schema.typename_field(),
        }
    }

    fn fetch_token_field(&self) -> FieldID {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.fetch_token_field(),
            SDLSchema::InMemory(schema) => schema.fetch_token_field(),
        }
    }

    fn is_fulfilled_field(&self) -> FieldID {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.is_fulfilled_field(),
            SDLSchema::InMemory(schema) => schema.is_fulfilled_field(),
        }
    }

    fn get_type(&self, type_name: StringKey) -> Option<Type> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.get_type(type_name),
            SDLSchema::InMemory(schema) => schema.get_type(type_name),
        }
    }

    fn get_directive(&self, name: DirectiveName) -> Option<&Directive> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.get_directive(name),
            SDLSchema::InMemory(schema) => schema.get_directive(name),
        }
    }

    fn input_object(&self, id: InputObjectID) -> &InputObject {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.input_object(id),
            SDLSchema::InMemory(schema) => schema.input_object(id),
        }
    }

    fn enum_(&self, id: EnumID) -> &Enum {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.enum_(id),
            SDLSchema::InMemory(schema) => schema.enum_(id),
        }
    }

    fn scalar(&self, id: ScalarID) -> &Scalar {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.scalar(id),
            SDLSchema::InMemory(schema) => schema.scalar(id),
        }
    }

    fn field(&self, id: FieldID) -> &Field {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.field(id),
            SDLSchema::InMemory(schema) => schema.field(id),
        }
    }

    fn object(&self, id: ObjectID) -> &Object {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.object(id),
            SDLSchema::InMemory(schema) => schema.object(id),
        }
    }

    fn union(&self, id: UnionID) -> &Union {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.union(id),
            SDLSchema::InMemory(schema) => schema.union(id),
        }
    }

    fn interface(&self, id: InterfaceID) -> &Interface {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.interface(id),
            SDLSchema::InMemory(schema) => schema.interface(id),
        }
    }

    fn get_type_name(&self, type_: Type) -> StringKey {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.get_type_name(type_),
            SDLSchema::InMemory(schema) => schema.get_type_name(type_),
        }
    }

    fn is_extension_type(&self, type_: Type) -> bool {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.is_extension_type(type_),
            SDLSchema::InMemory(schema) => schema.is_extension_type(type_),
        }
    }

    fn is_string(&self, type_: Type) -> bool {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.is_string(type_),
            SDLSchema::InMemory(schema) => schema.is_string(type_),
        }
    }

    fn is_id(&self, type_: Type) -> bool {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.is_id(type_),
            SDLSchema::InMemory(schema) => schema.is_id(type_),
        }
    }

    fn named_field(&self, parent_type: Type, name: StringKey) -> Option<FieldID> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.named_field(parent_type, name),
            SDLSchema::InMemory(schema) => schema.named_field(parent_type, name),
        }
    }

    /// A value that represents a type of unchecked arguments where we don't
    /// have a type to instantiate the argument.
    ///
    /// TODO: we probably want to replace this with a proper `Unknown` type.
    fn unchecked_argument_type_sentinel(&self) -> &TypeReference<Type> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.unchecked_argument_type_sentinel(),
            SDLSchema::InMemory(schema) => schema.unchecked_argument_type_sentinel(),
        }
    }

    fn snapshot_print(&self) -> String {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.snapshot_print(),
            SDLSchema::InMemory(schema) => schema.snapshot_print(),
        }
    }

    fn input_objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a InputObject> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.input_objects(),
            SDLSchema::InMemory(schema) => schema.input_objects(),
        }
    }

    fn enums<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Enum> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.enums(),
            SDLSchema::InMemory(schema) => schema.enums(),
        }
    }

    fn scalars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Scalar> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.scalars(),
            SDLSchema::InMemory(schema) => schema.scalars(),
        }
    }

    fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Field> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.fields(),
            SDLSchema::InMemory(schema) => schema.fields(),
        }
    }

    fn objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Object> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.objects(),
            SDLSchema::InMemory(schema) => schema.objects(),
        }
    }

    fn unions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Union> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.unions(),
            SDLSchema::InMemory(schema) => schema.unions(),
        }
    }

    fn interfaces<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Interface> + 'a> {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.interfaces(),
            SDLSchema::InMemory(schema) => schema.interfaces(),
        }
    }
}

impl SDLSchema {
    /// Creates an uninitialized, invalid schema which can then be added to using the add_*
    /// methods. Note that we still bake in some assumptions about the clientid and typename
    /// fields, but in practice this is not an issue.
    pub fn create_uninitialized() -> Self {
        SDLSchema::InMemory(InMemorySchema::create_uninitialized())
    }

    pub fn build(
        schema_documents: &[SchemaDocument],
        client_schema_documents: &[SchemaDocument],
    ) -> DiagnosticsResult<Self> {
        Ok(SDLSchema::InMemory(InMemorySchema::build(
            schema_documents,
            client_schema_documents,
        )?))
    }

    pub fn build_flatbuffer(
        schema_documents: &[SchemaDocument],
        client_schema_documents: &[SchemaDocument],
    ) -> DiagnosticsResult<Self> {
        let sdl_schema =
            crate::in_memory::InMemorySchema::build(schema_documents, client_schema_documents)?;
        let flatbuffer_bytes = crate::flatbuffer::serialize_as_flatbuffer(&sdl_schema);
        Ok(SDLSchema::FlatBuffer(SchemaWrapper::from_vec(
            flatbuffer_bytes,
        )))
    }

    pub fn add_object_type_extension(
        &mut self,
        object_extension: ObjectTypeExtension,
        location_key: SourceLocationKey,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => panic!("expected an underlying InMemorySchema"),
            SDLSchema::InMemory(schema) => {
                schema.add_object_type_extension(object_extension, location_key)
            }
        }
    }

    pub fn add_interface_type_extension(
        &mut self,
        interface_extension: InterfaceTypeExtension,
        location_key: SourceLocationKey,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => panic!("expected an underlying InMemorySchema"),
            SDLSchema::InMemory(schema) => {
                schema.add_interface_type_extension(interface_extension, location_key)
            }
        }
    }

    pub fn add_extension_scalar(
        &mut self,
        scalar: ScalarTypeDefinition,
        location_key: SourceLocationKey,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => panic!("expected an underlying InMemorySchema"),
            SDLSchema::InMemory(schema) => schema.add_extension_scalar(scalar, location_key),
        }
    }

    pub fn add_extension_object(
        &mut self,
        object: ObjectTypeDefinition,
        location_key: SourceLocationKey,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => panic!("expected an underlying InMemorySchema"),
            SDLSchema::InMemory(schema) => schema.add_extension_object(object, location_key),
        }
    }

    pub fn unwrap_in_memory_impl(self) -> InMemorySchema {
        match self {
            SDLSchema::FlatBuffer(_schema) => panic!("expected an underlying InMemorySchema"),
            SDLSchema::InMemory(schema) => schema,
        }
    }

    pub fn get_directive_mut(&mut self, name: DirectiveName) -> Option<&mut Directive> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_directive_mut(name),
        }
    }

    pub fn get_type_map(&self) -> impl Iterator<Item = (&StringKey, &Type)> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_type_map(),
        }
    }

    pub fn get_directives(&self) -> impl Iterator<Item = &Directive> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_directives(),
        }
    }

    /// Returns all directives applicable for a given location(Query, Field, etc).
    pub fn directives_for_location(&self, location: DirectiveLocation) -> Vec<&Directive> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.directives_for_location(location),
        }
    }

    pub fn get_fields(&self) -> impl Iterator<Item = &Field> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_fields(),
        }
    }

    pub fn get_interfaces(&self) -> impl Iterator<Item = &Interface> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_interfaces(),
        }
    }

    pub fn get_enums(&self) -> impl Iterator<Item = &Enum> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_enums(),
        }
    }

    pub fn get_objects(&self) -> impl Iterator<Item = &Object> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_objects(),
        }
    }

    pub fn get_unions(&self) -> impl Iterator<Item = &Union> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.get_unions(),
        }
    }

    pub fn has_directive(&self, directive_name: DirectiveName) -> bool {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.has_directive(directive_name),
        }
    }

    pub fn has_type(&self, type_name: StringKey) -> bool {
        match self {
            SDLSchema::FlatBuffer(schema) => schema.has_type(type_name),
            SDLSchema::InMemory(schema) => schema.has_type(type_name),
        }
    }

    pub fn add_directive(&mut self, directive: Directive) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_directive(directive),
        }
    }

    pub fn remove_directive(&mut self, directive_name: DirectiveName) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.remove_directive(directive_name),
        }
    }

    pub fn add_field(&mut self, field: Field) -> DiagnosticsResult<FieldID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_field(field),
        }
    }

    pub fn add_enum(&mut self, enum_: Enum) -> DiagnosticsResult<EnumID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_enum(enum_),
        }
    }

    pub fn add_input_object(
        &mut self,
        input_object: InputObject,
    ) -> DiagnosticsResult<InputObjectID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_input_object(input_object),
        }
    }

    pub fn add_interface(&mut self, interface: Interface) -> DiagnosticsResult<InterfaceID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_interface(interface),
        }
    }

    pub fn add_object(&mut self, object: Object) -> DiagnosticsResult<ObjectID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_object(object),
        }
    }

    pub fn add_scalar(&mut self, scalar: Scalar) -> DiagnosticsResult<ScalarID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_scalar(scalar),
        }
    }

    pub fn add_union(&mut self, union: Union) -> DiagnosticsResult<UnionID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_union(union),
        }
    }

    pub fn add_field_to_interface(
        &mut self,
        interface_id: InterfaceID,
        field_id: FieldID,
    ) -> DiagnosticsResult<InterfaceID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_field_to_interface(interface_id, field_id),
        }
    }

    pub fn add_field_to_object(
        &mut self,
        obj_id: ObjectID,
        field_id: FieldID,
    ) -> DiagnosticsResult<ObjectID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_field_to_object(obj_id, field_id),
        }
    }

    pub fn add_interface_to_object(
        &mut self,
        obj_id: ObjectID,
        interface_id: InterfaceID,
    ) -> DiagnosticsResult<ObjectID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_interface_to_object(obj_id, interface_id),
        }
    }

    pub fn add_parent_interface_to_interface(
        &mut self,
        interface_id: InterfaceID,
        parent_interface_id: InterfaceID,
    ) -> DiagnosticsResult<InterfaceID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => {
                schema.add_parent_interface_to_interface(interface_id, parent_interface_id)
            }
        }
    }

    pub fn add_implementing_object_to_interface(
        &mut self,
        interface_id: InterfaceID,
        object_id: ObjectID,
    ) -> DiagnosticsResult<InterfaceID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => {
                schema.add_implementing_object_to_interface(interface_id, object_id)
            }
        }
    }

    pub fn add_member_to_union(
        &mut self,
        union_id: UnionID,
        object_id: ObjectID,
    ) -> DiagnosticsResult<UnionID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.add_member_to_union(union_id, object_id),
        }
    }

    /// Sets argument definitions for a given input object.
    /// Any existing argument definitions will be erased.
    pub fn set_input_object_args(
        &mut self,
        input_object_id: InputObjectID,
        fields: ArgumentDefinitions,
    ) -> DiagnosticsResult<InputObjectID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.set_input_object_args(input_object_id, fields),
        }
    }

    /// Sets argument definitions for a given field.
    /// Any existing argument definitions on the field will be erased.
    pub fn set_field_args(
        &mut self,
        field_id: FieldID,
        args: ArgumentDefinitions,
    ) -> DiagnosticsResult<FieldID> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.set_field_args(field_id, args),
        }
    }

    /// Replaces the definition of interface type, but keeps the same id.
    /// Existing references to the old type now reference the replacement.
    pub fn replace_interface(
        &mut self,
        id: InterfaceID,
        interface: Interface,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_interface(id, interface),
        }
    }

    /// Replaces the definition of object type, but keeps the same id.
    /// Existing references to the old type now reference the replacement.
    pub fn replace_object(&mut self, id: ObjectID, object: Object) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_object(id, object),
        }
    }

    /// Replaces the definition of enum type, but keeps the same id.
    /// Existing references to the old type now reference the replacement.
    pub fn replace_enum(&mut self, id: EnumID, enum_: Enum) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_enum(id, enum_),
        }
    }

    /// Replaces the definition of input object type, but keeps the same id.
    /// Existing references to the old type now reference the replacement.
    pub fn replace_input_object(
        &mut self,
        id: InputObjectID,
        input_object: InputObject,
    ) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_input_object(id, input_object),
        }
    }

    /// Replaces the definition of union type, but keeps the same id.
    /// Existing references to the old type now reference the replacement.
    pub fn replace_union(&mut self, id: UnionID, union: Union) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_union(id, union),
        }
    }

    /// Replaces the definition of field, but keeps the same id.
    /// Existing references to the old field now reference the replacement.
    pub fn replace_field(&mut self, id: FieldID, field: Field) -> DiagnosticsResult<()> {
        match self {
            SDLSchema::FlatBuffer(_schema) => todo!(),
            SDLSchema::InMemory(schema) => schema.replace_field(id, field),
        }
    }
}


/*
 * 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 intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use strsim::damerau_levenshtein;

use crate::SDLSchema;
use crate::Schema;
use crate::Type;

/// Computes the lexical distance between strings A and B.
///
/// The "distance" between two strings is given by counting the minimum number
/// of edits needed to transform string A into string B. An edit can be an
/// insertion, deletion, or substitution of a single character, or a swap of two
/// adjacent characters.
///
/// Includes a custom alteration from Damerau-Levenshtein to treat case changes
/// as a single edit which helps identify mis-cased values with an edit distance
/// of 1.
///
/// This distance can be useful for detecting typos in input or sorting
struct LexicalDistance<'a> {
    input: &'a str,
    input_lower_case: String,
}

impl<'a> LexicalDistance<'a> {
    pub fn new(input: &'a str) -> Self {
        Self {
            input,
            input_lower_case: input.to_lowercase(),
        }
    }

    pub fn measure(&self, option: &str, threshold: usize) -> Option<usize> {
        if self.input == option {
            return Some(0);
        }

        let option_lower_case = option.to_lowercase();

        // Any case change counts as a single edit
        if self.input_lower_case == option_lower_case {
            return Some(1);
        }

        let distance = damerau_levenshtein(self.input, option);
        if distance <= threshold {
            Some(distance)
        } else {
            None
        }
    }
}

/// Generate the list of the suggested values that are closest to the `input`
pub fn suggestion_list(input: StringKey, options: &[StringKey], limit: usize) -> Vec<StringKey> {
    let lexical_distance = LexicalDistance::new(input.lookup());

    // This is from graphql-js (https://github.com/graphql/graphql-js/commit/4c10844f6f2ab3e2993d8d5f5f3ed97dce9d3655)
    // The original idea is from TypeScript: https://github.com/microsoft/TypeScript/blob/afddaf090ad8e3a4d3501ce91490b5552d1e4a42/src/compiler/core.ts#L1752
    let threshold = (input.lookup().len() as f32 * 0.4).floor() + 1.0;

    let mut options_with_distance: Vec<(StringKey, usize)> = options
        .iter()
        .filter_map(|option| {
            let distance = lexical_distance.measure(option.lookup(), threshold as usize)?;
            Some((*option, distance))
        })
        .collect();

    options_with_distance.sort_by(|(str_a, distance_a), (str_b, distance_b)| {
        if distance_a == distance_b {
            str_a.lookup().cmp(str_b.lookup())
        } else {
            distance_a.cmp(distance_b)
        }
    });

    options_with_distance
        .iter()
        .take(limit)
        .map(|(value, _)| *value)
        .collect::<Vec<StringKey>>()
}

pub struct GraphQLSuggestions<'schema> {
    schema: &'schema SDLSchema,
    enabled: bool,
}

impl<'schema> GraphQLSuggestions<'schema> {
    const MAX_SUGGESTIONS: usize = 5;

    pub fn new(schema: &'schema SDLSchema) -> Self {
        // If the schema is flatten schema, at this time, we need to temporary disable
        // the GraphQLSuggestions as `get_type_map` in flattenn schema is not yet implemented.
        let enabled = !matches!(schema, SDLSchema::FlatBuffer(_));

        Self { schema, enabled }
    }

    pub fn input_type_suggestions(&self, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let input_types = self
            .schema
            .get_type_map()
            .filter_map(|(type_name, type_)| {
                if type_.is_input_type() {
                    Some(*type_name)
                } else {
                    None
                }
            })
            .collect::<Vec<StringKey>>();

        suggestion_list(input, &input_types, GraphQLSuggestions::MAX_SUGGESTIONS)
    }

    pub fn output_type_suggestions(&self, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let type_names = self
            .schema
            .get_type_map()
            .filter_map(|(type_name, type_)| {
                if !type_.is_input_object() {
                    Some(*type_name)
                } else {
                    None
                }
            })
            .collect::<Vec<StringKey>>();

        suggestion_list(input, &type_names, GraphQLSuggestions::MAX_SUGGESTIONS)
    }

    pub fn composite_type_suggestions(&self, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let type_names = self
            .schema
            .get_type_map()
            .filter_map(|(type_name, type_)| {
                if type_.is_composite_type() {
                    Some(*type_name)
                } else {
                    None
                }
            })
            .collect::<Vec<StringKey>>();

        suggestion_list(input, &type_names, GraphQLSuggestions::MAX_SUGGESTIONS)
    }

    pub fn object_type_suggestions(&self, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let type_names = self
            .schema
            .get_type_map()
            .filter_map(|(type_name, type_)| {
                if type_.is_object() {
                    Some(*type_name)
                } else {
                    None
                }
            })
            .collect::<Vec<StringKey>>();

        suggestion_list(input, &type_names, GraphQLSuggestions::MAX_SUGGESTIONS)
    }

    pub fn interface_type_suggestions(&self, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let type_names = self
            .schema
            .get_type_map()
            .filter_map(|(type_name, type_)| {
                if type_.is_interface() {
                    Some(*type_name)
                } else {
                    None
                }
            })
            .collect::<Vec<StringKey>>();

        suggestion_list(input, &type_names, GraphQLSuggestions::MAX_SUGGESTIONS)
    }

    pub fn field_name_suggestion(&self, type_: Option<Type>, input: StringKey) -> Vec<StringKey> {
        if !self.enabled {
            return Vec::new();
        }

        let field_names: Vec<StringKey> = match type_ {
            Some(Type::Object(object_id)) => self
                .schema
                .object(object_id)
                .fields
                .iter()
                .map(|field_id| self.schema.field(*field_id).name.item)
                .collect(),
            Some(Type::Interface(interface_id)) => self
                .schema
                .interface(interface_id)
                .fields
                .iter()
                .map(|field_id| self.schema.field(*field_id).name.item)
                .collect(),
            Some(Type::InputObject(input_id)) => self
                .schema
                .input_object(input_id)
                .fields
                .iter()
                .map(|arg| arg.name.0)
                .collect(),
            _ => vec![],
        };

        suggestion_list(input, &field_names, GraphQLSuggestions::MAX_SUGGESTIONS)
    }
}

/// Given [ A, B, C ] return ' Did you mean A, B, or C?'.
pub fn did_you_mean(suggestions: &[StringKey]) -> String {
    if suggestions.is_empty() {
        return "".to_string();
    }

    let suggestions_string = match suggestions.len() {
        1 => format!("`{}`", suggestions[0].lookup()),
        2 => format!("`{}` or `{}`", suggestions[0], suggestions[1]),
        _ => {
            let mut suggestions = suggestions.to_vec();
            let last_option = suggestions.pop();

            format!(
                "{}, or `{}`",
                suggestions
                    .iter()
                    .map(|suggestion| format!("`{}`", suggestion.lookup()))
                    .collect::<Vec<String>>()
                    .join(", "),
                last_option.unwrap_or_else(|| "".intern())
            )
        }
    };

    format!(" Did you mean {}?", suggestions_string)
}

#[cfg(test)]
mod tests {
    use intern::string_key::Intern;

    use super::suggestion_list;

    #[test]
    fn test_suggestion_list_empty_input() {
        assert_eq!(
            suggestion_list("".intern(), &["a".intern()], 5),
            vec!["a".intern()]
        );
    }

    #[test]
    fn test_suggestion_no_options() {
        assert_eq!(suggestion_list("a".intern(), &[], 5), vec![]);
    }

    #[test]
    fn test_suggestion_small_lexical_distance() {
        assert_eq!(
            suggestion_list("greenish".intern(), &["green".intern()], 5),
            vec!["green".intern()]
        );
        assert_eq!(
            suggestion_list("green".intern(), &["greenish".intern()], 5),
            vec!["greenish".intern()]
        );
    }

    #[test]
    fn test_suggestion_reject_objects_with_long_distance() {
        assert_eq!(
            suggestion_list("aaaa".intern(), &["aaab".intern()], 5),
            vec!["aaab".intern()]
        );
        assert_eq!(
            suggestion_list("aaaa".intern(), &["aabb".intern()], 5),
            vec!["aabb".intern()]
        );
        assert_eq!(
            suggestion_list("aaaa".intern(), &["abbb".intern()], 5),
            vec![]
        );
        assert_eq!(suggestion_list("ab".intern(), &["ca".intern()], 5), vec![]);
    }

    #[test]
    fn test_suggestion_different_case() {
        assert_eq!(
            suggestion_list("verylongstring".intern(), &["VERYLONGSTRING".intern()], 5),
            vec!["VERYLONGSTRING".intern()]
        );
        assert_eq!(
            suggestion_list("VERYLONGSTRING".intern(), &["verylongstring".intern()], 5),
            vec!["verylongstring".intern()]
        );
        assert_eq!(
            suggestion_list("VERYLONGSTRING".intern(), &["VeryLongString".intern()], 5),
            vec!["VeryLongString".intern()]
        );
    }

    #[test]
    fn test_suggestion_with_transpositions() {
        assert_eq!(
            suggestion_list("agr".intern(), &["arg".intern()], 5),
            vec!["arg".intern()]
        );
        assert_eq!(
            suggestion_list("214365879".intern(), &["123456789".intern()], 5),
            vec!["123456789".intern()]
        );
    }

    #[test]
    fn test_suggestion_sorted() {
        assert_eq!(
            suggestion_list(
                "abc".intern(),
                &["a".intern(), "ab".intern(), "abc".intern()],
                5
            ),
            vec!["abc".intern(), "ab".intern(), "a".intern()]
        );

        assert_eq!(
            suggestion_list(
                "a".intern(),
                &["az".intern(), "ax".intern(), "ay".intern()],
                5
            ),
            vec!["ax".intern(), "ay".intern(), "az".intern()]
        );
    }

    #[test]
    fn test_suggestions_with_user() {
        assert_eq!(
            suggestion_list(
                "Users".intern(),
                &["User".intern(), "Query".intern(), "Mutation".intern()],
                5
            ),
            vec!["User".intern(), "Query".intern()]
        );
    }
}


/*
 * 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::HashSet;

use common::InterfaceName;
use common::WithLocation;
use intern::string_key::StringKey;

use crate::DirectiveValue;
use crate::FieldID;
use crate::InterfaceID;
use crate::ObjectID;
use crate::Schema;

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Interface {
    pub name: WithLocation<InterfaceName>,
    pub is_extension: bool,
    pub implementing_interfaces: Vec<InterfaceID>,
    pub implementing_objects: Vec<ObjectID>,
    pub fields: Vec<FieldID>,
    pub directives: Vec<DirectiveValue>,
    pub interfaces: Vec<InterfaceID>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

impl Interface {
    /// Return all objects that implement, directly or recursively, a given interface.
    /// The iteration order of the HashSet might depend on the order in which schema files
    /// are processed. It should not be relied upon when generating artifacts.
    pub fn recursively_implementing_objects(&self, schema: &impl Schema) -> HashSet<ObjectID> {
        // Note: we do not have the InterfaceID of self. This is awkward, and means that we cannot
        // prevent the loop below from visiting self if there is a recursive relationship
        // (e.g. in which InterfaceA implements InterfaceB, which implements InterfaceA).
        // This is [disallowed in the spec](https://spec.graphql.org/October2021/#sel-FAHbhBLCAACEkBq4P).
        //
        // However, even if we do, this is not a problem, because we're creating a HashSet, and
        // inserting the same item into a HashSet twice is a no-op, which is all that would happen.
        //
        // We do prevent infinite recursion, though.
        let mut encountered_interfaces: HashSet<InterfaceID> =
            HashSet::with_capacity(self.implementing_interfaces.len());

        let mut implementing_objects =
            HashSet::from_iter(self.implementing_objects.iter().copied());

        let mut interface_queue = self.implementing_interfaces.iter().collect::<Vec<_>>();
        while let Some(interface_id) = interface_queue.pop() {
            if !encountered_interfaces.contains(interface_id) {
                encountered_interfaces.insert(*interface_id);

                let interface = schema.interface(*interface_id);

                implementing_objects.extend(interface.implementing_objects.iter().copied());
                interface_queue.extend(interface.implementing_interfaces.iter());
            }
        }

        implementing_objects
    }
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use std::collections::HashSet;
    use std::sync::Arc;

    use common::InterfaceName;
    use common::WithLocation;
    use intern::string_key::Intern;

    use crate::Interface;
    use crate::InterfaceID;
    use crate::ObjectID;
    use crate::Schema;

    struct InterfaceOnlySchema {
        interface_map: HashMap<InterfaceID, Arc<Interface>>,
    }
    #[allow(unused_variables)]
    impl Schema for InterfaceOnlySchema {
        fn query_type(&self) -> Option<crate::Type> {
            unimplemented!()
        }

        fn mutation_type(&self) -> Option<crate::Type> {
            unimplemented!()
        }

        fn subscription_type(&self) -> Option<crate::Type> {
            unimplemented!()
        }

        fn clientid_field(&self) -> crate::FieldID {
            unimplemented!()
        }

        fn strongid_field(&self) -> crate::FieldID {
            unimplemented!()
        }

        fn typename_field(&self) -> crate::FieldID {
            unimplemented!()
        }

        fn fetch_token_field(&self) -> crate::FieldID {
            unimplemented!()
        }

        fn is_fulfilled_field(&self) -> crate::FieldID {
            unimplemented!()
        }

        fn get_type(&self, type_name: intern::string_key::StringKey) -> Option<crate::Type> {
            unimplemented!()
        }

        fn get_directive(&self, name: common::DirectiveName) -> Option<&crate::Directive> {
            unimplemented!()
        }

        fn input_object(&self, id: crate::InputObjectID) -> &crate::InputObject {
            unimplemented!()
        }

        fn input_objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::InputObject> + 'a> {
            unimplemented!()
        }

        fn enum_(&self, id: crate::EnumID) -> &crate::Enum {
            unimplemented!()
        }

        fn enums<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Enum> + 'a> {
            unimplemented!()
        }

        fn scalar(&self, id: crate::ScalarID) -> &crate::Scalar {
            unimplemented!()
        }

        fn scalars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Scalar> + 'a> {
            unimplemented!()
        }

        fn field(&self, id: crate::FieldID) -> &crate::Field {
            unimplemented!()
        }

        fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Field> + 'a> {
            unimplemented!()
        }

        fn object(&self, id: crate::ObjectID) -> &crate::Object {
            unimplemented!()
        }

        fn objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Object> + 'a> {
            unimplemented!()
        }

        fn union(&self, id: crate::UnionID) -> &crate::Union {
            unimplemented!()
        }

        fn unions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Union> + 'a> {
            unimplemented!()
        }

        fn interface(&self, id: crate::InterfaceID) -> &crate::Interface {
            self.interface_map.get(&id).as_ref().unwrap()
        }

        fn interfaces<'a>(&'a self) -> Box<dyn Iterator<Item = &'a crate::Interface> + 'a> {
            unimplemented!()
        }

        fn get_type_name(&self, type_: crate::Type) -> intern::string_key::StringKey {
            unimplemented!()
        }

        fn is_extension_type(&self, type_: crate::Type) -> bool {
            unimplemented!()
        }

        fn is_string(&self, type_: crate::Type) -> bool {
            unimplemented!()
        }

        fn is_id(&self, type_: crate::Type) -> bool {
            unimplemented!()
        }

        fn named_field(
            &self,
            parent_type: crate::Type,
            name: intern::string_key::StringKey,
        ) -> Option<crate::FieldID> {
            unimplemented!()
        }

        fn unchecked_argument_type_sentinel(&self) -> &crate::TypeReference<crate::Type> {
            unimplemented!()
        }

        fn snapshot_print(&self) -> String {
            unimplemented!()
        }
    }

    fn with_objects_and_interfaces(
        implementing_objects: Vec<ObjectID>,
        implementing_interfaces: Vec<InterfaceID>,
    ) -> Interface {
        Interface {
            name: WithLocation::generated(InterfaceName("AnInterface".intern())),
            is_extension: false,
            implementing_interfaces,
            implementing_objects,
            fields: vec![],
            directives: vec![],
            interfaces: vec![],
            description: None,
            hack_source: None,
        }
    }

    /// Test a basic case, in which IBase is implemented by
    /// INestA1 and INestB. INestA1 is implemented by INestA2.
    ///
    /// There is a mix of overlapping and non-overlapping ObjectIDs.
    #[test]
    fn basic_recursively_implementing_objects() {
        let i_nest_a2_id = InterfaceID(0);
        let i_nest_a2 =
            with_objects_and_interfaces(vec![ObjectID(0), ObjectID(1), ObjectID(2)], vec![]);

        let i_nest_a1_id = InterfaceID(1);
        let i_nest_a1 = with_objects_and_interfaces(
            vec![ObjectID(1), ObjectID(3), ObjectID(4)],
            vec![i_nest_a2_id],
        );

        let i_nest_b_id = InterfaceID(2);
        let i_nest_b =
            with_objects_and_interfaces(vec![ObjectID(4), ObjectID(5), ObjectID(6)], vec![]);

        let i_base_id = InterfaceID(3);
        let i_base = with_objects_and_interfaces(
            vec![ObjectID(6), ObjectID(2), ObjectID(5), ObjectID(7)],
            vec![i_nest_a1_id, i_nest_b_id],
        );

        let schema = InterfaceOnlySchema {
            interface_map: {
                let mut map = HashMap::new();
                map.insert(i_nest_a2_id, Arc::new(i_nest_a2));
                map.insert(i_nest_a1_id, Arc::new(i_nest_a1));
                map.insert(i_nest_b_id, Arc::new(i_nest_b));
                map.insert(i_base_id, Arc::new(i_base));
                map
            },
        };

        let expected_object_ids = {
            let mut set = HashSet::with_capacity(8);
            for x in 0..=7 {
                set.insert(ObjectID(x));
            }
            set
        };
        assert!(
            schema
                .interface(i_base_id)
                .recursively_implementing_objects(&schema)
                == expected_object_ids
        );
    }

    /// Test the case where IBase implements IBase
    #[test]
    fn recursively_implementing_objects_direct_cycle() {
        let i_base_id = InterfaceID(0);

        // The graphql spec disallows this, but we don't prevent it.
        let i_base = with_objects_and_interfaces(vec![ObjectID(0)], vec![i_base_id]);

        let schema = InterfaceOnlySchema {
            interface_map: {
                let mut map = HashMap::with_capacity(1);
                map.insert(i_base_id, Arc::new(i_base));
                map
            },
        };

        let expected_object_ids = {
            let mut set = HashSet::new();
            set.insert(ObjectID(0));
            set
        };

        assert!(
            schema
                .interface(i_base_id)
                .recursively_implementing_objects(&schema)
                == expected_object_ids
        );
    }

    /// Test the case where IBase implements INest which implements IBase
    #[test]
    fn recursively_implementing_objects_indirect_cycle() {
        let i_base_id = InterfaceID(0);
        let i_nest_id = InterfaceID(1);

        let i_base = with_objects_and_interfaces(vec![ObjectID(0)], vec![i_nest_id]);
        let i_nest = with_objects_and_interfaces(vec![ObjectID(1)], vec![i_base_id]);

        let schema = InterfaceOnlySchema {
            interface_map: {
                let mut map = HashMap::with_capacity(1);
                map.insert(i_base_id, Arc::new(i_base));
                map.insert(i_nest_id, Arc::new(i_nest));
                map
            },
        };

        let expected_object_ids = {
            let mut set = HashSet::new();
            set.insert(ObjectID(0));
            set.insert(ObjectID(1));
            set
        };

        assert!(
            schema
                .interface(i_base_id)
                .recursively_implementing_objects(&schema)
                == expected_object_ids
        );
    }
}


/*
 * 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 interface;

use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;
use std::slice::Iter;

use common::ArgumentName;
use common::DirectiveName;
use common::EnumName;
use common::InputObjectName;
use common::InterfaceName;
use common::Named;
use common::NamedItem;
use common::ObjectName;
use common::ScalarName;
use common::WithLocation;
use graphql_syntax::ConstantValue;
use graphql_syntax::DirectiveLocation;
pub use interface::*;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;

use crate::Schema;

lazy_static! {
    static ref DIRECTIVE_DEPRECATED: DirectiveName = DirectiveName("deprecated".intern());
    static ref ARGUMENT_REASON: ArgumentName = ArgumentName("reason".intern());
}

pub(crate) type TypeMap = HashMap<StringKey, Type>;

macro_rules! type_id {
    ($name:ident, $type:ident) => {
        #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
        pub struct $name(pub $type);
        impl $name {
            pub(crate) fn as_usize(&self) -> usize {
                self.0 as usize
            }
        }

        impl fmt::Debug for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, "{}({})", stringify!($name), self.0)
            }
        }

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

type_id!(EnumID, u32);
type_id!(InputObjectID, u32);
type_id!(InterfaceID, u32);
type_id!(ObjectID, u32);
type_id!(ScalarID, u32);
type_id!(UnionID, u32);
type_id!(FieldID, u32);

#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Type {
    Enum(EnumID),
    InputObject(InputObjectID),
    Interface(InterfaceID),
    Object(ObjectID),
    Scalar(ScalarID),
    Union(UnionID),
}

impl Type {
    pub fn is_scalar(self) -> bool {
        matches!(self, Type::Scalar(_))
    }

    pub fn is_enum(self) -> bool {
        matches!(self, Type::Enum(_))
    }

    pub fn is_input_type(self) -> bool {
        matches!(self, Type::Scalar(_) | Type::Enum(_) | Type::InputObject(_))
    }

    pub fn is_abstract_type(self) -> bool {
        matches!(self, Type::Union(_) | Type::Interface(_))
    }

    pub fn is_composite_type(self) -> bool {
        matches!(self, Type::Object(_) | Type::Interface(_) | Type::Union(_))
    }

    pub fn is_object(self) -> bool {
        matches!(self, Type::Object(_))
    }

    pub fn is_input_object(self) -> bool {
        matches!(self, Type::InputObject(_))
    }

    pub fn is_interface(self) -> bool {
        matches!(self, Type::Interface(_))
    }

    pub fn is_object_or_interface(self) -> bool {
        matches!(self, Type::Object(_) | Type::Interface(_))
    }

    pub fn is_union(self) -> bool {
        matches!(self, Type::Union(_))
    }

    pub fn is_root_type<S: Schema>(&self, schema: &S) -> bool {
        Some(*self) == schema.query_type()
            || Some(*self) == schema.mutation_type()
            || Some(*self) == schema.subscription_type()
    }

    pub fn get_enum_id(self) -> Option<EnumID> {
        match self {
            Type::Enum(id) => Some(id),
            _ => None,
        }
    }

    pub fn get_input_object_id(self) -> Option<InputObjectID> {
        match self {
            Type::InputObject(id) => Some(id),
            _ => None,
        }
    }

    pub fn get_interface_id(self) -> Option<InterfaceID> {
        match self {
            Type::Interface(id) => Some(id),
            _ => None,
        }
    }

    pub fn get_object_id(self) -> Option<ObjectID> {
        match self {
            Type::Object(id) => Some(id),
            _ => None,
        }
    }

    pub fn get_scalar_id(self) -> Option<ScalarID> {
        match self {
            Type::Scalar(id) => Some(id),
            _ => None,
        }
    }

    pub fn get_union_id(self) -> Option<UnionID> {
        match self {
            Type::Union(id) => Some(id),
            _ => None,
        }
    }
}

impl fmt::Debug for Type {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Type::Enum(id) => f.write_fmt(format_args!("Enum({:?})", id.0)),
            Type::InputObject(id) => f.write_fmt(format_args!("InputObject({:?})", id.0)),
            Type::Interface(id) => f.write_fmt(format_args!("Interface({:?})", id.0)),
            Type::Object(id) => f.write_fmt(format_args!("Object({:?})", id.0)),
            Type::Scalar(id) => f.write_fmt(format_args!("Scalar({:?})", id.0)),
            Type::Union(id) => f.write_fmt(format_args!("Union({:?})", id.0)),
        }
    }
}

impl Type {
    pub fn get_variant_name(&self) -> &'static str {
        match self {
            Type::Enum(_) => "an enum",
            Type::InputObject(_) => "an input object",
            Type::Interface(_) => "an interface",
            Type::Object(_) => "an object",
            Type::Scalar(_) => "a scalar",
            Type::Union(_) => "a union",
        }
    }
}

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

impl<T: Copy> TypeReference<T> {
    pub fn inner(&self) -> T {
        match self {
            TypeReference::Named(type_) => *type_,
            TypeReference::List(of) => of.inner(),
            TypeReference::NonNull(of) => of.inner(),
        }
    }

    pub fn non_null(&self) -> TypeReference<T> {
        match self {
            TypeReference::Named(_) => TypeReference::NonNull(Box::new(self.clone())),
            TypeReference::List(_) => TypeReference::NonNull(Box::new(self.clone())),
            TypeReference::NonNull(_) => self.clone(),
        }
    }

    // If the type is Named or NonNull<Named> return the inner named.
    // If the type is a List or NonNull<List> returns a matching list with nullable items.
    pub fn with_nullable_item_type(&self) -> TypeReference<T> {
        match self {
            TypeReference::Named(_) => self.clone(),
            TypeReference::List(of) => TypeReference::List(Box::new(of.nullable_type().clone())),
            TypeReference::NonNull(of) => {
                let inner: &TypeReference<T> = of;
                match inner {
                    TypeReference::List(_) => {
                        TypeReference::NonNull(Box::new(of.with_nullable_item_type()))
                    }
                    TypeReference::Named(_) => inner.clone(),
                    TypeReference::NonNull(_) => {
                        unreachable!("Invalid nested TypeReference::NonNull")
                    }
                }
            }
        }
    }

    // Return None if the type is a List, otherwise return the inner type
    pub fn non_list_type(&self) -> Option<T> {
        match self {
            TypeReference::List(_) => None,
            TypeReference::Named(type_) => Some(*type_),
            TypeReference::NonNull(of) => of.non_list_type(),
        }
    }
}

impl<T> TypeReference<T> {
    pub fn map<U>(self, transform: impl FnOnce(T) -> U) -> TypeReference<U> {
        match self {
            TypeReference::Named(inner) => TypeReference::Named(transform(inner)),
            TypeReference::NonNull(inner) => TypeReference::NonNull(Box::new(inner.map(transform))),
            TypeReference::List(inner) => TypeReference::List(Box::new(inner.map(transform))),
        }
    }

    pub fn as_ref(&self) -> TypeReference<&T> {
        match self {
            TypeReference::Named(inner) => TypeReference::Named(inner),
            TypeReference::NonNull(inner) => {
                TypeReference::NonNull(Box::new(Box::as_ref(inner).as_ref()))
            }
            TypeReference::List(inner) => {
                TypeReference::List(Box::new(Box::as_ref(inner).as_ref()))
            }
        }
    }

    pub fn nullable_type(&self) -> &TypeReference<T> {
        match self {
            TypeReference::Named(_) => self,
            TypeReference::List(_) => self,
            TypeReference::NonNull(of) => of,
        }
    }

    pub fn is_non_null(&self) -> bool {
        matches!(self, TypeReference::NonNull(_))
    }

    pub fn is_list(&self) -> bool {
        matches!(self.nullable_type(), TypeReference::List(_))
    }

    pub fn list_item_type(&self) -> Option<&TypeReference<T>> {
        match self.nullable_type() {
            TypeReference::List(of) => Some(of),
            _ => None,
        }
    }
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Directive {
    pub name: DirectiveName,
    pub arguments: ArgumentDefinitions,
    pub locations: Vec<DirectiveLocation>,
    pub repeatable: bool,
    pub is_extension: bool,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

impl Named for Directive {
    type Name = DirectiveName;
    fn name(&self) -> DirectiveName {
        self.name
    }
}

#[derive(Clone, Debug)]
pub struct Scalar {
    pub name: WithLocation<ScalarName>,
    pub is_extension: bool,
    pub directives: Vec<DirectiveValue>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Object {
    pub name: WithLocation<ObjectName>,
    pub is_extension: bool,
    pub fields: Vec<FieldID>,
    pub interfaces: Vec<InterfaceID>,
    pub directives: Vec<DirectiveValue>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct InputObject {
    pub name: WithLocation<InputObjectName>,
    pub fields: ArgumentDefinitions,
    pub directives: Vec<DirectiveValue>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Enum {
    pub name: WithLocation<EnumName>,
    pub is_extension: bool,
    pub values: Vec<EnumValue>,
    pub directives: Vec<DirectiveValue>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Union {
    pub name: WithLocation<StringKey>,
    pub is_extension: bool,
    pub members: Vec<ObjectID>,
    pub directives: Vec<DirectiveValue>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct Field {
    pub name: WithLocation<StringKey>,
    pub is_extension: bool,
    pub arguments: ArgumentDefinitions,
    pub type_: TypeReference<Type>,
    pub directives: Vec<DirectiveValue>,
    /// The type on which this field was defined. This field is (should)
    /// always be set, except for special fields such as __typename and
    /// __id, which are queryable on all types and therefore don't have
    /// a single parent type.
    pub parent_type: Option<Type>,
    pub description: Option<StringKey>,
    pub hack_source: Option<StringKey>,
}

pub struct Deprecation {
    pub reason: Option<StringKey>,
}

impl Field {
    pub fn deprecated(&self) -> Option<Deprecation> {
        self.directives
            .named(*DIRECTIVE_DEPRECATED)
            .map(|directive| Deprecation {
                reason: directive
                    .arguments
                    .named(*ARGUMENT_REASON)
                    .and_then(|reason| reason.value.get_string_literal()),
            })
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct Argument {
    pub name: ArgumentName,
    pub type_: TypeReference<Type>,
    pub default_value: Option<ConstantValue>,
    pub description: Option<StringKey>,
    pub directives: Vec<DirectiveValue>,
}

impl Argument {
    pub fn deprecated(&self) -> Option<Deprecation> {
        self.directives
            .named(*DIRECTIVE_DEPRECATED)
            .map(|directive| Deprecation {
                reason: directive
                    .arguments
                    .named(*ARGUMENT_REASON)
                    .and_then(|reason| reason.value.get_string_literal()),
            })
    }
}

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

#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct ArgumentValue {
    pub name: ArgumentName,
    pub value: ConstantValue,
}

impl ArgumentValue {
    /// If the value is a constant string literal, return the value, otherwise None.
    pub fn get_string_literal(&self) -> Option<StringKey> {
        if let ConstantValue::String(string_node) = &self.value {
            Some(string_node.value)
        } else {
            None
        }
    }
    /// Return the constant string literal of this value.
    /// Panics if the value is not a constant string literal.
    pub fn expect_string_literal(&self) -> StringKey {
        self.get_string_literal().unwrap_or_else(|| {
            panic!("expected a string literal, got {:?}", self);
        })
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
pub struct DirectiveValue {
    pub name: DirectiveName,
    pub arguments: Vec<ArgumentValue>,
}

#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct EnumValue {
    pub value: StringKey,
    pub directives: Vec<DirectiveValue>,
}

#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ArgumentDefinitions(pub(crate) Vec<Argument>);

impl ArgumentDefinitions {
    pub fn new(arguments: Vec<Argument>) -> Self {
        Self(arguments)
    }

    pub fn named(&self, name: ArgumentName) -> Option<&Argument> {
        self.0.named(name)
    }

    pub fn contains(&self, name: StringKey) -> bool {
        self.0.iter().any(|x| x.name == ArgumentName(name))
    }

    pub fn iter(&self) -> Iter<'_, Argument> {
        self.0.iter()
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl fmt::Debug for ArgumentDefinitions {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("{:#?}", self.0))
    }
}

impl IntoIterator for ArgumentDefinitions {
    type Item = Argument;
    type IntoIter = std::vec::IntoIter<Self::Item>;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

pub trait TypeWithFields {
    fn fields(&self) -> &Vec<FieldID>;
    fn interfaces(&self) -> &Vec<InterfaceID>;
}

impl TypeWithFields for Interface {
    fn fields(&self) -> &Vec<FieldID> {
        &self.fields
    }

    fn interfaces(&self) -> &Vec<InterfaceID> {
        &self.interfaces
    }
}

impl TypeWithFields for Object {
    fn fields(&self) -> &Vec<FieldID> {
        &self.fields
    }

    fn interfaces(&self) -> &Vec<InterfaceID> {
        &self.interfaces
    }
}

#[allow(unused_macros)]
macro_rules! impl_named {
    ($type_name:ident) => {
        impl Named for $type_name {
            type Name = StringKey;
            fn name(&self) -> StringKey {
                self.name
            }
        }
    };
}

macro_rules! impl_named_for_with_location {
    ($type_name:ident, $name_type:ident) => {
        impl Named for $type_name {
            type Name = $name_type;
            fn name(&self) -> $name_type {
                self.name.item
            }
        }
    };
}

impl_named_for_with_location!(Object, ObjectName);
impl_named_for_with_location!(Field, StringKey);
impl_named_for_with_location!(InputObject, InputObjectName);
impl_named_for_with_location!(Interface, InterfaceName);
impl_named_for_with_location!(Union, StringKey);
impl_named_for_with_location!(Scalar, ScalarName);
impl_named_for_with_location!(Enum, EnumName);

impl Named for DirectiveValue {
    type Name = DirectiveName;
    fn name(&self) -> DirectiveName {
        self.name
    }
}

impl Named for ArgumentValue {
    type Name = ArgumentName;
    fn name(&self) -> ArgumentName {
        self.name
    }
}