/*
 * 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 std::fmt::Result as FmtResult;
use std::path::PathBuf;

use ::intern::intern;
use ::intern::string_key::Intern;
use ::intern::string_key::StringKey;
use ::intern::Lookup;
use common::InputObjectName;
use common::NamedItem;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::OperationDefinition;
use graphql_ir::ProvidedVariableMetadata;
use graphql_ir::Selection;
use indexmap::IndexMap;
use itertools::Itertools;
use relay_config::JsModuleFormat;
use relay_config::TypegenLanguage;
use relay_transforms::RefetchableDerivedFromMetadata;
use relay_transforms::RefetchableMetadata;
use relay_transforms::RelayDirective;
use relay_transforms::ASSIGNABLE_DIRECTIVE;
use relay_transforms::CHILDREN_CAN_BUBBLE_METADATA_KEY;
use schema::Schema;

use crate::typegen_state::ActorChangeStatus;
use crate::typegen_state::EncounteredEnums;
use crate::typegen_state::EncounteredFragment;
use crate::typegen_state::EncounteredFragments;
use crate::typegen_state::ImportedRawResponseTypes;
use crate::typegen_state::ImportedResolverName;
use crate::typegen_state::ImportedResolvers;
use crate::typegen_state::InputObjectTypes;
use crate::typegen_state::MatchFields;
use crate::typegen_state::RuntimeImports;
use crate::visit::get_data_type;
use crate::visit::get_input_variables_type;
use crate::visit::get_operation_type_export;
use crate::visit::raw_response_selections_to_babel;
use crate::visit::raw_response_visit_selections;
use crate::visit::transform_input_type;
use crate::visit::visit_selections;
use crate::writer::ExactObject;
use crate::writer::InexactObject;
use crate::writer::KeyValuePairProp;
use crate::writer::Prop;
use crate::writer::SortedASTList;
use crate::writer::SortedStringKeyList;
use crate::writer::StringLiteral;
use crate::writer::Writer;
use crate::writer::AST;
use crate::MaskStatus;
use crate::TypegenContext;
use crate::ACTOR_CHANGE_POINT;
use crate::FUTURE_ENUM_VALUE;
use crate::KEY_CLIENTID;
use crate::KEY_DATA;
use crate::KEY_FRAGMENT_SPREADS;
use crate::KEY_FRAGMENT_TYPE;
use crate::KEY_RAW_RESPONSE;
use crate::KEY_TYPENAME;
use crate::KEY_UPDATABLE_FRAGMENT_SPREADS;
use crate::PROVIDED_VARIABLE_TYPE;
use crate::RAW_RESPONSE_TYPE_DIRECTIVE_NAME;
use crate::REACT_RELAY_MULTI_ACTOR;
use crate::VALIDATOR_EXPORT_NAME;

pub(crate) type CustomScalarsImports = HashSet<(StringKey, PathBuf)>;

pub(crate) fn write_operation_type_exports_section(
    typegen_context: &'_ TypegenContext<'_>,
    typegen_operation: &OperationDefinition,
    normalization_operation: &OperationDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    let mut encountered_enums = Default::default();
    let mut encountered_fragments = Default::default();
    let mut imported_resolvers = Default::default();
    let mut actor_change_status = ActorChangeStatus::NoActorChange;
    let mut runtime_imports = RuntimeImports::default();
    let mut custom_scalars = CustomScalarsImports::default();
    let mut input_object_types = Default::default();
    let mut imported_raw_response_types = Default::default();

    let type_selections = visit_selections(
        typegen_context,
        &typegen_operation.selections,
        &mut input_object_types,
        &mut encountered_enums,
        &mut imported_raw_response_types,
        &mut encountered_fragments,
        &mut imported_resolvers,
        &mut actor_change_status,
        &mut custom_scalars,
        &mut runtime_imports,
        None,
    );

    let data_type = get_data_type(
        typegen_context,
        type_selections.into_iter(),
        MaskStatus::Masked, // Queries are never unmasked
        None,
        typegen_operation
            .directives
            .named(*CHILDREN_CAN_BUBBLE_METADATA_KEY)
            .is_some(),
        false, // Query types can never be plural
        &mut encountered_enums,
        &mut encountered_fragments,
        &mut custom_scalars,
    );

    let raw_response_type_and_match_fields =
        if has_raw_response_type_directive(normalization_operation) {
            let mut match_fields = Default::default();
            let raw_response_selections = raw_response_visit_selections(
                typegen_context,
                &normalization_operation.selections,
                &mut encountered_enums,
                &mut match_fields,
                &mut encountered_fragments,
                &mut imported_raw_response_types,
                &mut runtime_imports,
                &mut custom_scalars,
                None,
            );
            Some((
                raw_response_selections_to_babel(
                    typegen_context,
                    raw_response_selections.into_iter(),
                    None,
                    &mut encountered_enums,
                    &mut runtime_imports,
                    &mut custom_scalars,
                ),
                match_fields,
            ))
        } else {
            None
        };

    let refetchable_fragment_name =
        RefetchableDerivedFromMetadata::find(&typegen_operation.directives);
    if refetchable_fragment_name.is_some() {
        runtime_imports.generic_fragment_type = true;
    }

    // Always include 'FragmentRef' for typescript codegen for operations that have fragment spreads
    if typegen_context.project_config.typegen_config.language == TypegenLanguage::TypeScript
        && has_fragment_spread(&typegen_operation.selections)
    {
        runtime_imports.generic_fragment_type = true;
    }

    write_import_actor_change_point(actor_change_status, writer)?;
    runtime_imports.write_runtime_imports(writer)?;
    write_fragment_imports(typegen_context, None, encountered_fragments, writer)?;
    write_relay_resolver_imports(typegen_context, imported_resolvers, writer)?;
    write_split_raw_response_type_imports(typegen_context, imported_raw_response_types, writer)?;

    let mut input_object_types = IndexMap::default();
    let provided_variables_object = generate_provided_variables_type(
        typegen_context,
        normalization_operation,
        &mut input_object_types,
        &mut encountered_enums,
        &mut custom_scalars,
    );

    let input_variables_type = get_input_variables_type(
        typegen_context,
        typegen_operation,
        &mut input_object_types,
        &mut encountered_enums,
        &mut custom_scalars,
    );
    let input_object_types = input_object_types
        .into_iter()
        .map(|(key, val)| (key, val.unwrap_resolved_type()));

    write_enum_definitions(typegen_context, encountered_enums, writer)?;
    write_custom_scalar_imports(custom_scalars, writer)?;
    write_input_object_types(input_object_types, writer)?;

    let variables_identifier = format!("{}$variables", typegen_operation.name.item.0);
    let variables_identifier_key = variables_identifier.as_str().intern();

    writer.write_export_type(&variables_identifier, &input_variables_type.into())?;

    let response_identifier = format!("{}$data", typegen_operation.name.item.0);
    let response_identifier_key = response_identifier.as_str().intern();
    writer.write_export_type(&response_identifier, &data_type)?;

    let raw_response_prop = write_raw_response_and_get_raw_response_prop(
        raw_response_type_and_match_fields,
        writer,
        typegen_operation,
    )?;
    let query_wrapper_type = get_operation_type_export(
        variables_identifier_key,
        response_identifier_key,
        raw_response_prop,
    )?;
    writer.write_export_type(
        typegen_operation.name.item.0.lookup(),
        &query_wrapper_type.into(),
    )?;

    if let Some(provided_variables) = provided_variables_object {
        writer.write_local_type(PROVIDED_VARIABLE_TYPE, &provided_variables)?;
    }

    Ok(())
}

fn write_raw_response_and_get_raw_response_prop(
    raw_response_type_and_match_fields: Option<(AST, MatchFields)>,
    writer: &mut Box<dyn Writer>,
    typegen_operation: &OperationDefinition,
) -> Result<Option<KeyValuePairProp>, std::fmt::Error> {
    if let Some((raw_response_type, match_fields)) = raw_response_type_and_match_fields {
        for (key, ast) in match_fields.0 {
            writer.write_export_type(key.lookup(), &ast)?;
        }
        let raw_response_identifier = format!("{}$rawResponse", typegen_operation.name.item.0);
        writer.write_export_type(&raw_response_identifier, &raw_response_type)?;

        Ok(Some(KeyValuePairProp {
            key: *KEY_RAW_RESPONSE,
            read_only: false,
            optional: false,
            value: AST::Identifier(raw_response_identifier.intern()),
        }))
    } else {
        Ok(None)
    }
}

pub(crate) fn write_split_operation_type_exports_section(
    typegen_context: &'_ TypegenContext<'_>,
    typegen_operation: &OperationDefinition,
    normalization_operation: &OperationDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    let mut encountered_enums = Default::default();
    let mut match_fields = Default::default();
    let mut encountered_fragments = Default::default();
    let mut imported_raw_response_types = Default::default();
    let mut runtime_imports = RuntimeImports::default();
    let mut custom_scalars = CustomScalarsImports::default();

    let raw_response_selections = raw_response_visit_selections(
        typegen_context,
        &normalization_operation.selections,
        &mut encountered_enums,
        &mut match_fields,
        &mut encountered_fragments,
        &mut imported_raw_response_types,
        &mut runtime_imports,
        &mut custom_scalars,
        None,
    );
    let raw_response_type = raw_response_selections_to_babel(
        typegen_context,
        raw_response_selections.into_iter(),
        None,
        &mut encountered_enums,
        &mut runtime_imports,
        &mut custom_scalars,
    );

    runtime_imports.write_runtime_imports(writer)?;
    write_fragment_imports(typegen_context, None, encountered_fragments, writer)?;
    write_split_raw_response_type_imports(typegen_context, imported_raw_response_types, writer)?;

    write_enum_definitions(typegen_context, encountered_enums, writer)?;
    write_custom_scalar_imports(custom_scalars, writer)?;

    for (key, ast) in match_fields.0 {
        writer.write_export_type(key.lookup(), &ast)?;
    }

    writer.write_export_type(typegen_operation.name.item.0.lookup(), &raw_response_type)?;

    Ok(())
}

pub(crate) fn write_fragment_type_exports_section(
    typegen_context: &'_ TypegenContext<'_>,
    fragment_definition: &FragmentDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    // Assignable fragments do not require $data and $ref type exports, and their aliases
    let is_assignable_fragment = fragment_definition
        .directives
        .named(*ASSIGNABLE_DIRECTIVE)
        .is_some();

    let mut encountered_enums = Default::default();
    let mut encountered_fragments = Default::default();
    let mut imported_resolvers = Default::default();
    let mut actor_change_status = ActorChangeStatus::NoActorChange;
    let mut custom_scalars = CustomScalarsImports::default();
    let mut input_object_types = Default::default();
    let mut runtime_imports = RuntimeImports {
        generic_fragment_type: true,
        ..Default::default()
    };
    let mut imported_raw_response_types = Default::default();

    let mut type_selections = visit_selections(
        typegen_context,
        &fragment_definition.selections,
        &mut input_object_types,
        &mut encountered_enums,
        &mut imported_raw_response_types,
        &mut encountered_fragments,
        &mut imported_resolvers,
        &mut actor_change_status,
        &mut custom_scalars,
        &mut runtime_imports,
        None,
    );
    if !fragment_definition.type_condition.is_abstract_type() {
        let num_concrete_selections = type_selections
            .iter()
            .filter(|sel| sel.get_enclosing_concrete_type().is_some())
            .count();
        if num_concrete_selections <= 1 {
            for selection in type_selections.iter_mut().filter(|sel| sel.is_typename()) {
                selection.set_concrete_type(fragment_definition.type_condition);
            }
        }
    }

    let data_type = fragment_definition.name.item;
    let data_type_name = format!("{}$data", data_type);

    let ref_type_data_property = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_DATA,
        optional: true,
        read_only: true,
        value: AST::Identifier(data_type_name.as_str().intern()),
    });
    let fragment_name = fragment_definition.name.item.0;
    let ref_type_fragment_spreads_property = Prop::KeyValuePair(KeyValuePairProp {
        key: if typegen_context.generating_updatable_types {
            *KEY_UPDATABLE_FRAGMENT_SPREADS
        } else {
            *KEY_FRAGMENT_SPREADS
        },
        optional: false,
        read_only: true,
        value: AST::FragmentReference(SortedStringKeyList::new(vec![fragment_name])),
    });
    let is_plural_fragment = is_plural(fragment_definition);
    let mut ref_type = AST::InexactObject(InexactObject::new(vec![
        ref_type_data_property,
        ref_type_fragment_spreads_property,
    ]));
    if is_plural_fragment {
        ref_type = AST::ReadOnlyArray(Box::new(ref_type));
    }

    let mask_status = if RelayDirective::is_unmasked_fragment_definition(fragment_definition) {
        MaskStatus::Unmasked
    } else {
        MaskStatus::Masked
    };

    let data_type = get_data_type(
        typegen_context,
        type_selections.into_iter(),
        mask_status,
        if mask_status == MaskStatus::Unmasked {
            None
        } else {
            Some(fragment_name)
        },
        fragment_definition
            .directives
            .named(*CHILDREN_CAN_BUBBLE_METADATA_KEY)
            .is_some(),
        is_plural_fragment,
        &mut encountered_enums,
        &mut encountered_fragments,
        &mut custom_scalars,
    );

    write_import_actor_change_point(actor_change_status, writer)?;
    let input_object_types = input_object_types
        .into_iter()
        .map(|(key, val)| (key, val.unwrap_resolved_type()));

    write_input_object_types(input_object_types, writer)?;
    write_fragment_imports(
        typegen_context,
        Some(fragment_definition.name.item),
        encountered_fragments,
        writer,
    )?;
    write_split_raw_response_type_imports(typegen_context, imported_raw_response_types, writer)?;

    write_enum_definitions(typegen_context, encountered_enums, writer)?;
    write_custom_scalar_imports(custom_scalars, writer)?;

    runtime_imports.write_runtime_imports(writer)?;
    write_relay_resolver_imports(typegen_context, imported_resolvers, writer)?;

    let refetchable_metadata = RefetchableMetadata::find(&fragment_definition.directives);
    let fragment_type_name = format!("{}$fragmentType", fragment_name);
    writer.write_export_fragment_type(&fragment_type_name)?;
    if let Some(refetchable_metadata) = refetchable_metadata {
        let variables_name = format!("{}$variables", refetchable_metadata.operation_name);
        match typegen_context.project_config.js_module_format {
            JsModuleFormat::CommonJS => {
                if typegen_context.has_unified_output {
                    writer.write_import_fragment_type(
                        &[&variables_name],
                        &format!("./{}.graphql", refetchable_metadata.operation_name),
                    )?;
                } else {
                    writer.write_any_type_definition(&variables_name)?;
                }
            }
            JsModuleFormat::Haste => {
                writer.write_import_fragment_type(
                    &[&variables_name],
                    &format!("{}.graphql", refetchable_metadata.operation_name),
                )?;
            }
        }
    }

    if !is_assignable_fragment {
        writer.write_export_type(&data_type_name, &data_type)?;
        writer.write_export_type(&format!("{}$key", fragment_definition.name.item), &ref_type)?;
    }

    Ok(())
}

fn write_fragment_imports(
    typegen_context: &'_ TypegenContext<'_>,
    fragment_name_to_skip: Option<FragmentDefinitionName>,
    encountered_fragments: EncounteredFragments,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    for current_referenced_fragment in encountered_fragments.0.into_iter().sorted() {
        let (current_referenced_fragment, fragment_type_name) = match current_referenced_fragment {
            EncounteredFragment::Key(current_referenced_fragment) => (
                current_referenced_fragment,
                format!("{}$key", current_referenced_fragment),
            ),
            EncounteredFragment::Spread(current_referenced_fragment) => (
                current_referenced_fragment,
                format!("{}$fragmentType", current_referenced_fragment),
            ),
            EncounteredFragment::Data(current_referenced_fragment) => (
                current_referenced_fragment,
                format!("{}$data", current_referenced_fragment),
            ),
        };

        let should_write_current_referenced_fragment = fragment_name_to_skip
            .map_or(true, |fragment_name_to_skip| {
                fragment_name_to_skip != current_referenced_fragment
            });

        if !should_write_current_referenced_fragment {
            continue;
        }

        match typegen_context.project_config.js_module_format {
            JsModuleFormat::CommonJS => {
                if typegen_context.has_unified_output {
                    writer.write_import_fragment_type(
                        &[&fragment_type_name],
                        &format!("./{}.graphql", current_referenced_fragment),
                    )?;
                } else {
                    let fragment_location = typegen_context
                        .fragment_locations
                        .location(&current_referenced_fragment)
                        .unwrap_or_else(|| {
                            panic!(
                                "Expected location for fragment {}.",
                                current_referenced_fragment
                            )
                        });

                    let path_for_artifact =
                        typegen_context.project_config.create_path_for_artifact(
                            fragment_location.source_location(),
                            current_referenced_fragment.to_string(),
                        );

                    let fragment_import_path =
                        typegen_context.project_config.js_module_import_path(
                            typegen_context.definition_source_location,
                            path_for_artifact.to_str().unwrap().intern(),
                        );

                    writer.write_import_fragment_type(
                        &[&fragment_type_name],
                        &format!("./{}.graphql", fragment_import_path),
                    )?;
                }
            }
            JsModuleFormat::Haste => {
                writer.write_import_fragment_type(
                    &[&fragment_type_name],
                    &format!("{}.graphql", current_referenced_fragment),
                )?;
            }
        }
    }
    Ok(())
}

fn write_import_actor_change_point(
    actor_change_status: ActorChangeStatus,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    if matches!(actor_change_status, ActorChangeStatus::HasActorChange) {
        writer.write_import_type(&[ACTOR_CHANGE_POINT], REACT_RELAY_MULTI_ACTOR)
    } else {
        Ok(())
    }
}

fn write_relay_resolver_imports(
    typegen_context: &'_ TypegenContext<'_>,
    mut imported_resolvers: ImportedResolvers,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    // We don't need to import resolver modules in the type-generation
    // they should be imported in the codegen.
    if matches!(
        typegen_context.project_config.typegen_config.language,
        TypegenLanguage::TypeScript
    ) {
        return Ok(());
    }

    imported_resolvers.0.sort_keys();
    for resolver in imported_resolvers.0.values() {
        match resolver.resolver_name {
            ImportedResolverName::Default(name) => {
                writer.write_import_module_default(name.lookup(), resolver.import_path.lookup())?;
            }
            ImportedResolverName::Named { name, import_as } => {
                writer.write_import_module_named(
                    name.lookup(),
                    Some(import_as.lookup()),
                    resolver.import_path.lookup(),
                )?;
            }
        }
        writer.write(&resolver.resolver_type)?;
    }
    Ok(())
}

fn write_split_raw_response_type_imports(
    typegen_context: &'_ TypegenContext<'_>,
    mut imported_raw_response_types: ImportedRawResponseTypes,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    if imported_raw_response_types.0.is_empty() {
        return Ok(());
    }

    imported_raw_response_types.0.sort_keys();
    for (imported_raw_response_type, imported_raw_response_document_location) in
        imported_raw_response_types.0
    {
        match typegen_context.project_config.js_module_format {
            JsModuleFormat::CommonJS => {
                if typegen_context.has_unified_output {
                    writer.write_import_fragment_type(
                        &[imported_raw_response_type.lookup()],
                        &format!("./{}.graphql", imported_raw_response_type),
                    )?;
                } else if let Some(imported_raw_response_document_location) =
                    imported_raw_response_document_location
                {
                    let path_for_artifact =
                        typegen_context.project_config.create_path_for_artifact(
                            imported_raw_response_document_location.source_location(),
                            imported_raw_response_type.to_string(),
                        );

                    let artifact_import_path =
                        typegen_context.project_config.js_module_import_path(
                            typegen_context.definition_source_location,
                            path_for_artifact.to_str().unwrap().intern(),
                        );

                    writer.write_import_fragment_type(
                        &[imported_raw_response_type.lookup()],
                        &format!("./{}.graphql", artifact_import_path),
                    )?;
                } else {
                    writer.write_any_type_definition(imported_raw_response_type.lookup())?;
                }
            }
            JsModuleFormat::Haste => {
                writer.write_import_fragment_type(
                    &[imported_raw_response_type.lookup()],
                    &format!("{}.graphql", imported_raw_response_type),
                )?;
            }
        }
    }

    Ok(())
}

fn write_enum_definitions(
    typegen_context: &'_ TypegenContext<'_>,
    encountered_enums: EncounteredEnums,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    let enum_ids = encountered_enums.into_sorted_vec(typegen_context.schema);
    for enum_id in enum_ids {
        let enum_type = typegen_context.schema.enum_(enum_id);
        if let Some(enum_module_suffix) = &typegen_context
            .project_config
            .typegen_config
            .enum_module_suffix
        {
            writer.write_import_type(
                &[enum_type.name.item.lookup()],
                &format!("{}{}", enum_type.name.item, enum_module_suffix),
            )?;
        } else {
            let mut members: Vec<AST> = enum_type
                .values
                .iter()
                .map(|enum_value| AST::StringLiteral(StringLiteral(enum_value.value)))
                .collect();

            if !typegen_context
                .project_config
                .typegen_config
                .flow_typegen
                .no_future_proof_enums
            {
                members.push(AST::StringLiteral(StringLiteral(*FUTURE_ENUM_VALUE)));
            }

            writer.write_export_type(
                enum_type.name.item.lookup(),
                &AST::Union(SortedASTList::new(members)),
            )?;
        }
    }
    Ok(())
}

fn generate_provided_variables_type(
    typegen_context: &'_ TypegenContext<'_>,
    node: &OperationDefinition,
    input_object_types: &mut InputObjectTypes,
    encountered_enums: &mut EncounteredEnums,
    custom_scalars: &mut CustomScalarsImports,
) -> Option<AST> {
    let fields = node
        .variable_definitions
        .iter()
        .filter_map(|def| {
            def.directives
                .named(ProvidedVariableMetadata::directive_name())?;

            let provider_func = AST::Callable(Box::new(transform_input_type(
                typegen_context,
                &def.type_,
                input_object_types,
                encountered_enums,
                custom_scalars,
            )));
            let provider_module = Prop::KeyValuePair(KeyValuePairProp {
                key: "get".intern(),
                read_only: true,
                optional: false,
                value: provider_func,
            });
            Some(Prop::KeyValuePair(KeyValuePairProp {
                key: def.name.item.0,
                read_only: true,
                optional: false,
                value: AST::ExactObject(ExactObject::new(vec![provider_module])),
            }))
        })
        .collect_vec();
    if !fields.is_empty() {
        Some(AST::ExactObject(ExactObject::new(fields)))
    } else {
        None
    }
}

fn write_input_object_types(
    input_object_types: impl Iterator<Item = (InputObjectName, ExactObject)>,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    for (type_identifier, input_object_type) in input_object_types {
        writer.write_export_type(type_identifier.lookup(), &input_object_type.into())?;
    }
    Ok(())
}

/// Write the assignable fragment validator function.
///
/// Validators accept an item which *may* be valid for assignment and returns either
/// a sentinel value or something which is necessarily valid for assignment.
///
/// The types of the validator:
///
/// - For fragments whose type condition is abstract:
/// ({ __id: string, __isFragmentName: ?string, $fragmentSpreads: FragmentRefType }) =>
///   ({ __id: string, __isFragmentName: string, $fragmentSpreads: FragmentRefType })
///   | false
///
/// - For fragments whose type condition is concrete:
/// ({ __id: string, __typename: string, $fragmentSpreads: FragmentRefType }) =>
///   ({ __id: string, __typename: FragmentType, $fragmentSpreads: FragmentRefType })
///   | false
///
/// Validators' runtime behavior checks for the presence of the __isFragmentName marker
/// (for abstract fragment types) or a matching concrete type (for concrete fragment
/// types), and returns false iff the parameter didn't pass.
/// Validators return the parameter (unmodified) if it did pass validation, but with
/// a changed flowtype.
pub(crate) fn write_validator_function(
    typegen_context: &'_ TypegenContext<'_>,
    fragment_definition: &FragmentDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    if fragment_definition.type_condition.is_abstract_type() {
        write_abstract_validator_function(
            typegen_context.project_config.typegen_config.language,
            fragment_definition,
            writer,
        )
    } else {
        write_concrete_validator_function(typegen_context, fragment_definition, writer)
    }
}

/// Write an abstract validator function. Flow example:
/// function validate(value/*: {
///   +__id: string,
///   +$fragmentSpreads: Assignable_node$fragmentType,
///   +__isAssignable_node: ?string,
///   ...
/// }*/)/*: ({
///   +__id: string,
///   +$fragmentSpreads: Assignable_node$fragmentType,
///   +__isAssignable_node: string,
///   ...
/// } | false)*/ {
///   return value.__isAssignable_node != null ? (value/*: any*/) : null
/// };
fn write_abstract_validator_function(
    language: TypegenLanguage,
    fragment_definition: &FragmentDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    let fragment_name = fragment_definition.name.item.0.lookup();
    let abstract_fragment_spread_marker = format!("__is{}", fragment_name).intern();
    let id_prop = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_CLIENTID,
        value: AST::String,
        read_only: true,
        optional: false,
    });
    let fragment_spread_prop = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_FRAGMENT_SPREADS,
        value: AST::Identifier(format!("{}{}", fragment_name, *KEY_FRAGMENT_TYPE).intern()),
        read_only: true,
        optional: false,
    });
    let parameter_discriminator = Prop::KeyValuePair(KeyValuePairProp {
        key: abstract_fragment_spread_marker,
        value: AST::String,
        read_only: true,
        optional: true,
    });
    let return_value_discriminator = Prop::KeyValuePair(KeyValuePairProp {
        key: abstract_fragment_spread_marker,
        value: AST::String,
        read_only: true,
        optional: false,
    });

    let parameter_type = AST::InexactObject(InexactObject::new(vec![
        id_prop.clone(),
        fragment_spread_prop.clone(),
        parameter_discriminator,
    ]));
    let return_type = AST::Union(SortedASTList::new(vec![
        AST::InexactObject(InexactObject::new(vec![
            id_prop,
            fragment_spread_prop,
            return_value_discriminator,
        ])),
        AST::RawType(intern!("false")),
    ]));

    let (open_comment, close_comment) = match language {
        TypegenLanguage::Flow | TypegenLanguage::JavaScript => ("/*", "*/"),
        TypegenLanguage::TypeScript => ("", ""),
    };

    write!(
        writer,
        "function {}(value{}: ",
        VALIDATOR_EXPORT_NAME, &open_comment
    )?;

    writer.write(&parameter_type)?;
    write!(writer, "{}){}: ", &close_comment, &open_comment)?;
    writer.write(&return_type)?;
    write!(
        writer,
        "{} {{\n  return value.{} != null ? (value{}: ",
        &close_comment,
        abstract_fragment_spread_marker.lookup(),
        open_comment
    )?;
    writer.write(&AST::Any)?;
    write!(writer, "{}) : false;\n}}", &close_comment)?;

    Ok(())
}

