//! Query context.

use std::{
    any::{Any, TypeId},
    collections::HashMap,
    fmt::{self, Debug, Display, Formatter},
    ops::Deref,
    sync::{Arc, Mutex},
};

use async_graphql_parser::types::ConstDirective;
use async_graphql_value::{Value as InputValue, Variables};
use indexmap::IndexMap;
use rustc_hash::FxHashMap;
use serde::{
    Serialize,
    ser::{SerializeSeq, Serializer},
};

use crate::{
    Error, InputType, Lookahead, MaybeSend, MaybeSync, Name, OneofObjectType, PathSegment, Pos,
    Positioned, Result, ServerError, ServerResult, UploadValue, Value,
    extensions::Extensions,
    parser::types::{
        Directive, Field, FragmentDefinition, OperationDefinition, Selection, SelectionSet,
    },
    schema::{IntrospectionMode, SchemaEnv},
};

/// Data related functions of the context.
pub trait DataContext<'a> {
    /// Gets the global data defined in the `Context` or `Schema`.
    ///
    /// If both `Schema` and `Query` have the same data type, the data in the
    /// `Query` is obtained.
    ///
    /// # Errors
    ///
    /// Returns a `Error` if the specified type data does not exist.
    fn data<D: Any + MaybeSend + MaybeSync>(&self) -> Result<&'a D>;

    /// Gets the global data defined in the `Context` or `Schema`.
    ///
    /// # Panics
    ///
    /// It will panic if the specified data type does not exist.
    #[deprecated(since = "7.0.12", note = "Use `data::<D>().unwrap()` instead.")]
    fn data_unchecked<D: Any + MaybeSend + MaybeSync>(&self) -> &'a D;

    /// Gets the global data defined in the `Context` or `Schema` or `None` if
    /// the specified type data does not exist.
    #[deprecated(since = "7.0.12", note = "Use `data::<D>().ok()` instead.")]
    fn data_opt<D: Any + MaybeSend + MaybeSync>(&self) -> Option<&'a D>;
}

#[cfg(not(feature = "no_send"))]
type BoxedAny = Box<dyn Any + Send + Sync>;

#[cfg(feature = "no_send")]
type BoxedAny = Box<dyn Any>;

/// Schema/Context data.
///
/// This is a type map, allowing you to store anything inside it.
#[derive(Default)]
pub struct Data(FxHashMap<TypeId, BoxedAny>);

impl Deref for Data {
    type Target = FxHashMap<TypeId, BoxedAny>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl Data {
    /// Insert data.
    pub fn insert<D: Any + MaybeSend + MaybeSync>(&mut self, data: D) {
        self.0.insert(TypeId::of::<D>(), Box::new(data));
    }

    pub(crate) fn merge(&mut self, other: Data) {
        self.0.extend(other.0);
    }
}

impl Debug for Data {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        f.debug_tuple("Data").finish()
    }
}

/// Context for `SelectionSet`
pub type ContextSelectionSet<'a> = ContextBase<'a, &'a Positioned<SelectionSet>>;

/// Context object for resolve field
pub type Context<'a> = ContextBase<'a, &'a Positioned<Field>>;

/// Context object for execute directive.
pub type ContextDirective<'a> = ContextBase<'a, &'a Positioned<Directive>>;

/// A segment in the path to the current query.
///
/// This is a borrowed form of [`PathSegment`](enum.PathSegment.html) used
/// during execution instead of passed back when errors occur.
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(untagged)]
pub enum QueryPathSegment<'a> {
    /// We are currently resolving an element in a list.
    Index(usize),
    /// We are currently resolving a field in an object.
    Name(&'a str),
}

/// A path to the current query.
///
/// The path is stored as a kind of reverse linked list.
#[derive(Debug, Clone, Copy)]
pub struct QueryPathNode<'a> {
    /// The parent node to this, if there is one.
    pub parent: Option<&'a QueryPathNode<'a>>,

    /// The current path segment being resolved.
    pub segment: QueryPathSegment<'a>,
}

impl serde::Serialize for QueryPathNode<'_> {
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        let mut seq = serializer.serialize_seq(None)?;
        self.try_for_each(|segment| seq.serialize_element(segment))?;
        seq.end()
    }
}

impl Display for QueryPathNode<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let mut first = true;
        self.try_for_each(|segment| {
            if !first {
                write!(f, ".")?;
            }
            first = false;

            match segment {
                QueryPathSegment::Index(idx) => write!(f, "{}", *idx),
                QueryPathSegment::Name(name) => write!(f, "{}", name),
            }
        })
    }
}

