pub(crate) mod prune_inacessible;
pub(crate) mod strip_schema_internals;

use std::{
    hash::{Hash as _, Hasher},
    sync::Arc,
};

use graphql_tools::static_graphql::schema::{Definition, Document, TypeDefinition};
use prune_inacessible::PruneInaccessible;
use strip_schema_internals::StripSchemaInternals;

#[derive(Debug)]
pub struct ConsumerSchema {
    pub document: Arc<Document>,
    pub hash: u64,
}

impl From<Arc<Document>> for ConsumerSchema {
    fn from(document: Arc<Document>) -> Self {
        let hash = {
            let mut hasher = xxhash_rust::xxh3::Xxh3::new();
            document.hash(&mut hasher);
            hasher.finish()
        };
        Self { document, hash }
    }
}

impl ConsumerSchema {
    pub fn new_from_supergraph(supergraph: &Document) -> Self {
        let document: Arc<Document> = Self::create_consumer_schema(supergraph).into();
        document.into()
    }

    fn create_consumer_schema(supergraph: &Document) -> Document {
        let mut result = PruneInaccessible::prune(supergraph);
        result = StripSchemaInternals::strip_schema_internals(&result);
        // Add introspection schema to the consumer schema
        let introspection_schema = include_str!("introspection_schema.graphql");
        let mut parsed_introspection_schema =
            graphql_tools::parser::schema::parse_schema(introspection_schema).unwrap();

        let query_type_name = result
            .query_type_name()
            .map(|name| name.to_string())
            // SAFETY: Supergraph is guaranteed to have a query type, it's one of the validation rules
            .expect("Query type not found in schema");

        parsed_introspection_schema
            .definitions
            .iter_mut()
            .for_each(|def| {
                match def {
                    Definition::TypeDefinition(TypeDefinition::Object(
                        type_def_in_introspection,
                    )) => {
                        if type_def_in_introspection.name == "Query" {
                            match result.definitions.iter_mut().find(|d| {
                                if let Definition::TypeDefinition(TypeDefinition::Object(
                                    query_def,
                                )) = d
                                {
                                    query_def.name == query_type_name
                                } else {
                                    false
                                }
                            }) {
                                Some(Definition::TypeDefinition(TypeDefinition::Object(
                                    query_def,
                                ))) => {
                                    // Query type already exists, extend it
                                    query_def
                                        .fields
                                        .extend(type_def_in_introspection.fields.clone());
                                }
                                _ => {
                                    // Add the Query type from introspection schema
                                    result.definitions.push(def.clone());
                                }
                            }
                        } else {
                            // Add other types from introspection schema
                            result.definitions.push(def.clone());
                        }
                    }
                    _ => result.definitions.push(def.clone()),
                }
            });
        result
    }
}
