/*
 * 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<<1e99c40a98b34b55c49b7b2a2bfddae2>>
 */

mod compile_relay_artifacts_with_custom_id;

use compile_relay_artifacts_with_custom_id::transform_fixture;
use fixture_tests::test_fixture;

#[test]
fn fragment_on_node_interface() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface.expected");
    test_fixture(transform_fixture, "fragment-on-node-interface.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface.expected", input, expected);
}

#[test]
fn fragment_on_node_interface_with_custom_variable_name() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface-with-custom-variable-name.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface-with-custom-variable-name.expected");
    test_fixture(transform_fixture, "fragment-on-node-interface-with-custom-variable-name.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-node-interface-with-custom-variable-name.expected", input, expected);
}

#[test]
fn fragment_on_object_implementing_node_interface() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface.expected");
    test_fixture(transform_fixture, "fragment-on-object-implementing-node-interface.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface.expected", input, expected);
}

#[test]
fn fragment_on_object_implementing_node_interface_with_custom_variable_name() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface-with-custom-variable-name.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface-with-custom-variable-name.expected");
    test_fixture(transform_fixture, "fragment-on-object-implementing-node-interface-with-custom-variable-name.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-object-implementing-node-interface-with-custom-variable-name.expected", input, expected);
}

#[test]
fn fragment_on_query() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query.expected");
    test_fixture(transform_fixture, "fragment-on-query.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query.expected", input, expected);
}

#[test]
fn fragment_on_query_with_cycle_invalid() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query-with-cycle.invalid.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query-with-cycle.invalid.expected");
    test_fixture(transform_fixture, "fragment-on-query-with-cycle.invalid.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-query-with-cycle.invalid.expected", input, expected);
}

#[test]
fn fragment_on_viewer() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-viewer.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/fragment-on-viewer.expected");
    test_fixture(transform_fixture, "fragment-on-viewer.graphql", "compile_relay_artifacts_with_custom_id/fixtures/fragment-on-viewer.expected", input, expected);
}

#[test]
fn id_as_alias_invalid() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/id-as-alias.invalid.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/id-as-alias.invalid.expected");
    test_fixture(transform_fixture, "id-as-alias.invalid.graphql", "compile_relay_artifacts_with_custom_id/fixtures/id-as-alias.invalid.expected", input, expected);
}

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

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

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

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

#[test]
fn refetchable_connection_with_custom_variable_name() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-connection-with-custom-variable-name.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-connection-with-custom-variable-name.expected");
    test_fixture(transform_fixture, "refetchable-connection-with-custom-variable-name.graphql", "compile_relay_artifacts_with_custom_id/fixtures/refetchable-connection-with-custom-variable-name.expected", input, expected);
}

#[test]
fn refetchable_fragment_on_node_with_missing_id() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-fragment-on-node-with-missing-id.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-fragment-on-node-with-missing-id.expected");
    test_fixture(transform_fixture, "refetchable-fragment-on-node-with-missing-id.graphql", "compile_relay_artifacts_with_custom_id/fixtures/refetchable-fragment-on-node-with-missing-id.expected", input, expected);
}

#[test]
fn refetchable_with_arguments_conflicting_invalid() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments-conflicting.invalid.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments-conflicting.invalid.expected");
    test_fixture(transform_fixture, "refetchable-with-arguments-conflicting.invalid.graphql", "compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments-conflicting.invalid.expected", input, expected);
}

#[test]
fn refetchable_with_arguments_invalid() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments.invalid.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments.invalid.expected");
    test_fixture(transform_fixture, "refetchable-with-arguments.invalid.graphql", "compile_relay_artifacts_with_custom_id/fixtures/refetchable-with-arguments.invalid.expected", input, expected);
}

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

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

#[test]
fn viewer_query() {
    let input = include_str!("compile_relay_artifacts_with_custom_id/fixtures/viewer-query.graphql");
    let expected = include_str!("compile_relay_artifacts_with_custom_id/fixtures/viewer-query.expected");
    test_fixture(transform_fixture, "viewer-query.graphql", "compile_relay_artifacts_with_custom_id/fixtures/viewer-query.expected", input, expected);
}


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

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

use common::ConsoleLogger;
use common::FeatureFlag;
use common::FeatureFlags;
use common::NamedItem;
use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_ir::build_ir_with_extra_features;
use graphql_ir::BuilderOptions;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentVariablesSemantic;
use graphql_ir::OperationDefinition;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::RelayMode;
use graphql_syntax::parse_executable;
use graphql_test_helpers::diagnostics_to_sorted_string;
use graphql_text_printer::print_full_operation;
use intern::string_key::Intern;
use relay_codegen::build_request_params;
use relay_codegen::print_fragment;
use relay_codegen::print_operation;
use relay_codegen::print_request;
use relay_codegen::JsModuleFormat;
use relay_compiler::find_duplicates;
use relay_compiler::validate;
use relay_compiler::ConfigFileProject;
use relay_compiler::ProjectConfig;
use relay_config::NonNodeIdFieldsConfig;
use relay_config::SchemaConfig;
use relay_test_schema::get_test_schema;
use relay_test_schema::get_test_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_transforms::DIRECTIVE_SPLIT_OPERATION;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);

    if fixture.content.contains("%TODO%") {
        if fixture.content.contains("expected-to-throw") {
            return Err("TODO".to_string());
        }
        return Ok("TODO".to_string());
    }
    let no_inline_allowlist = vec![
        "autoFilledArgumentOnMatchPlainUserNameRenderer_name".intern(),
        "autoFilledArgumentOnMatchMarkdownUserNameRenderer_name".intern(),
        "fragmentWithMatchDirective_PlainUserNameRenderer_name".intern(),
        "fragmentWithMatchDirective_MarkdownUserNameRenderer_name".intern(),
        "matchFieldOverlapAcrossDocuments_MarkdownUserNameRenderer_name".intern(),
        "matchOnChildOfPlural_PlainUserNameRenderer_name".intern(),
        "matchOnChildOfPlural_MarkdownUserNameRenderer_name".intern(),
        "moduleDeduping_frag".intern(),
        "moduleInInlineFragment_MarkdownUserNameRenderer_name".intern(),
        "moduleOverlapAcrossDocuments_MarkdownUserNameRenderer_name".intern(),
        "moduleOverlapAcrossDocuments_PlainUserNameRenderer_name".intern(),
        "moduleOverlapAcrossDocuments_MarkdownUserNameRenderer_name".intern(),
        "moduleOverlapWithinDocument_MarkdownUserNameRenderer_name".intern(),
        "moduleOverlapWithinDocument_PlainUserNameRenderer_name".intern(),
        "moduleOverlapWithinDocument_MarkdownUserNameRenderer_name".intern(),
        "moduleWithDefer_MarkdownUserNameRenderer_name".intern(),
        "multipleModulesDifferentComponent_MarkdownUserNameRenderer_name".intern(),
        "multipleModulesDifferentFragment_MarkdownUserNameRenderer_name".intern(),
        "multipleModulesDifferentFragment_OtherMarkdownUserNameRenderer_name".intern(),
        "multipleModulesSameSelections_MarkdownUserNameRenderer_name".intern(),
        "multipleModulesWithKey_PlainUserNameRenderer_name".intern(),
        "multipleModulesWithKey_MarkdownUserNameRenderer_name".intern(),
        "multipleModulesWithoutKey_PlainUserNameRenderer_name".intern(),
        "multipleModulesWithoutKey_MarkdownUserNameRenderer_name".intern(),
        "noInlineFragmentAndModule_parent".intern(),
        "queryWithAndWithoutModuleDirective_MarkdownUserNameRenderer_name".intern(),
        "queryWithConditionalModule_MarkdownUserNameRenderer_name".intern(),
        "queryWithMatchDirective_PlainUserNameRenderer_name".intern(),
        "queryWithMatchDirective_MarkdownUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveNoInlineExperimental_PlainUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveNoInlineExperimental_MarkdownUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveWithExtraArgument_PlainUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveWithExtraArgument_MarkdownUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveWithTypename_PlainUserNameRenderer_name".intern(),
        "queryWithMatchDirectiveWithTypename_MarkdownUserNameRenderer_name".intern(),
        "queryWithModuleDirective_MarkdownUserNameRenderer_name".intern(),
        "queryWithModuleDirectiveAndArguments_MarkdownUserNameRenderer_name".intern(),
        "queryWithModuleDirectiveAndArguments_PlainUserNameRenderer_name".intern(),
        "conflictingSelectionsWithNoInline_fragment".intern(),
        "providedVariableNoInlineFragment".intern(),
        "noInlineFragment_parent".intern(),
        "noInlineAbstractFragment_parent".intern(),
        "queryWithRelayClientComponentWithArgumentDefinitions_ClientComponentFragment".intern(),
        "queryWithRelayClientComponent_ClientComponentFragment".intern(),
    ];

    let feature_flags = FeatureFlags {
        // test SplitOperations that do not use @no-inline D28460294
        no_inline: FeatureFlag::Limited {
            allowlist: no_inline_allowlist.into_iter().collect(),
        },
        enable_relay_resolver_transform: true,
        enable_3d_branch_arg_generation: true,
        actor_change_support: FeatureFlag::Enabled,
        text_artifacts: FeatureFlag::Disabled,
        skip_printing_nulls: FeatureFlag::Disabled,
        enable_fragment_aliases: FeatureFlag::Enabled,
        compact_query_text: FeatureFlag::Disabled,
        emit_normalization_nodes_for_client_edges: true,
        relay_resolver_enable_output_type: FeatureFlag::Disabled,
    };

    let default_project_config = ProjectConfig {
        name: "test".intern(),
        feature_flags: Arc::new(feature_flags),
        js_module_format: JsModuleFormat::Haste,
        schema_config: SchemaConfig {
            non_node_id_fields: Some(NonNodeIdFieldsConfig {
                allowed_id_types: {
                    let mut mappings = HashMap::new();

                    mappings.insert("NonNode".intern(), "String".intern());

                    mappings
                },
            }),
            ..Default::default()
        },
        ..Default::default()
    };

    // Adding %project_config section on top of the fixture will allow
    // us to validate output changes with different configurations
    let parts: Vec<_> = fixture.content.split("%project_config%").collect();
    let (project_config, other_parts) = match parts.as_slice() {
        [fixture_content, project_config_str] => (
            {
                let config_file_project: ConfigFileProject =
                    serde_json::from_str(project_config_str).unwrap();
                ProjectConfig {
                    schema_config: config_file_project.schema_config,
                    typegen_config: config_file_project.typegen_config,
                    module_import_config: config_file_project.module_import_config,
                    feature_flags: config_file_project
                        .feature_flags
                        .map_or(default_project_config.feature_flags, |flags| {
                            Arc::new(flags)
                        }),
                    js_module_format: config_file_project.js_module_format,
                    ..default_project_config
                }
            },
            fixture_content.split("%extensions%").collect::<Vec<&str>>(),
        ),
        [fixture_content] => (
            default_project_config,
            fixture_content.split("%extensions%").collect::<Vec<&str>>(),
        ),
        _ => panic!("Invalid fixture input {}", fixture.content),
    };

    let (base, schema) = match other_parts.as_slice() {
        [base, extensions] => (base, get_test_schema_with_extensions(extensions)),
        [base] => (base, get_test_schema()),
        _ => panic!("Invalid fixture input {}", fixture.content),
    };

    let ast = parse_executable(base, source_location)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    find_duplicates(&ast.definitions, &[])
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    let ir_result = build_ir_with_extra_features(
        &schema,
        &ast.definitions,
        &BuilderOptions {
            allow_undefined_fragment_spreads: false,
            fragment_variables_semantic: FragmentVariablesSemantic::PassedValue,
            relay_mode: Some(RelayMode),
            default_anonymous_operation_name: None,
        },
    );
    let ir = ir_result
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;
    let program = Program::from_definitions(Arc::clone(&schema), ir);

    validate(&program, &project_config, &None)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    // TODO pass base fragment names
    let programs = apply_transforms(
        &project_config,
        Arc::new(program),
        Default::default(),
        Arc::new(ConsoleLogger),
        None,
        None,
    )
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    let mut operations: Vec<&std::sync::Arc<OperationDefinition>> =
        programs.normalization.operations().collect();
    operations.sort_by_key(|operation| operation.name.item.0);
    let result = operations
        .into_iter()
        .map(|operation| {
            if operation
                .directives
                .named(*DIRECTIVE_SPLIT_OPERATION)
                .is_some()
            {
                let mut import_statements = Default::default();
                let operation =
                    print_operation(&schema, operation, &project_config, &mut import_statements);
                format!("{}{}", import_statements, operation)
            } else {
                let name = operation.name.item.0;
                let print_operation_node = programs
                    .operation_text
                    .operation(OperationDefinitionName(name));
                let text = print_operation_node.map_or_else(
                    || "Query Text is Empty.".to_string(),
                    |print_operation_node| {
                        print_full_operation(
                            &programs.operation_text,
                            print_operation_node,
                            Default::default(),
                        )
                    },
                );

                let reader_operation = programs
                    .reader
                    .operation(OperationDefinitionName(name))
                    .expect("a reader fragment should be generated for this operation");
                let operation_fragment = FragmentDefinition {
                    name: reader_operation.name.map(|x| FragmentDefinitionName(x.0)),
                    variable_definitions: reader_operation.variable_definitions.clone(),
                    selections: reader_operation.selections.clone(),
                    used_global_variables: Default::default(),
                    directives: reader_operation.directives.clone(),
                    type_condition: reader_operation.type_,
                };
                let mut import_statements = Default::default();
                let request_parameters = build_request_params(operation);
                let request = print_request(
                    &schema,
                    operation,
                    &operation_fragment,
                    request_parameters,
                    &project_config,
                    &mut import_statements,
                );
                format!("{}{}\n\nQUERY:\n\n{}", import_statements, request, text)
            }
        })
        .chain({
            let mut fragments: Vec<&std::sync::Arc<FragmentDefinition>> =
                programs.reader.fragments().collect();
            fragments.sort_by_key(|fragment| fragment.name.item);
            fragments.into_iter().map(|fragment| {
                let mut import_statements = Default::default();
                let fragment =
                    print_fragment(&schema, fragment, &project_config, &mut import_statements);
                format!("{}{}", import_statements, fragment)
            })
        })
        .collect::<Vec<_>>();
    Ok(result.join("\n\n"))
}


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