/// Write a concrete validator function. Flow example:
/// function validate(value/*: {
///   +__id: string,
///   +$fragmentSpreads: Assignable_user$fragmentType,
///   +__typename: ?string,
///   ...
/// }*/)/*: ({
///   +__id: string,
///   +$fragmentSpreads: Assignable_user$fragmentType,
///   +__typename: 'User',
///   ...
/// } | false)*/ {
///   return value.__typename === 'User' ? (value/*: any*/) : null
/// };
fn write_concrete_validator_function(
    typegen_context: &'_ TypegenContext<'_>,
    fragment_definition: &FragmentDefinition,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    let fragment_name = fragment_definition.name.item.0.lookup();
    let concrete_typename = typegen_context
        .schema
        .get_type_name(fragment_definition.type_condition);
    let id_prop = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_CLIENTID,
        value: AST::String,
        read_only: true,
        optional: false,
    });
    let fragment_spread_prop = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_FRAGMENT_SPREADS,
        value: AST::Identifier(format!("{}{}", fragment_name, *KEY_FRAGMENT_TYPE).intern()),
        read_only: true,
        optional: false,
    });
    let parameter_discriminator = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_TYPENAME,
        value: AST::String,
        read_only: true,
        optional: false,
    });
    let return_value_discriminator = Prop::KeyValuePair(KeyValuePairProp {
        key: *KEY_TYPENAME,
        value: AST::StringLiteral(StringLiteral(concrete_typename)),
        read_only: true,
        optional: false,
    });

    let parameter_type = AST::InexactObject(InexactObject::new(vec![
        id_prop.clone(),
        fragment_spread_prop.clone(),
        parameter_discriminator,
    ]));
    let return_type = AST::Union(SortedASTList::new(vec![
        AST::InexactObject(InexactObject::new(vec![
            id_prop,
            fragment_spread_prop,
            return_value_discriminator,
        ])),
        AST::RawType(intern!("false")),
    ]));

    let (open_comment, close_comment) = match typegen_context.project_config.typegen_config.language
    {
        TypegenLanguage::Flow | TypegenLanguage::JavaScript => ("/*", "*/"),
        TypegenLanguage::TypeScript => ("", ""),
    };

    write!(
        writer,
        "function {}(value{}: ",
        VALIDATOR_EXPORT_NAME, &open_comment
    )?;
    writer.write(&parameter_type)?;
    write!(writer, "{}){}: ", &close_comment, &open_comment)?;
    writer.write(&return_type)?;
    write!(
        writer,
        "{} {{\n  return value.{} === '{}' ? (value{}: ",
        &close_comment,
        KEY_TYPENAME.lookup(),
        concrete_typename.lookup(),
        open_comment
    )?;
    writer.write(&AST::Any)?;
    write!(writer, "{}) : false;\n}}", &close_comment)?;

    Ok(())
}

