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

use std::fmt;

use graphql_ir::Argument;
use graphql_ir::ConstantValue;
use graphql_ir::Value;
use intern::Lookup;

use crate::murmurhash::murmurhash;

pub fn hash_arguments(args: &[Argument]) -> Option<String> {
    if args.is_empty() {
        None
    } else {
        let mut converted_args: Vec<_> = args
            .iter()
            .map(|arg| HashArgument {
                name: arg.name.item.0.lookup(),
                value: identifier_for_argument_value(&arg.value.item),
            })
            .collect();
        converted_args.sort_by(|a, b| a.name.cmp(b.name));

        let args_string = format!(
            "[{}]",
            converted_args
                .iter()
                .map(|item| item.to_string())
                .collect::<Vec<String>>()
                .join(",")
        );
        Some(murmurhash(&args_string))
    }
}

#[derive(Debug)]
struct HashArgument {
    name: &'static str,
    value: IdentiferValue,
}

impl fmt::Display for HashArgument {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{{\"name\":\"{}\",\"value\":{}}}", self.name, self.value)
    }
}

#[derive(Debug)]
enum IdentiferValue {
    Variable(&'static str),
    Value(String),
    List(Vec<IdentiferValue>),
    Object(Vec<IdentiferObjectProperty>),
}

impl fmt::Display for IdentiferValue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self {
            IdentiferValue::Variable(var) => write!(f, "{{\"variable\":\"{}\"}}", var),
            IdentiferValue::Value(val) => write!(f, "{{\"value\":{}}}", val),
            IdentiferValue::Object(obj) => write!(
                f,
                "{{\"object\":{{{}}}}}",
                obj.iter()
                    .map(|item| item.to_string())
                    .collect::<Vec<String>>()
                    .join(",")
            ),
            IdentiferValue::List(list) => write!(
                f,
                "{{\"list\":[{}]}}",
                list.iter()
                    .map(|item| item.to_string())
                    .collect::<Vec<String>>()
                    .join(",")
            ),
        }
    }
}

#[derive(Debug)]
struct IdentiferObjectProperty {
    name: &'static str,
    value: IdentiferValue,
}

impl fmt::Display for IdentiferObjectProperty {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{{\"name\":\"{}\",\"value\":{}}}", self.name, self.value)
    }
}

fn build_constant_value_string(value: &ConstantValue) -> String {
    match value {
        ConstantValue::Int(val) => val.to_string(),
        ConstantValue::Float(val) => val.to_string(),
        ConstantValue::String(val) => format!("\"{}\"", val),
        ConstantValue::Boolean(val) => val.to_string(),
        ConstantValue::Null() => "null".to_string(),
        ConstantValue::Enum(val) => format!("\"{}\"", val),
        ConstantValue::List(val_list) => {
            let json_values = val_list
                .iter()
                .map(|val| build_constant_value_string(val))
                .collect::<Vec<_>>();

            format!("[{}]", json_values.join(","))
        }
        ConstantValue::Object(val_object) => {
            let mut rows: Vec<String> = Vec::with_capacity(val_object.len());
            for arg in val_object.iter() {
                let field_name = arg.name.item;
                rows.push(format!(
                    "\"{}\":{}",
                    field_name,
                    build_constant_value_string(&arg.value.item)
                ));
            }
            format!("{{{}}}", rows.join(","))
        }
    }
}