impl<'a> QueryPathNode<'a> {
    /// Get the current field name.
    ///
    /// This traverses all the parents of the node until it finds one that is a
    /// field name.
    pub fn field_name(&self) -> &str {
        std::iter::once(self)
            .chain(self.parents())
            .find_map(|node| match node.segment {
                QueryPathSegment::Name(name) => Some(name),
                QueryPathSegment::Index(_) => None,
            })
            .unwrap()
    }

    /// Get the path represented by `Vec<String>`; numbers will be stringified.
    #[must_use]
    pub fn to_string_vec(self) -> Vec<String> {
        let mut res = Vec::new();
        self.for_each(|s| {
            res.push(match s {
                QueryPathSegment::Name(name) => (*name).to_string(),
                QueryPathSegment::Index(idx) => idx.to_string(),
            });
        });
        res
    }

    /// Iterate over the parents of the node.
    pub fn parents(&self) -> Parents<'_> {
        Parents(self)
    }

    pub(crate) fn for_each<F: FnMut(&QueryPathSegment<'a>)>(&self, mut f: F) {
        let _ = self.try_for_each::<std::convert::Infallible, _>(|segment| {
            f(segment);
            Ok(())
        });
    }

    pub(crate) fn try_for_each<E, F: FnMut(&QueryPathSegment<'a>) -> Result<(), E>>(
        &self,
        mut f: F,
    ) -> Result<(), E> {
        self.try_for_each_ref(&mut f)
    }

    fn try_for_each_ref<E, F: FnMut(&QueryPathSegment<'a>) -> Result<(), E>>(
        &self,
        f: &mut F,
    ) -> Result<(), E> {
        if let Some(parent) = &self.parent {
            parent.try_for_each_ref(f)?;
        }
        f(&self.segment)
    }
}

/// An iterator over the parents of a
/// [`QueryPathNode`](struct.QueryPathNode.html).
#[derive(Debug, Clone)]
pub struct Parents<'a>(&'a QueryPathNode<'a>);

impl<'a> Parents<'a> {
    /// Get the current query path node, which the next call to `next` will get
    /// the parents of.
    #[must_use]
    pub fn current(&self) -> &'a QueryPathNode<'a> {
        self.0
    }
}

impl<'a> Iterator for Parents<'a> {
    type Item = &'a QueryPathNode<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let parent = self.0.parent;
        if let Some(parent) = parent {
            self.0 = parent;
        }
        parent
    }
}

impl std::iter::FusedIterator for Parents<'_> {}

/// Query context.
///
/// **This type is not stable and should not be used directly.**
#[derive(Clone)]
pub struct ContextBase<'a, T> {
    /// The current path node being resolved.
    pub path_node: Option<QueryPathNode<'a>>,
    /// If `true` means the current field is for introspection.
    pub(crate) is_for_introspection: bool,
    #[doc(hidden)]
    pub item: T,
    #[doc(hidden)]
    pub schema_env: &'a SchemaEnv,
    #[doc(hidden)]
    pub query_env: &'a QueryEnv,
    #[doc(hidden)]
    pub execute_data: Option<&'a Data>,
}

#[doc(hidden)]
pub struct QueryEnvInner {
    pub extensions: Extensions,
    pub variables: Variables,
    pub operation_name: Option<String>,
    pub operation: Positioned<OperationDefinition>,
    pub fragments: HashMap<Name, Positioned<FragmentDefinition>>,
    pub uploads: Vec<UploadValue>,
    pub session_data: Arc<Data>,
    pub query_data: Arc<Data>,
    pub http_headers: Mutex<http::HeaderMap>,
    pub introspection_mode: IntrospectionMode,
    pub errors: Mutex<Vec<ServerError>>,
}

#[doc(hidden)]
#[derive(Clone)]
pub struct QueryEnv(Arc<QueryEnvInner>);

impl Deref for QueryEnv {
    type Target = QueryEnvInner;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl QueryEnv {
    #[doc(hidden)]
    pub fn new(inner: QueryEnvInner) -> QueryEnv {
        QueryEnv(Arc::new(inner))
    }

    #[doc(hidden)]
    pub fn create_context<'a, T>(
        &'a self,
        schema_env: &'a SchemaEnv,
        path_node: Option<QueryPathNode<'a>>,
        item: T,
        execute_data: Option<&'a Data>,
    ) -> ContextBase<'a, T> {
        ContextBase {
            path_node,
            is_for_introspection: false,
            item,
            schema_env,
            query_env: self,
            execute_data,
        }
    }
}