use std::sync::Arc;

use common::ConsoleLogger;
use common::FeatureFlag;
use common::FeatureFlags;
use common::NamedItem;
use common::SourceLocationKey;
use fixture_tests::Fixture;
use graphql_ir::build_ir_with_extra_features;
use graphql_ir::BuilderOptions;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentVariablesSemantic;
use graphql_ir::OperationDefinition;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::RelayMode;
use graphql_syntax::parse_executable;
use graphql_test_helpers::diagnostics_to_sorted_string;
use graphql_text_printer::print_full_operation;
use intern::string_key::Intern;
use relay_codegen::build_request_params;
use relay_codegen::print_fragment;
use relay_codegen::print_operation;
use relay_codegen::print_request;
use relay_codegen::JsModuleFormat;
use relay_compiler::validate;
use relay_compiler::ProjectConfig;
use relay_config::SchemaConfig;
use relay_test_schema::get_test_schema_with_custom_id;
use relay_test_schema::get_test_schema_with_custom_id_with_extensions;
use relay_transforms::apply_transforms;
use relay_transforms::DIRECTIVE_SPLIT_OPERATION;

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let source_location = SourceLocationKey::standalone(fixture.file_name);

    if fixture.content.contains("%TODO%") {
        if fixture.content.contains("expected-to-throw") {
            return Err("TODO".to_string());
        }
        return Ok("TODO".to_string());
    }

    let node_interface_query_variable_name =
        if fixture.content.contains("# use-custom-variable-name") {
            Some("variable_name".intern())
        } else {
            None
        };

    let parts: Vec<_> = fixture.content.split("%extensions%").collect();
    let (base, schema) = match parts.as_slice() {
        [base, extensions] => (
            base,
            get_test_schema_with_custom_id_with_extensions(extensions),
        ),
        [base] => (base, get_test_schema_with_custom_id()),
        _ => panic!("Invalid fixture input {}", fixture.content),
    };

    let ast = parse_executable(base, source_location)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;
    let ir_result = build_ir_with_extra_features(
        &schema,
        &ast.definitions,
        &BuilderOptions {
            allow_undefined_fragment_spreads: false,
            fragment_variables_semantic: FragmentVariablesSemantic::PassedValue,
            relay_mode: Some(RelayMode),
            default_anonymous_operation_name: None,
        },
    );
    let ir = ir_result
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;
    let program = Program::from_definitions(Arc::clone(&schema), ir);

    let feature_flags = FeatureFlags {
        no_inline: FeatureFlag::Enabled,
        enable_relay_resolver_transform: true,
        enable_3d_branch_arg_generation: true,
        actor_change_support: FeatureFlag::Enabled,
        text_artifacts: FeatureFlag::Disabled,
        skip_printing_nulls: FeatureFlag::Disabled,
        enable_fragment_aliases: FeatureFlag::Enabled,
        compact_query_text: FeatureFlag::Disabled,
        emit_normalization_nodes_for_client_edges: true,
        relay_resolver_enable_output_type: FeatureFlag::Disabled,
    };

    let default_schema_config = SchemaConfig::default();

    let project_config = ProjectConfig {
        name: "test".intern(),
        feature_flags: Arc::new(feature_flags),
        schema_config: SchemaConfig {
            node_interface_id_field: "global_id".intern(),
            node_interface_id_variable_name: node_interface_query_variable_name
                .unwrap_or(default_schema_config.node_interface_id_variable_name),
            ..default_schema_config
        },
        js_module_format: JsModuleFormat::Haste,
        ..Default::default()
    };

    validate(&program, &project_config, &None)
        .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    // TODO pass base fragment names
    let programs = apply_transforms(
        &project_config,
        Arc::new(program),
        Default::default(),
        Arc::new(ConsoleLogger),
        None,
        None,
    )
    .map_err(|diagnostics| diagnostics_to_sorted_string(fixture.content, &diagnostics))?;

    let mut operations: Vec<&std::sync::Arc<OperationDefinition>> =
        programs.normalization.operations().collect();
    operations.sort_by_key(|operation| operation.name.item.0);
    let result = operations
        .into_iter()
        .map(|operation| {
            if operation
                .directives
                .named(*DIRECTIVE_SPLIT_OPERATION)
                .is_some()
            {
                let mut import_statements = Default::default();
                let operation =
                    print_operation(&schema, operation, &project_config, &mut import_statements);
                format!("{}{}", import_statements, operation)
            } else {
                let name = operation.name.item.0;
                let print_operation_node = programs
                    .operation_text
                    .operation(OperationDefinitionName(name));
                let text = print_operation_node.map_or_else(
                    || "Query Text is Empty.".to_string(),
                    |print_operation_node| {
                        print_full_operation(
                            &programs.operation_text,
                            print_operation_node,
                            Default::default(),
                        )
                    },
                );

                let reader_operation = programs
                    .reader
                    .operation(OperationDefinitionName(name))
                    .expect("a reader fragment should be generated for this operation");
                let operation_fragment = FragmentDefinition {
                    name: reader_operation.name.map(|x| FragmentDefinitionName(x.0)),
                    variable_definitions: reader_operation.variable_definitions.clone(),
                    selections: reader_operation.selections.clone(),
                    used_global_variables: Default::default(),
                    directives: reader_operation.directives.clone(),
                    type_condition: reader_operation.type_,
                };
                let request_parameters = build_request_params(operation);
                let mut import_statements = Default::default();
                let request = print_request(
                    &schema,
                    operation,
                    &operation_fragment,
                    request_parameters,
                    &project_config,
                    &mut import_statements,
                );
                format!("{}{}\n\nQUERY:\n\n{}", import_statements, request, text)
            }
        })
        .chain({
            let mut fragments: Vec<&std::sync::Arc<FragmentDefinition>> =
                programs.reader.fragments().collect();
            fragments.sort_by_key(|fragment| fragment.name.item);
            fragments.into_iter().map(|fragment| {
                let mut import_statements = Default::default();
                let fragment =
                    print_fragment(&schema, fragment, &project_config, &mut import_statements);
                format!("{}{}", import_statements, fragment)
            })
        })
        .collect::<Vec<_>>();
    Ok(result.join("\n\n"))
}


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