fn identifier_for_argument_value(value: &Value) -> IdentiferValue {
    match value {
        Value::Constant(value) => IdentiferValue::Value(build_constant_value_string(value)),
        Value::Variable(variable) => IdentiferValue::Variable(variable.name.item.0.lookup()),
        Value::List(items) => {
            IdentiferValue::List(items.iter().map(identifier_for_argument_value).collect())
        }
        Value::Object(entries) => IdentiferValue::Object(
            entries
                .iter()
                .map(|entry| IdentiferObjectProperty {
                    name: entry.name.item.0.lookup(),
                    value: identifier_for_argument_value(&entry.value.item),
                })
                .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::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::Location;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::associated_data_impl;
use graphql_ir::Argument;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::Program;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use graphql_ir::VariableDefinition;
use intern::string_key::Intern;
use once_cell::sync::Lazy;
use thiserror::Error;

pub fn inline_data_fragment(program: &Program) -> DiagnosticsResult<Program> {
    let mut transform = InlineDataFragmentsTransform::new(program);
    let next_program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

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

pub const INLINE_DIRECTIVE_NAME: Lazy<DirectiveName> =
    Lazy::new(|| DirectiveName("inline".intern()));

struct InlineDataFragmentsTransform<'s> {
    program: &'s Program,
    errors: Vec<Diagnostic>,
    parent_inline_fragments: Vec<WithLocation<FragmentDefinitionName>>,
}

impl<'s> InlineDataFragmentsTransform<'s> {
    fn new(program: &'s Program) -> Self {
        Self {
            program,
            errors: Vec::new(),
            parent_inline_fragments: Vec::new(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct InlineDirectiveMetadata {
    pub fragment_name: FragmentDefinitionName,
    pub arguments: Vec<Argument>,
    pub variable_definitions: Vec<VariableDefinition>,
    pub used_global_variables: Vec<VariableDefinition>,
}
associated_data_impl!(InlineDirectiveMetadata);

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

    fn transform_fragment_spread(&mut self, spread: &FragmentSpread) -> Transformed<Selection> {
        let next_fragment_spread = self.default_transform_fragment_spread(spread);
        let fragment = self
            .program
            .fragment(spread.fragment.item)
            .unwrap_or_else(|| panic!("was expecting to find fragment `{}`", spread.fragment.item));

        if fragment.directives.named(*INLINE_DIRECTIVE_NAME).is_none() {
            next_fragment_spread
        } else {
            match &next_fragment_spread {
                Transformed::Keep => {
                    if !spread.directives.is_empty() {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::InlineDataFragmentDirectivesNotSupported,
                            spread.fragment.location,
                        ));
                    }
                }
                Transformed::Replace(Selection::FragmentSpread(next_fragment_spread)) => {
                    if !next_fragment_spread.directives.is_empty() {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::InlineDataFragmentDirectivesNotSupported,
                            next_fragment_spread.fragment.location,
                        ));
                    }
                }
                _ => {
                    panic!(
                        "InlineDataFragmentsTransform: Unexpected deletion during fragment spread transformation."
                    );
                }
            };

            if self
                .parent_inline_fragments
                .iter()
                .any(|name| name.item == fragment.name.item)
            {
                let mut cyclic_fragments = self.parent_inline_fragments.iter();
                let first = cyclic_fragments.next().unwrap();
                let mut diagnostic = Diagnostic::error(
                    ValidationMessage::CircularFragmentReference {
                        fragment_name: first.item,
                    },
                    first.location,
                );
                for spread in cyclic_fragments {
                    diagnostic =
                        diagnostic.annotate(format!("spreading {}", spread.item), spread.location);
                }
                self.errors.push(diagnostic);
                return Transformed::Keep;
            }
            self.parent_inline_fragments.push(spread.fragment);
            let transformed_fragment = self.default_transform_fragment(fragment);
            self.parent_inline_fragments.pop();

            let (name, selections) = match transformed_fragment {
                Transformed::Keep => (fragment.name.item, fragment.selections.clone()),
                Transformed::Replace(next_fragment) => {
                    (next_fragment.name.item, next_fragment.selections)
                }
                Transformed::Delete => {
                    panic!(
                        "InlineDataFragmentsTransform: Unexpected deletion during fragment spread transformation."
                    );
                }
            };

            let inline_fragment = InlineFragment {
                type_condition: None,
                directives: vec![
                    InlineDirectiveMetadata {
                        fragment_name: name,
                        arguments: spread.arguments.clone(),
                        variable_definitions: fragment.variable_definitions.clone(),
                        used_global_variables: fragment.used_global_variables.clone(),
                    }
                    .into(),
                ],
                selections: vec![Selection::InlineFragment(Arc::new(InlineFragment {
                    type_condition: Some(fragment.type_condition),
                    directives: vec![],
                    selections,
                    spread_location: Location::generated(),
                }))],
                spread_location: Location::generated(),
            };

            Transformed::Replace(Selection::InlineFragment(Arc::new(inline_fragment)))
        }
    }
}

#[derive(Error, Debug)]
enum ValidationMessage {
    #[error("Found a circular reference from fragment '{fragment_name}'.")]
    CircularFragmentReference {
        fragment_name: FragmentDefinitionName,
    },

    #[error("Directives on fragment spreads for @inline fragments are not yet supported")]
    InlineDataFragmentDirectivesNotSupported,
}


/*
 * 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::hash::Hash;
use std::sync::Arc;

use common::Location;
use fnv::FnvHashMap;
use graphql_ir::node_identifier::LocationAgnosticHash;
use graphql_ir::node_identifier::LocationAgnosticPartialEq;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;

use crate::NoInlineFragmentSpreadMetadata;
use crate::RelayLocationAgnosticBehavior;

/// Expand fragment spreads into inline fragments containing the named
/// fragment's directives and selections. Used for constructing a Normalization
/// AST that contains all the selections that may be found in the query response.
pub fn inline_fragments(program: &Program) -> Program {
    let mut transform = InlineFragmentsTransform::new(program);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

#[derive(Eq, Clone, Debug)]
struct FragmentSpreadKey(Arc<FragmentSpread>);
type Seen = FnvHashMap<FragmentSpreadKey, Arc<InlineFragment>>;

impl PartialEq for FragmentSpreadKey {
    fn eq(&self, other: &Self) -> bool {
        self.0.fragment.item == other.0.fragment.item
            && self
                .0
                .directives
                .location_agnostic_eq::<RelayLocationAgnosticBehavior>(&other.0.directives)
    }
}

impl Hash for FragmentSpreadKey {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        self.0.fragment.item.hash(state);
        self.0
            .directives
            .location_agnostic_hash::<_, RelayLocationAgnosticBehavior>(state);
    }
}

struct InlineFragmentsTransform<'s> {
    program: &'s Program,
    seen: Seen,
}

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

    fn transform_fragment_spread(&mut self, spread: &Arc<FragmentSpread>) -> Arc<InlineFragment> {
        let key = FragmentSpreadKey(Arc::clone(spread));
        // If we've already created an InlineFragment for this fragment name before,
        // share it
        if let Some(prev) = self.seen.get(&key) {
            return Arc::clone(prev);
        };
        // Otherwise create the InlineFragment equivalent of the fragment (recursively
        // inlining its contents). To guard against cycles, store a dummy value
        // that we overwrite once we finish.
        self.seen.insert(
            key.clone(),
            Arc::new(InlineFragment {
                type_condition: None,
                directives: Default::default(),
                selections: Default::default(),
                spread_location: Location::generated(),
            }),
        );
        let fragment = self
            .program
            .fragment(spread.fragment.item)
            .unwrap_or_else(|| {
                panic!(
                    "Fragment spread unable to resolve fragment `{}`.",
                    spread.fragment.item
                )
            });
        let selections = self.transform_selections(&fragment.selections);
        let result = Arc::new(InlineFragment {
            type_condition: Some(fragment.type_condition),
            directives: spread.directives.clone(),
            selections: selections.replace_or_else(|| fragment.selections.clone()),
            spread_location: Location::generated(),
        });
        self.seen.insert(key, Arc::clone(&result));
        result
    }
}

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

    fn transform_fragment(
        &mut self,
        _fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        Transformed::Delete
    }

    fn transform_selection(&mut self, selection: &Selection) -> Transformed<Selection> {
        match selection {
            Selection::FragmentSpread(selection) => {
                let should_skip_inline = selection.directives.iter().any(|directive| {
                    directive.name.item == NoInlineFragmentSpreadMetadata::directive_name()
                });
                if should_skip_inline {
                    Transformed::Keep
                } else {
                    Transformed::Replace(Selection::InlineFragment(
                        self.transform_fragment_spread(selection),
                    ))
                }
            }
            _ => self.default_transform_selection(selection),
        }
    }

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


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

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

mod applied_fragment_name;
mod apply_custom_transforms;
mod apply_fragment_arguments;
mod apply_transforms;
mod assignable_fragment_spread;
mod client_edges;
mod client_extensions;
mod client_extensions_abstract_types;
mod connections;
mod declarative_connection;
mod defer_stream;
mod directive_finder;
mod errors;
mod flatten;
mod fragment_alias_directive;
mod generate_data_driven_dependency_metadata;
mod generate_id_field;
mod generate_live_query_metadata;
mod generate_relay_resolvers_model_fragments;
mod generate_relay_resolvers_operations_for_nested_objects;
mod generate_typename;
mod handle_fields;
mod hash_arguments;
mod inline_data_fragment;
mod inline_fragments;
mod mask;
mod match_;
mod metadata_directive;
mod murmurhash;
mod no_inline;
mod preloadable_directive;
mod provided_variable_fragment_transform;
mod refetchable_fragment;
mod relay_actor_change;
mod relay_directive;
mod relay_node_identifier;
mod relay_resolvers;
mod remove_base_fragments;
mod required_directive;
mod root_variables;
mod skip_client_directives;
mod skip_client_extensions;
mod skip_null_arguments_transform;
mod skip_redundant_nodes;
mod skip_split_operation;
mod skip_unreachable_node;
mod skip_updatable_queries;
mod sort_selections;
mod test_operation_metadata;
mod transform_connections;
mod unwrap_custom_directive_selection;
mod util;
mod validate_operation_variables;
mod validations;

use std::collections::HashMap;
use std::collections::HashSet;

use intern::string_key::StringKey;
use intern::BuildIdHasher;
pub use metadata_directive::create_metadata_directive;
pub use metadata_directive::INTERNAL_METADATA_DIRECTIVE;

/// Name of an executable operation
type OperationName = StringKey;

// NOTE: Types are based on intern::string_key::{StringKeyMap, StringKeySet}
pub type DependencyMap = HashMap<OperationName, DependencySet, BuildIdHasher<u32>>;
pub type DependencySet = HashSet<OperationName, BuildIdHasher<u32>>;

pub use applied_fragment_name::get_applied_fragment_name;
pub use apply_custom_transforms::BaseFragmentNames;
pub use apply_custom_transforms::CustomTransform;
pub use apply_custom_transforms::CustomTransforms;
pub use apply_custom_transforms::CustomTransformsConfig;
pub use apply_fragment_arguments::apply_fragment_arguments;
pub use apply_fragment_arguments::NoInlineFragmentSpreadMetadata;
pub use apply_transforms::apply_transforms;
pub use apply_transforms::Programs;
pub use assignable_fragment_spread::transform_assignable_fragment_spreads_in_regular_queries;
pub use assignable_fragment_spread::transform_assignable_fragment_spreads_in_updatable_queries;
pub use assignable_fragment_spread::validate_assignable_directive;
pub use assignable_fragment_spread::validate_updatable_directive;
pub use assignable_fragment_spread::validate_updatable_fragment_spread;
pub use assignable_fragment_spread::TypeConditionInfo;
pub use assignable_fragment_spread::ASSIGNABLE_DIRECTIVE;
pub use assignable_fragment_spread::ASSIGNABLE_DIRECTIVE_FOR_TYPEGEN;
pub use assignable_fragment_spread::UPDATABLE_DIRECTIVE;
pub use assignable_fragment_spread::UPDATABLE_DIRECTIVE_FOR_TYPEGEN;
pub use client_edges::client_edges;
pub use client_edges::remove_client_edge_selections;
pub use client_edges::ClientEdgeGeneratedQueryMetadataDirective;
pub use client_edges::ClientEdgeMetadata;
pub use client_edges::ClientEdgeMetadataDirective;
pub use client_edges::CLIENT_EDGE_SOURCE_NAME;
pub use client_edges::CLIENT_EDGE_WATERFALL_DIRECTIVE_NAME;
pub use client_extensions::client_extensions;
pub use client_extensions::CLIENT_EXTENSION_DIRECTIVE_NAME;
pub use client_extensions_abstract_types::client_extensions_abstract_types;
pub use client_extensions_abstract_types::ClientExtensionAbstractTypeMetadataDirective;
pub use connections::extract_connection_metadata_from_directive;
pub use connections::ConnectionConstants;
pub use connections::ConnectionInterface;
pub use connections::ConnectionMetadata;
pub use declarative_connection::transform_declarative_connection;
pub use defer_stream::transform_defer_stream;
pub use defer_stream::DeferDirective;
pub use defer_stream::StreamDirective;
pub use defer_stream::DEFER_STREAM_CONSTANTS;
pub use directive_finder::DirectiveFinder;
pub use flatten::flatten;
pub use fragment_alias_directive::fragment_alias_directive;
pub use fragment_alias_directive::FragmentAliasMetadata;
pub use generate_data_driven_dependency_metadata::generate_data_driven_dependency_metadata;
pub use generate_data_driven_dependency_metadata::RelayDataDrivenDependencyMetadata;
pub use generate_id_field::generate_id_field;
pub use generate_live_query_metadata::generate_live_query_metadata;
pub use generate_relay_resolvers_operations_for_nested_objects::generate_relay_resolvers_operations_for_nested_objects;
pub use generate_typename::generate_typename;
pub use generate_typename::TYPE_DISCRIMINATOR_DIRECTIVE_NAME;
pub use handle_fields::extract_handle_field_directives;
pub use handle_fields::extract_values_from_handle_field_directive;
pub use handle_fields::handle_field_transform;
pub use hash_arguments::hash_arguments;
pub use inline_data_fragment::inline_data_fragment;
pub use inline_data_fragment::InlineDirectiveMetadata;
pub use inline_data_fragment::INLINE_DIRECTIVE_NAME;
pub use inline_fragments::inline_fragments;
pub use mask::mask;
pub use match_::split_module_import;
pub use match_::transform_match;
pub use match_::transform_subscriptions;
pub use match_::ModuleMetadata;
pub use match_::RawResponseGenerationMode;
pub use match_::SplitOperationMetadata;
pub use match_::DIRECTIVE_SPLIT_OPERATION;
pub use match_::MATCH_CONSTANTS;
pub use no_inline::NO_INLINE_DIRECTIVE_NAME;
pub use preloadable_directive::is_operation_preloadable;
pub use preloadable_directive::should_generate_hack_preloader;
pub use provided_variable_fragment_transform::provided_variable_fragment_transform;
pub use refetchable_fragment::transform_refetchable_fragment;
pub use refetchable_fragment::RefetchableDerivedFromMetadata;
pub use refetchable_fragment::RefetchableMetadata;
pub use refetchable_fragment::CONSTANTS as REFETCHABLE_CONSTANTS;
pub use refetchable_fragment::REFETCHABLE_NAME;
pub use relay_actor_change::relay_actor_change_transform;
pub use relay_actor_change::RELAY_ACTOR_CHANGE_DIRECTIVE_FOR_CODEGEN;
pub use relay_directive::RelayDirective;
pub use relay_node_identifier::RelayLocationAgnosticBehavior;
pub use relay_resolvers::get_resolver_fragment_dependency_name;
pub use relay_resolvers::relay_resolvers;
pub use relay_resolvers::FragmentDataInjectionMode;
pub use relay_resolvers::RelayResolverMetadata;
pub use relay_resolvers::ResolverOutputTypeInfo;
pub use remove_base_fragments::remove_base_fragments;
pub use required_directive::required_directive;
pub use required_directive::RequiredAction;
pub use required_directive::RequiredMetadataDirective;
pub use required_directive::ACTION_ARGUMENT;
pub use required_directive::CHILDREN_CAN_BUBBLE_METADATA_KEY;
pub use required_directive::REQUIRED_DIRECTIVE_NAME;
pub use skip_client_directives::skip_client_directives;
pub use skip_client_extensions::skip_client_extensions;
pub use skip_null_arguments_transform::skip_null_arguments_transform;
pub use skip_redundant_nodes::skip_redundant_nodes;
pub use skip_redundant_nodes::SkipRedundantNodesTransform;
pub use skip_split_operation::skip_split_operation;
pub use skip_unreachable_node::skip_unreachable_node_loose;
pub use skip_unreachable_node::skip_unreachable_node_strict;
pub use sort_selections::sort_selections;
pub use test_operation_metadata::generate_test_operation_metadata;
pub use transform_connections::transform_connections;
pub use unwrap_custom_directive_selection::unwrap_custom_directive_selection;
pub use util::extract_variable_name;
pub use util::generate_abstract_type_refinement_key;
pub use util::get_fragment_filename;
pub use util::get_normalization_operation_name;
pub use util::remove_directive;
pub use validate_operation_variables::validate_operation_variables;
pub use validations::*;

pub use crate::errors::ValidationMessage;
pub use crate::errors::ValidationMessageWithData;


/*
 * 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::ops::RangeFull;
use std::sync::Arc;

use common::Location;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use graphql_ir::VariableDefinition;
use graphql_ir::VariableName;
use indexmap::map::Entry;
use indexmap::IndexMap;
use schema::Schema;

use crate::relay_directive::RelayDirective;

/// Transform to inline fragment spreads with @relay(mask:false)
pub fn mask(program: &Program) -> Program {
    let mut transform = Mask::new(program);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

type JoinedArguments<'s> = IndexMap<VariableName, &'s VariableDefinition>;

struct Mask<'s> {
    program: &'s Program,
    current_reachable_arguments: Vec<&'s VariableDefinition>,
}

impl<'s> Mask<'s> {
    fn new(program: &'s Program) -> Self {
        Self {
            program,
            current_reachable_arguments: vec![],
        }
    }

    fn join_current_arguments_to_fragment(&mut self, fragment: &mut FragmentDefinition) {
        let mut joined_arguments = JoinedArguments::default();
        for variable in &fragment.used_global_variables {
            joined_arguments.insert(variable.name.item, variable);
        }
        for arg in self.current_reachable_arguments.drain(..) {
            match joined_arguments.entry(arg.name.item) {
                Entry::Vacant(entry) => {
                    entry.insert(arg);
                }
                Entry::Occupied(mut entry) => {
                    let prev_arg = entry.get();
                    if self
                        .program
                        .schema
                        .is_type_subtype_of(&arg.type_, &prev_arg.type_)
                    {
                        entry.insert(arg);
                    }
                }
            }
        }
        let range = RangeFull;
        fragment.used_global_variables = joined_arguments
            .drain(range)
            .map(|(_, v)| v)
            .cloned()
            .collect();
    }
}

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

    fn transform_operation(
        &mut self,
        operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        let result = self.default_transform_operation(operation);
        self.current_reachable_arguments.clear();
        result
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        let result = self.default_transform_fragment(fragment);
        if self.current_reachable_arguments.is_empty() {
            result
        } else {
            Transformed::Replace(match result {
                Transformed::Keep => {
                    let mut new_fragment = fragment.clone();
                    self.join_current_arguments_to_fragment(&mut new_fragment);
                    new_fragment
                }
                Transformed::Replace(mut new_fragment) => {
                    self.join_current_arguments_to_fragment(&mut new_fragment);
                    new_fragment
                }
                Transformed::Delete => {
                    panic!("Unexpected fragment deletion in mask transform.");
                }
            })
        }
    }

    fn transform_fragment_spread(&mut self, spread: &FragmentSpread) -> Transformed<Selection> {
        if RelayDirective::is_unmasked_fragment_spread(spread) {
            let fragment = self.program.fragment(spread.fragment.item).unwrap();
            self.current_reachable_arguments
                .extend(&fragment.used_global_variables);
            Transformed::Replace(Selection::InlineFragment(Arc::new(InlineFragment {
                type_condition: Some(fragment.type_condition),
                directives: vec![],
                selections: self
                    .transform_selections(&fragment.selections)
                    .replace_or_else(|| fragment.selections.to_vec()),
                spread_location: Location::generated(),
            })))
        } else {
            Transformed::Keep
        }
    }

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


/*
 * 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::DirectiveName;
use common::WithLocation;
use graphql_ir::Argument;
use graphql_ir::ConstantValue;
use graphql_ir::Directive;
use graphql_ir::Value;
use intern::string_key::Intern;
use lazy_static::lazy_static;

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

/// Creates a directive that is used during codegen to generate a "metadata"
/// key in the generated AST.
/// This "metadata" can be used for various purposes to transfer additional
/// information from a compile time transform to be available at runtime.
pub fn create_metadata_directive(key: ArgumentName, value: ConstantValue) -> Directive {
    Directive {
        name: WithLocation::generated(*INTERNAL_METADATA_DIRECTIVE),
        arguments: vec![Argument {
            name: WithLocation::generated(key),
            value: WithLocation::generated(Value::Constant(value)),
        }],
        data: None,
    }
}


/*
 * 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::ptr::copy_nonoverlapping;

const C1: u32 = 0xcc9e_2d51;
const C2: u32 = 0x1b87_3593;
const R1: u32 = 15;
const R2: u32 = 13;
const M: u32 = 5;
const N: u32 = 0xe654_6b64;
const CHARSET: [char; 62] = [
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
    'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
    'V', 'W', 'X', 'Y', 'Z',
];

// An implmentation of murmurHash.js of relay-compiler that produces the same output excepet for non-alphanumeric strings
// It does the 32bit murmurhash3 with seed 0, and applies a base62 to get the final string hash
pub fn murmurhash(data: &str) -> String {
    let bytes = data.as_bytes();
    let nbytes = bytes.len();
    let mut hash = 0; // Hardcoded seed 0
    let mut i = 0;

    let iterations = nbytes / 4;
    for _ in 0..iterations {
        hash = (hash ^ calculate_k(read_u32(&bytes[i..i + 4])))
            .rotate_left(R2)
            .wrapping_mul(M)
            .wrapping_add(N);
        i += 4;
    }

    match nbytes - i {
        1 => {
            hash ^= calculate_k(bytes[i] as u32);
        }
        2 => {
            hash ^= calculate_k((bytes[i] as u32) | ((bytes[i + 1] as u32) << 8));
        }
        3 => {
            hash ^= calculate_k(
                (bytes[i] as u32) | ((bytes[i + 1] as u32) << 8) | ((bytes[i + 2] as u32) << 16),
            );
        }
        _ => {}
    }

    hash ^= nbytes as u32;
    hash ^= hash.wrapping_shr(16);
    hash = hash.wrapping_mul(0x85eb_ca6b);
    hash ^= hash.wrapping_shr(13);
    hash = hash.wrapping_mul(0xc2b2_ae35);
    hash ^= hash.wrapping_shr(16);

    if hash == 0 {
        return "0".to_owned();
    }

    let mut chars = Vec::with_capacity(6);
    while hash > 0 {
        let d = hash % 62;
        chars.push(CHARSET[d as usize]);
        hash = (hash - d) / 62;
    }

    chars.iter().rev().collect()
}

// From byte_order::LittleEndian::read_u32
fn read_u32(src: &[u8]) -> u32 {
    let mut data: u32 = 0;
    unsafe {
        copy_nonoverlapping(src.as_ptr(), &mut data as *mut u32 as *mut u8, 4);
    }
    data.to_le()
}

fn calculate_k(k: u32) -> u32 {
    k.wrapping_mul(C1).rotate_left(R1).wrapping_mul(C2)
}

#[test]
fn test_murmurhash() {
    assert_eq!(murmurhash("{count: 20, start: 0, end: 5}"), "31sjku");
    assert_eq!(
        murmurhash("{arg: \"{arg: {count: 20, start: 0, end: 5}}\"}"),
        "3RGiWM"
    );
    assert_eq!(
        murmurhash(&str::repeat(
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
            40
        )),
        "3OKbT6"
    );
    assert_eq!(murmurhash("{}"), "2wIPj2");
    assert_eq!(murmurhash(""), "0");
}


/*
 * 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::DiagnosticsResult;
use common::DirectiveName;
use common::NamedItem;
use graphql_ir::Directive;
use graphql_ir::OperationDefinition;
use intern::intern;

/// Check, if the operation is @preloadable.
pub fn is_operation_preloadable(operation: &OperationDefinition) -> bool {
    find_preloadable_directive(operation).is_some()
}

/// Check if th the operation has a `@preloadable(hackPreloader: true)` directive
pub fn should_generate_hack_preloader(operation: &OperationDefinition) -> DiagnosticsResult<bool> {
    if let Some(directive) = find_preloadable_directive(operation) {
        if let Some(arg) = directive
            .arguments
            .named(ArgumentName(intern!("hackPreloader")))
        {
            return Ok(arg.value.item.expect_constant().unwrap_boolean());
        }
    }
    Ok(false)
}

fn find_preloadable_directive(operation: &OperationDefinition) -> Option<&Directive> {
    operation
        .directives
        .named(DirectiveName(intern!("preloadable")))
}


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

use std::cmp::Reverse;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::Location;
use common::Named;
use common::NamedItem;
use common::WithLocation;
use fnv::FnvHashMap;
use graphql_ir::FragmentDefinition;
use graphql_ir::Program;
use graphql_ir::ProvidedVariableMetadata;
use graphql_ir::Transformed;
use graphql_ir::TransformedValue;
use graphql_ir::Transformer;
use graphql_ir::Variable;
use graphql_ir::VariableDefinition;
use graphql_ir::VariableName;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use itertools::Itertools;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;
use schema::TypeReference;
use thiserror::Error;

use crate::util::format_provided_variable_name;

/// This transform applies provided variables in each fragment.
///  - Rename all uses of provided variables (in values)
///     [provided_variable_name] --> __pv__[module_name]
///  - Remove provided variables from (local) argument definitions
///  - Add provided variables to list of used global variables
/// apply_fragment_arguments depends on provide_variable_fragment_transform
pub fn provided_variable_fragment_transform(program: &Program) -> DiagnosticsResult<Program> {
    let mut transform = ProvidedVariableFragmentTransform::new(&*program.schema);
    let program = transform
        .transform_program(program)
        .replace_or_else(|| program.clone());

    let errors = transform.get_errors();
    if errors.is_empty() {
        Ok(program)
    } else {
        Err(errors)
    }
}

struct ProvidedVariableDefinitions {
    // Different (module name, variable type) tuples give conflicting
    //  provided variable definitions.
    // We need to keep track of usages under each definition for stable
    //  error reporting.
    usages_map: FnvHashMap<(StringKey, TypeReference<Type>), Vec<Location>>,
}

impl ProvidedVariableDefinitions {
    fn new() -> ProvidedVariableDefinitions {
        ProvidedVariableDefinitions {
            usages_map: Default::default(),
        }
    }
    fn add(&mut self, module_name: StringKey, variable_def: &VariableDefinition) {
        let usages = self
            .usages_map
            .entry((module_name, variable_def.type_.clone()))
            .or_insert_with(Vec::new);
        usages.push(variable_def.name.location);
    }

    fn get_errors(&self, schema: &SDLSchema, errors: &mut Vec<Diagnostic>) {
        if self.usages_map.len() > 1 {
            // The most frequently used definition is likely the intended one
            // Tie break by string ordering of module + type name
            let ((most_used_module, most_used_type), _) = self
                .usages_map
                .iter()
                .max_by_key(|((module, type_), usages)| {
                    (
                        usages.len(),
                        Reverse((module, schema.get_type_string(type_))),
                    )
                })
                .unwrap();

            for ((other_module, other_type), other_usages) in self.usages_map.iter() {
                if other_module != most_used_module {
                    for usage in other_usages {
                        errors.push(Diagnostic::error(
                            ValidationMessage::ProvidedVariableConflictingModuleNames {
                                module1: *most_used_module,
                                module2: *other_module,
                            },
                            *usage,
                        ));
                    }
                }

                if other_type != most_used_type {
                    for usage in other_usages {
                        errors.push(Diagnostic::error(
                            ValidationMessage::ProvidedVariableConflictingTypes {
                                module: *most_used_module,
                                existing_type: schema.get_type_string(most_used_type).intern(),
                                new_type: schema.get_type_string(other_type).intern(),
                            },
                            *usage,
                        ));
                    }
                }
            }
        }
    }
}

struct ProvidedVariableFragmentTransform<'schema> {
    schema: &'schema SDLSchema,
    all_provided_variables: FnvHashMap<VariableName, ProvidedVariableDefinitions>,
    // fragment local identifier --> transformed identifier
    in_scope_providers: FnvHashMap<VariableName, VariableName>,
}

impl<'schema> ProvidedVariableFragmentTransform<'schema> {
    fn new(schema: &'schema SDLSchema) -> Self {
        ProvidedVariableFragmentTransform {
            schema,
            all_provided_variables: Default::default(),
            in_scope_providers: Default::default(),
        }
    }

    fn get_errors(&self) -> Vec<Diagnostic> {
        let mut errors = Vec::new();

        for (_, def) in self.all_provided_variables.iter() {
            def.get_errors(self.schema, &mut errors);
        }
        errors
    }

    fn add_provided_variable(
        &mut self,
        transformed_name: VariableName,
        module_name: StringKey,
        variable_def: &VariableDefinition,
    ) {
        let usages = self
            .all_provided_variables
            .entry(transformed_name)
            .or_insert_with(ProvidedVariableDefinitions::new);

        usages.add(module_name, variable_def);
    }

    fn get_variable_definitions(&self, fragment: &FragmentDefinition) -> Vec<VariableDefinition> {
        fragment
            .variable_definitions
            .iter()
            .filter(|def| {
                def.directives
                    .named(ProvidedVariableMetadata::directive_name())
                    .is_none()
            })
            .cloned()
            .collect_vec()
    }

    fn get_global_variables(&self, fragment: &FragmentDefinition) -> Vec<VariableDefinition> {
        let mut new_globals = fragment.used_global_variables.clone();
        new_globals.extend(fragment.variable_definitions.iter().filter_map(|def| {
            let transformed_name = self.in_scope_providers.get(&def.name())?;
            Some(VariableDefinition {
                name: WithLocation {
                    item: *transformed_name,
                    location: def.name.location,
                },
                ..def.clone()
            })
        }));
        new_globals
    }
}

impl<'schema> Transformer for ProvidedVariableFragmentTransform<'schema> {
    const NAME: &'static str = "ProvidedVariableFragmentTransform";
    const VISIT_ARGUMENTS: bool = true;
    const VISIT_DIRECTIVES: bool = true;

    fn transform_variable(&mut self, variable: &Variable) -> TransformedValue<Variable> {
        if let Some(transformed_name) = self.in_scope_providers.get(&variable.name.item) {
            TransformedValue::Replace(Variable {
                name: WithLocation {
                    location: variable.name.location,
                    item: *transformed_name,
                },
                type_: variable.type_.clone(),
            })
        } else {
            TransformedValue::Keep
        }
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        debug_assert!(self.in_scope_providers.is_empty());
        for def in fragment.variable_definitions.iter() {
            if let Some(metadata) = ProvidedVariableMetadata::find(&def.directives) {
                let transformed_name = format_provided_variable_name(metadata.module_name);
                self.in_scope_providers.insert(def.name(), transformed_name);
                self.add_provided_variable(transformed_name, metadata.module_name, def);
            }
        }

        if self.in_scope_providers.is_empty() {
            Transformed::Keep
        } else {
            let selections = self.transform_selections(&fragment.selections);
            let new_fragment = FragmentDefinition {
                name: fragment.name,
                variable_definitions: self.get_variable_definitions(fragment),
                used_global_variables: self.get_global_variables(fragment),
                type_condition: fragment.type_condition,
                directives: fragment.directives.clone(),
                selections: selections.replace_or_else(|| fragment.selections.clone()),
            };
            self.in_scope_providers.clear();
            Transformed::Replace(new_fragment)
        }
    }
}

#[derive(Debug, Error)]
enum ValidationMessage {
    #[error(
        "Modules '{module1}' and '{module2}' used by provided variables have indistinguishable names. (All non ascii-alphanumeric characters are stripped in Relay transform)"
    )]
    ProvidedVariableConflictingModuleNames {
        module1: StringKey,
        module2: StringKey,
    },

    #[error(
        "All provided variables using module '{module}' must declare the same type. Expected '{existing_type}' but found '{new_type}'"
    )]
    ProvidedVariableConflictingTypes {
        module: StringKey,
        existing_type: StringKey,
        new_type: StringKey,
    },
}


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

use std::sync::Arc;

use common::Diagnostic;
use common::DiagnosticsResult;
use common::DirectiveName;
use common::FeatureFlag;
use common::Location;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::Directive;
use graphql_ir::Field;
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::Transformer;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use lazy_static::lazy_static;
use schema::Schema;

use super::ValidationMessage;

pub fn relay_actor_change_transform(
    program: &Program,
    feature_flag: &FeatureFlag,
) -> DiagnosticsResult<Program> {
    let mut transform = ActorChangeTransform::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)
    }
}

lazy_static! {
    pub static ref RELAY_ACTOR_CHANGE_DIRECTIVE: DirectiveName =
        DirectiveName("fb_actor_change".intern());
    pub static ref RELAY_ACTOR_CHANGE_DIRECTIVE_FOR_CODEGEN: DirectiveName =
        DirectiveName("__fb_actor_change".intern());
    static ref ACTOR_CHANGE_FIELD: StringKey = "actor_key".intern();
}

struct ActorChangeTransform<'program, 'feature> {
    program: &'program Program,
    feature_flag: &'feature FeatureFlag,
    errors: Vec<Diagnostic>,
}

impl<'program, 'feature> ActorChangeTransform<'program, 'feature> {
    fn new(program: &'program Program, feature_flag: &'feature FeatureFlag) -> Self {
        Self {
            program,
            feature_flag,
            errors: Default::default(),
        }
    }
}

impl<'program, 'feature> Transformer for ActorChangeTransform<'program, 'feature> {
    const NAME: &'static str = "ActorChangeTransform";
    const VISIT_ARGUMENTS: bool = false;
    const VISIT_DIRECTIVES: bool = false;

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        if let Some(actor_change_directive) = field.directives.named(*RELAY_ACTOR_CHANGE_DIRECTIVE)
        {
            if field.selections.len() != 1 {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::ActorChangeInvalidSelection,
                    field.alias_or_name_location(),
                ));
                return Transformed::Keep;
            }

            match &field.selections[0] {
                Selection::FragmentSpread(fragment_spread) => {
                    if !self
                        .feature_flag
                        .is_enabled_for(fragment_spread.fragment.item.0)
                    {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ActorChangeIsExperimental,
                            fragment_spread.fragment.location,
                        ));
                        return Transformed::Keep;
                    }
                }

                selection => {
                    self.errors.push(Diagnostic::error(
                        ValidationMessage::ActorChangeInvalidSelection,
                        selection.location(),
                    ));
                    return Transformed::Keep;
                }
            }

            let schema_field = self.program.schema.field(field.definition.item);
            if schema_field.type_.is_list() {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::ActorChangePluralFieldsNotSupported,
                    field.alias_or_name_location(),
                ));
                return Transformed::Keep;
            }
            let field_type = schema_field.type_.inner();
            let viewer_field = match self
                .program
                .schema
                .named_field(field_type, *ACTOR_CHANGE_FIELD)
            {
                Some(viewer_field_id) => {
                    let viewer_field = self.program.schema.field(viewer_field_id);
                    if !viewer_field.type_.inner().is_scalar() {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::ActorChangeViewerShouldBeScalar {
                                directive_name: *RELAY_ACTOR_CHANGE_DIRECTIVE,
                                actor_change_field: *ACTOR_CHANGE_FIELD,
                                field_name: schema_field.name.item,
                                actor_change_field_type: self
                                    .program
                                    .schema
                                    .get_type_name(viewer_field.type_.inner()),
                            },
                            actor_change_directive.name.location,
                        ));
                        return Transformed::Keep;
                    } else {
                        viewer_field_id
                    }
                }
                None => {
                    self.errors.push(Diagnostic::error(
                        ValidationMessage::ActorChangeExpectViewerFieldOnType {
                            directive_name: *RELAY_ACTOR_CHANGE_DIRECTIVE,
                            actor_change_field: *ACTOR_CHANGE_FIELD,
                            field_name: schema_field.name.item,
                            type_name: self.program.schema.get_type_name(field_type),
                        },
                        actor_change_directive.name.location,
                    ));
                    return Transformed::Keep;
                }
            };
            let mut next_selections = field.selections.clone();
            next_selections.push(Selection::ScalarField(Arc::new(ScalarField {
                alias: None,
                definition: WithLocation::new(actor_change_directive.name.location, viewer_field),
                arguments: vec![],
                directives: vec![],
            })));

            let next_selection = Selection::InlineFragment(Arc::new(InlineFragment {
                type_condition: None,
                directives: vec![Directive {
                    name: WithLocation::new(
                        actor_change_directive.name.location,
                        *RELAY_ACTOR_CHANGE_DIRECTIVE_FOR_CODEGEN,
                    ),
                    arguments: vec![],
                    data: None,
                }],
                selections: vec![Selection::LinkedField(Arc::new(LinkedField {
                    selections: next_selections,
                    ..field.clone()
                }))],
                spread_location: Location::generated(),
            }));

            Transformed::Replace(next_selection)
        } else {
            self.default_transform_linked_field(field)
        }
    }

    fn transform_scalar_field(&mut self, field: &ScalarField) -> Transformed<Selection> {
        if field
            .directives
            .named(*RELAY_ACTOR_CHANGE_DIRECTIVE)
            .is_some()
        {
            self.errors.push(Diagnostic::error(
                ValidationMessage::ActorChangeCannotUseOnScalarFields,
                field.alias_or_name_location(),
            ));
        }

        Transformed::Keep
    }
}


/*
 * 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::DirectiveName;
use common::NamedItem;
use graphql_ir::*;
use intern::string_key::Intern;
use lazy_static::lazy_static;

lazy_static! {
    pub static ref RELAY_DIRECTIVE_NAME: DirectiveName = DirectiveName("relay".intern());
    pub static ref PLURAL_ARG_NAME: ArgumentName = ArgumentName("plural".intern());
    pub static ref MASK_ARG_NAME: ArgumentName = ArgumentName("mask".intern());
}

/// Easy access to the arguments of the @relay directive.
///
///   directive @relay(
///     mask: Boolean
///     plural: Boolean
///   ) on FRAGMENT_DEFINITION | FRAGMENT_SPREAD
pub struct RelayDirective {
    /// Note: this is the negated value of the directive as it makes more sense
    /// to default to false.
    pub unmask: bool,
    pub plural: bool,
}

impl RelayDirective {
    /// Returns true for a fragment spread that should not be masked, i.e.:
    /// ...User @relay(mask: false)
    pub fn is_unmasked_fragment_spread(fragment_spread: &FragmentSpread) -> bool {
        Self::has_unmasked_directive(&fragment_spread.directives)
    }

    pub fn is_unmasked_fragment_definition(fragment_definition: &FragmentDefinition) -> bool {
        Self::has_unmasked_directive(&fragment_definition.directives)
    }

    /// Tries to find a @relay directive and parses the directive into a struct.
    /// Panics on unknown @relay arguments or arguments with invalid values.
    /// Validation should happen on the IR before.
    pub fn find(directives: &[Directive]) -> Option<Self> {
        if let Some(relay_directive) = directives.named(*RELAY_DIRECTIVE_NAME) {
            let mut unmask = false;
            let mut plural = false;
            for arg in &relay_directive.arguments {
                if arg.name.item == *MASK_ARG_NAME {
                    if let Value::Constant(ConstantValue::Boolean(arg_value)) = arg.value.item {
                        unmask = !arg_value;
                    } else {
                        panic!("Invalid @relay(mask: ...) directive argument: {:?}", arg);
                    }
                } else if arg.name.item == *PLURAL_ARG_NAME {
                    if let Value::Constant(ConstantValue::Boolean(arg_value)) = arg.value.item {
                        plural = arg_value;
                    } else {
                        panic!("Invalid @relay(plural: ...) directive argument: {:?}", arg);
                    }
                } else {
                    panic!("Invalid @relay directive argument: {:?}", arg);
                }
            }
            Some(RelayDirective { unmask, plural })
        } else {
            None
        }
    }

    fn has_unmasked_directive(directives: &[Directive]) -> bool {
        if let Some(relay_directive) = directives.named(*RELAY_DIRECTIVE_NAME) {
            if let Some(mask_arg) = relay_directive.arguments.named(*MASK_ARG_NAME) {
                if let Value::Constant(ConstantValue::Boolean(arg_value)) = mask_arg.value.item {
                    return !arg_value;
                } else {
                    panic!(
                        "Invalid @relay(mask: ...) directive argument: {:?}",
                        mask_arg
                    );
                }
            }
        }
        false
    }
}


/*
 * 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::DirectiveName;
use graphql_ir::node_identifier::LocationAgnosticBehavior;

use crate::util::CustomMetadataDirectives;
use crate::ModuleMetadata;

#[derive(Clone)]
pub struct RelayLocationAgnosticBehavior;
impl LocationAgnosticBehavior for RelayLocationAgnosticBehavior {
    fn should_skip_in_node_identifier(name: DirectiveName) -> bool {
        CustomMetadataDirectives::should_skip_in_node_identifier(name)
    }
    fn hash_for_name_only(name: DirectiveName) -> bool {
        ModuleMetadata::directive_name() == name
    }
}


/*
 * 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::Location;
use common::NamedItem;
use common::WithLocation;
use docblock_shared::FRAGMENT_KEY_ARGUMENT_NAME;
use docblock_shared::GENERATED_FRAGMENT_ARGUMENT_NAME;
use docblock_shared::HAS_OUTPUT_TYPE_ARGUMENT_NAME;
use docblock_shared::IMPORT_NAME_ARGUMENT_NAME;
use docblock_shared::IMPORT_PATH_ARGUMENT_NAME;
use docblock_shared::INJECT_FRAGMENT_DATA_ARGUMENT_NAME;
use docblock_shared::LIVE_ARGUMENT_NAME;
use docblock_shared::RELAY_RESOLVER_DIRECTIVE_NAME;
use docblock_shared::RELAY_RESOLVER_WEAK_OBJECT_DIRECTIVE;
use graphql_ir::associated_data_impl;
use graphql_ir::Argument;
use graphql_ir::Directive;
use graphql_ir::Field as IrField;
use graphql_ir::FragmentDefinitionName;
use graphql_ir::FragmentSpread;
use graphql_ir::InlineFragment;
use graphql_ir::LinkedField;
use graphql_ir::OperationDefinitionName;
use graphql_ir::Program;
use graphql_ir::ScalarField;
use graphql_ir::Selection;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use graphql_ir::VariableName;
use graphql_syntax::BooleanNode;
use graphql_syntax::ConstantValue;
use intern::string_key::Intern;
use intern::string_key::StringKey;
use intern::Lookup;
use schema::ArgumentValue;
use schema::Field;
use schema::FieldID;
use schema::SDLSchema;
use schema::Schema;
use schema::Type;

use super::ValidationMessage;
use crate::generate_relay_resolvers_operations_for_nested_objects::generate_name_for_nested_object_operation;
use crate::ClientEdgeMetadata;
use crate::FragmentAliasMetadata;
use crate::RequiredMetadataDirective;
use crate::CLIENT_EDGE_WATERFALL_DIRECTIVE_NAME;
use crate::REQUIRED_DIRECTIVE_NAME;

/// Transform Relay Resolver fields. This is done in two passes.
///
/// First we locate fields which are backed Relay Resolvers and attach a
/// metadata directive to them, then we convert those fields into either an
/// annotated stub `__id` field, or an annotated fragment spread referencing the
/// resolver's root fragment.
///
/// See the docblock for `relay_resolvers_spread_transform` for more details
/// about the resulting format.
pub fn relay_resolvers(program: &Program, enabled: bool) -> DiagnosticsResult<Program> {
    let transformed_fields_program = relay_resolvers_fields_transform(program, enabled)?;
    relay_resolvers_spread_transform(&transformed_fields_program)
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ResolverNormalizationInfo {
    pub inner_type: Type,
    pub plural: bool,
    pub normalization_operation: WithLocation<OperationDefinitionName>,
    pub weak_object_instance_field: Option<FieldID>,
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum ResolverOutputTypeInfo {
    /// Resolver returns an opaque scalar field
    ScalarField,
    Composite(ResolverNormalizationInfo),
    /// Resolver returns one or more edges to items in the store.
    EdgeTo,
    Legacy,
}

impl ResolverOutputTypeInfo {
    pub fn normalization_ast_should_have_is_output_type_true(&self) -> bool {
        match self {
            ResolverOutputTypeInfo::ScalarField => true,
            ResolverOutputTypeInfo::Composite(_) => true,
            ResolverOutputTypeInfo::EdgeTo => false,
            ResolverOutputTypeInfo::Legacy => false,
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum FragmentDataInjectionMode {
    Field { name: StringKey, is_required: bool }, // TODO: Add Support for FullData
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct RelayResolverFieldMetadata {
    field_parent_type: StringKey,
    import_path: StringKey,
    import_name: Option<StringKey>,
    fragment_name: Option<FragmentDefinitionName>,
    fragment_data_injection_mode: Option<FragmentDataInjectionMode>,
    field_path: StringKey,
    live: bool,
    output_type_info: ResolverOutputTypeInfo,
}
associated_data_impl!(RelayResolverFieldMetadata);

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct RelayResolverMetadata {
    field_id: FieldID,
    pub import_path: StringKey,
    pub import_name: Option<StringKey>,
    pub field_alias: Option<StringKey>,
    pub field_path: StringKey,
    pub field_arguments: Vec<Argument>,
    pub live: bool,
    pub output_type_info: ResolverOutputTypeInfo,
    /// A tuple with fragment name and field name we need read
    /// of that fragment to pass it to the resolver function.
    pub fragment_data_injection_mode: Option<(
        WithLocation<FragmentDefinitionName>,
        FragmentDataInjectionMode,
    )>,
}
associated_data_impl!(RelayResolverMetadata);

impl RelayResolverMetadata {
    pub fn field<'schema>(&self, schema: &'schema SDLSchema) -> &'schema Field {
        schema.field(self.field_id)
    }

    pub fn field_name(&self, schema: &SDLSchema) -> StringKey {
        self.field(schema).name.item
    }

    pub fn field_parent_type_name(&self, schema: &SDLSchema) -> StringKey {
        let parent_type = self
            .field(schema)
            .parent_type
            .expect("Expected parent type");
        match parent_type {
            Type::Interface(interface_id) => schema.interface(interface_id).name.item.0,
            Type::Object(object_id) => schema.object(object_id).name.item.0,
            _ => panic!("Unexpected parent type for resolver."),
        }
    }

    pub fn generate_local_resolver_name(&self, schema: &SDLSchema) -> StringKey {
        to_camel_case(format!(
            "{}_{}_resolver",
            self.field_parent_type_name(schema),
            self.field_name(schema)
        ))
        .intern()
    }
    pub fn generate_local_resolver_type_name(&self, schema: &SDLSchema) -> StringKey {
        to_camel_case(format!(
            "{}_{}_resolver_type",
            self.field_parent_type_name(schema),
            self.field_name(schema)
        ))
        .intern()
    }
}

/// Convert fields with attached Relay Resolver metadata into the fragment
/// spread of their data dependencies (root fragment). Their
/// `RelayResolverMetadata` IR directive is left attached to this fragment
/// spread.
///
/// For resolvers without a fragment (for example @live resolvers that read from
/// an external source, or resolvers which are simply a function of their
/// arguments) the field is transformed into a `__id` field with the
/// `RelayResolverMetadata` IR directive attached.
fn relay_resolvers_spread_transform(program: &Program) -> DiagnosticsResult<Program> {
    let mut transform = RelayResolverSpreadTransform::new(program);
    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 RelayResolverSpreadTransform<'program> {
    program: &'program Program,
    errors: Vec<Diagnostic>,
}

impl<'program> RelayResolverSpreadTransform<'program> {
    fn new(program: &'program Program) -> Self {
        Self {
            program,
            errors: Default::default(),
        }
    }

    fn transformed_field(&self, field: &impl IrField) -> Option<Selection> {
        RelayResolverFieldMetadata::find(field.directives()).map(|field_metadata| {
            let fragment_definition = field_metadata.fragment_name.map(|fragment_name| {
                self.program
                    .fragment(fragment_name)
                    .expect("Previous validation passes ensured this exists.")
            });

            let (fragment_arguments, field_arguments) = field
                .arguments()
                .iter()
                .map(|arg| arg.clone())
                .partition(|arg| {
                    if let Some(fragment_definition) = fragment_definition {
                        fragment_definition
                            .variable_definitions
                            .named(VariableName(arg.name.item.0))
                            .is_some()
                    } else {
                        false
                    }
                });

            let resolver_metadata = RelayResolverMetadata {
                import_path: field_metadata.import_path,
                import_name: field_metadata.import_name,
                field_alias: field.alias().map(|field_alias| field_alias.item),
                field_path: field_metadata.field_path,
                field_id: field.definition().item,
                field_arguments,
                live: field_metadata.live,
                output_type_info: field_metadata.output_type_info.clone(),
                fragment_data_injection_mode: field_metadata
                    .fragment_data_injection_mode
                    .as_ref()
                    .map(|injection_mode| {
                        (
                            self.program
                                .fragment(
                                    field_metadata
                                        .fragment_name
                                        .expect("Expected to have a fragment name."),
                                )
                                .expect("Expect to have a fragment node.")
                                .name,
                            *injection_mode,
                        )
                    }),
            };

            let mut new_directives: Vec<Directive> = vec![resolver_metadata.into()];

            for directive in field.directives() {
                if directive.name.item != RelayResolverFieldMetadata::directive_name() {
                    new_directives.push(directive.clone())
                }
            }
            if let Some(fragment_definition) = fragment_definition {
                Selection::FragmentSpread(Arc::new(FragmentSpread {
                    fragment: fragment_definition.name,
                    arguments: fragment_arguments,
                    directives: new_directives,
                }))
            } else {
                Selection::ScalarField(Arc::new(ScalarField {
                    alias: None,
                    definition: WithLocation::generated(self.program.schema.clientid_field()),
                    arguments: vec![],
                    directives: new_directives,
                }))
            }
        })
    }
}

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

    fn transform_scalar_field(&mut self, field: &ScalarField) -> Transformed<Selection> {
        match self.transformed_field(field) {
            Some(selection) => Transformed::Replace(selection),
            None => Transformed::Keep,
        }
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        match self.transformed_field(field) {
            Some(selection) => Transformed::Replace(selection),
            None => self.default_transform_linked_field(field),
        }
    }

    fn transform_inline_fragment(
        &mut self,
        fragment: &graphql_ir::InlineFragment,
    ) -> Transformed<Selection> {
        match ClientEdgeMetadata::find(fragment) {
            Some(client_edge_metadata) => {
                let backing_id_field = self
                    .transform_selection(&client_edge_metadata.backing_field)
                    .unwrap_or_else(|| client_edge_metadata.backing_field.clone());

                let selections_field = self
                    .default_transform_linked_field(client_edge_metadata.linked_field)
                    .unwrap_or_else(|| {
                        Selection::LinkedField(Arc::new(client_edge_metadata.linked_field.clone()))
                    });

                let selections = vec![backing_id_field, selections_field];

                Transformed::Replace(Selection::InlineFragment(Arc::new(InlineFragment {
                    selections,
                    ..fragment.clone()
                })))
            }
            None => self.default_transform_inline_fragment(fragment),
        }
    }
}

/// Identify fields which are backed Relay Resolvers, and attach additional
/// metadata to those fields, such as their resolvers' module locations, root
/// fragments (if any), whether they're @live, etc. This is all derived from the
/// schema, which is itself derived from the Resolver docblock annotations.
///
/// After this transform, future transforms should not need to consult the
/// schema to know whether a field is backed by a Relay Resolver, or what its
/// root fragment dependencies are. They should simply be able to check for the
/// presence of the `RelayResolverFieldMetadata` IR directive on the field.
fn relay_resolvers_fields_transform(
    program: &Program,
    enabled: bool,
) -> DiagnosticsResult<Program> {
    let mut transform = RelayResolverFieldTransform::new(program, enabled);
    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 RelayResolverFieldTransform<'program> {
    enabled: bool,
    program: &'program Program,
    errors: Vec<Diagnostic>,
    path: Vec<&'program str>,
}

impl<'program> RelayResolverFieldTransform<'program> {
    fn new(program: &'program Program, enabled: bool) -> Self {
        Self {
            program,
            enabled,
            errors: Default::default(),
            path: Vec::new(),
        }
    }

    fn extract_resolver_field_directives(
        &mut self,
        field: &impl IrField,
    ) -> Option<Vec<Directive>> {
        let schema_field = self.program.schema.field(field.definition().item);

        get_resolver_info(
            &self.program.schema,
            schema_field,
            field.definition().location,
        )
        .and_then(|info| {
            if !self.enabled {
                self.errors.push(Diagnostic::error(
                    ValidationMessage::RelayResolversDisabled,
                    field.alias_or_name_location(),
                ));
                return None;
            }
            match info {
                Ok(ResolverInfo {
                    fragment_name,
                    import_path,
                    import_name,
                    live,
                    has_output_type,
                    fragment_data_injection_mode,
                }) => {
                    let mut non_required_directives =
                        field.directives().iter().filter(|directive| {
                            // For now, only @required and @waterfall are allowed on Resolver fields.
                            directive.name.item != RequiredMetadataDirective::directive_name()
                                && directive.name.item != *REQUIRED_DIRECTIVE_NAME
                                && directive.name.item != *CLIENT_EDGE_WATERFALL_DIRECTIVE_NAME
                        });
                    if let Some(directive) = non_required_directives.next() {
                        self.errors.push(Diagnostic::error(
                            ValidationMessage::RelayResolverUnexpectedDirective,
                            directive.name.location,
                        ));
                    }

                    let parent_type = schema_field.parent_type.unwrap();
                    let inner_type = schema_field.type_.inner();

                    if let Some(fragment_name) = fragment_name {
                        match self.program.fragment(fragment_name) {
                            Some(fragment_definition) => {
                                if !self.program.schema.are_overlapping_types(
                                    fragment_definition.type_condition,
                                    parent_type,
                                ) {
                                    // This invariant is enforced when we generate docblock IR, but we double check here to
                                    // ensure no later transforms break that invariant, and that manually written test
                                    // schemas gets this right.
                                    panic!("Invalid type condition on `{}`, the fragment backing the Relay Resolver field `{}`.", fragment_name, schema_field.name.item);
                                }
                            }
                            None => {
                                self.errors.push(Diagnostic::error(
                                    ValidationMessage::InvalidRelayResolverFragmentName {
                                        fragment_name,
                                    },
                                    // We don't have locations for directives in schema files.
                                    // So we send them to the field name, rather than the directive value.
                                    schema_field.name.location,
                                ));
                                return None;
                            }
                        }
                    }

                    let output_type_info = if has_output_type {
                        if inner_type.is_composite_type() {
                            let normalization_operation = generate_name_for_nested_object_operation(
                                &self.program.schema,
                                self.program.schema.field(field.definition().item),
                            );

                            let weak_object_instance_field =
                                inner_type.get_object_id().and_then(|id| {
                                    let object = self.program.schema.object(id);
                                    if object
                                        .directives
                                        .named(*RELAY_RESOLVER_WEAK_OBJECT_DIRECTIVE)
                                        .is_some()
                                    {
                                        // This is expect to be `__relay_model_instance`
                                        // TODO: Add validation/panic to assert that weak object has only
                                        // one field here, and it's a magic relay instance field.
                                        Some(*object.fields.get(0).unwrap())
                                    } else {
                                        None
                                    }
                                });

                            ResolverOutputTypeInfo::Composite(
                                ResolverNormalizationInfo {
                                    inner_type,
                                    plural: schema_field.type_.is_list(),
                                    normalization_operation,
                                    weak_object_instance_field,
                                },
                            )
                        } else {
                            ResolverOutputTypeInfo::ScalarField
                        }
                    } else if inner_type.is_composite_type() {
                        ResolverOutputTypeInfo::EdgeTo
                    } else {
                        ResolverOutputTypeInfo::Legacy
                    };

                    let resolver_field_metadata = RelayResolverFieldMetadata {
                        import_path,
                        import_name,
                        field_parent_type: self.program.schema.get_type_name(parent_type),
                        fragment_name,
                        field_path: self.path.join(".").intern(),
                        live,
                        output_type_info,
                        fragment_data_injection_mode,
                    };

                    let mut directives: Vec<Directive> = field.directives().to_vec();

                    directives.push(resolver_field_metadata.into());
                    Some(directives)
                }
                Err(diagnostics) => {
                    for diagnostic in diagnostics {
                        self.errors.push(diagnostic);
                    }
                    None
                }
            }
        })
    }
}

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

    fn transform_scalar_field(&mut self, field: &ScalarField) -> Transformed<Selection> {
        self.path
            .push(field.alias_or_name(&self.program.schema).lookup());

        let transformed =
            self.extract_resolver_field_directives(field)
                .map_or(Transformed::Keep, |directives| {
                    Transformed::Replace(Selection::ScalarField(Arc::new(ScalarField {
                        directives,
                        ..field.clone()
                    })))
                });

        self.path.pop();
        transformed
    }

    fn transform_linked_field(&mut self, field: &LinkedField) -> Transformed<Selection> {
        self.path
            .push(field.alias_or_name(&self.program.schema).lookup());

        let transformed = self.extract_resolver_field_directives(field).map_or_else(
            || self.default_transform_linked_field(field),
            |directives| {
                Transformed::Replace(Selection::LinkedField(Arc::new(LinkedField {
                    directives,
                    ..field.clone()
                })))
            },
        );
        self.path.pop();
        transformed
    }

    fn transform_inline_fragment(
        &mut self,
        fragment: &graphql_ir::InlineFragment,
    ) -> Transformed<Selection> {
        let maybe_alias =
            FragmentAliasMetadata::find(&fragment.directives).map(|metadata| metadata.alias.item);

        if let Some(alias) = maybe_alias {
            self.path.push(alias.lookup())
        }

        // Note that Client Edge fields have already been transformed into an inline
        // fragment. This inline fragment is used like a tuple to group together the
        // backing field which defines the relationship (resolver that returns an ID)
        // and the selections hanging off of that.
        let transformed = match ClientEdgeMetadata::find(fragment) {
            Some(client_edge_metadata) => {
                let backing_id_field = self
                    .transform_selection(&client_edge_metadata.backing_field)
                    .unwrap_or_else(|| client_edge_metadata.backing_field.clone());

                let field_name = client_edge_metadata
                    .linked_field
                    .alias_or_name(&self.program.schema);

                self.path.push(field_name.lookup());
                let selections_field = self
                    .default_transform_linked_field(client_edge_metadata.linked_field)
                    .unwrap_or_else(|| {
                        Selection::LinkedField(Arc::new(client_edge_metadata.linked_field.clone()))
                    });
                self.path.pop();

                let selections = vec![backing_id_field, selections_field];

                Transformed::Replace(Selection::InlineFragment(Arc::new(InlineFragment {
                    selections,
                    ..fragment.clone()
                })))
            }
            None => self.default_transform_inline_fragment(fragment),
        };

        if maybe_alias.is_some() {
            self.path.pop();
        }

        transformed
    }
}

struct ResolverInfo {
    fragment_name: Option<FragmentDefinitionName>,
    fragment_data_injection_mode: Option<FragmentDataInjectionMode>,
    import_path: StringKey,
    import_name: Option<StringKey>,
    live: bool,
    has_output_type: bool,
}

fn get_resolver_info(
    schema: &SDLSchema,
    resolver_field: &Field,
    error_location: Location,
) -> Option<DiagnosticsResult<ResolverInfo>> {
    if !resolver_field.is_extension {
        return None;
    }
    resolver_field
        .directives
        .named(*RELAY_RESOLVER_DIRECTIVE_NAME)
        .map(|directive| {
            let arguments = &directive.arguments;
            let fragment_name =
                get_argument_value(arguments, *FRAGMENT_KEY_ARGUMENT_NAME, error_location)
                    .ok()
                    .map(FragmentDefinitionName);
            let import_path =
                get_argument_value(arguments, *IMPORT_PATH_ARGUMENT_NAME, error_location)?;
            let live = get_bool_argument_is_true(arguments, *LIVE_ARGUMENT_NAME);
            let has_output_type =
                get_bool_argument_is_true(arguments, *HAS_OUTPUT_TYPE_ARGUMENT_NAME);
            let import_name =
                get_argument_value(arguments, *IMPORT_NAME_ARGUMENT_NAME, error_location).ok();
            let inject_fragment_data = get_argument_value(
                arguments,
                *INJECT_FRAGMENT_DATA_ARGUMENT_NAME,
                error_location,
            )
            .ok();

            Ok(ResolverInfo {
                fragment_name,
                import_path,
                import_name,
                live,
                has_output_type,
                fragment_data_injection_mode: inject_fragment_data.map(|field_name| {
                    let injected_field_id = schema
                        .named_field(
                            resolver_field.parent_type.unwrap_or_else(|| {
                                panic!(
                                    "Parent type should be defined for the field `{}`.",
                                    field_name
                                )
                            }),
                            field_name,
                        )
                        .unwrap_or_else(|| {
                            panic!(
                                "Expect a field `{}` to be defined on the resolvers parent type.",
                                field_name
                            )
                        });
                    FragmentDataInjectionMode::Field {
                        name: field_name,
                        is_required: schema.field(injected_field_id).type_.is_non_null(),
                    }
                }),
            })
        })
}

pub(crate) fn get_argument_value(
    arguments: &[ArgumentValue],
    argument_name: ArgumentName,
    error_location: Location,
) -> DiagnosticsResult<StringKey> {
    match arguments.named(argument_name) {
        Some(argument) => {
            match argument.value.get_string_literal() {
                Some(import_path) => Ok(import_path),
                None => {
                    // This is a validation error, but ideally it would be done when we validate the client schema.
                    Err(vec![Diagnostic::error(
                        ValidationMessage::InvalidRelayResolverKeyArg { key: argument_name },
                        error_location,
                    )])
                }
            }
        }
        None => {
            // Should we expect schema validation to catch this for required fields?
            Err(vec![Diagnostic::error(
                ValidationMessage::MissingRelayResolverKeyArg { key: argument_name },
                error_location,
            )])
        }
    }
}

pub(crate) fn get_bool_argument_is_true(
    arguments: &[ArgumentValue],
    argument_name: ArgumentName,
) -> bool {
    match arguments.named(argument_name) {
        Some(ArgumentValue {
            value: ConstantValue::Boolean(BooleanNode { value, .. }),
            ..
        }) => *value,
        None => false,
        // These schema extensions are generated by our compiler. If they are malformed, it indicates a bug in
        // relay-docblock.
        _ => panic!(
            "Expected the `live` argument in a @relay_resolver schema directive to be either omitted or a static boolean."
        ),
    }
}

// If the field is a resolver, return its user defined fragment name. Does not
// return generated fragment names.
pub fn get_resolver_fragment_dependency_name(field: &Field) -> Option<FragmentDefinitionName> {
    if !field.is_extension {
        return None;
    }

    field
        .directives
        .named(*RELAY_RESOLVER_DIRECTIVE_NAME)
        .filter(|resolver_directive| {
            let generated = resolver_directive
                .arguments
                .named(*GENERATED_FRAGMENT_ARGUMENT_NAME)
                .and_then(|arg| arg.value.get_bool_literal())
                .unwrap_or(false);
            !generated
        })
        .and_then(|resolver_directive| {
            resolver_directive
                .arguments
                .named(*FRAGMENT_KEY_ARGUMENT_NAME)
        })
        .and_then(|arg| arg.value.get_string_literal().map(FragmentDefinitionName))
}

fn to_camel_case(non_camelized_string: String) -> String {
    let mut camelized_string = String::with_capacity(non_camelized_string.len());
    let mut last_character_was_not_alphanumeric = false;
    for (i, ch) in non_camelized_string.chars().enumerate() {
        if !ch.is_alphanumeric() {
            last_character_was_not_alphanumeric = true;
        } else if last_character_was_not_alphanumeric {
            camelized_string.push(ch.to_ascii_uppercase());
            last_character_was_not_alphanumeric = false;
        } else {
            camelized_string.push(if i == 0 { ch.to_ascii_lowercase() } else { ch });
            last_character_was_not_alphanumeric = false;
        }
    }
    camelized_string
}


/*
 * 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::FragmentDefinition;
use graphql_ir::FragmentDefinitionNameSet;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::Transformed;
use graphql_ir::Transformer;

/// This transform removes the given list of base fragments from the Program.
/// This is useful if earlier steps need access to fragments from some base
/// project, but we don't want to write output files for them and can skip over
/// some transform steps.
pub fn remove_base_fragments(
    program: &Program,
    base_fragment_names: &FragmentDefinitionNameSet,
) -> Program {
    if base_fragment_names.is_empty() {
        // Nothing to remove.
        return program.clone();
    }
    let mut transform = StripBaseFragmentsTransform {
        base_fragment_names: &base_fragment_names,
    };
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

struct StripBaseFragmentsTransform<'a> {
    base_fragment_names: &'a FragmentDefinitionNameSet,
}

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

    fn transform_operation(
        &mut self,
        _operation: &OperationDefinition,
    ) -> Transformed<OperationDefinition> {
        Transformed::Keep
    }

    fn transform_fragment(
        &mut self,
        fragment: &FragmentDefinition,
    ) -> Transformed<FragmentDefinition> {
        if self.base_fragment_names.contains(&fragment.name.item) {
            Transformed::Delete
        } else {
            Transformed::Keep
        }
    }
}


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

use std::collections::HashMap;
use std::collections::HashSet;

use common::Diagnostic;
use common::NamedItem;
use common::WithLocation;
use graphql_ir::FragmentDefinition;
use graphql_ir::FragmentDefinitionNameMap;
use graphql_ir::FragmentSpread;
use graphql_ir::OperationDefinition;
use graphql_ir::Program;
use graphql_ir::ValidationMessage;
use graphql_ir::Value;
use graphql_ir::Variable;
use graphql_ir::VariableName;
use graphql_ir::Visitor;
use schema::Schema;
use schema::Type;
use schema::TypeReference;

use crate::no_inline::NO_INLINE_DIRECTIVE_NAME;

pub type VariableMap = HashMap<VariableName, Variable>;
type Visited = FragmentDefinitionNameMap<VariableMap>;

pub struct InferVariablesVisitor<'program> {
    /// Cache fragments as they are transformed to avoid duplicate processing.
    /// Because @argument values don't matter (only variable names/types),
    /// each reachable fragment only has to be checked once.
    visited_fragments: Visited,
    program: &'program Program,
}

impl<'program> InferVariablesVisitor<'program> {
    pub fn new(program: &'program Program) -> Self {
        Self {
            visited_fragments: Default::default(),
            program,
        }
    }

    /// Determine the set of root variables that are transitively
    /// referenced by each fragment, ie the union of all root variables used in the
    /// fragment and any fragments it transitively spreads. The type of each variable will be
    /// the most specific type with which that variable is used (ie, the type that the variable
    /// must have for the query to be valid).
    pub fn infer_operation_variables(
        &mut self,
        operation: &OperationDefinition,
    ) -> (VariableMap, Vec<Diagnostic>) {
        let transitive_local_variables = Default::default();
        let mut visitor = VariablesVisitor::new(
            self.program,
            &mut self.visited_fragments,
            Default::default(),
            &transitive_local_variables,
        );
        visitor.visit_operation(operation);
        (visitor.variable_map, visitor.errors)
    }

    /// Similar to infer_operation_variables(), but finds root variables referenced transitively
    /// from the given fragment.
    pub fn infer_fragment_variables(&mut self, fragment: &FragmentDefinition) -> VariableMap {
        let transitive_local_variables = Default::default();
        let mut visitor = VariablesVisitor::new(
            self.program,
            &mut self.visited_fragments,
            Default::default(),
            &transitive_local_variables,
        );
        visitor.infer_fragment_variables(fragment)
    }
}

struct VariablesVisitor<'a, 'b> {
    variable_map: VariableMap,
    visited_fragments: &'a mut Visited,
    program: &'a Program,
    local_variables: HashSet<VariableName>,
    transitive_local_variables: &'b HashSet<VariableName>,
    errors: Vec<Diagnostic>,
}

impl<'a, 'b> VariablesVisitor<'a, 'b> {
    fn new(
        program: &'a Program,
        visited_fragments: &'a mut Visited,
        local_variables: HashSet<VariableName>,
        transitive_local_variables: &'b HashSet<VariableName>,
    ) -> Self {
        Self {
            variable_map: Default::default(),
            visited_fragments,
            program,
            local_variables,
            transitive_local_variables,
            errors: Default::default(),
        }
    }
}

impl VariablesVisitor<'_, '_> {
    /// Determine the set of root variables referenced locally in each
    /// fragment. Note that RootArgumentDefinitions in the fragment's
    /// argumentDefinitions can contain spurious entries for legacy
    /// reasons. Instead of using those the fragment is traversed
    /// to reanalyze variable usage.
    fn infer_fragment_variables(&mut self, fragment: &FragmentDefinition) -> VariableMap {
        if let Some(map) = self.visited_fragments.get(&fragment.name.item) {
            map.clone()
        } else {
            // Break cycles by initially caching a version that is empty.
            // If the current fragment is reached again, it won't have any
            // root variables to add to its parents. The traversal below will
            // find any root variables and update the cached version of the
            // fragment.
            self.visited_fragments
                .insert(fragment.name.item, Default::default());

            // Avoid collecting local variables usages as root variables
            let local_variables = fragment
                .variable_definitions
                .iter()
                .map(|var| var.name.item)
                .collect::<HashSet<VariableName>>();
            let transitive_local_variables = if fragment
                .directives
                .named(*NO_INLINE_DIRECTIVE_NAME)
                .is_some()
            {
                Some(local_variables.clone())
            } else {
                None
            };
            let mut visitor = VariablesVisitor::new(
                self.program,
                self.visited_fragments,
                local_variables,
                transitive_local_variables
                    .as_ref()
                    .unwrap_or(self.transitive_local_variables),
            );
            visitor.visit_fragment(fragment);
            let result = visitor.variable_map;
            self.visited_fragments
                .insert(fragment.name.item, result.clone());
            result
        }
    }

    fn is_root_variable(&self, name: VariableName) -> bool {
        !self.local_variables.contains(&name) && !self.transitive_local_variables.contains(&name)
    }

    // Record that the given (root) variable was used with the given type, updating the mapping of
    // used root variables:
    // - If the first time the variable has been seen, add it to the variable map
    // - If the variable has been seen before, update the type if the new type is more specific
    //   then the type with which the variable was previously used.
    // The net effect is that for each variable name the most specific type will be recorded along
    // with a location that requires that type.
    fn record_root_variable_usage(
        &mut self,
        name: &WithLocation<VariableName>,
        type_: &TypeReference<Type>,
    ) {
        let schema = &self.program.schema;
        let errors = &mut self.errors;
        self.variable_map
            .entry(name.item)
            .and_modify(|prev_variable| {
                // Note the use of *strict* subtype check: there's no reason to update the mapping
                // if the types are equivalent, only if the current type is more specific than
                // the previous type
                let is_stricter_type =
                    schema.is_type_strict_subtype_of(type_, &prev_variable.type_);
                if is_stricter_type {
                    *prev_variable = Variable {
                        name: *name,
                        type_: type_.clone(),
                    };
                } else if !schema.is_type_subtype_of(&prev_variable.type_, type_) {
                    errors.push(
                        Diagnostic::error(
                            ValidationMessage::IncompatibleVariableUsage {
                                prev_type: schema.get_type_string(&prev_variable.type_),
                                next_type: schema.get_type_string(type_),
                            },
                            name.location,
                        )
                        .annotate("is incompatible with", prev_variable.name.location),
                    )
                }
            })
            .or_insert_with(|| Variable {
                name: *name,
                type_: type_.clone(),
            });
    }
}

impl<'a, 'b> Visitor for VariablesVisitor<'a, 'b> {
    const NAME: &'static str = "VariablesVisitor";
    const VISIT_ARGUMENTS: bool = true;
    const VISIT_DIRECTIVES: bool = true;

    fn visit_fragment_spread(&mut self, spread: &FragmentSpread) {
        self.visit_directives(&spread.directives);
        let fragment = self
            .program
            .fragment(spread.fragment.item)
            .expect("Expect fragment to exist.");

        // Detect root variables being passed as the value of @arguments;
        // recover the expected type from the corresponding argument definitions.
        if !fragment.variable_definitions.is_empty() {
            for arg in spread.arguments.iter() {
                if let Value::Variable(var) = &arg.value.item {
                    if let Some(def) = fragment
                        .variable_definitions
                        .named(VariableName(arg.name.item.0))
                    {
                        if self.is_root_variable(var.name.item) {
                            self.record_root_variable_usage(&var.name, &def.type_);
                        }
                    }
                }
            }
        }

        // Merge any root variables referenced by the spread fragment
        // into this (parent) fragment's arguments.
        let referenced_fragment_variables = self.infer_fragment_variables(fragment);
        for (_, variable) in referenced_fragment_variables.into_iter() {
            self.record_root_variable_usage(&variable.name, &variable.type_);
        }
    }

    fn visit_variable(&mut self, variable: &Variable) {
        if self.is_root_variable(variable.name.item) {
            self.record_root_variable_usage(&variable.name, &variable.type_)
        }
    }
}


/*
 * 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::Directive;
use graphql_ir::Program;
use graphql_ir::Transformed;
use graphql_ir::Transformer;
use schema::Schema;

pub fn skip_client_directives(program: &Program) -> Program {
    let mut transform = SkipClientDirectives::new(program);
    transform
        .transform_program(program)
        .replace_or_else(|| program.clone())
}

pub struct SkipClientDirectives<'s> {
    program: &'s Program,
}

impl<'s> SkipClientDirectives<'s> {
    fn new(program: &'s Program) -> Self {
        Self { program }
    }
}

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

    fn transform_directive(&mut self, directive: &Directive) -> Transformed<Directive> {
        if self
            .program
            .schema
            .is_extension_directive(directive.name.item)
        {
            Transformed::Delete
        } else {
            Transformed::Keep
        }
    }
}