use crate::ast;
use crate::schema;
use crate::Node;
use crate::Parser;
use crate::Schema;
use indexmap::map::Entry;
use indexmap::IndexMap;
use std::collections::HashSet;
use std::path::Path;
pub(crate) mod from_ast;
mod serialize;
pub(crate) mod validation;
pub use crate::ast::{
    Argument, Directive, DirectiveList, Name, NamedType, OperationType, Type, Value,
    VariableDefinition,
};
use crate::validation::DiagnosticList;
use crate::NodeLocation;
use std::fmt;
#[derive(Debug, Clone, Default)]
pub struct ExecutableDocument {
    pub sources: crate::SourceMap,
    build_errors: Vec<BuildError>,
    pub anonymous_operation: Option<Node<Operation>>,
    pub named_operations: IndexMap<Name, Node<Operation>>,
    pub fragments: IndexMap<Name, Node<Fragment>>,
}
#[derive(Debug, Clone)]
pub struct FieldSet {
    pub sources: crate::SourceMap,
    pub(crate) build_errors: Vec<BuildError>,
    pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Operation {
    pub operation_type: OperationType,
    pub name: Option<Name>,
    pub variables: Vec<Node<VariableDefinition>>,
    pub directives: DirectiveList,
    pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Fragment {
    pub name: Name,
    pub directives: DirectiveList,
    pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SelectionSet {
    pub ty: NamedType,
    pub selections: Vec<Selection>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Selection {
    Field(Node<Field>),
    FragmentSpread(Node<FragmentSpread>),
    InlineFragment(Node<InlineFragment>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
    pub definition: Node<schema::FieldDefinition>,
    pub alias: Option<Name>,
    pub name: Name,
    pub arguments: Vec<Node<Argument>>,
    pub directives: DirectiveList,
    pub selection_set: SelectionSet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FragmentSpread {
    pub fragment_name: Name,
    pub directives: DirectiveList,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InlineFragment {
    pub type_condition: Option<NamedType>,
    pub directives: DirectiveList,
    pub selection_set: SelectionSet,
}
#[derive(thiserror::Error, Debug, Clone)]
pub(crate) enum BuildError {
    #[error("an executable document must not contain {describe}")]
    TypeSystemDefinition {
        location: Option<NodeLocation>,
        describe: &'static str,
    },
    #[error("anonymous operation cannot be selected when the document contains other operations")]
    AmbiguousAnonymousOperation { location: Option<NodeLocation> },
    #[error(
        "the operation `{name_at_previous_location}` is defined multiple times in the document"
    )]
    OperationNameCollision {
        location: Option<NodeLocation>,
        name_at_previous_location: Name,
    },
    #[error(
        "the fragment `{name_at_previous_location}` is defined multiple times in the document"
    )]
    FragmentNameCollision {
        location: Option<NodeLocation>,
        name_at_previous_location: Name,
    },
    #[error("`{operation_type}` root operation type is not defined")]
    UndefinedRootOperation {
        location: Option<NodeLocation>,
        operation_type: &'static str,
    },
    #[error(
        "type condition `{type_name}` of fragment `{fragment_name}` \
         is not a type defined in the schema"
    )]
    UndefinedTypeInNamedFragmentTypeCondition {
        location: Option<NodeLocation>,
        type_name: NamedType,
        fragment_name: Name,
    },
    #[error("type condition `{type_name}` of inline fragment is not a type defined in the schema")]
    UndefinedTypeInInlineFragmentTypeCondition {
        location: Option<NodeLocation>,
        type_name: NamedType,
        path: SelectionPath,
    },
    #[error("field selection of scalar type `{type_name}` must not have subselections")]
    SubselectionOnScalarType {
        location: Option<NodeLocation>,
        type_name: NamedType,
        path: SelectionPath,
    },
    #[error("field selection of enum type `{type_name}` must not have subselections")]
    SubselectionOnEnumType {
        location: Option<NodeLocation>,
        type_name: NamedType,
        path: SelectionPath,
    },
    #[error("type `{type_name}` does not have a field `{field_name}`")]
    UndefinedField {
        location: Option<NodeLocation>,
        type_name: NamedType,
        field_name: Name,
        path: SelectionPath,
    },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SelectionPath {
    pub(crate) root: ExecutableDefinitionName,
    pub(crate) nested_fields: Vec<Name>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ExecutableDefinitionName {
    AnonymousOperation(ast::OperationType),
    NamedOperation(ast::OperationType, Name),
    Fragment(Name),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct GetOperationError();
impl ExecutableDocument {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn parse(schema: &Schema, source_text: impl Into<String>, path: impl AsRef<Path>) -> Self {
        Parser::new().parse_executable(schema, source_text, path)
    }
    pub fn validate(&self, schema: &Schema) -> Result<(), DiagnosticList> {
        let mut errors = DiagnosticList::new(Some(schema.sources.clone()), self.sources.clone());
        validation::validate_executable_document(&mut errors, schema, self);
        errors.into_result()
    }
    pub fn all_operations(&self) -> impl Iterator<Item = &'_ Node<Operation>> {
        self.anonymous_operation
            .as_ref()
            .into_iter()
            .chain(self.named_operations.values())
    }
    pub fn get_operation(
        &self,
        name_request: Option<&str>,
    ) -> Result<&Node<Operation>, GetOperationError> {
        if let Some(name) = name_request {
            self.named_operations.get(name)
        } else if let Some(op) = &self.anonymous_operation {
            self.named_operations.is_empty().then_some(op)
        } else {
            self.named_operations
                .values()
                .next()
                .and_then(|op| (self.named_operations.len() == 1).then_some(op))
        }
        .ok_or(GetOperationError())
    }
    pub fn get_operation_mut(
        &mut self,
        name_request: Option<&str>,
    ) -> Result<&mut Operation, GetOperationError> {
        if let Some(name) = name_request {
            self.named_operations.get_mut(name)
        } else if let Some(op) = &mut self.anonymous_operation {
            self.named_operations.is_empty().then_some(op)
        } else {
            let len = self.named_operations.len();
            self.named_operations
                .values_mut()
                .next()
                .and_then(|op| (len == 1).then_some(op))
        }
        .map(Node::make_mut)
        .ok_or(GetOperationError())
    }
    serialize_method!();
}
impl Eq for ExecutableDocument {}
impl PartialEq for ExecutableDocument {
    fn eq(&self, other: &Self) -> bool {
        let Self {
            sources: _,
            build_errors: _,
            anonymous_operation,
            named_operations,
            fragments,
        } = self;
        *anonymous_operation == other.anonymous_operation
            && *named_operations == other.named_operations
            && *fragments == other.fragments
    }
}
impl Operation {
    pub fn object_type(&self) -> &NamedType {
        &self.selection_set.ty
    }
    pub fn is_introspection(&self, document: &ExecutableDocument) -> bool {
        fn is_introspection_impl<'a>(
            document: &'a ExecutableDocument,
            seen_fragments: &mut HashSet<&'a Name>,
            set: &'a SelectionSet,
        ) -> bool {
            set.selections.iter().all(|selection| match selection {
                Selection::Field(field) => {
                    matches!(field.name.as_str(), "__type" | "__schema" | "__typename")
                }
                Selection::FragmentSpread(spread) => {
                    document
                        .fragments
                        .get(&spread.fragment_name)
                        .is_some_and(|fragment| {
                            let new = seen_fragments.insert(&spread.fragment_name);
                            if new {
                                is_introspection_impl(
                                    document,
                                    seen_fragments,
                                    &fragment.selection_set,
                                )
                            } else {
                                true
                            }
                        })
                }
                Selection::InlineFragment(inline) => {
                    is_introspection_impl(document, seen_fragments, &inline.selection_set)
                }
            })
        }
        self.operation_type == OperationType::Query
            && is_introspection_impl(document, &mut HashSet::new(), &self.selection_set)
    }
    serialize_method!();
}
impl Fragment {
    pub fn type_condition(&self) -> &NamedType {
        &self.selection_set.ty
    }
    serialize_method!();
}
impl SelectionSet {
    pub fn new(ty: NamedType) -> Self {
        Self {
            ty,
            selections: Vec::new(),
        }
    }
    pub fn push(&mut self, selection: impl Into<Selection>) {
        self.selections.push(selection.into())
    }
    pub fn extend(&mut self, selections: impl IntoIterator<Item = impl Into<Selection>>) {
        self.selections
            .extend(selections.into_iter().map(|sel| sel.into()))
    }
    pub fn new_field<'schema>(
        &self,
        schema: &'schema Schema,
        name: Name,
    ) -> Result<Field, schema::FieldLookupError<'schema>> {
        let definition = schema.type_field(&self.ty, &name)?.node.clone();
        Ok(Field::new(name, definition))
    }
    pub fn new_inline_fragment(&self, opt_type_condition: Option<NamedType>) -> InlineFragment {
        if let Some(type_condition) = opt_type_condition {
            InlineFragment::with_type_condition(type_condition)
        } else {
            InlineFragment::without_type_condition(self.ty.clone())
        }
    }
    pub fn new_fragment_spread(&self, fragment_name: Name) -> FragmentSpread {
        FragmentSpread::new(fragment_name)
    }
    pub fn fields(&self) -> impl Iterator<Item = &Node<Field>> {
        self.selections.iter().filter_map(|sel| sel.as_field())
    }
    serialize_method!();
}
impl Selection {
    pub fn directives(&self) -> &DirectiveList {
        match self {
            Self::Field(sel) => &sel.directives,
            Self::FragmentSpread(sel) => &sel.directives,
            Self::InlineFragment(sel) => &sel.directives,
        }
    }
    serialize_method!();
}
impl From<Node<Field>> for Selection {
    fn from(node: Node<Field>) -> Self {
        Self::Field(node)
    }
}
impl From<Node<InlineFragment>> for Selection {
    fn from(node: Node<InlineFragment>) -> Self {
        Self::InlineFragment(node)
    }
}
impl From<Node<FragmentSpread>> for Selection {
    fn from(node: Node<FragmentSpread>) -> Self {
        Self::FragmentSpread(node)
    }
}
impl From<Field> for Selection {
    fn from(value: Field) -> Self {
        Self::Field(Node::new(value))
    }
}
impl From<InlineFragment> for Selection {
    fn from(value: InlineFragment) -> Self {
        Self::InlineFragment(Node::new(value))
    }
}
impl From<FragmentSpread> for Selection {
    fn from(value: FragmentSpread) -> Self {
        Self::FragmentSpread(Node::new(value))
    }
}
impl Selection {
    pub fn as_field(&self) -> Option<&Node<Field>> {
        if let Self::Field(field) = self {
            Some(field)
        } else {
            None
        }
    }
    pub fn as_inline_fragment(&self) -> Option<&Node<InlineFragment>> {
        if let Self::InlineFragment(inline) = self {
            Some(inline)
        } else {
            None
        }
    }
    pub fn as_fragment_spread(&self) -> Option<&Node<FragmentSpread>> {
        if let Self::FragmentSpread(spread) = self {
            Some(spread)
        } else {
            None
        }
    }
}
impl Field {
    pub fn new(name: Name, definition: Node<schema::FieldDefinition>) -> Self {
        let selection_set = SelectionSet::new(definition.ty.inner_named_type().clone());
        Field {
            definition,
            alias: None,
            name,
            arguments: Vec::new(),
            directives: DirectiveList::new(),
            selection_set,
        }
    }
    pub fn with_alias(mut self, alias: Name) -> Self {
        self.alias = Some(alias);
        self
    }
    pub fn with_opt_alias(mut self, alias: Option<Name>) -> Self {
        self.alias = alias.map(Into::into);
        self
    }
    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
        self.directives.push(directive.into());
        self
    }
    pub fn with_directives(
        mut self,
        directives: impl IntoIterator<Item = Node<Directive>>,
    ) -> Self {
        self.directives.extend(directives);
        self
    }
    pub fn with_argument(mut self, name: Name, value: impl Into<Node<Value>>) -> Self {
        self.arguments.push((name, value).into());
        self
    }
    pub fn with_arguments(mut self, arguments: impl IntoIterator<Item = Node<Argument>>) -> Self {
        self.arguments.extend(arguments);
        self
    }
    pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
        self.selection_set.push(selection);
        self
    }
    pub fn with_selections(
        mut self,
        selections: impl IntoIterator<Item = impl Into<Selection>>,
    ) -> Self {
        self.selection_set.extend(selections);
        self
    }
    pub fn response_key(&self) -> &Name {
        self.alias.as_ref().unwrap_or(&self.name)
    }
    pub fn ty(&self) -> &Type {
        &self.definition.ty
    }
    pub fn inner_type_def<'a>(&self, schema: &'a Schema) -> Option<&'a schema::ExtendedType> {
        schema.types.get(self.ty().inner_named_type())
    }
    serialize_method!();
}
impl InlineFragment {
    pub fn with_type_condition(type_condition: NamedType) -> Self {
        let selection_set = SelectionSet::new(type_condition.clone());
        Self {
            type_condition: Some(type_condition),
            directives: DirectiveList::new(),
            selection_set,
        }
    }
    pub fn without_type_condition(parent_selection_set_type: NamedType) -> Self {
        Self {
            type_condition: None,
            directives: DirectiveList::new(),
            selection_set: SelectionSet::new(parent_selection_set_type),
        }
    }
    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
        self.directives.push(directive.into());
        self
    }
    pub fn with_directives(
        mut self,
        directives: impl IntoIterator<Item = Node<Directive>>,
    ) -> Self {
        self.directives.extend(directives);
        self
    }
    pub fn with_selection(mut self, selection: impl Into<Selection>) -> Self {
        self.selection_set.push(selection);
        self
    }
    pub fn with_selections(
        mut self,
        selections: impl IntoIterator<Item = impl Into<Selection>>,
    ) -> Self {
        self.selection_set.extend(selections);
        self
    }
    serialize_method!();
}
impl FragmentSpread {
    pub fn new(fragment_name: Name) -> Self {
        Self {
            fragment_name,
            directives: DirectiveList::new(),
        }
    }
    pub fn with_directive(mut self, directive: impl Into<Node<Directive>>) -> Self {
        self.directives.push(directive.into());
        self
    }
    pub fn with_directives(
        mut self,
        directives: impl IntoIterator<Item = Node<Directive>>,
    ) -> Self {
        self.directives.extend(directives);
        self
    }
    pub fn fragment_def<'a>(&self, document: &'a ExecutableDocument) -> Option<&'a Node<Fragment>> {
        document.fragments.get(&self.fragment_name)
    }
    serialize_method!();
}
impl FieldSet {
    pub fn parse(
        schema: &Schema,
        type_name: NamedType,
        source_text: impl Into<String>,
        path: impl AsRef<Path>,
    ) -> Self {
        Parser::new().parse_field_set(schema, type_name, source_text, path)
    }
    pub fn validate(&self, schema: &Schema) -> Result<(), DiagnosticList> {
        let mut errors = DiagnosticList::new(Some(schema.sources.clone()), self.sources.clone());
        validation::validate_field_set(&mut errors, schema, self);
        errors.into_result()
    }
    serialize_method!();
}
impl fmt::Display for SelectionPath {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.root {
            ExecutableDefinitionName::AnonymousOperation(operation_type) => {
                write!(f, "{operation_type}")?
            }
            ExecutableDefinitionName::NamedOperation(operation_type, name) => {
                write!(f, "{operation_type} {name}")?
            }
            ExecutableDefinitionName::Fragment(name) => write!(f, "fragment {name}")?,
        }
        for name in &self.nested_fields {
            write!(f, " → {name}")?
        }
        Ok(())
    }
}