use super::interface::ExecutorFactory;
use crate::prelude::*;
use cocoindex_utils::internal_error;
use std::collections::HashMap;
use std::sync::Arc;

pub struct ExecutorFactoryRegistry {
    source_factories: HashMap<String, Arc<dyn super::interface::SourceFactory + Send + Sync>>,
    function_factories:
        HashMap<String, Arc<dyn super::interface::SimpleFunctionFactory + Send + Sync>>,
    target_factories: HashMap<String, Arc<dyn super::interface::TargetFactory + Send + Sync>>,
    target_attachment_factories:
        HashMap<String, Arc<dyn super::interface::TargetAttachmentFactory + Send + Sync>>,
}

impl Default for ExecutorFactoryRegistry {
    fn default() -> Self {
        Self::new()
    }
}

impl ExecutorFactoryRegistry {
    pub fn new() -> Self {
        Self {
            source_factories: HashMap::new(),
            function_factories: HashMap::new(),
            target_factories: HashMap::new(),
            target_attachment_factories: HashMap::new(),
        }
    }

    pub fn register(&mut self, name: String, factory: ExecutorFactory) -> Result<()> {
        match factory {
            ExecutorFactory::Source(source_factory) => match self.source_factories.entry(name) {
                std::collections::hash_map::Entry::Occupied(entry) => Err(internal_error!(
                    "Source factory with name already exists: {}",
                    entry.key()
                )),
                std::collections::hash_map::Entry::Vacant(entry) => {
                    entry.insert(source_factory);
                    Ok(())
                }
            },
            ExecutorFactory::SimpleFunction(function_factory) => {
                match self.function_factories.entry(name) {
                    std::collections::hash_map::Entry::Occupied(entry) => Err(internal_error!(
                        "Function factory with name already exists: {}",
                        entry.key()
                    )),
                    std::collections::hash_map::Entry::Vacant(entry) => {
                        entry.insert(function_factory);
                        Ok(())
                    }
                }
            }
            ExecutorFactory::ExportTarget(target_factory) => {
                match self.target_factories.entry(name) {
                    std::collections::hash_map::Entry::Occupied(entry) => Err(internal_error!(
                        "Target factory with name already exists: {}",
                        entry.key()
                    )),
                    std::collections::hash_map::Entry::Vacant(entry) => {
                        entry.insert(target_factory);
                        Ok(())
                    }
                }
            }
            ExecutorFactory::TargetAttachment(target_attachment_factory) => {
                match self.target_attachment_factories.entry(name) {
                    std::collections::hash_map::Entry::Occupied(entry) => Err(internal_error!(
                        "Target attachment factory with name already exists: {}",
                        entry.key()
                    )),
                    std::collections::hash_map::Entry::Vacant(entry) => {
                        entry.insert(target_attachment_factory);
                        Ok(())
                    }
                }
            }
        }
    }

    pub fn get_source(
        &self,
        name: &str,
    ) -> Option<&Arc<dyn super::interface::SourceFactory + Send + Sync>> {
        self.source_factories.get(name)
    }

    pub fn get_function(
        &self,
        name: &str,
    ) -> Option<&Arc<dyn super::interface::SimpleFunctionFactory + Send + Sync>> {
        self.function_factories.get(name)
    }

    pub fn get_target(
        &self,
        name: &str,
    ) -> Option<&Arc<dyn super::interface::TargetFactory + Send + Sync>> {
        self.target_factories.get(name)
    }

    pub fn get_target_attachment(
        &self,
        name: &str,
    ) -> Option<&Arc<dyn super::interface::TargetAttachmentFactory + Send + Sync>> {
        self.target_attachment_factories.get(name)
    }
}
