/*
 * 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 serialize;
mod wrapper;

use std::cmp::Ordering;

use common::ArgumentName;
use common::DirectiveName;
use common::EnumName;
use common::InputObjectName;
use common::InterfaceName;
use common::ObjectName;
use common::ScalarName;
use common::Span;
use common::WithLocation;
use flatbuffers::ForwardsUOffset;
use flatbuffers::Vector;
use graphql_syntax::BooleanNode;
use graphql_syntax::ConstantArgument;
use graphql_syntax::ConstantValue;
use graphql_syntax::DirectiveLocation;
use graphql_syntax::EnumNode;
use graphql_syntax::FloatNode;
use graphql_syntax::FloatValue;
use graphql_syntax::Identifier;
use graphql_syntax::IntNode;
use graphql_syntax::List;
use graphql_syntax::StringNode;
use graphql_syntax::Token;
use graphql_syntax::TokenKind;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
pub use serialize::serialize_as_flatbuffer;
pub use wrapper::SchemaWrapper;

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

#[derive(Debug)]
pub struct FlatBufferSchema<'fb> {
    query_type: Type,
    mutation_type: Option<Type>,
    subscription_type: Option<Type>,
    directives: Vector<'fb, ForwardsUOffset<schema_flatbuffer::DirectiveMapEntry<'fb>>>,
    enums: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Enum<'fb>>>,
    fields: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Field<'fb>>>,
    input_objects: Vector<'fb, ForwardsUOffset<schema_flatbuffer::InputObject<'fb>>>,
    interfaces: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Interface<'fb>>>,
    objects: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Object<'fb>>>,
    scalars: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Scalar<'fb>>>,
    types: Vector<'fb, ForwardsUOffset<schema_flatbuffer::TypeMapEntry<'fb>>>,
    unions: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Union<'fb>>>,
}

impl<'fb> FlatBufferSchema<'fb> {
    pub fn build(bytes: &'fb [u8]) -> Self {
        #![allow(deprecated)]
        let fb_schema: schema_flatbuffer::Schema<'fb> =
            schema_flatbuffer::get_root_as_schema(bytes);

        let query_type = Type::Object(ObjectID(fb_schema.query_type()));
        let mutation_type = fb_schema
            .has_mutation_type()
            .then(|| Type::Object(ObjectID(fb_schema.mutation_type())));
        let subscription_type = fb_schema
            .has_subscription_type()
            .then(|| Type::Object(ObjectID(fb_schema.subscription_type())));

        Self {
            query_type,
            mutation_type,
            subscription_type,
            directives: fb_schema.directives(),
            enums: fb_schema.enums(),
            fields: fb_schema.fields(),
            input_objects: fb_schema.input_objects(),
            interfaces: fb_schema.interfaces(),
            objects: fb_schema.objects(),
            scalars: fb_schema.scalars(),
            types: fb_schema.types(),
            unions: fb_schema.unions(),
        }
    }

    pub fn query_type(&self) -> Type {
        self.query_type
    }

    pub fn mutation_type(&self) -> Option<Type> {
        self.mutation_type
    }

    pub fn subscription_type(&self) -> Option<Type> {
        self.subscription_type
    }

    pub fn get_type(&self, type_name: StringKey) -> Option<Type> {
        self.read_type(type_name)
    }

    pub fn has_type(&self, type_name: StringKey) -> bool {
        self.get_type(type_name).is_some()
    }

    pub fn get_directive(&self, directive_name: DirectiveName) -> Option<Directive> {
        self.read_directive(directive_name)
    }

    pub fn input_object(&self, id: InputObjectID) -> InputObject {
        self.parse_input_object(id).unwrap()
    }

    pub fn enum_(&self, id: EnumID) -> Enum {
        self.parse_enum(id).unwrap()
    }

    pub fn scalar(&self, id: ScalarID) -> Scalar {
        self.parse_scalar(id).unwrap()
    }

    pub fn field(&self, id: FieldID) -> Field {
        self.parse_field(id).unwrap()
    }

    pub fn object(&self, id: ObjectID) -> Object {
        self.parse_object(id).unwrap()
    }

    pub fn union(&self, id: UnionID) -> Union {
        self.parse_union(id).unwrap()
    }

    pub fn interface(&self, id: InterfaceID) -> Interface {
        self.parse_interface(id).unwrap()
    }

    fn read_directive(&self, name: DirectiveName) -> Option<Directive> {
        let mut start = 0;
        let mut end = self.directives.len();
        let name = name.0.lookup();
        while start < end {
            let mid = start + ((end - start) / 2);
            match self.directives.get(mid).key_compare_with_value(name) {
                Ordering::Less => {
                    start = mid + 1;
                }
                Ordering::Equal => {
                    let directive = self.directives.get(mid).value()?;
                    return self.parse_directive(directive);
                }
                Ordering::Greater => end = mid,
            }
        }
        None
    }

    fn parse_directive(&self, directive: schema_flatbuffer::Directive<'fb>) -> Option<Directive> {
        let locations = directive
            .locations()?
            .iter()
            .map(get_mapped_location)
            .collect::<Vec<_>>();
        let parsed_directive = Directive {
            name: DirectiveName(directive.name()?.intern()),
            is_extension: directive.is_extension(),
            arguments: self.parse_arguments(directive.arguments()?)?,
            locations,
            repeatable: directive.repeatable(),
            description: None,
            hack_source: None,
        };
        Some(parsed_directive)
    }

    fn read_type(&self, type_name: StringKey) -> Option<Type> {
        let mut start = 0;
        let mut end = self.types.len();
        let type_name = type_name.lookup();
        while start < end {
            let mid = start + ((end - start) / 2);
            match self.types.get(mid).key_compare_with_value(type_name) {
                Ordering::Less => {
                    start = mid + 1;
                }
                Ordering::Equal => {
                    let type_ = self.types.get(mid).value()?;
                    return Some(self.parse_type(type_));
                }
                Ordering::Greater => end = mid,
            }
        }
        None
    }

    fn parse_type(&self, type_: schema_flatbuffer::Type<'_>) -> Type {
        match type_.kind() {
            schema_flatbuffer::TypeKind::Scalar => Type::Scalar(ScalarID(type_.scalar_id())),
            schema_flatbuffer::TypeKind::InputObject => {
                Type::InputObject(InputObjectID(type_.input_object_id()))
            }
            schema_flatbuffer::TypeKind::Enum => Type::Enum(EnumID(type_.enum_id())),
            schema_flatbuffer::TypeKind::Object => Type::Object(ObjectID(type_.object_id())),
            schema_flatbuffer::TypeKind::Interface => {
                Type::Interface(InterfaceID(type_.interface_id()))
            }
            schema_flatbuffer::TypeKind::Union => Type::Union(UnionID(type_.union_id())),
            unknown => panic!("unknown TypeKind value: {:?}", unknown),
        }
    }

    fn parse_scalar(&self, id: ScalarID) -> Option<Scalar> {
        let scalar = self.scalars.get(id.0.try_into().unwrap());
        let name = ScalarName(scalar.name()?.intern());
        let parsed_scalar = Scalar {
            name: WithLocation::generated(name),
            is_extension: scalar.is_extension(),
            directives: self.parse_directive_values(scalar.directives()?)?,
            description: None,
            hack_source: None,
        };
        Some(parsed_scalar)
    }

    fn parse_input_object(&self, id: InputObjectID) -> Option<InputObject> {
        let input_object = self.input_objects.get(id.0.try_into().unwrap());
        let name = InputObjectName(input_object.name()?.to_string().intern());
        let parsed_input_object = InputObject {
            name: WithLocation::generated(name),
            fields: self.parse_arguments(input_object.fields()?)?,
            directives: self.parse_directive_values(input_object.directives()?)?,
            description: None,
            hack_source: None,
        };
        Some(parsed_input_object)
    }

    fn parse_enum(&self, id: EnumID) -> Option<Enum> {
        let enum_ = self.enums.get(id.0.try_into().unwrap());
        let name = EnumName(enum_.name()?.to_string().intern());
        let parsed_enum = Enum {
            name: WithLocation::generated(name),
            is_extension: enum_.is_extension(),
            values: self.parse_enum_values(enum_.values()?)?,
            directives: self.parse_directive_values(enum_.directives()?)?,
            description: None,
            hack_source: None,
        };
        Some(parsed_enum)
    }

    fn parse_object(&self, id: ObjectID) -> Option<Object> {
        let object = self.objects.get(id.0.try_into().unwrap());
        let name = object.name()?.intern();
        let parsed_object = Object {
            name: WithLocation::generated(ObjectName(name)),
            is_extension: object.is_extension(),
            fields: object.fields()?.iter().map(FieldID).collect(),
            interfaces: object.interfaces()?.iter().map(InterfaceID).collect(),
            directives: self.parse_directive_values(object.directives()?)?,
            description: None,
            hack_source: None,
        };
        Some(parsed_object)
    }

    fn parse_interface(&self, id: InterfaceID) -> Option<Interface> {
        let interface = self.interfaces.get(id.0.try_into().unwrap());
        let name = InterfaceName(interface.name()?.intern());

        let parsed_interface = Interface {
            name: WithLocation::generated(name),
            is_extension: interface.is_extension(),
            implementing_interfaces: wrap_ids(interface.implementing_interfaces(), InterfaceID),
            implementing_objects: wrap_ids(interface.implementing_objects(), ObjectID),
            fields: wrap_ids(interface.fields(), FieldID),
            directives: self.parse_directive_values(interface.directives()?)?,
            interfaces: wrap_ids(interface.interfaces(), InterfaceID),
            description: None,
            hack_source: None,
        };
        Some(parsed_interface)
    }

    fn parse_union(&self, id: UnionID) -> Option<Union> {
        let union = self.unions.get(id.0.try_into().unwrap());
        let name = union.name()?.intern();
        let parsed_union = Union {
            name: WithLocation::generated(name),
            is_extension: union.is_extension(),
            members: wrap_ids(union.members(), ObjectID),
            directives: self.parse_directive_values(union.directives()?)?,
            description: None,
            hack_source: None,
        };
        Some(parsed_union)
    }

    fn parse_field(&self, id: FieldID) -> Option<Field> {
        let field = self.fields.get(id.0.try_into().unwrap());
        let parsed_field = Field {
            name: WithLocation::generated(field.name()?.intern()),
            is_extension: field.is_extension(),
            arguments: self.parse_arguments(field.arguments()?)?,
            type_: self.parse_type_reference(field.type_()?)?,
            directives: self.parse_directive_values(field.directives()?)?,
            parent_type: self.get_type(self.get_fbtype_name(&field.parent_type()?)),
            description: None,
            hack_source: None,
        };
        Some(parsed_field)
    }

    fn parse_enum_values(
        &self,
        values: Vector<'_, ForwardsUOffset<schema_flatbuffer::EnumValue<'_>>>,
    ) -> Option<Vec<EnumValue>> {
        values
            .iter()
            .map(|value| self.parse_enum_value(value))
            .collect::<Option<Vec<_>>>()
    }

    fn parse_enum_value(&self, value: schema_flatbuffer::EnumValue<'fb>) -> Option<EnumValue> {
        let directives = self.parse_directive_values(value.directives()?)?;
        Some(EnumValue {
            value: value.value()?.intern(),
            directives,
        })
    }

    fn parse_arguments(
        &self,
        arguments: Vector<'fb, ForwardsUOffset<schema_flatbuffer::Argument<'_>>>,
    ) -> Option<ArgumentDefinitions> {
        let items = arguments
            .iter()
            .map(|argument| self.parse_argument(argument))
            .collect::<Option<Vec<_>>>();
        Some(ArgumentDefinitions::new(items?))
    }

    fn parse_argument(&self, argument: schema_flatbuffer::Argument<'fb>) -> Option<Argument> {
        Some(Argument {
            name: ArgumentName(argument.name().unwrap().intern()),
            default_value: match argument.value() {
                Some(value) => Some(self.parse_const_value(value)?),
                _ => None,
            },
            type_: self.parse_type_reference(argument.type_()?)?,
            description: None,
            directives: Default::default(),
        })
    }

    fn parse_type_reference(
        &self,
        type_reference: schema_flatbuffer::TypeReference<'fb>,
    ) -> Option<TypeReference<Type>> {
        Some(match type_reference.kind() {
            schema_flatbuffer::TypeReferenceKind::Named => {
                let type_name = self.get_fbtype_name(&type_reference.named()?);
                TypeReference::Named(self.get_type(type_name).unwrap())
            }
            schema_flatbuffer::TypeReferenceKind::NonNull => {
                TypeReference::NonNull(Box::new(self.parse_type_reference(type_reference.null()?)?))
            }
            schema_flatbuffer::TypeReferenceKind::List => {
                TypeReference::List(Box::new(self.parse_type_reference(type_reference.list()?)?))
            }
            unknown => panic!("unknown TypeReferenceKind value: {:?}", unknown),
        })
    }

    fn parse_directive_values(
        &self,
        directives: Vector<'_, ForwardsUOffset<schema_flatbuffer::DirectiveValue<'_>>>,
    ) -> Option<Vec<DirectiveValue>> {
        directives
            .iter()
            .map(|directive| self.parse_directive_value(directive))
            .collect::<Option<Vec<_>>>()
    }

    fn parse_directive_value(
        &self,
        directive: schema_flatbuffer::DirectiveValue<'fb>,
    ) -> Option<DirectiveValue> {
        let arguments = self.parse_argument_values(directive.arguments()?)?;
        Some(DirectiveValue {
            name: DirectiveName(directive.name()?.intern()),
            arguments,
        })
    }

    fn parse_argument_values(
        &self,
        arguments: Vector<'_, ForwardsUOffset<schema_flatbuffer::ArgumentValue<'_>>>,
    ) -> Option<Vec<ArgumentValue>> {
        arguments
            .iter()
            .map(|argument| self.parse_argument_value(argument))
            .collect::<Option<Vec<_>>>()
    }

    fn parse_argument_value(
        &self,
        argument: schema_flatbuffer::ArgumentValue<'fb>,
    ) -> Option<ArgumentValue> {
        Some(ArgumentValue {
            name: ArgumentName(argument.name()?.intern()),
            value: self.parse_const_value(argument.value()?)?,
        })
    }

    fn parse_const_value(
        &self,
        value: schema_flatbuffer::ConstValue<'fb>,
    ) -> Option<ConstantValue> {
        use schema_flatbuffer::ConstValueKind as FB;
        Some(match value.kind() {
            FB::Null => ConstantValue::Null(get_empty_token()),
            FB::String => ConstantValue::String(get_string_node(value.string_value()?.to_string())),
            FB::Bool => ConstantValue::Boolean(get_boolean_node(value.bool_value())),
            FB::Int => ConstantValue::Int(get_int_node(value.int_value()?.to_string())),
            FB::Float => ConstantValue::Float(get_float_node(value.float_value()?.to_string())),
            FB::Enum => ConstantValue::Enum(get_enum_node(value.enum_value()?.to_string())),
            FB::List => ConstantValue::List(self.parse_list_value(value.list_value()?)?),
            FB::Object => ConstantValue::Object(self.parse_object_value(value.object_value()?)?),
            unknown => panic!("unknown ConstValueKind value: {:?}", unknown),
        })
    }

    fn parse_list_value(
        &self,
        list_value: schema_flatbuffer::ListValue<'fb>,
    ) -> Option<List<ConstantValue>> {
        let items = list_value
            .values()?
            .iter()
            .map(|value| self.parse_const_value(value))
            .collect::<Option<Vec<_>>>();
        Some(List {
            span: get_empty_span(),
            start: get_empty_token(),
            items: items?,
            end: get_empty_token(),
        })
    }

    fn parse_object_value(
        &self,
        object_value: schema_flatbuffer::ObjectValue<'fb>,
    ) -> Option<List<ConstantArgument>> {
        let items = object_value
            .fields()?
            .iter()
            .map(|field| {
                Some(ConstantArgument {
                    span: get_empty_span(),
                    name: get_identifier(field.name()?.to_string()),
                    colon: get_empty_token(),
                    value: self.parse_const_value(field.value()?)?,
                })
            })
            .collect::<Option<Vec<_>>>();
        Some(List {
            span: get_empty_span(),
            start: get_empty_token(),
            items: items?,
            end: get_empty_token(),
        })
    }

    fn get_fbtype_name(&self, type_: &schema_flatbuffer::Type<'_>) -> StringKey {
        match type_.kind() {
            schema_flatbuffer::TypeKind::Scalar => self
                .scalars
                .get(type_.scalar_id().try_into().unwrap())
                .name(),
            schema_flatbuffer::TypeKind::InputObject => self
                .input_objects
                .get(type_.input_object_id().try_into().unwrap())
                .name(),
            schema_flatbuffer::TypeKind::Enum => {
                self.enums.get(type_.enum_id().try_into().unwrap()).name()
            }
            schema_flatbuffer::TypeKind::Object => self
                .objects
                .get(type_.object_id().try_into().unwrap())
                .name(),
            schema_flatbuffer::TypeKind::Interface => self
                .interfaces
                .get(type_.interface_id().try_into().unwrap())
                .name(),
            schema_flatbuffer::TypeKind::Union => {
                self.unions.get(type_.union_id().try_into().unwrap()).name()
            }
            unknown => panic!("unknown TypeKind value: {:?}", unknown),
        }
        .unwrap()
        .intern()
    }
}

fn get_identifier(value: String) -> Identifier {
    Identifier {
        span: get_empty_span(),
        token: get_empty_token(),
        value: value.intern(),
    }
}

fn get_enum_node(value: String) -> EnumNode {
    EnumNode {
        token: get_empty_token(),
        value: value.intern(),
    }
}

fn get_float_node(value: String) -> FloatNode {
    FloatNode {
        token: get_empty_token(),
        value: FloatValue::new(value.parse::<f64>().unwrap()),
        source_value: value.intern(),
    }
}

fn get_int_node(value: String) -> IntNode {
    IntNode {
        token: get_empty_token(),
        value: value.parse().unwrap(),
    }
}

fn get_boolean_node(value: bool) -> BooleanNode {
    BooleanNode {
        token: get_empty_token(),
        value,
    }
}

fn get_string_node(value: String) -> StringNode {
    StringNode {
        token: get_empty_token(),
        value: value.intern(),
    }
}

fn get_empty_token() -> Token {
    Token {
        span: get_empty_span(),
        kind: TokenKind::EndOfFile,
    }
}

fn get_empty_span() -> Span {
    Span { start: 0, end: 0 }
}

fn wrap_ids<T>(ids: Option<Vector<'_, u32>>, f: impl Fn(u32) -> T) -> Vec<T> {
    ids.map_or_else(Vec::new, |ids| ids.into_iter().map(f).collect())
}

fn get_mapped_location(location: schema_flatbuffer::DirectiveLocation) -> DirectiveLocation {
    use schema_flatbuffer::DirectiveLocation as FDL;
    use DirectiveLocation as DL;
    match location {
        FDL::Query => DL::Query,
        FDL::Mutation => DL::Mutation,
        FDL::Subscription => DL::Subscription,
        FDL::Field => DL::Field,
        FDL::FragmentDefinition => DL::FragmentDefinition,
        FDL::FragmentSpread => DL::FragmentSpread,
        FDL::InlineFragment => DL::InlineFragment,
        FDL::Schema => DL::Schema,
        FDL::Scalar => DL::Scalar,
        FDL::Object => DL::Object,
        FDL::FieldDefinition => DL::FieldDefinition,
        FDL::ArgumentDefinition => DL::ArgumentDefinition,
        FDL::Interface => DL::Interface,
        FDL::Union => DL::Union,
        FDL::Enum => DL::Enum,
        FDL::EnumValue => DL::EnumValue,
        FDL::InputObject => DL::InputObject,
        FDL::InputFieldDefinition => DL::InputFieldDefinition,
        FDL::VariableDefinition => DL::VariableDefinition,
        unknown => panic!("unknown DirectiveLocation value: {:?}", unknown),
    }
}

#[cfg(test)]
mod tests {
    use common::DiagnosticsResult;

    use super::*;
    use crate::build_schema;

    #[test]
    fn binary_search() -> DiagnosticsResult<()> {
        let sdl = "
        directive @ref_type(schema: String, name: String) on FIELD_DEFINITION
        directive @extern_type(schema: String, name: String) on INTERFACE
        directive @fetchable(field_name: String) on OBJECT

        type Query { id: ID }
        type User { id: ID }
        type MailingAddress { id: ID }
        type Country { id: ID }
        ";
        let sdl_schema = build_schema(sdl)?.unwrap_in_memory_impl();
        let bytes = serialize_as_flatbuffer(&sdl_schema);
        let fb_schema = FlatBufferSchema::build(&bytes);

        assert!(fb_schema.read_type("Query".intern()).is_some());
        assert!(fb_schema.read_type("User".intern()).is_some());
        assert!(fb_schema.read_type("MailingAddress".intern()).is_some());

        assert!(fb_schema.read_type("State".intern()).is_none());
        assert!(fb_schema.read_type("Aaaa".intern()).is_none());
        assert!(fb_schema.read_type("Zzzz".intern()).is_none());

        assert!(
            fb_schema
                .read_directive(DirectiveName("ref_type".intern()))
                .is_some()
        );
        assert!(
            fb_schema
                .read_directive(DirectiveName("extern_type".intern()))
                .is_some()
        );
        assert!(
            fb_schema
                .read_directive(DirectiveName("fetchable".intern()))
                .is_some()
        );

        assert!(
            fb_schema
                .read_directive(DirectiveName("goto".intern()))
                .is_none()
        );
        assert!(
            fb_schema
                .read_directive(DirectiveName("aaaa".intern()))
                .is_none()
        );
        assert!(
            fb_schema
                .read_directive(DirectiveName("zzzz".intern()))
                .is_none()
        );

        Ok(())
    }
}


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

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

use std::collections::BTreeMap;

use flatbuffers::FlatBufferBuilder;
use flatbuffers::WIPOffset;
use fnv::FnvHashMap;
use graphql_syntax::ConstantArgument;
use graphql_syntax::ConstantValue;
use graphql_syntax::DirectiveLocation;
use graphql_syntax::List;
use intern::string_key::StringKey;
use intern::Lookup;

use crate::in_memory::InMemorySchema;
use crate::Argument;
use crate::ArgumentDefinitions;
use crate::ArgumentValue;
use crate::Directive;
use crate::DirectiveValue;
use crate::EnumID;
use crate::EnumValue;
use crate::FieldID;
use crate::InputObjectID;
use crate::InterfaceID;
use crate::ObjectID;
use crate::ScalarID;
use crate::Schema;
use crate::Type;
use crate::TypeReference;
use crate::UnionID;

pub fn serialize_as_flatbuffer(schema: &InMemorySchema) -> Vec<u8> {
    let mut serializer = Serializer::new(schema);
    serializer.serialize_schema()
}

struct Serializer<'fb, 'schema> {
    schema: &'schema InMemorySchema,
    bldr: FlatBufferBuilder<'fb>,
    scalars: Vec<WIPOffset<schema_flatbuffer::Scalar<'fb>>>,
    input_objects: Vec<WIPOffset<schema_flatbuffer::InputObject<'fb>>>,
    enums: Vec<WIPOffset<schema_flatbuffer::Enum<'fb>>>,
    objects: Vec<WIPOffset<schema_flatbuffer::Object<'fb>>>,
    interfaces: Vec<WIPOffset<schema_flatbuffer::Interface<'fb>>>,
    unions: Vec<WIPOffset<schema_flatbuffer::Union<'fb>>>,
    fields: Vec<WIPOffset<schema_flatbuffer::Field<'fb>>>,
    types: FnvHashMap<String, WIPOffset<schema_flatbuffer::TypeMapEntry<'fb>>>,
    type_map: FnvHashMap<StringKey, schema_flatbuffer::TypeArgs>,
    directives: FnvHashMap<String, WIPOffset<schema_flatbuffer::DirectiveMapEntry<'fb>>>,
}

impl<'fb, 'schema> Serializer<'fb, 'schema> {
    fn new(schema: &'schema InMemorySchema) -> Self {
        Self {
            schema,
            bldr: FlatBufferBuilder::new(),
            scalars: Vec::new(),
            input_objects: Vec::new(),
            enums: Vec::new(),
            objects: Vec::new(),
            interfaces: Vec::new(),
            unions: Vec::new(),
            fields: Vec::new(),
            types: FnvHashMap::default(),
            type_map: FnvHashMap::default(),
            directives: FnvHashMap::default(),
        }
    }

    fn serialize_schema(&mut self) -> Vec<u8> {
        self.serialize_types();
        self.serialize_directives();
        let mut ordered_types = Vec::new();
        for (_key, value) in self.types.iter().collect::<BTreeMap<_, _>>() {
            ordered_types.push(*value);
        }
        let mut ordered_directives = Vec::new();
        for (_key, value) in self.directives.iter().collect::<BTreeMap<_, _>>() {
            ordered_directives.push(*value);
        }

        let (has_query_type, query_type) = self.get_object_id(self.schema.query_type());
        assert!(has_query_type);
        let (has_mutation_type, mutation_type) = self.get_object_id(self.schema.mutation_type());
        let (has_subscription_type, subscription_type) =
            self.get_object_id(self.schema.subscription_type());

        let schema_args = schema_flatbuffer::SchemaArgs {
            query_type,
            has_mutation_type,
            mutation_type,
            has_subscription_type,
            subscription_type,
            types: Some(self.bldr.create_vector(&ordered_types)),
            scalars: Some(self.bldr.create_vector(&self.scalars)),
            input_objects: Some(self.bldr.create_vector(&self.input_objects)),
            enums: Some(self.bldr.create_vector(&self.enums)),
            objects: Some(self.bldr.create_vector(&self.objects)),
            interfaces: Some(self.bldr.create_vector(&self.interfaces)),
            unions: Some(self.bldr.create_vector(&self.unions)),
            fields: Some(self.bldr.create_vector(&self.fields)),
            directives: Some(self.bldr.create_vector(&ordered_directives)),
        };
        let schema_offset = schema_flatbuffer::Schema::create(&mut self.bldr, &schema_args);
        schema_flatbuffer::finish_schema_buffer(&mut self.bldr, schema_offset);
        self.bldr.finished_data().to_owned()
    }

    /// Returns true and an object id when passed Some(Type) or false and 0 for None
    /// This is used to serialize the query/mutation/subscription types.
    fn get_object_id(&mut self, type_: Option<Type>) -> (bool, u32) {
        if let Some(type_) = type_ {
            let type_args = self.get_type_args(type_);
            assert!(type_args.kind == schema_flatbuffer::TypeKind::Object);
            (true, type_args.object_id)
        } else {
            (false, 0)
        }
    }

    fn serialize_types(&mut self) {
        let ordered_type_map = self.schema.get_type_map().collect::<BTreeMap<_, _>>();
        for (_key, value) in ordered_type_map.iter() {
            self.serialize_type(**value);
        }
    }

    fn serialize_directives(&mut self) {
        for directive in self.schema.get_directives() {
            self.serialize_directive(directive)
        }
    }

    fn serialize_directive(&mut self, directive: &Directive) {
        let name = directive.name.0.lookup();
        if self.directives.contains_key(name) {
            return;
        }
        let arguments = &self.serialize_arguments(&directive.arguments);
        let locations = &directive
            .locations
            .iter()
            .map(|location| get_mapped_location(*location))
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::DirectiveArgs {
            name: Some(self.bldr.create_string(name)),
            is_extension: directive.is_extension,
            arguments: Some(self.bldr.create_vector(arguments)),
            locations: Some(self.bldr.create_vector(locations)),
            repeatable: directive.repeatable,
        };
        let fb_directive = schema_flatbuffer::Directive::create(&mut self.bldr, &args);

        let directive_map_args = schema_flatbuffer::DirectiveMapEntryArgs {
            name: Some(self.bldr.create_string(name)),
            value: Some(fb_directive),
        };
        self.directives.insert(
            name.to_string(),
            schema_flatbuffer::DirectiveMapEntry::create(&mut self.bldr, &directive_map_args),
        );
    }

    fn serialize_type(&mut self, type_: Type) {
        let name = self.schema.get_type_name(type_);
        if self.type_map.contains_key(&name) {
            return;
        }
        match type_ {
            Type::Scalar(id) => self.serialize_scalar(id),
            Type::InputObject(id) => self.serialize_input_object(id),
            Type::Enum(id) => self.serialize_enum(id),
            Type::Object(id) => self.serialize_object(id),
            Type::Interface(id) => self.serialize_interface(id),
            Type::Union(id) => self.serialize_union(id),
        }
    }

    fn get_type_args(&mut self, type_: Type) -> schema_flatbuffer::TypeArgs {
        let name = self.schema.get_type_name(type_);
        if !self.type_map.contains_key(&name) {
            self.serialize_type(type_);
        }
        self.type_map[&name]
    }

    fn serialize_scalar(&mut self, id: ScalarID) {
        let scalar = self.schema.scalar(id);
        let name = scalar.name;
        let directives = &self.serialize_directive_values(&scalar.directives);
        let args = schema_flatbuffer::ScalarArgs {
            name: Some(self.bldr.create_string(name.item.lookup())),
            is_extension: scalar.is_extension,
            directives: Some(self.bldr.create_vector(directives)),
        };
        self.add_to_type_map(
            self.scalars.len(),
            schema_flatbuffer::TypeKind::Scalar,
            name.item.0,
        );
        self.scalars
            .push(schema_flatbuffer::Scalar::create(&mut self.bldr, &args));
    }

    fn serialize_input_object(&mut self, id: InputObjectID) {
        let input_object = self.schema.input_object(id);
        let name = input_object.name;
        // Reserve idx and add to typemap. Else we could endup in a cycle
        let idx = self.input_objects.len();
        self.add_to_type_map(idx, schema_flatbuffer::TypeKind::InputObject, name.item.0);
        self.input_objects
            .push(schema_flatbuffer::InputObject::create(
                &mut self.bldr,
                &schema_flatbuffer::InputObjectArgs::default(),
            ));
        let items = &self.serialize_directive_values(&input_object.directives);
        let fields = &self.serialize_arguments(&input_object.fields);
        let args = schema_flatbuffer::InputObjectArgs {
            name: Some(self.bldr.create_string(name.item.0.lookup())),
            directives: Some(self.bldr.create_vector(items)),
            fields: Some(self.bldr.create_vector(fields)),
        };
        self.input_objects[idx] = schema_flatbuffer::InputObject::create(&mut self.bldr, &args);
    }

    fn serialize_enum(&mut self, id: EnumID) {
        let enum_ = self.schema.enum_(id);
        let name = enum_.name;
        let directives = &self.serialize_directive_values(&enum_.directives);
        let values = &self.serialize_enum_values(&enum_.values);
        let args = schema_flatbuffer::EnumArgs {
            name: Some(self.bldr.create_string(name.item.lookup())),
            is_extension: enum_.is_extension,
            directives: Some(self.bldr.create_vector(directives)),
            values: Some(self.bldr.create_vector(values)),
        };
        self.add_to_type_map(
            self.enums.len(),
            schema_flatbuffer::TypeKind::Enum,
            name.item.0,
        );
        self.enums
            .push(schema_flatbuffer::Enum::create(&mut self.bldr, &args));
    }

    fn serialize_object(&mut self, id: ObjectID) {
        let object = self.schema.object(id);
        let name = object.name;
        let idx = self.objects.len();
        self.add_to_type_map(idx, schema_flatbuffer::TypeKind::Object, name.item.0);
        self.objects.push(schema_flatbuffer::Object::create(
            &mut self.bldr,
            &schema_flatbuffer::ObjectArgs::default(),
        ));

        let directives = &self.serialize_directive_values(&object.directives);
        let fields = &object
            .fields
            .iter()
            .map(|field_id| self.serialize_field(*field_id).try_into().unwrap())
            .collect::<Vec<u32>>();
        let interfaces = &object
            .interfaces
            .iter()
            .map(|interface_id| {
                self.get_type_args(Type::Interface(*interface_id))
                    .interface_id
            })
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::ObjectArgs {
            name: Some(self.bldr.create_string(name.item.lookup())),
            is_extension: object.is_extension,
            directives: Some(self.bldr.create_vector(directives)),
            fields: Some(self.bldr.create_vector(fields)),
            interfaces: Some(self.bldr.create_vector(interfaces)),
        };
        self.objects[idx] = schema_flatbuffer::Object::create(&mut self.bldr, &args);
    }

    fn serialize_interface(&mut self, id: InterfaceID) {
        let interface = self.schema.interface(id);
        let name = interface.name;
        let idx = self.interfaces.len();
        self.add_to_type_map(idx, schema_flatbuffer::TypeKind::Interface, name.item.0);
        self.interfaces.push(schema_flatbuffer::Interface::create(
            &mut self.bldr,
            &schema_flatbuffer::InterfaceArgs::default(),
        ));

        let directives = &self.serialize_directive_values(&interface.directives);
        let fields = &interface
            .fields
            .iter()
            .map(|field_id| self.serialize_field(*field_id).try_into().unwrap())
            .collect::<Vec<u32>>();
        let interfaces = &interface
            .interfaces
            .iter()
            .map(|interface_id| {
                self.get_type_args(Type::Interface(*interface_id))
                    .interface_id
            })
            .collect::<Vec<_>>();
        let implementing_interfaces = &interface
            .implementing_interfaces
            .iter()
            .map(|interface_id| {
                self.get_type_args(Type::Interface(*interface_id))
                    .interface_id
            })
            .collect::<Vec<_>>();
        let implementing_objects = &interface
            .implementing_objects
            .iter()
            .map(|object_id| self.get_type_args(Type::Object(*object_id)).object_id)
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::InterfaceArgs {
            name: Some(self.bldr.create_string(name.item.lookup())),
            is_extension: interface.is_extension,
            directives: Some(self.bldr.create_vector(directives)),
            fields: Some(self.bldr.create_vector(fields)),
            interfaces: Some(self.bldr.create_vector(interfaces)),
            implementing_interfaces: Some(self.bldr.create_vector(implementing_interfaces)),
            implementing_objects: Some(self.bldr.create_vector(implementing_objects)),
        };
        self.interfaces[idx] = schema_flatbuffer::Interface::create(&mut self.bldr, &args);
    }

    fn serialize_union(&mut self, id: UnionID) {
        let union = self.schema.union(id);
        let name = union.name;
        let idx = self.unions.len();
        self.add_to_type_map(idx, schema_flatbuffer::TypeKind::Union, name.item);
        self.unions.push(schema_flatbuffer::Union::create(
            &mut self.bldr,
            &schema_flatbuffer::UnionArgs::default(),
        ));

        let directives = &self.serialize_directive_values(&union.directives);
        let members = &union
            .members
            .iter()
            .map(|object_id| self.get_type_args(Type::Object(*object_id)).object_id)
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::UnionArgs {
            name: Some(self.bldr.create_string(name.item.lookup())),
            is_extension: union.is_extension,
            members: Some(self.bldr.create_vector(members)),
            directives: Some(self.bldr.create_vector(directives)),
        };
        self.unions[idx] = schema_flatbuffer::Union::create(&mut self.bldr, &args);
    }

    fn serialize_field(&mut self, id: FieldID) -> usize {
        let field = self.schema.field(id);
        let name = field.name.item.lookup();
        let directives = &self.serialize_directive_values(&field.directives);
        let arguments = &self.serialize_arguments(&field.arguments);
        let args = schema_flatbuffer::FieldArgs {
            name: Some(self.bldr.create_string(name)),
            is_extension: field.is_extension,
            arguments: Some(self.bldr.create_vector(arguments)),
            type_: Some(self.serialize_type_reference(&field.type_)),
            directives: Some(self.bldr.create_vector(directives)),
            parent_type: match &field.parent_type {
                Some(type_) => {
                    let args = self.get_type_args(*type_);
                    Some(schema_flatbuffer::Type::create(&mut self.bldr, &args))
                }
                _ => None,
            },
        };
        self.fields
            .push(schema_flatbuffer::Field::create(&mut self.bldr, &args));
        self.fields.len() - 1
    }

    fn serialize_enum_values(
        &mut self,
        values: &[EnumValue],
    ) -> Vec<WIPOffset<schema_flatbuffer::EnumValue<'fb>>> {
        values
            .iter()
            .map(|value| self.serialize_enum_value(value))
            .collect::<Vec<_>>()
    }

    fn serialize_enum_value(
        &mut self,
        value: &EnumValue,
    ) -> WIPOffset<schema_flatbuffer::EnumValue<'fb>> {
        let directives = &self.serialize_directive_values(&value.directives);
        let args = schema_flatbuffer::EnumValueArgs {
            value: Some(self.bldr.create_string(value.value.lookup())),
            directives: Some(self.bldr.create_vector(directives)),
        };
        schema_flatbuffer::EnumValue::create(&mut self.bldr, &args)
    }

    fn serialize_arguments(
        &mut self,
        arguments: &ArgumentDefinitions,
    ) -> Vec<WIPOffset<schema_flatbuffer::Argument<'fb>>> {
        arguments
            .iter()
            .map(|argument| self.serialize_argument(argument))
            .collect::<Vec<_>>()
    }

    fn serialize_argument(
        &mut self,
        value: &Argument,
    ) -> WIPOffset<schema_flatbuffer::Argument<'fb>> {
        let args = schema_flatbuffer::ArgumentArgs {
            name: Some(self.bldr.create_string(value.name.0.lookup())),
            value: value
                .default_value
                .as_ref()
                .map(|default_value| self.serialize_const_value(default_value)),
            type_: Some(self.serialize_type_reference(&value.type_)),
        };
        schema_flatbuffer::Argument::create(&mut self.bldr, &args)
    }

    fn serialize_type_reference(
        &mut self,
        type_: &TypeReference<Type>,
    ) -> WIPOffset<schema_flatbuffer::TypeReference<'fb>> {
        let mut args = schema_flatbuffer::TypeReferenceArgs::default();
        match type_ {
            TypeReference::Named(type_) => {
                let type_args = self.get_type_args(*type_);
                args.kind = schema_flatbuffer::TypeReferenceKind::Named;
                args.named = Some(schema_flatbuffer::Type::create(&mut self.bldr, &type_args));
            }
            TypeReference::List(of) => {
                args.kind = schema_flatbuffer::TypeReferenceKind::List;
                args.list = Some(self.serialize_type_reference(of));
            }
            TypeReference::NonNull(of) => {
                args.kind = schema_flatbuffer::TypeReferenceKind::NonNull;
                args.null = Some(self.serialize_type_reference(of));
            }
        }
        schema_flatbuffer::TypeReference::create(&mut self.bldr, &args)
    }

    fn serialize_directive_values(
        &mut self,
        directives: &[DirectiveValue],
    ) -> Vec<WIPOffset<schema_flatbuffer::DirectiveValue<'fb>>> {
        directives
            .iter()
            .map(|directive| self.serialize_directive_value(directive))
            .collect::<Vec<_>>()
    }

    fn serialize_directive_value(
        &mut self,
        directive: &DirectiveValue,
    ) -> WIPOffset<schema_flatbuffer::DirectiveValue<'fb>> {
        let argument_values = &self.serialize_argument_values(&directive.arguments);
        let args = schema_flatbuffer::DirectiveValueArgs {
            name: Some(self.bldr.create_string(directive.name.0.lookup())),
            arguments: Some(self.bldr.create_vector(argument_values)),
        };
        schema_flatbuffer::DirectiveValue::create(&mut self.bldr, &args)
    }

    fn serialize_argument_values(
        &mut self,
        argument_values: &[ArgumentValue],
    ) -> Vec<WIPOffset<schema_flatbuffer::ArgumentValue<'fb>>> {
        argument_values
            .iter()
            .map(|argument_value| self.serialize_argument_value(argument_value))
            .collect::<Vec<_>>()
    }

    fn serialize_argument_value(
        &mut self,
        argument_value: &ArgumentValue,
    ) -> WIPOffset<schema_flatbuffer::ArgumentValue<'fb>> {
        let args = schema_flatbuffer::ArgumentValueArgs {
            name: Some(self.bldr.create_string(argument_value.name.0.lookup())),
            value: Some(self.serialize_const_value(&argument_value.value)),
        };
        schema_flatbuffer::ArgumentValue::create(&mut self.bldr, &args)
    }

    fn serialize_const_value(
        &mut self,
        value: &ConstantValue,
    ) -> WIPOffset<schema_flatbuffer::ConstValue<'fb>> {
        let mut args = schema_flatbuffer::ConstValueArgs::default();
        match value {
            ConstantValue::String(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::String;
                args.string_value = Some(self.bldr.create_string(&format!("{}", value)));
            }
            ConstantValue::Int(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::Int;
                args.int_value = Some(self.bldr.create_string(&format!("{}", value)));
            }
            ConstantValue::Float(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::Float;
                args.float_value = Some(self.bldr.create_string(&format!("{}", value)));
            }
            ConstantValue::Boolean(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::Bool;
                args.bool_value = value.value;
            }
            ConstantValue::Enum(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::Enum;
                args.enum_value = Some(self.bldr.create_string(&format!("{}", value)));
            }
            ConstantValue::List(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::List;
                args.list_value = Some(self.serialize_list_value(value));
            }
            ConstantValue::Object(value) => {
                args.kind = schema_flatbuffer::ConstValueKind::Object;
                args.object_value = Some(self.serialize_object_value(value));
            }
            ConstantValue::Null(_) => {
                args.kind = schema_flatbuffer::ConstValueKind::Null;
            }
        };
        schema_flatbuffer::ConstValue::create(&mut self.bldr, &args)
    }

    fn serialize_list_value(
        &mut self,
        list: &List<ConstantValue>,
    ) -> WIPOffset<schema_flatbuffer::ListValue<'fb>> {
        let items = &list
            .items
            .iter()
            .map(|value| self.serialize_const_value(value))
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::ListValueArgs {
            values: Some(self.bldr.create_vector(items)),
        };
        schema_flatbuffer::ListValue::create(&mut self.bldr, &args)
    }

    fn serialize_object_value(
        &mut self,
        object: &List<ConstantArgument>,
    ) -> WIPOffset<schema_flatbuffer::ObjectValue<'fb>> {
        let items = &object
            .items
            .iter()
            .map(|value| self.serialize_object_field(value))
            .collect::<Vec<_>>();
        let args = schema_flatbuffer::ObjectValueArgs {
            fields: Some(self.bldr.create_vector(items)),
        };
        schema_flatbuffer::ObjectValue::create(&mut self.bldr, &args)
    }

    fn serialize_object_field(
        &mut self,
        value: &ConstantArgument,
    ) -> WIPOffset<schema_flatbuffer::ObjectField<'fb>> {
        let args = schema_flatbuffer::ObjectFieldArgs {
            name: Some(self.bldr.create_string(&format!("{}", value.name))),
            value: Some(self.serialize_const_value(&value.value)),
        };
        schema_flatbuffer::ObjectField::create(&mut self.bldr, &args)
    }

    fn add_to_type_map(&mut self, id: usize, kind: schema_flatbuffer::TypeKind, name: StringKey) {
        let id = id.try_into().unwrap();
        let type_args = self.build_type_args(id, kind);
        let args = schema_flatbuffer::TypeMapEntryArgs {
            name: Some(self.bldr.create_string(name.lookup())),
            value: Some(schema_flatbuffer::Type::create(&mut self.bldr, &type_args)),
        };
        self.types.insert(
            name.to_string(),
            schema_flatbuffer::TypeMapEntry::create(&mut self.bldr, &args),
        );
        self.type_map.insert(name, type_args);
    }

    #[allow(clippy::field_reassign_with_default)]
    fn build_type_args(
        &mut self,
        id: u32,
        kind: schema_flatbuffer::TypeKind,
    ) -> schema_flatbuffer::TypeArgs {
        let mut type_args = schema_flatbuffer::TypeArgs::default();
        type_args.kind = kind;
        match kind {
            schema_flatbuffer::TypeKind::Scalar => {
                type_args.scalar_id = id;
            }
            schema_flatbuffer::TypeKind::InputObject => {
                type_args.input_object_id = id;
            }
            schema_flatbuffer::TypeKind::Enum => {
                type_args.enum_id = id;
            }
            schema_flatbuffer::TypeKind::Object => {
                type_args.object_id = id;
            }
            schema_flatbuffer::TypeKind::Interface => {
                type_args.interface_id = id;
            }
            schema_flatbuffer::TypeKind::Union => {
                type_args.union_id = id;
            }
            unknown => panic!("unknown TypeKind value: {:?}", unknown),
        }
        type_args
    }
}

fn get_mapped_location(location: DirectiveLocation) -> schema_flatbuffer::DirectiveLocation {
    use schema_flatbuffer::DirectiveLocation as FDL;
    use DirectiveLocation as DL;
    match location {
        DL::Query => FDL::Query,
        DL::Mutation => FDL::Mutation,
        DL::Subscription => FDL::Subscription,
        DL::Field => FDL::Field,
        DL::FragmentDefinition => FDL::FragmentDefinition,
        DL::FragmentSpread => FDL::FragmentSpread,
        DL::InlineFragment => FDL::InlineFragment,
        DL::Schema => FDL::Schema,
        DL::Scalar => FDL::Scalar,
        DL::Object => FDL::Object,
        DL::FieldDefinition => FDL::FieldDefinition,
        DL::ArgumentDefinition => FDL::ArgumentDefinition,
        DL::Interface => FDL::Interface,
        DL::Union => FDL::Union,
        DL::Enum => FDL::Enum,
        DL::EnumValue => FDL::EnumValue,
        DL::InputObject => FDL::InputObject,
        DL::InputFieldDefinition => FDL::InputFieldDefinition,
        DL::VariableDefinition => FDL::VariableDefinition,
    }
}


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

use std::fmt;
use std::hash::Hash;

use common::ArgumentName;
use common::DirectiveName;
use common::WithLocation;
use dashmap::DashMap;
use fnv::FnvBuildHasher;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use ouroboros::self_referencing;

use super::FlatBufferSchema;
use crate::field_descriptions::CLIENT_ID_DESCRIPTION;
use crate::field_descriptions::TYPENAME_DESCRIPTION;
use crate::Argument;
use crate::ArgumentDefinitions;
use crate::Directive;
use crate::Enum;
use crate::EnumID;
use crate::Field;
use crate::FieldID;
use crate::InputObject;
use crate::InputObjectID;
use crate::Interface;
use crate::InterfaceID;
use crate::Object;
use crate::ObjectID;
use crate::Scalar;
use crate::ScalarID;
use crate::Schema;
use crate::Type;
use crate::TypeReference;
use crate::Union;
use crate::UnionID;

#[self_referencing]
struct OwnedFlatBufferSchema {
    #[allow(dead_code)]
    data: Vec<u8>,

    #[borrows(data)]
    #[covariant]
    pub schema: FlatBufferSchema<'this>,
}

const CLIENTID_FIELD_ID: FieldID = FieldID(10_000_000);
const TYPENAME_FIELD_ID: FieldID = FieldID(10_000_001);
const FETCH_TOKEN_FIELD_ID: FieldID = FieldID(10_000_002);
const STRONGID_FIELD_ID: FieldID = FieldID(10_000_003);
const IS_FULFILLED_FIELD_ID: FieldID = FieldID(10_000_004);

pub struct SchemaWrapper {
    clientid_field_name: StringKey,
    strongid_field_name: StringKey,
    typename_field_name: StringKey,
    fetch_token_field_name: StringKey,
    is_fulfilled_field_name: StringKey,
    unchecked_argument_type_sentinel: Option<TypeReference<Type>>,

    directives: Cache<DirectiveName, Option<Directive>>,
    interfaces: Cache<InterfaceID, Interface>,
    unions: Cache<UnionID, Union>,
    input_objects: Cache<InputObjectID, InputObject>,
    enums: Cache<EnumID, Enum>,
    scalars: Cache<ScalarID, Scalar>,
    fields: Cache<FieldID, Field>,
    objects: Cache<ObjectID, Object>,
    fb: OwnedFlatBufferSchema,
}
impl fmt::Debug for SchemaWrapper {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Schema").finish()
    }
}

fn _assert_send_sync(_: impl Send + Sync) {}
fn _assert_send_sync2(s: &SchemaWrapper) {
    _assert_send_sync(s)
}

impl SchemaWrapper {
    pub fn from_vec(data: Vec<u8>) -> Self {
        let fb = OwnedFlatBufferSchemaBuilder {
            data,
            schema_builder: |data| FlatBufferSchema::build(data),
        }
        .build();

        let mut result = Self {
            clientid_field_name: "__id".intern(),
            strongid_field_name: "strong_id__".intern(),
            typename_field_name: "__typename".intern(),
            fetch_token_field_name: "__token".intern(),
            is_fulfilled_field_name: "is_fulfilled__".intern(),
            unchecked_argument_type_sentinel: None,
            directives: Cache::new(),
            unions: Cache::new(),
            interfaces: Cache::new(),
            input_objects: Cache::new(),
            enums: Cache::new(),
            scalars: Cache::new(),
            fields: Cache::new(),
            objects: Cache::new(),
            fb,
        };

        // prepopulate special fields
        result.fields.get(TYPENAME_FIELD_ID, || Field {
            name: WithLocation::generated(result.typename_field_name),
            is_extension: false,
            arguments: ArgumentDefinitions::new(Default::default()),
            type_: TypeReference::NonNull(Box::new(TypeReference::Named(
                result.get_type("String".intern()).unwrap(),
            ))),
            directives: Vec::new(),
            parent_type: None,
            description: None,
            hack_source: None,
        });
        result.fields.get(CLIENTID_FIELD_ID, || -> Field {
            Field {
                name: WithLocation::generated(result.clientid_field_name),
                is_extension: true,
                arguments: ArgumentDefinitions::new(Default::default()),
                type_: TypeReference::NonNull(Box::new(TypeReference::Named(
                    result.get_type("ID".intern()).unwrap(),
                ))),
                directives: Vec::new(),
                parent_type: None,
                description: Some(*CLIENT_ID_DESCRIPTION),
                hack_source: None,
            }
        });
        result.fields.get(STRONGID_FIELD_ID, || Field {
            name: WithLocation::generated(result.strongid_field_name),
            is_extension: true,
            arguments: ArgumentDefinitions::new(Default::default()),
            type_: TypeReference::Named(result.get_type("ID".intern()).unwrap()),
            directives: Vec::new(),
            parent_type: None,
            description: Some(*TYPENAME_DESCRIPTION),
            hack_source: None,
        });
        result.fields.get(FETCH_TOKEN_FIELD_ID, || Field {
            name: WithLocation::generated(result.fetch_token_field_name),
            is_extension: false,
            arguments: ArgumentDefinitions::new(Default::default()),
            type_: TypeReference::NonNull(Box::new(TypeReference::Named(
                result.get_type("ID".intern()).unwrap(),
            ))),
            directives: Vec::new(),
            parent_type: None,
            description: None,
            hack_source: None,
        });
        result.fields.get(IS_FULFILLED_FIELD_ID, || Field {
            name: WithLocation::generated(result.is_fulfilled_field_name),
            is_extension: true,
            arguments: ArgumentDefinitions::new(vec![Argument {
                name: ArgumentName("name".intern()),
                type_: TypeReference::NonNull(Box::new(TypeReference::Named(
                    result.get_type("String".intern()).unwrap(),
                ))),
                default_value: None,
                description: None,
                directives: Default::default(),
            }]),
            type_: TypeReference::NonNull(Box::new(TypeReference::Named(
                result.get_type("Boolean".intern()).unwrap(),
            ))),
            directives: Vec::new(),
            parent_type: None,
            description: None,
            hack_source: None,
        });

        result.unchecked_argument_type_sentinel = Some(TypeReference::Named(
            result.get_type("Boolean".intern()).unwrap(),
        ));

        result
    }

    pub fn has_type(&self, type_name: StringKey) -> bool {
        self.flatbuffer_schema().has_type(type_name)
    }

    pub fn has_directive(&self, name: DirectiveName) -> bool {
        self.get_directive(name).is_some()
    }

    pub fn get_directives(&self) -> Vec<&Directive> {
        todo!()
    }

    pub fn get_type_map(&self) -> impl Iterator<Item = (&StringKey, &Type)> {
        if true {
            todo!();
        }
        (vec![]).into_iter()
    }

    fn flatbuffer_schema(&self) -> &FlatBufferSchema<'_> {
        self.fb.borrow_schema()
    }
}

impl Schema for SchemaWrapper {
    fn query_type(&self) -> Option<Type> {
        Some(self.flatbuffer_schema().query_type())
    }

    fn mutation_type(&self) -> Option<Type> {
        self.flatbuffer_schema().mutation_type()
    }

    fn subscription_type(&self) -> Option<Type> {
        self.flatbuffer_schema().subscription_type()
    }

    fn clientid_field(&self) -> FieldID {
        CLIENTID_FIELD_ID
    }

    fn strongid_field(&self) -> FieldID {
        STRONGID_FIELD_ID
    }

    fn typename_field(&self) -> FieldID {
        TYPENAME_FIELD_ID
    }

    fn fetch_token_field(&self) -> FieldID {
        FETCH_TOKEN_FIELD_ID
    }

    fn is_fulfilled_field(&self) -> FieldID {
        IS_FULFILLED_FIELD_ID
    }

    fn get_type(&self, type_name: StringKey) -> Option<Type> {
        self.flatbuffer_schema().get_type(type_name)
    }

    fn get_directive(&self, name: DirectiveName) -> Option<&Directive> {
        self.directives
            .get(name, || {
                match (
                    name.0.lookup(),
                    self.flatbuffer_schema().get_directive(name),
                ) {
                    ("defer", Some(mut directive)) | ("stream", Some(mut directive)) => {
                        let mut next_args: Vec<_> = directive.arguments.iter().cloned().collect();
                        for arg in next_args.iter_mut() {
                            if arg.name.0.lookup() == "label" {
                                if let TypeReference::NonNull(of) = &arg.type_ {
                                    arg.type_ = *of.clone()
                                };
                            }
                        }
                        directive.arguments = ArgumentDefinitions::new(next_args);
                        Some(directive)
                    }
                    (_, result) => result,
                }
            })
            .as_ref()
    }

    fn input_object(&self, id: InputObjectID) -> &InputObject {
        self.input_objects
            .get(id, || self.flatbuffer_schema().input_object(id))
    }

    fn enum_(&self, id: EnumID) -> &Enum {
        self.enums.get(id, || self.flatbuffer_schema().enum_(id))
    }

    fn scalar(&self, id: ScalarID) -> &Scalar {
        self.scalars.get(id, || self.flatbuffer_schema().scalar(id))
    }

    fn field(&self, id: FieldID) -> &Field {
        self.fields.get(id, || self.flatbuffer_schema().field(id))
    }

    fn object(&self, id: ObjectID) -> &Object {
        self.objects.get(id, || self.flatbuffer_schema().object(id))
    }

    fn union(&self, id: UnionID) -> &Union {
        self.unions.get(id, || self.flatbuffer_schema().union(id))
    }

    fn interface(&self, id: InterfaceID) -> &Interface {
        self.interfaces
            .get(id, || self.flatbuffer_schema().interface(id))
    }

    fn get_type_name(&self, type_: Type) -> StringKey {
        match type_ {
            Type::Enum(id) => self.enum_(id).name.item.0,
            Type::InputObject(id) => self.input_object(id).name.item.0,
            Type::Interface(id) => self.interface(id).name.item.0,
            Type::Object(id) => self.object(id).name.item.0,
            Type::Scalar(id) => self.scalar(id).name.item.0,
            Type::Union(id) => self.union(id).name.item,
        }
    }

    fn is_extension_type(&self, type_: Type) -> bool {
        match type_ {
            Type::Enum(id) => self.enum_(id).is_extension,
            Type::Interface(id) => self.interface(id).is_extension,
            Type::Object(id) => self.object(id).is_extension,
            Type::Scalar(id) => self.scalar(id).is_extension,
            Type::Union(id) => self.union(id).is_extension,
            Type::InputObject(_) => false,
        }
    }

    fn is_string(&self, type_: Type) -> bool {
        match type_ {
            Type::Scalar(id) => self.scalar(id).name.item.lookup() == "String",
            _ => false,
        }
    }

    fn is_id(&self, type_: Type) -> bool {
        match type_ {
            Type::Scalar(id) => self.scalar(id).name.item.lookup() == "ID",
            _ => false,
        }
    }

    fn named_field(&self, parent_type: Type, name: StringKey) -> Option<FieldID> {
        // Special case for __typename and __id fields, which should not be in the list of type fields
        // but should be fine to select.
        let can_have_typename = matches!(
            parent_type,
            Type::Object(_) | Type::Interface(_) | Type::Union(_)
        );
        if can_have_typename {
            if name == self.typename_field_name {
                return Some(TYPENAME_FIELD_ID);
            }
            // TODO(inanc): Also check if the parent type is fetchable?
            if name == self.fetch_token_field_name {
                return Some(FETCH_TOKEN_FIELD_ID);
            }
            if name == self.clientid_field_name {
                return Some(CLIENTID_FIELD_ID);
            }
            if name == self.strongid_field_name {
                return Some(STRONGID_FIELD_ID);
            }
            if name == self.is_fulfilled_field_name {
                return Some(IS_FULFILLED_FIELD_ID);
            }
        }

        let fields = match parent_type {
            Type::Object(id) => {
                let object = self.object(id);
                &object.fields
            }
            Type::Interface(id) => {
                let interface = self.interface(id);
                &interface.fields
            }
            // Unions don't have any fields, but can have selections like __typename
            // or a field with @fixme_fat_interface
            Type::Union(_) => return None,
            _ => panic!(
                "Cannot get field {} on type '{:?}', this type does not have fields",
                name,
                self.get_type_name(parent_type)
            ),
        };
        fields
            .iter()
            .find(|field_id| {
                let field = self.field(**field_id);
                field.name.item == name
            })
            .cloned()
    }

    fn unchecked_argument_type_sentinel(&self) -> &TypeReference<Type> {
        self.unchecked_argument_type_sentinel.as_ref().unwrap()
    }

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

    fn input_objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a InputObject> + 'a> {
        if self.input_objects.map.len() < self.flatbuffer_schema().input_objects.len() {
            for i in 0..self.flatbuffer_schema().input_objects.len() {
                let id = InputObjectID(i.try_into().unwrap());
                self.input_object(id);
            }
        }

        Box::new(self.input_objects.map.iter().map(|ref_| *ref_.value()))
    }

    fn enums<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Enum> + 'a> {
        if self.enums.map.len() < self.flatbuffer_schema().enums.len() {
            for i in 0..self.flatbuffer_schema().enums.len() {
                let id = EnumID(i.try_into().unwrap());
                self.enum_(id);
            }
        }

        Box::new(self.enums.map.iter().map(|ref_| *ref_.value()))
    }

    fn scalars<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Scalar> + 'a> {
        if self.scalars.map.len() < self.flatbuffer_schema().scalars.len() {
            for i in 0..self.flatbuffer_schema().scalars.len() {
                let id = ScalarID(i.try_into().unwrap());
                self.scalar(id);
            }
        }

        Box::new(self.scalars.map.iter().map(|ref_| *ref_.value()))
    }

    fn fields<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Field> + 'a> {
        if self.fields.map.len() < self.flatbuffer_schema().fields.len() {
            for i in 0..self.flatbuffer_schema().fields.len() {
                let id = FieldID(i.try_into().unwrap());
                self.field(id);
            }
        }

        Box::new(self.fields.map.iter().map(|ref_| *ref_.value()))
    }

    fn objects<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Object> + 'a> {
        if self.objects.map.len() < self.flatbuffer_schema().objects.len() {
            for i in 0..self.flatbuffer_schema().objects.len() {
                let id = ObjectID(i.try_into().unwrap());
                self.object(id);
            }
        }

        Box::new(self.objects.map.iter().map(|ref_| *ref_.value()))
    }

    fn unions<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Union> + 'a> {
        if self.unions.map.len() < self.flatbuffer_schema().unions.len() {
            for i in 0..self.flatbuffer_schema().unions.len() {
                let id = UnionID(i.try_into().unwrap());
                self.union(id);
            }
        }

        Box::new(self.unions.map.iter().map(|ref_| *ref_.value()))
    }

    fn interfaces<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Interface> + 'a> {
        if self.interfaces.map.len() < self.flatbuffer_schema().interfaces.len() {
            for i in 0..self.flatbuffer_schema().interfaces.len() {
                let id = InterfaceID(i.try_into().unwrap());
                self.interface(id);
            }
        }

        Box::new(self.interfaces.map.iter().map(|ref_| *ref_.value()))
    }
}

struct Cache<K: Hash + Eq, V: 'static> {
    map: DashMap<K, &'static V, FnvBuildHasher>,
}
impl<K: Hash + Eq, V> Cache<K, V> {
    fn new() -> Self {
        Self {
            map: DashMap::with_hasher(FnvBuildHasher::default()),
        }
    }

    fn get(&self, key: K, f: impl FnOnce() -> V) -> &V {
        *self
            .map
            .entry(key)
            .or_insert_with(|| Box::leak(Box::new(f())))
    }
}

#[cfg(test)]
mod tests {
    use common::DiagnosticsResult;

    use super::*;
    use crate::build_schema;
    use crate::flatbuffer::serialize::serialize_as_flatbuffer;

    #[test]
    fn all_scalars() -> DiagnosticsResult<()> {
        let sdl = "
        type Query { id: ID }
        ";
        let sdl_schema = build_schema(sdl)?.unwrap_in_memory_impl();
        let bytes = serialize_as_flatbuffer(&sdl_schema);
        let fb_schema = SchemaWrapper::from_vec(bytes);

        let mut scalar_names = fb_schema
            .scalars()
            .map(|scalar| scalar.name.item.0.lookup())
            .collect::<Vec<&str>>();
        scalar_names.sort();
        assert_eq!(
            scalar_names,
            vec!["Boolean", "Float", "ID", "Int", "String"]
        );

        Ok(())
    }

    #[test]
    fn all_scalars_with_lookup() -> DiagnosticsResult<()> {
        let sdl = "
        type Query { id: ID }
        ";
        let sdl_schema = build_schema(sdl)?.unwrap_in_memory_impl();
        let bytes = serialize_as_flatbuffer(&sdl_schema);
        let fb_schema = SchemaWrapper::from_vec(bytes);

        // Look up a scalar type to populate cache
        let user_type = fb_schema.get_type("String".intern()).unwrap();
        fb_schema.scalar(user_type.get_scalar_id().unwrap());

        let mut scalar_names = fb_schema
            .scalars()
            .map(|scalar| scalar.name.item.0.lookup())
            .collect::<Vec<&str>>();
        scalar_names.sort();
        assert_eq!(
            scalar_names,
            vec!["Boolean", "Float", "ID", "Int", "String"]
        );

        Ok(())
    }

    #[test]
    fn all_types() -> DiagnosticsResult<()> {
        let sdl = "
        type Query { id: ID }
        type User { id: ID }
        type MailingAddress { id: ID }
        type Country { id: ID }
        ";
        let sdl_schema = build_schema(sdl)?.unwrap_in_memory_impl();
        let bytes = serialize_as_flatbuffer(&sdl_schema);
        let fb_schema = SchemaWrapper::from_vec(bytes);

        let mut object_names = fb_schema
            .objects()
            .map(|object| object.name.item.0.lookup())
            .collect::<Vec<&str>>();
        object_names.sort();
        assert_eq!(
            object_names,
            vec!["Country", "MailingAddress", "Query", "User"]
        );

        Ok(())
    }

    #[test]
    fn all_types_with_lookup() -> DiagnosticsResult<()> {
        let sdl = "
        type Query { id: ID }
        type User { id: ID }
        type MailingAddress { id: ID }
        type Country { id: ID }
        ";
        let sdl_schema = build_schema(sdl)?.unwrap_in_memory_impl();
        let bytes = serialize_as_flatbuffer(&sdl_schema);
        let fb_schema = SchemaWrapper::from_vec(bytes);

        // Look up User type to populate cache
        let user_type = fb_schema.get_type("User".intern()).unwrap();
        fb_schema.object(user_type.get_object_id().unwrap());

        let mut object_names = fb_schema
            .objects()
            .map(|object| object.name.item.0.lookup())
            .collect::<Vec<&str>>();
        object_names.sort();
        assert_eq!(
            object_names,
            vec!["Country", "MailingAddress", "Query", "User"]
        );

        Ok(())
    }
}