fn is_plural(node: &FragmentDefinition) -> bool {
    RelayDirective::find(&node.directives).map_or(false, |relay_directive| relay_directive.plural)
}

fn has_fragment_spread(selections: &[Selection]) -> bool {
    selections.iter().any(|selection| match selection {
        Selection::FragmentSpread(_) => true,
        Selection::Condition(condition) => has_fragment_spread(&condition.selections),
        Selection::LinkedField(linked_field) => has_fragment_spread(&linked_field.selections),
        Selection::InlineFragment(inline_fragment) => {
            has_fragment_spread(&inline_fragment.selections)
        }
        Selection::ScalarField(_) => false,
    })
}

pub fn has_raw_response_type_directive(operation: &OperationDefinition) -> bool {
    operation
        .directives
        .named(*RAW_RESPONSE_TYPE_DIRECTIVE_NAME)
        .is_some()
}

fn write_custom_scalar_imports(
    custom_scalars: CustomScalarsImports,
    writer: &mut Box<dyn Writer>,
) -> FmtResult {
    for (name, path) in custom_scalars.iter().sorted_by_key(|(key, _)| *key) {
        writer.write_import_type(&[name.lookup()], path.to_str().unwrap())?
    }

    Ok(())
}


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

use std::cmp::Ordering;
use std::fmt::Result as FmtResult;
use std::fmt::Write;
use std::ops::Deref;

