use std::{any, any::Any, mem};
use crate::{metadata::ConfigMetadata, utils::JsonObject, value::Pointer, SerializerOptions};
#[doc(hidden)] pub trait ConfigVisitor {
    fn visit_tag(&mut self, variant_index: usize);
    fn visit_param(&mut self, param_index: usize, value: &dyn any::Any);
    fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig);
    fn visit_nested_opt_config(&mut self, config_index: usize, config: Option<&dyn VisitConfig>) {
        if let Some(config) = config {
            self.visit_nested_config(config_index, config);
        }
    }
}
pub trait VisitConfig {
    fn visit_config(&self, visitor: &mut dyn ConfigVisitor);
}
#[derive(Debug)]
pub(crate) struct Serializer {
    metadata: &'static ConfigMetadata,
    current_prefix: Option<String>,
    json: JsonObject,
    options: SerializerOptions,
}
impl Serializer {
    pub(crate) fn new(
        metadata: &'static ConfigMetadata,
        prefix: &str,
        options: SerializerOptions,
    ) -> Self {
        Self {
            metadata,
            current_prefix: options.flat.then(|| prefix.to_owned()),
            json: serde_json::Map::new(),
            options,
        }
    }
    pub(crate) fn into_inner(self) -> JsonObject {
        self.json
    }
    fn insert(&mut self, param_name: &str, value: serde_json::Value) {
        let key = if let Some(prefix) = &self.current_prefix {
            Pointer(prefix).join(param_name)
        } else {
            param_name.to_owned()
        };
        self.json.insert(key, value);
    }
}
impl ConfigVisitor for Serializer {
    fn visit_tag(&mut self, variant_index: usize) {
        let tag = self.metadata.tag.unwrap();
        let tag_variant = &tag.variants[variant_index];
        let should_insert = !self.options.diff_with_default
            || !tag
                .default_variant
                .is_some_and(|default_variant| default_variant.rust_name == tag_variant.rust_name);
        if should_insert {
            self.insert(tag.param.name, tag_variant.name.into());
        }
    }
    fn visit_param(&mut self, param_index: usize, value: &dyn Any) {
        let param = &self.metadata.params[param_index];
        let mut value = param.deserializer.serialize_param(value);
        let should_insert = !self.options.diff_with_default
            || param.fallback.is_some()
            || param.default_value_json().as_ref() != Some(&value);
        if should_insert {
            if let (Some(placeholder), true) = (
                &self.options.secret_placeholder,
                param.type_description().contains_secrets(),
            ) {
                value = placeholder.clone().into();
            }
            self.insert(param.name, value);
        }
    }
    fn visit_nested_config(&mut self, config_index: usize, config: &dyn VisitConfig) {
        let nested_metadata = &self.metadata.nested_configs[config_index];
        let prev_metadata = mem::replace(&mut self.metadata, nested_metadata.meta);
        if nested_metadata.name.is_empty() {
            config.visit_config(self);
        } else if let Some(prefix) = &mut self.current_prefix {
            let new_prefix = Pointer(prefix).join(nested_metadata.name);
            let prev_prefix = mem::replace(prefix, new_prefix);
            config.visit_config(self);
            self.current_prefix = Some(prev_prefix);
        } else {
            let mut prev_json = mem::take(&mut self.json);
            config.visit_config(self);
            let nested_json = mem::take(&mut self.json);
            let should_insert = !self.options.diff_with_default || !nested_json.is_empty();
            if should_insert {
                prev_json.insert(nested_metadata.name.to_owned(), nested_json.into());
            }
            self.json = prev_json;
        }
        self.metadata = prev_metadata;
    }
}
#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use super::*;
    use crate::{
        metadata::ConfigMetadata,
        testonly::{ConfigWithNesting, DefaultingConfig, EnumConfig, NestedConfig, SimpleEnum},
        DescribeConfig,
    };
    #[derive(Debug)]
    struct PersistingVisitor {
        metadata: &'static ConfigMetadata,
        tag: Option<&'static str>,
        param_values: HashMap<&'static str, serde_json::Value>,
    }
    impl PersistingVisitor {
        fn new(metadata: &'static ConfigMetadata) -> Self {
            Self {
                metadata,
                tag: None,
                param_values: HashMap::new(),
            }
        }
    }
    impl ConfigVisitor for PersistingVisitor {
        fn visit_tag(&mut self, variant_index: usize) {
            assert!(self.tag.is_none());
            self.tag = Some(self.metadata.tag.unwrap().variants[variant_index].rust_name);
        }
        fn visit_param(&mut self, param_index: usize, value: &dyn any::Any) {
            let param = &self.metadata.params[param_index];
            let prev_value = self
                .param_values
                .insert(param.name, param.deserializer.serialize_param(value));
            assert!(
                prev_value.is_none(),
                "Param value {} is visited twice",
                param.name
            );
        }
        fn visit_nested_config(&mut self, _config_index: usize, _config: &dyn VisitConfig) {
            }
    }
    #[test]
    fn visiting_struct_config() {
        let config = DefaultingConfig::default();
        let mut visitor = PersistingVisitor::new(&DefaultingConfig::DESCRIPTION);
        config.visit_config(&mut visitor);
        assert_eq!(visitor.tag, None);
        assert_eq!(
            visitor.param_values,
            HashMap::from([
                ("float", serde_json::Value::Null),
                ("set", serde_json::json!([])),
                ("int", 12_u32.into()),
                ("url", "https://example.com/".into())
            ])
        );
    }
    #[test]
    fn visiting_enum_config() {
        let config = EnumConfig::First;
        let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
        config.visit_config(&mut visitor);
        assert_eq!(visitor.tag, Some("First"));
        assert_eq!(visitor.param_values, HashMap::new());
        let config = EnumConfig::Nested(NestedConfig::default_nested());
        let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
        config.visit_config(&mut visitor);
        assert_eq!(visitor.tag, Some("Nested"));
        assert_eq!(visitor.param_values, HashMap::new());
        let config = EnumConfig::WithFields {
            string: Some("test".to_owned()),
            flag: true,
            set: [1].into(),
        };
        let mut visitor = PersistingVisitor::new(&EnumConfig::DESCRIPTION);
        config.visit_config(&mut visitor);
        assert_eq!(visitor.tag, Some("WithFields"));
        assert_eq!(
            visitor.param_values,
            HashMap::from([
                ("string", "test".into()),
                ("flag", true.into()),
                ("set", serde_json::json!([1]))
            ])
        );
    }
    #[test]
    fn serializing_config() {
        let config = EnumConfig::WithFields {
            string: Some("test".to_owned()),
            flag: true,
            set: [1].into(),
        };
        let json = SerializerOptions::default().serialize(&config);
        assert_eq!(
            serde_json::Value::from(json),
            serde_json::json!({
                "type": "WithFields",
                "string": "test",
                "flag": true,
                "set": [1]
            })
        );
    }
    #[test]
    fn serializing_nested_config() {
        let config = ConfigWithNesting {
            value: 23,
            merged: String::new(),
            nested: NestedConfig {
                simple_enum: SimpleEnum::First,
                other_int: 42,
                map: HashMap::new(),
            },
        };
        let json = SerializerOptions::default().serialize(&config);
        assert_eq!(
            serde_json::Value::from(json),
            serde_json::json!({
                "value": 23,
                "merged": "",
                "nested": {
                    "renamed": "first",
                    "other_int": 42,
                    "map": {},
                },
            })
        );
        let json = SerializerOptions::diff_with_default().serialize(&config);
        assert_eq!(
            serde_json::Value::from(json),
            serde_json::json!({
                "value": 23,
                "nested": {
                    "renamed": "first",
                },
            })
        );
        let json = SerializerOptions::default().flat(true).serialize(&config);
        assert_eq!(
            serde_json::Value::from(json),
            serde_json::json!({
                "value": 23,
                "merged": "",
                "nested.renamed": "first",
                "nested.other_int": 42,
                "nested.map": {},
            })
        );
        let json = SerializerOptions::diff_with_default()
            .flat(true)
            .serialize(&config);
        assert_eq!(
            serde_json::Value::from(json),
            serde_json::json!({
                "value": 23,
                "nested.renamed": "first",
            })
        );
    }
}