//! Per-frame updater channel. Runs alongside the immutable Timeline pipeline
//! and produces *additional* shapes (or scalar mutations) each frame from a
//! caller-supplied closure of `(t, dt) -> Vec<SceneShape>`.
//!
//! The Timeline pipeline (Segment + Animation::*) remains untouched and
//! bit-exact. Updaters are a separate, mutable channel: they cannot rewrite
//! existing primitives, only append fresh ones for the current frame. This
//! preserves the deterministic apply()/final_state() contract that other
//! crates rely on.
//!
//! Coordinates: world units, same convention as the rest of the model.

use crate::SceneShape;

/// Closure invoked once per frame. `t` is wall-clock time in seconds since
/// the start of the timeline (`frame_idx / fps`); `dt` is the inverse fps,
/// matching the per-frame updater cadence.
///
/// The returned `Vec<SceneShape>` is appended to the frame's shape list so
/// individual updaters can register multiple primitives. Returning an empty
/// vector is equivalent to a no-op — useful when the closure only mutates
/// external state (e.g. a `ValueTracker`) that other dynamics already read.
pub type UpdaterFn = Box<dyn Fn(f32, f32) -> Vec<SceneShape>>;

/// Trait form of the updater so callers that prefer a struct (e.g. for
/// holding state across frames behind interior mutability) can register
/// without boxing into `Fn`.
pub trait Updater {
    fn update(&self, t: f32, dt: f32) -> Vec<SceneShape>;
}

impl<F> Updater for F
where
    F: Fn(f32, f32) -> Vec<SceneShape>,
{
    fn update(&self, t: f32, dt: f32) -> Vec<SceneShape> {
        (self)(t, dt)
    }
}

/// Collection of updaters that share one frame. Drives them in registration
/// order and concatenates results — the renderer is responsible for layer
/// sorting downstream.
#[derive(Default)]
pub struct UpdaterChannel {
    updaters: Vec<UpdaterFn>,
}

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

    pub fn push(&mut self, f: UpdaterFn) {
        self.updaters.push(f);
    }

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

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

    /// Drive every registered updater for the given `(t, dt)` and return the
    /// flattened additions for the frame.
    pub fn shapes_at(&self, t: f32, dt: f32) -> Vec<SceneShape> {
        let mut out = Vec::new();
        for u in &self.updaters {
            out.extend(u(t, dt));
        }
        out
    }
}

/// Convenience helper: convert a frame index to `(t, dt)` in seconds for an
/// fps schedule. Renderers should prefer this over inlining the math so the
/// cadence stays consistent across crates.
pub fn frame_clock(frame_idx: u32, fps: u32) -> (f32, f32) {
    let fps_f = fps.max(1) as f32;
    (frame_idx as f32 / fps_f, 1.0 / fps_f)
}