use intern::string_key::StringKey;
use intern::Lookup;
use relay_config::TypegenConfig;
use relay_config::TypegenLanguage;

use crate::flow::FlowPrinter;
use crate::javascript::JavaScriptPrinter;
use crate::typescript::TypeScriptPrinter;
use crate::FUTURE_ENUM_VALUE;
use crate::KEY_FRAGMENT_SPREADS;
use crate::KEY_FRAGMENT_TYPE;
use crate::KEY_TYPENAME;

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum AST {
    Union(SortedASTList),
    ReadOnlyArray(Box<AST>),
    Nullable(Box<AST>),
    NonNullable(Box<AST>),
    Identifier(StringKey),
    /// Printed as is, should be valid Flow code.
    RawType(StringKey),
    String,
    StringLiteral(StringLiteral),
    /// Prints as `"%other" with a comment explaining open enums.
    OtherTypename,
    Local3DPayload(StringKey, Box<AST>),
    ExactObject(ExactObject),
    InexactObject(InexactObject),
    Number,
    Boolean,
    Callable(Box<AST>),
    Any,
    Mixed,
    FragmentReference(SortedStringKeyList),
    FragmentReferenceType(StringKey),
    ReturnTypeOfFunctionWithName(StringKey),
    ReturnTypeOfMethodCall(Box<AST>, StringKey),
    ActorChangePoint(Box<AST>),
    AssertFunctionType(FunctionTypeAssertion),
    GenericType {
        outer: StringKey,
        inner: Box<AST>,
    },
    PropertyType {
        type_: Box<AST>,
        property_name: StringKey,
    },
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct FunctionTypeAssertion {
    pub function_name: StringKey,
    pub arguments: Vec<KeyValuePairProp>,
    pub return_type: Box<AST>,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct SortedASTList(Vec<AST>);

impl Deref for SortedASTList {
    type Target = Vec<AST>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl SortedASTList {
    pub fn new(mut members: Vec<AST>) -> Self {
        members.sort();
        Self(members)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExactObject(Vec<Prop>);

impl Deref for ExactObject {
    type Target = Vec<Prop>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl ExactObject {
    pub fn new(mut props: Vec<Prop>) -> Self {
        props.sort();
        Self(props)
    }

    /// Return the object's literal typename, if it is found. Typenames cannot be
    /// modified at runtime, so they will never be stored in getter/setters.
    pub fn typename_literal(&self) -> Option<StringLiteral> {
        self.0.iter().find_map(|p| match p {
            Prop::KeyValuePair(kvp) => {
                if kvp.key == *KEY_TYPENAME {
                    match kvp.value {
                        AST::StringLiteral(s) => Some(s),
                        _ => None,
                    }
                } else {
                    None
                }
            }
            _ => None,
        })
    }
}

impl From<ExactObject> for AST {
    fn from(other: ExactObject) -> AST {
        AST::ExactObject(other)
    }
}

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

/// Sort as follows:
/// - An object containing a typename field that is a string literal comes
///   before an object without a typename field that is a string literal
/// - If both objects contain string literal typenames, compare those first
///   and only compare objects if the items are tied
/// - If neither object contains a string literal typename, default to the
///   default order for the inner object
impl Ord for ExactObject {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self.typename_literal(), other.typename_literal()) {
            (None, None) => self.0.cmp(&other.0),
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s1), Some(s2)) => match s1.cmp(&s2) {
                Ordering::Less => Ordering::Less,
                Ordering::Equal => self.0.cmp(&other.0),
                Ordering::Greater => Ordering::Greater,
            },
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InexactObject(Vec<Prop>);

impl Deref for InexactObject {
    type Target = Vec<Prop>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl InexactObject {
    pub fn new(mut props: Vec<Prop>) -> Self {
        props.sort();
        Self(props)
    }

    /// Return the object's literal typename, if it is found. Typenames cannot be
    /// modified at runtime, so they will never be stored in getter/setters.
    pub fn typename_literal(&self) -> Option<StringLiteral> {
        self.0.iter().find_map(|p| match p {
            Prop::KeyValuePair(kvp) => {
                if kvp.key == *KEY_TYPENAME {
                    match kvp.value {
                        AST::StringLiteral(s) => Some(s),
                        _ => None,
                    }
                } else {
                    None
                }
            }
            _ => None,
        })
    }
}

/// Sort as follows:
/// - An object containing a typename field that is a string literal comes
///   before an object without a typename field that is a string literal
/// - If both objects contain string literal typenames, compare those first
///   and only compare objects if the items are tied
/// - If neither object contains a string literal typename, default to the
///   default order.
impl Ord for InexactObject {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self.typename_literal(), other.typename_literal()) {
            (None, None) => self.cmp(other),
            (None, Some(_)) => Ordering::Greater,
            (Some(_), None) => Ordering::Less,
            (Some(s1), Some(s2)) => match s1.cmp(&s2) {
                Ordering::Less => Ordering::Less,
                Ordering::Equal => self.cmp(other),
                Ordering::Greater => Ordering::Greater,
            },
        }
    }
}

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

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct SortedStringKeyList(Vec<StringKey>);

impl Deref for SortedStringKeyList {
    type Target = Vec<StringKey>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl SortedStringKeyList {
    pub fn new(mut props: Vec<StringKey>) -> Self {
        // We can sort unstably, because we don't care that StringKey's are re-ordered.
        // Unlike sorting stably, sorting unstably doesn't allocated extra memory.
        props.sort_unstable();
        Self(props)
    }
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Prop {
    KeyValuePair(KeyValuePairProp),
    Spread(SpreadProp),
    GetterSetterPair(GetterSetterPairProp),
}

impl From<KeyValuePairProp> for Prop {
    fn from(other: KeyValuePairProp) -> Self {
        Prop::KeyValuePair(other)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct KeyValuePairProp {
    pub key: StringKey,
    pub value: AST,
    pub read_only: bool,
    pub optional: bool,
}

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

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

impl Prop {
    fn get_sort_order_key(&self) -> (PropSortOrder, StringKey) {
        match self {
            Prop::KeyValuePair(kvp) => (
                if kvp.key == *KEY_TYPENAME {
                    PropSortOrder::Typename
                } else if kvp.key == *KEY_FRAGMENT_SPREADS || kvp.key == *KEY_FRAGMENT_TYPE {
                    PropSortOrder::FragmentSpread
                } else {
                    PropSortOrder::KeyValuePair
                },
                kvp.key,
            ),
            Prop::GetterSetterPair(pair) => (PropSortOrder::GetterSetterPair, pair.key),
            Prop::Spread(spread) => (PropSortOrder::ObjectSpread, spread.value),
        }
    }
}

#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
enum PropSortOrder {
    Typename,
    KeyValuePair,
    GetterSetterPair,
    ObjectSpread,
    FragmentSpread,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpreadProp {
    pub value: StringKey,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GetterSetterPairProp {
    pub key: StringKey,
    pub getter_return_value: AST,
    pub setter_parameter: AST,
}

/// A newtype wrapper around StringKey that sorts StringKey's in
/// the following fashion:
/// - '%future added value' goes last
/// - Otherwise, alphabetically according to the string value
///
/// StringKey, by default, will sort alphabetically.
///
/// This exception is to preserve the "natural" order of enums, which
/// are Union's containing StringLiteral's, i.e. we want
/// "%future added value" to follow the variants.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct StringLiteral(pub StringKey);

impl Deref for StringLiteral {
    type Target = StringKey;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Ord for StringLiteral {
    fn cmp(&self, other: &Self) -> Ordering {
        match (self.0 == *FUTURE_ENUM_VALUE, other.0 == *FUTURE_ENUM_VALUE) {
            (true, true) => Ordering::Equal,
            (true, false) => Ordering::Greater,
            (false, true) => Ordering::Less,
            (false, false) => self.lookup().cmp(other.lookup()),
        }
    }
}

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

pub trait Writer: Write {
    fn into_string(self: Box<Self>) -> String;

    fn get_runtime_fragment_import(&self) -> &'static str;

    fn write(&mut self, ast: &AST) -> FmtResult;

    fn write_local_type(&mut self, name: &str, ast: &AST) -> FmtResult;

    fn write_export_type(&mut self, name: &str, ast: &AST) -> FmtResult;

    fn write_import_module_default(&mut self, name: &str, from: &str) -> FmtResult;

    fn write_import_module_named(
        &mut self,
        name: &str,
        import_as: Option<&str>,
        from: &str,
    ) -> FmtResult;

    fn write_import_type(&mut self, types: &[&str], from: &str) -> FmtResult;

    fn write_import_fragment_type(&mut self, types: &[&str], from: &str) -> FmtResult;

    fn write_export_fragment_type(&mut self, name: &str) -> FmtResult;

    fn write_export_fragment_types(
        &mut self,
        fragment_type_name_1: &str,
        fragment_type_name_2: &str,
    ) -> FmtResult;

    fn write_any_type_definition(&mut self, name: &str) -> FmtResult;
}

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

    use super::StringLiteral;
    use crate::FUTURE_ENUM_VALUE;

    #[test]
    fn ast_string_key_sort() {
        let mut keys = vec![
            StringLiteral(*FUTURE_ENUM_VALUE),
            StringLiteral("B".intern()),
            StringLiteral("A".intern()),
        ];
        keys.sort();
        assert_eq!(
            keys,
            vec![
                StringLiteral("A".intern()),
                StringLiteral("B".intern()),
                StringLiteral(*FUTURE_ENUM_VALUE),
            ]
        )
    }

    #[test]
    fn ast_string_key_sort_duplicated_intern() {
        let future_enum_value = FUTURE_ENUM_VALUE.lookup().intern();
        let mut keys = vec![
            StringLiteral("B".intern()),
            StringLiteral(future_enum_value),
            StringLiteral("A".intern()),
        ];
        keys.sort();
        assert_eq!(
            keys,
            vec![
                StringLiteral("A".intern()),
                StringLiteral("B".intern()),
                StringLiteral(*FUTURE_ENUM_VALUE),
            ]
        )
    }
}

pub(crate) fn new_writer_from_config(config: &TypegenConfig) -> Box<dyn Writer> {
    match config.language {
        TypegenLanguage::JavaScript => Box::new(JavaScriptPrinter::default()),
        TypegenLanguage::Flow => Box::new(FlowPrinter::new()),
        TypegenLanguage::TypeScript => Box::new(TypeScriptPrinter::new(config)),
    }
}


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

mod generate_flow;

use generate_flow::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn actor_change() {
    let input = include_str!("generate_flow/fixtures/actor-change.graphql");
    let expected = include_str!("generate_flow/fixtures/actor-change.expected");
    test_fixture(transform_fixture, "actor-change.graphql", "generate_flow/fixtures/actor-change.expected", input, expected);
}

#[test]
fn actor_change_with_query() {
    let input = include_str!("generate_flow/fixtures/actor-change-with-query.graphql");
    let expected = include_str!("generate_flow/fixtures/actor-change-with-query.expected");
    test_fixture(transform_fixture, "actor-change-with-query.graphql", "generate_flow/fixtures/actor-change-with-query.expected", input, expected);
}

#[test]
fn aliased_fragment_raw_response_type() {
    let input = include_str!("generate_flow/fixtures/aliased-fragment-raw-response-type.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-fragment-raw-response-type.expected");
    test_fixture(transform_fixture, "aliased-fragment-raw-response-type.graphql", "generate_flow/fixtures/aliased-fragment-raw-response-type.expected", input, expected);
}

#[test]
fn aliased_fragment_spread() {
    let input = include_str!("generate_flow/fixtures/aliased-fragment-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-fragment-spread.expected");
    test_fixture(transform_fixture, "aliased-fragment-spread.graphql", "generate_flow/fixtures/aliased-fragment-spread.expected", input, expected);
}

#[test]
fn aliased_fragment_spread_in_abstract_selection() {
    let input = include_str!("generate_flow/fixtures/aliased-fragment-spread-in-abstract-selection.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-fragment-spread-in-abstract-selection.expected");
    test_fixture(transform_fixture, "aliased-fragment-spread-in-abstract-selection.graphql", "generate_flow/fixtures/aliased-fragment-spread-in-abstract-selection.expected", input, expected);
}

#[test]
fn aliased_inline_fragment_spread() {
    let input = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread.expected");
    test_fixture(transform_fixture, "aliased-inline-fragment-spread.graphql", "generate_flow/fixtures/aliased-inline-fragment-spread.expected", input, expected);
}

#[test]
fn aliased_inline_fragment_spread_without_type_condition_fragment_root() {
    let input = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-fragment-root.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-fragment-root.expected");
    test_fixture(transform_fixture, "aliased-inline-fragment-spread-without-type-condition-fragment-root.graphql", "generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-fragment-root.expected", input, expected);
}

#[test]
fn aliased_inline_fragment_spread_without_type_condition_linked_field() {
    let input = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-linked-field.expected");
    test_fixture(transform_fixture, "aliased-inline-fragment-spread-without-type-condition-linked-field.graphql", "generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-linked-field.expected", input, expected);
}

#[test]
fn aliased_inline_fragment_spread_without_type_condition_query_root() {
    let input = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-query-root.graphql");
    let expected = include_str!("generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-query-root.expected");
    test_fixture(transform_fixture, "aliased-inline-fragment-spread-without-type-condition-query-root.graphql", "generate_flow/fixtures/aliased-inline-fragment-spread-without-type-condition-query-root.expected", input, expected);
}

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

#[test]
fn custom_scalar_type_import() {
    let input = include_str!("generate_flow/fixtures/custom-scalar-type-import.graphql");
    let expected = include_str!("generate_flow/fixtures/custom-scalar-type-import.expected");
    test_fixture(transform_fixture, "custom-scalar-type-import.graphql", "generate_flow/fixtures/custom-scalar-type-import.expected", input, expected);
}

#[test]
fn fragment_spread() {
    let input = include_str!("generate_flow/fixtures/fragment-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/fragment-spread.expected");
    test_fixture(transform_fixture, "fragment-spread.graphql", "generate_flow/fixtures/fragment-spread.expected", input, expected);
}

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

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

#[test]
fn match_field() {
    let input = include_str!("generate_flow/fixtures/match-field.graphql");
    let expected = include_str!("generate_flow/fixtures/match-field.expected");
    test_fixture(transform_fixture, "match-field.graphql", "generate_flow/fixtures/match-field.expected", input, expected);
}

#[test]
fn match_field_in_query() {
    let input = include_str!("generate_flow/fixtures/match-field-in-query.graphql");
    let expected = include_str!("generate_flow/fixtures/match-field-in-query.expected");
    test_fixture(transform_fixture, "match-field-in-query.graphql", "generate_flow/fixtures/match-field-in-query.expected", input, expected);
}

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

#[test]
fn mutation_input_has_array() {
    let input = include_str!("generate_flow/fixtures/mutation-input-has-array.graphql");
    let expected = include_str!("generate_flow/fixtures/mutation-input-has-array.expected");
    test_fixture(transform_fixture, "mutation-input-has-array.graphql", "generate_flow/fixtures/mutation-input-has-array.expected", input, expected);
}

#[test]
fn mutation_with_client_extension() {
    let input = include_str!("generate_flow/fixtures/mutation-with-client-extension.graphql");
    let expected = include_str!("generate_flow/fixtures/mutation-with-client-extension.expected");
    test_fixture(transform_fixture, "mutation-with-client-extension.graphql", "generate_flow/fixtures/mutation-with-client-extension.expected", input, expected);
}

#[test]
fn mutation_with_enums_on_fragment() {
    let input = include_str!("generate_flow/fixtures/mutation-with-enums-on-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/mutation-with-enums-on-fragment.expected");
    test_fixture(transform_fixture, "mutation-with-enums-on-fragment.graphql", "generate_flow/fixtures/mutation-with-enums-on-fragment.expected", input, expected);
}

#[test]
fn mutation_with_nested_fragments() {
    let input = include_str!("generate_flow/fixtures/mutation-with-nested-fragments.graphql");
    let expected = include_str!("generate_flow/fixtures/mutation-with-nested-fragments.expected");
    test_fixture(transform_fixture, "mutation-with-nested-fragments.graphql", "generate_flow/fixtures/mutation-with-nested-fragments.expected", input, expected);
}

#[test]
fn mutation_with_response_on_inline_fragments() {
    let input = include_str!("generate_flow/fixtures/mutation-with-response-on-inline-fragments.graphql");
    let expected = include_str!("generate_flow/fixtures/mutation-with-response-on-inline-fragments.expected");
    test_fixture(transform_fixture, "mutation-with-response-on-inline-fragments.graphql", "generate_flow/fixtures/mutation-with-response-on-inline-fragments.expected", input, expected);
}

#[test]
fn no_inline_fragment() {
    let input = include_str!("generate_flow/fixtures/no-inline-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/no-inline-fragment.expected");
    test_fixture(transform_fixture, "no-inline-fragment.graphql", "generate_flow/fixtures/no-inline-fragment.expected", input, expected);
}

#[test]
fn plural_fragment() {
    let input = include_str!("generate_flow/fixtures/plural-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/plural-fragment.expected");
    test_fixture(transform_fixture, "plural-fragment.graphql", "generate_flow/fixtures/plural-fragment.expected", input, expected);
}

#[test]
fn query_mixed_provided_variables() {
    let input = include_str!("generate_flow/fixtures/query-mixed-provided-variables.graphql");
    let expected = include_str!("generate_flow/fixtures/query-mixed-provided-variables.expected");
    test_fixture(transform_fixture, "query-mixed-provided-variables.graphql", "generate_flow/fixtures/query-mixed-provided-variables.expected", input, expected);
}

#[test]
fn query_only_provided_variables() {
    let input = include_str!("generate_flow/fixtures/query-only-provided-variables.graphql");
    let expected = include_str!("generate_flow/fixtures/query-only-provided-variables.expected");
    test_fixture(transform_fixture, "query-only-provided-variables.graphql", "generate_flow/fixtures/query-only-provided-variables.expected", input, expected);
}

#[test]
fn query_provided_variables_custom_scalar() {
    let input = include_str!("generate_flow/fixtures/query-provided-variables-custom-scalar.graphql");
    let expected = include_str!("generate_flow/fixtures/query-provided-variables-custom-scalar.expected");
    test_fixture(transform_fixture, "query-provided-variables-custom-scalar.graphql", "generate_flow/fixtures/query-provided-variables-custom-scalar.expected", input, expected);
}

#[test]
fn query_with_handles() {
    let input = include_str!("generate_flow/fixtures/query-with-handles.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-handles.expected");
    test_fixture(transform_fixture, "query-with-handles.graphql", "generate_flow/fixtures/query-with-handles.expected", input, expected);
}

#[test]
fn query_with_match_fields() {
    let input = include_str!("generate_flow/fixtures/query-with-match-fields.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-match-fields.expected");
    test_fixture(transform_fixture, "query-with-match-fields.graphql", "generate_flow/fixtures/query-with-match-fields.expected", input, expected);
}

#[test]
fn query_with_module_field() {
    let input = include_str!("generate_flow/fixtures/query-with-module-field.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-module-field.expected");
    test_fixture(transform_fixture, "query-with-module-field.graphql", "generate_flow/fixtures/query-with-module-field.expected", input, expected);
}

#[test]
fn query_with_multiple_match_fields() {
    let input = include_str!("generate_flow/fixtures/query-with-multiple-match-fields.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-multiple-match-fields.expected");
    test_fixture(transform_fixture, "query-with-multiple-match-fields.graphql", "generate_flow/fixtures/query-with-multiple-match-fields.expected", input, expected);
}

#[test]
fn query_with_raw_response_on_conditional() {
    let input = include_str!("generate_flow/fixtures/query-with-raw-response-on-conditional.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-raw-response-on-conditional.expected");
    test_fixture(transform_fixture, "query-with-raw-response-on-conditional.graphql", "generate_flow/fixtures/query-with-raw-response-on-conditional.expected", input, expected);
}

#[test]
fn query_with_raw_response_on_literal_conditional() {
    let input = include_str!("generate_flow/fixtures/query-with-raw-response-on-literal-conditional.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-raw-response-on-literal-conditional.expected");
    test_fixture(transform_fixture, "query-with-raw-response-on-literal-conditional.graphql", "generate_flow/fixtures/query-with-raw-response-on-literal-conditional.expected", input, expected);
}

#[test]
fn query_with_stream() {
    let input = include_str!("generate_flow/fixtures/query-with-stream.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-stream.expected");
    test_fixture(transform_fixture, "query-with-stream.graphql", "generate_flow/fixtures/query-with-stream.expected", input, expected);
}

#[test]
fn query_with_stream_connection() {
    let input = include_str!("generate_flow/fixtures/query-with-stream-connection.graphql");
    let expected = include_str!("generate_flow/fixtures/query-with-stream-connection.expected");
    test_fixture(transform_fixture, "query-with-stream-connection.graphql", "generate_flow/fixtures/query-with-stream-connection.expected", input, expected);
}

#[test]
fn recursive_fragments() {
    let input = include_str!("generate_flow/fixtures/recursive-fragments.graphql");
    let expected = include_str!("generate_flow/fixtures/recursive-fragments.expected");
    test_fixture(transform_fixture, "recursive-fragments.graphql", "generate_flow/fixtures/recursive-fragments.expected", input, expected);
}

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

#[test]
fn refetchable_fragment() {
    let input = include_str!("generate_flow/fixtures/refetchable-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/refetchable-fragment.expected");
    test_fixture(transform_fixture, "refetchable-fragment.graphql", "generate_flow/fixtures/refetchable-fragment.expected", input, expected);
}

#[test]
fn regular_query_with_assignable_fragment_spread() {
    let input = include_str!("generate_flow/fixtures/regular-query-with-assignable-fragment-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/regular-query-with-assignable-fragment-spread.expected");
    test_fixture(transform_fixture, "regular-query-with-assignable-fragment-spread.graphql", "generate_flow/fixtures/regular-query-with-assignable-fragment-spread.expected", input, expected);
}

#[test]
fn relay_client_id_field() {
    let input = include_str!("generate_flow/fixtures/relay-client-id-field.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-client-id-field.expected");
    test_fixture(transform_fixture, "relay-client-id-field.graphql", "generate_flow/fixtures/relay-client-id-field.expected", input, expected);
}

#[test]
fn relay_live_resolver() {
    let input = include_str!("generate_flow/fixtures/relay-live-resolver.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-live-resolver.expected");
    test_fixture(transform_fixture, "relay-live-resolver.graphql", "generate_flow/fixtures/relay-live-resolver.expected", input, expected);
}

#[test]
fn relay_live_resolver_no_fragment() {
    let input = include_str!("generate_flow/fixtures/relay-live-resolver-no-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-live-resolver-no-fragment.expected");
    test_fixture(transform_fixture, "relay-live-resolver-no-fragment.graphql", "generate_flow/fixtures/relay-live-resolver-no-fragment.expected", input, expected);
}

#[test]
fn relay_live_resolver_with_field_args() {
    let input = include_str!("generate_flow/fixtures/relay-live-resolver-with-field-args.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-live-resolver-with-field-args.expected");
    test_fixture(transform_fixture, "relay-live-resolver-with-field-args.graphql", "generate_flow/fixtures/relay-live-resolver-with-field-args.expected", input, expected);
}

#[test]
fn relay_live_resolver_with_field_args_no_fragment() {
    let input = include_str!("generate_flow/fixtures/relay-live-resolver-with-field-args-no-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-live-resolver-with-field-args-no-fragment.expected");
    test_fixture(transform_fixture, "relay-live-resolver-with-field-args-no-fragment.graphql", "generate_flow/fixtures/relay-live-resolver-with-field-args-no-fragment.expected", input, expected);
}

#[test]
fn relay_resolver() {
    let input = include_str!("generate_flow/fixtures/relay-resolver.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver.expected");
    test_fixture(transform_fixture, "relay-resolver.graphql", "generate_flow/fixtures/relay-resolver.expected", input, expected);
}

#[test]
fn relay_resolver_client_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-client-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-client-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-client-edge.graphql", "generate_flow/fixtures/relay-resolver-client-edge.expected", input, expected);
}

#[test]
fn relay_resolver_client_edge_required() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-client-edge-required.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-client-edge-required.expected");
    test_fixture(transform_fixture, "relay-resolver-client-edge-required.graphql", "generate_flow/fixtures/relay-resolver-client-edge-required.expected", input, expected);
}

#[test]
fn relay_resolver_client_edge_required_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-client-edge-required-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-client-edge-required-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-client-edge-required-edge.graphql", "generate_flow/fixtures/relay-resolver-client-edge-required-edge.expected", input, expected);
}

#[test]
fn relay_resolver_in_fragment() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-in-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-in-fragment.expected");
    test_fixture(transform_fixture, "relay-resolver-in-fragment.graphql", "generate_flow/fixtures/relay-resolver-in-fragment.expected", input, expected);
}

#[test]
fn relay_resolver_inject_fragment_data() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-inject-fragment-data.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-inject-fragment-data.expected");
    test_fixture(transform_fixture, "relay-resolver-inject-fragment-data.graphql", "generate_flow/fixtures/relay-resolver-inject-fragment-data.expected", input, expected);
}

#[test]
fn relay_resolver_live_client_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-live-client-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-live-client-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-live-client-edge.graphql", "generate_flow/fixtures/relay-resolver-live-client-edge.expected", input, expected);
}

#[test]
fn relay_resolver_multiple_consumers() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-multiple-consumers.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-multiple-consumers.expected");
    test_fixture(transform_fixture, "relay-resolver-multiple-consumers.graphql", "generate_flow/fixtures/relay-resolver-multiple-consumers.expected", input, expected);
}

#[test]
fn relay_resolver_named_import() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-named-import.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-named-import.expected");
    test_fixture(transform_fixture, "relay-resolver-named-import.graphql", "generate_flow/fixtures/relay-resolver-named-import.expected", input, expected);
}

#[test]
fn relay_resolver_plural_client_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-plural-client-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-plural-client-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-plural-client-edge.graphql", "generate_flow/fixtures/relay-resolver-plural-client-edge.expected", input, expected);
}

#[test]
fn relay_resolver_plural_client_edge_with_required_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-plural-client-edge-with-required-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-plural-client-edge-with-required-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-plural-client-edge-with-required-edge.graphql", "generate_flow/fixtures/relay-resolver-plural-client-edge-with-required-edge.expected", input, expected);
}

