//! Generic plugin registry builder.

#![expect(
    clippy::type_complexity,
    reason = "`BTreeMap<&'static str, Box<dyn Policy>>` is the id-keyed registry shape."
)]

use std::collections::BTreeMap;

use aqc_file_engine_core::Engine;
use shakrs_core::{Adapter, Policy};

use crate::types::{Registry, RegistryError};

/// Builder for a closed registry.
#[derive(Default)]
pub struct RegistryBuilder {
    policies: BTreeMap<&'static str, Box<dyn Policy>>,
    adapters: BTreeMap<&'static str, Box<dyn Adapter>>,
    engines: BTreeMap<&'static str, Box<dyn Engine>>,
}

impl core::fmt::Debug for Registry {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.debug_struct("Registry")
            .field("policies", &self.policies.keys().collect::<Vec<_>>())
            .field("adapters", &self.adapters.keys().collect::<Vec<_>>())
            .field("engines", &self.engines.keys().collect::<Vec<_>>())
            .finish()
    }
}

impl core::fmt::Display for RegistryError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::DuplicateId { stage, id } => write!(f, "duplicate {stage} id `{id}`"),
            Self::EmptyId { stage } => write!(f, "a {stage} reported an empty id"),
            Self::EmptyStage { stage } => write!(f, "no {stage} registered"),
        }
    }
}

impl core::error::Error for RegistryError {}

impl RegistryBuilder {
    /// Create an empty registry builder.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Add one policy.
    ///
    /// # Errors
    ///
    /// Returns [`RegistryError`] if the policy id is empty or duplicated.
    pub fn policy<P: Policy + 'static>(mut self, policy: P) -> Result<Self, RegistryError> {
        let boxed: Box<dyn Policy> = Box::new(policy);
        let id = boxed.id();
        insert_unique(&mut self.policies, "policy", id, boxed)?;
        Ok(self)
    }

    /// Add one adapter.
    ///
    /// # Errors
    ///
    /// Returns [`RegistryError`] if the adapter id is empty or duplicated.
    pub fn adapter<A: Adapter + 'static>(mut self, adapter: A) -> Result<Self, RegistryError> {
        let boxed: Box<dyn Adapter> = Box::new(adapter);
        let id = boxed.id();
        insert_unique(&mut self.adapters, "adapter", id, boxed)?;
        Ok(self)
    }

    /// Add one engine.
    ///
    /// # Errors
    ///
    /// Returns [`RegistryError`] if the engine id is empty or duplicated.
    pub fn engine<E: Engine + 'static>(mut self, engine: E) -> Result<Self, RegistryError> {
        let boxed: Box<dyn Engine> = Box::new(engine);
        let id = boxed.id();
        insert_unique(&mut self.engines, "engine", id, boxed)?;
        Ok(self)
    }

    /// Finish the registry.
    ///
    /// # Errors
    ///
    /// Returns [`RegistryError`] if any stage is empty.
    pub fn build(self) -> Result<Registry, RegistryError> {
        if self.policies.is_empty() {
            return Err(RegistryError::EmptyStage { stage: "policy" });
        }
        if self.adapters.is_empty() {
            return Err(RegistryError::EmptyStage { stage: "adapter" });
        }
        if self.engines.is_empty() {
            return Err(RegistryError::EmptyStage { stage: "engine" });
        }
        Ok(Registry {
            policies: self.policies,
            adapters: self.adapters,
            engines: self.engines,
        })
    }
}

fn insert_unique<V>(
    map: &mut BTreeMap<&'static str, V>,
    stage: &'static str,
    id: &'static str,
    value: V,
) -> Result<(), RegistryError> {
    if id.is_empty() {
        return Err(RegistryError::EmptyId { stage });
    }
    if map.insert(id, value).is_some() {
        return Err(RegistryError::DuplicateId {
            stage,
            id: id.to_owned(),
        });
    }
    Ok(())
}