use std::sync::Arc;

use common::Diagnostic;
use common::FeatureFlags;
use common::NoopPerfLogger;
use common::SourceLocationKey;
use common::SourceLocationKey::Generated;
use common::TextSource;
use graphql_ir::Program;
use graphql_text_printer::PrinterOptions;
use intern::string_key::Intern;
use relay_codegen::print_fragment;
use relay_codegen::print_operation;
use relay_config::ProjectConfig;
use relay_schema::build_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_transforms::Programs;
use relay_typegen::generate_fragment_type_exports_section;
use relay_typegen::generate_operation_type_exports_section;
use relay_typegen::FragmentLocations;
use relay_typegen::TypegenConfig;
use schema::SDLSchema;
use serde::Serialize;
use wasm_bindgen::prelude::*;

// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

#[derive(Serialize, Debug)]
pub struct WasmDiagnostic {
    message: String,
    line_start: u32,
    line_end: u32,
    column_start: u32,
    column_end: u32,
}

enum InputType<'a> {
    Schema(&'a str),
    Document(&'a str),
}

impl<'a> InputType<'a> {
    fn source(&self) -> &'a str {
        match self {
            Self::Schema(source) => source,
            Self::Document(source) => source,
        }
    }
}

#[derive(Serialize, Debug)]
pub enum PlaygroundError {
    DocumentDiagnostics(Vec<WasmDiagnostic>),
    SchemaDiagnostics(Vec<WasmDiagnostic>),
    ConfigError(String),
    TypegenConfigError(String),
}

pub type PlaygroundResult = Result<String, PlaygroundError>;

#[wasm_bindgen]
pub fn parse_to_ast(document_text: &str) -> String {
    serialize_result(parse_to_ast_impl(document_text))
}

pub fn parse_to_ast_impl(document_text: &str) -> PlaygroundResult {
    let serialized_document = graphql_syntax::parse_executable(document_text, Generated)
        .map(|document| format!("{:?}", document))
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(serialized_document)
}

#[wasm_bindgen]
pub fn parse_to_ir(schema_text: &str, document_text: &str) -> String {
    serialize_result(parse_to_ir_impl(schema_text, document_text))
}

pub fn parse_to_ir_impl(schema_text: &str, document_text: &str) -> PlaygroundResult {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::generated())],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );

    let ir = graphql_ir::build(&schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    Ok(ir
        .iter()
        .map(|definition| format!("{:?}", definition))
        .collect::<Vec<String>>()
        .join("\n"))
}

#[wasm_bindgen]
pub fn parse_to_reader_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_reader_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_reader_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let reader_ast_string = programs
        .reader
        .fragments()
        .map(|def| {
            let mut import_statements = Default::default();
            let fragment = print_fragment(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, fragment)
        })
        .chain(programs.reader.operations().map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);
            format!("{}{}", import_statements, operation)
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(reader_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_normalization_ast(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_normalization_ast_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_normalization_ast_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let normalization_ast_string = programs
        .normalization
        .operations()
        .map(|def| {
            let mut import_statements = Default::default();
            let operation = print_operation(&schema, def, &project_config, &mut import_statements);

            format!("{}{}", import_statements, operation)
        })
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(normalization_ast_string)
}

#[wasm_bindgen]
pub fn parse_to_types(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> String {
    serialize_result(parse_to_types_impl(
        feature_flags_json,
        typegen_config_json,
        schema_text,
        document_text,
    ))
}

pub fn parse_to_types_impl(
    feature_flags_json: &str,
    typegen_config_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, Some(typegen_config_json))?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let fragment_locations = FragmentLocations::new(programs.typegen.fragments());
    let types_string = programs
        .typegen
        .fragments()
        .map(|def| {
            generate_fragment_type_exports_section(
                def,
                &schema,
                &project_config,
                &fragment_locations,
            )
        })
        .chain(programs.typegen.operations().map(|typegen_operation| {
            let normalization_operation = programs
                .normalization
                .operation(typegen_operation.name.item)
                .unwrap();

            generate_operation_type_exports_section(
                typegen_operation,
                normalization_operation,
                &schema,
                &project_config,
                &fragment_locations,
            )
        }))
        .collect::<Vec<_>>()
        .join("\n\n");

    Ok(types_string)
}

#[wasm_bindgen]
pub fn transform(feature_flags_json: &str, schema_text: &str, document_text: &str) -> String {
    serialize_result(transform_impl(
        feature_flags_json,
        schema_text,
        document_text,
    ))
}

fn transform_impl(
    feature_flags_json: &str,
    schema_text: &str,
    document_text: &str,
) -> PlaygroundResult {
    let schema = Arc::new(
        build_schema_with_extensions(
            &[(schema_text, SourceLocationKey::Generated)],
            &Vec::<(&str, SourceLocationKey)>::new(),
        )
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Schema(schema_text)))?,
    );
    let project_config = get_project_config(feature_flags_json, None)?;
    let programs = get_programs(&schema, &project_config, document_text)?;

    let transformed_operations = programs
        .operation_text
        .operations()
        .map(|operation| {
            graphql_text_printer::print_operation(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let transformed_fragments = programs
        .operation_text
        .fragments()
        .map(|operation| {
            graphql_text_printer::print_fragment(&schema, operation, PrinterOptions::default())
        })
        .collect::<Vec<_>>()
        .join("\n");

    let output = transformed_operations + "\n\n" + &transformed_fragments;
    Ok(output)
}

fn get_project_config(
    feature_flags_json: &str,
    typegen_config_json: Option<&str>,
) -> Result<ProjectConfig, PlaygroundError> {
    let feature_flags: FeatureFlags = serde_json::from_str(feature_flags_json)
        .map_err(|err| PlaygroundError::ConfigError(format!("{}", err)))?;
    let project_name = "test_project".intern();
    let typegen_config: TypegenConfig = typegen_config_json
        .map(|str| {
            serde_json::from_str(str)
                .map_err(|err| PlaygroundError::TypegenConfigError(format!("{}", err)))
        })
        .unwrap_or(Ok(TypegenConfig::default()))?;

    Ok(ProjectConfig {
        name: project_name,
        feature_flags: Arc::new(feature_flags),
        typegen_config,
        ..Default::default()
    })
}

fn get_programs(
    schema: &Arc<SDLSchema>,
    project_config: &ProjectConfig,
    document_text: &str,
) -> Result<Programs, PlaygroundError> {
    let document = graphql_syntax::parse_executable(document_text, Generated)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let ir = graphql_ir::build(schema, &document.definitions)
        .map_err(|diagnostics| map_diagnostics(diagnostics, &InputType::Document(document_text)))?;

    let program = Program::from_definitions(schema.clone(), ir);
    let base_fragment_names = Arc::new(Default::default());

    apply_transforms(
        &project_config,
        Arc::new(program),
        base_fragment_names,
        Arc::new(NoopPerfLogger),
        None,
        None,
    )
    .map_err(|diagnostics: Vec<Diagnostic>| {
        map_diagnostics(diagnostics, &InputType::Document(document_text))
    })
}

fn map_diagnostics(diagnostics: Vec<Diagnostic>, source: &InputType<'_>) -> PlaygroundError {
    let wasm_diagnostics = diagnostics
        .iter()
        .map(|diagnostic| {
            let range = TextSource::from_whole_document(source.source())
                .to_span_range(diagnostic.location().span());
            WasmDiagnostic {
                message: diagnostic.print_without_source(),
                line_start: range.start.line,
                line_end: range.end.line,
                column_start: range.start.character,
                column_end: range.end.character,
            }
        })
        .collect();
    match source {
        InputType::Document(_) => PlaygroundError::DocumentDiagnostics(wasm_diagnostics),
        InputType::Schema(_) => PlaygroundError::SchemaDiagnostics(wasm_diagnostics),
    }
}

fn serialize_result(result: PlaygroundResult) -> String {
    serde_json::to_string(&result).unwrap()
}

#[wasm_bindgen(start)]
pub fn start() {
    console_error_panic_hook::set_once();
}


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

use intern::string_key::Intern;
use intern::string_key::StringKey;
use serde::Deserialize;
use serde::Serialize;

/// Configuration where Relay should expect some fields in the schema.
#[derive(Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ConnectionInterface {
    pub cursor: StringKey,
    pub edges: StringKey,
    pub end_cursor: StringKey,
    pub has_next_page: StringKey,
    pub has_previous_page: StringKey,
    pub node: StringKey,
    pub page_info: StringKey,
    pub start_cursor: StringKey,
}

impl Default for ConnectionInterface {
    fn default() -> Self {
        ConnectionInterface {
            cursor: "cursor".intern(),
            edges: "edges".intern(),
            end_cursor: "endCursor".intern(),
            has_next_page: "hasNextPage".intern(),
            has_previous_page: "hasPreviousPage".intern(),
            node: "node".intern(),
            page_info: "pageInfo".intern(),
            start_cursor: "startCursor".intern(),
        }
    }
}


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

use common::DiagnosticSeverity;
use serde::Deserialize;
use serde::Serialize;

/// Levels for reporting errors in the compiler.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticLevel {
    /// Report only errors
    Error,
    /// Report diagnostics up to warnings
    Warning,
    /// Report diagnostics up to informational diagnostics
    Info,
    /// Report diagnostics up to hints
    Hint,
}