#[test]
fn relay_resolver_plural_required_client_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-plural-required-client-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-plural-required-client-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-plural-required-client-edge.graphql", "generate_flow/fixtures/relay-resolver-plural-required-client-edge.expected", input, expected);
}

#[test]
fn relay_resolver_plural_required_client_edge_with_required_edge() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-plural-required-client-edge-with-required-edge.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-plural-required-client-edge-with-required-edge.expected");
    test_fixture(transform_fixture, "relay-resolver-plural-required-client-edge-with-required-edge.graphql", "generate_flow/fixtures/relay-resolver-plural-required-client-edge-with-required-edge.expected", input, expected);
}

#[test]
fn relay_resolver_raw_response() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-raw-response.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-raw-response.expected");
    test_fixture(transform_fixture, "relay-resolver-raw-response.graphql", "generate_flow/fixtures/relay-resolver-raw-response.expected", input, expected);
}

#[test]
fn relay_resolver_required() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-required.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-required.expected");
    test_fixture(transform_fixture, "relay-resolver-required.graphql", "generate_flow/fixtures/relay-resolver-required.expected", input, expected);
}

#[test]
fn relay_resolver_scalar_plural() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-scalar-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-scalar-plural.expected");
    test_fixture(transform_fixture, "relay-resolver-scalar-plural.graphql", "generate_flow/fixtures/relay-resolver-scalar-plural.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_client_interface() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-interface.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-interface.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-client-interface.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-client-interface.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_client_object() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-object.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-object.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-client-object.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-client-object.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_client_object_plural() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-object-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-client-object-plural.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-client-object-plural.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-client-object-plural.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_enum() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-enum.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-enum.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-enum.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-enum.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_enum_plural() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-enum-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-enum-plural.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-enum-plural.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-enum-plural.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_relay_resolver_value() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-relay-resolver-value.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_relay_resolver_value_plural() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-plural.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-relay-resolver-value-plural.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-plural.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_relay_resolver_value_required() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-required.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-required.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-relay-resolver-value-required.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-relay-resolver-value-required.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_scalar() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-scalar.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-scalar.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_scalar_plural() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar-plural.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-scalar-plural.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-scalar-plural.expected", input, expected);
}