impl<'a, T> DataContext<'a> for ContextBase<'a, T> {
    fn data<D: Any + MaybeSend + MaybeSync>(&self) -> Result<&'a D> {
        ContextBase::data::<D>(self)
    }

    fn data_unchecked<D: Any + MaybeSend + MaybeSync>(&self) -> &'a D {
        ContextBase::data::<D>(self).unwrap()
    }

    fn data_opt<D: Any + MaybeSend + MaybeSync>(&self) -> Option<&'a D> {
        ContextBase::data::<D>(self).ok()
    }
}

impl<'a, T> ContextBase<'a, T> {
    #[doc(hidden)]
    pub fn with_field(
        &'a self,
        field: &'a Positioned<Field>,
    ) -> ContextBase<'a, &'a Positioned<Field>> {
        ContextBase {
            path_node: Some(QueryPathNode {
                parent: self.path_node.as_ref(),
                segment: QueryPathSegment::Name(&field.node.response_key().node),
            }),
            is_for_introspection: self.is_for_introspection,
            item: field,
            schema_env: self.schema_env,
            query_env: self.query_env,
            execute_data: self.execute_data,
        }
    }

    #[doc(hidden)]
    pub fn with_selection_set(
        &self,
        selection_set: &'a Positioned<SelectionSet>,
    ) -> ContextBase<'a, &'a Positioned<SelectionSet>> {
        ContextBase {
            path_node: self.path_node,
            is_for_introspection: self.is_for_introspection,
            item: selection_set,
            schema_env: self.schema_env,
            query_env: self.query_env,
            execute_data: self.execute_data,
        }
    }

    #[doc(hidden)]
    pub fn set_error_path(&self, error: ServerError) -> ServerError {
        if let Some(node) = self.path_node {
            let mut path = Vec::new();
            node.for_each(|current_node| {
                path.push(match current_node {
                    QueryPathSegment::Name(name) => PathSegment::Field((*name).to_string()),
                    QueryPathSegment::Index(idx) => PathSegment::Index(*idx),
                })
            });
            ServerError { path, ..error }
        } else {
            error
        }
    }

    /// Report a resolver error.
    ///
    /// When implementing `OutputType`, if an error occurs, call this function
    /// to report this error and return `Value::Null`.
    pub fn add_error(&self, error: ServerError) {
        self.query_env.errors.lock().unwrap().push(error);
    }

