use daipendency_extractor::Extractor;
use daipendency_extractor_rust::RustExtractor;
use std::collections::HashMap;
use std::sync::OnceLock;

type ExtractorInitialiser = fn() -> Box<dyn Extractor + Send + Sync>;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Language {
    Rust,
}

pub struct LanguageConfig {
    pub name: &'static str,
    pub extractor_initialiser: ExtractorInitialiser,
}

static LANGUAGE_CONFIGS: OnceLock<HashMap<Language, LanguageConfig>> = OnceLock::new();

fn initialise_config() -> HashMap<Language, LanguageConfig> {
    let mut configs = HashMap::new();
    configs.insert(
        Language::Rust,
        LanguageConfig {
            name: "rust",
            extractor_initialiser: || Box::new(RustExtractor::new()),
        },
    );
    configs
}

impl LanguageConfig {
    pub fn get_from_language(language: Language) -> &'static LanguageConfig {
        Self::get_all()
            .get(&language)
            .unwrap_or_else(|| panic!("No config found for language {:?}", language))
    }

    pub fn get_all() -> &'static HashMap<Language, LanguageConfig> {
        LANGUAGE_CONFIGS.get_or_init(initialise_config)
    }
}

impl std::str::FromStr for Language {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        LanguageConfig::get_all()
            .iter()
            .find(|(_, config)| config.name == s)
            .map(|(lang, _)| *lang)
            .ok_or_else(|| anyhow::anyhow!("Unknown language '{}'", s))
    }
}

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

    mod language_config {
        use super::*;

        #[test]
        fn get_from_language() {
            let config = LanguageConfig::get_from_language(Language::Rust);

            assert_eq!(config.name, "rust");
        }

        #[test]
        fn get_all() {
            let configs = LanguageConfig::get_all();

            assert_eq!(configs.len(), 1);
            let rust_config = configs.get(&Language::Rust).unwrap();
            assert_eq!(rust_config.name, "rust");
        }
    }

    mod language {
        use super::*;

        #[test]
        fn from_str() {
            let input = "rust";

            let result = input.parse::<Language>().unwrap();

            assert_eq!(result, Language::Rust);
        }

        #[test]
        fn unsupported_language() {
            let input = "python";

            let result = input.parse::<Language>();

            assert!(result.is_err());
        }
    }
}