impl From<DiagnosticLevel> for DiagnosticSeverity {
    fn from(level: DiagnosticLevel) -> DiagnosticSeverity {
        match level {
            DiagnosticLevel::Error => DiagnosticSeverity::ERROR,
            DiagnosticLevel::Warning => DiagnosticSeverity::WARNING,
            DiagnosticLevel::Info => DiagnosticSeverity::INFORMATION,
            DiagnosticLevel::Hint => DiagnosticSeverity::HINT,
        }
    }
}

/// Configuration for all diagnostic reporting in the compiler
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DiagnosticReportConfig {
    /// Threshold for diagnostics to be critical to the compiler's execution.
    /// All diagnostic with severities at and below this level will cause the
    /// compiler to fatally exit.
    pub critical_level: DiagnosticLevel,
}

impl Default for DiagnosticReportConfig {
    fn default() -> Self {
        Self {
            critical_level: DiagnosticLevel::Error,
        }
    }
}


/*
 * 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 serde::Deserialize;
use serde::Serialize;

/// Formatting style for generated files.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum JsModuleFormat {
    /// Common JS style, e.g. `require('../path/MyModule')`
    CommonJS,
    /// Facebook style, e.g. `require('MyModule')`
    Haste,
}

impl Default for JsModuleFormat {
    fn default() -> Self {
        JsModuleFormat::CommonJS
    }
}


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

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

mod connection_interface;
mod diagnostic_report_config;
mod js_module_format;
mod module_import_config;
mod non_node_id_fields_config;
mod project_config;
mod typegen_config;

pub use connection_interface::ConnectionInterface;
pub use diagnostic_report_config::DiagnosticLevel;
pub use diagnostic_report_config::DiagnosticReportConfig;
pub use js_module_format::JsModuleFormat;
pub use module_import_config::DynamicModuleProvider;
pub use module_import_config::ModuleImportConfig;
pub use non_node_id_fields_config::NonNodeIdFieldsConfig;
pub use project_config::LocalPersistAlgorithm;
pub use project_config::LocalPersistConfig;
pub use project_config::PersistConfig;
pub use project_config::ProjectConfig;
pub use project_config::ProjectName;
pub use project_config::RemotePersistConfig;
pub use project_config::SchemaConfig;
pub use project_config::SchemaLocation;
pub use typegen_config::CustomScalarType;
pub use typegen_config::CustomScalarTypeImport;
pub use typegen_config::FlowTypegenConfig;
pub use typegen_config::TypegenConfig;
pub use typegen_config::TypegenLanguage;


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

use intern::string_key::StringKey;
use serde::Deserialize;
use serde::Serialize;

/// Configuration for @module.
#[derive(Debug, Deserialize, Serialize, Default, Copy, Clone)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct ModuleImportConfig {
    /// Defines the custom import statement to be generated on the
    /// `ModuleImport` node in ASTs, used for dynamically loading
    /// components at runtime.
    pub dynamic_module_provider: Option<DynamicModuleProvider>,
}

#[derive(Debug, Deserialize, Serialize, Eq, Clone, Copy, PartialEq, Hash)]
#[serde(tag = "mode")]
pub enum DynamicModuleProvider {
    /// Generates a module provider using JSResource
    JSResource,
    /// Generates a custom JS import, Use `<$module>` as the placeholder
    /// for the actual module. e.g. `"() => import('<$module>')"`
    Custom { statement: StringKey },
}


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

use std::collections::HashMap;

use intern::string_key::StringKey;
use serde::Deserialize;
use serde::Serialize;

/// Configuration of Relay's validation for `id` fields outside of the `Node` interface.
#[derive(Debug, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct NonNodeIdFieldsConfig {
    /// A map of parent type names to allowed type names for fields named `id`
    #[serde(default)]
    pub allowed_id_types: HashMap<StringKey, StringKey>,
}


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

use std::fmt;
use std::path::Path;
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use std::usize;

use common::DirectiveName;
use common::FeatureFlags;
use common::Rollout;
use common::SourceLocationKey;
use common::WithLocation;
use fmt::Debug;
use fnv::FnvBuildHasher;
use indexmap::IndexMap;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use regex::Regex;
use serde::de::Error;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde_json::Value;

use crate::connection_interface::ConnectionInterface;
use crate::diagnostic_report_config::DiagnosticReportConfig;
use crate::module_import_config::ModuleImportConfig;
use crate::non_node_id_fields_config::NonNodeIdFieldsConfig;
use crate::JsModuleFormat;
use crate::TypegenConfig;
use crate::TypegenLanguage;

type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;

pub type ProjectName = StringKey;

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct RemotePersistConfig {
    /// URL to send a POST request to to persist.
    pub url: String,
    /// The document will be in a POST parameter `text`. This map can contain
    /// additional parameters to send.
    #[serde(default)]
    pub params: FnvIndexMap<String, String>,

    /// Additional headers to send
    #[serde(default)]
    pub headers: FnvIndexMap<String, String>,

    #[serde(
        default,
        rename = "concurrency",
        deserialize_with = "deserialize_semaphore_permits"
    )]
    pub semaphore_permits: Option<usize>,

    #[serde(default)]
    pub include_query_text: bool,
}

fn deserialize_semaphore_permits<'de, D>(d: D) -> Result<Option<usize>, D::Error>
where
    D: Deserializer<'de>,
{
    let permits: usize = Deserialize::deserialize(d)?;
    if permits == 0 {
        return Err(Error::custom(
            "Invalid `persistConfig.concurrency` value. Please, increase the number of concurrent request for query persisting. 0 is not going to work.",
        ));
    }
    Ok(Some(permits))
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum LocalPersistAlgorithm {
    MD5,
    SHA1,
    SHA256,
}

impl Default for LocalPersistAlgorithm {
    // For backwards compatibility
    fn default() -> Self {
        Self::MD5
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct LocalPersistConfig {
    pub file: PathBuf,

    #[serde(default)]
    pub algorithm: LocalPersistAlgorithm,

    #[serde(default)]
    pub include_query_text: bool,
}

#[derive(Debug, Serialize, Clone)]
#[serde(untagged)]
pub enum PersistConfig {
    Remote(RemotePersistConfig),
    Local(LocalPersistConfig),
}

impl PersistConfig {
    pub fn include_query_text(&self) -> bool {
        match self {
            PersistConfig::Remote(remote_config) => remote_config.include_query_text,
            PersistConfig::Local(local_config) => local_config.include_query_text,
        }
    }
}

impl<'de> Deserialize<'de> for PersistConfig {
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
        let value = Value::deserialize(deserializer)?;
        match RemotePersistConfig::deserialize(value.clone()) {
            Ok(remote_config) => Ok(PersistConfig::Remote(remote_config)),
            Err(remote_error) => match LocalPersistConfig::deserialize(value) {
                Ok(local_config) => {
                    if !local_config.file.exists() {
                        Err(Error::custom(format!(
                            "The file `{}` for the local query persisting does not exist. Please, make sure the file path is correct.",
                            local_config.file.display()
                        )))
                    } else {
                        Ok(PersistConfig::Local(local_config))
                    }
                }
                Err(local_error) => {
                    let error_message = format!(
                        r#"Persist configuration cannot be parsed as a remote configuration due to:
- {:?}.

It also cannot be a local persist configuration due to:
- {:?}."#,
                        remote_error, local_error
                    );

                    Err(Error::custom(error_message))
                }
            },
        }
    }
}

#[derive(Clone, Debug)]
pub enum SchemaLocation {
    File(PathBuf),
    Directory(PathBuf),
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaConfig {
    #[serde(default)]
    pub connection_interface: ConnectionInterface,

    /// The name of the `id` field that exists on the `Node` interface.
    #[serde(default = "default_node_interface_id_field")]
    pub node_interface_id_field: StringKey,

    /// The name of the variable expected by the `node` query.
    #[serde(default = "default_node_interface_id_variable_name")]
    pub node_interface_id_variable_name: StringKey,

    #[serde(default)]
    pub non_node_id_fields: Option<NonNodeIdFieldsConfig>,

    /// The name of the directive indicating fields that cannot be selected
    #[serde(default = "default_unselectable_directive_name")]
    pub unselectable_directive_name: DirectiveName,
}

fn default_node_interface_id_field() -> StringKey {
    "id".intern()
}

fn default_node_interface_id_variable_name() -> StringKey {
    "id".intern()
}

fn default_unselectable_directive_name() -> DirectiveName {
    DirectiveName("unselectable".intern())
}

impl Default for SchemaConfig {
    fn default() -> Self {
        Self {
            connection_interface: ConnectionInterface::default(),
            node_interface_id_field: default_node_interface_id_field(),
            node_interface_id_variable_name: default_node_interface_id_variable_name(),
            non_node_id_fields: None,
            unselectable_directive_name: default_unselectable_directive_name(),
        }
    }
}

pub struct ProjectConfig {
    pub name: ProjectName,
    pub base: Option<ProjectName>,
    pub output: Option<PathBuf>,
    pub extra_artifacts_output: Option<PathBuf>,
    pub shard_output: bool,
    pub shard_strip_regex: Option<Regex>,
    pub schema_extensions: Vec<PathBuf>,
    pub enabled: bool,
    pub schema_location: SchemaLocation,
    pub schema_config: SchemaConfig,
    pub typegen_config: TypegenConfig,
    pub persist: Option<PersistConfig>,
    pub variable_names_comment: bool,
    pub extra: serde_json::Value,
    pub feature_flags: Arc<FeatureFlags>,
    pub test_path_regex: Option<Regex>,
    pub filename_for_artifact:
        Option<Box<dyn (Fn(SourceLocationKey, StringKey) -> String) + Send + Sync>>,
    pub skip_types_for_artifact: Option<Box<dyn (Fn(SourceLocationKey) -> bool) + Send + Sync>>,
    pub rollout: Rollout,
    pub js_module_format: JsModuleFormat,
    pub module_import_config: ModuleImportConfig,
    pub diagnostic_report_config: DiagnosticReportConfig,
}

impl Default for ProjectConfig {
    fn default() -> Self {
        Self {
            name: "default".intern(),
            feature_flags: Default::default(),
            base: None,
            output: None,
            extra_artifacts_output: None,
            shard_output: false,
            shard_strip_regex: None,
            schema_extensions: vec![],
            enabled: true,
            schema_location: SchemaLocation::File(PathBuf::default()),
            schema_config: Default::default(),
            typegen_config: Default::default(),
            persist: None,
            variable_names_comment: false,
            extra: Default::default(),
            test_path_regex: None,
            filename_for_artifact: None,
            skip_types_for_artifact: None,
            rollout: Default::default(),
            js_module_format: Default::default(),
            module_import_config: Default::default(),
            diagnostic_report_config: Default::default(),
        }
    }
}

impl Debug for ProjectConfig {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let ProjectConfig {
            name,
            base,
            output,
            extra_artifacts_output,
            shard_output,
            shard_strip_regex,
            schema_extensions,
            enabled,
            schema_location,
            schema_config,
            typegen_config,
            persist,
            variable_names_comment,
            extra,
            feature_flags,
            test_path_regex,
            filename_for_artifact,
            skip_types_for_artifact,
            rollout,
            js_module_format,
            module_import_config,
            diagnostic_report_config,
        } = self;
        f.debug_struct("ProjectConfig")
            .field("name", name)
            .field("base", base)
            .field("output", output)
            .field("extra_artifacts_output", extra_artifacts_output)
            .field("shard_output", shard_output)
            .field("shard_strip_regex", shard_strip_regex)
            .field("schema_extensions", schema_extensions)
            .field("enabled", enabled)
            .field("schema_location", schema_location)
            .field("schema_config", schema_config)
            .field("typegen_config", typegen_config)
            .field("persist", persist)
            .field("variable_names_comment", variable_names_comment)
            .field("extra", extra)
            .field("feature_flags", feature_flags)
            .field("test_path_regex", test_path_regex)
            .field(
                "filename_for_artifact",
                &if filename_for_artifact.is_some() {
                    "Some<Fn>"
                } else {
                    "None"
                },
            )
            .field(
                "skip_types_for_artifact",
                &if skip_types_for_artifact.is_some() {
                    "Some<Fn>"
                } else {
                    "None"
                },
            )
            .field("rollout", rollout)
            .field("js_module_format", js_module_format)
            .field("module_import_config", module_import_config)
            .field("diagnostic_report_config", diagnostic_report_config)
            .finish()
    }
}

impl ProjectConfig {
    /// This function will create a correct path for an artifact based on the project configuration
    pub fn create_path_for_artifact(
        &self,
        source_file: SourceLocationKey,
        artifact_file_name: String,
    ) -> PathBuf {
        if let Some(output) = &self.output {
            // If an output directory is specified, output into that directory.
            if self.shard_output {
                if let Some(ref regex) = self.shard_strip_regex {
                    let full_source_path = regex.replace_all(source_file.path(), "");
                    let mut output = output.join(full_source_path.to_string());
                    output.pop();
                    output
                } else {
                    output.join(source_file.get_dir())
                }
                .join(artifact_file_name)
            } else {
                output.join(artifact_file_name)
            }
        } else {
            // Otherwise, output into a file relative to the source.
            source_file
                .get_dir()
                .join("__generated__")
                .join(artifact_file_name)
        }
    }

    pub fn path_for_artifact(
        &self,
        source_file: SourceLocationKey,
        definition_name: StringKey,
    ) -> PathBuf {
        let filename = if let Some(filename_for_artifact) = &self.filename_for_artifact {
            filename_for_artifact(source_file, definition_name)
        } else {
            match &self.typegen_config.language {
                TypegenLanguage::Flow | TypegenLanguage::JavaScript => {
                    format!("{}.graphql.js", definition_name)
                }
                TypegenLanguage::TypeScript => format!("{}.graphql.ts", definition_name),
            }
        };
        self.create_path_for_artifact(source_file, filename)
    }

    /// Generates a relative import path in Common JS projects, and a module name in Haste projects.
    pub fn js_module_import_path(
        &self,
        definition_source_location: WithLocation<StringKey>,
        target_module: StringKey,
    ) -> StringKey {
        match self.js_module_format {
            JsModuleFormat::CommonJS => {
                let definition_artifact_location = self.path_for_artifact(
                    definition_source_location.location.source_location(),
                    definition_source_location.item,
                );

                let module_location =
                    PathBuf::from_str(target_module.lookup()).unwrap_or_else(|_| {
                        panic!(
                            "expected to be able to build a path from target_module : {}",
                            target_module.lookup()
                        );
                    });

                let module_path = module_location.parent().unwrap_or_else(||{
                    panic!(
                        "expected module_location: {:?} to have a parent path, maybe it's not a file?",
                        module_location
                    );
                });

                let definition_artifact_location_path = definition_artifact_location.parent().unwrap_or_else(||{panic!("expected definition_artifact_location: {:?} to have a parent path, maybe it's not a file?", definition_artifact_location);
            });

                let resolver_module_location =
                    pathdiff::diff_paths(module_path, definition_artifact_location_path).unwrap();

                let module_file_name = module_location.file_name().unwrap_or_else(|| {
                    panic!(
                        "expected module_location: {:?} to have a file name",
                        module_location
                    )
                });

                resolver_module_location
                    .join(module_file_name)
                    .to_string_lossy()
                    .intern()
            }
            JsModuleFormat::Haste => Path::new(&target_module.to_string())
                .file_stem()
                .unwrap()
                .to_string_lossy()
                .intern(),
        }
    }
}


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

use std::path::PathBuf;

use common::ScalarName;
use fnv::FnvBuildHasher;
use indexmap::IndexMap;
use intern::string_key::StringKey;
use serde::Deserialize;
use serde::Serialize;
use strum::EnumIter;
use strum::IntoEnumIterator;
type FnvIndexMap<K, V> = IndexMap<K, V, FnvBuildHasher>;

#[derive(
    EnumIter,
    strum::Display,
    Debug,
    Copy,
    Clone,
    Serialize,
    Deserialize,
    PartialEq
)]
#[serde(deny_unknown_fields, rename_all = "lowercase")]
pub enum TypegenLanguage {
    JavaScript,
    TypeScript,
    Flow,
}

impl Default for TypegenLanguage {
    fn default() -> Self {
        Self::JavaScript
    }
}

impl TypegenLanguage {
    pub fn get_variants_as_string() -> Vec<String> {
        let mut res = vec![];
        for lang in Self::iter() {
            res.push(lang.to_string().to_lowercase());
        }
        res
    }
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum CustomScalarType {
    Name(StringKey),
    Path(CustomScalarTypeImport),
}

#[derive(Debug, Serialize, Deserialize, Clone)]

pub struct CustomScalarTypeImport {
    pub name: StringKey,
    pub path: PathBuf,
}

#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields, rename_all = "camelCase")]
pub struct TypegenConfig {
    /// The desired output language, "flow" or "typescript".
    pub language: TypegenLanguage,

    /// # For Flow type generation
    /// When set, enum values are imported from a module with this suffix.
    /// For example, an enum Foo and this property set to ".test" would be
    /// imported from "Foo.test".
    /// Note: an empty string is allowed and different from not setting the
    /// value, in the example above it would just import from "Foo".
    pub enum_module_suffix: Option<String>,

    /// # For Flow type generation
    /// When set, generated input types will have the listed fields optional
    /// even if the schema defines them as required.
    #[serde(default)]
    pub optional_input_fields: Vec<StringKey>,

    /// # For Typescript type generation
    /// Whether to use the `import type` syntax introduced in Typescript
    /// version 3.8. This will prevent warnings from `importsNotUsedAsValues`.
    #[serde(default)]
    pub use_import_type_syntax: bool,

    /// A map from GraphQL scalar types to a custom JS type, example:
    /// { "Url": "String" }
    /// { "Url": {"name:: "MyURL", "path": "../src/MyUrlTypes"} }
    #[serde(default)]
    pub custom_scalar_types: FnvIndexMap<ScalarName, CustomScalarType>,

    /// Require all GraphQL scalar types mapping to be defined, will throw
    /// if a GraphQL scalar type doesn't have a JS type
    #[serde(default)]
    pub require_custom_scalar_types: bool,

    /// Work in progress new Flow type definitions
    #[serde(default)]
    pub flow_typegen: FlowTypegenConfig,

    /// This option enables emitting es modules artifacts.
    #[serde(default)]
    pub eager_es_modules: bool,
}

#[derive(Default, Debug, Serialize, Deserialize, Clone, Copy)]
#[serde(deny_unknown_fields, tag = "phase")]
pub struct FlowTypegenConfig {
    /// This option controls whether or not a catch-all entry is added to enum type definitions
    /// for values that may be added in the future. Enabling this means you will have to update
    /// your application whenever the GraphQL server schema adds new enum values to prevent it
    /// from breaking.
    #[serde(default)]
    pub no_future_proof_enums: bool,
}


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

use std::collections::HashMap;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::NamedItem;
use common::SourceLocationKey;
use common::Span;
use common::WithLocation;
use docblock_shared::ARGUMENT_DEFINITIONS;
use docblock_shared::ARGUMENT_TYPE;
use docblock_shared::DEFAULT_VALUE;
use docblock_shared::PROVIDER_ARG_NAME;
use graphql_ir::reexport::Intern;
use graphql_ir::reexport::StringKey;
use graphql_ir::FragmentDefinitionName;
use graphql_syntax::parse_field_definition;
use graphql_syntax::parse_field_definition_stub;
use graphql_syntax::parse_identifier;
use graphql_syntax::parse_identifier_and_implements_interfaces;
use graphql_syntax::parse_type;
use graphql_syntax::ConstantValue;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::FragmentDefinition;
use graphql_syntax::Identifier;
use graphql_syntax::InputValueDefinition;
use graphql_syntax::List;
use graphql_syntax::TypeAnnotation;
use intern::Lookup;

use crate::errors::ErrorMessagesWithData;
use crate::errors::IrParsingErrorMessages;
use crate::ir::Argument;
use crate::ir::IrField;
use crate::ir::OutputType;
use crate::ir::PopulatedIrField;
use crate::ir::StrongObjectIr;
use crate::ir::TerseRelayResolverIr;
use crate::ir::UnpopulatedIrField;
use crate::ir::WeakObjectIr;
use crate::untyped_representation::AllowedFieldName;
use crate::untyped_representation::UntypedDocblockRepresentation;
use crate::DocblockIr;
use crate::On;
use crate::ParseOptions;
use crate::RelayResolverIr;

pub(crate) fn parse_docblock_ir(
    untyped_representation: UntypedDocblockRepresentation,
    definitions_in_file: Option<&Vec<ExecutableDefinition>>,
    parse_options: &ParseOptions<'_>,
    // The location corresponding to the entire docblock. Used for error messages like
    // "this field is missing".
    docblock_location: Location,
) -> DiagnosticsResult<Option<DocblockIr>> {
    // Categorization:
    // - If there is no @RelayResolver field, return Ok(None)
    // - If there is an unpopulated @RelayResolver field, parse a RelayResolverIr
    // - If there is a populated @RelayResolver field and its value contains a '.'
    //   (dot character), parse a TerseRelayResolver
    // - If there is a populated @RelayResolver field and a populated @weak field,
    //   emit an error.
    // - If there is a populated @RelayResolver field and an unpopulated @weak field,
    //   parse a WeakResolverIr
    // - Otherwise, if there is a populated @RelayResolver field and no @weak field,
    //   parse a StrongResolverIr
    //
    // Categorization is infallible (except as detailed above); we assume that the user
    // got that part right and surface errors accordingly, even though it might be better
    // (for example) if a user includes multiple strong-only fields *and* a @weak field to
    // suggest the the user removes the @weak field. But we don't do that.

    let UntypedDocblockRepresentation {
        description,
        mut fields,
    } = untyped_representation;

    let resolver_field = match fields.remove(&AllowedFieldName::RelayResolverField) {
        Some(resolver_field) => resolver_field,
        None => return Ok(None),
    };
    let parsed_docblock_ir = match resolver_field {
        IrField::UnpopulatedIrField(unpopulated_ir_field) => {
            DocblockIr::RelayResolver(parse_relay_resolver_ir(
                &mut fields,
                definitions_in_file,
                description,
                None, // This might be necessary for field hack source links
                docblock_location,
                unpopulated_ir_field,
                parse_options,
            )?)
        }
        IrField::PopulatedIrField(populated_ir_field) => {
            if populated_ir_field.value.item.lookup().contains('.') {
                DocblockIr::TerseRelayResolver(parse_terse_relay_resolver_ir(
                    &mut fields,
                    populated_ir_field,
                    definitions_in_file,
                    docblock_location,
                )?)
            } else {
                match get_optional_unpopulated_field_named(
                    &mut fields,
                    AllowedFieldName::WeakField,
                )? {
                    Some(weak_field) => DocblockIr::WeakObjectType(parse_weak_object_ir(
                        &mut fields,
                        description,
                        None, // This might be necessary for field hack source links
                        docblock_location,
                        populated_ir_field,
                        weak_field,
                    )?),
                    None => DocblockIr::StrongObjectResolver(parse_strong_object_ir(
                        &mut fields,
                        description,
                        docblock_location,
                        populated_ir_field,
                    )?),
                }
            }
        }
    };

    assert_all_fields_removed(
        fields,
        resolver_field.key_location(),
        parsed_docblock_ir.get_variant_name(),
    )?;

    Ok(Some(parsed_docblock_ir))
}

fn parse_relay_resolver_ir(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    definitions_in_file: Option<&Vec<ExecutableDefinition>>,
    description: Option<WithLocation<StringKey>>,
    hack_source: Option<WithLocation<StringKey>>,
    location: Location,
    _resolver_field: UnpopulatedIrField,
    parse_options: &ParseOptions<'_>,
) -> DiagnosticsResult<RelayResolverIr> {
    let root_fragment =
        get_optional_populated_field_named(fields, AllowedFieldName::RootFragmentField)?;
    let field_name =
        get_required_populated_field_named(fields, AllowedFieldName::FieldNameField, location)?;
    let field_string = field_name.value;
    let field_string_offset = field_string.location.span().start;
    let field_definition_stub = parse_field_definition_stub(
        field_string.item.lookup(),
        field_string.location.source_location(),
        field_string_offset,
    )?;
    let (fragment_type_condition, fragment_arguments) = parse_fragment_definition(
        root_fragment,
        field_name.value.location.source_location(),
        &field_definition_stub.arguments,
        definitions_in_file,
    )?;

    let edge_to_opt = get_optional_populated_field_named(fields, AllowedFieldName::EdgeToField)?;
    let output_type_opt =
        get_optional_populated_field_named(fields, AllowedFieldName::OutputTypeField)?;

    if let Some(output_type) = output_type_opt {
        if !parse_options
            .enable_output_type
            .is_enabled_for(field_definition_stub.name.value)
        {
            return Err(vec![Diagnostic::error(
                IrParsingErrorMessages::UnexpectedOutputType {
                    field_name: field_definition_stub.name.value,
                },
                output_type.key_location,
            )]);
        }
    }

    let output_type = combine_edge_to_and_output_type(edge_to_opt, output_type_opt)?;

    let on_type_opt = fields.remove(&AllowedFieldName::OnTypeField);
    let on_interface_opt = fields.remove(&AllowedFieldName::OnInterfaceField);
    let on = combine_on_type_on_interface_fields(
        on_type_opt,
        on_interface_opt,
        location,
        &fragment_type_condition,
    )?;

    validate_field_arguments(&field_definition_stub.arguments, location.source_location())?;

    Ok(RelayResolverIr {
        live: get_optional_unpopulated_field_named(fields, AllowedFieldName::LiveField)?,
        on,
        root_fragment: root_fragment
            .map(|root_fragment| root_fragment.value.map(FragmentDefinitionName)),
        description,
        hack_source,
        deprecated: fields.remove(&AllowedFieldName::DeprecatedField),
        location,
        field: field_definition_stub,
        output_type,
        fragment_arguments,
    })
}

fn parse_strong_object_ir(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    description: Option<WithLocation<StringKey>>,
    location: Location,
    relay_resolver_field: PopulatedIrField,
) -> DiagnosticsResult<StrongObjectIr> {
    let type_str = relay_resolver_field.value;
    let (identifier, implements_interfaces) = parse_identifier_and_implements_interfaces(
        type_str.item.lookup(),
        type_str.location.source_location(),
        type_str.location.span().start,
    )?;

    let fragment_name = FragmentDefinitionName(format!("{}__id", identifier.value).intern());

    Ok(StrongObjectIr {
        type_name: identifier,
        rhs_location: relay_resolver_field.value.location,
        root_fragment: WithLocation::generated(fragment_name),
        description,
        deprecated: fields.remove(&AllowedFieldName::DeprecatedField),
        live: get_optional_unpopulated_field_named(fields, AllowedFieldName::LiveField)?,
        location,
        implements_interfaces,
    })
}

fn parse_weak_object_ir(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    description: Option<WithLocation<StringKey>>,
    hack_source: Option<WithLocation<StringKey>>,
    location: Location,
    relay_resolver_field: PopulatedIrField,
    _weak_field: UnpopulatedIrField,
) -> DiagnosticsResult<WeakObjectIr> {
    // Validate that the right hand side of the @RelayResolver field is a valid identifier
    let identifier = assert_only_identifier(relay_resolver_field)?;

    Ok(WeakObjectIr {
        type_name: identifier,
        rhs_location: relay_resolver_field.value.location,
        description,
        hack_source,
        deprecated: fields.remove(&AllowedFieldName::DeprecatedField),
        location,
    })
}

fn parse_terse_relay_resolver_ir(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    relay_resolver_field: PopulatedIrField,
    definitions_in_file: Option<&Vec<ExecutableDefinition>>,
    location: Location,
) -> DiagnosticsResult<TerseRelayResolverIr> {
    let root_fragment =
        get_optional_populated_field_named(fields, AllowedFieldName::RootFragmentField)?;
    let type_str = relay_resolver_field.value;

    // Validate that the right hand side of the @RelayResolver field is a valid identifier
    let type_name = extract_identifier(relay_resolver_field)?;

    let (start, end) = type_name.span.as_usize();
    let offset = end - start;
    let remaining_source = &type_str.item.lookup()[offset..];
    let span_start = type_str.location.span().start + offset as u32;

    match remaining_source.chars().next() {
        Some(dot) if dot == '.' => {}
        Some(other) => {
            return Err(vec![Diagnostic::error(
                IrParsingErrorMessages::UnexpectedNonDot { found: other },
                type_str
                    .location
                    .with_span(Span::new(span_start, span_start + 1)),
            )]);
        }
        None => {
            // This won't happen, unless the dot was parsed as part of
            // the identifier.
            panic!("Unexpected missing next char. This indicates a bug in the Relay compiler.");
        }
    };

    let field = parse_field_definition(
        &remaining_source[1..],
        type_str.location.source_location(),
        span_start + 1,
    )?;

    if let TypeAnnotation::NonNull(non_null) = field.type_ {
        return Err(vec![Diagnostic::error(
            IrParsingErrorMessages::FieldWithNonNullType,
            Location::new(type_str.location.source_location(), non_null.span),
        )]);
    }

    validate_field_arguments(&field.arguments, location.source_location())?;

    let (fragment_type_condition, fragment_arguments) = parse_fragment_definition(
        root_fragment,
        relay_resolver_field.value.location.source_location(),
        &field.arguments,
        definitions_in_file,
    )?;

    if let Some(fragment_type_condition) = fragment_type_condition {
        if fragment_type_condition.item != type_name.value {
            return Err(vec![
                Diagnostic::error(
                    IrParsingErrorMessages::MismatchRootFragmentTypeConditionTerseSyntax {
                        fragment_type_condition: fragment_type_condition.item,
                        type_name: type_name.value,
                    },
                    type_str.location.with_span(type_name.span),
                )
                .annotate(
                    "with fragment type condition",
                    fragment_type_condition.location,
                ),
            ]);
        }
    }

    Ok(TerseRelayResolverIr {
        field,
        type_: WithLocation::new(type_str.location.with_span(type_name.span), type_name.value),
        root_fragment: root_fragment
            .map(|root_fragment| root_fragment.value.map(FragmentDefinitionName)),
        location,
        deprecated: fields.remove(&AllowedFieldName::DeprecatedField),
        live: get_optional_unpopulated_field_named(fields, AllowedFieldName::LiveField)?,
        fragment_arguments,
    })
}

fn combine_on_type_on_interface_fields(
    on_type_opt: Option<IrField>,
    on_interface_opt: Option<IrField>,
    location: Location,
    fragment_type_condition: &Option<WithLocation<StringKey>>,
) -> Result<On, Diagnostic> {
    match (on_type_opt, on_interface_opt) {
        (None, None) => Err(Diagnostic::error(
            IrParsingErrorMessages::ExpectedOneOrTheOther {
                field_1: AllowedFieldName::OnTypeField,
                field_2: AllowedFieldName::OnInterfaceField,
            },
            location,
        )),
        (Some(on_type), None) => {
            let on_type: PopulatedIrField = on_type.try_into().map_err(|_| {
                Diagnostic::error(
                    IrParsingErrorMessages::MissingFieldValue {
                        field_name: AllowedFieldName::OnTypeField,
                    },
                    on_type.key_location(),
                )
            })?;
            match fragment_type_condition {
                Some(fragment_type_condition) => {
                    if on_type.value.item == fragment_type_condition.item {
                        Ok(On::Type(on_type))
                    } else {
                        Err(Diagnostic::error(
                            IrParsingErrorMessages::MismatchRootFragmentTypeCondition {
                                fragment_type_condition: fragment_type_condition.item,
                                on_field_value: on_type.value.item,
                                on_field_name: AllowedFieldName::OnTypeField,
                            },
                            on_type.value.location,
                        )
                        .annotate(
                            "with fragment type condition",
                            fragment_type_condition.location,
                        ))
                    }
                }
                None => Ok(On::Type(on_type)),
            }
        }
        (None, Some(on_interface)) => {
            let on_interface: PopulatedIrField = on_interface.try_into().map_err(|_| {
                Diagnostic::error(
                    IrParsingErrorMessages::MissingFieldValue {
                        field_name: AllowedFieldName::OnInterfaceField,
                    },
                    on_interface.key_location(),
                )
            })?;
            match fragment_type_condition {
                Some(fragment_type_condition) => {
                    if on_interface.value.item == fragment_type_condition.item {
                        Ok(On::Interface(on_interface))
                    } else {
                        Err(Diagnostic::error(
                            IrParsingErrorMessages::MismatchRootFragmentTypeCondition {
                                fragment_type_condition: fragment_type_condition.item,
                                on_field_value: on_interface.value.item,
                                on_field_name: AllowedFieldName::OnInterfaceField,
                            },
                            on_interface.value.location,
                        )
                        .annotate(
                            "with fragment type condition",
                            fragment_type_condition.location,
                        ))
                    }
                }
                None => Ok(On::Interface(on_interface)),
            }
        }
        (Some(on_type), Some(on_interface)) => Err(Diagnostic::error(
            IrParsingErrorMessages::IncompatibleFields {
                field_1: AllowedFieldName::OnTypeField,
                field_2: AllowedFieldName::OnInterfaceField,
            },
            on_type.key_location(),
        )
        .annotate("@onInterface", on_interface.key_location())),
    }
}

fn combine_edge_to_and_output_type(
    edge_to_opt: Option<PopulatedIrField>,
    output_type_opt: Option<PopulatedIrField>,
) -> DiagnosticsResult<Option<OutputType>> {
    match (edge_to_opt, output_type_opt) {
        (None, None) => Ok(None),
        (Some(edge_to), None) => {
            parse_type_annotation(edge_to.value).map(|val| Some(OutputType::EdgeTo(val)))
        }
        (None, Some(output_type)) => {
            parse_type_annotation(output_type.value).map(|val| Some(OutputType::Output(val)))
        }
        (Some(edge_to), Some(output_type)) => Err(vec![
            Diagnostic::error(
                IrParsingErrorMessages::IncompatibleFields {
                    field_1: AllowedFieldName::EdgeToField,
                    field_2: AllowedFieldName::OutputTypeField,
                },
                edge_to.key_location,
            )
            .annotate("@outputType", output_type.key_location),
        ]),
    }
}

fn parse_type_annotation(
    value: WithLocation<StringKey>,
) -> DiagnosticsResult<WithLocation<TypeAnnotation>> {
    let type_annotation = parse_type(
        value.item.lookup(),
        value.location.source_location(),
        value.location.span().start,
    )?;

    let valid_type_annotation = match &type_annotation {
        TypeAnnotation::Named(_) => type_annotation,
        TypeAnnotation::List(item_type) => match &item_type.type_ {
            TypeAnnotation::NonNull(_) => {
                return Err(vec![Diagnostic::error(
                    IrParsingErrorMessages::UnexpectedNonNullableItemInListEdgeTo {},
                    value.location,
                )]);
            }
            _ => type_annotation,
        },
        TypeAnnotation::NonNull(_) => {
            return Err(vec![Diagnostic::error(
                IrParsingErrorMessages::UnexpectedNonNullableEdgeTo {},
                value.location,
            )]);
        }
    };
    Ok(WithLocation::new(value.location, valid_type_annotation))
}

fn get_optional_unpopulated_field_named(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    field_name: AllowedFieldName,
) -> Result<Option<UnpopulatedIrField>, Diagnostic> {
    fields
        .remove(&field_name)
        .map(|field_value| try_into_unpopulated_field(field_name, field_value))
        .transpose()
}

fn get_optional_populated_field_named(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    field_name: AllowedFieldName,
) -> Result<Option<PopulatedIrField>, Diagnostic> {
    fields
        .remove(&field_name)
        .map(|field_value| try_into_populated_field(field_name, field_value))
        .transpose()
}

fn get_required_populated_field_named(
    fields: &mut HashMap<AllowedFieldName, IrField>,
    field_name: AllowedFieldName,
    location: Location,
) -> Result<PopulatedIrField, Diagnostic> {
    get_optional_populated_field_named(fields, field_name).and_then(|e| {
        e.ok_or_else(|| {
            Diagnostic::error(
                IrParsingErrorMessages::MissingField { field_name },
                location,
            )
        })
    })
}

fn try_into_unpopulated_field(
    field_name: AllowedFieldName,
    field_value: IrField,
) -> Result<UnpopulatedIrField, Diagnostic> {
    field_value.try_into().map_err(|_| {
        Diagnostic::error(
            IrParsingErrorMessages::FieldWithUnexpectedData { field_name },
            field_value.key_location(),
        )
    })
}

fn try_into_populated_field(
    field_name: AllowedFieldName,
    field_value: IrField,
) -> Result<PopulatedIrField, Diagnostic> {
    field_value.try_into().map_err(|_| {
        Diagnostic::error(
            IrParsingErrorMessages::FieldWithMissingData { field_name },
            field_value.key_location(),
        )
    })
}

fn parse_fragment_definition(
    root_fragment: Option<PopulatedIrField>,
    source_location: SourceLocationKey,
    field_arguments: &Option<List<InputValueDefinition>>,
    definitions_in_file: Option<&Vec<ExecutableDefinition>>,
) -> DiagnosticsResult<(Option<WithLocation<StringKey>>, Option<Vec<Argument>>)> {
    let fragment_definition = root_fragment
        .map(|root_fragment| assert_fragment_definition(root_fragment.value, definitions_in_file))
        .transpose()?;

    let fragment_arguments = fragment_definition
        .as_ref()
        .and_then(extract_fragment_arguments)
        .transpose()?;

    // Validate that the field arguments don't collide with the fragment arguments.
    if let (Some(field_arguments), Some(fragment_definition), Some(fragment_arguments)) =
        (&field_arguments, &fragment_definition, &fragment_arguments)
    {
        for field_arg in &field_arguments.items {
            if let Some(fragment_arg) = fragment_arguments.named(field_arg.name.value) {
                return Err(vec![
                    Diagnostic::error(
                        IrParsingErrorMessages::ConflictingArguments,
                        Location::new(source_location, field_arg.name.span),
                    )
                    .annotate(
                        "conflicts with this fragment argument",
                        fragment_definition
                            .location
                            .with_span(fragment_arg.name.span),
                    ),
                ]);
            }
        }
    }
    let fragment_type_condition = fragment_definition.as_ref().map(|fragment_definition| {
        WithLocation::from_span(
            fragment_definition.location.source_location(),
            fragment_definition.type_condition.span,
            fragment_definition.type_condition.type_.value,
        )
    });
    Ok((fragment_type_condition, fragment_arguments))
}

fn assert_fragment_definition(
    root_fragment: WithLocation<StringKey>,
    definitions_in_file: Option<&Vec<ExecutableDefinition>>,
) -> Result<FragmentDefinition, Diagnostic> {
    let fragment_definition = definitions_in_file.and_then(|defs| {
        defs.iter().find(|item| {
            if let ExecutableDefinition::Fragment(fragment) = item {
                fragment.name.value == root_fragment.item
            } else {
                false
            }
        })
    });
    if let Some(ExecutableDefinition::Fragment(fragment_definition)) = fragment_definition {
        Ok(fragment_definition.clone())
    } else {
        let suggestions = definitions_in_file
            .map(|defs| defs.iter().filter_map(|def| def.name()).collect::<Vec<_>>())
            .unwrap_or_default();

        Err(Diagnostic::error(
            ErrorMessagesWithData::FragmentNotFound {
                fragment_name: root_fragment.item,
                suggestions,
            },
            root_fragment.location,
        ))
    }
}

fn extract_fragment_arguments(
    fragment_definition: &FragmentDefinition,
) -> Option<DiagnosticsResult<Vec<Argument>>> {
    fragment_definition
        .directives
        .named(ARGUMENT_DEFINITIONS.0)
        .and_then(|directive| directive.arguments.as_ref())
        .map(|arguments| {
            arguments
                .items
                .iter()
                .filter_map(|arg: &graphql_syntax::Argument| {
                    let (type_, default_value) =
                        if let graphql_syntax::Value::Constant(ConstantValue::Object(object)) =
                            &arg.value
                        {
                            if object
                                .items
                                .iter()
                                .any(|item| item.name.value == *PROVIDER_ARG_NAME)
                            {
                                return None;
                            }

                            let type_value = &object
                                .items
                                .iter()
                                .find(|item| item.name.value == *ARGUMENT_TYPE)
                                .map(|type_| type_.value.clone());

                            let default_value = &object
                                .items
                                .iter()
                                .find(|item| item.name.value == *DEFAULT_VALUE)
                                .map(|default_value| default_value.value.clone());

                            if let Some(ConstantValue::String(string_value)) = type_value {
                                (
                                    parse_type(
                                        string_value.value.lookup(),
                                        fragment_definition.location.source_location(),
                                        // We don't currently have span information
                                        // for constant values, so we can't derive a
                                        // reasonable offset here.
                                        0,
                                    ),
                                    default_value.clone(),
                                )
                            } else {
                                panic!("Expect ConstantValue::String as a type");
                            }
                        } else {
                            panic!("Expect the constant value for the argDef: {:?}", &arg.value);
                        };

                    Some(type_.map(|type_| Argument {
                        name: arg.name.clone(),
                        type_,
                        default_value,
                    }))
                })
                .collect::<Result<_, _>>()
        })
}

fn validate_field_arguments(
    arguments: &Option<List<InputValueDefinition>>,
    source_location: SourceLocationKey,
) -> DiagnosticsResult<()> {
    let mut errors = vec![];
    if let Some(field_arguments) = &arguments {
        for argument in field_arguments.items.iter() {
            if let Some(default_value) = &argument.default_value {
                errors.push(Diagnostic::error(
                    IrParsingErrorMessages::ArgumentDefaultValuesNoSupported,
                    Location::new(source_location, default_value.span()),
                ));
            }
        }
    }
    if errors.is_empty() {
        Ok(())
    } else {
        Err(errors)
    }
}

fn assert_all_fields_removed(
    fields: HashMap<AllowedFieldName, IrField>,
    location: Location,
    resolver_type: &'static str,
) -> Result<(), Diagnostic> {
    if fields.is_empty() {
        Ok(())
    } else {
        let field_string = format_remaining_fields(&fields);
        let mut err = Diagnostic::error(
            IrParsingErrorMessages::LeftoverFields {
                resolver_type,
                field_string,
            },
            location,
        );
        let mut fields: Vec<_> = fields.into_iter().collect();
        fields.sort_by_key(|x| x.1.key_location());
        for (_name, field) in fields {
            err = err.annotate("Unexpected field", field.key_location());
        }
        Err(err)
    }
}

fn format_remaining_fields(fields: &HashMap<AllowedFieldName, IrField>) -> String {
    let mut remaining_keys: Vec<_> = fields.keys().collect();
    let len = remaining_keys.len();
    if len == 1 {
        let item = remaining_keys
            .first()
            .expect("Just checked that there was one item");
        return format!("@{item}");
    }
    remaining_keys.sort();
    remaining_keys
        .into_iter()
        .enumerate()
        .fold(String::new(), |a, (index, b)| {
            if index == len - 1 {
                a + "and @" + &b.to_string()
            } else {
                a + "@" + &b.to_string() + ", "
            }
        })
}

fn extract_identifier(relay_resolver_field: PopulatedIrField) -> DiagnosticsResult<Identifier> {
    let type_str = relay_resolver_field.value;
    parse_identifier(
        type_str.item.lookup(),
        type_str.location.source_location(),
        type_str.location.span().start,
    )
}

fn assert_only_identifier(relay_resolver_field: PopulatedIrField) -> DiagnosticsResult<Identifier> {
    // Extract the identifier, then assert that nothing unparsed remains
    let identifier = extract_identifier(relay_resolver_field)?;
    if identifier.value == relay_resolver_field.value.item {
        Ok(identifier)
    } else {
        let user_provided = relay_resolver_field.value.item;
        let parsed = identifier.value;
        Err(vec![Diagnostic::error(
            IrParsingErrorMessages::RemainingCharsWhenParsingIdentifier {
                user_provided,
                parsed,
            },
            relay_resolver_field.value.location,
        )])
    }
}