    /// Gets the global data defined in the `Context` or `Schema`.
    ///
    /// If both `Schema` and `Query` have the same data type, the data in the
    /// `Query` is obtained.
    ///
    /// # Errors
    ///
    /// Returns a `Error` if the specified type data does not exist.
    pub fn data<D: Any + MaybeSend + MaybeSync>(&self) -> Result<&'a D> {
        self.execute_data
            .as_ref()
            .and_then(|execute_data| execute_data.get(&TypeId::of::<D>()))
            .or_else(|| self.query_env.query_data.0.get(&TypeId::of::<D>()))
            .or_else(|| self.query_env.session_data.0.get(&TypeId::of::<D>()))
            .or_else(|| self.schema_env.data.0.get(&TypeId::of::<D>()))
            .and_then(|d| d.downcast_ref::<D>())
            .ok_or_else(|| {
                Error::new(format!(
                    "Data `{}` does not exist.",
                    std::any::type_name::<D>()
                ))
            })
    }

    /// Gets the global data defined in the `Context` or `Schema`.
    ///
    /// # Panics
    ///
    /// It will panic if the specified data type does not exist.
    #[deprecated(since = "7.0.12", note = "Use `data::<D>().unwrap()` instead.")]
    pub fn data_unchecked<D: Any + MaybeSend + MaybeSync>(&self) -> &'a D {
        self.data::<D>().unwrap()
    }

    /// Gets the global data defined in the `Context` or `Schema` or `None` if
    /// the specified type data does not exist.
    #[deprecated(since = "7.0.12", note = "Use `data::<D>().ok()` instead.")]
    pub fn data_opt<D: Any + MaybeSend + MaybeSync>(&self) -> Option<&'a D> {
        self.data::<D>().ok()
    }

    /// Returns whether the HTTP header `key` is currently set on the response
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ::http::header::ACCESS_CONTROL_ALLOW_ORIGIN;
    /// use gqlrs::*;
    ///
    /// struct Query;
    ///
    /// #[Object]
    /// impl Query {
    ///     async fn greet(&self, ctx: &Context<'_>) -> String {
    ///         let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
    ///         assert!(!header_exists);
    ///
    ///         ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
    ///
    ///         let header_exists = ctx.http_header_contains("Access-Control-Allow-Origin");
    ///         assert!(header_exists);
    ///
    ///         String::from("Hello world")
    ///     }
    /// }
    /// ```
    pub fn http_header_contains(&self, key: impl http::header::AsHeaderName) -> bool {
        self.query_env
            .http_headers
            .lock()
            .unwrap()
            .contains_key(key)
    }

    /// Sets a HTTP header to response.
    ///
    /// If the header was not currently set on the response, then `None` is
    /// returned.
    ///
    /// If the response already contained this header then the new value is
    /// associated with this key and __all the previous values are
    /// removed__, however only a the first previous value is returned.
    ///
    /// See [`http::HeaderMap`] for more details on the underlying
    /// implementation
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ::http::{HeaderValue, header::ACCESS_CONTROL_ALLOW_ORIGIN};
    /// use gqlrs::*;
    ///
    /// struct Query;
    ///
    /// #[Object]
    /// impl Query {
    ///     async fn greet(&self, ctx: &Context<'_>) -> String {
    ///         // Headers can be inserted using the `http` constants
    ///         let was_in_headers = ctx.insert_http_header(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
    ///         assert_eq!(was_in_headers, None);
    ///
    ///         // They can also be inserted using &str
    ///         let was_in_headers = ctx.insert_http_header("Custom-Header", "1234");
    ///         assert_eq!(was_in_headers, None);
    ///
    ///         // If multiple headers with the same key are `inserted` then the most recent
    ///         // one overwrites the previous. If you want multiple headers for the same key, use
    ///         // `append_http_header` for subsequent headers
    ///         let was_in_headers = ctx.insert_http_header("Custom-Header", "Hello World");
    ///         assert_eq!(was_in_headers, Some(HeaderValue::from_static("1234")));
    ///
    ///         String::from("Hello world")
    ///     }
    /// }
    /// ```
    pub fn insert_http_header(
        &self,
        name: impl http::header::IntoHeaderName,
        value: impl TryInto<http::HeaderValue>,
    ) -> Option<http::HeaderValue> {
        if let Ok(value) = value.try_into() {
            self.query_env
                .http_headers
                .lock()
                .unwrap()
                .insert(name, value)
        } else {
            None
        }
    }

    /// Sets a HTTP header to response.
    ///
    /// If the header was not currently set on the response, then `false` is
    /// returned.
    ///
    /// If the response did have this header then the new value is appended to
    /// the end of the list of values currently associated with the key,
    /// however the key is not updated _(which is important for types that
    /// can be `==` without being identical)_.
    ///
    /// See [`http::HeaderMap`] for more details on the underlying
    /// implementation
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use ::http::header::SET_COOKIE;
    /// use gqlrs::*;
    ///
    /// struct Query;
    ///
    /// #[Object]
    /// impl Query {
    ///     async fn greet(&self, ctx: &Context<'_>) -> String {
    ///         // Insert the first instance of the header
    ///         ctx.insert_http_header(SET_COOKIE, "Chocolate Chip");
    ///
    ///         // Subsequent values should be appended
    ///         let header_already_exists = ctx.append_http_header("Set-Cookie", "Macadamia");
    ///         assert!(header_already_exists);
    ///
    ///         String::from("Hello world")
    ///     }
    /// }
    /// ```
    pub fn append_http_header(
        &self,
        name: impl http::header::IntoHeaderName,
        value: impl TryInto<http::HeaderValue>,
    ) -> bool {
        if let Ok(value) = value.try_into() {
            self.query_env
                .http_headers
                .lock()
                .unwrap()
                .append(name, value)
        } else {
            false
        }
    }

    fn var_value(&self, name: &str, pos: Pos) -> ServerResult<Option<Value>> {
        let def = self
            .query_env
            .operation
            .node
            .variable_definitions
            .iter()
            .find(|def| def.node.name.node == name)
            .ok_or_else(|| {
                ServerError::new(format!("Variable {} is not defined.", name), Some(pos))
            })?;

        // Preserve omitted variables as `None`; only explicit variable defaults become
        // values.
        Ok(self
            .query_env
            .variables
            .get(&def.node.name.node)
            .cloned()
            .or_else(|| {
                def.node
                    .default_value
                    .as_ref()
                    .map(|value| value.node.clone())
            }))
    }

    fn resolve_input_value_inner(
        &self,
        value: InputValue,
        pos: Pos,
    ) -> ServerResult<Option<Value>> {
        Ok(match value {
            InputValue::Variable(name) => self.var_value(&name, pos)?,
            InputValue::Null => Some(Value::Null),
            InputValue::Number(num) => Some(Value::Number(num)),
            InputValue::String(value) => Some(Value::String(value)),
            InputValue::Boolean(value) => Some(Value::Boolean(value)),
            InputValue::Binary(value) => Some(Value::Binary(value)),
            InputValue::Enum(value) => Some(Value::Enum(value)),
            InputValue::List(items) => {
                let mut resolved_items = Vec::with_capacity(items.len());
                for item in items {
                    // Lists still need a concrete slot value, so omitted entries become `null`.
                    resolved_items.push(
                        self.resolve_input_value_inner(item, pos)?
                            .unwrap_or(Value::Null),
                    );
                }
                Some(Value::List(resolved_items))
            }
            InputValue::Object(object) => {
                let mut resolved_object = IndexMap::with_capacity(object.len());
                for (name, value) in object {
                    // Omit object fields whose variables were not provided.
                    if let Some(value) = self.resolve_input_value_inner(value, pos)? {
                        resolved_object.insert(name, value);
                    }
                }
                Some(Value::Object(resolved_object))
            }
        })
    }

    pub(crate) fn resolve_input_value(
        &self,
        value: Positioned<InputValue>,
    ) -> ServerResult<Option<Value>> {
        self.resolve_input_value_inner(value.node, value.pos)
    }

    #[doc(hidden)]
    fn get_param_value<Q: InputType>(
        &self,
        arguments: &[(Positioned<Name>, Positioned<InputValue>)],
        name: &str,
        default: Option<fn() -> Q>,
    ) -> ServerResult<(Pos, Q)> {
        let value = arguments
            .iter()
            .find(|(n, _)| n.node.as_str() == name)
            .map(|(_, value)| value)
            .cloned();
        if value.is_none()
            && let Some(default) = default
        {
            return Ok((Pos::default(), default()));
        }
        let (pos, value) = match value {
            Some(value) => (value.pos, self.resolve_input_value(value)?),
            None => (Pos::default(), None),
        };
        InputType::parse(value)
            .map(|value| (pos, value))
            .map_err(|e| e.into_server_error(pos))
    }

    #[doc(hidden)]
    fn get_param_value_with_raw<Q: InputType>(
        &self,
        arguments: &[(Positioned<Name>, Positioned<InputValue>)],
        name: &str,
        default: Option<fn() -> Q>,
    ) -> ServerResult<(Pos, Q, Option<Value>)> {
        let value = arguments
            .iter()
            .find(|(n, _)| n.node.as_str() == name)
            .map(|(_, value)| value)
            .cloned();
        if value.is_none()
            && let Some(default) = default
        {
            return Ok((Pos::default(), default(), None));
        }
        let (pos, value) = match value {
            Some(value) => (value.pos, self.resolve_input_value(value)?),
            None => (Pos::default(), None),
        };
        InputType::parse(value.clone())
            .map(|parsed| (pos, parsed, value))
            .map_err(|e| e.into_server_error(pos))
    }

    #[doc(hidden)]
    #[must_use]
    pub fn with_index(&'a self, idx: usize) -> ContextBase<'a, T>
    where
        T: Copy,
    {
        ContextBase {
            path_node: Some(QueryPathNode {
                parent: self.path_node.as_ref(),
                segment: QueryPathSegment::Index(idx),
            }),
            is_for_introspection: self.is_for_introspection,
            item: self.item,
            schema_env: self.schema_env,
            query_env: self.query_env,
            execute_data: self.execute_data,
        }
    }
}

