//! Resolve-once, stamp-many handler templates.
//!
//! Templates separate handler *creation cost* (registry lookup, access
//! validation) from *instantiation*. A [`HandlerTemplate`] resolves
//! parameters once, then [`generate`](HandlerTemplate::generate) stamps
//! out [`TemplatedHandler`]s by copying the pre-resolved state.
//!
//! Same pattern for context-owning handlers: [`CallbackTemplate`] +
//! [`TemplatedCallback`].
//!
//! # Constraints
//!
//! - **`P::State: Copy`** — required for `generate()` to stamp copies.
//!   This effectively rules out [`Local<T>`](crate::Local) when the
//!   per-instance state is not `Copy` (non-duplicable state is
//!   incompatible with template stamping). All World-backed params
//!   ([`Res`](crate::Res), [`ResMut`](crate::ResMut), `Option` variants)
//!   have `State = ResourceId` which is `Copy`.
//! - **Zero-sized, non-capturing callables only** — `F` must be a ZST
//!   (zero-sized type). Capturing closures and function pointers are
//!   rejected at compile time; captureless closures and function items
//!   are allowed.
//!
//! # Examples
//!
//! ```
//! use nexus_rt::{WorldBuilder, ResMut, Handler};
//! use nexus_rt::template::{Blueprint, HandlerTemplate};
//!
//! struct OnTick;
//! impl Blueprint for OnTick {
//!     type Event = u32;
//!     type Params = (ResMut<'static, u64>,);
//! }
//!
//! fn tick(mut counter: ResMut<u64>, event: u32) {
//!     *counter += event as u64;
//! }
//!
//! let mut builder = WorldBuilder::new();
//! builder.register::<u64>(0);
//! let world = builder.build();
//!
//! let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
//! let mut h1 = template.generate();
//! let mut h2 = template.generate();
//!
//! // Both share pre-resolved state — no redundant registry lookups.
//! ```

use std::marker::PhantomData;

use crate::Handler;
use crate::handler::Param;
use crate::world::{Registry, World};

// =============================================================================
// Blueprint traits
// =============================================================================

/// Declares a handler template's event and parameter types.
///
/// The implementing struct is a *key* — a zero-sized marker that names
/// the template. No `'static` bound on `Event` because wire protocols
/// (e.g. Aeron) pass events borrowed from term buffers.
///
/// # Examples
///
/// ```
/// use nexus_rt::template::Blueprint;
/// use nexus_rt::ResMut;
///
/// struct OnTick;
/// impl Blueprint for OnTick {
///     type Event = u32;
///     type Params = (ResMut<'static, u64>,);
/// }
/// ```
///
/// The `'static` on `Res`/`ResMut` is a naming requirement (GATs need
/// a concrete lifetime). It is never used at runtime — `fetch()` binds
/// a real lifetime at each call site.
pub trait Blueprint {
    /// The event type dispatched to handlers stamped from this template.
    type Event;
    /// The parameter tuple. Must satisfy [`Param`] + `'static`.
    type Params: Param + 'static;
}

/// Extension of [`Blueprint`] for context-owning handlers.
///
/// Adds the per-instance context type `C` that each
/// [`TemplatedCallback`] carries.
pub trait CallbackBlueprint: Blueprint {
    /// Per-instance owned context type.
    type Context: Send + 'static;
}

// =============================================================================
// TemplateDispatch — doc-hidden, per-arity fn ptr provider
// =============================================================================

/// Bridges arity-independent template structs with arity-dependent
/// dispatch logic. Generated by `all_tuples!`.
///
/// Each arity provides a fn ptr that performs `Param::fetch` + call,
/// and a `validate` method that checks for conflicting resource access.
#[doc(hidden)]
pub trait TemplateDispatch<P: Param, E> {
    /// Returns a fn ptr that fetches params and calls the handler.
    fn run_fn_ptr() -> unsafe fn(&mut P::State, &mut World, E);
    /// Validates resource access (panics on conflict).
    fn validate(state: &P::State, registry: &Registry);
}

