use std::collections::HashSet;

use coil_core::ModuleManifest;

use crate::error::OpsModelError;
use crate::identifiers::BulkOperationId;

use super::BulkOperationDefinition;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BulkCatalog {
    pub definitions: Vec<BulkOperationDefinition>,
}

impl BulkCatalog {
    pub fn new(definitions: Vec<BulkOperationDefinition>) -> Self {
        Self { definitions }
    }

    pub fn standard() -> Self {
        Self {
            definitions: Vec::new(),
        }
    }

    pub fn from_manifests(manifests: &[ModuleManifest]) -> Result<Self, OpsModelError> {
        let mut definitions = Vec::new();
        for manifest in manifests {
            for definition in &manifest.bulk_operations {
                definitions.push(BulkOperationDefinition::from_manifest_definition(
                    &manifest.name,
                    definition,
                )?);
            }
        }
        let catalog = Self::new(definitions);
        catalog.validate()?;
        Ok(catalog)
    }

    pub fn validate(&self) -> Result<(), OpsModelError> {
        let mut seen = HashSet::new();
        for definition in &self.definitions {
            if !seen.insert(definition.id.as_str().to_string()) {
                return Err(OpsModelError::DuplicateIdentifier {
                    kind: "bulk operation",
                    id: definition.id.to_string(),
                });
            }

            BulkOperationDefinition::new(
                definition.id.clone(),
                definition.source_module.clone(),
                definition.title.clone(),
                definition.description.clone(),
                definition.required_capability,
                definition.kind,
                definition.scope,
                definition.retry_policy.clone(),
                definition.max_items,
                definition.requires_idempotency_key,
            )?;
        }

        Ok(())
    }

    pub fn definition(&self, id: &BulkOperationId) -> Option<&BulkOperationDefinition> {
        self.definitions
            .iter()
            .find(|definition| &definition.id == id)
    }
}

impl Default for BulkCatalog {
    fn default() -> Self {
        Self::standard()
    }
}