impl<'a> ContextBase<'a, &'a Positioned<Field>> {
    #[doc(hidden)]
    pub fn param_value<T: InputType>(
        &self,
        name: &str,
        default: Option<fn() -> T>,
    ) -> ServerResult<(Pos, T)> {
        self.get_param_value(&self.item.node.arguments, name, default)
    }

    #[doc(hidden)]
    pub fn param_value_with_raw<T: InputType>(
        &self,
        name: &str,
        default: Option<fn() -> T>,
    ) -> ServerResult<(Pos, T, Option<Value>)> {
        self.get_param_value_with_raw(&self.item.node.arguments, name, default)
    }

    #[doc(hidden)]
    pub fn oneof_param_value<T: OneofObjectType>(&self) -> ServerResult<(Pos, T)> {
        use indexmap::IndexMap;

        let mut map = IndexMap::new();

        for (name, value) in &self.item.node.arguments {
            // Skip omitted variable-backed arguments so input objects can observe
            // `Undefined`.
            if let Some(value) = self.resolve_input_value(value.clone())? {
                map.insert(name.node.clone(), value);
            }
        }

        InputType::parse(Some(Value::Object(map)))
            .map(|value| (self.item.pos, value))
            .map_err(|e| e.into_server_error(self.item.pos))
    }

    /// Creates a uniform interface to inspect the forthcoming selections.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use gqlrs::*;
    ///
    /// #[derive(SimpleObject)]
    /// struct Detail {
    ///     c: i32,
    ///     d: i32,
    /// }
    ///
    /// #[derive(SimpleObject)]
    /// struct MyObj {
    ///     a: i32,
    ///     b: i32,
    ///     detail: Detail,
    /// }
    ///
    /// struct Query;
    ///
    /// #[Object]
    /// impl Query {
    ///     async fn obj(&self, ctx: &Context<'_>) -> MyObj {
    ///         if ctx.look_ahead().field("a").exists() {
    ///             // This is a query like `obj { a }`
    ///         } else if ctx.look_ahead().field("detail").field("c").exists() {
    ///             // This is a query like `obj { detail { c } }`
    ///         } else {
    ///             // This query doesn't have `a`
    ///         }
    ///         unimplemented!()
    ///     }
    /// }
    /// ```
    pub fn look_ahead(&self) -> Lookahead<'_> {
        Lookahead::new(&self.query_env.fragments, &self.item.node, self)
    }

    /// Get the current field.
    ///
    /// # Examples
    ///
    /// ```rust
    /// use gqlrs::*;
    ///
    /// #[derive(SimpleObject)]
    /// struct MyObj {
    ///     a: i32,
    ///     b: i32,
    ///     c: i32,
    /// }
    ///
    /// pub struct Query;
    ///
    /// #[Object]
    /// impl Query {
    ///     async fn obj(&self, ctx: &Context<'_>) -> MyObj {
    ///         let fields = ctx
    ///             .field()
    ///             .selection_set()
    ///             .map(|field| field.name())
    ///             .collect::<Vec<_>>();
    ///         assert_eq!(fields, vec!["a", "b", "c"]);
    ///         MyObj { a: 1, b: 2, c: 3 }
    ///     }
    /// }
    ///
    /// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
    /// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
    /// assert!(schema.execute("{ obj { a b c }}").await.is_ok());
    /// assert!(schema.execute("{ obj { a ... { b c } }}").await.is_ok());
    /// assert!(
    ///     schema
    ///         .execute("{ obj { a ... BC }} fragment BC on MyObj { b c }")
    ///         .await
    ///         .is_ok()
    /// );
    /// # });
    /// ```
    pub fn field(&self) -> SelectionField<'_> {
        SelectionField {
            fragments: &self.query_env.fragments,
            field: &self.item.node,
            context: self,
        }
    }
}

