use indexmap::IndexMap;
use crate::{
    dynamic::InputValue,
    registry::{MetaDirectiveInvocation, MetaInputValue, MetaType, Registry},
};
#[derive(Debug)]
pub struct InputObject {
    pub(crate) name: String,
    pub(crate) description: Option<String>,
    pub(crate) fields: IndexMap<String, InputValue>,
    pub(crate) oneof: bool,
    inaccessible: bool,
    tags: Vec<String>,
    directive_invocations: Vec<MetaDirectiveInvocation>,
}
impl InputObject {
    #[inline]
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            description: None,
            fields: Default::default(),
            oneof: false,
            inaccessible: false,
            tags: Vec::new(),
            directive_invocations: Vec::new(),
        }
    }
    impl_set_description!();
    impl_set_inaccessible!();
    impl_set_tags!();
    #[inline]
    pub fn field(mut self, field: InputValue) -> Self {
        assert!(
            !self.fields.contains_key(&field.name),
            "Field `{}` already exists",
            field.name
        );
        self.fields.insert(field.name.clone(), field);
        self
    }
    pub fn oneof(self) -> Self {
        Self {
            oneof: true,
            ..self
        }
    }
    #[inline]
    pub fn type_name(&self) -> &str {
        &self.name
    }
    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), super::SchemaError> {
        let mut input_fields = IndexMap::new();
        for field in self.fields.values() {
            input_fields.insert(
                field.name.clone(),
                MetaInputValue {
                    name: field.name.clone(),
                    description: field.description.clone(),
                    ty: field.ty.to_string(),
                    default_value: field.default_value.as_ref().map(ToString::to_string),
                    visible: None,
                    inaccessible: self.inaccessible,
                    tags: self.tags.clone(),
                    is_secret: false,
                    directive_invocations: field.directive_invocations.clone(),
                },
            );
        }
        registry.types.insert(
            self.name.clone(),
            MetaType::InputObject {
                name: self.name.clone(),
                description: self.description.clone(),
                input_fields,
                visible: None,
                inaccessible: self.inaccessible,
                tags: self.tags.clone(),
                rust_typename: None,
                oneof: self.oneof,
                directive_invocations: self.directive_invocations.clone(),
            },
        );
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use crate::{dynamic::*, value, Pos, ServerError, Value};
    #[tokio::test]
    async fn input_object() {
        let myinput = InputObject::new("MyInput")
            .field(InputValue::new("a", TypeRef::named_nn(TypeRef::INT)))
            .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT)));
        let query = Object::new("Query").field(
            Field::new("add", TypeRef::named_nn(TypeRef::INT), |ctx| {
                FieldFuture::new(async move {
                    let input = ctx.args.try_get("input")?;
                    let input = input.object()?;
                    let a = input.try_get("a")?.i64()?;
                    let b = input.try_get("b")?.i64()?;
                    Ok(Some(Value::from(a + b)))
                })
            })
            .argument(InputValue::new(
                "input",
                TypeRef::named_nn(myinput.type_name()),
            )),
        );
        let schema = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(myinput)
            .finish()
            .unwrap();
        assert_eq!(
            schema
                .execute("{ add(input: {a: 10, b: 20}) }")
                .await
                .into_result()
                .unwrap()
                .data,
            value!({
                "add": 30
            })
        );
    }
    #[tokio::test]
    async fn oneof_input_object() {
        let myinput = InputObject::new("MyInput")
            .oneof()
            .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
            .field(InputValue::new("b", TypeRef::named(TypeRef::INT)));
        let query = Object::new("Query").field(
            Field::new("add10", TypeRef::named_nn(TypeRef::INT), |ctx| {
                FieldFuture::new(async move {
                    let input = ctx.args.try_get("input")?;
                    let input = input.object()?;
                    Ok(Some(Value::from(if let Some(a) = input.get("a") {
                        a.i64()? + 10
                    } else if let Some(b) = input.get("b") {
                        b.i64()? + 10
                    } else {
                        unreachable!()
                    })))
                })
            })
            .argument(InputValue::new(
                "input",
                TypeRef::named_nn(myinput.type_name()),
            )),
        );
        let schema = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(myinput)
            .finish()
            .unwrap();
        assert_eq!(
            schema
                .execute("{ add10(input: {a: 10}) }")
                .await
                .into_result()
                .unwrap()
                .data,
            value!({
                "add10": 20
            })
        );
        assert_eq!(
            schema
                .execute("{ add10(input: {b: 20}) }")
                .await
                .into_result()
                .unwrap()
                .data,
            value!({
                "add10": 30
            })
        );
        assert_eq!(
            schema
                .execute("{ add10(input: {}) }")
                .await
                .into_result()
                .unwrap_err(),
            vec![ServerError {
                message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(),
                source: None,
                locations: vec![Pos { column: 9, line: 1 }],
                path: vec![],
                extensions: None,
            }]
        );
        assert_eq!(
            schema
                .execute("{ add10(input: { a: 10, b: 20 }) }")
                .await
                .into_result()
                .unwrap_err(),
            vec![ServerError {
                message: "Invalid value for argument \"input\", Oneof input objects requires have exactly one field".to_owned(),
                source: None,
                locations: vec![Pos { column: 9, line: 1 }],
                path: vec![],
                extensions: None,
            }]
        );
    }
    #[tokio::test]
    async fn invalid_oneof_input_object() {
        let myinput = InputObject::new("MyInput")
            .oneof()
            .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
            .field(InputValue::new("b", TypeRef::named_nn(TypeRef::INT)));
        let query = Object::new("Query").field(
            Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| {
                FieldFuture::new(async move { Ok(Some(Value::from(10))) })
            })
            .argument(InputValue::new(
                "input",
                TypeRef::named_nn(myinput.type_name()),
            )),
        );
        let err = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(myinput)
            .finish()
            .unwrap_err();
        assert_eq!(err.0, "Field \"MyInput.b\" must be nullable".to_string());
        let myinput = InputObject::new("MyInput")
            .oneof()
            .field(InputValue::new("a", TypeRef::named(TypeRef::INT)))
            .field(InputValue::new("b", TypeRef::named(TypeRef::INT)).default_value(value!(10)));
        let query = Object::new("Query").field(
            Field::new("value", TypeRef::named_nn(TypeRef::INT), |_| {
                FieldFuture::new(async move { Ok(Some(Value::from(10))) })
            })
            .argument(InputValue::new(
                "input",
                TypeRef::named_nn(myinput.type_name()),
            )),
        );
        let err = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(myinput)
            .finish()
            .unwrap_err();
        assert_eq!(
            err.0,
            "Field \"MyInput.b\" must not have a default value".to_string()
        );
    }
}