use std::{collections::HashMap, path::PathBuf, sync::LazyLock};

static CWD: LazyLock<PathBuf> = LazyLock::new(|| {
    std::env::current_dir()
        .expect("failed to get current working directory")
        .canonicalize()
        .expect("failed to canonicalize current working directory")
});

#[derive(Debug, miette::Diagnostic, thiserror::Error)]
pub enum ConfigError {
    #[error("failed to read config file")]
    ReadFile(#[source] std::io::Error),
    #[error("failed to deserialize config file")]
    Deserialize(#[source] serde_toml::de::Error),
    #[error("failed to create missing directory")]
    CreateDir(#[source] std::io::Error),
    #[error("failed to canonicalize config's file paths ({1})")]
    Canonicalize(#[source] std::io::Error, PathBuf),
}

#[derive(Debug, Clone, serde::Deserialize)]
#[serde(default)]
pub struct Config {
    pub build: PathBuf,
    pub r#static: PathBuf,
    pub templates: PathBuf,
    pub vault: PathBuf,

    pub port: u16,

    pub default_template: Option<String>,

    pub assets: Vec<AssetConfig>,

    pub modules: ModulesConfig,

    pub pages: Vec<PageConfig>,

    pub data: Option<serde_json::Value>,
}

impl Config {
    pub fn load() -> Result<Self, ConfigError> {
        let path = CWD.join("sable.toml");

        let config = if path.exists() {
            let config = std::fs::read_to_string(path).map_err(ConfigError::ReadFile)?;

            let mut config: Self =
                serde_toml::from_str(&config).map_err(ConfigError::Deserialize)?;

            config.canonicalize()?;

            config
        } else {
            Self::default()
        };

        Ok(config)
    }

    fn canonicalize(&mut self) -> Result<(), ConfigError> {
        std::fs::create_dir_all(&self.build).map_err(ConfigError::CreateDir)?;

        self.build = self
            .build
            .canonicalize()
            .map_err(|err| ConfigError::Canonicalize(err, self.build.clone()))?;
        self.r#static = self
            .r#static
            .canonicalize()
            .map_err(|err| ConfigError::Canonicalize(err, self.r#static.clone()))?;
        self.templates = self
            .templates
            .canonicalize()
            .map_err(|err| ConfigError::Canonicalize(err, self.templates.clone()))?;
        self.vault = self
            .vault
            .canonicalize()
            .map_err(|err| ConfigError::Canonicalize(err, self.vault.clone()))?;

        Ok(())
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            build: CWD.join("dist"),
            r#static: CWD.join("static"),
            templates: CWD.join("templates"),
            vault: CWD.join("content"),

            port: 3000,

            default_template: None,

            assets: Vec::new(),

            modules: ModulesConfig::default(),

            pages: Vec::new(),

            data: None,
        }
    }
}

#[derive(Debug, Clone, serde::Deserialize)]
pub struct AssetConfig {
    pub output: PathBuf,
    pub command: String,
    #[serde(default)]
    pub env: Option<HashMap<String, String>>,
}

#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(default)]
pub struct ModulesConfig {
    pub rss: RssConfig,
}

#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(default)]
pub struct RssConfig {
    pub enabled: bool,
    pub template: String,
    pub path: String,
}

#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(default)]
pub struct PageConfig {
    pub template: String,
    pub path: String,
    pub metadata: Option<serde_json::Value>,
}