impl<'a> ContextBase<'a, &'a Positioned<Directive>> {
    #[doc(hidden)]
    pub fn param_value<T: InputType>(
        &self,
        name: &str,
        default: Option<fn() -> T>,
    ) -> ServerResult<(Pos, T)> {
        self.get_param_value(&self.item.node.arguments, name, default)
    }
}

/// An item in a GraphQL selection set.
///
/// This enum represents the three kinds of selections defined by the GraphQL
/// specification: fields, fragment spreads, and inline fragments.  Unlike
/// [`SelectionField`], which only yields plain fields and silently flattens
/// fragments, `SelectionItem` preserves fragment boundaries and type
/// conditions so that callers can observe the original query structure.
///
/// # Examples
///
/// ```rust
/// use gqlrs::*;
///
/// #[derive(SimpleObject)]
/// struct MyObj {
///     a: i32,
///     b: i32,
///     c: i32,
/// }
///
/// struct Query;
///
/// #[Object]
/// impl Query {
///     async fn obj(&self, ctx: &Context<'_>) -> MyObj {
///         for item in ctx.field().selection_set_items() {
///             match item {
///                 SelectionItem::Field(field) => {
///                     // A plain field
///                     let _name = field.name();
///                 }
///                 SelectionItem::FragmentSpread(spread) => {
///                     // A named fragment spread (e.g. `... MyFragment`)
///                     let _fragment_name = spread.fragment_name();
///                     let _sub_items = spread.selection_set_items();
///                 }
///                 SelectionItem::InlineFragment(fragment) => {
///                     // An inline fragment (e.g. `... on MyType { ... }`)
///                     let _type_condition = fragment.type_condition();
///                     let _sub_items = fragment.selection_set_items();
///                 }
///             }
///         }
///         MyObj { a: 1, b: 2, c: 3 }
///     }
/// }
///
/// # tokio::runtime::Runtime::new().unwrap().block_on(async move {
/// let schema = Schema::new(Query, EmptyMutation, EmptySubscription);
/// assert!(schema.execute("{ obj { a b c }}").await.is_ok());
/// assert!(schema.execute("{ obj { a ... { b c } }}").await.is_ok());
/// assert!(
///     schema
///         .execute("{ obj { a ... on MyObj { b c } }}")
///         .await
///         .is_ok()
/// );
/// # });
/// ```
#[derive(Debug)]
pub enum SelectionItem<'a> {
    /// A plain field selection.
    Field(SelectionField<'a>),
    /// A named fragment spread (e.g. `... MyFragment`).
    FragmentSpread(FragmentSpreadItem<'a>),
    /// An inline fragment (e.g. `... on MyType { ... }`).
    InlineFragment(InlineFragmentItem<'a>),
}

/// A fragment spread item within a selection set.
///
/// Wraps a named fragment spread (`... FragmentName`) and provides access to
/// the fragment name and its nested selection set.
#[derive(Debug)]
pub struct FragmentSpreadItem<'a> {
    /// The name of the spread fragment.
    name: &'a str,
    /// The resolved items from the fragment's selection set.
    items: Vec<SelectionItem<'a>>,
}

impl<'a> FragmentSpreadItem<'a> {
    /// Returns the name of the spread fragment.
    #[inline]
    pub fn fragment_name(&self) -> &'a str {
        self.name
    }

    /// Returns the selection set items within the fragment.
    #[inline]
    pub fn selection_set_items(&self) -> &[SelectionItem<'a>] {
        &self.items
    }
}

