use anyhow::{Context, Result};
use indexmap::IndexMap;
use serde::Deserialize;
use std::{
    fs,
    path::{Path, PathBuf},
    str::FromStr,
};
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Dependency {
    pub path: PathBuf,
}
impl FromStr for Dependency {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self { path: s.into() })
    }
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct InstantiationArg {
    pub instance: String,
    #[serde(default)]
    pub export: Option<String>,
}
impl FromStr for InstantiationArg {
    type Err = ();
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self {
            instance: s.to_string(),
            export: None,
        })
    }
}
#[derive(Debug, Clone, Deserialize, Default)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Instantiation {
    pub dependency: Option<String>,
    #[serde(default, deserialize_with = "de::index_map")]
    pub arguments: IndexMap<String, InstantiationArg>,
}
#[derive(Default, Debug, Clone, Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct Config {
    #[serde(skip)]
    pub dir: PathBuf,
    #[serde(default)]
    pub definitions: Vec<PathBuf>,
    #[serde(default)]
    pub search_paths: Vec<PathBuf>,
    #[serde(default)]
    pub skip_validation: bool,
    #[serde(default)]
    pub import_components: bool,
    #[serde(default)]
    pub disallow_imports: bool,
    #[serde(default, deserialize_with = "de::index_map")]
    pub dependencies: IndexMap<String, Dependency>,
    #[serde(default)]
    pub instantiations: IndexMap<String, Instantiation>,
}
impl Config {
    pub fn from_file(path: impl Into<PathBuf>) -> Result<Self> {
        let path = path.into();
        log::info!("reading configuration file `{}`", path.display());
        let config = fs::read_to_string(&path)
            .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
        let mut config: Config = serde_yaml::from_str(&config)
            .with_context(|| format!("failed to parse configuration file `{}`", path.display()))?;
        config.dir = path.parent().map(Path::to_path_buf).unwrap_or_default();
        Ok(config)
    }
    pub fn dependency_name<'a>(&'a self, instance: &'a str) -> &'a str {
        self.instantiations
            .get(instance)
            .and_then(|i| i.dependency.as_deref())
            .unwrap_or(instance)
    }
}
mod de {
    use indexmap::IndexMap;
    use serde::{
        de::{self, MapAccess, Visitor},
        Deserialize, Deserializer,
    };
    use std::{fmt, hash::Hash, marker::PhantomData, str::FromStr};
    pub fn index_map<'de, K, V, D>(deserializer: D) -> Result<IndexMap<K, V>, D::Error>
    where
        K: Hash + Eq + Deserialize<'de>,
        V: Deserialize<'de> + FromStr<Err = ()>,
        D: Deserializer<'de>,
    {
        deserializer.deserialize_map(MapVisitor(PhantomData))
    }
    struct MapVisitor<K, V>(PhantomData<(K, V)>);
    impl<'de, K, V> Visitor<'de> for MapVisitor<K, V>
    where
        K: Hash + Eq + Deserialize<'de>,
        V: Deserialize<'de> + FromStr<Err = ()>,
    {
        type Value = IndexMap<K, V>;
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("map")
        }
        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            struct Wrapper<V>(V);
            impl<'de, V> Deserialize<'de> for Wrapper<V>
            where
                V: Deserialize<'de> + FromStr<Err = ()>,
            {
                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
                where
                    D: Deserializer<'de>,
                {
                    Ok(Self(
                        deserializer.deserialize_any(StringOrMapVisitor(PhantomData))?,
                    ))
                }
            }
            let mut map = Self::Value::with_capacity(access.size_hint().unwrap_or(0));
            while let Some((key, value)) = access.next_entry::<_, Wrapper<V>>()? {
                map.insert(key, value.0);
            }
            Ok(map)
        }
    }
    struct StringOrMapVisitor<V>(PhantomData<V>);
    impl<'de, V> Visitor<'de> for StringOrMapVisitor<V>
    where
        V: Deserialize<'de> + FromStr<Err = ()>,
    {
        type Value = V;
        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
            formatter.write_str("string or map")
        }
        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
        where
            E: de::Error,
        {
            Ok(V::from_str(value).unwrap())
        }
        fn visit_map<M>(self, access: M) -> Result<Self::Value, M::Error>
        where
            M: MapAccess<'de>,
        {
            Deserialize::deserialize(de::value::MapAccessDeserializer::new(access))
        }
    }
}