#[test]
fn relay_resolver_with_output_type_scalar_required() {
    let input = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar-required.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-resolver-with-output-type-scalar-required.expected");
    test_fixture(transform_fixture, "relay-resolver-with-output-type-scalar-required.graphql", "generate_flow/fixtures/relay-resolver-with-output-type-scalar-required.expected", input, expected);
}

#[test]
fn relay_weak_client_type() {
    let input = include_str!("generate_flow/fixtures/relay-weak-client-type.graphql");
    let expected = include_str!("generate_flow/fixtures/relay-weak-client-type.expected");
    test_fixture(transform_fixture, "relay-weak-client-type.graphql", "generate_flow/fixtures/relay-weak-client-type.expected", input, expected);
}

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

#[test]
fn required_bubbles_through_inline_fragments_to_fragment() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-through-inline-fragments-to-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected");
    test_fixture(transform_fixture, "required-bubbles-through-inline-fragments-to-fragment.graphql", "generate_flow/fixtures/required-bubbles-through-inline-fragments-to-fragment.expected", input, expected);
}

#[test]
fn required_bubbles_to_fragment() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-fragment.expected");
    test_fixture(transform_fixture, "required-bubbles-to-fragment.graphql", "generate_flow/fixtures/required-bubbles-to-fragment.expected", input, expected);
}

