use std::{
    fmt::{self, Debug},
    sync::Arc,
};
use crate::{
    dynamic::SchemaError,
    registry::{MetaType, Registry, ScalarValidatorFn},
    Value,
};
pub struct Scalar {
    pub(crate) name: String,
    pub(crate) description: Option<String>,
    pub(crate) specified_by_url: Option<String>,
    pub(crate) validator: Option<ScalarValidatorFn>,
    inaccessible: bool,
    tags: Vec<String>,
}
impl Debug for Scalar {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Scalar")
            .field("name", &self.name)
            .field("description", &self.description)
            .field("specified_by_url", &self.specified_by_url)
            .field("inaccessible", &self.inaccessible)
            .field("tags", &self.tags)
            .finish()
    }
}
impl Scalar {
    #[inline]
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            description: None,
            specified_by_url: None,
            validator: None,
            inaccessible: false,
            tags: Vec::new(),
        }
    }
    impl_set_description!();
    impl_set_inaccessible!();
    impl_set_tags!();
    #[inline]
    pub fn validator(self, validator: impl Fn(&Value) -> bool + Send + Sync + 'static) -> Self {
        Self {
            validator: Some(Arc::new(validator)),
            ..self
        }
    }
    #[inline]
    pub(crate) fn validate(&self, value: &Value) -> bool {
        match &self.validator {
            Some(validator) => (validator)(value),
            None => true,
        }
    }
    #[inline]
    pub fn specified_by_url(self, specified_by_url: impl Into<String>) -> Self {
        Self {
            specified_by_url: Some(specified_by_url.into()),
            ..self
        }
    }
    #[inline]
    pub fn type_name(&self) -> &str {
        &self.name
    }
    pub(crate) fn register(&self, registry: &mut Registry) -> Result<(), SchemaError> {
        registry.types.insert(
            self.name.clone(),
            MetaType::Scalar {
                name: self.name.clone(),
                description: self.description.clone(),
                is_valid: self.validator.clone(),
                visible: None,
                inaccessible: self.inaccessible,
                tags: self.tags.clone(),
                specified_by_url: self.specified_by_url.clone(),
            },
        );
        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use async_graphql_parser::Pos;
    use crate::{dynamic::*, value, PathSegment, ServerError};
    #[tokio::test]
    async fn custom_scalar() {
        let scalar = Scalar::new("MyScalar");
        let query = Object::new("Query").field(Field::new(
            "value",
            TypeRef::named_nn(scalar.type_name()),
            |_| {
                FieldFuture::new(async move {
                    Ok(Some(value!({
                        "a": 1,
                        "b": "abc",
                    })))
                })
            },
        ));
        let schema = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(scalar)
            .finish()
            .unwrap();
        assert_eq!(
            schema
                .execute("{ value }")
                .await
                .into_result()
                .unwrap()
                .data,
            value!({
                "value": {
                    "a": 1,
                    "b": "abc",
                }
            })
        );
    }
    #[tokio::test]
    async fn invalid_scalar_value() {
        let scalar = Scalar::new("MyScalar");
        let query = Object::new("Query").field(Field::new(
            "value",
            TypeRef::named_nn(scalar.type_name()),
            |_| FieldFuture::new(async move { Ok(Some(FieldValue::owned_any(10i32))) }),
        ));
        let schema = Schema::build(query.type_name(), None, None)
            .register(query)
            .register(scalar)
            .finish()
            .unwrap();
        assert_eq!(
            schema.execute("{ value }").await.into_result().unwrap_err(),
            vec![ServerError {
                message: "internal: invalid value for scalar \"MyScalar\", expected \"FieldValue::Value\""
                    .to_owned(),
                source: None,
                locations: vec![Pos { column: 3, line: 1 }],
                path: vec![PathSegment::Field("value".to_owned())],
                extensions: None,
            }]
        );
    }
}