/// An inline fragment item within a selection set.
///
/// Wraps an inline fragment (`... on TypeCondition { ... }`) and provides
/// access to its optional type condition and nested selection set.
#[derive(Debug)]
pub struct InlineFragmentItem<'a> {
    /// The optional type condition (the `on TypeName` part).
    type_condition: Option<&'a str>,
    /// The resolved items from the inline fragment's selection set.
    items: Vec<SelectionItem<'a>>,
}

impl<'a> InlineFragmentItem<'a> {
    /// Returns the type condition of the inline fragment, if any.
    ///
    /// For `... on MyType { ... }`, this returns `Some("MyType")`.
    /// For `... { ... }`, this returns `None`.
    #[inline]
    pub fn type_condition(&self) -> Option<&'a str> {
        self.type_condition
    }

    /// Returns the selection set items within the inline fragment.
    #[inline]
    pub fn selection_set_items(&self) -> &[SelectionItem<'a>] {
        &self.items
    }
}

/// Selection field.
#[derive(Clone, Copy)]
pub struct SelectionField<'a> {
    pub(crate) fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
    pub(crate) field: &'a Field,
    pub(crate) context: &'a Context<'a>,
}

impl<'a> SelectionField<'a> {
    /// Get the name of this field.
    #[inline]
    pub fn name(&self) -> &'a str {
        self.field.name.node.as_str()
    }

    /// Get the alias of this field.
    #[inline]
    pub fn alias(&self) -> Option<&'a str> {
        self.field.alias.as_ref().map(|alias| alias.node.as_str())
    }

    /// Get the directives of this field.
    pub fn directives(&self) -> ServerResult<Vec<ConstDirective>> {
        let mut directives = Vec::with_capacity(self.field.directives.len());

        for directive in &self.field.directives {
            let directive = &directive.node;

            let mut arguments = Vec::with_capacity(directive.arguments.len());
            for (name, value) in &directive.arguments {
                // Preserve omission when exposing directive arguments through the context API.
                if let Some(resolved_value) = self.context.resolve_input_value(value.clone())? {
                    arguments.push((name.clone(), value.position_node(resolved_value)));
                }
            }

            directives.push(ConstDirective {
                name: directive.name.clone(),
                arguments,
            });
        }

        Ok(directives)
    }

    /// Get the arguments of this field.
    pub fn arguments(&self) -> ServerResult<Vec<(Name, Value)>> {
        let mut arguments = Vec::with_capacity(self.field.arguments.len());
        for (name, value) in &self.field.arguments {
            // Preserve omission when exposing field arguments through the context API.
            if let Some(value) = self.context.resolve_input_value(value.clone())? {
                arguments.push((name.node.clone(), value));
            }
        }
        Ok(arguments)
    }

    /// Get all subfields of the current selection set.
    pub fn selection_set(&self) -> impl Iterator<Item = SelectionField<'a>> {
        SelectionFieldsIter {
            fragments: self.fragments,
            iter: vec![self.field.selection_set.node.items.iter()],
            context: self.context,
        }
    }

    /// Get all selection items of the current selection set, preserving
    /// fragment boundaries.
    ///
    /// Unlike [`selection_set`](Self::selection_set), which silently flattens
    /// fragment spreads and inline fragments into plain fields, this method
    /// returns [`SelectionItem`] values that let callers distinguish between
    /// fields, fragment spreads, and inline fragments.  This is useful when
    /// the original query structure matters — for example, when building a
    /// response that must mirror the requested selection shape.
    pub fn selection_set_items(&self) -> Vec<SelectionItem<'a>> {
        collect_selection_items(
            self.fragments,
            &self.field.selection_set.node.items,
            self.context,
        )
    }
}