#[test]
fn required_bubbles_to_item_in_plural_field() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-item-in-plural-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-item-in-plural-field.expected");
    test_fixture(transform_fixture, "required-bubbles-to-item-in-plural-field.graphql", "generate_flow/fixtures/required-bubbles-to-item-in-plural-field.expected", input, expected);
}

#[test]
fn required_bubbles_to_item_in_required_plural_field() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-item-in-required-plural-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-item-in-required-plural-field.expected");
    test_fixture(transform_fixture, "required-bubbles-to-item-in-required-plural-field.graphql", "generate_flow/fixtures/required-bubbles-to-item-in-required-plural-field.expected", input, expected);
}

#[test]
fn required_bubbles_to_non_null_item_in_non_null_plural_linked_field() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-item-in-non-null-plural-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-item-in-non-null-plural-linked-field.expected");
    test_fixture(transform_fixture, "required-bubbles-to-non-null-item-in-non-null-plural-linked-field.graphql", "generate_flow/fixtures/required-bubbles-to-non-null-item-in-non-null-plural-linked-field.expected", input, expected);
}

#[test]
fn required_bubbles_to_non_null_linked_field() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-linked-field.expected");
    test_fixture(transform_fixture, "required-bubbles-to-non-null-linked-field.graphql", "generate_flow/fixtures/required-bubbles-to-non-null-linked-field.expected", input, expected);
}

#[test]
fn required_bubbles_to_non_null_linked_field_through_inline_fragment() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-linked-field-through-inline-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-linked-field-through-inline-fragment.expected");
    test_fixture(transform_fixture, "required-bubbles-to-non-null-linked-field-through-inline-fragment.graphql", "generate_flow/fixtures/required-bubbles-to-non-null-linked-field-through-inline-fragment.expected", input, expected);
}

#[test]
fn required_bubbles_to_non_null_plural_linked_field() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-plural-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-non-null-plural-linked-field.expected");
    test_fixture(transform_fixture, "required-bubbles-to-non-null-plural-linked-field.graphql", "generate_flow/fixtures/required-bubbles-to-non-null-plural-linked-field.expected", input, expected);
}

#[test]
fn required_bubbles_to_plural_fragment_root() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-plural-fragment-root.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected");
    test_fixture(transform_fixture, "required-bubbles-to-plural-fragment-root.graphql", "generate_flow/fixtures/required-bubbles-to-plural-fragment-root.expected", input, expected);
}

#[test]
fn required_bubbles_to_query() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-to-query.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-to-query.expected");
    test_fixture(transform_fixture, "required-bubbles-to-query.graphql", "generate_flow/fixtures/required-bubbles-to-query.expected", input, expected);
}

#[test]
fn required_bubbles_up_to_mutation_response() {
    let input = include_str!("generate_flow/fixtures/required-bubbles-up-to-mutation-response.graphql");
    let expected = include_str!("generate_flow/fixtures/required-bubbles-up-to-mutation-response.expected");
    test_fixture(transform_fixture, "required-bubbles-up-to-mutation-response.graphql", "generate_flow/fixtures/required-bubbles-up-to-mutation-response.expected", input, expected);
}

#[test]
fn required_chain_bubbles_to_non_null_linked_field_through_inline_fragment() {
    let input = include_str!("generate_flow/fixtures/required-chain-bubbles-to-non-null-linked-field-through-inline-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-chain-bubbles-to-non-null-linked-field-through-inline-fragment.expected");
    test_fixture(transform_fixture, "required-chain-bubbles-to-non-null-linked-field-through-inline-fragment.graphql", "generate_flow/fixtures/required-chain-bubbles-to-non-null-linked-field-through-inline-fragment.expected", input, expected);
}

#[test]
fn required_isolates_concrete_inline_fragments() {
    let input = include_str!("generate_flow/fixtures/required-isolates-concrete-inline-fragments.graphql");
    let expected = include_str!("generate_flow/fixtures/required-isolates-concrete-inline-fragments.expected");
    test_fixture(transform_fixture, "required-isolates-concrete-inline-fragments.graphql", "generate_flow/fixtures/required-isolates-concrete-inline-fragments.expected", input, expected);
}

#[test]
fn required_raw_response_type() {
    let input = include_str!("generate_flow/fixtures/required-raw-response-type.graphql");
    let expected = include_str!("generate_flow/fixtures/required-raw-response-type.expected");
    test_fixture(transform_fixture, "required-raw-response-type.graphql", "generate_flow/fixtures/required-raw-response-type.expected", input, expected);
}

#[test]
fn required_throw_doesnt_bubbles_to_fragment() {
    let input = include_str!("generate_flow/fixtures/required-throw-doesnt-bubbles-to-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-throw-doesnt-bubbles-to-fragment.expected");
    test_fixture(transform_fixture, "required-throw-doesnt-bubbles-to-fragment.graphql", "generate_flow/fixtures/required-throw-doesnt-bubbles-to-fragment.expected", input, expected);
}

