use serde::Deserialize;
use std::path::PathBuf;

#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
pub(crate) struct PlantUmlRuntimeConfig {
    #[serde(default, rename = "plantuml_cache_dir", alias = "plantumlCacheDir")]
    cache_dir: String,
}

impl PlantUmlRuntimeConfig {
    pub(crate) fn from_value(value: &serde_json::Value) -> Result<Self, String> {
        if value.is_null() {
            return Ok(Self::default());
        }
        let config: Self =
            serde_json::from_value(value.clone()).map_err(|error| error.to_string())?;
        config.validate()?;
        Ok(config)
    }

    pub(crate) fn cache_dir(&self) -> Option<PathBuf> {
        let value = self.cache_dir.trim();
        (!value.is_empty()).then(|| PathBuf::from(value))
    }

    fn validate(&self) -> Result<(), String> {
        if self.cache_dir.chars().any(char::is_control) {
            return Err("plantuml_cache_dir must not contain control characters".to_string());
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::PlantUmlRuntimeConfig;

    #[test]
    fn runtime_config_accepts_snake_and_camel_cache_dir() -> Result<(), String> {
        let snake = PlantUmlRuntimeConfig::from_value(&serde_json::json!({
            "plantuml_cache_dir": "/tmp/krr-cache",
        }))?;
        let camel = PlantUmlRuntimeConfig::from_value(&serde_json::json!({
            "plantumlCacheDir": "/tmp/krr-cache",
        }))?;

        assert_eq!(snake.cache_dir(), camel.cache_dir());
        Ok(())
    }

    #[test]
    fn runtime_config_rejects_control_characters() {
        let result = PlantUmlRuntimeConfig::from_value(&serde_json::json!({
            "plantuml_cache_dir": "/tmp/krr\ncache",
        }));

        assert!(matches!(
            result,
            Err(error) if error.contains("plantuml_cache_dir")
        ));
    }
}