/// Context-owning variant of [`TemplateDispatch`].
#[doc(hidden)]
pub trait CallbackTemplateDispatch<C, P: Param, E> {
    /// Returns a fn ptr that fetches params and calls the handler with context.
    fn run_fn_ptr() -> unsafe fn(&mut C, &mut P::State, &mut World, E);
    /// Validates resource access (panics on conflict).
    fn validate(state: &P::State, registry: &Registry);
}

// =============================================================================
// Arity 0: event-only handlers
// =============================================================================

impl<E, F: FnMut(E) + Send + 'static> TemplateDispatch<(), E> for F {
    fn run_fn_ptr() -> unsafe fn(&mut (), &mut World, E) {
        /// # Safety
        ///
        /// `F` must be a ZST (enforced by `HandlerTemplate::new`).
        /// `std::mem::zeroed()` on a ZST is sound — no bytes to initialize.
        unsafe fn run<E, F: FnMut(E) + Send>(_state: &mut (), _world: &mut World, event: E) {
            // SAFETY: F is ZST (asserted in HandlerTemplate::new).
            // A ZST has no data — zeroed() produces the unique value.
            let mut f: F = unsafe { std::mem::zeroed() };
            f(event);
        }
        run::<E, F>
    }

    fn validate(_state: &(), _registry: &Registry) {}
}

impl<C: Send + 'static, E, F: FnMut(&mut C, E) + Send + 'static> CallbackTemplateDispatch<C, (), E>
    for F
{
    fn run_fn_ptr() -> unsafe fn(&mut C, &mut (), &mut World, E) {
        unsafe fn run<C: Send, E, F: FnMut(&mut C, E) + Send>(
            ctx: &mut C,
            _state: &mut (),
            _world: &mut World,
            event: E,
        ) {
            // SAFETY: F is ZST — see arity-0 TemplateDispatch.
            let mut f: F = unsafe { std::mem::zeroed() };
            f(ctx, event);
        }
        run::<C, E, F>
    }

    fn validate(_state: &(), _registry: &Registry) {}
}

// =============================================================================
// Per-arity TemplateDispatch / CallbackTemplateDispatch via macro
// =============================================================================