#[test]
fn required_throw_doesnt_bubbles_to_query() {
    let input = include_str!("generate_flow/fixtures/required-throw-doesnt-bubbles-to-query.graphql");
    let expected = include_str!("generate_flow/fixtures/required-throw-doesnt-bubbles-to-query.expected");
    test_fixture(transform_fixture, "required-throw-doesnt-bubbles-to-query.graphql", "generate_flow/fixtures/required-throw-doesnt-bubbles-to-query.expected", input, expected);
}

#[test]
fn required_throws_nested() {
    let input = include_str!("generate_flow/fixtures/required-throws-nested.graphql");
    let expected = include_str!("generate_flow/fixtures/required-throws-nested.expected");
    test_fixture(transform_fixture, "required-throws-nested.graphql", "generate_flow/fixtures/required-throws-nested.expected", input, expected);
}

#[test]
fn required_throws_within_non_null_linked_field() {
    let input = include_str!("generate_flow/fixtures/required-throws-within-non-null-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-throws-within-non-null-linked-field.expected");
    test_fixture(transform_fixture, "required-throws-within-non-null-linked-field.graphql", "generate_flow/fixtures/required-throws-within-non-null-linked-field.expected", input, expected);
}

#[test]
fn required_throws_within_non_null_plural_linked_field() {
    let input = include_str!("generate_flow/fixtures/required-throws-within-non-null-plural-linked-field.graphql");
    let expected = include_str!("generate_flow/fixtures/required-throws-within-non-null-plural-linked-field.expected");
    test_fixture(transform_fixture, "required-throws-within-non-null-plural-linked-field.graphql", "generate_flow/fixtures/required-throws-within-non-null-plural-linked-field.expected", input, expected);
}

#[test]
fn required_within_aliased_inline_fragment() {
    let input = include_str!("generate_flow/fixtures/required-within-aliased-inline-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/required-within-aliased-inline-fragment.expected");
    test_fixture(transform_fixture, "required-within-aliased-inline-fragment.graphql", "generate_flow/fixtures/required-within-aliased-inline-fragment.expected", input, expected);
}

#[test]
fn required_within_aliased_inline_fragment_on_abstract() {
    let input = include_str!("generate_flow/fixtures/required-within-aliased-inline-fragment-on-abstract.graphql");
    let expected = include_str!("generate_flow/fixtures/required-within-aliased-inline-fragment-on-abstract.expected");
    test_fixture(transform_fixture, "required-within-aliased-inline-fragment-on-abstract.graphql", "generate_flow/fixtures/required-within-aliased-inline-fragment-on-abstract.expected", input, expected);
}

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

#[test]
fn scalar_field() {
    let input = include_str!("generate_flow/fixtures/scalar-field.graphql");
    let expected = include_str!("generate_flow/fixtures/scalar-field.expected");
    test_fixture(transform_fixture, "scalar-field.graphql", "generate_flow/fixtures/scalar-field.expected", input, expected);
}

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

#[test]
fn typename_in_union_with_other_fields() {
    let input = include_str!("generate_flow/fixtures/typename-in-union-with-other-fields.graphql");
    let expected = include_str!("generate_flow/fixtures/typename-in-union-with-other-fields.expected");
    test_fixture(transform_fixture, "typename-in-union-with-other-fields.graphql", "generate_flow/fixtures/typename-in-union-with-other-fields.expected", input, expected);
}

#[test]
fn typename_inside_with_overlapping_fields() {
    let input = include_str!("generate_flow/fixtures/typename-inside-with-overlapping-fields.graphql");
    let expected = include_str!("generate_flow/fixtures/typename-inside-with-overlapping-fields.expected");
    test_fixture(transform_fixture, "typename-inside-with-overlapping-fields.graphql", "generate_flow/fixtures/typename-inside-with-overlapping-fields.expected", input, expected);
}

#[test]
fn typename_on_union() {
    let input = include_str!("generate_flow/fixtures/typename-on-union.graphql");
    let expected = include_str!("generate_flow/fixtures/typename-on-union.expected");
    test_fixture(transform_fixture, "typename-on-union.graphql", "generate_flow/fixtures/typename-on-union.expected", input, expected);
}

#[test]
fn typename_on_union_with_non_matching_aliases() {
    let input = include_str!("generate_flow/fixtures/typename-on-union-with-non-matching-aliases.graphql");
    let expected = include_str!("generate_flow/fixtures/typename-on-union-with-non-matching-aliases.expected");
    test_fixture(transform_fixture, "typename-on-union-with-non-matching-aliases.graphql", "generate_flow/fixtures/typename-on-union-with-non-matching-aliases.expected", input, expected);
}

#[test]
fn unmasked_fragment_spreads() {
    let input = include_str!("generate_flow/fixtures/unmasked-fragment-spreads.graphql");
    let expected = include_str!("generate_flow/fixtures/unmasked-fragment-spreads.expected");
    test_fixture(transform_fixture, "unmasked-fragment-spreads.graphql", "generate_flow/fixtures/unmasked-fragment-spreads.expected", input, expected);
}

#[test]
fn updatable_fragment_spread() {
    let input = include_str!("generate_flow/fixtures/updatable-fragment-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-fragment-spread.expected");
    test_fixture(transform_fixture, "updatable-fragment-spread.graphql", "generate_flow/fixtures/updatable-fragment-spread.expected", input, expected);
}

#[test]
fn updatable_fragment_spread_and_regular_spread() {
    let input = include_str!("generate_flow/fixtures/updatable-fragment-spread-and-regular-spread.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-fragment-spread-and-regular-spread.expected");
    test_fixture(transform_fixture, "updatable-fragment-spread-and-regular-spread.graphql", "generate_flow/fixtures/updatable-fragment-spread-and-regular-spread.expected", input, expected);
}

#[test]
fn updatable_fragment_spread_multiple() {
    let input = include_str!("generate_flow/fixtures/updatable-fragment-spread-multiple.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-fragment-spread-multiple.expected");
    test_fixture(transform_fixture, "updatable-fragment-spread-multiple.graphql", "generate_flow/fixtures/updatable-fragment-spread-multiple.expected", input, expected);
}

#[test]
fn updatable_operation() {
    let input = include_str!("generate_flow/fixtures/updatable-operation.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation.expected");
    test_fixture(transform_fixture, "updatable-operation.graphql", "generate_flow/fixtures/updatable-operation.expected", input, expected);
}

#[test]
fn updatable_operation_assignable_fragment() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragment.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragment.expected");
    test_fixture(transform_fixture, "updatable-operation-assignable-fragment.graphql", "generate_flow/fixtures/updatable-operation-assignable-fragment.expected", input, expected);
}

#[test]
fn updatable_operation_assignable_fragment_plural() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragment-plural.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragment-plural.expected");
    test_fixture(transform_fixture, "updatable-operation-assignable-fragment-plural.graphql", "generate_flow/fixtures/updatable-operation-assignable-fragment-plural.expected", input, expected);
}

#[test]
fn updatable_operation_assignable_fragments_within_narrowing() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragments-within-narrowing.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-assignable-fragments-within-narrowing.expected");
    test_fixture(transform_fixture, "updatable-operation-assignable-fragments-within-narrowing.graphql", "generate_flow/fixtures/updatable-operation-assignable-fragments-within-narrowing.expected", input, expected);
}

#[test]
fn updatable_operation_multiple_assignable_fragments() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-multiple-assignable-fragments.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-multiple-assignable-fragments.expected");
    test_fixture(transform_fixture, "updatable-operation-multiple-assignable-fragments.graphql", "generate_flow/fixtures/updatable-operation-multiple-assignable-fragments.expected", input, expected);
}

#[test]
fn updatable_operation_plural_field_no_spreads() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-plural-field-no-spreads.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-plural-field-no-spreads.expected");
    test_fixture(transform_fixture, "updatable-operation-plural-field-no-spreads.graphql", "generate_flow/fixtures/updatable-operation-plural-field-no-spreads.expected", input, expected);
}

#[test]
fn updatable_operation_plural_field_with_spreads() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-plural-field-with-spreads.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-plural-field-with-spreads.expected");
    test_fixture(transform_fixture, "updatable-operation-plural-field-with-spreads.graphql", "generate_flow/fixtures/updatable-operation-plural-field-with-spreads.expected", input, expected);
}

#[test]
fn updatable_operation_special_fields() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-special-fields.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-special-fields.expected");
    test_fixture(transform_fixture, "updatable-operation-special-fields.graphql", "generate_flow/fixtures/updatable-operation-special-fields.expected", input, expected);
}

#[test]
fn updatable_operation_type_refinement() {
    let input = include_str!("generate_flow/fixtures/updatable-operation-type-refinement.graphql");
    let expected = include_str!("generate_flow/fixtures/updatable-operation-type-refinement.expected");
    test_fixture(transform_fixture, "updatable-operation-type-refinement.graphql", "generate_flow/fixtures/updatable-operation-type-refinement.expected", input, expected);
}


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

mod generate_flow_with_custom_id;

use generate_flow_with_custom_id::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn relay_client_id_field() {
    let input = include_str!("generate_flow_with_custom_id/fixtures/relay-client-id-field.graphql");
    let expected = include_str!("generate_flow_with_custom_id/fixtures/relay-client-id-field.expected");
    test_fixture(transform_fixture, "relay-client-id-field.graphql", "generate_flow_with_custom_id/fixtures/relay-client-id-field.expected", input, expected);
}

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