/*
 * 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::sync::*;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::NamedItem;
use common::PointerAddress;
use fnv::FnvHashMap;
use graphql_ir::node_identifier::LocationAgnosticPartialEq;
use graphql_ir::node_identifier::NodeIdentifier;
use graphql_ir::Condition;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionNameMap;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::TransformedValue;
use parking_lot::Mutex;
use parking_lot::RwLock;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use crate::handle_fields::HANDLER_ARG_NAME;
use crate::handle_fields::KEY_ARG_NAME;
use crate::util::is_relay_custom_inline_fragment_directive;
use crate::util::CustomMetadataDirectives;
use crate::ModuleMetadata;
use crate::RelayLocationAgnosticBehavior;
use crate::ValidationMessage;

type SeenLinkedFields = Arc<RwLock<FnvHashMap<PointerAddress, TransformedValue<Arc<LinkedField>>>>>;
type SeenInlineFragments =
    Arc<RwLock<FnvHashMap<(PointerAddress, Type), TransformedValue<Arc<InlineFragment>>>>>;

/// Transform that flattens inline fragments, fragment spreads, merges linked fields selections.
///
/// Inline fragments are inlined (replaced with their selections) when:
/// - The fragment type matches the type of its parent, and it `is_for_codegen`,
///   or the inline fragment doesn't have directives .
///
/// with the exception that it never flattens the inline fragment with relay
/// directives (@defer, @__clientExtensions).
///
/// `should_validate_fragment_spreads` - This flag indicates that we need to
/// validate the fragment spreads selections, and if they can be
/// flattened with other selections. This flag should be passed when
/// we're generating query texts.
pub fn flatten(
    program: &mut Program,
    is_for_codegen: bool,
    should_validate_fragment_spreads: bool,
) -> DiagnosticsResult<()> {
    let mut fragment_for_validation = FragmentDefinitionNameMap::default();
    if should_validate_fragment_spreads {
        for (name, fragment) in &program.fragments {
            fragment_for_validation.insert(*name, Arc::clone(fragment));
        }
    }

    let transform = FlattenTransform::new(
        Arc::clone(&program.schema),
        fragment_for_validation,
        is_for_codegen,
        should_validate_fragment_spreads,
    );
    let errors = Arc::new(Mutex::new(Vec::new()));

    par_iter(&mut program.operations).for_each(|operation| {
        if let Err(err) = transform.transform_operation(operation) {
            errors.lock().extend(err.into_iter());
        }
    });
    par_iter(&mut program.fragments).for_each(|(_, fragment)| {
        if let Err(err) = transform.transform_fragment(fragment) {
            errors.lock().extend(err.into_iter());
        }
    });
    let is_errors_empty = { errors.lock().is_empty() };
    if is_errors_empty {
        Ok(())
    } else {
        Err(Arc::try_unwrap(errors).unwrap().into_inner())
    }
}

struct FlattenTransform {
    fragments: FragmentDefinitionNameMap<Arc<FragmentDefinition>>,
    schema: Arc<SDLSchema>,
    is_for_codegen: bool,
    should_validate_fragment_spreads: bool,
    seen_linked_fields: SeenLinkedFields,
    seen_inline_fragments: SeenInlineFragments,
}

impl FlattenTransform {
    fn new(
        schema: Arc<SDLSchema>,
        fragments: FragmentDefinitionNameMap<Arc<FragmentDefinition>>,
        is_for_codegen: bool,
        should_validate_fragment_spreads: bool,
    ) -> Self {
        Self {
            schema,
            fragments,
            is_for_codegen,
            should_validate_fragment_spreads,
            seen_linked_fields: Default::default(),
            seen_inline_fragments: Default::default(),
        }
    }

    fn transform_operation(
        &self,
        operation: &mut Arc<OperationDefinition>,
    ) -> DiagnosticsResult<()> {
        let next_selections = self.transform_selections(&operation.selections, operation.type_)?;
        if let TransformedValue::Replace(next_selections) = next_selections {
            Arc::make_mut(operation).selections = next_selections
        };
        Ok(())
    }

    fn transform_fragment(&self, fragment: &mut Arc<FragmentDefinition>) -> DiagnosticsResult<()> {
        let next_selections =
            self.transform_selections(&fragment.selections, fragment.type_condition)?;
        if let TransformedValue::Replace(next_selections) = next_selections {
            Arc::make_mut(fragment).selections = next_selections
        };
        Ok(())
    }

    fn transform_selections(
        &self,
        selections: &[Selection],
        parent_type: Type,
    ) -> DiagnosticsResult<TransformedValue<Vec<Selection>>> {
        let mut next_selections = Vec::new();
        let mut has_changes = false;
        for (index, selection) in selections.iter().enumerate() {
            let next_selection = self.transform_selection(selection, parent_type)?;
            if let TransformedValue::Replace(next_selection) = next_selection {
                if !has_changes {
                    has_changes = true;
                    next_selections.reserve(selections.len());
                    next_selections.extend(selections.iter().take(index).cloned())
                }
                next_selections.push(next_selection);
            } else if has_changes {
                next_selections.push(selection.clone());
            }
        }

        let mut flattened_selections_for_check = Vec::with_capacity(selections.len());
        self.can_flatten_selections(
            &mut flattened_selections_for_check,
            if has_changes {
                &next_selections
            } else {
                selections
            },
            parent_type,
        )?;

        let mut flattened_selections = Vec::with_capacity(selections.len());
        has_changes = self.flatten_selections(
            &mut flattened_selections,
            if has_changes {
                &next_selections
            } else {
                selections
            },
            parent_type,
        ) || has_changes;

        Ok(if has_changes {
            TransformedValue::Replace(flattened_selections)
        } else {
            TransformedValue::Keep
        })
    }

    fn transform_linked_field(
        &self,
        linked_field: &Arc<LinkedField>,
    ) -> DiagnosticsResult<TransformedValue<Arc<LinkedField>>> {
        let should_cache = Arc::strong_count(linked_field) > 1;
        let key = PointerAddress::new(Arc::as_ref(linked_field));
        if should_cache {
            let seen_linked_fields = self.seen_linked_fields.read();
            if let Some(prev) = seen_linked_fields.get(&key) {
                return Ok(prev.clone());
            }
        }
        let type_ = self
            .schema
            .field(linked_field.definition.item)
            .type_
            .inner();
        let result = self
            .transform_selections(&linked_field.selections, type_)?
            .map(|next_selections| {
                Arc::new(LinkedField {
                    alias: linked_field.alias,
                    definition: linked_field.definition,
                    arguments: linked_field.arguments.clone(),
                    directives: linked_field.directives.clone(),
                    selections: next_selections,
                })
            });
        if should_cache {
            let mut seen_linked_fields = self.seen_linked_fields.write();
            // If another thread computed this in the meantime, use that result
            if let Some(prev) = seen_linked_fields.get(&key) {
                return Ok(prev.clone());
            }
            seen_linked_fields.insert(key, result.clone());
        }
        Ok(result)
    }

    fn transform_inline_fragment(
        &self,
        fragment: &Arc<InlineFragment>,
        parent_type: Type,
    ) -> DiagnosticsResult<TransformedValue<Arc<InlineFragment>>> {
        let should_cache = Arc::strong_count(fragment) > 1;
        let key = (PointerAddress::new(Arc::as_ref(fragment)), parent_type);
        if should_cache {
            let seen_inline_fragments = self.seen_inline_fragments.read();
            if let Some(prev) = seen_inline_fragments.get(&key) {
                return Ok(prev.clone());
            }
        }
        let next_parent_type = match fragment.type_condition {
            Some(type_condition) => type_condition,
            None => parent_type,
        };
        let result = self
            .transform_selections(&fragment.selections, next_parent_type)?
            .map(|next_selections| {
                Arc::new(InlineFragment {
                    type_condition: fragment.type_condition,
                    directives: fragment.directives.clone(),
                    selections: next_selections,
                    spread_location: fragment.spread_location,
                })
            });
        if should_cache {
            let mut seen_inline_fragments = self.seen_inline_fragments.write();
            // If another thread computed this in the meantime, use that result
            if let Some(prev) = seen_inline_fragments.get(&key) {
                return Ok(prev.clone());
            }
            seen_inline_fragments.insert(key, result.clone());
        }
        Ok(result)
    }

    fn transform_selection(
        &self,
        selection: &Selection,
        parent_type: Type,
    ) -> DiagnosticsResult<TransformedValue<Selection>> {
        Ok(match selection {
            Selection::InlineFragment(node) => self
                .transform_inline_fragment(node, parent_type)?
                .map(Selection::InlineFragment),
            Selection::LinkedField(node) => self
                .transform_linked_field(node)?
                .map(Selection::LinkedField),
            Selection::Condition(node) => self
                .transform_selections(&node.selections, parent_type)?
                .map(|next_selections| {
                    Selection::Condition(Arc::new(Condition {
                        value: node.value.clone(),
                        passing_value: node.passing_value,
                        selections: next_selections,
                        location: node.location,
                    }))
                }),
            Selection::FragmentSpread(_) | Selection::ScalarField(_) => TransformedValue::Keep,
        })
    }

    fn flatten_selections(
        &self,
        flattened_selections: &mut Vec<Selection>,
        selections: &[Selection],
        parent_type: Type,
    ) -> bool {
        let mut has_changes = false;
        for selection in selections {
            if let Selection::InlineFragment(inline_fragment) = selection {
                if should_flatten_inline_fragment(inline_fragment, parent_type, self.is_for_codegen)
                {
                    has_changes = true;
                    self.flatten_selections(
                        flattened_selections,
                        &inline_fragment.selections,
                        parent_type,
                    );
                    continue;
                }
            }

            let flattened_selection = flattened_selections.iter_mut().find(|sel| {
                sel.ptr_eq(selection)
                    || NodeIdentifier::<RelayLocationAgnosticBehavior>::are_equal(
                        &self.schema,
                        sel,
                        selection,
                    )
            });

            match flattened_selection {
                None => {
                    flattened_selections.push(selection.clone());
                }
                Some(flattened_selection) => {
                    has_changes = true;
                    if flattened_selection.ptr_eq(selection) {
                        continue;
                    }
                    match flattened_selection {
                        Selection::InlineFragment(flattened_node) => {
                            let node = match selection {
                                Selection::InlineFragment(node) => node,
                                _ => unreachable!("FlattenTransform: Expected an InlineFragment."),
                            };

                            let type_condition =
                                flattened_node.type_condition.unwrap_or(parent_type);

                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            self.flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                type_condition,
                            );
                        }
                        Selection::LinkedField(flattened_node) => {
                            let node = match selection {
                                Selection::LinkedField(node) => node,
                                _ => unreachable!("FlattenTransform: Expected a LinkedField."),
                            };
                            let type_ = self
                                .schema
                                .field(flattened_node.definition.item)
                                .type_
                                .inner();
                            let should_merge_handles = selection.directives().iter().any(|d| {
                                CustomMetadataDirectives::is_handle_field_directive(d.name.item)
                            });

                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            if should_merge_handles {
                                flattened_node_mut.directives = merge_handle_directives(
                                    &flattened_node_mut.directives,
                                    selection.directives(),
                                )
                            };
                            self.flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                type_,
                            );
                        }
                        Selection::Condition(flattened_node) => {
                            let node = match selection {
                                Selection::Condition(node) => node,
                                _ => unreachable!("FlattenTransform: Expected a Condition."),
                            };

                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            self.flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                parent_type,
                            );
                        }
                        Selection::ScalarField(flattened_node) => {
                            let node = match selection {
                                Selection::ScalarField(node) => node,
                                _ => unreachable!("FlattenTransform: Expected a ScalarField."),
                            };
                            let should_merge_handles = node.directives.iter().any(|d| {
                                CustomMetadataDirectives::is_handle_field_directive(d.name.item)
                            });
                            if should_merge_handles {
                                let flattened_node_mut = Arc::make_mut(flattened_node);
                                flattened_node_mut.directives = merge_handle_directives(
                                    &flattened_node_mut.directives,
                                    selection.directives(),
                                );
                            }
                        }
                        Selection::FragmentSpread(_) => {}
                    };
                }
            }
        }

        has_changes
    }

    fn can_flatten_selections(
        &self,
        flattened_selections: &mut Vec<Selection>,
        selections: &[Selection],
        parent_type: Type,
    ) -> DiagnosticsResult<()> {
        for selection in selections {
            if self.should_validate_fragment_spreads {
                if let Selection::FragmentSpread(spread) = selection {
                    let fragment_definition =
                        self.fragments
                            .get(&spread.fragment.item)
                            .ok_or(Diagnostic::error(
                                ValidationMessage::UndefinedFragment(spread.fragment.item),
                                spread.fragment.location,
                            ))?;
                    if fragment_definition.type_condition == parent_type {
                        self.can_flatten_selections(
                            flattened_selections,
                            &fragment_definition.selections,
                            fragment_definition.type_condition,
                        )?;
                    }
                    continue;
                }
            }

            if let Selection::InlineFragment(inline_fragment) = selection {
                // We will iterate through a selection of inline fragments
                // (ignoring its directives) if this inline fragment doesn't
                // have a type condition or type condition is the same as `parent_type`.
                // The one exception for the latter case is @module directive.
                // These are specially validated: they don't have conflicts in selections
                // but may have conflicts in directive arguments.
                if inline_fragment.type_condition.is_none()
                    || (inline_fragment.type_condition == Some(parent_type)
                        && inline_fragment
                            .directives
                            .named(ModuleMetadata::directive_name())
                            .is_none())
                {
                    self.can_flatten_selections(
                        flattened_selections,
                        &inline_fragment.selections,
                        parent_type,
                    )?;
                    continue;
                }
            }

            let flattened_selection = flattened_selections.iter_mut().find(|sel| {
                sel.ptr_eq(selection)
                    || NodeIdentifier::<RelayLocationAgnosticBehavior>::are_equal(
                        &self.schema,
                        sel,
                        selection,
                    )
            });
            match flattened_selection {
                None => {
                    flattened_selections.push(selection.clone());
                }
                Some(flattened_selection) => {
                    if flattened_selection.ptr_eq(selection) {
                        continue;
                    }
                    match flattened_selection {
                        Selection::InlineFragment(flattened_node) => {
                            let node = match selection {
                                Selection::InlineFragment(node) => node,
                                _ => unreachable!("FlattenTransform: Expected an InlineFragment."),
                            };
                            if let Some(flattened_module_metadata) =
                                ModuleMetadata::find(&flattened_node.directives)
                            {
                                if let Some(module_metadata) =
                                    ModuleMetadata::find(&node.directives)
                                {
                                    if flattened_module_metadata.key != module_metadata.key
                                        || flattened_module_metadata.module_name
                                            != module_metadata.module_name
                                        || flattened_module_metadata.fragment_name
                                            != module_metadata.fragment_name
                                    {
                                        let error = Diagnostic::error(
                                            ValidationMessage::ConflictingModuleSelections,
                                            module_metadata.location,
                                        )
                                        .annotate(
                                            "conflicts with",
                                            flattened_module_metadata.location,
                                        );
                                        return Err(vec![error]);
                                    }
                                }
                            }
                            let type_condition =
                                flattened_node.type_condition.unwrap_or(parent_type);
                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            self.can_flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                type_condition,
                            )?;
                        }
                        Selection::LinkedField(flattened_node) => {
                            let node = match selection {
                                Selection::LinkedField(node) => node,
                                _ => unreachable!("FlattenTransform: Expected a LinkedField."),
                            };
                            let type_ = self
                                .schema
                                .field(flattened_node.definition.item)
                                .type_
                                .inner();

                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            self.can_flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                type_,
                            )?;
                        }
                        Selection::Condition(flattened_node) => {
                            let node = match selection {
                                Selection::Condition(node) => node,
                                _ => unreachable!("FlattenTransform: Expected a Condition."),
                            };

                            let flattened_node_mut = Arc::make_mut(flattened_node);
                            self.can_flatten_selections(
                                &mut flattened_node_mut.selections,
                                &node.selections,
                                parent_type,
                            )?;
                        }
                        Selection::ScalarField(_) | Selection::FragmentSpread(_) => {}
                    };
                }
            }
        }

        Ok(())
    }
}

fn should_flatten_inline_fragment(
    inline_fragment: &InlineFragment,
    parent_type: Type,
    is_for_codegen: bool,
) -> bool {
    if let Some(type_condition) = inline_fragment.type_condition {
        if type_condition != parent_type {
            return false;
        }
    }
    if is_for_codegen {
        !inline_fragment
            .directives
            .iter()
            .any(is_relay_custom_inline_fragment_directive)
    } else {
        inline_fragment.directives.is_empty()
    }
}

fn merge_handle_directives(
    directives_a: &[Directive],
    directives_b: &[Directive],
) -> Vec<Directive> {
    let (mut handles, mut directives): (Vec<_>, Vec<_>) =
        directives_a.iter().cloned().partition(|directive| {
            CustomMetadataDirectives::is_handle_field_directive(directive.name.item)
        });
    for directive in directives_b {
        if CustomMetadataDirectives::is_handle_field_directive(directive.name.item) {
            if handles.is_empty() {
                handles.push(directive.clone());
            } else {
                let current_handler_arg = directive.arguments.named(*HANDLER_ARG_NAME);
                let current_name_arg = directive.arguments.named(*KEY_ARG_NAME);
                let is_duplicate_handle = handles.iter().any(|handle| {
                    current_handler_arg.location_agnostic_eq::<RelayLocationAgnosticBehavior>(
                        &handle.arguments.named(*HANDLER_ARG_NAME),
                    ) && current_name_arg.location_agnostic_eq::<RelayLocationAgnosticBehavior>(
                        &handle.arguments.named(*KEY_ARG_NAME),
                    )
                });
                if !is_duplicate_handle {
                    handles.push(directive.clone());
                }
            }
        }
    }
    directives.extend(handles.into_iter());
    directives
}


/*
 * 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::ArgumentName;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::FeatureFlag;
use common::Location;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::associated_data_impl;
use graphql_ir::transform_list;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::TransformedValue;
use graphql_ir::Transformer;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;
use schema::Schema;
use schema::Type;

use crate::ValidationMessage;

lazy_static! {
    pub static ref FRAGMENT_ALIAS_DIRECTIVE_NAME: DirectiveName = DirectiveName("alias".intern());
    pub static ref FRAGMENT_ALIAS_ARGUMENT_NAME: ArgumentName = ArgumentName("as".intern());
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct FragmentAliasMetadata {
    pub alias: WithLocation<StringKey>,
    pub type_condition: Option<Type>,
    pub selection_type: Type,
}
associated_data_impl!(FragmentAliasMetadata);

pub fn fragment_alias_directive(
    program: &Program,
    feature_flag: &FeatureFlag,
) -> DiagnosticsResult<Program> {
    let mut transform = FragmentAliasTransform::new(program, feature_flag);
    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());
    if transform.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transform.errors)
    }
}

struct FragmentAliasTransform<'program> {
    program: &'program Program,
    feature_flag: &'program FeatureFlag,
    document_name: Option<StringKey>,
    parent_type: Option<Type>,
    errors: Vec<Diagnostic>,
}

impl<'program> FragmentAliasTransform<'program> {
    fn new(program: &'program Program, feature_flag: &'program FeatureFlag) -> Self {
        Self {
            program,
            feature_flag,
            document_name: None,
            parent_type: None,
            errors: Vec::new(),
        }
    }

    fn transform_alias_directives<N>(
        &mut self,
        directives: &[Directive],
        type_condition: Option<Type>,
        get_default_name: N,
    ) -> TransformedValue<Vec<Directive>>
    where
        N: Fn() -> Option<StringKey>,
    {
        transform_list(directives, |directive| {
            if directive.name.item != *FRAGMENT_ALIAS_DIRECTIVE_NAME {
                return Transformed::Keep;
            }

            let allowed = match self.document_name {
                Some(name) => self.feature_flag.is_enabled_for(name),
                None => false,
            };

            if !allowed {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::FragmentAliasDirectiveDisabled,
                    directive.name.location,
                ));
                return Transformed::Keep;
            }
            let alias = match directive.arguments.named(*FRAGMENT_ALIAS_ARGUMENT_NAME) {
                Some(arg) => match arg.value.item.get_string_literal() {
                    Some(name) => WithLocation::new(arg.name.location, name),
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::FragmentAliasDirectiveDynamicNameArg,
                            arg.value.location,
                        ));
                        return Transformed::Keep;
                    }
                },
                None => match get_default_name() {
                    None => {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::FragmentAliasDirectiveMissingAs,
                            directive.name.location,
                        ));
                        return Transformed::Keep;
                    }
                    Some(as_) => WithLocation::new(directive.name.location, as_),
                },
            };

            // In the future we might want to relax this restriction, but for now this allows us
            // to avoid having to consider how @alias would interact
            // with all other directives like @defer.
            if directives.len() > 1 {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::FragmentAliasIncompatibleDirective,
                    directive.name.location,
                ));
                return Transformed::Keep;
            }
            Transformed::Replace(
                FragmentAliasMetadata {
                    alias,
                    type_condition,
                    selection_type: type_condition.unwrap_or(
                        self.parent_type
                            .expect("Selection should be within a parent type."),
                    ),
                }
                .into(),
            )
        })
    }
}

impl Transformer for FragmentAliasTransform<'_> {
    const NAME: &'static str = "NamedFragmentSpreadsTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        self.document_name = Some(fragment.name.item.0);
        self.parent_type = Some(fragment.type_condition);
        let transformed = self.default_transform_fragment(fragment);
        self.parent_type = None;
        self.document_name = None;
        transformed
    }

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        self.document_name = Some(operation.name.item.0);
        self.parent_type = Some(operation.type_);
        let transformed = self.default_transform_operation(operation);
        self.parent_type = None;
        self.document_name = None;
        transformed
    }

    fn transform_inline_fragment(&mut self, fragment: &InlineFragment) -> Transformed<Selection> {
        let get_default_name = || {
            fragment
                .type_condition
                .map(|type_| self.program.schema.get_type_name(type_))
        };
        let previous_parent_type = self.parent_type;

        if let Some(type_condition) = fragment.type_condition {
            self.parent_type = Some(type_condition);
        }

        let transformed = match self.transform_alias_directives(
            &fragment.directives,
            fragment.type_condition,
            get_default_name,
        ) {
            TransformedValue::Keep => self.default_transform_inline_fragment(fragment),
            TransformedValue::Replace(next_directives) => {
                Transformed::Replace(Selection::InlineFragment(Arc::new(InlineFragment {
                    directives: next_directives,
                    type_condition: fragment.type_condition,
                    selections: self
                        .transform_selections(&fragment.selections)
                        .replace_or_else(|| fragment.selections.clone()),
                    spread_location: Location::generated(),
                })))
            }
        };

        self.parent_type = previous_parent_type;

        transformed
    }

    fn transform_fragment_spread(&mut self, spread: &FragmentSpread) -> Transformed<Selection> {
        let type_condition = Some(
            self.program
                .fragment(spread.fragment.item)
                .expect("I believe we have already validated that all fragments exist")
                .type_condition,
        );
        let get_default_name = || Some(spread.fragment.item.0);
        self.transform_alias_directives(&spread.directives, type_condition, get_default_name)
            .map(|directives| {
                Selection::FragmentSpread(Arc::new(FragmentSpread {
                    fragment: spread.fragment,
                    arguments: spread.arguments.clone(),
                    directives,
                }))
            })
            .into()
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        let previous_parent_type = self.parent_type;

        self.parent_type = Some(
            self.program
                .schema
                .field(field.definition.item)
                .type_
                .inner(),
        );

        let transformed = self.default_transform_linked_field(field);

        self.parent_type = previous_parent_type;

        transformed
    }
}


/*
 * 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_ir::associated_data_impl;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use intern::string_key::StringKey;
use intern::string_key::StringKeyMap;
use itertools::Itertools;
use schema::Schema;
use schema::Type;
use schema::TypeReference;

use crate::util::get_fragment_filename;
use crate::ModuleMetadata;

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RelayDataDrivenDependencyMetadata {
    pub direct_dependencies: Option<Vec<(StringKey, String)>>,
    // always None for fragments
    pub indirect_dependencies: Option<Vec<(StringKey, String)>>,
}
associated_data_impl!(RelayDataDrivenDependencyMetadata);

pub fn generate_data_driven_dependency_metadata(program: &Program) -> Program {
    let mut transformer = GenerateDataDrivenDependencyMetadata::new(program);
    transformer
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

struct GenerateDataDrivenDependencyMetadata<'s> {
    pub program: &'s Program,
    cache: StringKeyMap<Option<ModuleEntries>>,
}

impl<'s> GenerateDataDrivenDependencyMetadata<'s> {
    fn new(program: &'s Program) -> Self {
        GenerateDataDrivenDependencyMetadata {
            program,
            cache: Default::default(),
        }
    }

    fn get_direct_module_entries_for_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Option<&ModuleEntries> {
        let cache_key = fragment.name.item.0;
        #[allow(clippy::map_entry)]
        if !self.cache.contains_key(&cache_key) {
            let entries = self.extract_module_entries_from_selections(
                TypeReference::Named(fragment.type_condition),
                &fragment.selections,
                ModuleEntriesKind::Direct,
            );
            self.cache.insert(cache_key, entries);
        }
        self.cache.get(&cache_key).and_then(|v| v.as_ref())
    }

    fn extract_module_entries_from_selections(
        &mut self,
        type_: TypeReference<Type>,
        selections: &[Selection],
        kind: ModuleEntriesKind,
    ) -> Option<ModuleEntries> {
        let mut processing_queue: Vec<ProcessingItem<'_>> = vec![ProcessingItem {
            plural: false,
            parent_type: type_,
            selections,
        }];

        let mut module_entries: ModuleEntries = Default::default();

        while let Some(processing_item) = processing_queue.pop() {
            for selection in processing_item.selections {
                match selection {
                    Selection::ScalarField(_) => {}
                    Selection::FragmentSpread(spread) => {
                        if kind == ModuleEntriesKind::Direct {
                            continue;
                        }
                        let fragment = self.program.fragment(spread.fragment.item)?;
                        if let Some(fragment_module_entries) =
                            self.get_direct_module_entries_for_fragment(fragment)
                        {
                            for (id, entry) in fragment_module_entries {
                                module_entries
                                    .entry(*id)
                                    .and_modify(|module_entry| {
                                        module_entry.branches.extend(entry.branches.iter())
                                    })
                                    .or_insert_with(|| entry.clone());
                            }
                        }
                    }
                    Selection::LinkedField(linked_filed) => {
                        let field_type = &self
                            .program
                            .schema
                            .field(linked_filed.definition.item)
                            .type_;
                        processing_queue.push(ProcessingItem {
                            plural: processing_item.plural || field_type.is_list(),
                            parent_type: field_type.clone(),
                            selections: &linked_filed.selections,
                        });
                    }
                    Selection::InlineFragment(inline_fragment) => {
                        let parent_type = match inline_fragment.type_condition {
                            Some(type_) => TypeReference::Named(type_),
                            None => processing_item.parent_type.clone(),
                        };
                        processing_queue.push(ProcessingItem {
                            plural: processing_item.plural,
                            parent_type,
                            selections: &inline_fragment.selections,
                        });
                        if kind == ModuleEntriesKind::Indirect {
                            continue;
                        }
                        if let Some(module_metadata) =
                            ModuleMetadata::find(&inline_fragment.directives)
                        {
                            let id = module_metadata.module_id;
                            let component = module_metadata.module_name;

                            let fragment_spread = inline_fragment
                                .selections
                                .iter()
                                .find(|item| matches!(item, Selection::FragmentSpread(_)));
                            // This is expected to be a fragment spread
                            let fragment_name = match fragment_spread {
                                Some(Selection::FragmentSpread(spread)) => spread.fragment.item,
                                _ => panic!("Expected to have a fragment spread"),
                            };

                            let type_name = self
                                .program
                                .schema
                                .get_type_name(processing_item.parent_type.inner());
                            module_entries
                                .entry(id)
                                .and_modify(|module_entry| {
                                    module_entry.branches.insert(
                                        type_name,
                                        Branch {
                                            component,
                                            fragment: get_fragment_filename(fragment_name),
                                        },
                                    );
                                })
                                .or_insert(ModuleEntry {
                                    branches: {
                                        let mut map = StringKeyMap::default();
                                        map.insert(
                                            type_name,
                                            Branch {
                                                component,
                                                fragment: get_fragment_filename(fragment_name),
                                            },
                                        );
                                        map
                                    },
                                    plural: processing_item.plural,
                                });
                        }
                    }
                    Selection::Condition(condition) => {
                        processing_queue.push(ProcessingItem {
                            plural: processing_item.plural,
                            parent_type: processing_item.parent_type.clone(),
                            selections: &condition.selections,
                        });
                    }
                }
            }
        }

        if !module_entries.is_empty() {
            Some(module_entries)
        } else {
            None
        }
    }
}

#[derive(Debug, Copy, Clone)]
struct Branch {
    component: StringKey,
    fragment: StringKey,
}

type ModuleEntries = StringKeyMap<ModuleEntry>;

#[derive(Debug, Clone)]
struct ModuleEntry {
    branches: StringKeyMap<Branch>,
    plural: bool,
}
#[derive(Debug, PartialEq, Eq)]

enum ModuleEntriesKind {
    Direct,
    Indirect,
}

#[derive(Debug)]
struct ProcessingItem<'a> {
    plural: bool,
    parent_type: TypeReference<Type>,
    selections: &'a [Selection],
}

impl<'s> Transformer for GenerateDataDrivenDependencyMetadata<'s> {
    const NAME: &'static str = "GenerateDataDrivenDependencyMetadata";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        let indirect_module_entries = self.extract_module_entries_from_selections(
            TypeReference::Named(operation.type_),
            &operation.selections,
            ModuleEntriesKind::Indirect,
        );
        let direct_module_entries = self.extract_module_entries_from_selections(
            TypeReference::Named(operation.type_),
            &operation.selections,
            ModuleEntriesKind::Direct,
        );
        if direct_module_entries.is_some() || indirect_module_entries.is_some() {
            let mut next_directives: Vec<Directive> =
                Vec::with_capacity(operation.directives.len() + 1);
            next_directives.extend(operation.directives.iter().cloned());
            next_directives.push(
                RelayDataDrivenDependencyMetadata {
                    direct_dependencies: direct_module_entries
                        .as_ref()
                        .map(get_metadata_from_module_entries),
                    indirect_dependencies: indirect_module_entries
                        .as_ref()
                        .map(get_metadata_from_module_entries),
                }
                .into(),
            );
            Transformed::Replace(OperationDefinition {
                directives: next_directives,
                ..operation.clone()
            })
        } else {
            Transformed::Keep
        }
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        if let Some(module_entries) = self.get_direct_module_entries_for_fragment(fragment) {
            let mut next_directives: Vec<Directive> =
                Vec::with_capacity(fragment.directives.len() + 1);
            next_directives.extend(fragment.directives.iter().cloned());
            next_directives.push(
                RelayDataDrivenDependencyMetadata {
                    direct_dependencies: Some(get_metadata_from_module_entries(module_entries)),
                    indirect_dependencies: None,
                }
                .into(),
            );
            Transformed::Replace(FragmentDefinition {
                directives: next_directives,
                ..fragment.clone()
            })
        } else {
            Transformed::Keep
        }
    }
}

fn get_metadata_from_module_entries(module_entries: &ModuleEntries) -> Vec<(StringKey, String)> {
    module_entries
        .iter()
        .map(|(key, entry)| {
            (
                *key,
                format!(
                    "{{\"branches\":{{{}}},\"plural\":{}}}",
                    entry
                        .branches
                        .iter()
                        .sorted_unstable_by(|a, b| a.0.cmp(b.0))
                        .map(|(id, branch)| format!(
                            "\"{}\":{{\"component\":\"{}\",\"fragment\":\"{}\"}}",
                            id, branch.component, branch.fragment
                        ))
                        .join(","),
                    entry.plural,
                ),
            )
        })
        .sorted_unstable_by(|a, b| a.0.cmp(&b.0))
        .collect()
}


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

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

use common::Location;
use common::WithLocation;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::TransformedValue;
use graphql_ir::Transformer;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use relay_config::SchemaConfig;
use schema::FieldID;
use schema::InterfaceID;
use schema::ObjectID;
use schema::Schema;
use schema::Type;

/// A transform that adds an `id` field on any type that has an id field but
/// where there is no unaliased `id` selection.
pub fn generate_id_field(program: &Program, schema_config: &SchemaConfig) -> Program {
    let mut transform =
        GenerateIDFieldTransform::new(program, schema_config.node_interface_id_field);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

struct GenerateIDFieldTransform<'s> {
    program: &'s Program,
    id_name: StringKey,
    node_interface: Option<NodeInterface>,
    cache: HashMap<Type, Option<FieldID>>,
}

/// If the schema defines a `Node` interface, this contains information on that interface.
struct NodeInterface {
    id: InterfaceID,
    id_field: FieldID,
}

impl<'s> Transformer for GenerateIDFieldTransform<'s> {
    const NAME: &'static str = "GenerateIDFieldTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        let selections = self.transform_selections(&field.selections);

        let next_selections = if self.has_unaliased_id_field(&field.selections) {
            selections
        } else {
            let schema = &self.program.schema;
            let type_ = schema.field(field.definition.item).type_.inner();
            match type_ {
                Type::Object(id) => {
                    let object = schema.object(id);
                    if let Some(id_field_id) = self.get_id_field_id(type_, &object.fields) {
                        let mut next_selections =
                            selections.replace_or_else(|| field.selections.clone());
                        next_selections
                            .push(self.create_id_selection(field.definition.location, id_field_id));
                        TransformedValue::Replace(next_selections)
                    } else {
                        selections
                    }
                }
                Type::Interface(id) => {
                    let interface = schema.interface(id);
                    if let Some(id_field_id) = self.get_id_field_id(type_, &interface.fields) {
                        let mut next_selections =
                            selections.replace_or_else(|| field.selections.clone());
                        next_selections
                            .push(self.create_id_selection(field.definition.location, id_field_id));
                        TransformedValue::Replace(next_selections)
                    } else {
                        self.get_selections_with_inline_id_fragments(
                            field,
                            selections,
                            &interface.implementing_objects,
                        )
                    }
                }
                Type::Union(id) => {
                    let union = schema.union(id);
                    self.get_selections_with_inline_id_fragments(field, selections, &union.members)
                }
                _ => selections,
            }
        };
        match next_selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
                    alias: field.alias,
                    definition: field.definition,
                    arguments: field.arguments.clone(),
                    directives: field.directives.clone(),
                    selections,
                })))
            }
        }
    }

    fn transform_fragment_spread(&mut self, _spread: &FragmentSpread) -> Transformed<Selection> {
        Transformed::Keep
    }

    fn transform_scalar_field(&mut self, _field: &ScalarField) -> Transformed<Selection> {
        Transformed::Keep
    }
}

impl<'s> GenerateIDFieldTransform<'s> {
    fn new(program: &'s Program, id_name: StringKey) -> Self {
        let schema = &program.schema;
        let node_interface = match schema.get_type("Node".intern()) {
            Some(Type::Interface(node_interface_id)) => {
                let node_interface = schema.interface(node_interface_id);
                let id_field = *node_interface
                    .fields
                    .iter()
                    .find(|&&id| schema.field(id).name.item == id_name)
                    .unwrap_or_else(|| {
                        panic!("Expected `Node` to contain a field named `{:}`.", id_name)
                    });

                Some(NodeInterface {
                    id: node_interface_id,
                    id_field,
                })
            }
            _ => None,
        };

        Self {
            program,
            id_name,
            node_interface,
            cache: Default::default(),
        }
    }

    fn has_unaliased_id_field(&self, selections: &[Selection]) -> bool {
        selections.iter().any(|x| match x {
            Selection::ScalarField(child) => {
                child.alias.is_none()
                    && self.program.schema.field(child.definition.item).name.item == self.id_name
            }
            _ => false,
        })
    }

    fn get_id_field_id(&mut self, type_: Type, fields: &[FieldID]) -> Option<FieldID> {
        match self.cache.entry(type_) {
            Entry::Occupied(e) => *e.get(),
            Entry::Vacant(e) => {
                for id in fields {
                    let field = &self.program.schema.field(*id);
                    if field.name.item == self.id_name
                        && self.program.schema.is_id(field.type_.inner())
                    {
                        let result = Some(*id);
                        e.insert(result);
                        return result;
                    }
                }
                e.insert(None);
                None
            }
        }
    }

    /// For interfaces and unions: generate a `... on Node { id }`
    /// fragment if *any* concrete type implements Node. Then generate a
    /// `... on PossibleType { id }` for every concrete type that does *not*
    /// implement `Node`
    fn get_selections_with_inline_id_fragments(
        &mut self,
        field: &LinkedField,
        selections: TransformedValue<Vec<Selection>>,
        concrete_ids: &[ObjectID],
    ) -> TransformedValue<Vec<Selection>> {
        let mut next_selections = vec![];
        let mut should_generate_node = false;

        let mut concrete_types_with_id = Vec::new();
        for object_id in concrete_ids {
            let object = self.program.schema.object(*object_id);
            let implements_node = if let Some(ref node_interface) = self.node_interface {
                object
                    .interfaces
                    .iter()
                    .any(|&interface_id| interface_id == node_interface.id)
            } else {
                false
            };
            if implements_node {
                should_generate_node = true;
            } else if let Some(id_field_id) =
                self.get_id_field_id(Type::Object(*object_id), &object.fields)
            {
                concrete_types_with_id.push((*object_id, id_field_id))
            }
        }

        if concrete_types_with_id.len() > 1 {
            concrete_types_with_id.sort_unstable_by_key(|(concrete_type, _)| {
                self.program.schema.object(*concrete_type).name
            })
        }

        for (object_id, id_field_id) in concrete_types_with_id {
            next_selections.push(Selection::InlineFragment(self.create_inline_id_fragment(
                field.definition.location,
                Type::Object(object_id),
                id_field_id,
            )));
        }

        if next_selections.is_empty() && !should_generate_node {
            return selections;
        }

        let mut result = if let TransformedValue::Replace(selections) = selections {
            selections
        } else {
            field.selections.clone()
        };
        if should_generate_node {
            // This should not happen because we can only set
            // `should_generate_node` to true, if this is Some.
            let node_interface = self.node_interface.as_ref().unwrap();
            result.push(Selection::InlineFragment(self.create_inline_id_fragment(
                field.definition.location,
                Type::Interface(node_interface.id),
                node_interface.id_field,
            )));
        }
        result.extend(next_selections.into_iter());
        TransformedValue::Replace(result)
    }

    fn create_id_selection(&self, location: Location, id_field_id: FieldID) -> Selection {
        Selection::ScalarField(Arc::new(ScalarField {
            alias: None,
            definition: WithLocation::new(location, id_field_id),
            arguments: Default::default(),
            directives: Default::default(),
        }))
    }

    fn create_inline_id_fragment(
        &self,
        location: Location,
        type_: Type,
        id_field_id: FieldID,
    ) -> Arc<InlineFragment> {
        Arc::new(InlineFragment {
            type_condition: Some(type_),
            directives: Default::default(),
            selections: vec![self.create_id_selection(location, id_field_id)],
            spread_location: Location::generated(),
        })
    }
}


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

use common::ArgumentName;
use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::ConstantArgument;
use graphql_ir::ConstantValue;
use graphql_ir::OperationDefinition;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use graphql_ir::Value;
use graphql_syntax::OperationKind;
use intern::string_key::Intern;
use lazy_static::lazy_static;
use thiserror::Error;

use crate::create_metadata_directive;

lazy_static! {
    static ref LIVE_QUERY_DIRECTIVE_NAME: DirectiveName = DirectiveName("live_query".intern());
    static ref LIVE_METADATA_KEY: ArgumentName = ArgumentName("live".intern());
    static ref POLLING_INTERVAL_ARG: ArgumentName = ArgumentName("polling_interval".intern());
    static ref CONFIG_ID_ARG: ArgumentName = ArgumentName("config_id".intern());
}

pub fn generate_live_query_metadata(program: &Program) -> DiagnosticsResult<Program> {
    let mut transformer = GenerateLiveQueryMetadata::default();
    let next_program = transformer
        .transform_program(program)
        .replace_or_else(|| program.clone());

    if transformer.errors.is_empty() {
        Ok(next_program)
    } else {
        Err(transformer.errors)
    }
}

#[derive(Default)]
struct GenerateLiveQueryMetadata {
    errors: Vec<Diagnostic>,
}

impl Transformer for GenerateLiveQueryMetadata {
    const NAME: &'static str = "GenerateLiveQueryMetadata";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        match operation.kind {
            OperationKind::Query => {
                let live_query_directive = operation.directives.named(*LIVE_QUERY_DIRECTIVE_NAME);
                if let Some(live_query_directive) = live_query_directive {
                    let polling_interval =
                        live_query_directive.arguments.named(*POLLING_INTERVAL_ARG);
                    let config_id = live_query_directive.arguments.named(*CONFIG_ID_ARG);

                    if polling_interval.is_none() && config_id.is_none() {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::LiveQueryTransformMissingConfig {
                                query_name: operation.name.item,
                            },
                            live_query_directive.name.location,
                        ));
                        return Transformed::Keep;
                    }

                    let mut next_directives = operation.directives.clone();

                    // I'm porting JS logic here. Not sure if the case where
                    // polling_interval and config_id both provided handled correctly
                    if let Some(polling_interval) = polling_interval {
                        let poll_interval_value = match polling_interval.value.item {
                            Value::Constant(ConstantValue::Int(value)) => value,
                            _ => {
                                self.errors.push(Diagnostic::error(
                                    ValidationMessage::LiveQueryTransformInvalidPollingInterval {
                                        query_name: operation.name.item,
                                    },
                                    polling_interval.value.location,
                                ));
                                return Transformed::Keep;
                            }
                        };
                        next_directives.push(create_metadata_directive(
                            *LIVE_METADATA_KEY,
                            ConstantValue::Object(vec![ConstantArgument {
                                name: WithLocation::generated(*POLLING_INTERVAL_ARG),
                                value: WithLocation::generated(ConstantValue::Int(
                                    poll_interval_value,
                                )),
                            }]),
                        ));
                    } else if let Some(config_id) = config_id {
                        let config_id_value = match config_id.value.item.get_string_literal() {
                            Some(value) => value,
                            None => {
                                self.errors.push(Diagnostic::error(
                                    ValidationMessage::LiveQueryTransformInvalidConfigId {
                                        query_name: operation.name.item,
                                    },
                                    config_id.value.location,
                                ));
                                return Transformed::Keep;
                            }
                        };
                        next_directives.push(create_metadata_directive(
                            *LIVE_METADATA_KEY,
                            ConstantValue::Object(vec![ConstantArgument {
                                name: WithLocation::generated(*CONFIG_ID_ARG),
                                value: WithLocation::generated(ConstantValue::String(
                                    config_id_value,
                                )),
                            }]),
                        ));
                    }

                    Transformed::Replace(OperationDefinition {
                        directives: next_directives,
                        ..operation.clone()
                    })
                } else {
                    Transformed::Keep
                }
            }
            _ => Transformed::Keep,
        }
    }
}

#[derive(Error, Debug)]
enum ValidationMessage {
    #[error(
        "Live query expects 'polling_interval' or 'config_id' as an argument to @live_query to for root field {query_name}"
    )]
    LiveQueryTransformMissingConfig { query_name: OperationDefinitionName },

    #[error(
        "Expected the 'polling_interval' argument to @live_query to be a literal number for root field {query_name}"
    )]
    LiveQueryTransformInvalidPollingInterval { query_name: OperationDefinitionName },

    #[error(
        "Expected the 'config_id' argument to @live_query to be a literal string for root field {query_name}"
    )]
    LiveQueryTransformInvalidConfigId { query_name: OperationDefinitionName },
}


/*
 * 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::NamedItem;
use common::WithLocation;
use docblock_shared::RELAY_RESOLVER_MODEL_DIRECTIVE_NAME;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;
use relay_config::SchemaConfig;
use schema::Schema;

lazy_static! {
    // Using a longer name version for this "special" field
    // help us avoid potential collision with product code (__self, __instance can be used for something else)
    static ref RESOLVER_MODEL_INSTANCE_FIELD_NAME: StringKey =
        "__relay_model_instance".intern();
}

pub fn generate_relay_resolvers_model_fragments(
    program: &Program,
    schema_config: &SchemaConfig,
) -> Program {
    let mut next_program = program.clone();
    for object in program.schema.get_objects() {
        if !object.is_extension {
            continue;
        }

        if object
            .directives
            .named(*RELAY_RESOLVER_MODEL_DIRECTIVE_NAME)
            .is_some()
        {
            let object_type = program.schema.get_type(object.name.item.0).unwrap();

            let model_instance_field_id = program
                .schema
                .named_field(object_type, *RESOLVER_MODEL_INSTANCE_FIELD_NAME)
                .unwrap_or_else(|| {
                    panic!(
                        "Objects with directive @{} expected to have field `{}`.",
                        *RELAY_RESOLVER_MODEL_DIRECTIVE_NAME, *RESOLVER_MODEL_INSTANCE_FIELD_NAME
                    )
                });

            let model_fragment_name = FragmentDefinitionName(
                format!(
                    "{}__{}",
                    object.name.item.0, *RESOLVER_MODEL_INSTANCE_FIELD_NAME
                )
                .intern(),
            );

            let fragment_definition = FragmentDefinition {
                name: WithLocation::new(object.name.location, model_fragment_name),
                variable_definitions: vec![],
                used_global_variables: vec![],
                type_condition: object_type,
                directives: vec![],
                selections: vec![Selection::ScalarField(Arc::new(ScalarField {
                    alias: None,
                    definition: WithLocation::generated(model_instance_field_id),
                    arguments: vec![],
                    directives: vec![],
                }))],
            };

            next_program.insert_fragment(Arc::new(fragment_definition));

            // For "strong" models (with `id` field) we will also
            // generate fragment with `id` field, so we can pass it to the Model
            // resolver function.
            if let Some(id_field_id) = program
                .schema
                .named_field(object_type, schema_config.node_interface_id_field)
            {
                let id_fragment_name = FragmentDefinitionName(
                    format!(
                        "{}__{}",
                        object.name.item.0, schema_config.node_interface_id_field
                    )
                    .intern(),
                );

                let id_fragment = FragmentDefinition {
                    name: WithLocation::new(object.name.location, id_fragment_name),
                    variable_definitions: vec![],
                    used_global_variables: vec![],
                    type_condition: object_type,
                    directives: vec![],
                    selections: vec![Selection::ScalarField(Arc::new(ScalarField {
                        alias: None,
                        definition: WithLocation::generated(id_field_id),
                        arguments: vec![],
                        directives: vec![],
                    }))],
                };

                next_program.insert_fragment(Arc::new(id_fragment));
            }
        }
    }

    next_program
}


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

use std::collections::HashSet;
use std::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::NamedItem;
use common::WithLocation;
use docblock_shared::HAS_OUTPUT_TYPE_ARGUMENT_NAME;
use docblock_shared::RELAY_RESOLVER_DIRECTIVE_NAME;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_syntax::OperationKind;
use intern::string_key::Intern;
use relay_config::SchemaConfig;
use schema::Field;
use schema::FieldID;
use schema::Interface;
use schema::InterfaceID;
use schema::ObjectID;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use crate::get_normalization_operation_name;
use crate::match_::RawResponseGenerationMode;
use crate::relay_resolvers::get_bool_argument_is_true;
use crate::SplitOperationMetadata;
use crate::ValidationMessage;

fn generate_fat_selections_from_type(
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    type_: Type,
    field: &Field,
) -> DiagnosticsResult<Vec<Selection>> {
    match type_ {
        Type::Object(object_id) => {
            let object = schema.object(object_id);
            if !object.is_extension {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::RelayResolverServerTypeNotSupported {
                        field_name: field.name.item,
                        type_name: schema.get_type_name(type_),
                    },
                    field.name.location,
                )]);
            }

            let mut parent_types = HashSet::from([type_]);
            generate_selections_from_object_fields(
                schema,
                schema_config,
                &object.fields,
                &mut parent_types,
            )
        }
        Type::Enum(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeUnsupported {
                type_kind: "enum".intern(),
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
        Type::Scalar(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeUnsupported {
                type_kind: "scalar".intern(),
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
        Type::InputObject(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeInvalidInputObjectType {
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
        Type::Interface(interface_id) => {
            let interface = schema.interface(interface_id);

            if !interface.is_extension {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::RelayResolverServerTypeNotSupported {
                        field_name: field.name.item,
                        type_name: schema.get_type_name(type_),
                    },
                    field.name.location,
                )]);
            }

            let mut parent_types = HashSet::new();
            parent_types.insert(type_);
            generate_selections_from_interface_fields(
                schema,
                schema_config,
                interface,
                &mut parent_types,
                field,
            )
        }
        Type::Union(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeUnsupported {
                type_kind: "union".intern(),
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
    }
}

fn generate_selections_from_object_fields(
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    field_ids: &[FieldID],
    parent_types: &mut HashSet<Type>,
) -> DiagnosticsResult<Vec<Selection>> {
    let mut errors = vec![];
    let mut selections = vec![];
    for field_id in field_ids {
        if schema
            .field(*field_id)
            .directives
            .named(*RELAY_RESOLVER_DIRECTIVE_NAME)
            .is_some()
        {
            continue;
        }
        match generate_selection_from_field(schema, schema_config, field_id, parent_types) {
            Ok(selection) => selections.push(selection),
            Err(err) => {
                errors.extend(err);
            }
        }
    }
    if errors.is_empty() {
        Ok(selections)
    } else {
        Err(errors)
    }
}

fn generate_selection_from_field(
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    field_id: &FieldID,
    parent_types: &mut HashSet<Type>,
) -> DiagnosticsResult<Selection> {
    let field = schema.field(*field_id);
    if field.name.item == schema_config.node_interface_id_field {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverIDFieldNotSupported {
                id_name: field.name.item,
            },
            field.name.location,
        )]);
    }

    if !field.arguments.is_empty() {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverArgumentsNotSupported,
            field.name.location,
        )]);
    }

    let type_ = field.type_.inner();
    match type_ {
        Type::Enum(_) => Ok(Selection::ScalarField(Arc::new(ScalarField {
            alias: None,
            definition: WithLocation::generated(*field_id),
            arguments: vec![],
            directives: vec![],
        }))),
        Type::Scalar(_) => Ok(Selection::ScalarField(Arc::new(ScalarField {
            alias: None,
            definition: WithLocation::generated(*field_id),
            arguments: vec![],
            directives: vec![],
        }))),
        Type::Object(object_id) => {
            let selections = generate_selections_from_object(
                object_id,
                parent_types,
                schema,
                schema_config,
                field,
            )?;
            Ok(Selection::LinkedField(Arc::new(LinkedField {
                alias: None,
                definition: WithLocation::generated(*field_id),
                arguments: vec![],
                directives: vec![],
                selections,
            })))
        }
        Type::InputObject(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeInvalidInputObjectType {
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
        Type::Union(_) => Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverOutputTypeUnsupported {
                type_kind: "union".intern(),
                field_name: field.name.item,
                type_name: schema.get_type_name(type_),
            },
            field.name.location,
        )]),
        Type::Interface(interface_id) => {
            let selections = generate_inline_fragments_from_interface(
                interface_id,
                parent_types,
                schema,
                schema_config,
                field,
            )?;

            Ok(Selection::LinkedField(Arc::new(LinkedField {
                alias: None,
                definition: WithLocation::generated(*field_id),
                arguments: vec![],
                directives: vec![],
                selections: selections
                    .into_iter()
                    .map(|inline_fragment| Selection::InlineFragment(Arc::new(inline_fragment)))
                    .collect::<Vec<_>>(),
            })))
        }
    }
}

fn generate_selections_from_object(
    object_id: ObjectID,
    parent_types: &mut HashSet<Type>,
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    field: &Field,
) -> Result<Vec<Selection>, Vec<Diagnostic>> {
    let object = schema.object(object_id);

    if !object.is_extension {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverServerTypeNotSupported {
                field_name: field.name.item,
                type_name: object.name.item.0,
            },
            field.name.location,
        )]);
    }

    let type_ = Type::Object(object_id);
    if parent_types.contains(&type_) {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverTypeRecursionDetected {
                type_name: object.name.item.0,
            },
            field.name.location,
        )]);
    }
    let selections = with_additional_parent_type(parent_types, type_, |parent_types| {
        generate_selections_from_object_fields(schema, schema_config, &object.fields, parent_types)
    })?;

    Ok(selections)
}

/// Unlike generate_selections_from_object, this returns a vector of inline fragments,
/// because an interface is transformed into a series of inline fragments, each of which
/// refines the interface to a distinct concrete type. All concrete types are accounted for.
fn generate_inline_fragments_from_interface(
    interface_id: InterfaceID,
    parent_types: &mut HashSet<Type>,
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    field: &Field,
) -> Result<Vec<InlineFragment>, Vec<Diagnostic>> {
    let interface = schema.interface(interface_id);

    if !interface.is_extension {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverServerTypeNotSupported {
                field_name: field.name.item,
                type_name: interface.name.item.0,
            },
            field.name.location,
        )]);
    }

    let type_ = Type::Interface(interface_id);
    if parent_types.contains(&type_) {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverTypeRecursionDetected {
                type_name: interface.name.item.0,
            },
            field.name.location,
        )]);
    }

    let selections = with_additional_parent_type(
        parent_types,
        type_,
        |parent_types: &mut HashSet<Type>| {
            let implementing_objects = interface.recursively_implementing_objects(schema);
            if implementing_objects.is_empty() {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::RelayResolverClientInterfaceMustBeImplemented {
                        interface_name: interface.name.item,
                    },
                    interface.name.location,
                )]);
            }

            let typename_field = schema.typename_field();
            let implementing_objects =
                get_implementing_objects_sorted(schema, implementing_objects);

            implementing_objects.into_iter()
                .map(|(object_id, object)| {
                    if !object.is_extension {
                        return Err(vec![Diagnostic::error(
                            ValidationMessage::RelayResolverClientInterfaceImplementingTypeMustBeClientTypes {
                                interface_name: interface.name.item,
                                object_name: object.name.item,
                            },
                            interface.name.location,
                        )]);
                    }

                    let mut selections = generate_selections_from_object(object_id, parent_types, schema, schema_config, field)?;
                    // The returned InlineFragment contains "fat" selections, i.e. selects every field
                    // defined on every concrete implementation. These InlineFragments are top-level selections
                    // on newly created "split" operations, and the $normalization type of these newly-created operations
                    // becomes the return type of user-defined resolver functions.
                    //
                    // For client types, all fields must be returned, since there is now way to fetch missing
                    // fields from the server. Hence, we create "fat" selections here.
                    //
                    // Note also that the fat selection on an interface must also include the __typename field,
                    // but that is not included in the return value of `generate_selections_from_object`. We
                    // add it here manually.
                    selections.push(Selection::ScalarField(Arc::new(ScalarField {
                        alias: None,
                        definition: WithLocation::generated(typename_field),
                        arguments: vec![],
                        directives: vec![],
                    })));

                    Ok(InlineFragment {
                        type_condition: Some(Type::Object(object_id)),
                        directives: vec![],
                        selections,
                        spread_location: Location::generated(),
                    })
                })
                .collect::<DiagnosticsResult<Vec<_>>>()
        },
    )?;

    Ok(selections)
}

fn generate_selections_from_interface_fields(
    schema: &SDLSchema,
    schema_config: &SchemaConfig,
    interface: &Interface,
    parent_types: &mut HashSet<Type>,
    field: &Field,
) -> DiagnosticsResult<Vec<Selection>> {
    let implementing_objects = interface.recursively_implementing_objects(schema);
    if implementing_objects.is_empty() {
        return Err(vec![Diagnostic::error(
            ValidationMessage::RelayResolverClientInterfaceMustBeImplemented {
                interface_name: interface.name.item,
            },
            interface.name.location,
        )]);
    }

    let implementing_objects = get_implementing_objects_sorted(schema, implementing_objects);
    let typename_field = schema.typename_field();

    implementing_objects
        .iter()
        .map(|(object_id, object)| {
            if !object.is_extension {
                return Err(vec![Diagnostic::error(
                    ValidationMessage::RelayResolverClientInterfaceImplementingTypeMustBeClientTypes {
                        interface_name: interface.name.item,
                        object_name: object.name.item,
                    },
                    interface.name.location,
                )]);
            }

            let mut selections = generate_selections_from_object(*object_id, parent_types, schema, schema_config, field)?;
            // The returned InlineFragment contains "fat" selections, i.e. selects every field
            // defined on every concrete implementation. These InlineFragments are top-level selections
            // on newly created "split" operations, and the $normalization type of these newly-created operations
            // becomes the return type of user-defined resolver functions.
            //
            // For client types, all fields must be returned, since there is now way to fetch missing
            // fields from the server. Hence, we create "fat" selections here.
            //
            // Note also that the fat selection on an interface must also include the __typename field,
            // but that is not included in the return value of `generate_selections_from_object`. We
            // add it here manually.
            selections.push(Selection::ScalarField(Arc::new(ScalarField {
                alias: None,
                definition: WithLocation::generated(typename_field),
                arguments: vec![],
                directives: vec![],
            })));

            Ok(Selection::InlineFragment(Arc::new(InlineFragment {
                type_condition: Some(Type::Object(*object_id)),
                directives: vec![],
                selections,
                spread_location: Location::generated(),
            })))
        })
        .collect::<DiagnosticsResult<Vec<_>>>()
}

pub(crate) fn generate_name_for_nested_object_operation(
    schema: &SDLSchema,
    field: &Field,
) -> WithLocation<OperationDefinitionName> {
    let parent_type = field
        .parent_type
        .unwrap_or_else(|| panic!("Expected parent type for field {:?}.", field));

    let normalization_name = get_normalization_operation_name(
        format!("{}__{}", schema.get_type_name(parent_type), field.name.item).intern(),
    )
    .intern();

    field
        .name
        .map(|_| OperationDefinitionName(normalization_name))
}

pub fn generate_relay_resolvers_operations_for_nested_objects(
    program: &Program,
    schema_config: &SchemaConfig,
) -> DiagnosticsResult<Program> {
    let mut operations = vec![];
    let mut errors = vec![];
    for field in program.schema.get_fields() {
        if !field.is_extension {
            continue;
        }

        if let Some(directive) = field.directives.named(*RELAY_RESOLVER_DIRECTIVE_NAME) {
            let has_output_type =
                get_bool_argument_is_true(&directive.arguments, *HAS_OUTPUT_TYPE_ARGUMENT_NAME);
            if !has_output_type {
                continue;
            }

            // Allow scalar/enums as @outputType
            if field.type_.inner().is_scalar() || field.type_.inner().is_enum() {
                continue;
            }

            let selections = match generate_fat_selections_from_type(
                &program.schema,
                schema_config,
                field.type_.inner(),
                field,
            ) {
                Ok(selections) => selections,
                Err(err) => {
                    errors.extend(err);
                    vec![]
                }
            };

            if !errors.is_empty() {
                continue;
            }

            let operation_name = generate_name_for_nested_object_operation(&program.schema, field);

            let parent_documents = {
                let mut parent_documents = HashSet::default();
                parent_documents.insert(operation_name.item.into());
                parent_documents
            };

            let directives = vec![
                SplitOperationMetadata {
                    location: field.name.location,
                    parent_documents,
                    derived_from: None,
                    raw_response_type_generation_mode: Some(
                        RawResponseGenerationMode::AllFieldsRequired,
                    ),
                }
                .into(),
            ];

            let operation = OperationDefinition {
                name: operation_name,
                type_: field.type_.inner(),
                variable_definitions: vec![],
                directives,
                selections,
                kind: OperationKind::Query,
            };

            operations.push(Arc::new(operation))
        }
    }

    if errors.is_empty() {
        if operations.is_empty() {
            Ok(program.clone())
        } else {
            let mut next_program = program.clone();

            for operation in operations {
                next_program.insert_operation(operation)
            }

            Ok(next_program)
        }
    } else {
        Err(errors)
    }
}

/// A wrapper function that allows us to avoid the pattern
///
/// parent_types.insert(t);
/// // do some work
/// parent_types.remove(t);
///
/// If there is an early return in the do some work section (e.g. a return or a ?),
/// then we may never remove `t` from `parent_types`, which may cause logic errors.
fn with_additional_parent_type<T>(
    parent_types: &mut HashSet<Type>,
    additional_parent_type: Type,
    f: impl FnOnce(&mut HashSet<Type>) -> T,
) -> T {
    let len = parent_types.len();
    assert!(
        !parent_types.contains(&additional_parent_type),
        "parent_types already contains {:?}",
        additional_parent_type
    );

    parent_types.insert(additional_parent_type);
    let t = f(parent_types);

    let successfully_removed = parent_types.remove(&additional_parent_type);
    assert!(
        successfully_removed,
        "parent_types unexpectedly did not contain {:?}",
        additional_parent_type
    );
    assert!(
        parent_types.len() == len,
        "parent_types changed length unexpectedly"
    );

    t
}

fn get_implementing_objects_sorted(
    schema: &SDLSchema,
    implementing_objects: HashSet<ObjectID>,
) -> Vec<(ObjectID, &schema::Object)> {
    let mut implementing_objects: Vec<_> = implementing_objects
        .into_iter()
        .map(|object_id| (object_id, schema.object(object_id)))
        .collect();

    // The inline fragments below were being generated in what appears to be a random order
    // that changed after a rebase. Sort the objects to guarantee a consistent ordering
    // and stable artifacts.
    implementing_objects.sort_by(|(_, first_object), (_, second_object)| {
        first_object.name.item.cmp(&second_object.name.item)
    });

    implementing_objects
}


/*
 * 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::DirectiveName;
use common::Location;
use common::PointerAddress;
use common::WithLocation;
use fnv::FnvHashMap;
use graphql_ir::Directive;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::TransformedValue;
use graphql_ir::Transformer;
use intern::string_key::Intern;
use lazy_static::lazy_static;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use crate::util::generate_abstract_type_refinement_key;
use crate::util::is_relay_custom_inline_fragment_directive;

lazy_static! {
    pub static ref TYPE_DISCRIMINATOR_DIRECTIVE_NAME: DirectiveName =
        DirectiveName("__TypeDiscriminator".intern());
}

/// Transform to add the `__typename` field to any LinkedField that both a) returns an
/// abstract type and b) does not already directly query `__typename`.
pub fn generate_typename(program: &Program, is_for_codegen: bool) -> Program {
    let mut transform = GenerateTypenameTransform::new(program, is_for_codegen);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

// Note on correctness: the PointerAddress here is calculated from addresses of the input
// context. Because those value are still referenced, that memory cannot be freed/
// reused for the lifetime of the transform.
type Seen = FnvHashMap<PointerAddress, Transformed<Selection>>;

struct GenerateTypenameTransform<'s> {
    program: &'s Program,
    seen: Seen,
    is_for_codegen: bool,
    parent_type: Option<Type>,
}

impl<'s> GenerateTypenameTransform<'s> {
    fn new(program: &'s Program, is_for_codegen: bool) -> Self {
        Self {
            program,
            seen: Default::default(),
            is_for_codegen,
            parent_type: None,
        }
    }
}

impl<'s> Transformer for GenerateTypenameTransform<'s> {
    const NAME: &'static str = "GenerateTypenameTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        self.parent_type = Some(operation.type_);
        self.default_transform_operation(operation)
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        self.parent_type = Some(fragment.type_condition);
        let schema = &self.program.schema;
        let mut selections = self.transform_selections(&fragment.selections);
        let type_ = fragment.type_condition;
        if !schema.is_extension_type(type_) && type_.is_abstract_type() {
            let mut next_selections = Vec::with_capacity(fragment.selections.len() + 1);
            next_selections.push(generate_abstract_key_field(
                schema,
                type_,
                fragment.name.location,
                self.is_for_codegen,
            ));
            if let TransformedValue::Replace(selections) = selections {
                next_selections.extend(selections.into_iter())
            } else {
                next_selections.extend(fragment.selections.iter().cloned())
            };
            selections = TransformedValue::Replace(next_selections);
        }
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => Transformed::Replace(FragmentDefinition {
                selections,
                ..fragment.clone()
            }),
        }
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        let schema = &self.program.schema;
        let field_definition = schema.field(field.definition.item);
        let parent_type = self.parent_type;
        self.parent_type = Some(field_definition.type_.inner());
        let selections = self.transform_selections(&field.selections);
        self.parent_type = parent_type;
        let is_abstract = field_definition.type_.inner().is_abstract_type();
        let selections = if is_abstract && !has_typename_field(schema, &field.selections) {
            let mut next_selections = Vec::with_capacity(field.selections.len() + 1);
            next_selections.push(Selection::ScalarField(Arc::new(ScalarField {
                alias: None,
                definition: WithLocation::new(field.definition.location, schema.typename_field()),
                arguments: Default::default(),
                directives: Default::default(),
            })));
            if let TransformedValue::Replace(selections) = selections {
                next_selections.extend(selections.into_iter())
            } else {
                next_selections.extend(field.selections.iter().cloned());
            }
            TransformedValue::Replace(next_selections)
        } else {
            selections
        };
        match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
                    alias: field.alias,
                    definition: field.definition,
                    arguments: field.arguments.clone(),
                    directives: field.directives.clone(),
                    selections,
                })))
            }
        }
    }

    fn transform_inline_fragment(&mut self, fragment: &InlineFragment) -> Transformed<Selection> {
        let key = PointerAddress::new(fragment);
        if let Some(prev) = self.seen.get(&key) {
            return prev.clone();
        }
        self.seen.insert(key, Transformed::Delete);
        let parent_type = self.parent_type;
        if fragment.type_condition.is_some() {
            self.parent_type = fragment.type_condition;
        }
        let mut selections = self.transform_selections(&fragment.selections);
        self.parent_type = parent_type;
        let schema = &self.program.schema;
        let type_ = if let Some(type_) = fragment.type_condition {
            type_
        } else {
            parent_type.expect("Expect the parent type to exist.")
        };
        if !fragment
            .directives
            .iter()
            .any(is_relay_custom_inline_fragment_directive)
            && !schema.is_extension_type(type_)
            && type_.is_abstract_type()
        {
            let mut next_selections = Vec::with_capacity(fragment.selections.len() + 1);
            next_selections.push(generate_abstract_key_field(
                schema,
                type_,
                Location::generated(),
                self.is_for_codegen,
            ));
            if let TransformedValue::Replace(selections) = selections {
                next_selections.extend(selections.into_iter())
            } else {
                next_selections.extend(fragment.selections.iter().cloned())
            };
            selections = TransformedValue::Replace(next_selections);
        }
        let result = match selections {
            TransformedValue::Keep => Transformed::Keep,
            TransformedValue::Replace(selections) => {
                Transformed::Replace(Selection::InlineFragment(Arc::new(InlineFragment {
                    type_condition: fragment.type_condition,
                    directives: fragment.directives.clone(),
                    selections,
                    spread_location: fragment.spread_location,
                })))
            }
        };
        self.seen.insert(key, result.clone());
        result
    }

    fn transform_scalar_field(&mut self, _field: &ScalarField) -> Transformed<Selection> {
        Transformed::Keep
    }

    fn transform_fragment_spread(&mut self, _spread: &FragmentSpread) -> Transformed<Selection> {
        Transformed::Keep
    }
}

fn has_typename_field(schema: &SDLSchema, selections: &[Selection]) -> bool {
    let typename_field = schema.typename_field();
    selections.iter().any(|x| match x {
        Selection::ScalarField(child) => {
            child.alias.is_none() && child.definition.item == typename_field
        }
        _ => false,
    })
}

fn generate_abstract_key_field(
    schema: &SDLSchema,
    type_: Type,
    location: Location,
    is_for_codegen: bool,
) -> Selection {
    let abstract_key = generate_abstract_type_refinement_key(schema, type_);
    Selection::ScalarField(Arc::new(ScalarField {
        alias: Some(WithLocation::new(location, abstract_key)),
        definition: WithLocation::new(location, schema.typename_field()),
        arguments: vec![],
        directives: if is_for_codegen {
            vec![Directive {
                name: WithLocation::new(location, *TYPE_DISCRIMINATOR_DIRECTIVE_NAME),
                arguments: vec![],
                data: None,
            }]
        } else {
            vec![]
        },
    }))
}