use crate::Result;
use std::path::PathBuf;

pub trait TransformsValues {
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
        Self: Sized;
}

impl TransformsValues for String {
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
    {
        let b = render(self);
        Ok(b)
    }
}

impl TransformsValues for PathBuf {
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
    {
        self.to_str()
            .ok_or_else(|| crate::Error::Unknown("failed to stringify path".to_owned()))
            .map(|s| PathBuf::from(render(s)))
    }
}

impl<T> TransformsValues for Vec<T>
where
    T: TransformsValues,
{
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
    {
        let mut b = Vec::with_capacity(self.len());
        for i in self {
            b.push(i.transforms_values(render)?);
        }
        Ok(b)
    }
}

impl<T> TransformsValues for Option<T>
where
    T: TransformsValues,
{
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
    {
        match self {
            None => Ok(None),
            Some(v) => Ok(Some(v.transforms_values(render)?)),
        }
    }
}

impl TransformsValues for serde_yaml::Value {
    fn transforms_values<F>(&self, render: &F) -> Result<Self>
    where
        F: Fn(&str) -> String,
    {
        match self {
            serde_yaml::Value::String(v) => {
                Ok(serde_yaml::Value::String(v.transforms_values(render)?))
            }
            serde_yaml::Value::Sequence(s) => {
                Ok(serde_yaml::Value::Sequence(s.transforms_values(render)?))
            }
            serde_yaml::Value::Mapping(m) => {
                let mut expected = serde_yaml::Mapping::new();
                for (k, v) in m {
                    expected.insert(k.transforms_values(render)?, v.transforms_values(render)?);
                }
                Ok(serde_yaml::Value::Mapping(expected))
            }
            _ => Ok(self.clone()),
        }
    }
}
