//! LU1 (CR1-C) — per-session registry of plugin-declared level-up
//! `(spec_fn, compose_fn)` pairs. Mirror of `creation_registry` but
//! parameterized by `target_level: u32` since level-up specs vary
//! per-level (e.g. dnd5e level 4 grants ASI/feat; level 6 Fighter has
//! no choices; level 5 Wizard learns 3 new spells).
//!
//! Plugins register entries during `ModelPlugin::run_setup` via
//! [`crate::session::GameSession::register_level_up_spec`]. The engine
//! looks up entries when proposing
//! [`maat_sdk::events::GameEvent::EntityLevelUpRequested`] to drive
//! the engine-composed level-up wizard.

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::{EntityId, EntityTypeId};

/// Plugin's `level_up_spec` constructor — parameterized by the target
/// level. Returns the per-level spec that drives the wizard for that
/// level transition (could be empty for no-choice levels).
pub type LevelUpSpecFn = fn(target_level: u32) -> CreationSpec;

/// Plugin's `level_up_compose` function — receives the target level,
/// the resolved CreationHistory from the wizard, the ComposeCtx, and
/// the EntityId of the entity being leveled up. Returns the events
/// that mutate the entity (typically PoolChanged for level/HP/etc.,
/// EntityCreated for new attached entities like spells).
///
/// Arc-wrapped since WASM1 Phase 3c-compose; see `ComposeFn` docs.
pub type LevelUpComposeFn = Arc<
    dyn Fn(
            u32,
            &CreationHistory,
            &dyn GameContext,
            &mut ComposeCtx,
            EntityId,
        ) -> Vec<GameEvent>
        + Send
        + Sync,
>;

#[derive(Clone)]
pub struct LevelUpEntry {
    pub spec_fn: LevelUpSpecFn,
    pub compose_fn: LevelUpComposeFn,
}

#[derive(Default, Clone)]
pub struct LevelUpRegistry {
    entries: HashMap<EntityTypeId, LevelUpEntry>,
}

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

    pub fn register(
        &mut self,
        type_id: EntityTypeId,
        spec_fn: LevelUpSpecFn,
        compose_fn: LevelUpComposeFn,
    ) {
        self.entries.insert(type_id, LevelUpEntry { spec_fn, compose_fn });
    }

    /// Construct the level-up spec for a given (type_id, target_level).
    /// Returns `None` when no plugin registered a level-up spec for the
    /// type — the engine then routes a Reject reason for the wizard.
    pub fn spec_for(
        &self,
        type_id: EntityTypeId,
        target_level: u32,
    ) -> Option<CreationSpec> {
        self.entries.get(&type_id).map(|e| (e.spec_fn)(target_level))
    }

    pub fn compose_fn_for(&self, type_id: EntityTypeId) -> Option<LevelUpComposeFn> {
        self.entries.get(&type_id).map(|e| e.compose_fn.clone())
    }

    pub fn len(&self) -> usize {
        self.entries.len()
    }

    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    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(target_level: u32) -> CreationSpec {
        // Build a spec whose label encodes the target_level so tests can verify.
        CreationSpec::builder()
            .group("levelup", PresentationMode::Parallel)
                .text("note").label(format!("level {}", target_level)).optional()
            .build()
    }

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

    #[test]
    fn lu1_registry_register_and_lookup() {
        let mut reg = LevelUpRegistry::new();
        let type_id = EntityTypeId::from_raw(42);
        assert!(reg.spec_for(type_id, 2).is_none());
        reg.register(type_id, dummy_spec, Arc::new(dummy_compose));
        let spec = reg.spec_for(type_id, 2).expect("registered");
        assert_eq!(spec.groups.len(), 1);
        assert!(reg.compose_fn_for(type_id).is_some());
    }

    #[test]
    fn lu1_registry_spec_per_target_level() {
        // Same registered fn produces a different spec per target_level
        // (because the fn pointer takes target_level as input).
        let mut reg = LevelUpRegistry::new();
        let type_id = EntityTypeId::from_raw(42);
        reg.register(type_id, dummy_spec, Arc::new(dummy_compose));
        let spec_lvl2 = reg.spec_for(type_id, 2).expect("lvl2");
        let spec_lvl5 = reg.spec_for(type_id, 5).expect("lvl5");
        // Field 0 is the text field; its label encodes the level.
        let label_2 = match &spec_lvl2.groups[0].fields[0] {
            crate::creation::CreationField::Text { label, .. } => label.clone(),
            _ => panic!("expected text field"),
        };
        let label_5 = match &spec_lvl5.groups[0].fields[0] {
            crate::creation::CreationField::Text { label, .. } => label.clone(),
            _ => panic!("expected text field"),
        };
        assert_eq!(label_2, "level 2");
        assert_eq!(label_5, "level 5");
    }

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