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

mod generate_typescript;

use generate_typescript::transform_fixture;
use fixture_tests::test_fixture;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#[test]
fn simple_use_import_type_syntax() {
    let input = include_str!("generate_typescript/fixtures/simple-use-import-type-syntax.graphql");
    let expected = include_str!("generate_typescript/fixtures/simple-use-import-type-syntax.expected");
    test_fixture(transform_fixture, "simple-use-import-type-syntax.graphql", "generate_typescript/fixtures/simple-use-import-type-syntax.expected", input, expected);
}

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

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

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

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

#[test]
fn unmasked_fragment_spreads() {
    let input = include_str!("generate_typescript/fixtures/unmasked-fragment-spreads.graphql");
    let expected = include_str!("generate_typescript/fixtures/unmasked-fragment-spreads.expected");
    test_fixture(transform_fixture, "unmasked-fragment-spreads.graphql", "generate_typescript/fixtures/unmasked-fragment-spreads.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::sync::Arc;

use common::ConsoleLogger;
use common::FeatureFlag;
use common::FeatureFlags;
use common::ScalarName;
use common::SourceLocationKey;
use fixture_tests::Fixture;
use fnv::FnvBuildHasher;
use fnv::FnvHashMap;
use graphql_ir::build_ir_in_relay_mode;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_syntax::parse_executable;
use graphql_test_helpers::diagnostics_to_sorted_string;
use indexmap::IndexMap;
use intern::string_key::Intern;
use relay_codegen::JsModuleFormat;
use relay_config::CustomScalarType;
use relay_config::CustomScalarTypeImport;
use relay_config::ProjectConfig;
use relay_test_schema::get_test_schema;
use relay_test_schema::get_test_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_typegen::FragmentLocations;
use relay_typegen::TypegenConfig;
use relay_typegen::TypegenLanguage;

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

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let parts = fixture.content.split("%extensions%").collect::<Vec<_>>();
    let (source, schema) = match parts.as_slice() {
        [source, extensions] => (source, get_test_schema_with_extensions(extensions)),
        [source] => (source, get_test_schema()),
        _ => panic!(),
    };

    let source_location = SourceLocationKey::standalone(fixture.file_name);

    let mut sources = FnvHashMap::default();
    sources.insert(source_location, source);
    let ast = parse_executable(source, source_location)
        .map_err(|diagnostics| diagnostics_to_sorted_string(source, &diagnostics))?;
    let feature_flags = FeatureFlags {
        no_inline: FeatureFlag::Limited {
            allowlist: [
                "noInlineFragment_address".intern(),
                "noInlineFragment_user".intern(),
                "MarkdownUserNameRenderer_name".intern(),
                "Test_userRenderer".intern(),
                "PlainUserNameRenderer_name".intern(),
            ]
            .into_iter()
            .collect(),
        },
        enable_relay_resolver_transform: true,
        actor_change_support: FeatureFlag::Enabled,
        enable_fragment_aliases: FeatureFlag::Enabled,
        ..Default::default()
    };
    let ir = build_ir_in_relay_mode(&schema, &ast.definitions)
        .map_err(|diagnostics| diagnostics_to_sorted_string(source, &diagnostics))?;
    let program = Program::from_definitions(Arc::clone(&schema), ir);

    let mut custom_scalar_types = FnvIndexMap::default();

    custom_scalar_types.insert(
        ScalarName("Boolean".intern()),
        CustomScalarType::Name("CustomBoolean".intern()),
    );
    custom_scalar_types.insert(
        ScalarName("JSON".intern()),
        CustomScalarType::Path(CustomScalarTypeImport {
            name: "JSON".intern(),
            path: "TypeDefsFile".into(),
        }),
    );
    let project_config = ProjectConfig {
        name: "test".intern(),
        js_module_format: JsModuleFormat::Haste,
        feature_flags: Arc::new(feature_flags),
        typegen_config: TypegenConfig {
            language: TypegenLanguage::Flow,
            custom_scalar_types,
            ..Default::default()
        },
        ..Default::default()
    };

    let programs = apply_transforms(
        &project_config,
        Arc::new(program),
        Default::default(),
        Arc::new(ConsoleLogger),
        None,
        None,
    )
    .map_err(|diagnostics| diagnostics_to_sorted_string(source, &diagnostics))?;

    let fragment_locations = FragmentLocations::new(programs.typegen.fragments());
    let mut operations: Vec<_> = programs.typegen.operations().collect();
    operations.sort_by_key(|op| op.name.item.0);
    let operation_strings = operations.into_iter().map(|typegen_operation| {
        // `normalization` ASTs are present unless we are processing an updatable query
        // In that case, `reader` ASTs are present.
        let op = programs
            .normalization
            .operation(OperationDefinitionName(typegen_operation.name.item.0))
            .unwrap_or_else(|| {
                programs
                    .reader
                    .operation(OperationDefinitionName(typegen_operation.name.item.0))
                    .unwrap_or_else(|| {
                        panic!(
                            "Couldn't find normalization or reader operations for {}",
                            typegen_operation.name.item
                        )
                    })
            });

        relay_typegen::generate_operation_type_exports_section(
            typegen_operation,
            op,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut fragments: Vec<_> = programs.typegen.fragments().collect();
    fragments.sort_by_key(|frag| frag.name.item);
    let fragment_strings = fragments.into_iter().map(|frag| {
        relay_typegen::generate_fragment_type_exports_section(
            frag,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut result: Vec<String> = operation_strings.collect();
    result.extend(fragment_strings);
    Ok(result
        .join("-------------------------------------------------------------------------------\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::ScalarName;
use common::SourceLocationKey;
use fixture_tests::Fixture;
use fnv::FnvBuildHasher;
use fnv::FnvHashMap;
use graphql_ir::build_ir_in_relay_mode;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_syntax::parse_executable;
use indexmap::IndexMap;
use intern::string_key::Intern;
use relay_codegen::JsModuleFormat;
use relay_config::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_typegen::FragmentLocations;
use relay_typegen::TypegenConfig;
use relay_typegen::TypegenLanguage;

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

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let parts = fixture.content.split("%extensions%").collect::<Vec<_>>();
    let (source, schema) = match parts.as_slice() {
        [source, extensions] => (
            source,
            get_test_schema_with_custom_id_with_extensions(extensions),
        ),
        [source] => (source, get_test_schema_with_custom_id()),
        _ => panic!(),
    };

    let source_location = SourceLocationKey::standalone(fixture.file_name);

    let mut sources = FnvHashMap::default();
    sources.insert(source_location, source);
    let ast = parse_executable(source, source_location).unwrap_or_else(|e| {
        panic!("Encountered error building AST: {:?}", e);
    });
    let feature_flags = FeatureFlags {
        no_inline: FeatureFlag::Enabled,
        enable_relay_resolver_transform: true,
        actor_change_support: FeatureFlag::Enabled,
        ..Default::default()
    };
    let ir = build_ir_in_relay_mode(&schema, &ast.definitions).unwrap_or_else(|e| {
        panic!("Encountered error building IR {:?}", e);
    });
    let program = Program::from_definitions(Arc::clone(&schema), ir);

    let mut custom_scalar_types = FnvIndexMap::default();
    custom_scalar_types.insert(
        ScalarName("Boolean".intern()),
        relay_config::CustomScalarType::Name("CustomBoolean".intern()),
    );
    let project_config = ProjectConfig {
        name: "test".intern(),
        js_module_format: JsModuleFormat::Haste,
        feature_flags: Arc::new(feature_flags),
        schema_config: SchemaConfig {
            node_interface_id_field: "global_id".intern(),
            ..Default::default()
        },
        typegen_config: TypegenConfig {
            language: TypegenLanguage::Flow,
            custom_scalar_types,
            flow_typegen: Default::default(),
            ..Default::default()
        },
        ..Default::default()
    };

    let programs = apply_transforms(
        &project_config,
        Arc::new(program),
        Default::default(),
        Arc::new(ConsoleLogger),
        None,
        None,
    )
    .unwrap();

    let fragment_locations = FragmentLocations::new(programs.typegen.fragments());
    let mut operations: Vec<_> = programs.typegen.operations().collect();
    operations.sort_by_key(|op| op.name.item.0);
    let operation_strings = operations.into_iter().map(|typegen_operation| {
        let normalization_operation = programs
            .normalization
            .operation(OperationDefinitionName(typegen_operation.name.item.0))
            .unwrap();
        relay_typegen::generate_operation_type_exports_section(
            typegen_operation,
            normalization_operation,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut fragments: Vec<_> = programs.typegen.fragments().collect();
    fragments.sort_by_key(|frag| frag.name.item);
    let fragment_strings = fragments.into_iter().map(|frag| {
        relay_typegen::generate_fragment_type_exports_section(
            frag,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut result: Vec<String> = operation_strings.collect();
    result.extend(fragment_strings);
    Ok(result
        .join("-------------------------------------------------------------------------------\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::ScalarName;
use common::SourceLocationKey;
use fixture_tests::Fixture;
use fnv::FnvBuildHasher;
use fnv::FnvHashMap;
use graphql_ir::build;
use graphql_ir::Program;
use graphql_syntax::parse_executable;
use indexmap::IndexMap;
use intern::string_key::Intern;
use relay_codegen::JsModuleFormat;
use relay_config::CustomScalarType;
use relay_config::CustomScalarTypeImport;
use relay_config::ProjectConfig;
use relay_test_schema::get_test_schema;
use relay_test_schema::get_test_schema_with_extensions;
use relay_transforms::apply_transforms;
use relay_typegen::FragmentLocations;
use relay_typegen::TypegenConfig;
use relay_typegen::TypegenLanguage;

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

pub fn transform_fixture(fixture: &Fixture<'_>) -> Result<String, String> {
    let parts = fixture.content.split("%extensions%").collect::<Vec<_>>();
    let (source, schema) = match parts.as_slice() {
        [source, extensions] => (source, get_test_schema_with_extensions(extensions)),
        [source] => (source, get_test_schema()),
        _ => panic!(),
    };

    let source_location = SourceLocationKey::standalone(fixture.file_name);

    let mut sources = FnvHashMap::default();
    sources.insert(source_location, source);
    let ast = parse_executable(source, source_location).unwrap_or_else(|e| {
        panic!("Encountered error building AST: {:?}", e);
    });
    let ir = build(&schema, &ast.definitions).unwrap_or_else(|e| {
        panic!("Encountered error building IR {:?}", e);
    });
    let program = Program::from_definitions(Arc::clone(&schema), ir);
    let mut custom_scalar_types = FnvIndexMap::default();
    custom_scalar_types.insert(
        ScalarName("JSON".intern()),
        CustomScalarType::Path(CustomScalarTypeImport {
            name: "JSON".intern(),
            path: "TypeDefsFile".into(),
        }),
    );
    let project_config = ProjectConfig {
        name: "test".intern(),
        js_module_format: JsModuleFormat::Haste,
        typegen_config: TypegenConfig {
            language: TypegenLanguage::TypeScript,
            custom_scalar_types,
            use_import_type_syntax: fixture
                .content
                .contains("# typegen_config.use_import_type_syntax = true"),
            ..Default::default()
        },
        feature_flags: Arc::new(FeatureFlags {
            enable_fragment_aliases: FeatureFlag::Enabled,
            enable_relay_resolver_transform: true,
            ..Default::default()
        }),
        ..Default::default()
    };
    let programs = apply_transforms(
        &project_config,
        Arc::new(program),
        Default::default(),
        Arc::new(ConsoleLogger),
        None,
        None,
    )
    .unwrap();

    let fragment_locations = FragmentLocations::new(programs.typegen.fragments());
    let mut operations: Vec<_> = programs.typegen.operations().collect();
    operations.sort_by_key(|op| op.name.item.0);
    let operation_strings = operations.into_iter().map(|typegen_operation| {
        let normalization_operation = programs
            .normalization
            .operation(typegen_operation.name.item)
            .unwrap();
        relay_typegen::generate_operation_type_exports_section(
            typegen_operation,
            normalization_operation,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut fragments: Vec<_> = programs.typegen.fragments().collect();
    fragments.sort_by_key(|frag| frag.name.item);
    let fragment_strings = fragments.into_iter().map(|frag| {
        relay_typegen::generate_fragment_type_exports_section(
            frag,
            &schema,
            &project_config,
            &fragment_locations,
        )
    });

    let mut result: Vec<String> = operation_strings.collect();
    result.extend(fragment_strings);
    Ok(result
        .join("-------------------------------------------------------------------------------\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 super::*;

impl<'a> ArgumentParent<'a> {
    pub fn find_argument_root(&'a self) -> ArgumentRoot<'a> {
        match self {
            Self::Directive(directive_path) => ArgumentRoot::Directive(directive_path),
            Self::ScalarField(scalar_field_path) => ArgumentRoot::ScalarField(scalar_field_path),
            Self::LinkedField(linked_field_path) => ArgumentRoot::LinkedField(linked_field_path),
            Self::ConstantObject(ConstantObjectPath { inner: _, parent }) => parent
                .parent
                .find_enclosing_argument_path()
                .parent
                .find_argument_root(),
        }
    }
}

/// An Argument contains a Value. One variant of Value is a vector of Arguments.
/// This makes it difficult to match on arguments to find their "logical parent"
/// (i.e. the first parent that isn't another argument.)
///
/// The ArgumentParentRoot enum represents the possible logical parents for these
/// arguments. An argument can ultimately be passed to a field or a directive.
#[derive(Debug)]
pub enum ArgumentRoot<'a> {
    LinkedField(&'a LinkedFieldPath<'a>),
    ScalarField(&'a ScalarFieldPath<'a>),
    Directive(&'a DirectivePath<'a>),
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::test::test_resolution;

    #[test]
    fn argument_root_scalar_field_constant_object_path() {
        let source = r#"
            query Foo {
                me(foo: {bar: {baz: $qux}})
            }
        "#;
        test_resolution(source, ": $qux", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::ConstantObject(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::ScalarField(_))
            } else {
                panic!(
                    "Should resolve to Argument with ConstantObject parent, instead got {:?}",
                    resolved
                );
            }
        });
    }

    #[test]
    fn argument_root_scalar_field_scalar_field_path() {
        let source = r#"
            query Foo {
                me(foo: {bar: {baz: $qux}})
            }
        "#;
        test_resolution(source, ": {bar", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::ScalarField(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::ScalarField(_))
            } else {
                panic!(
                    "Should resolve to Argument with ScalarField parent, instead got {:?}",
                    resolved
                );
            }
        });
    }

    #[test]
    fn argument_root_linked_field_constant_object_path() {
        let source = r#"
            query Foo {
                me(foo: {bar: {baz: $qux}}) {
                    name
                }
            }
        "#;
        test_resolution(source, ": $qux", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::ConstantObject(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::LinkedField(_))
            } else {
                panic!(
                    "Should resolve to Argument with ConstantObject parent, instead got {:?}",
                    resolved
                );
            }
        });
    }

    #[test]
    fn argument_root_linked_field_linked_field_path() {
        let source = r#"
            query Foo {
                me(foo: {bar: {baz: $qux}}) {
                    name
                }
            }
        "#;
        test_resolution(source, ": {bar", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::LinkedField(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::LinkedField(_))
            } else {
                panic!(
                    "Should resolve to Argument with LinkedField parent, instead got {:?}",
                    resolved
                );
            }
        });
    }

    #[test]
    fn argument_root_directive_constant_object_path() {
        let source = r#"
            query Foo {
                me @foo(param: {bar: {baz: $qux}})
            }
        "#;
        test_resolution(source, ": $qux", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::ConstantObject(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::Directive(_))
            } else {
                panic!(
                    "Should resolve to Argument with ConstantObject parent, instead got {:?}",
                    resolved
                );
            }
        });
    }

    #[test]
    fn argument_root_directive_directive_path() {
        let source = r#"
            query Foo {
                me @foo(param: {bar: {baz: $qux}})
            }
        "#;
        test_resolution(source, ": {bar", |resolved| {
            if let ResolutionPath::Argument(ArgumentPath {
                inner: _,
                parent: parent @ ArgumentParent::Directive(_),
            }) = resolved
            {
                assert_matches!(parent.find_argument_root(), ArgumentRoot::Directive(_))
            } else {
                panic!(
                    "Should resolve to Argument with Directive parent, instead got {:?}",
                    resolved
                );
            }
        });
    }
}


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

use super::*;

impl<'a> ConstantValueParent<'a> {
    pub fn find_constant_value_root(&'a self) -> ConstantValueRoot<'a> {
        match self {
            ConstantValueParent::DefaultValue(DefaultValuePath {
                inner: _,
                parent: variable_definition_path,
            }) => ConstantValueRoot::VariableDefinition(variable_definition_path),
            ConstantValueParent::Value(ValuePath {
                inner: _,
                parent: value_parent,
            }) => ConstantValueRoot::Argument(value_parent.find_enclosing_argument_path()),
            ConstantValueParent::ConstantList(ConstantListPath {
                inner: _,
                parent: constant_list_path_parent,
            }) => constant_list_path_parent.parent.find_constant_value_root(),
            ConstantValueParent::ConstantObj(constant_obj) => {
                constant_obj.parent.parent.find_constant_value_root()
            }
            ConstantValueParent::ConstantArgValue(ConstantArgPath {
                inner: _,
                parent:
                    ConstantObjPath {
                        inner: _,
                        parent: constant_value_path,
                    },
            }) => constant_value_path.parent.find_constant_value_root(),
        }
    }
}

impl<'a> ValueParent<'a> {
    /// Values can be contained in other Values or in an Argument, meaning
    /// that all Values ultimately are contained in an Argument.
    /// Recursively traverse the ValueParent until we find the enclosing
    /// Argument, and return the wrapping ArgumentPath.
    pub fn find_enclosing_argument_path(&'a self) -> &'a ArgumentPath<'a> {
        match self {
            ValueParent::ArgumentValue(argument_path) => argument_path,
            ValueParent::ValueList(ValueListPath {
                inner: _,
                parent: value_path,
            }) => value_path.parent.find_enclosing_argument_path(),
        }
    }
}

/// Constant objects and lists can contain each other, as well as constant
/// booleans, floats, ints, strings, nulls and enums. This makes it difficult to match
/// on these items to find their "logical parent" (i.e. the first parent that isn't
/// related to the literal).
///
/// The ConstantValueRoot enum represents the possible logical parents for these items.
/// This can be either a VariableDefinitionPath (meaning the value was part of a default
/// value for a variable), or an ArgumentPath (meaning the value was an argument to a
/// field or directive.)
#[derive(Debug)]
pub enum ConstantValueRoot<'a> {
    VariableDefinition(&'a VariableDefinitionPath<'a>),
    Argument(&'a ArgumentPath<'a>),
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::test::test_resolution;

    #[test]
    fn constant_value_root_variable_definition_simple_boolean() {
        let source = r#"
            query Foo($isCool: Boolean = true) {
                me
            }
        "#;
        test_resolution(source, "true", |resolved| {
            if let ResolutionPath::ConstantBoolean(ConstantBooleanPath {
                inner: _,
                parent: ConstantValuePath { inner: _, parent },
            }) = resolved
            {
                assert_matches!(
                    parent.find_constant_value_root(),
                    ConstantValueRoot::VariableDefinition(_)
                )
            } else {
                panic!(
                    "Should resolve to ConstantBoolean, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn constant_value_root_variable_definition_nested_boolean() {
        let source = r#"
            query Foo($isCool: AComplexType = {query: "Hello", inputs: [true]}) {
                me
            }
        "#;
        test_resolution(source, "true", |resolved| {
            if let ResolutionPath::ConstantBoolean(ConstantBooleanPath {
                inner: _,
                parent: ConstantValuePath { inner: _, parent },
            }) = resolved
            {
                assert_matches!(
                    parent.find_constant_value_root(),
                    ConstantValueRoot::VariableDefinition(_)
                )
            } else {
                panic!(
                    "Should resolve to ConstantBoolean, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn constant_value_root_argument_simple_boolean() {
        let source = r#"
            query Foo {
                me(is_cool: true)
            }
        "#;
        test_resolution(source, "true", |resolved| {
            if let ResolutionPath::ConstantBoolean(ConstantBooleanPath {
                inner: _,
                parent: ConstantValuePath { inner: _, parent },
            }) = resolved
            {
                assert_matches!(
                    parent.find_constant_value_root(),
                    ConstantValueRoot::Argument(_)
                )
            } else {
                panic!(
                    "Should resolve to ConstantBoolean, instead got {:?}",
                    resolved
                );
            }
        })
    }

    #[test]
    fn constant_value_root_argument_nested_boolean() {
        let source = r#"
            query Foo {
                me(is_cool: {query: "Hello", inputs:[true]})
            }
        "#;
        test_resolution(source, "true", |resolved| {
            if let ResolutionPath::ConstantBoolean(ConstantBooleanPath {
                inner: _,
                parent: ConstantValuePath { inner: _, parent },
            }) = resolved
            {
                assert_matches!(
                    parent.find_constant_value_root(),
                    ConstantValueRoot::Argument(_)
                )
            } else {
                panic!(
                    "Should resolve to ConstantBoolean, instead got {:?}",
                    resolved
                );
            }
        })
    }
}


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

#![allow(dead_code)]
use common::Span;
use graphql_syntax::Argument;
use graphql_syntax::BooleanNode;
use graphql_syntax::ConstantArgument;
use graphql_syntax::ConstantValue;
use graphql_syntax::DefaultValue;
use graphql_syntax::Directive;
use graphql_syntax::EnumNode;
use graphql_syntax::ExecutableDefinition;
use graphql_syntax::ExecutableDocument;
use graphql_syntax::FloatNode;
use graphql_syntax::FragmentDefinition;
use graphql_syntax::FragmentSpread;
use graphql_syntax::Identifier;
use graphql_syntax::InlineFragment;
use graphql_syntax::IntNode;
use graphql_syntax::LinkedField;
use graphql_syntax::List;
use graphql_syntax::ListTypeAnnotation;
use graphql_syntax::NamedTypeAnnotation;
use graphql_syntax::NonNullTypeAnnotation;
use graphql_syntax::OperationDefinition;
use graphql_syntax::OperationKind;
use graphql_syntax::ScalarField;
use graphql_syntax::Selection;
use graphql_syntax::StringNode;
use graphql_syntax::Token;
use graphql_syntax::TypeAnnotation;
use graphql_syntax::TypeCondition;
use graphql_syntax::Value;
use graphql_syntax::VariableDefinition;
use graphql_syntax::VariableIdentifier;

mod constant_value_root;
pub use constant_value_root::ConstantValueRoot;
mod argument_root;
pub use argument_root::*;
mod selection_parent_type;
mod variable_definition_path;

#[cfg(test)]
#[macro_use]
extern crate assert_matches;

/// This module resolves a position (`Span`) to a `ResolvePosition` which
/// enumerates the types of AST nodes which have a "surface area", meaning a
/// character in the code which belongs to the AST node but none of its
/// children.
///
/// By convention each `ResolvePosition` variant wraps a `[NODE_TYPE]Path`
/// struct. The `[NODE_TYPE]Path` structs can be thought of an edge between an
/// child node (`inner`) and its parent (`parent`). By chaining these edges we can
/// construct a type safe path from the leaf node up to the AST root.
///
/// The `parent` field of each `[NODE_TYPE]Path` struct is -- again, by
/// convention -- either an enum or a type alias called `[NODE_TYPE]Parent`. If
/// a node type has only one valid parent AST node type, then a type alias to
/// that parent's `[NODE_TYPE]Path` is used. If a node type has multiple valid
/// parents, and enum wrapping all the valid `[NODE_TYPE]Path]`s is used.
///
/// **Note**: In some cases, a node type may appear as the child of a given
/// parent in multiple different fields. For example, an `Identifier` might
/// appear as either the name _or_ the alias of a `ScalarField`. In this case
/// the `IdentParent` enum should include two variants for `ScalarField`, one
/// for name and one for alias. This allows consumers to understand exactly where
/// the AST this `Identifier` appears.
///
/// ## Implementation Details
///
/// Each AST node type must implement three things:
///
/// - A `[NODE_TYPE]Parent` enum or type alias defining the valid parent///Path
///     type(s) that this node can have.
/// - A `[NODE_TYPE]Path` struct which is a `Path` with the type arguments
///     `Inner` defined as the AST node itself and `Parent` defined as the above
///     `[NODE_TYPE]Parent`.
/// - Implement the `ResolvePosition` trait for the AST node itself.
///
/// The actual logic of the module lives in the implementation of
/// `ResolvePosition::resolve` for each AST node. Each implementation must call
/// `.contains()` on each of its child nodes. When the child node containing the
/// `position` is discovered, it must return the result of calling `.resolve()`
/// on that child. If no child contains the `position`, then the node must
/// assume that _it itself_ is the leaf node and return its own variant of
/// `ResolutionPath`.
///
/// This approach is not ideal because there is an implicit (not enforced by the
/// compiler) contract that each parent node must check the result of
/// `.contains()` on a child before calling that child's `.resolve()` method.
/// Ideally `.resolve()` could return an `Option`, however calling `.resolve()`
/// moves the parent into child's method which means we need to convince the
/// compiler that we are only ever going to return the result of one child's
/// `.resolve()`. This pattern of `.contains()` followed by unconditionally
/// returning the result of the child's `.resolve()` allows us to prove this to
/// the compiler.

#[derive(Debug)]
pub enum ResolutionPath<'a> {
    Ident(IdentPath<'a>),
    OperationDefinition(OperationDefinitionPath<'a>),
    Operation(OperationPath<'a>),
    ExecutableDocument(&'a ExecutableDocument),
    ScalarField(ScalarFieldPath<'a>),
    LinkedField(LinkedFieldPath<'a>),
    InlineFragment(InlineFragmentPath<'a>),
    TypeCondition(TypeConditionPath<'a>),
    FragmentDefinition(FragmentDefinitionPath<'a>),
    FragmentSpread(FragmentSpreadPath<'a>),
    Directive(DirectivePath<'a>),
    Argument(ArgumentPath<'a>),
    ConstantInt(ConstantIntPath<'a>),
    ConstantFloat(ConstantFloatPath<'a>),
    ConstantString(ConstantStringPath<'a>),
    ConstantBoolean(ConstantBooleanPath<'a>),
    ConstantNull(ConstantNullPath<'a>),
    ConstantEnum(ConstantEnumPath<'a>),
    ConstantList(ConstantListPath<'a>),
    ConstantObj(ConstantObjPath<'a>),
    ConstantArg(ConstantArgPath<'a>),
    ValueList(ValueListPath<'a>),
    VariableIdentifier(VariableIdentifierPath<'a>),
    ConstantObject(ConstantObjectPath<'a>),
    VariableDefinitionList(VariableDefinitionListPath<'a>),
    VariableDefinition(VariableDefinitionPath<'a>),
    ListTypeAnnotation(ListTypeAnnotationPath<'a>),
    NonNullTypeAnnotation(NonNullTypeAnnotationPath<'a>),
    DefaultValue(DefaultValuePath<'a>),
}
#[derive(Debug)]
pub struct Path<Inner, Parent> {
    pub inner: Inner,
    pub parent: Parent,
}

pub trait ResolvePosition<'a>: Sized {
    type Parent;
    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a>;
    fn contains(&'a self, position: Span) -> bool;
    fn path(&'a self, parent: Self::Parent) -> Path<&Self, Self::Parent> {
        Path {
            inner: self,
            parent,
        }
    }
}

pub type ExecutableDocumentPath<'a> = Path<&'a ExecutableDocument, ()>;

// Clippy gets grumpy about us passing around `()`, but we need it for consistency with other implementations of `ResolvePosition`.
#[allow(clippy::unit_arg)]
impl<'a> ResolvePosition<'a> for ExecutableDocument {
    type Parent = ();

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for definition in &self.definitions {
            if definition.contains(position) {
                return definition.resolve(self.path(parent), position);
            }
        }
        // We didn't find the position in the definitions
        ResolutionPath::ExecutableDocument(self)
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type OperationKindPath<'a> = Path<&'a (Token, OperationKind), OperationKindParent<'a>>;

pub type OperationKindParent<'a> = OperationDefinitionPath<'a>;

pub type FragmentDefinitionPath<'a> = Path<&'a FragmentDefinition, FragmentDefinitionParent<'a>>;

pub type FragmentDefinitionParent<'a> = ExecutableDefinitionPath<'a>;

impl<'a> ResolvePosition<'a> for FragmentDefinition {
    type Parent = FragmentDefinitionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't currently implement a node for the keyword `fragment`.
        if self.name.contains(position) {
            return self.name.resolve(
                IdentParent::FragmentDefinitionName(self.path(parent)),
                position,
            );
        }

        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive.resolve(
                    DirectiveParent::FragmentDefinition(self.path(parent)),
                    position,
                );
            }
        }

        if let Some(variable_definitions) = &self.variable_definitions {
            if variable_definitions.contains(position) {
                return variable_definitions.resolve(
                    VariableDefinitionListParent::FragmentDefinition(self.path(parent)),
                    position,
                );
            }
        }

        if self.type_condition.contains(position) {
            return self.type_condition.resolve(
                TypeConditionParent::FragmentDefinition(self.path(parent)),
                position,
            );
        }
        for selection in &self.selections.items {
            if selection.span().contains(position) {
                return selection.resolve(
                    SelectionParent::FragmentDefinitionSelection(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::FragmentDefinition(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.location.contains(position)
    }
}

pub type VariableDefinitionListPath<'a> =
    Path<&'a List<VariableDefinition>, VariableDefinitionListParent<'a>>;

#[derive(Debug)]
pub enum VariableDefinitionListParent<'a> {
    FragmentDefinition(FragmentDefinitionPath<'a>),
    OperationDefinition(OperationDefinitionPath<'a>),
}

impl<'a> ResolvePosition<'a> for List<VariableDefinition> {
    type Parent = VariableDefinitionListParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for value in &self.items {
            if value.contains(position) {
                return value.resolve(self.path(parent), position);
            }
        }
        ResolutionPath::VariableDefinitionList(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type VariableDefinitionPath<'a> = Path<&'a VariableDefinition, VariableDefinitionParent<'a>>;
pub type VariableDefinitionParent<'a> = VariableDefinitionListPath<'a>;

impl<'a> ResolvePosition<'a> for VariableDefinition {
    type Parent = VariableDefinitionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if self.name.contains(position) {
            return self.name.resolve(
                VariableIdentifierParent::VariableDefinition(self.path(parent)),
                position,
            );
        }
        // Note: We skip the `:` for now
        if self.type_.contains(position) {
            return self.type_.resolve(
                TypeAnnotationParent::VariableDefinition(self.path(parent)),
                position,
            );
        }

        if let Some(default) = &self.default_value {
            if default.contains(position) {
                return default.resolve(self.path(parent), position);
            }
        }

        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive.resolve(
                    DirectiveParent::VariableDefinition(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::VariableDefinition(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type DefaultValuePath<'a> = Path<&'a DefaultValue, DefaultValueParent<'a>>;
pub type DefaultValueParent<'a> = VariableDefinitionPath<'a>;

impl<'a> ResolvePosition<'a> for DefaultValue {
    type Parent = DefaultValueParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if self.value.contains(position) {
            return self.value.resolve(
                ConstantValueParent::DefaultValue(self.path(parent)),
                position,
            );
        }

        // Note: We skip the `=` for now

        ResolutionPath::DefaultValue(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type TypeAnnotationPath<'a> = Path<&'a TypeAnnotation, TypeAnnotationParent<'a>>;
#[derive(Debug)]
pub enum TypeAnnotationParent<'a> {
    VariableDefinition(VariableDefinitionPath<'a>),
    ListTypeAnnotation(ListTypeAnnotationPath<'a>),
    NonNullTypeAnnotation(NonNullTypeAnnotationPath<'a>),
}
impl<'a> ResolvePosition<'a> for TypeAnnotation {
    type Parent = TypeAnnotationParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        match self {
            TypeAnnotation::Named(named_type) => named_type.resolve(self.path(parent), position),
            TypeAnnotation::List(list) => list.resolve(Box::new(self.path(parent)), position),
            TypeAnnotation::NonNull(non_null) => {
                non_null.resolve(Box::new(self.path(parent)), position)
            }
        }
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span().contains(position)
    }
}

// Note: since a NamedTypeAnnotation contains only a single Ident, it is impossible
// to resolve a span to a NamedTypeAnnotationPath. Instead, such a span would resolve to
// an IdentPath whose parent is a NamedTypeAnnotationPath.
//
// As a consequence, the ResolutionPath enum does not contain a NamedTypeAnnotation
// variant, and we return self.name.resolve(...) directly instead.
impl<'a> ResolvePosition<'a> for NamedTypeAnnotation {
    type Parent = TypeAnnotationPath<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        self.name.resolve(
            IdentParent::NamedTypeAnnotation(self.path(parent)),
            position,
        )
    }

    fn contains(&'a self, position: Span) -> bool {
        self.name.contains(position)
    }
}

pub type NamedTypeAnnotationPath<'a> = Path<&'a NamedTypeAnnotation, TypeAnnotationPath<'a>>;

pub type ListTypeAnnotationPath<'a> =
    Path<&'a ListTypeAnnotation, Box<ListTypeAnnotationParent<'a>>>;
pub type ListTypeAnnotationParent<'a> = TypeAnnotationPath<'a>;

impl<'a> ResolvePosition<'a> for ListTypeAnnotation {
    type Parent = Box<ListTypeAnnotationParent<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't currently return the `[]` as separate tokens.
        if self.type_.contains(position) {
            return self.type_.resolve(
                TypeAnnotationParent::ListTypeAnnotation(self.path(parent)),
                position,
            );
        }
        ResolutionPath::ListTypeAnnotation(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type NonNullTypeAnnotationPath<'a> =
    Path<&'a NonNullTypeAnnotation, Box<NonNullTypeAnnotationParent<'a>>>;
pub type NonNullTypeAnnotationParent<'a> = TypeAnnotationPath<'a>;

impl<'a> ResolvePosition<'a> for NonNullTypeAnnotation {
    type Parent = Box<NonNullTypeAnnotationParent<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't currently return the `[]` as separate tokens.
        if self.type_.contains(position) {
            return self.type_.resolve(
                TypeAnnotationParent::NonNullTypeAnnotation(self.path(parent)),
                position,
            );
        }
        ResolutionPath::NonNullTypeAnnotation(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type OperationDefinitionPath<'a> = Path<&'a OperationDefinition, OperationDefinitionParent<'a>>;
pub type OperationDefinitionParent<'a> = ExecutableDefinitionPath<'a>;

impl<'a> ResolvePosition<'a> for OperationDefinition {
    type Parent = OperationDefinitionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if let Some(name) = &self.name {
            if name.contains(position) {
                return name.resolve(
                    IdentParent::OperationDefinitionName(self.path(parent)),
                    position,
                );
            }
        }
        if let Some(operation) = &self.operation {
            if operation.contains(position) {
                return operation.resolve(self.path(parent), position);
            }
        }
        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive.resolve(
                    DirectiveParent::OperationDefinition(self.path(parent)),
                    position,
                );
            }
        }

        if let Some(variable_definitions) = &self.variable_definitions {
            if variable_definitions.contains(position) {
                return variable_definitions.resolve(
                    VariableDefinitionListParent::OperationDefinition(self.path(parent)),
                    position,
                );
            }
        }

        for selection in &self.selections.items {
            if selection.span().contains(position) {
                return selection.resolve(
                    SelectionParent::OperationDefinitionSelection(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::OperationDefinition(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.location.contains(position)
    }
}

pub type OperationPath<'a> = Path<&'a (Token, OperationKind), OperationParent<'a>>;
pub type OperationParent<'a> = OperationDefinitionPath<'a>;

impl<'a> ResolvePosition<'a> for (Token, OperationKind) {
    type Parent = OperationParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, _position: Span) -> ResolutionPath<'a> {
        ResolutionPath::Operation(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.0.span.contains(position)
    }
}

pub type ExecutableDefinitionPath<'a> =
    Path<&'a ExecutableDefinition, ExecutableDefinitionParent<'a>>;
pub type ExecutableDefinitionParent<'a> = ExecutableDocumentPath<'a>;

impl<'a> ResolvePosition<'a> for ExecutableDefinition {
    type Parent = ExecutableDefinitionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        match self {
            ExecutableDefinition::Operation(operation) => {
                operation.resolve(self.path(parent), position)
            }
            ExecutableDefinition::Fragment(fragment) => {
                fragment.resolve(self.path(parent), position)
            }
        }
    }

    fn contains(&'a self, position: Span) -> bool {
        match self {
            ExecutableDefinition::Operation(def) => def.contains(position),
            ExecutableDefinition::Fragment(def) => def.contains(position),
        }
    }
}

pub type IdentPath<'a> = Path<&'a Identifier, IdentParent<'a>>;

#[derive(Debug)]
pub enum IdentParent<'a> {
    OperationDefinitionName(OperationDefinitionPath<'a>),
    FragmentDefinitionName(FragmentDefinitionPath<'a>),
    ScalarFieldName(ScalarFieldPath<'a>),
    ScalarFieldAlias(ScalarFieldPath<'a>),
    LinkedFieldName(LinkedFieldPath<'a>),
    LinkedFieldAlias(LinkedFieldPath<'a>),
    TypeConditionType(TypeConditionPath<'a>),
    FragmentSpreadName(FragmentSpreadPath<'a>),
    DirectiveName(DirectivePath<'a>),
    ArgumentName(ArgumentPath<'a>),
    ArgumentValue(ArgumentPath<'a>),
    NamedTypeAnnotation(NamedTypeAnnotationPath<'a>),
    ConstantArgKey(ConstantArgPath<'a>),
}

impl<'a> ResolvePosition<'a> for Identifier {
    type Parent = IdentParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, _position: Span) -> ResolutionPath<'a> {
        ResolutionPath::Ident(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type SelectionPath<'a> = Path<&'a Selection, SelectionParent<'a>>;
#[derive(Debug)]
pub enum SelectionParent<'a> {
    OperationDefinitionSelection(OperationDefinitionPath<'a>),
    LinkedFieldSelection(LinkedFieldPath<'a>),
    FragmentDefinitionSelection(FragmentDefinitionPath<'a>),
    InlineFragmentSelection(InlineFragmentPath<'a>),
}

impl<'a> ResolvePosition<'a> for Selection {
    type Parent = SelectionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        match self {
            Selection::ScalarField(field) => field.resolve(self.path(parent), position),
            Selection::LinkedField(field) => field.resolve(Box::new(self.path(parent)), position),
            Selection::InlineFragment(fragment) => {
                fragment.resolve(Box::new(self.path(parent)), position)
            }
            Selection::FragmentSpread(fragment_spread) => {
                fragment_spread.resolve(self.path(parent), position)
            }
        }
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span().contains(position)
    }
}

pub type FragmentSpreadPath<'a> = Path<&'a FragmentSpread, FragmentSpreadParent<'a>>;
pub type FragmentSpreadParent<'a> = SelectionPath<'a>;

impl<'a> ResolvePosition<'a> for FragmentSpread {
    type Parent = FragmentSpreadParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't currently handle `...` explicitly
        if self.name.contains(position) {
            return self
                .name
                .resolve(IdentParent::FragmentSpreadName(self.path(parent)), position);
        }
        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive
                    .resolve(DirectiveParent::FragmentSpread(self.path(parent)), position);
            }
        }
        ResolutionPath::FragmentSpread(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type DirectivePath<'a> = Path<&'a Directive, DirectiveParent<'a>>;
#[derive(Debug)]
pub enum DirectiveParent<'a> {
    ScalarField(ScalarFieldPath<'a>),
    LinkedField(LinkedFieldPath<'a>),
    FragmentDefinition(FragmentDefinitionPath<'a>),
    FragmentSpread(FragmentSpreadPath<'a>),
    VariableDefinition(VariableDefinitionPath<'a>),
    OperationDefinition(OperationDefinitionPath<'a>),
    InlineFragment(InlineFragmentPath<'a>),
}
impl<'a> ResolvePosition<'a> for Directive {
    type Parent = DirectiveParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't currently handle the `@` explicitly.
        if self.name.contains(position) {
            return self
                .name
                .resolve(IdentParent::DirectiveName(self.path(parent)), position);
        }
        if let Some(arguments) = &self.arguments {
            for argument in &arguments.items {
                if argument.contains(position) {
                    return argument
                        .resolve(ArgumentParent::Directive(self.path(parent)), position);
                }
            }
        }
        ResolutionPath::Directive(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ScalarFieldPath<'a> = Path<&'a ScalarField, SelectionPath<'a>>;

impl<'a> ResolvePosition<'a> for ScalarField {
    type Parent = SelectionPath<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if self.name.contains(position) {
            return self
                .name
                .resolve(IdentParent::ScalarFieldName(self.path(parent)), position);
        }

        if let Some(alias) = &self.alias {
            if alias.span.contains(position) {
                return alias
                    .alias
                    .resolve(IdentParent::ScalarFieldAlias(self.path(parent)), position);
            }
        }

        if let Some(arguments) = &self.arguments {
            for argument in &arguments.items {
                if argument.contains(position) {
                    return argument
                        .resolve(ArgumentParent::ScalarField(self.path(parent)), position);
                }
            }
        }

        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive
                    .resolve(DirectiveParent::ScalarField(self.path(parent)), position);
            }
        }
        ResolutionPath::ScalarField(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type LinkedFieldPath<'a> = Path<&'a LinkedField, Box<SelectionPath<'a>>>;

impl<'a> ResolvePosition<'a> for LinkedField {
    type Parent = Box<SelectionPath<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if self.name.contains(position) {
            return self
                .name
                .resolve(IdentParent::LinkedFieldName(self.path(parent)), position);
        }

        if let Some(alias) = &self.alias {
            if alias.span.contains(position) {
                return alias
                    .alias
                    .resolve(IdentParent::LinkedFieldAlias(self.path(parent)), position);
            }
        }

        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive
                    .resolve(DirectiveParent::LinkedField(self.path(parent)), position);
            }
        }

        if let Some(arguments) = &self.arguments {
            for argument in &arguments.items {
                if argument.contains(position) {
                    return argument
                        .resolve(ArgumentParent::LinkedField(self.path(parent)), position);
                }
            }
        }
        for selection in &self.selections.items {
            if selection.contains(position) {
                return selection.resolve(
                    SelectionParent::LinkedFieldSelection(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::LinkedField(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type InlineFragmentPath<'a> = Path<&'a InlineFragment, Box<SelectionPath<'a>>>;

impl<'a> ResolvePosition<'a> for InlineFragment {
    type Parent = Box<SelectionPath<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't explicitly handle `...`
        if let Some(type_condition) = &self.type_condition {
            if type_condition.contains(position) {
                return type_condition.resolve(
                    TypeConditionParent::InlineFragment(self.path(parent)),
                    position,
                );
            }
        }
        for directive in self.directives.iter() {
            if directive.contains(position) {
                return directive
                    .resolve(DirectiveParent::InlineFragment(self.path(parent)), position);
            }
        }
        for selection in &self.selections.items {
            if selection.contains(position) {
                return selection.resolve(
                    SelectionParent::InlineFragmentSelection(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::InlineFragment(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type TypeConditionPath<'a> = Path<&'a TypeCondition, TypeConditionParent<'a>>;
#[derive(Debug)]
pub enum TypeConditionParent<'a> {
    InlineFragment(InlineFragmentPath<'a>),
    FragmentDefinition(FragmentDefinitionPath<'a>),
}
impl<'a> ResolvePosition<'a> for TypeCondition {
    type Parent = TypeConditionParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't explicitly handle `on`
        if self.type_.contains(position) {
            return self
                .type_
                .resolve(IdentParent::TypeConditionType(self.path(parent)), position);
        }

        ResolutionPath::TypeCondition(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ArgumentPath<'a> = Path<&'a Argument, ArgumentParent<'a>>;
#[derive(Debug)]
pub enum ArgumentParent<'a> {
    LinkedField(LinkedFieldPath<'a>),
    ScalarField(ScalarFieldPath<'a>),
    ConstantObject(ConstantObjectPath<'a>),
    Directive(DirectivePath<'a>),
}

impl<'a> ResolvePosition<'a> for Argument {
    type Parent = ArgumentParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        // Note: We don't explicitly handle `:`
        if self.name.span.contains(position) {
            return self
                .name
                .resolve(IdentParent::ArgumentName(self.path(parent)), position);
        }
        if self.value.contains(position) {
            return self
                .value
                .resolve(ValueParent::ArgumentValue(self.path(parent)), position);
        }
        ResolutionPath::Argument(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ValuePath<'a> = Path<&'a Value, ValueParent<'a>>;
#[derive(Debug)]
pub enum ValueParent<'a> {
    ArgumentValue(ArgumentPath<'a>),
    ValueList(ValueListPath<'a>),
}
impl<'a> ResolvePosition<'a> for Value {
    type Parent = ValueParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        match self {
            Value::Constant(val) => {
                val.resolve(ConstantValueParent::Value(self.path(parent)), position)
            }
            Value::Variable(val) => {
                val.resolve(VariableIdentifierParent::Value(self.path(parent)), position)
            }
            Value::List(list) => list.resolve(Box::new(self.path(parent)), position),
            Value::Object(obj) => obj.resolve(Box::new(self.path(parent)), position),
        }
    }

    fn contains(&'a self, position: Span) -> bool {
        match self {
            Value::Constant(val) => val.contains(position),
            Value::Variable(val) => val.contains(position),
            Value::List(list) => list.contains(position),
            Value::Object(obj) => obj.contains(position),
        }
    }
}

pub type ConstantValuePath<'a> = Path<&'a ConstantValue, ConstantValueParent<'a>>;
#[derive(Debug)]
pub enum ConstantValueParent<'a> {
    Value(ValuePath<'a>),
    DefaultValue(DefaultValuePath<'a>),
    ConstantList(ConstantListPath<'a>),
    ConstantObj(Box<ConstantObjPath<'a>>),
    ConstantArgValue(ConstantArgPath<'a>),
}
impl<'a> ResolvePosition<'a> for ConstantValue {
    type Parent = ConstantValueParent<'a>;
    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        match self {
            ConstantValue::Int(int) => ResolutionPath::ConstantInt(ConstantIntPath {
                inner: int,
                parent: self.path(parent),
            }),
            ConstantValue::Float(float) => ResolutionPath::ConstantFloat(ConstantFloatPath {
                inner: float,
                parent: self.path(parent),
            }),
            ConstantValue::String(string) => ResolutionPath::ConstantString(ConstantStringPath {
                inner: string,
                parent: self.path(parent),
            }),
            ConstantValue::Boolean(boolean) => {
                ResolutionPath::ConstantBoolean(ConstantBooleanPath {
                    inner: boolean,
                    parent: self.path(parent),
                })
            }
            ConstantValue::Null(null) => ResolutionPath::ConstantNull(ConstantNullPath {
                inner: null,
                parent: self.path(parent),
            }),
            ConstantValue::Enum(enum_) => ResolutionPath::ConstantEnum(ConstantEnumPath {
                inner: enum_,
                parent: self.path(parent),
            }),
            ConstantValue::List(list) => list.resolve(Box::new(self.path(parent)), position),
            ConstantValue::Object(obj) => obj.resolve(Box::new(self.path(parent)), position),
        }
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span().contains(position)
    }
}

pub type ConstantIntPath<'a> = Path<&'a IntNode, ConstantValuePath<'a>>;
pub type ConstantFloatPath<'a> = Path<&'a FloatNode, ConstantValuePath<'a>>;
pub type ConstantStringPath<'a> = Path<&'a StringNode, ConstantValuePath<'a>>;
pub type ConstantBooleanPath<'a> = Path<&'a BooleanNode, ConstantValuePath<'a>>;
pub type ConstantNullPath<'a> = Path<&'a Token, ConstantValuePath<'a>>;
pub type ConstantEnumPath<'a> = Path<&'a EnumNode, ConstantValuePath<'a>>;

pub type ConstantListPath<'a> = Path<&'a List<ConstantValue>, Box<ConstantValuePath<'a>>>;
impl<'a> ResolvePosition<'a> for List<ConstantValue> {
    type Parent = Box<ConstantValuePath<'a>>;
    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for constant in &self.items {
            if constant.contains(position) {
                return constant.resolve(
                    ConstantValueParent::ConstantList(self.path(parent)),
                    position,
                );
            }
        }
        ResolutionPath::ConstantList(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ConstantObjPath<'a> = Path<&'a List<ConstantArgument>, Box<ConstantValuePath<'a>>>;

impl<'a> ResolvePosition<'a> for List<ConstantArgument> {
    type Parent = Box<ConstantValuePath<'a>>;
    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for arg in &self.items {
            if arg.contains(position) {
                return arg.resolve(self.path(parent), position);
            }
        }
        ResolutionPath::ConstantObj(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ConstantArgPath<'a> = Path<&'a ConstantArgument, ConstantObjPath<'a>>;

impl<'a> ResolvePosition<'a> for ConstantArgument {
    type Parent = ConstantObjPath<'a>;
    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        if self.name.contains(position) {
            return self
                .name
                .resolve(IdentParent::ConstantArgKey(self.path(parent)), position);
        }
        if self.value.contains(position) {
            return self.value.resolve(
                ConstantValueParent::ConstantArgValue(self.path(parent)),
                position,
            );
        }
        ResolutionPath::ConstantArg(self.path(parent))
    }
    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type VariableIdentifierPath<'a> = Path<&'a VariableIdentifier, VariableIdentifierParent<'a>>;
#[derive(Debug)]
pub enum VariableIdentifierParent<'a> {
    Value(ValuePath<'a>),
    VariableDefinition(VariableDefinitionPath<'a>),
}
impl<'a> ResolvePosition<'a> for VariableIdentifier {
    type Parent = VariableIdentifierParent<'a>;

    fn resolve(&'a self, parent: Self::Parent, _position: Span) -> ResolutionPath<'a> {
        ResolutionPath::VariableIdentifier(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ValueListPath<'a> = Path<&'a List<Value>, Box<ValuePath<'a>>>;

impl<'a> ResolvePosition<'a> for List<Value> {
    type Parent = Box<ValuePath<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for value in &self.items {
            if value.contains(position) {
                return value.resolve(ValueParent::ValueList(self.path(parent)), position);
            }
        }
        ResolutionPath::ValueList(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

pub type ConstantObjectPath<'a> = Path<&'a List<Argument>, Box<ValuePath<'a>>>;

impl<'a> ResolvePosition<'a> for List<Argument> {
    type Parent = Box<ValuePath<'a>>;

    fn resolve(&'a self, parent: Self::Parent, position: Span) -> ResolutionPath<'a> {
        for arg in &self.items {
            if arg.contains(position) {
                return arg.resolve(ArgumentParent::ConstantObject(self.path(parent)), position);
            }
        }
        ResolutionPath::ConstantObject(self.path(parent))
    }

    fn contains(&'a self, position: Span) -> bool {
        self.span.contains(position)
    }
}

#[cfg(test)]
mod test;


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

use graphql_syntax::OperationKind;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use super::FragmentDefinitionPath;
use super::InlineFragmentPath;
use super::LinkedFieldPath;
use super::OperationDefinitionPath;
use super::SelectionParent;

impl<'a> SelectionParent<'a> {
    pub fn find_parent_type(&self, schema: &SDLSchema) -> Option<Type> {
        match self {
            SelectionParent::OperationDefinitionSelection(OperationDefinitionPath {
                inner: operation_definition,
                parent: _,
            }) => match operation_definition.operation_kind() {
                OperationKind::Mutation => schema.mutation_type(),
                OperationKind::Query => schema.query_type(),
                OperationKind::Subscription => schema.subscription_type(),
            },
            SelectionParent::LinkedFieldSelection(LinkedFieldPath {
                inner: field,
                parent: selection_path,
            }) => {
                let parent_type = selection_path.parent.find_parent_type(schema)?;
                let field_id = schema.named_field(parent_type, field.name.value)?;
                let field = schema.field(field_id);
                Some(field.type_.inner())
            }
            SelectionParent::FragmentDefinitionSelection(FragmentDefinitionPath {
                inner: definition,
                parent: _,
            }) => schema.get_type(definition.type_condition.type_.value),
            SelectionParent::InlineFragmentSelection(InlineFragmentPath {
                inner: fragment,
                parent: selection_path,
            }) => match &fragment.type_condition {
                Some(condition) => schema.get_type(condition.type_.value),
                None => selection_path.parent.find_parent_type(schema),
            },
        }
    }

    pub fn find_type_path(&self, schema: &SDLSchema) -> Vec<Type> {
        match self {
            SelectionParent::OperationDefinitionSelection(OperationDefinitionPath {
                inner: operation_definition,
                parent: _,
            }) => match operation_definition.operation_kind() {
                OperationKind::Mutation => schema.mutation_type(),
                OperationKind::Query => schema.query_type(),
                OperationKind::Subscription => schema.subscription_type(),
            }
            .into_iter()
            .collect::<Vec<_>>(),
            SelectionParent::LinkedFieldSelection(LinkedFieldPath {
                inner: linked_field,
                parent: linked_field_selection_path,
            }) => {
                let mut path = linked_field_selection_path.parent.find_type_path(schema);
                if let Some(type_) = path
                    .last()
                    .and_then(|parent_type| {
                        schema.named_field(*parent_type, linked_field.name.value)
                    })
                    .map(|field_id| schema.field(field_id).type_.inner())
                {
                    path.push(type_)
                }
                path
            }
            SelectionParent::FragmentDefinitionSelection(FragmentDefinitionPath {
                inner: fragment_definition,
                parent: _,
            }) => {
                let type_ = fragment_definition.type_condition.type_.value;
                schema.get_type(type_).into_iter().collect::<Vec<_>>()
            }
            SelectionParent::InlineFragmentSelection(InlineFragmentPath {
                inner: inline_fragment,
                parent: inline_fragment_selection_path,
            }) => {
                let mut path = inline_fragment_selection_path.parent.find_type_path(schema);
                if let Some(type_) =
                    inline_fragment
                        .type_condition
                        .as_ref()
                        .and_then(|type_condition| {
                            let type_ = type_condition.type_.value;
                            schema.get_type(type_)
                        })
                {
                    path.push(type_)
                };
                path
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use common::SourceLocationKey;
    use common::Span;
    use graphql_syntax::parse_executable_with_features;
    use graphql_syntax::FragmentArgumentSyntaxKind;
    use graphql_syntax::ParserFeatures;
    use intern::string_key::Intern;
    use intern::string_key::StringKey;
    use relay_test_schema::get_test_schema;

    use super::*;
    use crate::IdentParent;
    use crate::IdentPath;
    use crate::ResolutionPath;
    use crate::ResolvePosition;
    use crate::ScalarFieldPath;

    fn get_field_parent_type_name(source: &str, sub_str: &str) -> StringKey {
        let schema = get_test_schema();
        let document = parse_executable_with_features(
            source,
            SourceLocationKey::standalone("/test/file"),
            ParserFeatures {
                fragment_argument_capability:
                    FragmentArgumentSyntaxKind::SpreadArgumentsAndFragmentVariableDefinitions,
            },
        )
        .unwrap();

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

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

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

        let selection_type = match resolved {
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::LinkedFieldName(LinkedFieldPath {
                        inner: _,
                        parent: selection,
                    }),
            }) => selection.parent.find_parent_type(&schema),
            ResolutionPath::Ident(IdentPath {
                inner: _,
                parent:
                    IdentParent::ScalarFieldName(ScalarFieldPath {
                        inner: _,
                        parent: selection,
                    }),
            }) => selection.parent.find_parent_type(&schema),
            _ => None,
        }
        .expect("Expected to get selection type.");

        schema.get_type_name(selection_type)
    }

    #[test]
    fn test_me() {
        let source = r#"
        query Foo {
            me {
                id
                ... {
                    name
                }
                emailAddresses
                screennames {
                    service
                }
                author {
                    author {
                        author {
                            username
                        }
                    }
                }
                ... on Actor {
                    websites
                }
            }
        }"#;
        assert_eq!(get_field_parent_type_name(source, "id"), "User".intern());
        assert_eq!(get_field_parent_type_name(source, "name"), "User".intern());
        assert_eq!(
            get_field_parent_type_name(source, "emailAddresses"),
            "User".intern()
        );
        assert_eq!(
            get_field_parent_type_name(source, "service"),
            "Screenname".intern()
        );
        assert_eq!(get_field_parent_type_name(source, "me"), "Query".intern());
        assert_eq!(
            get_field_parent_type_name(source, "username"),
            "User".intern()
        );
        assert_eq!(
            get_field_parent_type_name(source, "websites"),
            "Actor".intern()
        );
    }

    #[test]
    fn test_fragment_root() {
        let source = r#"
        fragment foo on User {
            id
        }"#;
        assert_eq!(get_field_parent_type_name(source, "id"), "User".intern());
    }
}