//! CR1-A — per-session registry of plugin-declared `CreationSpec` /
//! `compose` pairs.
//!
//! Plugins register entries during `ModelPlugin::run_setup` via
//! [`GameSession::register_creation_spec`]. The engine looks up entries
//! when proposing [`GameEvent::EntityCreationRequested`] to drive the
//! engine-composed wizard (CR1-A step 4) and the compose/event-emission
//! path (CR1-A step 5).
//!
//! `spec_fn` stays a bare `fn` pointer (stateless construction, called
//! once at registration).
//!
//! `compose_fn` is `Arc<dyn Fn>` (CR1-B trigger, WASM1 Phase 3c-compose).
//! Wasm-backed plugins need per-instance state captured in the closure
//! to route compose calls into the persistent wasm runtime. Native
//! plugins continue to coerce `fn` items into the Arc via
//! `Arc::new(my_fn)` at registration sites.

use std::collections::HashMap;
use std::sync::Arc;

use crate::creation::{ComposeCtx, CreationHistory, CreationSpec};
use crate::events::GameEvent;
use crate::plugin_slices::GameContext;
use crate::types::EntityTypeId;

/// Plugin's `creation_spec` constructor.
///
/// Returns the static (CR1-A) shape declaring the entity's creation
/// fields. Dynamic narrowing (CR1-B) flows through closures embedded
/// in the spec, not through this top-level fn.
pub type CreationSpecFn = fn() -> CreationSpec;

/// Plugin's `compose` function — called by the engine when the wizard
/// resolves. The plugin reads the [`CreationHistory`] and returns the
/// concrete `GameEvent`s that bring the entity into existence.
///
/// Arc-wrapped trait object since WASM1 Phase 3c-compose: wasm-backed
/// plugins capture per-instance state (the persistent wasm runtime) in
/// their compose closures. Native plugins still register bare `fn`s via
/// `Arc::new(my_compose)`.
pub type ComposeFn = Arc<
    dyn Fn(&CreationHistory, &dyn GameContext, &mut ComposeCtx) -> Vec<GameEvent>
        + Send
        + Sync,
>;

/// One registry entry: a paired `(spec_fn, compose_fn)`.
#[derive(Clone)]
pub struct CreationEntry {
    pub spec_fn: CreationSpecFn,
    pub compose_fn: ComposeFn,
}

/// Per-session lookup from `EntityTypeId` to its creation pair.
///
/// Append-only at runtime: registration during `run_setup`, no removal
/// after the session is constructed. Re-registration for the same
/// `EntityTypeId` overwrites silently — plugin authors hold the
/// invariant that each type registers exactly once.
#[derive(Default, Clone)]
pub struct CreationRegistry {
    entries: HashMap<EntityTypeId, CreationEntry>,
}

impl CreationRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    /// Register a `(spec_fn, compose_fn)` pair for `type_id`.
    pub fn register(
        &mut self,
        type_id: EntityTypeId,
        spec_fn: CreationSpecFn,
        compose_fn: ComposeFn,
    ) {
        self.entries.insert(type_id, CreationEntry { spec_fn, compose_fn });
    }

    /// Look up the registered `creation_spec()` for an entity type, or
    /// `None` if no plugin registered one. The returned `CreationSpec`
    /// is freshly constructed by the plugin's fn pointer on each call.
    pub fn spec_for(&self, type_id: EntityTypeId) -> Option<CreationSpec> {
        self.entries.get(&type_id).map(|e| (e.spec_fn)())
    }

    /// Look up the registered `compose` fn for an entity type.
    pub fn compose_fn_for(&self, type_id: EntityTypeId) -> Option<ComposeFn> {
        self.entries.get(&type_id).map(|e| e.compose_fn.clone())
    }

    /// Total number of registered entity types. Diagnostic only.
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    /// True iff no registrations have happened.
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    /// CL1-B — list of all `EntityTypeId`s with a registered creation
    /// spec. Order is unspecified (HashMap iteration).
    pub fn registered_type_ids(&self) -> Vec<EntityTypeId> {
        self.entries.keys().copied().collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::creation::CreationSpec;
    use crate::staged_decision::PresentationMode;

    fn dummy_spec() -> CreationSpec {
        CreationSpec::builder()
            .group("g", PresentationMode::Parallel)
                .text("name").required()
            .build()
    }

    fn dummy_compose(
        _h: &CreationHistory,
        _game_ctx: &dyn GameContext,
        _ctx: &mut ComposeCtx,
    ) -> Vec<GameEvent> {
        Vec::new()
    }

    #[test]
    fn cr1a_registry_register_and_lookup() {
        let mut reg = CreationRegistry::new();
        let type_id = EntityTypeId::from_raw(42);

        assert!(reg.spec_for(type_id).is_none());
        assert!(reg.compose_fn_for(type_id).is_none());

        reg.register(type_id, dummy_spec, Arc::new(dummy_compose));

        let spec = reg.spec_for(type_id);
        assert!(spec.is_some());
        assert_eq!(spec.expect("registered").groups.len(), 1);
        assert!(reg.compose_fn_for(type_id).is_some());
        assert_eq!(reg.len(), 1);
    }

    #[test]
    fn cr1a_registry_distinct_type_ids_do_not_collide() {
        let mut reg = CreationRegistry::new();
        let a = EntityTypeId::from_raw(1);
        let b = EntityTypeId::from_raw(2);

        reg.register(a, dummy_spec, Arc::new(dummy_compose));
        reg.register(b, dummy_spec, Arc::new(dummy_compose));

        assert!(reg.spec_for(a).is_some());
        assert!(reg.spec_for(b).is_some());
        assert_eq!(reg.len(), 2);
    }

    #[test]
    fn cr1a_registry_unregistered_type_returns_none() {
        let reg = CreationRegistry::new();
        assert!(reg.spec_for(EntityTypeId::from_raw(999)).is_none());
        assert!(reg.compose_fn_for(EntityTypeId::from_raw(999)).is_none());
        assert!(reg.is_empty());
    }
}
