presentar_core/
runtime.rs

1//! Command runtime for executing side effects.
2//!
3//! This module provides the infrastructure to execute `Command` values
4//! produced by state updates.
5
6use crate::state::Command;
7use std::collections::HashMap;
8use std::sync::{Arc, Mutex};
9
10/// Router trait for navigation commands.
11pub trait Router: Send + Sync {
12    /// Navigate to a route.
13    fn navigate(&self, route: &str);
14
15    /// Get the current route.
16    fn current_route(&self) -> String;
17}
18
19/// Storage trait for state persistence commands.
20pub trait Storage: Send + Sync {
21    /// Save data to storage.
22    fn save(&self, key: &str, data: &[u8]);
23
24    /// Load data from storage.
25    fn load(&self, key: &str) -> Option<Vec<u8>>;
26
27    /// Remove data from storage.
28    fn remove(&self, key: &str);
29
30    /// Check if a key exists.
31    fn contains(&self, key: &str) -> bool;
32}
33
34/// In-memory storage for testing.
35#[derive(Debug, Default)]
36pub struct MemoryStorage {
37    data: Mutex<HashMap<String, Vec<u8>>>,
38}
39
40impl MemoryStorage {
41    /// Create a new empty memory storage.
42    #[must_use]
43    pub fn new() -> Self {
44        Self::default()
45    }
46
47    /// Get the number of stored items.
48    #[must_use]
49    pub fn len(&self) -> usize {
50        self.data
51            .lock()
52            .expect("MemoryStorage mutex poisoned")
53            .len()
54    }
55
56    /// Check if storage is empty.
57    #[must_use]
58    pub fn is_empty(&self) -> bool {
59        self.data
60            .lock()
61            .expect("MemoryStorage mutex poisoned")
62            .is_empty()
63    }
64
65    /// Clear all stored data.
66    pub fn clear(&self) {
67        self.data
68            .lock()
69            .expect("MemoryStorage mutex poisoned")
70            .clear();
71    }
72}
73
74impl Storage for MemoryStorage {
75    fn save(&self, key: &str, data: &[u8]) {
76        self.data
77            .lock()
78            .expect("MemoryStorage mutex poisoned")
79            .insert(key.to_string(), data.to_vec());
80    }
81
82    fn load(&self, key: &str) -> Option<Vec<u8>> {
83        self.data
84            .lock()
85            .expect("MemoryStorage mutex poisoned")
86            .get(key)
87            .cloned()
88    }
89
90    fn remove(&self, key: &str) {
91        self.data
92            .lock()
93            .expect("MemoryStorage mutex poisoned")
94            .remove(key);
95    }
96
97    fn contains(&self, key: &str) -> bool {
98        self.data
99            .lock()
100            .expect("MemoryStorage mutex poisoned")
101            .contains_key(key)
102    }
103}
104
105/// In-memory router for testing.
106#[derive(Debug)]
107pub struct MemoryRouter {
108    route: Mutex<String>,
109    history: Mutex<Vec<String>>,
110}
111
112impl Default for MemoryRouter {
113    fn default() -> Self {
114        Self::new()
115    }
116}
117
118impl MemoryRouter {
119    /// Create a new memory router.
120    #[must_use]
121    pub fn new() -> Self {
122        Self {
123            route: Mutex::new("/".to_string()),
124            history: Mutex::new(vec!["/".to_string()]),
125        }
126    }
127
128    /// Get navigation history.
129    #[must_use]
130    pub fn history(&self) -> Vec<String> {
131        self.history
132            .lock()
133            .expect("MemoryRouter mutex poisoned")
134            .clone()
135    }
136
137    /// Get history length.
138    #[must_use]
139    pub fn history_len(&self) -> usize {
140        self.history
141            .lock()
142            .expect("MemoryRouter mutex poisoned")
143            .len()
144    }
145}
146
147impl Router for MemoryRouter {
148    fn navigate(&self, route: &str) {
149        let mut current = self.route.lock().expect("MemoryRouter mutex poisoned");
150        *current = route.to_string();
151        self.history
152            .lock()
153            .expect("MemoryRouter mutex poisoned")
154            .push(route.to_string());
155    }
156
157    fn current_route(&self) -> String {
158        self.route
159            .lock()
160            .expect("MemoryRouter mutex poisoned")
161            .clone()
162    }
163}
164
165/// Result of command execution.
166#[derive(Debug)]
167pub enum ExecutionResult<M> {
168    /// No result (`Command::None` or non-message-producing commands)
169    None,
170    /// A single message was produced
171    Message(M),
172    /// Multiple messages were produced
173    Messages(Vec<M>),
174    /// Command is pending (async)
175    Pending,
176}
177
178impl<M> ExecutionResult<M> {
179    /// Check if the result has no messages.
180    #[must_use]
181    pub const fn is_none(&self) -> bool {
182        matches!(self, Self::None)
183    }
184
185    /// Check if there are messages.
186    #[must_use]
187    pub const fn has_messages(&self) -> bool {
188        matches!(self, Self::Message(_) | Self::Messages(_))
189    }
190
191    /// Get messages as a vector.
192    pub fn into_messages(self) -> Vec<M> {
193        match self {
194            Self::None | Self::Pending => vec![],
195            Self::Message(m) => vec![m],
196            Self::Messages(ms) => ms,
197        }
198    }
199}
200
201/// Command executor configuration.
202pub struct ExecutorConfig<R, S> {
203    /// Router for navigation commands
204    pub router: Arc<R>,
205    /// Storage for persistence commands
206    pub storage: Arc<S>,
207}
208
209impl<R: Router, S: Storage> ExecutorConfig<R, S> {
210    /// Create a new executor config.
211    pub fn new(router: R, storage: S) -> Self {
212        Self {
213            router: Arc::new(router),
214            storage: Arc::new(storage),
215        }
216    }
217}
218
219/// Command executor for synchronous commands.
220///
221/// Note: Task commands require async execution and return `ExecutionResult::Pending`.
222pub struct CommandExecutor<R, S> {
223    config: ExecutorConfig<R, S>,
224}
225
226impl<R: Router, S: Storage> CommandExecutor<R, S> {
227    /// Create a new command executor.
228    pub const fn new(config: ExecutorConfig<R, S>) -> Self {
229        Self { config }
230    }
231
232    /// Execute a command synchronously.
233    ///
234    /// For async Task commands, this returns `ExecutionResult::Pending`.
235    /// Use `execute_blocking` to block on async tasks.
236    pub fn execute<M: Send>(&self, command: Command<M>) -> ExecutionResult<M> {
237        match command {
238            Command::None => ExecutionResult::None,
239            Command::Batch(commands) => {
240                let mut messages = Vec::new();
241                for cmd in commands {
242                    match self.execute(cmd) {
243                        ExecutionResult::None | ExecutionResult::Pending => {}
244                        ExecutionResult::Message(m) => messages.push(m),
245                        ExecutionResult::Messages(ms) => messages.extend(ms),
246                    }
247                }
248                if messages.is_empty() {
249                    ExecutionResult::None
250                } else {
251                    ExecutionResult::Messages(messages)
252                }
253            }
254            Command::Task(_) => {
255                // Async tasks can't be executed synchronously
256                ExecutionResult::Pending
257            }
258            Command::Navigate { route } => {
259                self.config.router.navigate(&route);
260                ExecutionResult::None
261            }
262            Command::SaveState { key } => {
263                // SaveState requires the actual state to be passed
264                // This is a limitation - we'd need state access
265                // For now, just record that we tried to save
266                // In practice, the runtime would have state access
267                let _ = key;
268                ExecutionResult::None
269            }
270            Command::LoadState { key, on_load } => {
271                let data = self.config.storage.load(&key);
272                let message = on_load(data);
273                ExecutionResult::Message(message)
274            }
275        }
276    }
277
278    /// Get the router.
279    pub fn router(&self) -> &R {
280        &self.config.router
281    }
282
283    /// Get the storage.
284    pub fn storage(&self) -> &S {
285        &self.config.storage
286    }
287}
288
289/// Create a default executor with memory-based backends.
290#[must_use]
291pub fn default_executor() -> CommandExecutor<MemoryRouter, MemoryStorage> {
292    CommandExecutor::new(ExecutorConfig::new(
293        MemoryRouter::new(),
294        MemoryStorage::new(),
295    ))
296}
297
298// =============================================================================
299// Focus Management
300// =============================================================================
301
302/// Focus direction for keyboard navigation.
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum FocusDirection {
305    /// Move focus forward (Tab)
306    Forward,
307    /// Move focus backward (Shift+Tab)
308    Backward,
309    /// Move focus up (Arrow Up)
310    Up,
311    /// Move focus down (Arrow Down)
312    Down,
313    /// Move focus left (Arrow Left)
314    Left,
315    /// Move focus right (Arrow Right)
316    Right,
317}
318
319/// Manages keyboard focus for widgets.
320#[derive(Debug, Default)]
321pub struct FocusManager {
322    /// Currently focused widget ID
323    focused: Option<u64>,
324    /// Focus ring (ordered list of focusable widget IDs)
325    focus_ring: Vec<u64>,
326    /// Focus trap stack (for modals/dialogs)
327    traps: Vec<FocusTrap>,
328}
329
330/// A focus trap that restricts focus to a subset of widgets.
331#[derive(Debug)]
332pub struct FocusTrap {
333    /// Widget IDs in this trap
334    pub widget_ids: Vec<u64>,
335    /// Initial focused widget when trap was created
336    pub initial_focus: Option<u64>,
337}
338
339impl FocusManager {
340    /// Create a new focus manager.
341    #[must_use]
342    pub fn new() -> Self {
343        Self::default()
344    }
345
346    /// Set the focus ring (ordered list of focusable widgets).
347    pub fn set_focus_ring(&mut self, widget_ids: Vec<u64>) {
348        self.focus_ring = widget_ids;
349    }
350
351    /// Get the currently focused widget ID.
352    #[must_use]
353    pub const fn focused(&self) -> Option<u64> {
354        self.focused
355    }
356
357    /// Set focus to a specific widget.
358    pub fn focus(&mut self, widget_id: u64) -> bool {
359        let available = self.available_focus_ring();
360        if available.contains(&widget_id) {
361            self.focused = Some(widget_id);
362            true
363        } else {
364            false
365        }
366    }
367
368    /// Clear focus.
369    pub fn blur(&mut self) {
370        self.focused = None;
371    }
372
373    /// Move focus in a direction.
374    pub fn move_focus(&mut self, direction: FocusDirection) -> Option<u64> {
375        let ring = self.available_focus_ring();
376        if ring.is_empty() {
377            return None;
378        }
379
380        let current_idx = self
381            .focused
382            .and_then(|f| ring.iter().position(|&id| id == f));
383
384        let next_idx = match direction {
385            FocusDirection::Forward | FocusDirection::Down | FocusDirection::Right => {
386                match current_idx {
387                    Some(idx) => (idx + 1) % ring.len(),
388                    None => 0,
389                }
390            }
391            FocusDirection::Backward | FocusDirection::Up | FocusDirection::Left => {
392                match current_idx {
393                    Some(0) | None => ring.len() - 1,
394                    Some(idx) => idx - 1,
395                }
396            }
397        };
398
399        let next_id = ring[next_idx];
400        self.focused = Some(next_id);
401        Some(next_id)
402    }
403
404    /// Push a focus trap (for modals/dialogs).
405    pub fn push_trap(&mut self, widget_ids: Vec<u64>) {
406        let initial = self.focused;
407        self.traps.push(FocusTrap {
408            widget_ids,
409            initial_focus: initial,
410        });
411        // Focus first item in trap
412        if let Some(first) = self.available_focus_ring().first().copied() {
413            self.focused = Some(first);
414        }
415    }
416
417    /// Pop the current focus trap.
418    pub fn pop_trap(&mut self) -> Option<FocusTrap> {
419        let trap = self.traps.pop();
420        // Restore previous focus
421        if let Some(ref t) = trap {
422            self.focused = t.initial_focus;
423        }
424        trap
425    }
426
427    /// Check if focus is currently trapped.
428    #[must_use]
429    pub fn is_trapped(&self) -> bool {
430        !self.traps.is_empty()
431    }
432
433    /// Get the available focus ring (respecting traps).
434    fn available_focus_ring(&self) -> Vec<u64> {
435        if let Some(trap) = self.traps.last() {
436            trap.widget_ids.clone()
437        } else {
438            self.focus_ring.clone()
439        }
440    }
441
442    /// Check if a widget is focusable.
443    #[must_use]
444    pub fn is_focusable(&self, widget_id: u64) -> bool {
445        self.available_focus_ring().contains(&widget_id)
446    }
447}
448
449// =============================================================================
450// Animation & Timer System
451// =============================================================================
452
453/// Easing functions for smooth animations.
454#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
455pub enum EasingFunction {
456    /// Linear interpolation (no easing)
457    #[default]
458    Linear,
459    /// Quadratic ease in
460    EaseInQuad,
461    /// Quadratic ease out
462    EaseOutQuad,
463    /// Quadratic ease in-out
464    EaseInOutQuad,
465    /// Cubic ease in
466    EaseInCubic,
467    /// Cubic ease out
468    EaseOutCubic,
469    /// Cubic ease in-out
470    EaseInOutCubic,
471    /// Elastic ease out (spring-like)
472    EaseOutElastic,
473    /// Bounce ease out
474    EaseOutBounce,
475}
476
477impl EasingFunction {
478    /// Apply the easing function to a normalized time value (0.0 to 1.0).
479    #[must_use]
480    #[allow(clippy::suboptimal_flops)]
481    pub fn apply(self, t: f32) -> f32 {
482        let t = t.clamp(0.0, 1.0);
483        match self {
484            Self::Linear => t,
485            Self::EaseInQuad => t * t,
486            Self::EaseOutQuad => 1.0 - (1.0 - t) * (1.0 - t),
487            Self::EaseInOutQuad => {
488                if t < 0.5 {
489                    2.0 * t * t
490                } else {
491                    1.0 - (-2.0 * t + 2.0).powi(2) / 2.0
492                }
493            }
494            Self::EaseInCubic => t * t * t,
495            Self::EaseOutCubic => 1.0 - (1.0 - t).powi(3),
496            Self::EaseInOutCubic => {
497                if t < 0.5 {
498                    4.0 * t * t * t
499                } else {
500                    1.0 - (-2.0 * t + 2.0).powi(3) / 2.0
501                }
502            }
503            Self::EaseOutElastic => {
504                if t == 0.0 || t == 1.0 {
505                    t
506                } else {
507                    let c4 = (2.0 * std::f32::consts::PI) / 3.0;
508                    2.0_f32.powf(-10.0 * t) * ((t * 10.0 - 0.75) * c4).sin() + 1.0
509                }
510            }
511            Self::EaseOutBounce => {
512                let n1 = 7.5625;
513                let d1 = 2.75;
514                if t < 1.0 / d1 {
515                    n1 * t * t
516                } else if t < 2.0 / d1 {
517                    let t = t - 1.5 / d1;
518                    n1 * t * t + 0.75
519                } else if t < 2.5 / d1 {
520                    let t = t - 2.25 / d1;
521                    n1 * t * t + 0.9375
522                } else {
523                    let t = t - 2.625 / d1;
524                    n1 * t * t + 0.984_375
525                }
526            }
527        }
528    }
529}
530
531/// A tween that interpolates between two values over time.
532#[derive(Debug, Clone)]
533pub struct Tween<T> {
534    /// Starting value
535    pub from: T,
536    /// Ending value
537    pub to: T,
538    /// Duration in milliseconds
539    pub duration_ms: u32,
540    /// Easing function
541    pub easing: EasingFunction,
542    /// Current elapsed time in milliseconds
543    elapsed_ms: u32,
544}
545
546impl<T: Clone> Tween<T> {
547    /// Create a new tween.
548    pub fn new(from: T, to: T, duration_ms: u32) -> Self {
549        Self {
550            from,
551            to,
552            duration_ms,
553            easing: EasingFunction::default(),
554            elapsed_ms: 0,
555        }
556    }
557
558    /// Set the easing function.
559    #[must_use]
560    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
561        self.easing = easing;
562        self
563    }
564
565    /// Get the normalized progress (0.0 to 1.0).
566    #[must_use]
567    pub fn progress(&self) -> f32 {
568        if self.duration_ms == 0 {
569            1.0
570        } else {
571            (self.elapsed_ms as f32 / self.duration_ms as f32).min(1.0)
572        }
573    }
574
575    /// Get the eased progress value.
576    #[must_use]
577    pub fn eased_progress(&self) -> f32 {
578        self.easing.apply(self.progress())
579    }
580
581    /// Check if the tween is complete.
582    #[must_use]
583    pub const fn is_complete(&self) -> bool {
584        self.elapsed_ms >= self.duration_ms
585    }
586
587    /// Advance the tween by delta milliseconds.
588    pub fn advance(&mut self, delta_ms: u32) {
589        self.elapsed_ms = self
590            .elapsed_ms
591            .saturating_add(delta_ms)
592            .min(self.duration_ms);
593    }
594
595    /// Reset the tween to the beginning.
596    pub fn reset(&mut self) {
597        self.elapsed_ms = 0;
598    }
599}
600
601impl Tween<f32> {
602    /// Get the current interpolated value.
603    #[must_use]
604    #[allow(clippy::suboptimal_flops)]
605    pub fn value(&self) -> f32 {
606        let t = self.eased_progress();
607        self.from + (self.to - self.from) * t
608    }
609}
610
611impl Tween<f64> {
612    /// Get the current interpolated value.
613    #[must_use]
614    #[allow(clippy::suboptimal_flops)]
615    pub fn value(&self) -> f64 {
616        let t = f64::from(self.eased_progress());
617        self.from + (self.to - self.from) * t
618    }
619}
620
621/// Animation state for tracking animation lifecycle.
622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
623pub enum AnimationState {
624    /// Animation is idle/not started
625    Idle,
626    /// Animation is running
627    Running,
628    /// Animation is paused
629    Paused,
630    /// Animation has completed
631    Completed,
632}
633
634/// Unique identifier for an animation.
635pub type AnimationId = u64;
636
637/// An animation instance that can be managed by an Animator.
638#[derive(Debug)]
639pub struct AnimationInstance {
640    /// Unique ID
641    pub id: AnimationId,
642    /// Tween for the animation
643    pub tween: Tween<f32>,
644    /// Current state
645    pub state: AnimationState,
646    /// Loop count (0 = infinite, 1 = once, N = N times)
647    pub loop_count: u32,
648    /// Current loop iteration
649    pub current_loop: u32,
650    /// Whether to reverse on alternate loops (ping-pong)
651    pub alternate: bool,
652    /// Direction (true = forward, false = reverse)
653    forward: bool,
654}
655
656impl AnimationInstance {
657    /// Create a new animation instance.
658    pub fn new(id: AnimationId, from: f32, to: f32, duration_ms: u32) -> Self {
659        Self {
660            id,
661            tween: Tween::new(from, to, duration_ms),
662            state: AnimationState::Idle,
663            loop_count: 1,
664            current_loop: 0,
665            alternate: false,
666            forward: true,
667        }
668    }
669
670    /// Set easing function.
671    #[must_use]
672    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
673        self.tween = self.tween.with_easing(easing);
674        self
675    }
676
677    /// Set loop count (0 = infinite).
678    #[must_use]
679    pub const fn with_loop_count(mut self, count: u32) -> Self {
680        self.loop_count = count;
681        self
682    }
683
684    /// Enable ping-pong alternating.
685    #[must_use]
686    pub const fn with_alternate(mut self, alternate: bool) -> Self {
687        self.alternate = alternate;
688        self
689    }
690
691    /// Start the animation.
692    pub fn start(&mut self) {
693        self.state = AnimationState::Running;
694        self.current_loop = 0;
695        self.forward = true;
696        self.tween.reset();
697    }
698
699    /// Pause the animation.
700    pub fn pause(&mut self) {
701        if self.state == AnimationState::Running {
702            self.state = AnimationState::Paused;
703        }
704    }
705
706    /// Resume the animation.
707    pub fn resume(&mut self) {
708        if self.state == AnimationState::Paused {
709            self.state = AnimationState::Running;
710        }
711    }
712
713    /// Stop the animation.
714    pub fn stop(&mut self) {
715        self.state = AnimationState::Idle;
716        self.tween.reset();
717    }
718
719    /// Get the current value.
720    #[must_use]
721    #[allow(clippy::suboptimal_flops)]
722    pub fn value(&self) -> f32 {
723        if self.forward {
724            self.tween.value()
725        } else {
726            self.tween.from
727                + (self.tween.to - self.tween.from) * (1.0 - self.tween.eased_progress())
728        }
729    }
730
731    /// Advance the animation by delta milliseconds.
732    pub fn advance(&mut self, delta_ms: u32) {
733        if self.state != AnimationState::Running {
734            return;
735        }
736
737        self.tween.advance(delta_ms);
738
739        if self.tween.is_complete() {
740            // Handle looping
741            if self.loop_count == 0 || self.current_loop + 1 < self.loop_count {
742                self.current_loop += 1;
743                self.tween.reset();
744
745                if self.alternate {
746                    self.forward = !self.forward;
747                }
748            } else {
749                self.state = AnimationState::Completed;
750            }
751        }
752    }
753}
754
755/// Manages multiple animations.
756#[derive(Debug, Default)]
757pub struct Animator {
758    animations: Vec<AnimationInstance>,
759    next_id: AnimationId,
760}
761
762impl Animator {
763    /// Create a new animator.
764    #[must_use]
765    pub fn new() -> Self {
766        Self::default()
767    }
768
769    /// Create a new animation and return its ID.
770    pub fn create(&mut self, from: f32, to: f32, duration_ms: u32) -> AnimationId {
771        let id = self.next_id;
772        self.next_id += 1;
773        self.animations
774            .push(AnimationInstance::new(id, from, to, duration_ms));
775        id
776    }
777
778    /// Get an animation by ID.
779    #[must_use]
780    pub fn get(&self, id: AnimationId) -> Option<&AnimationInstance> {
781        self.animations.iter().find(|a| a.id == id)
782    }
783
784    /// Get a mutable animation by ID.
785    pub fn get_mut(&mut self, id: AnimationId) -> Option<&mut AnimationInstance> {
786        self.animations.iter_mut().find(|a| a.id == id)
787    }
788
789    /// Start an animation.
790    pub fn start(&mut self, id: AnimationId) {
791        if let Some(anim) = self.get_mut(id) {
792            anim.start();
793        }
794    }
795
796    /// Pause an animation.
797    pub fn pause(&mut self, id: AnimationId) {
798        if let Some(anim) = self.get_mut(id) {
799            anim.pause();
800        }
801    }
802
803    /// Resume an animation.
804    pub fn resume(&mut self, id: AnimationId) {
805        if let Some(anim) = self.get_mut(id) {
806            anim.resume();
807        }
808    }
809
810    /// Stop an animation.
811    pub fn stop(&mut self, id: AnimationId) {
812        if let Some(anim) = self.get_mut(id) {
813            anim.stop();
814        }
815    }
816
817    /// Remove an animation.
818    pub fn remove(&mut self, id: AnimationId) {
819        self.animations.retain(|a| a.id != id);
820    }
821
822    /// Advance all animations by delta milliseconds.
823    pub fn advance(&mut self, delta_ms: u32) {
824        for anim in &mut self.animations {
825            anim.advance(delta_ms);
826        }
827    }
828
829    /// Get the value of an animation.
830    #[must_use]
831    pub fn value(&self, id: AnimationId) -> Option<f32> {
832        self.get(id).map(AnimationInstance::value)
833    }
834
835    /// Get the number of animations.
836    #[must_use]
837    pub fn len(&self) -> usize {
838        self.animations.len()
839    }
840
841    /// Check if there are no animations.
842    #[must_use]
843    pub fn is_empty(&self) -> bool {
844        self.animations.is_empty()
845    }
846
847    /// Remove all completed animations.
848    pub fn cleanup_completed(&mut self) {
849        self.animations
850            .retain(|a| a.state != AnimationState::Completed);
851    }
852
853    /// Check if any animations are running.
854    #[must_use]
855    pub fn has_running(&self) -> bool {
856        self.animations
857            .iter()
858            .any(|a| a.state == AnimationState::Running)
859    }
860}
861
862/// A timer that fires at regular intervals.
863#[derive(Debug)]
864pub struct Timer {
865    /// Interval in milliseconds
866    pub interval_ms: u32,
867    /// Elapsed time since last tick
868    elapsed_ms: u32,
869    /// Whether the timer is running
870    running: bool,
871    /// Number of times the timer has fired
872    tick_count: u64,
873    /// Optional limit on tick count (0 = unlimited)
874    max_ticks: u64,
875}
876
877impl Timer {
878    /// Create a new timer with the given interval.
879    #[must_use]
880    pub const fn new(interval_ms: u32) -> Self {
881        Self {
882            interval_ms,
883            elapsed_ms: 0,
884            running: false,
885            tick_count: 0,
886            max_ticks: 0,
887        }
888    }
889
890    /// Set maximum tick count (0 = unlimited).
891    #[must_use]
892    pub const fn with_max_ticks(mut self, max: u64) -> Self {
893        self.max_ticks = max;
894        self
895    }
896
897    /// Start the timer.
898    pub fn start(&mut self) {
899        self.running = true;
900    }
901
902    /// Stop the timer.
903    pub fn stop(&mut self) {
904        self.running = false;
905    }
906
907    /// Reset the timer.
908    pub fn reset(&mut self) {
909        self.elapsed_ms = 0;
910        self.tick_count = 0;
911    }
912
913    /// Check if the timer is running.
914    #[must_use]
915    pub const fn is_running(&self) -> bool {
916        self.running
917    }
918
919    /// Get the tick count.
920    #[must_use]
921    pub const fn tick_count(&self) -> u64 {
922        self.tick_count
923    }
924
925    /// Advance the timer and return the number of ticks that occurred.
926    pub fn advance(&mut self, delta_ms: u32) -> u32 {
927        if !self.running || self.interval_ms == 0 {
928            return 0;
929        }
930
931        self.elapsed_ms += delta_ms;
932        let ticks = self.elapsed_ms / self.interval_ms;
933        self.elapsed_ms %= self.interval_ms;
934
935        // Apply ticks with limit check
936        let mut actual_ticks = 0;
937        for _ in 0..ticks {
938            if self.max_ticks > 0 && self.tick_count >= self.max_ticks {
939                self.running = false;
940                break;
941            }
942            self.tick_count += 1;
943            actual_ticks += 1;
944        }
945
946        actual_ticks
947    }
948
949    /// Get progress to next tick (0.0 to 1.0).
950    #[must_use]
951    pub fn progress(&self) -> f32 {
952        if self.interval_ms == 0 {
953            0.0
954        } else {
955            self.elapsed_ms as f32 / self.interval_ms as f32
956        }
957    }
958}
959
960/// Frame timer for 60fps animations.
961#[derive(Debug)]
962pub struct FrameTimer {
963    /// Target frame duration in microseconds (16667 for 60fps)
964    target_frame_us: u64,
965    /// Last frame timestamp in microseconds
966    last_frame_us: Option<u64>,
967    /// Accumulated frame time for averaging
968    frame_times: [u64; 60],
969    /// Current frame index
970    frame_index: usize,
971    /// Number of recorded frame deltas
972    delta_count: usize,
973    /// Total frames rendered
974    total_frames: u64,
975}
976
977impl Default for FrameTimer {
978    fn default() -> Self {
979        Self::new(60)
980    }
981}
982
983impl FrameTimer {
984    /// Create a new frame timer with target FPS.
985    #[must_use]
986    pub fn new(target_fps: u32) -> Self {
987        let target_frame_us = if target_fps > 0 {
988            1_000_000 / u64::from(target_fps)
989        } else {
990            16667
991        };
992        Self {
993            target_frame_us,
994            last_frame_us: None,
995            frame_times: [0; 60],
996            frame_index: 0,
997            delta_count: 0,
998            total_frames: 0,
999        }
1000    }
1001
1002    /// Record a frame with the current timestamp in microseconds.
1003    pub fn frame(&mut self, now_us: u64) {
1004        if let Some(last) = self.last_frame_us {
1005            let delta = now_us.saturating_sub(last);
1006            self.frame_times[self.frame_index] = delta;
1007            self.frame_index = (self.frame_index + 1) % 60;
1008            self.delta_count = (self.delta_count + 1).min(60);
1009        }
1010        self.last_frame_us = Some(now_us);
1011        self.total_frames += 1;
1012    }
1013
1014    /// Get the average frame time in microseconds.
1015    #[must_use]
1016    pub fn average_frame_time_us(&self) -> u64 {
1017        if self.delta_count == 0 {
1018            return self.target_frame_us;
1019        }
1020        let sum: u64 = self.frame_times[..self.delta_count].iter().sum();
1021        sum / self.delta_count as u64
1022    }
1023
1024    /// Get the current FPS.
1025    #[must_use]
1026    pub fn fps(&self) -> f32 {
1027        let avg = self.average_frame_time_us();
1028        if avg == 0 {
1029            0.0
1030        } else {
1031            1_000_000.0 / avg as f32
1032        }
1033    }
1034
1035    /// Check if we're hitting target FPS (within 10% tolerance).
1036    #[must_use]
1037    pub fn is_on_target(&self) -> bool {
1038        let avg = self.average_frame_time_us();
1039        let target = self.target_frame_us;
1040        // Within 10% of target
1041        avg <= target + target / 10
1042    }
1043
1044    /// Get the target frame time in milliseconds.
1045    #[must_use]
1046    pub fn target_frame_ms(&self) -> f32 {
1047        self.target_frame_us as f32 / 1000.0
1048    }
1049
1050    /// Get total frames rendered.
1051    #[must_use]
1052    pub const fn total_frames(&self) -> u64 {
1053        self.total_frames
1054    }
1055}
1056
1057// =============================================================================
1058// Data Refresh Manager
1059// =============================================================================
1060
1061/// Manages periodic data refresh for data sources.
1062#[derive(Debug)]
1063pub struct DataRefreshManager {
1064    /// Registered refresh tasks
1065    tasks: Vec<RefreshTask>,
1066    /// Current timestamp in milliseconds
1067    current_time_ms: u64,
1068}
1069
1070/// A scheduled data refresh task.
1071#[derive(Debug, Clone)]
1072pub struct RefreshTask {
1073    /// Data source key
1074    pub key: String,
1075    /// Refresh interval in milliseconds
1076    pub interval_ms: u64,
1077    /// Last refresh timestamp
1078    pub last_refresh_ms: u64,
1079    /// Whether task is active
1080    pub active: bool,
1081}
1082
1083impl DataRefreshManager {
1084    /// Create a new refresh manager.
1085    #[must_use]
1086    pub const fn new() -> Self {
1087        Self {
1088            tasks: Vec::new(),
1089            current_time_ms: 0,
1090        }
1091    }
1092
1093    /// Register a data source for periodic refresh.
1094    ///
1095    /// # Arguments
1096    ///
1097    /// * `key` - Data source identifier
1098    /// * `interval_ms` - Refresh interval in milliseconds
1099    pub fn register(&mut self, key: impl Into<String>, interval_ms: u64) {
1100        let key = key.into();
1101
1102        // Check if already registered
1103        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1104            task.interval_ms = interval_ms;
1105            task.active = true;
1106            return;
1107        }
1108
1109        self.tasks.push(RefreshTask {
1110            key,
1111            interval_ms,
1112            last_refresh_ms: 0,
1113            active: true,
1114        });
1115    }
1116
1117    /// Unregister a data source.
1118    pub fn unregister(&mut self, key: &str) {
1119        self.tasks.retain(|t| t.key != key);
1120    }
1121
1122    /// Pause refresh for a data source.
1123    pub fn pause(&mut self, key: &str) {
1124        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1125            task.active = false;
1126        }
1127    }
1128
1129    /// Resume refresh for a data source.
1130    pub fn resume(&mut self, key: &str) {
1131        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1132            task.active = true;
1133        }
1134    }
1135
1136    /// Update the manager with the current timestamp.
1137    ///
1138    /// Returns keys of data sources that need to be refreshed.
1139    pub fn update(&mut self, current_time_ms: u64) -> Vec<String> {
1140        self.current_time_ms = current_time_ms;
1141
1142        let mut to_refresh = Vec::new();
1143
1144        for task in &mut self.tasks {
1145            if !task.active {
1146                continue;
1147            }
1148
1149            let elapsed = current_time_ms.saturating_sub(task.last_refresh_ms);
1150            if elapsed >= task.interval_ms {
1151                to_refresh.push(task.key.clone());
1152                task.last_refresh_ms = current_time_ms;
1153            }
1154        }
1155
1156        to_refresh
1157    }
1158
1159    /// Force immediate refresh of a data source.
1160    pub fn force_refresh(&mut self, key: &str) -> bool {
1161        if let Some(task) = self.tasks.iter_mut().find(|t| t.key == key) {
1162            task.last_refresh_ms = 0;
1163            true
1164        } else {
1165            false
1166        }
1167    }
1168
1169    /// Get all registered tasks.
1170    #[must_use]
1171    pub fn tasks(&self) -> &[RefreshTask] {
1172        &self.tasks
1173    }
1174
1175    /// Get task by key.
1176    #[must_use]
1177    pub fn get_task(&self, key: &str) -> Option<&RefreshTask> {
1178        self.tasks.iter().find(|t| t.key == key)
1179    }
1180
1181    /// Check if a data source is due for refresh.
1182    #[must_use]
1183    pub fn is_due(&self, key: &str) -> bool {
1184        if let Some(task) = self.tasks.iter().find(|t| t.key == key) {
1185            if !task.active {
1186                return false;
1187            }
1188            let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1189            elapsed >= task.interval_ms
1190        } else {
1191            false
1192        }
1193    }
1194
1195    /// Get time until next refresh for a data source (in ms).
1196    #[must_use]
1197    pub fn time_until_refresh(&self, key: &str) -> Option<u64> {
1198        self.tasks.iter().find(|t| t.key == key).map(|task| {
1199            if !task.active {
1200                return u64::MAX;
1201            }
1202            let elapsed = self.current_time_ms.saturating_sub(task.last_refresh_ms);
1203            task.interval_ms.saturating_sub(elapsed)
1204        })
1205    }
1206}
1207
1208impl Default for DataRefreshManager {
1209    fn default() -> Self {
1210        Self::new()
1211    }
1212}
1213
1214// =============================================================================
1215// Widget Animation API
1216// =============================================================================
1217
1218/// Configuration for property transitions.
1219#[derive(Debug, Clone)]
1220pub struct TransitionConfig {
1221    /// Duration in milliseconds
1222    pub duration_ms: u32,
1223    /// Easing function
1224    pub easing: EasingFunction,
1225    /// Delay before starting in milliseconds
1226    pub delay_ms: u32,
1227}
1228
1229impl Default for TransitionConfig {
1230    fn default() -> Self {
1231        Self {
1232            duration_ms: 300,
1233            easing: EasingFunction::EaseInOutCubic,
1234            delay_ms: 0,
1235        }
1236    }
1237}
1238
1239impl TransitionConfig {
1240    /// Create a new transition configuration.
1241    #[must_use]
1242    pub const fn new(duration_ms: u32) -> Self {
1243        Self {
1244            duration_ms,
1245            easing: EasingFunction::EaseInOutCubic,
1246            delay_ms: 0,
1247        }
1248    }
1249
1250    /// Set the easing function.
1251    #[must_use]
1252    pub const fn with_easing(mut self, easing: EasingFunction) -> Self {
1253        self.easing = easing;
1254        self
1255    }
1256
1257    /// Set the delay.
1258    #[must_use]
1259    pub const fn with_delay(mut self, delay_ms: u32) -> Self {
1260        self.delay_ms = delay_ms;
1261        self
1262    }
1263
1264    /// Quick preset (150ms)
1265    #[must_use]
1266    pub const fn quick() -> Self {
1267        Self::new(150)
1268    }
1269
1270    /// Normal preset (300ms)
1271    #[must_use]
1272    pub const fn normal() -> Self {
1273        Self::new(300)
1274    }
1275
1276    /// Slow preset (500ms)
1277    #[must_use]
1278    pub const fn slow() -> Self {
1279        Self::new(500)
1280    }
1281}
1282
1283/// An animated property that smoothly transitions between values.
1284///
1285/// Use this in widget state to animate property changes automatically.
1286#[derive(Debug, Clone)]
1287pub struct AnimatedProperty<T> {
1288    /// Current visual value (what's rendered)
1289    current: T,
1290    /// Target value we're animating towards
1291    target: T,
1292    /// Starting value of current animation
1293    start: T,
1294    /// Transition configuration
1295    config: TransitionConfig,
1296    /// Elapsed time in milliseconds
1297    elapsed_ms: u32,
1298    /// Whether an animation is in progress
1299    animating: bool,
1300}
1301
1302impl<T: Clone + Default> Default for AnimatedProperty<T> {
1303    fn default() -> Self {
1304        Self::new(T::default())
1305    }
1306}
1307
1308impl<T: Clone> AnimatedProperty<T> {
1309    /// Create a new animated property with an initial value.
1310    pub fn new(value: T) -> Self {
1311        Self {
1312            current: value.clone(),
1313            target: value.clone(),
1314            start: value,
1315            config: TransitionConfig::default(),
1316            elapsed_ms: 0,
1317            animating: false,
1318        }
1319    }
1320
1321    /// Create with a custom transition config.
1322    pub fn with_config(value: T, config: TransitionConfig) -> Self {
1323        Self {
1324            current: value.clone(),
1325            target: value.clone(),
1326            start: value,
1327            config,
1328            elapsed_ms: 0,
1329            animating: false,
1330        }
1331    }
1332
1333    /// Get the current visual value.
1334    pub const fn get(&self) -> &T {
1335        &self.current
1336    }
1337
1338    /// Get the target value.
1339    pub const fn target(&self) -> &T {
1340        &self.target
1341    }
1342
1343    /// Check if currently animating.
1344    #[must_use]
1345    pub const fn is_animating(&self) -> bool {
1346        self.animating
1347    }
1348
1349    /// Set a new target value, starting an animation.
1350    pub fn set(&mut self, value: T) {
1351        self.start = self.current.clone();
1352        self.target = value;
1353        self.elapsed_ms = 0;
1354        self.animating = true;
1355    }
1356
1357    /// Set value immediately without animation.
1358    pub fn set_immediate(&mut self, value: T) {
1359        self.current = value.clone();
1360        self.target = value.clone();
1361        self.start = value;
1362        self.animating = false;
1363        self.elapsed_ms = 0;
1364    }
1365
1366    /// Get animation progress (0.0 to 1.0).
1367    #[must_use]
1368    pub fn progress(&self) -> f32 {
1369        if !self.animating {
1370            return 1.0;
1371        }
1372
1373        let total = self.config.duration_ms + self.config.delay_ms;
1374        if total == 0 {
1375            return 1.0;
1376        }
1377
1378        if self.elapsed_ms < self.config.delay_ms {
1379            return 0.0;
1380        }
1381
1382        let elapsed_after_delay = self.elapsed_ms - self.config.delay_ms;
1383        (elapsed_after_delay as f32 / self.config.duration_ms as f32).min(1.0)
1384    }
1385
1386    /// Get eased progress.
1387    #[must_use]
1388    pub fn eased_progress(&self) -> f32 {
1389        self.config.easing.apply(self.progress())
1390    }
1391}
1392
1393impl AnimatedProperty<f32> {
1394    /// Advance the animation by delta milliseconds.
1395    pub fn advance(&mut self, delta_ms: u32) {
1396        if !self.animating {
1397            return;
1398        }
1399
1400        self.elapsed_ms += delta_ms;
1401
1402        let t = self.eased_progress();
1403        self.current = (self.target - self.start).mul_add(t, self.start);
1404
1405        if self.progress() >= 1.0 {
1406            self.current = self.target;
1407            self.animating = false;
1408        }
1409    }
1410}
1411
1412impl AnimatedProperty<f64> {
1413    /// Advance the animation by delta milliseconds.
1414    pub fn advance(&mut self, delta_ms: u32) {
1415        if !self.animating {
1416            return;
1417        }
1418
1419        self.elapsed_ms += delta_ms;
1420
1421        let t = f64::from(self.eased_progress());
1422        self.current = (self.target - self.start).mul_add(t, self.start);
1423
1424        if self.progress() >= 1.0 {
1425            self.current = self.target;
1426            self.animating = false;
1427        }
1428    }
1429}
1430
1431impl AnimatedProperty<crate::Color> {
1432    /// Advance the animation by delta milliseconds.
1433    pub fn advance(&mut self, delta_ms: u32) {
1434        if !self.animating {
1435            return;
1436        }
1437
1438        self.elapsed_ms += delta_ms;
1439
1440        let t = self.eased_progress();
1441        self.current = crate::Color {
1442            r: (self.target.r - self.start.r).mul_add(t, self.start.r),
1443            g: (self.target.g - self.start.g).mul_add(t, self.start.g),
1444            b: (self.target.b - self.start.b).mul_add(t, self.start.b),
1445            a: (self.target.a - self.start.a).mul_add(t, self.start.a),
1446        };
1447
1448        if self.progress() >= 1.0 {
1449            self.current = self.target;
1450            self.animating = false;
1451        }
1452    }
1453}
1454
1455impl AnimatedProperty<crate::Point> {
1456    /// Advance the animation by delta milliseconds.
1457    pub fn advance(&mut self, delta_ms: u32) {
1458        if !self.animating {
1459            return;
1460        }
1461
1462        self.elapsed_ms += delta_ms;
1463
1464        let t = self.eased_progress();
1465        self.current = crate::Point {
1466            x: (self.target.x - self.start.x).mul_add(t, self.start.x),
1467            y: (self.target.y - self.start.y).mul_add(t, self.start.y),
1468        };
1469
1470        if self.progress() >= 1.0 {
1471            self.current = self.target;
1472            self.animating = false;
1473        }
1474    }
1475}
1476
1477impl AnimatedProperty<crate::Size> {
1478    /// Advance the animation by delta milliseconds.
1479    pub fn advance(&mut self, delta_ms: u32) {
1480        if !self.animating {
1481            return;
1482        }
1483
1484        self.elapsed_ms += delta_ms;
1485
1486        let t = self.eased_progress();
1487        self.current = crate::Size {
1488            width: (self.target.width - self.start.width).mul_add(t, self.start.width),
1489            height: (self.target.height - self.start.height).mul_add(t, self.start.height),
1490        };
1491
1492        if self.progress() >= 1.0 {
1493            self.current = self.target;
1494            self.animating = false;
1495        }
1496    }
1497}
1498
1499/// Spring animation configuration.
1500#[derive(Debug, Clone, Copy)]
1501pub struct SpringConfig {
1502    /// Spring stiffness (higher = faster oscillation)
1503    pub stiffness: f32,
1504    /// Damping (higher = less oscillation)
1505    pub damping: f32,
1506    /// Mass of the object
1507    pub mass: f32,
1508}
1509
1510impl Default for SpringConfig {
1511    fn default() -> Self {
1512        Self {
1513            stiffness: 100.0,
1514            damping: 10.0,
1515            mass: 1.0,
1516        }
1517    }
1518}
1519
1520impl SpringConfig {
1521    /// Create a new spring configuration.
1522    #[must_use]
1523    pub const fn new(stiffness: f32, damping: f32, mass: f32) -> Self {
1524        Self {
1525            stiffness,
1526            damping,
1527            mass,
1528        }
1529    }
1530
1531    /// Gentle spring preset.
1532    #[must_use]
1533    pub const fn gentle() -> Self {
1534        Self::new(100.0, 15.0, 1.0)
1535    }
1536
1537    /// Bouncy spring preset.
1538    #[must_use]
1539    pub const fn bouncy() -> Self {
1540        Self::new(300.0, 10.0, 1.0)
1541    }
1542
1543    /// Stiff spring preset.
1544    #[must_use]
1545    pub const fn stiff() -> Self {
1546        Self::new(500.0, 30.0, 1.0)
1547    }
1548}
1549
1550/// Spring-based animation for physics-like motion.
1551#[derive(Debug, Clone)]
1552pub struct SpringAnimation {
1553    /// Current position
1554    position: f32,
1555    /// Current velocity
1556    velocity: f32,
1557    /// Target position
1558    target: f32,
1559    /// Spring configuration
1560    config: SpringConfig,
1561    /// Velocity threshold for considering animation complete
1562    velocity_threshold: f32,
1563    /// Position threshold for considering animation complete
1564    position_threshold: f32,
1565}
1566
1567impl SpringAnimation {
1568    /// Create a new spring animation.
1569    #[must_use]
1570    pub fn new(initial: f32) -> Self {
1571        Self {
1572            position: initial,
1573            velocity: 0.0,
1574            target: initial,
1575            config: SpringConfig::default(),
1576            velocity_threshold: 0.01,
1577            position_threshold: 0.001,
1578        }
1579    }
1580
1581    /// Create with custom spring config.
1582    #[must_use]
1583    pub const fn with_config(initial: f32, config: SpringConfig) -> Self {
1584        Self {
1585            position: initial,
1586            velocity: 0.0,
1587            target: initial,
1588            config,
1589            velocity_threshold: 0.01,
1590            position_threshold: 0.001,
1591        }
1592    }
1593
1594    /// Get the current position.
1595    #[must_use]
1596    pub const fn position(&self) -> f32 {
1597        self.position
1598    }
1599
1600    /// Get the current velocity.
1601    #[must_use]
1602    pub const fn velocity(&self) -> f32 {
1603        self.velocity
1604    }
1605
1606    /// Get the target.
1607    #[must_use]
1608    pub const fn target(&self) -> f32 {
1609        self.target
1610    }
1611
1612    /// Set the target position.
1613    pub fn set_target(&mut self, target: f32) {
1614        self.target = target;
1615    }
1616
1617    /// Set position immediately without animation.
1618    pub fn set_immediate(&mut self, position: f32) {
1619        self.position = position;
1620        self.target = position;
1621        self.velocity = 0.0;
1622    }
1623
1624    /// Check if the animation is at rest.
1625    #[must_use]
1626    pub fn is_at_rest(&self) -> bool {
1627        let position_diff = (self.position - self.target).abs();
1628        let velocity_abs = self.velocity.abs();
1629        position_diff < self.position_threshold && velocity_abs < self.velocity_threshold
1630    }
1631
1632    /// Advance the spring animation by delta seconds.
1633    pub fn advance(&mut self, delta_s: f32) {
1634        if self.is_at_rest() {
1635            self.position = self.target;
1636            self.velocity = 0.0;
1637            return;
1638        }
1639
1640        // Spring physics: F = -kx - cv
1641        // a = F/m = (-kx - cv) / m
1642        let displacement = self.position - self.target;
1643        let spring_force = -self.config.stiffness * displacement;
1644        let damping_force = -self.config.damping * self.velocity;
1645        let acceleration = (spring_force + damping_force) / self.config.mass;
1646
1647        // Semi-implicit Euler integration
1648        self.velocity += acceleration * delta_s;
1649        self.position += self.velocity * delta_s;
1650    }
1651
1652    /// Advance by delta milliseconds.
1653    pub fn advance_ms(&mut self, delta_ms: u32) {
1654        self.advance(delta_ms as f32 / 1000.0);
1655    }
1656}
1657
1658#[cfg(test)]
1659mod tests {
1660    use super::*;
1661
1662    // =========================================================================
1663    // MemoryStorage Tests
1664    // =========================================================================
1665
1666    #[test]
1667    fn test_memory_storage_new() {
1668        let storage = MemoryStorage::new();
1669        assert!(storage.is_empty());
1670        assert_eq!(storage.len(), 0);
1671    }
1672
1673    #[test]
1674    fn test_memory_storage_save_load() {
1675        let storage = MemoryStorage::new();
1676        storage.save("key1", b"value1");
1677
1678        assert!(!storage.is_empty());
1679        assert_eq!(storage.len(), 1);
1680        assert_eq!(storage.load("key1"), Some(b"value1".to_vec()));
1681    }
1682
1683    #[test]
1684    fn test_memory_storage_load_missing() {
1685        let storage = MemoryStorage::new();
1686        assert_eq!(storage.load("nonexistent"), None);
1687    }
1688
1689    #[test]
1690    fn test_memory_storage_contains() {
1691        let storage = MemoryStorage::new();
1692        storage.save("exists", b"data");
1693
1694        assert!(storage.contains("exists"));
1695        assert!(!storage.contains("missing"));
1696    }
1697
1698    #[test]
1699    fn test_memory_storage_remove() {
1700        let storage = MemoryStorage::new();
1701        storage.save("key", b"value");
1702        assert!(storage.contains("key"));
1703
1704        storage.remove("key");
1705        assert!(!storage.contains("key"));
1706    }
1707
1708    #[test]
1709    fn test_memory_storage_clear() {
1710        let storage = MemoryStorage::new();
1711        storage.save("a", b"1");
1712        storage.save("b", b"2");
1713        assert_eq!(storage.len(), 2);
1714
1715        storage.clear();
1716        assert!(storage.is_empty());
1717    }
1718
1719    #[test]
1720    fn test_memory_storage_overwrite() {
1721        let storage = MemoryStorage::new();
1722        storage.save("key", b"first");
1723        storage.save("key", b"second");
1724
1725        assert_eq!(storage.len(), 1);
1726        assert_eq!(storage.load("key"), Some(b"second".to_vec()));
1727    }
1728
1729    // =========================================================================
1730    // MemoryRouter Tests
1731    // =========================================================================
1732
1733    #[test]
1734    fn test_memory_router_new() {
1735        let router = MemoryRouter::new();
1736        assert_eq!(router.current_route(), "/");
1737        assert_eq!(router.history_len(), 1);
1738    }
1739
1740    #[test]
1741    fn test_memory_router_navigate() {
1742        let router = MemoryRouter::new();
1743        router.navigate("/home");
1744
1745        assert_eq!(router.current_route(), "/home");
1746    }
1747
1748    #[test]
1749    fn test_memory_router_history() {
1750        let router = MemoryRouter::new();
1751        router.navigate("/page1");
1752        router.navigate("/page2");
1753        router.navigate("/page3");
1754
1755        let history = router.history();
1756        assert_eq!(history, vec!["/", "/page1", "/page2", "/page3"]);
1757    }
1758
1759    #[test]
1760    fn test_memory_router_default() {
1761        let router = MemoryRouter::default();
1762        assert_eq!(router.current_route(), "/");
1763    }
1764
1765    // =========================================================================
1766    // ExecutionResult Tests
1767    // =========================================================================
1768
1769    #[test]
1770    fn test_execution_result_none() {
1771        let result: ExecutionResult<i32> = ExecutionResult::None;
1772        assert!(result.is_none());
1773        assert!(!result.has_messages());
1774    }
1775
1776    #[test]
1777    fn test_execution_result_message() {
1778        let result = ExecutionResult::Message(42);
1779        assert!(!result.is_none());
1780        assert!(result.has_messages());
1781    }
1782
1783    #[test]
1784    fn test_execution_result_messages() {
1785        let result = ExecutionResult::Messages(vec![1, 2, 3]);
1786        assert!(!result.is_none());
1787        assert!(result.has_messages());
1788    }
1789
1790    #[test]
1791    fn test_execution_result_pending() {
1792        let result: ExecutionResult<i32> = ExecutionResult::Pending;
1793        assert!(!result.is_none());
1794        assert!(!result.has_messages());
1795    }
1796
1797    #[test]
1798    fn test_execution_result_into_messages_none() {
1799        let result: ExecutionResult<i32> = ExecutionResult::None;
1800        assert!(result.into_messages().is_empty());
1801    }
1802
1803    #[test]
1804    fn test_execution_result_into_messages_single() {
1805        let result = ExecutionResult::Message(42);
1806        assert_eq!(result.into_messages(), vec![42]);
1807    }
1808
1809    #[test]
1810    fn test_execution_result_into_messages_multiple() {
1811        let result = ExecutionResult::Messages(vec![1, 2, 3]);
1812        assert_eq!(result.into_messages(), vec![1, 2, 3]);
1813    }
1814
1815    #[test]
1816    fn test_execution_result_into_messages_pending() {
1817        let result: ExecutionResult<i32> = ExecutionResult::Pending;
1818        assert!(result.into_messages().is_empty());
1819    }
1820
1821    // =========================================================================
1822    // CommandExecutor Tests
1823    // =========================================================================
1824
1825    #[test]
1826    fn test_executor_execute_none() {
1827        let executor = default_executor();
1828        let result = executor.execute::<()>(Command::None);
1829        assert!(result.is_none());
1830    }
1831
1832    #[test]
1833    fn test_executor_execute_navigate() {
1834        let executor = default_executor();
1835        let result = executor.execute::<()>(Command::Navigate {
1836            route: "/dashboard".to_string(),
1837        });
1838
1839        assert!(result.is_none());
1840        assert_eq!(executor.router().current_route(), "/dashboard");
1841    }
1842
1843    #[test]
1844    fn test_executor_execute_navigate_multiple() {
1845        let executor = default_executor();
1846
1847        executor.execute::<()>(Command::Navigate {
1848            route: "/page1".to_string(),
1849        });
1850        executor.execute::<()>(Command::Navigate {
1851            route: "/page2".to_string(),
1852        });
1853
1854        assert_eq!(executor.router().current_route(), "/page2");
1855        assert_eq!(executor.router().history_len(), 3); // "/" + "/page1" + "/page2"
1856    }
1857
1858    fn load_state_handler(data: Option<Vec<u8>>) -> String {
1859        data.map_or_else(
1860            || "not found".to_string(),
1861            |d| String::from_utf8(d).unwrap(),
1862        )
1863    }
1864
1865    #[test]
1866    fn test_executor_execute_load_state_found() {
1867        let executor = default_executor();
1868        executor.storage().save("my_key", b"stored_data");
1869
1870        let result = executor.execute(Command::LoadState {
1871            key: "my_key".to_string(),
1872            on_load: load_state_handler,
1873        });
1874
1875        match result {
1876            ExecutionResult::Message(msg) => assert_eq!(msg, "stored_data"),
1877            _ => panic!("Expected Message result"),
1878        }
1879    }
1880
1881    #[test]
1882    fn test_executor_execute_load_state_not_found() {
1883        let executor = default_executor();
1884
1885        let result = executor.execute(Command::LoadState {
1886            key: "missing_key".to_string(),
1887            on_load: load_state_handler,
1888        });
1889
1890        match result {
1891            ExecutionResult::Message(msg) => assert_eq!(msg, "not found"),
1892            _ => panic!("Expected Message result"),
1893        }
1894    }
1895
1896    #[test]
1897    fn test_executor_execute_batch_empty() {
1898        let executor = default_executor();
1899        let result = executor.execute::<()>(Command::Batch(vec![]));
1900        assert!(result.is_none());
1901    }
1902
1903    #[test]
1904    fn test_executor_execute_batch_navigations() {
1905        let executor = default_executor();
1906        let result = executor.execute::<()>(Command::Batch(vec![
1907            Command::Navigate {
1908                route: "/a".to_string(),
1909            },
1910            Command::Navigate {
1911                route: "/b".to_string(),
1912            },
1913            Command::Navigate {
1914                route: "/c".to_string(),
1915            },
1916        ]));
1917
1918        assert!(result.is_none());
1919        assert_eq!(executor.router().current_route(), "/c");
1920        assert_eq!(executor.router().history_len(), 4);
1921    }
1922
1923    fn batch_load_handler(data: Option<Vec<u8>>) -> i32 {
1924        data.map_or(0, |_| 42)
1925    }
1926
1927    #[test]
1928    fn test_executor_execute_batch_mixed() {
1929        let executor = default_executor();
1930        executor.storage().save("key", b"data");
1931
1932        let result = executor.execute(Command::Batch(vec![
1933            Command::Navigate {
1934                route: "/page".to_string(),
1935            },
1936            Command::LoadState {
1937                key: "key".to_string(),
1938                on_load: batch_load_handler,
1939            },
1940        ]));
1941
1942        match result {
1943            ExecutionResult::Messages(msgs) => {
1944                assert_eq!(msgs, vec![42]);
1945            }
1946            _ => panic!("Expected Messages result"),
1947        }
1948        assert_eq!(executor.router().current_route(), "/page");
1949    }
1950
1951    #[test]
1952    fn test_executor_execute_task_returns_pending() {
1953        let executor = default_executor();
1954        let result = executor.execute(Command::task(async { 42 }));
1955
1956        match result {
1957            ExecutionResult::Pending => {}
1958            _ => panic!("Expected Pending result for Task"),
1959        }
1960    }
1961
1962    #[test]
1963    fn test_executor_execute_save_state() {
1964        let executor = default_executor();
1965        let result = executor.execute::<()>(Command::SaveState {
1966            key: "test".to_string(),
1967        });
1968
1969        // SaveState without state access just returns None
1970        assert!(result.is_none());
1971    }
1972
1973    #[test]
1974    fn test_default_executor() {
1975        let executor = default_executor();
1976        assert_eq!(executor.router().current_route(), "/");
1977        assert!(executor.storage().is_empty());
1978    }
1979
1980    // =========================================================================
1981    // Integration Tests
1982    // =========================================================================
1983
1984    #[test]
1985    fn test_state_update_with_command_execution() {
1986        use crate::state::{CounterMessage, CounterState, State};
1987
1988        let executor = default_executor();
1989        let mut state = CounterState::default();
1990
1991        // Update state
1992        let cmd = state.update(CounterMessage::Increment);
1993        assert_eq!(state.count, 1);
1994
1995        // Execute command (should be None for CounterState)
1996        let result = executor.execute(cmd);
1997        assert!(result.is_none());
1998    }
1999
2000    #[test]
2001    fn test_navigation_state_flow() {
2002        let executor = default_executor();
2003
2004        // Simulate app navigation
2005        executor.execute::<()>(Command::Navigate {
2006            route: "/login".to_string(),
2007        });
2008        assert_eq!(executor.router().current_route(), "/login");
2009
2010        executor.execute::<()>(Command::Navigate {
2011            route: "/dashboard".to_string(),
2012        });
2013        assert_eq!(executor.router().current_route(), "/dashboard");
2014
2015        // Check history
2016        let history = executor.router().history();
2017        assert_eq!(history, vec!["/", "/login", "/dashboard"]);
2018    }
2019
2020    fn serialized_state_handler(data: Option<Vec<u8>>) -> Option<i32> {
2021        data.and_then(|d| {
2022            let json = String::from_utf8(d).ok()?;
2023            // Simple extraction for test
2024            let count_str = json.split(':').nth(1)?;
2025            count_str.trim_end_matches('}').parse().ok()
2026        })
2027    }
2028
2029    #[test]
2030    fn test_load_state_with_serialized_data() {
2031        let executor = default_executor();
2032
2033        // Simulate saved state (serialized counter)
2034        let saved_data = br#"{"count":42}"#;
2035        executor.storage().save("counter_state", saved_data);
2036
2037        let result = executor.execute(Command::LoadState {
2038            key: "counter_state".to_string(),
2039            on_load: serialized_state_handler,
2040        });
2041
2042        match result {
2043            ExecutionResult::Message(Some(count)) => assert_eq!(count, 42),
2044            _ => panic!("Expected Message with Some(42)"),
2045        }
2046    }
2047
2048    // =========================================================================
2049    // FocusManager Tests
2050    // =========================================================================
2051
2052    #[test]
2053    fn test_focus_manager_new() {
2054        let fm = FocusManager::new();
2055        assert!(fm.focused().is_none());
2056        assert!(!fm.is_trapped());
2057    }
2058
2059    #[test]
2060    fn test_focus_manager_set_ring() {
2061        let mut fm = FocusManager::new();
2062        fm.set_focus_ring(vec![1, 2, 3]);
2063        assert!(fm.is_focusable(1));
2064        assert!(fm.is_focusable(2));
2065        assert!(!fm.is_focusable(4));
2066    }
2067
2068    #[test]
2069    fn test_focus_manager_focus() {
2070        let mut fm = FocusManager::new();
2071        fm.set_focus_ring(vec![1, 2, 3]);
2072
2073        assert!(fm.focus(2));
2074        assert_eq!(fm.focused(), Some(2));
2075
2076        // Can't focus non-focusable widget
2077        assert!(!fm.focus(99));
2078        assert_eq!(fm.focused(), Some(2));
2079    }
2080
2081    #[test]
2082    fn test_focus_manager_blur() {
2083        let mut fm = FocusManager::new();
2084        fm.set_focus_ring(vec![1, 2, 3]);
2085        fm.focus(1);
2086        assert!(fm.focused().is_some());
2087
2088        fm.blur();
2089        assert!(fm.focused().is_none());
2090    }
2091
2092    #[test]
2093    fn test_focus_manager_move_forward() {
2094        let mut fm = FocusManager::new();
2095        fm.set_focus_ring(vec![1, 2, 3]);
2096
2097        // No focus, should focus first
2098        let next = fm.move_focus(FocusDirection::Forward);
2099        assert_eq!(next, Some(1));
2100
2101        // Move forward
2102        let next = fm.move_focus(FocusDirection::Forward);
2103        assert_eq!(next, Some(2));
2104
2105        let next = fm.move_focus(FocusDirection::Forward);
2106        assert_eq!(next, Some(3));
2107
2108        // Wrap around
2109        let next = fm.move_focus(FocusDirection::Forward);
2110        assert_eq!(next, Some(1));
2111    }
2112
2113    #[test]
2114    fn test_focus_manager_move_backward() {
2115        let mut fm = FocusManager::new();
2116        fm.set_focus_ring(vec![1, 2, 3]);
2117
2118        // No focus, should focus last
2119        let next = fm.move_focus(FocusDirection::Backward);
2120        assert_eq!(next, Some(3));
2121
2122        // Move backward
2123        let next = fm.move_focus(FocusDirection::Backward);
2124        assert_eq!(next, Some(2));
2125
2126        let next = fm.move_focus(FocusDirection::Backward);
2127        assert_eq!(next, Some(1));
2128
2129        // Wrap around
2130        let next = fm.move_focus(FocusDirection::Backward);
2131        assert_eq!(next, Some(3));
2132    }
2133
2134    #[test]
2135    fn test_focus_manager_empty_ring() {
2136        let mut fm = FocusManager::new();
2137        let next = fm.move_focus(FocusDirection::Forward);
2138        assert!(next.is_none());
2139    }
2140
2141    #[test]
2142    fn test_focus_manager_trap() {
2143        let mut fm = FocusManager::new();
2144        fm.set_focus_ring(vec![1, 2, 3, 4, 5]);
2145        fm.focus(2);
2146
2147        // Push trap (like opening a modal)
2148        fm.push_trap(vec![10, 11, 12]);
2149        assert!(fm.is_trapped());
2150        assert_eq!(fm.focused(), Some(10)); // Auto-focuses first in trap
2151
2152        // Can only focus within trap
2153        assert!(fm.is_focusable(10));
2154        assert!(!fm.is_focusable(1));
2155
2156        // Navigate within trap
2157        fm.move_focus(FocusDirection::Forward);
2158        assert_eq!(fm.focused(), Some(11));
2159    }
2160
2161    #[test]
2162    fn test_focus_manager_pop_trap() {
2163        let mut fm = FocusManager::new();
2164        fm.set_focus_ring(vec![1, 2, 3]);
2165        fm.focus(2);
2166
2167        fm.push_trap(vec![10, 11]);
2168        assert_eq!(fm.focused(), Some(10));
2169
2170        // Pop trap should restore previous focus
2171        let trap = fm.pop_trap();
2172        assert!(trap.is_some());
2173        assert!(!fm.is_trapped());
2174        assert_eq!(fm.focused(), Some(2)); // Restored
2175    }
2176
2177    #[test]
2178    fn test_focus_manager_nested_traps() {
2179        let mut fm = FocusManager::new();
2180        fm.set_focus_ring(vec![1, 2, 3]);
2181        fm.focus(1);
2182
2183        // First trap
2184        fm.push_trap(vec![10, 11]);
2185        assert_eq!(fm.focused(), Some(10));
2186
2187        // Nested trap
2188        fm.push_trap(vec![20, 21]);
2189        assert_eq!(fm.focused(), Some(20));
2190
2191        // Pop inner trap
2192        fm.pop_trap();
2193        assert_eq!(fm.focused(), Some(10));
2194
2195        // Pop outer trap
2196        fm.pop_trap();
2197        assert_eq!(fm.focused(), Some(1));
2198    }
2199
2200    #[test]
2201    fn test_focus_direction_variants() {
2202        let mut fm = FocusManager::new();
2203        fm.set_focus_ring(vec![1, 2, 3]);
2204        fm.focus(2);
2205
2206        // Down/Right act like Forward
2207        fm.move_focus(FocusDirection::Down);
2208        assert_eq!(fm.focused(), Some(3));
2209
2210        fm.focus(2);
2211        fm.move_focus(FocusDirection::Right);
2212        assert_eq!(fm.focused(), Some(3));
2213
2214        // Up/Left act like Backward
2215        fm.focus(2);
2216        fm.move_focus(FocusDirection::Up);
2217        assert_eq!(fm.focused(), Some(1));
2218
2219        fm.focus(2);
2220        fm.move_focus(FocusDirection::Left);
2221        assert_eq!(fm.focused(), Some(1));
2222    }
2223
2224    // =========================================================================
2225    // EasingFunction Tests
2226    // =========================================================================
2227
2228    #[test]
2229    fn test_easing_linear() {
2230        assert_eq!(EasingFunction::Linear.apply(0.0), 0.0);
2231        assert_eq!(EasingFunction::Linear.apply(0.5), 0.5);
2232        assert_eq!(EasingFunction::Linear.apply(1.0), 1.0);
2233    }
2234
2235    #[test]
2236    fn test_easing_clamps_input() {
2237        assert_eq!(EasingFunction::Linear.apply(-0.5), 0.0);
2238        assert_eq!(EasingFunction::Linear.apply(1.5), 1.0);
2239    }
2240
2241    #[test]
2242    fn test_easing_quad() {
2243        // EaseInQuad starts slow
2244        assert!(EasingFunction::EaseInQuad.apply(0.5) < 0.5);
2245        // EaseOutQuad ends slow
2246        assert!(EasingFunction::EaseOutQuad.apply(0.5) > 0.5);
2247        // Boundaries
2248        assert_eq!(EasingFunction::EaseInQuad.apply(0.0), 0.0);
2249        assert_eq!(EasingFunction::EaseInQuad.apply(1.0), 1.0);
2250    }
2251
2252    #[test]
2253    fn test_easing_cubic() {
2254        assert!(EasingFunction::EaseInCubic.apply(0.5) < 0.5);
2255        assert!(EasingFunction::EaseOutCubic.apply(0.5) > 0.5);
2256        assert_eq!(EasingFunction::EaseInCubic.apply(0.0), 0.0);
2257        assert_eq!(EasingFunction::EaseOutCubic.apply(1.0), 1.0);
2258    }
2259
2260    #[test]
2261    fn test_easing_in_out_quad() {
2262        // First half accelerates
2263        let first_quarter = EasingFunction::EaseInOutQuad.apply(0.25);
2264        assert!(first_quarter < 0.25);
2265        // Second half decelerates
2266        let third_quarter = EasingFunction::EaseInOutQuad.apply(0.75);
2267        assert!(third_quarter > 0.75);
2268    }
2269
2270    #[test]
2271    fn test_easing_elastic() {
2272        assert_eq!(EasingFunction::EaseOutElastic.apply(0.0), 0.0);
2273        assert_eq!(EasingFunction::EaseOutElastic.apply(1.0), 1.0);
2274        // Elastic overshoots then settles
2275        let mid = EasingFunction::EaseOutElastic.apply(0.5);
2276        assert!(mid > 0.9); // Already past target due to elastic
2277    }
2278
2279    #[test]
2280    fn test_easing_bounce() {
2281        assert_eq!(EasingFunction::EaseOutBounce.apply(0.0), 0.0);
2282        assert!((EasingFunction::EaseOutBounce.apply(1.0) - 1.0).abs() < 0.001);
2283    }
2284
2285    #[test]
2286    fn test_easing_default() {
2287        assert_eq!(EasingFunction::default(), EasingFunction::Linear);
2288    }
2289
2290    // =========================================================================
2291    // Tween Tests
2292    // =========================================================================
2293
2294    #[test]
2295    fn test_tween_new() {
2296        let tween = Tween::new(0.0_f32, 100.0, 1000);
2297        assert_eq!(tween.from, 0.0);
2298        assert_eq!(tween.to, 100.0);
2299        assert_eq!(tween.duration_ms, 1000);
2300        assert_eq!(tween.easing, EasingFunction::Linear);
2301    }
2302
2303    #[test]
2304    fn test_tween_progress() {
2305        let mut tween = Tween::new(0.0_f32, 100.0, 1000);
2306        assert_eq!(tween.progress(), 0.0);
2307
2308        tween.advance(500);
2309        assert_eq!(tween.progress(), 0.5);
2310
2311        tween.advance(500);
2312        assert_eq!(tween.progress(), 1.0);
2313    }
2314
2315    #[test]
2316    fn test_tween_value() {
2317        let mut tween = Tween::new(0.0_f32, 100.0, 1000);
2318        assert_eq!(tween.value(), 0.0);
2319
2320        tween.advance(500);
2321        assert_eq!(tween.value(), 50.0);
2322
2323        tween.advance(500);
2324        assert_eq!(tween.value(), 100.0);
2325    }
2326
2327    #[test]
2328    fn test_tween_f64_value() {
2329        let mut tween = Tween::new(0.0_f64, 100.0, 1000);
2330        tween.advance(250);
2331        assert!((tween.value() - 25.0).abs() < 0.001);
2332    }
2333
2334    #[test]
2335    fn test_tween_with_easing() {
2336        let mut tween = Tween::new(0.0_f32, 100.0, 1000).with_easing(EasingFunction::EaseInQuad);
2337        tween.advance(500);
2338        // With ease-in, value at 50% time should be less than 50
2339        assert!(tween.value() < 50.0);
2340    }
2341
2342    #[test]
2343    fn test_tween_is_complete() {
2344        let mut tween = Tween::new(0.0_f32, 100.0, 1000);
2345        assert!(!tween.is_complete());
2346
2347        tween.advance(999);
2348        assert!(!tween.is_complete());
2349
2350        tween.advance(1);
2351        assert!(tween.is_complete());
2352    }
2353
2354    #[test]
2355    fn test_tween_reset() {
2356        let mut tween = Tween::new(0.0_f32, 100.0, 1000);
2357        tween.advance(500);
2358        assert_eq!(tween.progress(), 0.5);
2359
2360        tween.reset();
2361        assert_eq!(tween.progress(), 0.0);
2362    }
2363
2364    #[test]
2365    fn test_tween_zero_duration() {
2366        let tween = Tween::new(0.0_f32, 100.0, 0);
2367        assert_eq!(tween.progress(), 1.0);
2368        assert!(tween.is_complete());
2369    }
2370
2371    #[test]
2372    fn test_tween_advance_overflow() {
2373        let mut tween = Tween::new(0.0_f32, 100.0, 1000);
2374        tween.advance(2000); // Way past duration
2375        assert_eq!(tween.progress(), 1.0);
2376        assert!(tween.is_complete());
2377    }
2378
2379    // =========================================================================
2380    // AnimationInstance Tests
2381    // =========================================================================
2382
2383    #[test]
2384    fn test_animation_instance_new() {
2385        let anim = AnimationInstance::new(1, 0.0, 100.0, 1000);
2386        assert_eq!(anim.id, 1);
2387        assert_eq!(anim.state, AnimationState::Idle);
2388        assert_eq!(anim.loop_count, 1);
2389    }
2390
2391    #[test]
2392    fn test_animation_instance_start() {
2393        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000);
2394        anim.start();
2395        assert_eq!(anim.state, AnimationState::Running);
2396    }
2397
2398    #[test]
2399    fn test_animation_instance_pause_resume() {
2400        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000);
2401        anim.start();
2402        anim.advance(500);
2403
2404        anim.pause();
2405        assert_eq!(anim.state, AnimationState::Paused);
2406
2407        // Advance while paused does nothing
2408        anim.advance(500);
2409        assert!(!anim.tween.is_complete());
2410
2411        anim.resume();
2412        assert_eq!(anim.state, AnimationState::Running);
2413    }
2414
2415    #[test]
2416    fn test_animation_instance_stop() {
2417        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000);
2418        anim.start();
2419        anim.advance(500);
2420
2421        anim.stop();
2422        assert_eq!(anim.state, AnimationState::Idle);
2423        assert_eq!(anim.tween.progress(), 0.0);
2424    }
2425
2426    #[test]
2427    fn test_animation_instance_complete() {
2428        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000);
2429        anim.start();
2430        anim.advance(1000);
2431
2432        assert_eq!(anim.state, AnimationState::Completed);
2433    }
2434
2435    #[test]
2436    fn test_animation_instance_loop() {
2437        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000).with_loop_count(3);
2438        anim.start();
2439
2440        // First loop
2441        anim.advance(1000);
2442        assert_eq!(anim.state, AnimationState::Running);
2443        assert_eq!(anim.current_loop, 1);
2444
2445        // Second loop
2446        anim.advance(1000);
2447        assert_eq!(anim.current_loop, 2);
2448
2449        // Third loop completes
2450        anim.advance(1000);
2451        assert_eq!(anim.state, AnimationState::Completed);
2452    }
2453
2454    #[test]
2455    fn test_animation_instance_infinite_loop() {
2456        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000).with_loop_count(0);
2457        anim.start();
2458
2459        for _ in 0..100 {
2460            anim.advance(1000);
2461            assert_eq!(anim.state, AnimationState::Running);
2462        }
2463    }
2464
2465    #[test]
2466    fn test_animation_instance_alternate() {
2467        let mut anim = AnimationInstance::new(1, 0.0, 100.0, 1000)
2468            .with_loop_count(2)
2469            .with_alternate(true);
2470        anim.start();
2471
2472        // Forward
2473        anim.advance(500);
2474        assert!((anim.value() - 50.0).abs() < 0.001);
2475
2476        // Complete first loop
2477        anim.advance(500);
2478        assert_eq!(anim.current_loop, 1);
2479
2480        // Now going backward
2481        anim.advance(500);
2482        // Value should be going from 100 back toward 0
2483        assert!((anim.value() - 50.0).abs() < 0.001);
2484    }
2485
2486    #[test]
2487    fn test_animation_instance_with_easing() {
2488        let anim =
2489            AnimationInstance::new(1, 0.0, 100.0, 1000).with_easing(EasingFunction::EaseInQuad);
2490        assert_eq!(anim.tween.easing, EasingFunction::EaseInQuad);
2491    }
2492
2493    // =========================================================================
2494    // Animator Tests
2495    // =========================================================================
2496
2497    #[test]
2498    fn test_animator_new() {
2499        let animator = Animator::new();
2500        assert!(animator.is_empty());
2501        assert_eq!(animator.len(), 0);
2502    }
2503
2504    #[test]
2505    fn test_animator_create() {
2506        let mut animator = Animator::new();
2507        let id = animator.create(0.0, 100.0, 1000);
2508
2509        assert_eq!(animator.len(), 1);
2510        assert!(animator.get(id).is_some());
2511    }
2512
2513    #[test]
2514    fn test_animator_unique_ids() {
2515        let mut animator = Animator::new();
2516        let id1 = animator.create(0.0, 100.0, 1000);
2517        let id2 = animator.create(0.0, 100.0, 1000);
2518        let id3 = animator.create(0.0, 100.0, 1000);
2519
2520        assert_ne!(id1, id2);
2521        assert_ne!(id2, id3);
2522    }
2523
2524    #[test]
2525    fn test_animator_start_and_value() {
2526        let mut animator = Animator::new();
2527        let id = animator.create(0.0, 100.0, 1000);
2528
2529        animator.start(id);
2530        assert_eq!(animator.value(id), Some(0.0));
2531
2532        animator.advance(500);
2533        assert_eq!(animator.value(id), Some(50.0));
2534    }
2535
2536    #[test]
2537    fn test_animator_pause_resume() {
2538        let mut animator = Animator::new();
2539        let id = animator.create(0.0, 100.0, 1000);
2540        animator.start(id);
2541        animator.advance(250);
2542
2543        animator.pause(id);
2544        animator.advance(500); // Should not advance
2545
2546        animator.resume(id);
2547        animator.advance(250);
2548
2549        // Total should be 500ms (250 + 250, not counting paused time)
2550        assert_eq!(animator.value(id), Some(50.0));
2551    }
2552
2553    #[test]
2554    fn test_animator_stop() {
2555        let mut animator = Animator::new();
2556        let id = animator.create(0.0, 100.0, 1000);
2557        animator.start(id);
2558        animator.advance(500);
2559
2560        animator.stop(id);
2561        assert_eq!(animator.value(id), Some(0.0));
2562    }
2563
2564    #[test]
2565    fn test_animator_remove() {
2566        let mut animator = Animator::new();
2567        let id = animator.create(0.0, 100.0, 1000);
2568        assert_eq!(animator.len(), 1);
2569
2570        animator.remove(id);
2571        assert!(animator.is_empty());
2572        assert!(animator.get(id).is_none());
2573    }
2574
2575    #[test]
2576    fn test_animator_has_running() {
2577        let mut animator = Animator::new();
2578        let id = animator.create(0.0, 100.0, 1000);
2579
2580        assert!(!animator.has_running());
2581
2582        animator.start(id);
2583        assert!(animator.has_running());
2584
2585        animator.advance(1000);
2586        assert!(!animator.has_running()); // Completed
2587    }
2588
2589    #[test]
2590    fn test_animator_cleanup_completed() {
2591        let mut animator = Animator::new();
2592        let id1 = animator.create(0.0, 100.0, 500);
2593        let id2 = animator.create(0.0, 100.0, 1000);
2594
2595        animator.start(id1);
2596        animator.start(id2);
2597        animator.advance(500);
2598
2599        assert_eq!(animator.len(), 2);
2600
2601        animator.cleanup_completed();
2602        assert_eq!(animator.len(), 1);
2603        assert!(animator.get(id1).is_none());
2604        assert!(animator.get(id2).is_some());
2605    }
2606
2607    #[test]
2608    fn test_animator_multiple_animations() {
2609        let mut animator = Animator::new();
2610        let id1 = animator.create(0.0, 100.0, 1000);
2611        let id2 = animator.create(100.0, 0.0, 1000);
2612
2613        animator.start(id1);
2614        animator.start(id2);
2615        animator.advance(500);
2616
2617        assert_eq!(animator.value(id1), Some(50.0));
2618        assert_eq!(animator.value(id2), Some(50.0)); // Going from 100 to 0
2619    }
2620
2621    // =========================================================================
2622    // Timer Tests
2623    // =========================================================================
2624
2625    #[test]
2626    fn test_timer_new() {
2627        let timer = Timer::new(1000);
2628        assert_eq!(timer.interval_ms, 1000);
2629        assert!(!timer.is_running());
2630        assert_eq!(timer.tick_count(), 0);
2631    }
2632
2633    #[test]
2634    fn test_timer_start_stop() {
2635        let mut timer = Timer::new(1000);
2636        timer.start();
2637        assert!(timer.is_running());
2638
2639        timer.stop();
2640        assert!(!timer.is_running());
2641    }
2642
2643    #[test]
2644    fn test_timer_advance() {
2645        let mut timer = Timer::new(1000);
2646        timer.start();
2647
2648        // Advance less than interval
2649        let ticks = timer.advance(500);
2650        assert_eq!(ticks, 0);
2651        assert_eq!(timer.tick_count(), 0);
2652
2653        // Complete first interval
2654        let ticks = timer.advance(500);
2655        assert_eq!(ticks, 1);
2656        assert_eq!(timer.tick_count(), 1);
2657    }
2658
2659    #[test]
2660    fn test_timer_multiple_ticks() {
2661        let mut timer = Timer::new(100);
2662        timer.start();
2663
2664        let ticks = timer.advance(350);
2665        assert_eq!(ticks, 3);
2666        assert_eq!(timer.tick_count(), 3);
2667
2668        // Remainder should carry over
2669        assert!((timer.progress() - 0.5).abs() < 0.01);
2670    }
2671
2672    #[test]
2673    fn test_timer_max_ticks() {
2674        let mut timer = Timer::new(100).with_max_ticks(3);
2675        timer.start();
2676
2677        timer.advance(200); // 2 ticks
2678        assert!(timer.is_running());
2679
2680        timer.advance(200); // Would be 2 more, but limited to 1
2681        assert!(!timer.is_running());
2682        assert_eq!(timer.tick_count(), 3);
2683    }
2684
2685    #[test]
2686    fn test_timer_reset() {
2687        let mut timer = Timer::new(100);
2688        timer.start();
2689        timer.advance(250);
2690        assert_eq!(timer.tick_count(), 2);
2691
2692        timer.reset();
2693        assert_eq!(timer.tick_count(), 0);
2694        assert_eq!(timer.progress(), 0.0);
2695    }
2696
2697    #[test]
2698    fn test_timer_progress() {
2699        let mut timer = Timer::new(100);
2700        timer.start();
2701        timer.advance(50);
2702        assert!((timer.progress() - 0.5).abs() < 0.01);
2703    }
2704
2705    #[test]
2706    fn test_timer_zero_interval() {
2707        let mut timer = Timer::new(0);
2708        timer.start();
2709        let ticks = timer.advance(1000);
2710        assert_eq!(ticks, 0); // Zero interval means no ticks
2711    }
2712
2713    #[test]
2714    fn test_timer_not_running() {
2715        let mut timer = Timer::new(100);
2716        let ticks = timer.advance(1000);
2717        assert_eq!(ticks, 0); // Not started, no ticks
2718    }
2719
2720    // =========================================================================
2721    // FrameTimer Tests
2722    // =========================================================================
2723
2724    #[test]
2725    fn test_frame_timer_new() {
2726        let ft = FrameTimer::new(60);
2727        assert_eq!(ft.total_frames(), 0);
2728        assert!((ft.target_frame_ms() - 16.667).abs() < 0.01);
2729    }
2730
2731    #[test]
2732    fn test_frame_timer_default() {
2733        let ft = FrameTimer::default();
2734        assert_eq!(ft.total_frames(), 0);
2735    }
2736
2737    #[test]
2738    fn test_frame_timer_frame() {
2739        let mut ft = FrameTimer::new(60);
2740        ft.frame(0);
2741        assert_eq!(ft.total_frames(), 1);
2742
2743        ft.frame(16667); // 16.667ms later
2744        assert_eq!(ft.total_frames(), 2);
2745    }
2746
2747    #[test]
2748    fn test_frame_timer_fps() {
2749        let mut ft = FrameTimer::new(60);
2750
2751        // Simulate 60fps
2752        for i in 0..60 {
2753            ft.frame(i * 16667);
2754        }
2755
2756        let fps = ft.fps();
2757        assert!(fps > 55.0 && fps < 65.0);
2758    }
2759
2760    #[test]
2761    fn test_frame_timer_is_on_target() {
2762        let mut ft = FrameTimer::new(60);
2763
2764        // Perfect 60fps
2765        for i in 0..10 {
2766            ft.frame(i * 16667);
2767        }
2768        assert!(ft.is_on_target());
2769    }
2770
2771    #[test]
2772    fn test_frame_timer_slow_frames() {
2773        let mut ft = FrameTimer::new(60);
2774
2775        // Simulate 30fps (33ms frames)
2776        for i in 0..10 {
2777            ft.frame(i * 33333);
2778        }
2779
2780        let fps = ft.fps();
2781        assert!(fps < 35.0);
2782        assert!(!ft.is_on_target());
2783    }
2784
2785    #[test]
2786    fn test_frame_timer_zero_fps() {
2787        let ft = FrameTimer::new(0);
2788        assert!((ft.target_frame_ms() - 16.667).abs() < 0.01); // Falls back to 60fps
2789    }
2790
2791    // =========================================================================
2792    // TransitionConfig Tests
2793    // =========================================================================
2794
2795    #[test]
2796    fn test_transition_config_default() {
2797        let config = TransitionConfig::default();
2798        assert_eq!(config.duration_ms, 300);
2799        assert_eq!(config.delay_ms, 0);
2800        assert_eq!(config.easing, EasingFunction::EaseInOutCubic);
2801    }
2802
2803    #[test]
2804    fn test_transition_config_new() {
2805        let config = TransitionConfig::new(500);
2806        assert_eq!(config.duration_ms, 500);
2807    }
2808
2809    #[test]
2810    fn test_transition_config_presets() {
2811        assert_eq!(TransitionConfig::quick().duration_ms, 150);
2812        assert_eq!(TransitionConfig::normal().duration_ms, 300);
2813        assert_eq!(TransitionConfig::slow().duration_ms, 500);
2814    }
2815
2816    #[test]
2817    fn test_transition_config_builder() {
2818        let config = TransitionConfig::new(200)
2819            .with_easing(EasingFunction::EaseOutBounce)
2820            .with_delay(50);
2821
2822        assert_eq!(config.duration_ms, 200);
2823        assert_eq!(config.easing, EasingFunction::EaseOutBounce);
2824        assert_eq!(config.delay_ms, 50);
2825    }
2826
2827    // =========================================================================
2828    // AnimatedProperty Tests
2829    // =========================================================================
2830
2831    #[test]
2832    fn test_animated_property_new() {
2833        let prop = AnimatedProperty::new(0.0_f32);
2834        assert_eq!(*prop.get(), 0.0);
2835        assert_eq!(*prop.target(), 0.0);
2836        assert!(!prop.is_animating());
2837    }
2838
2839    #[test]
2840    fn test_animated_property_default() {
2841        let prop: AnimatedProperty<f32> = AnimatedProperty::default();
2842        assert_eq!(*prop.get(), 0.0);
2843    }
2844
2845    #[test]
2846    fn test_animated_property_set() {
2847        let mut prop = AnimatedProperty::new(0.0_f32);
2848        prop.set(100.0);
2849
2850        assert!(prop.is_animating());
2851        assert_eq!(*prop.target(), 100.0);
2852        assert_eq!(*prop.get(), 0.0); // Not advanced yet
2853    }
2854
2855    #[test]
2856    fn test_animated_property_advance() {
2857        let mut prop = AnimatedProperty::with_config(0.0_f32, TransitionConfig::new(1000));
2858        prop.set(100.0);
2859
2860        prop.advance(500);
2861        let value = *prop.get();
2862        assert!(value > 0.0 && value < 100.0);
2863        assert!(prop.is_animating());
2864
2865        prop.advance(500);
2866        assert_eq!(*prop.get(), 100.0);
2867        assert!(!prop.is_animating());
2868    }
2869
2870    #[test]
2871    fn test_animated_property_set_immediate() {
2872        let mut prop = AnimatedProperty::new(0.0_f32);
2873        prop.set_immediate(50.0);
2874
2875        assert_eq!(*prop.get(), 50.0);
2876        assert_eq!(*prop.target(), 50.0);
2877        assert!(!prop.is_animating());
2878    }
2879
2880    #[test]
2881    fn test_animated_property_with_delay() {
2882        let mut prop =
2883            AnimatedProperty::with_config(0.0_f32, TransitionConfig::new(1000).with_delay(500));
2884        prop.set(100.0);
2885
2886        // During delay, progress should be 0
2887        prop.advance(250);
2888        assert_eq!(prop.progress(), 0.0);
2889        assert_eq!(*prop.get(), 0.0);
2890
2891        // After delay, animation begins
2892        prop.advance(500); // Now 750ms total, 250ms into animation
2893        assert!(prop.progress() > 0.0);
2894        assert!(*prop.get() > 0.0);
2895    }
2896
2897    #[test]
2898    fn test_animated_property_f64() {
2899        let mut prop = AnimatedProperty::with_config(0.0_f64, TransitionConfig::new(1000));
2900        prop.set(100.0);
2901
2902        prop.advance(500);
2903        let value = *prop.get();
2904        assert!(value > 0.0 && value < 100.0);
2905    }
2906
2907    #[test]
2908    fn test_animated_property_color() {
2909        let mut prop =
2910            AnimatedProperty::with_config(crate::Color::BLACK, TransitionConfig::new(1000));
2911        prop.set(crate::Color::WHITE);
2912
2913        prop.advance(500);
2914        let color = *prop.get();
2915        assert!(color.r > 0.0 && color.r < 1.0);
2916    }
2917
2918    #[test]
2919    fn test_animated_property_point() {
2920        let mut prop =
2921            AnimatedProperty::with_config(crate::Point::new(0.0, 0.0), TransitionConfig::new(1000));
2922        prop.set(crate::Point::new(100.0, 200.0));
2923
2924        prop.advance(500);
2925        let point = *prop.get();
2926        assert!(point.x > 0.0 && point.x < 100.0);
2927        assert!(point.y > 0.0 && point.y < 200.0);
2928    }
2929
2930    #[test]
2931    fn test_animated_property_size() {
2932        let mut prop =
2933            AnimatedProperty::with_config(crate::Size::new(0.0, 0.0), TransitionConfig::new(1000));
2934        prop.set(crate::Size::new(100.0, 100.0));
2935
2936        prop.advance(500);
2937        let size = *prop.get();
2938        assert!(size.width > 0.0 && size.width < 100.0);
2939    }
2940
2941    #[test]
2942    fn test_animated_property_progress() {
2943        let mut prop = AnimatedProperty::with_config(0.0_f32, TransitionConfig::new(1000));
2944        prop.set(100.0);
2945
2946        assert_eq!(prop.progress(), 0.0);
2947
2948        prop.advance(250);
2949        assert!((prop.progress() - 0.25).abs() < 0.001);
2950
2951        prop.advance(750);
2952        assert!((prop.progress() - 1.0).abs() < 0.001);
2953    }
2954
2955    #[test]
2956    fn test_animated_property_interrupt() {
2957        let mut prop = AnimatedProperty::with_config(0.0_f32, TransitionConfig::new(1000));
2958        prop.set(100.0);
2959
2960        prop.advance(500);
2961        let mid_value = *prop.get();
2962        assert!(mid_value > 0.0);
2963
2964        // Interrupt with new target
2965        prop.set(0.0);
2966        assert!(prop.is_animating());
2967        assert_eq!(*prop.target(), 0.0);
2968        // Current value becomes new start
2969    }
2970
2971    // =========================================================================
2972    // SpringConfig Tests
2973    // =========================================================================
2974
2975    #[test]
2976    fn test_spring_config_default() {
2977        let config = SpringConfig::default();
2978        assert_eq!(config.stiffness, 100.0);
2979        assert_eq!(config.damping, 10.0);
2980        assert_eq!(config.mass, 1.0);
2981    }
2982
2983    #[test]
2984    fn test_spring_config_presets() {
2985        let gentle = SpringConfig::gentle();
2986        assert_eq!(gentle.damping, 15.0);
2987
2988        let bouncy = SpringConfig::bouncy();
2989        assert_eq!(bouncy.stiffness, 300.0);
2990
2991        let stiff = SpringConfig::stiff();
2992        assert_eq!(stiff.stiffness, 500.0);
2993        assert_eq!(stiff.damping, 30.0);
2994    }
2995
2996    // =========================================================================
2997    // SpringAnimation Tests
2998    // =========================================================================
2999
3000    #[test]
3001    fn test_spring_animation_new() {
3002        let spring = SpringAnimation::new(0.0);
3003        assert_eq!(spring.position(), 0.0);
3004        assert_eq!(spring.velocity(), 0.0);
3005        assert_eq!(spring.target(), 0.0);
3006    }
3007
3008    #[test]
3009    fn test_spring_animation_set_target() {
3010        let mut spring = SpringAnimation::new(0.0);
3011        spring.set_target(100.0);
3012
3013        assert_eq!(spring.target(), 100.0);
3014        assert_eq!(spring.position(), 0.0);
3015    }
3016
3017    #[test]
3018    fn test_spring_animation_advance() {
3019        let mut spring = SpringAnimation::new(0.0);
3020        spring.set_target(100.0);
3021
3022        // Advance several steps
3023        for _ in 0..100 {
3024            spring.advance_ms(16);
3025        }
3026
3027        // Should be close to target
3028        assert!(spring.position() > 50.0);
3029    }
3030
3031    #[test]
3032    fn test_spring_animation_at_rest() {
3033        let mut spring = SpringAnimation::new(0.0);
3034        assert!(spring.is_at_rest()); // At initial position
3035
3036        spring.set_target(100.0);
3037        assert!(!spring.is_at_rest());
3038
3039        // Advance until at rest
3040        for _ in 0..500 {
3041            spring.advance_ms(16);
3042            if spring.is_at_rest() {
3043                break;
3044            }
3045        }
3046
3047        assert!(spring.is_at_rest());
3048        assert!((spring.position() - 100.0).abs() < 0.01);
3049    }
3050
3051    #[test]
3052    fn test_spring_animation_set_immediate() {
3053        let mut spring = SpringAnimation::new(0.0);
3054        spring.set_target(100.0);
3055        spring.advance_ms(100);
3056
3057        spring.set_immediate(50.0);
3058
3059        assert_eq!(spring.position(), 50.0);
3060        assert_eq!(spring.target(), 50.0);
3061        assert_eq!(spring.velocity(), 0.0);
3062        assert!(spring.is_at_rest());
3063    }
3064
3065    #[test]
3066    fn test_spring_animation_bouncy() {
3067        let mut spring = SpringAnimation::with_config(0.0, SpringConfig::bouncy());
3068        spring.set_target(100.0);
3069
3070        let mut max_position = 0.0_f32;
3071
3072        // With bouncy spring, position should overshoot
3073        for _ in 0..200 {
3074            spring.advance_ms(16);
3075            max_position = max_position.max(spring.position());
3076        }
3077
3078        // Should overshoot past target
3079        assert!(max_position > 100.0);
3080    }
3081
3082    #[test]
3083    fn test_spring_animation_overdamped() {
3084        // High damping = critically damped or overdamped
3085        let config = SpringConfig::new(100.0, 50.0, 1.0);
3086        let mut spring = SpringAnimation::with_config(0.0, config);
3087        spring.set_target(100.0);
3088
3089        let mut max_position = 0.0_f32;
3090
3091        for _ in 0..500 {
3092            spring.advance_ms(16);
3093            max_position = max_position.max(spring.position());
3094        }
3095
3096        // Should NOT overshoot with high damping
3097        assert!(max_position <= 100.1); // Allow small numerical error
3098    }
3099
3100    // =========================================================================
3101    // DataRefreshManager Tests
3102    // =========================================================================
3103
3104    #[test]
3105    fn test_data_refresh_manager_new() {
3106        let manager = DataRefreshManager::new();
3107        assert!(manager.tasks().is_empty());
3108    }
3109
3110    #[test]
3111    fn test_data_refresh_manager_default() {
3112        let manager = DataRefreshManager::default();
3113        assert!(manager.tasks().is_empty());
3114    }
3115
3116    #[test]
3117    fn test_data_refresh_manager_register() {
3118        let mut manager = DataRefreshManager::new();
3119        manager.register("source1", 1000);
3120
3121        assert_eq!(manager.tasks().len(), 1);
3122        assert_eq!(manager.tasks()[0].key, "source1");
3123        assert_eq!(manager.tasks()[0].interval_ms, 1000);
3124        assert!(manager.tasks()[0].active);
3125    }
3126
3127    #[test]
3128    fn test_data_refresh_manager_register_multiple() {
3129        let mut manager = DataRefreshManager::new();
3130        manager.register("source1", 1000);
3131        manager.register("source2", 2000);
3132        manager.register("source3", 500);
3133
3134        assert_eq!(manager.tasks().len(), 3);
3135    }
3136
3137    #[test]
3138    fn test_data_refresh_manager_register_duplicate_updates() {
3139        let mut manager = DataRefreshManager::new();
3140        manager.register("source1", 1000);
3141        manager.register("source1", 2000);
3142
3143        assert_eq!(manager.tasks().len(), 1);
3144        assert_eq!(manager.tasks()[0].interval_ms, 2000);
3145    }
3146
3147    #[test]
3148    fn test_data_refresh_manager_unregister() {
3149        let mut manager = DataRefreshManager::new();
3150        manager.register("source1", 1000);
3151        manager.register("source2", 2000);
3152
3153        manager.unregister("source1");
3154
3155        assert_eq!(manager.tasks().len(), 1);
3156        assert_eq!(manager.tasks()[0].key, "source2");
3157    }
3158
3159    #[test]
3160    fn test_data_refresh_manager_unregister_nonexistent() {
3161        let mut manager = DataRefreshManager::new();
3162        manager.register("source1", 1000);
3163
3164        manager.unregister("nonexistent");
3165
3166        assert_eq!(manager.tasks().len(), 1);
3167    }
3168
3169    #[test]
3170    fn test_data_refresh_manager_pause() {
3171        let mut manager = DataRefreshManager::new();
3172        manager.register("source1", 1000);
3173
3174        manager.pause("source1");
3175
3176        assert!(!manager.tasks()[0].active);
3177    }
3178
3179    #[test]
3180    fn test_data_refresh_manager_pause_nonexistent() {
3181        let mut manager = DataRefreshManager::new();
3182        manager.register("source1", 1000);
3183
3184        // Should not panic
3185        manager.pause("nonexistent");
3186
3187        assert!(manager.tasks()[0].active);
3188    }
3189
3190    #[test]
3191    fn test_data_refresh_manager_resume() {
3192        let mut manager = DataRefreshManager::new();
3193        manager.register("source1", 1000);
3194        manager.pause("source1");
3195
3196        manager.resume("source1");
3197
3198        assert!(manager.tasks()[0].active);
3199    }
3200
3201    #[test]
3202    fn test_data_refresh_manager_resume_nonexistent() {
3203        let mut manager = DataRefreshManager::new();
3204        manager.register("source1", 1000);
3205        manager.pause("source1");
3206
3207        // Should not panic
3208        manager.resume("nonexistent");
3209
3210        assert!(!manager.tasks()[0].active);
3211    }
3212
3213    #[test]
3214    fn test_data_refresh_manager_update_initial() {
3215        let mut manager = DataRefreshManager::new();
3216        manager.register("source1", 1000);
3217
3218        // At time 0, elapsed=0, interval=1000, so no refresh yet
3219        let to_refresh = manager.update(0);
3220        assert!(to_refresh.is_empty());
3221
3222        // After interval elapses, refresh should trigger
3223        let to_refresh = manager.update(1000);
3224        assert_eq!(to_refresh, vec!["source1"]);
3225    }
3226
3227    #[test]
3228    fn test_data_refresh_manager_update_before_interval() {
3229        let mut manager = DataRefreshManager::new();
3230        manager.register("source1", 1000);
3231
3232        // First refresh at 1000ms
3233        manager.update(1000);
3234
3235        // Update before interval elapsed (500ms later)
3236        let to_refresh = manager.update(1500);
3237
3238        assert!(to_refresh.is_empty());
3239    }
3240
3241    #[test]
3242    fn test_data_refresh_manager_update_after_interval() {
3243        let mut manager = DataRefreshManager::new();
3244        manager.register("source1", 1000);
3245
3246        // First refresh at 1000ms
3247        manager.update(1000);
3248
3249        // Update after interval elapsed (1000ms later)
3250        let to_refresh = manager.update(2000);
3251
3252        assert_eq!(to_refresh, vec!["source1"]);
3253    }
3254
3255    #[test]
3256    fn test_data_refresh_manager_update_multiple_sources() {
3257        let mut manager = DataRefreshManager::new();
3258        manager.register("fast", 100);
3259        manager.register("slow", 1000);
3260
3261        // First refresh for both at their respective intervals
3262        let to_refresh = manager.update(100);
3263        assert!(to_refresh.contains(&"fast".to_string()));
3264
3265        let to_refresh = manager.update(1000);
3266        assert!(to_refresh.contains(&"slow".to_string()));
3267
3268        // After 1200ms: fast should refresh (elapsed=200), slow should not (elapsed=200)
3269        let to_refresh = manager.update(1200);
3270        assert_eq!(to_refresh.len(), 1);
3271        assert!(to_refresh.contains(&"fast".to_string()));
3272    }
3273
3274    #[test]
3275    fn test_data_refresh_manager_update_paused_skipped() {
3276        let mut manager = DataRefreshManager::new();
3277        manager.register("source1", 1000);
3278        manager.pause("source1");
3279
3280        let to_refresh = manager.update(2000);
3281
3282        assert!(to_refresh.is_empty());
3283    }
3284
3285    #[test]
3286    fn test_data_refresh_manager_force_refresh() {
3287        let mut manager = DataRefreshManager::new();
3288        manager.register("source1", 1000);
3289
3290        // First update at 1000ms triggers refresh (elapsed >= interval)
3291        manager.update(1000);
3292
3293        // Update to 1500ms - no refresh yet (elapsed=500 < interval=1000)
3294        let to_refresh = manager.update(1500);
3295        assert!(to_refresh.is_empty());
3296
3297        // Force refresh sets last_refresh_ms to 0
3298        let result = manager.force_refresh("source1");
3299        assert!(result);
3300
3301        // Now update should trigger refresh (elapsed=1500 >= interval=1000)
3302        let to_refresh = manager.update(1500);
3303        assert_eq!(to_refresh, vec!["source1"]);
3304    }
3305
3306    #[test]
3307    fn test_data_refresh_manager_force_refresh_nonexistent() {
3308        let mut manager = DataRefreshManager::new();
3309        manager.register("source1", 1000);
3310
3311        let result = manager.force_refresh("nonexistent");
3312
3313        assert!(!result);
3314    }
3315
3316    #[test]
3317    fn test_data_refresh_manager_get_task() {
3318        let mut manager = DataRefreshManager::new();
3319        manager.register("source1", 1000);
3320
3321        let task = manager.get_task("source1");
3322        assert!(task.is_some());
3323        assert_eq!(task.unwrap().key, "source1");
3324
3325        let task = manager.get_task("nonexistent");
3326        assert!(task.is_none());
3327    }
3328
3329    #[test]
3330    fn test_data_refresh_manager_is_due() {
3331        let mut manager = DataRefreshManager::new();
3332        manager.register("source1", 1000);
3333
3334        // At time 0, elapsed=0, interval=1000, not due yet
3335        manager.update(0);
3336        assert!(!manager.is_due("source1"));
3337
3338        // At time 500, still not due
3339        manager.update(500);
3340        assert!(!manager.is_due("source1"));
3341
3342        // At time 999, still not due (elapsed=999 < 1000)
3343        manager.update(999);
3344        assert!(!manager.is_due("source1"));
3345
3346        // At time 1000, should be due (elapsed >= interval)
3347        // But update() triggers and resets, so we need to force check differently
3348        // Use force_refresh to reset and then check is_due
3349        manager.update(1000); // Triggers refresh, sets last_refresh_ms=1000
3350        assert!(!manager.is_due("source1")); // Just refreshed
3351
3352        // Advance time without triggering refresh
3353        manager.update(1500);
3354        assert!(!manager.is_due("source1")); // elapsed=500 < 1000
3355
3356        // Now check at 2000 before update
3357        // We need to manually check - but update also triggers, so this is tricky
3358        // The is_due check uses stored current_time_ms which is 1500
3359    }
3360
3361    #[test]
3362    fn test_data_refresh_manager_is_due_paused() {
3363        let mut manager = DataRefreshManager::new();
3364        manager.register("source1", 1000);
3365        manager.pause("source1");
3366
3367        manager.update(2000);
3368
3369        assert!(!manager.is_due("source1"));
3370    }
3371
3372    #[test]
3373    fn test_data_refresh_manager_is_due_nonexistent() {
3374        let manager = DataRefreshManager::new();
3375        assert!(!manager.is_due("nonexistent"));
3376    }
3377
3378    #[test]
3379    fn test_data_refresh_manager_time_until_refresh() {
3380        let mut manager = DataRefreshManager::new();
3381        manager.register("source1", 1000);
3382
3383        // First update at time 1000 triggers refresh
3384        manager.update(1000);
3385
3386        // At time 1000, just refreshed, 1000ms until next
3387        assert_eq!(manager.time_until_refresh("source1"), Some(1000));
3388
3389        // At time 1500, 500ms until next
3390        manager.update(1500);
3391        // last_refresh_ms is 1000, current_time is 1500, elapsed = 500
3392        // time_until = 1000 - 500 = 500
3393        assert_eq!(manager.time_until_refresh("source1"), Some(500));
3394    }
3395
3396    #[test]
3397    fn test_data_refresh_manager_time_until_refresh_paused() {
3398        let mut manager = DataRefreshManager::new();
3399        manager.register("source1", 1000);
3400        manager.pause("source1");
3401
3402        manager.update(500);
3403
3404        assert_eq!(manager.time_until_refresh("source1"), Some(u64::MAX));
3405    }
3406
3407    #[test]
3408    fn test_data_refresh_manager_time_until_refresh_nonexistent() {
3409        let manager = DataRefreshManager::new();
3410        assert_eq!(manager.time_until_refresh("nonexistent"), None);
3411    }
3412
3413    #[test]
3414    fn test_data_refresh_manager_saturating_arithmetic() {
3415        let mut manager = DataRefreshManager::new();
3416        manager.register("source1", 1000);
3417
3418        // Update with very large time, shouldn't panic
3419        manager.update(u64::MAX - 1);
3420        let to_refresh = manager.update(u64::MAX);
3421
3422        // Should handle overflow gracefully
3423        assert!(to_refresh.is_empty() || to_refresh.len() == 1);
3424    }
3425
3426    #[test]
3427    fn test_data_refresh_manager_reactivate_updates_interval() {
3428        let mut manager = DataRefreshManager::new();
3429        manager.register("source1", 1000);
3430        manager.pause("source1");
3431
3432        // Re-register while paused should reactivate with new interval
3433        manager.register("source1", 500);
3434
3435        assert!(manager.tasks()[0].active);
3436        assert_eq!(manager.tasks()[0].interval_ms, 500);
3437    }
3438
3439    #[test]
3440    fn test_data_refresh_manager_multiple_refresh_cycles() {
3441        let mut manager = DataRefreshManager::new();
3442        manager.register("source1", 100);
3443
3444        let mut refresh_count = 0;
3445
3446        for time in (0..1000).step_by(50) {
3447            let to_refresh = manager.update(time as u64);
3448            refresh_count += to_refresh.len();
3449        }
3450
3451        // With 100ms interval over 1000ms, should refresh ~10 times
3452        // (at 0, 100, 200, 300, 400, 500, 600, 700, 800, 900)
3453        assert!(refresh_count >= 9 && refresh_count <= 11);
3454    }
3455}