macro_rules! impl_template_dispatch {
    ($($P:ident),+) => {
        impl<E, F: Send + 'static, $($P: Param + 'static),+> TemplateDispatch<($($P,)+), E> for F
        where
            for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
        {
            fn run_fn_ptr() -> unsafe fn(&mut ($($P::State,)+), &mut World, E) {
                /// # Safety
                ///
                /// - `F` must be a ZST (enforced by `HandlerTemplate::new`).
                /// - `state` must have been produced by `Param::init` on the
                ///   same registry that built `world`.
                /// - Caller ensures no mutable aliasing across params.
                #[allow(non_snake_case)]
                unsafe fn run<E, F: Send + 'static, $($P: Param + 'static),+>(
                    state: &mut ($($P::State,)+),
                    world: &mut World,
                    event: E,
                ) where
                    for<'a> &'a mut F: FnMut($($P,)+ E) + FnMut($($P::Item<'a>,)+ E),
                {
                    #[allow(clippy::too_many_arguments)]
                    fn call_inner<$($P,)+ Ev>(
                        mut f: impl FnMut($($P,)+ Ev),
                        $($P: $P,)+
                        event: Ev,
                    ) {
                        f($($P,)+ event);
                    }

                    // SAFETY: state produced by init() on same registry.
                    // Single-threaded sequential dispatch — no mutable aliasing.
                    #[cfg(debug_assertions)]
                    world.clear_borrows();
                    let ($($P,)+) = unsafe {
                        <($($P,)+) as Param>::fetch(world, state)
                    };
                    // SAFETY: F is ZST — zeroed() produces the unique value.
                    let mut f: F = unsafe { std::mem::zeroed() };
                    call_inner(&mut f, $($P,)+ event);
                }
                run::<E, F, $($P),+>
            }

            #[allow(non_snake_case)]
            fn validate(state: &($($P::State,)+), registry: &Registry) {
                let ($($P,)+) = state;
                registry.check_access(&[
                    $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
                ]);
            }
        }

        impl<C: Send + 'static, E, F: Send + 'static, $($P: Param + 'static),+>
            CallbackTemplateDispatch<C, ($($P,)+), E> for F
        where
            for<'a> &'a mut F:
                FnMut(&mut C, $($P,)+ E) +
                FnMut(&mut C, $($P::Item<'a>,)+ E),
        {
            fn run_fn_ptr() -> unsafe fn(&mut C, &mut ($($P::State,)+), &mut World, E) {
                #[allow(non_snake_case)]
                unsafe fn run<C: Send, E, F: Send + 'static, $($P: Param + 'static),+>(
                    ctx: &mut C,
                    state: &mut ($($P::State,)+),
                    world: &mut World,
                    event: E,
                ) where
                    for<'a> &'a mut F:
                        FnMut(&mut C, $($P,)+ E) +
                        FnMut(&mut C, $($P::Item<'a>,)+ E),
                {
                    #[allow(clippy::too_many_arguments)]
                    fn call_inner<Ctx, $($P,)+ Ev>(
                        mut f: impl FnMut(&mut Ctx, $($P,)+ Ev),
                        ctx: &mut Ctx,
                        $($P: $P,)+
                        event: Ev,
                    ) {
                        f(ctx, $($P,)+ event);
                    }

                    #[cfg(debug_assertions)]
                    world.clear_borrows();
                    let ($($P,)+) = unsafe {
                        <($($P,)+) as Param>::fetch(world, state)
                    };
                    let mut f: F = unsafe { std::mem::zeroed() };
                    call_inner(&mut f, ctx, $($P,)+ event);
                }
                run::<C, E, F, $($P),+>
            }

            #[allow(non_snake_case)]
            fn validate(state: &($($P::State,)+), registry: &Registry) {
                let ($($P,)+) = state;
                registry.check_access(&[
                    $((<$P as Param>::resource_id($P), std::any::type_name::<$P>()),)+
                ]);
            }
        }
    };
}

macro_rules! all_tuples {
    ($m:ident) => {
        $m!(P0);
        $m!(P0, P1);
        $m!(P0, P1, P2);
        $m!(P0, P1, P2, P3);
        $m!(P0, P1, P2, P3, P4);
        $m!(P0, P1, P2, P3, P4, P5);
        $m!(P0, P1, P2, P3, P4, P5, P6);
        $m!(P0, P1, P2, P3, P4, P5, P6, P7);
    };
}

all_tuples!(impl_template_dispatch);

// =============================================================================
// HandlerTemplate<K>
// =============================================================================

/// Resolve-once template for context-free handlers.
///
/// Created from a named function + [`Registry`]. Calling
/// [`generate`](Self::generate) stamps out [`TemplatedHandler`]s by
/// copying pre-resolved state — no registry lookups, no validation.
///
/// # Examples
///
/// ```
/// use nexus_rt::{WorldBuilder, ResMut, Handler};
/// use nexus_rt::template::{Blueprint, HandlerTemplate};
///
/// struct OnTick;
/// impl Blueprint for OnTick {
///     type Event = u32;
///     type Params = (ResMut<'static, u64>,);
/// }
///
/// fn tick(mut counter: ResMut<u64>, event: u32) {
///     *counter += event as u64;
/// }
///
/// let mut builder = WorldBuilder::new();
/// builder.register::<u64>(0);
/// let world = builder.build();
///
/// let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
///
/// let mut h1 = template.generate();
/// let mut h2 = template.generate();
///
/// // Both share pre-resolved ResourceIds — no redundant lookups.
/// ```
pub struct HandlerTemplate<K: Blueprint>
where
    <K::Params as Param>::State: Copy,
{
    prototype: <K::Params as Param>::State,
    run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
    name: &'static str,
    // fn() -> K avoids propagating K's Send/Sync bounds through PhantomData.
    _key: PhantomData<fn() -> K>,
}

impl<K: Blueprint> HandlerTemplate<K>
where
    <K::Params as Param>::State: Copy,
{
    /// Create a template from a named function.
    ///
    /// Resolves all parameters from the registry and validates access.
    /// `F` must be a ZST (named function item) — closures and function
    /// pointers are rejected at compile time.
    ///
    /// # Panics
    ///
    /// - If any required resource is not registered.
    /// - If params have conflicting access (same resource twice).
    #[allow(clippy::needless_pass_by_value)]
    pub fn new<F>(f: F, registry: &Registry) -> Self
    where
        F: TemplateDispatch<K::Params, K::Event>,
    {
        const {
            assert!(
                std::mem::size_of::<F>() == 0,
                "F must be a ZST (named function item, not a closure or fn pointer)"
            );
        }
        let _ = f;
        let prototype = K::Params::init(registry);
        F::validate(&prototype, registry);
        Self {
            prototype,
            run_fn: F::run_fn_ptr(),
            name: std::any::type_name::<F>(),
            _key: PhantomData,
        }
    }

    /// Stamp out a new handler by copying pre-resolved state.
    pub fn generate(&self) -> TemplatedHandler<K> {
        TemplatedHandler {
            state: self.prototype,
            run_fn: self.run_fn,
            name: self.name,
            _key: PhantomData,
        }
    }
}

// =============================================================================
// TemplatedHandler<K>
// =============================================================================

/// A handler stamped from a [`HandlerTemplate`].
///
/// Implements [`Handler<K::Event>`] for direct or boxed dispatch.
/// No registry lookups — all state was pre-resolved by the template.
pub struct TemplatedHandler<K: Blueprint>
where
    <K::Params as Param>::State: Copy,
{
    state: <K::Params as Param>::State,
    run_fn: unsafe fn(&mut <K::Params as Param>::State, &mut World, K::Event),
    name: &'static str,
    _key: PhantomData<fn() -> K>,
}

impl<K: Blueprint> Handler<K::Event> for TemplatedHandler<K>
where
    <K::Params as Param>::State: Copy,
{
    fn run(&mut self, world: &mut World, event: K::Event) {
        // SAFETY: run_fn was produced by TemplateDispatch for the matching
        // F + K combination. State was produced by Param::init on the same
        // registry that built world.
        unsafe { (self.run_fn)(&mut self.state, world, event) }
    }

    fn name(&self) -> &'static str {
        self.name
    }
}

// =============================================================================
// CallbackTemplate<K>
// =============================================================================

/// Fn ptr type for context-owning dispatch (avoids `type_complexity` lint).
type CallbackRunFn<K> = unsafe fn(
    &mut <K as CallbackBlueprint>::Context,
    &mut <<K as Blueprint>::Params as Param>::State,
    &mut World,
    <K as Blueprint>::Event,
);

/// Resolve-once template for context-owning handlers.
///
/// Like [`HandlerTemplate`], but each [`generate`](Self::generate)
/// call takes a `K::Context` to produce a [`TemplatedCallback`] with
/// per-instance owned state.
///
/// # Examples
///
/// ```
/// use nexus_rt::{WorldBuilder, ResMut, Handler};
/// use nexus_rt::template::{Blueprint, CallbackBlueprint, CallbackTemplate};
///
/// struct TimerCtx { order_id: u64, fires: u64 }
///
/// struct OnTimeout;
/// impl Blueprint for OnTimeout {
///     type Event = ();
///     type Params = (ResMut<'static, u64>,);
/// }
/// impl CallbackBlueprint for OnTimeout {
///     type Context = TimerCtx;
/// }
///
/// fn on_timeout(ctx: &mut TimerCtx, mut counter: ResMut<u64>, _event: ()) {
///     ctx.fires += 1;
///     *counter += ctx.order_id;
/// }
///
/// let mut builder = WorldBuilder::new();
/// builder.register::<u64>(0);
/// let world = builder.build();
///
/// let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
/// let mut cb1 = template.generate(TimerCtx { order_id: 10, fires: 0 });
/// let mut cb2 = template.generate(TimerCtx { order_id: 20, fires: 0 });
///
/// // Each carries its own context, shares pre-resolved state.
/// ```
pub struct CallbackTemplate<K: CallbackBlueprint>
where
    <K::Params as Param>::State: Copy,
{
    prototype: <K::Params as Param>::State,
    run_fn: CallbackRunFn<K>,
    name: &'static str,
    _key: PhantomData<fn() -> K>,
}

impl<K: CallbackBlueprint> CallbackTemplate<K>
where
    <K::Params as Param>::State: Copy,
{
    /// Create a template from a named function.
    ///
    /// Same constraints as [`HandlerTemplate::new`]: `F` must be ZST,
    /// all resources must be registered.
    ///
    /// # Panics
    ///
    /// - If any required resource is not registered.
    /// - If params have conflicting access.
    #[allow(clippy::needless_pass_by_value)]
    pub fn new<F>(f: F, registry: &Registry) -> Self
    where
        F: CallbackTemplateDispatch<K::Context, K::Params, K::Event>,
    {
        const {
            assert!(
                std::mem::size_of::<F>() == 0,
                "F must be a ZST (named function item, not a closure or fn pointer)"
            );
        }
        let _ = f;
        let prototype = K::Params::init(registry);
        F::validate(&prototype, registry);
        Self {
            prototype,
            run_fn: F::run_fn_ptr(),
            name: std::any::type_name::<F>(),
            _key: PhantomData,
        }
    }

    /// Stamp out a new callback with the given context.
    pub fn generate(&self, ctx: K::Context) -> TemplatedCallback<K> {
        TemplatedCallback {
            ctx,
            state: self.prototype,
            run_fn: self.run_fn,
            name: self.name,
            _key: PhantomData,
        }
    }
}

// =============================================================================
// TemplatedCallback<K>
// =============================================================================

/// A callback stamped from a [`CallbackTemplate`].
///
/// Implements [`Handler<K::Event>`].
pub struct TemplatedCallback<K: CallbackBlueprint>
where
    <K::Params as Param>::State: Copy,
{
    ctx: K::Context,
    state: <K::Params as Param>::State,
    run_fn: CallbackRunFn<K>,
    name: &'static str,
    _key: PhantomData<fn() -> K>,
}

impl<K: CallbackBlueprint> TemplatedCallback<K>
where
    <K::Params as Param>::State: Copy,
{
    /// Returns a shared reference to the per-instance context.
    pub fn ctx(&self) -> &K::Context {
        &self.ctx
    }

    /// Returns an exclusive reference to the per-instance context.
    pub fn ctx_mut(&mut self) -> &mut K::Context {
        &mut self.ctx
    }
}

impl<K: CallbackBlueprint> Handler<K::Event> for TemplatedCallback<K>
where
    <K::Params as Param>::State: Copy,
{
    fn run(&mut self, world: &mut World, event: K::Event) {
        // SAFETY: run_fn was produced by CallbackTemplateDispatch for the
        // matching F + K combination. State from Param::init on same registry.
        unsafe { (self.run_fn)(&mut self.ctx, &mut self.state, world, event) }
    }

    fn name(&self) -> &'static str {
        self.name
    }
}

// =============================================================================
// Convenience macros
// =============================================================================

/// Declares a handler [`Blueprint`] key struct.
///
/// # Examples
///
/// ```ignore
/// handler_blueprint!(OnTick, Event = u32, Params = (Res<'static, u64>, ResMut<'static, bool>));
/// ```
///
/// Expands to a unit struct + `Blueprint` impl. The `'static` on
/// `Res`/`ResMut` is required (declarative macro limitation). Future
/// proc macros will inject it automatically.
#[macro_export]
macro_rules! handler_blueprint {
    ($name:ident, Event = $event:ty, Params = $params:ty) => {
        struct $name;
        impl $crate::template::Blueprint for $name {
            type Event = $event;
            type Params = $params;
        }
    };
}

/// Declares a callback [`CallbackBlueprint`] key struct.
///
/// # Examples
///
/// ```ignore
/// callback_blueprint!(OnConn, Context = ConnState, Event = u32, Params = (ResMut<'static, u64>,));
/// ```
#[macro_export]
macro_rules! callback_blueprint {
    ($name:ident, Context = $ctx:ty, Event = $event:ty, Params = $params:ty) => {
        struct $name;
        impl $crate::template::Blueprint for $name {
            type Event = $event;
            type Params = $params;
        }
        impl $crate::template::CallbackBlueprint for $name {
            type Context = $ctx;
        }
    };
}

// =============================================================================
// Tests
// =============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{Res, ResMut, WorldBuilder};

    // -- Blueprint definitions ------------------------------------------------

    struct OnTick;
    impl Blueprint for OnTick {
        type Event = u32;
        type Params = (ResMut<'static, u64>,);
    }

    struct EventOnly;
    impl Blueprint for EventOnly {
        type Event = u32;
        type Params = ();
    }

    struct TwoParams;
    impl Blueprint for TwoParams {
        type Event = ();
        type Params = (Res<'static, u64>, ResMut<'static, bool>);
    }

    // -- HandlerTemplate tests ------------------------------------------------

    fn tick(mut counter: ResMut<u64>, event: u32) {
        *counter += event as u64;
    }

    #[test]
    fn handler_template_basic() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
        let mut h = template.generate();
        h.run(&mut world, 10);
        assert_eq!(*world.resource::<u64>(), 10);
    }

    #[test]
    fn handler_template_stamps_independent() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
        let mut h1 = template.generate();
        let mut h2 = template.generate();

        h1.run(&mut world, 10);
        h2.run(&mut world, 5);
        assert_eq!(*world.resource::<u64>(), 15);
    }

    fn event_only_fn(event: u32) {
        assert!(event > 0);
    }

    #[test]
    fn handler_template_event_only() {
        let mut world = WorldBuilder::new().build();
        let template = HandlerTemplate::<EventOnly>::new(event_only_fn, world.registry());
        let mut h = template.generate();
        h.run(&mut world, 42);
    }

    fn two_params_fn(counter: Res<u64>, mut flag: ResMut<bool>, _event: ()) {
        if *counter > 0 {
            *flag = true;
        }
    }

    #[test]
    fn handler_template_two_params() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(1);
        builder.register::<bool>(false);
        let mut world = builder.build();

        let template = HandlerTemplate::<TwoParams>::new(two_params_fn, world.registry());
        let mut h = template.generate();
        h.run(&mut world, ());
        assert!(*world.resource::<bool>());
    }

    // -- Box<dyn Handler<E>> --------------------------------------------------

    #[test]
    fn templated_handler_boxable() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
        let h = template.generate();
        let mut boxed: Box<dyn Handler<u32>> = Box::new(h);
        boxed.run(&mut world, 7);
        assert_eq!(*world.resource::<u64>(), 7);
    }

    // -- Name -----------------------------------------------------------------

    #[test]
    fn templated_handler_name() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let world = builder.build();

        let template = HandlerTemplate::<OnTick>::new(tick, world.registry());
        let h = template.generate();
        assert!(h.name().contains("tick"));
    }

    // -- Panics ---------------------------------------------------------------

    #[test]
    #[should_panic(expected = "not registered")]
    fn handler_template_missing_resource() {
        let world = WorldBuilder::new().build();
        let _template = HandlerTemplate::<OnTick>::new(tick, world.registry());
    }

    #[test]
    #[should_panic(expected = "conflicting access")]
    fn handler_template_duplicate_access() {
        struct BadBlueprint;
        impl Blueprint for BadBlueprint {
            type Event = ();
            type Params = (Res<'static, u64>, ResMut<'static, u64>);
        }

        fn bad(a: Res<u64>, b: ResMut<u64>, _e: ()) {
            let _ = (*a, &*b);
        }

        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let world = builder.build();
        let _template = HandlerTemplate::<BadBlueprint>::new(bad, world.registry());
    }

    // -- CallbackTemplate tests -----------------------------------------------

    struct TimerCtx {
        order_id: u64,
        fires: u64,
    }

    struct OnTimeout;
    impl Blueprint for OnTimeout {
        type Event = ();
        type Params = (ResMut<'static, u64>,);
    }
    impl CallbackBlueprint for OnTimeout {
        type Context = TimerCtx;
    }

    fn on_timeout(ctx: &mut TimerCtx, mut counter: ResMut<u64>, _event: ()) {
        ctx.fires += 1;
        *counter += ctx.order_id;
    }

    #[test]
    fn callback_template_basic() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
        let mut cb = template.generate(TimerCtx {
            order_id: 42,
            fires: 0,
        });
        cb.run(&mut world, ());
        assert_eq!(cb.ctx().fires, 1);
        assert_eq!(*world.resource::<u64>(), 42);
    }

    #[test]
    fn callback_template_independent_contexts() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
        let mut cb1 = template.generate(TimerCtx {
            order_id: 10,
            fires: 0,
        });
        let mut cb2 = template.generate(TimerCtx {
            order_id: 20,
            fires: 0,
        });

        cb1.run(&mut world, ());
        cb2.run(&mut world, ());
        assert_eq!(cb1.ctx().fires, 1);
        assert_eq!(cb2.ctx().fires, 1);
        assert_eq!(*world.resource::<u64>(), 30);
    }

    struct CtxOnlyKey;
    impl Blueprint for CtxOnlyKey {
        type Event = u32;
        type Params = ();
    }
    impl CallbackBlueprint for CtxOnlyKey {
        type Context = u64;
    }

    fn ctx_only(ctx: &mut u64, event: u32) {
        *ctx += event as u64;
    }

    #[test]
    fn callback_template_event_only() {
        let mut world = WorldBuilder::new().build();
        let template = CallbackTemplate::<CtxOnlyKey>::new(ctx_only, world.registry());
        let mut cb = template.generate(0u64);
        cb.run(&mut world, 5);
        assert_eq!(*cb.ctx(), 5);
    }

    #[test]
    fn callback_template_boxable() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
        let cb = template.generate(TimerCtx {
            order_id: 7,
            fires: 0,
        });
        let mut boxed: Box<dyn Handler<()>> = Box::new(cb);
        boxed.run(&mut world, ());
        assert_eq!(*world.resource::<u64>(), 7);
    }

    #[test]
    fn callback_template_ctx_accessible() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = CallbackTemplate::<OnTimeout>::new(on_timeout, world.registry());
        let mut cb = template.generate(TimerCtx {
            order_id: 42,
            fires: 0,
        });
        assert_eq!(cb.ctx().order_id, 42);
        cb.run(&mut world, ());
        assert_eq!(cb.ctx().fires, 1);
        cb.ctx_mut().order_id = 99;
        cb.run(&mut world, ());
        assert_eq!(*world.resource::<u64>(), 42 + 99);
    }

    // -- Convenience macros ---------------------------------------------------

    handler_blueprint!(MacroOnTick, Event = u32, Params = (ResMut<'static, u64>,));

    #[test]
    fn macro_handler_blueprint() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = HandlerTemplate::<MacroOnTick>::new(tick, world.registry());
        let mut h = template.generate();
        h.run(&mut world, 3);
        assert_eq!(*world.resource::<u64>(), 3);
    }

    callback_blueprint!(MacroOnTimeout, Context = TimerCtx, Event = (), Params = (ResMut<'static, u64>,));

    #[test]
    fn macro_callback_blueprint() {
        let mut builder = WorldBuilder::new();
        builder.register::<u64>(0);
        let mut world = builder.build();

        let template = CallbackTemplate::<MacroOnTimeout>::new(on_timeout, world.registry());
        let mut cb = template.generate(TimerCtx {
            order_id: 5,
            fires: 0,
        });
        cb.run(&mut world, ());
        assert_eq!(cb.ctx().fires, 1);
        assert_eq!(*world.resource::<u64>(), 5);
    }
}