/// Recursively collect [`SelectionItem`]s from a selection set.
fn collect_selection_items<'a>(
    fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
    items: &'a [Positioned<Selection>],
    context: &'a Context<'a>,
) -> Vec<SelectionItem<'a>> {
    let mut result = Vec::new();
    for selection in items {
        match &selection.node {
            Selection::Field(field) => {
                result.push(SelectionItem::Field(SelectionField {
                    fragments,
                    field: &field.node,
                    context,
                }));
            }
            Selection::FragmentSpread(fragment_spread) => {
                if let Some(fragment) = fragments.get(&fragment_spread.node.fragment_name.node) {
                    let items = collect_selection_items(
                        fragments,
                        &fragment.node.selection_set.node.items,
                        context,
                    );
                    result.push(SelectionItem::FragmentSpread(FragmentSpreadItem {
                        name: fragment_spread.node.fragment_name.node.as_str(),
                        items,
                    }));
                }
            }
            Selection::InlineFragment(inline_fragment) => {
                let items = collect_selection_items(
                    fragments,
                    &inline_fragment.node.selection_set.node.items,
                    context,
                );
                let type_condition = inline_fragment
                    .node
                    .type_condition
                    .as_ref()
                    .map(|tc| tc.node.on.node.as_str());
                result.push(SelectionItem::InlineFragment(InlineFragmentItem {
                    type_condition,
                    items,
                }));
            }
        }
    }
    result
}

impl Debug for SelectionField<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        struct DebugSelectionSet<'a>(Vec<SelectionField<'a>>);

        impl Debug for DebugSelectionSet<'_> {
            fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
                f.debug_list().entries(&self.0).finish()
            }
        }

        f.debug_struct(self.name())
            .field("name", &self.name())
            .field(
                "selection_set",
                &DebugSelectionSet(self.selection_set().collect()),
            )
            .finish()
    }
}

struct SelectionFieldsIter<'a> {
    fragments: &'a HashMap<Name, Positioned<FragmentDefinition>>,
    iter: Vec<std::slice::Iter<'a, Positioned<Selection>>>,
    context: &'a Context<'a>,
}

impl<'a> Iterator for SelectionFieldsIter<'a> {
    type Item = SelectionField<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let it = self.iter.last_mut()?;
            let item = it.next();

            match item {
                Some(selection) => match &selection.node {
                    Selection::Field(field) => {
                        return Some(SelectionField {
                            fragments: self.fragments,
                            field: &field.node,
                            context: self.context,
                        });
                    }
                    Selection::FragmentSpread(fragment_spread) => {
                        if let Some(fragment) =
                            self.fragments.get(&fragment_spread.node.fragment_name.node)
                        {
                            self.iter
                                .push(fragment.node.selection_set.node.items.iter());
                        }
                    }
                    Selection::InlineFragment(inline_fragment) => {
                        self.iter
                            .push(inline_fragment.node.selection_set.node.items.iter());
                    }
                },
                None => {
                    self.iter.pop();
                }
            }
        }
    }
}
