Skip to main content

presentar_core/
history.rs

1// Undo/Redo Command History - WASM-first command pattern implementation
2//
3// Provides:
4// - Command pattern for undoable operations
5// - History stack with undo/redo navigation
6// - Batch command grouping
7// - Memory-limited history
8// - Checkpoints and save points
9// - History branching support
10
11use std::any::Any;
12use std::collections::HashMap;
13use std::sync::Arc;
14
15/// Unique identifier for a command in history
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct CommandId(u64);
18
19impl CommandId {
20    pub fn as_u64(self) -> u64 {
21        self.0
22    }
23}
24
25/// Unique identifier for a command group
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
27pub struct GroupId(u64);
28
29impl GroupId {
30    pub fn as_u64(self) -> u64 {
31        self.0
32    }
33}
34
35/// Unique identifier for a checkpoint
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub struct CheckpointId(u64);
38
39impl CheckpointId {
40    pub fn as_u64(self) -> u64 {
41        self.0
42    }
43}
44
45/// Result of executing or undoing a command
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum CommandResult {
48    /// Command executed successfully
49    Success,
50    /// Command failed with error message
51    Failed(String),
52    /// Command was cancelled
53    Cancelled,
54    /// Command requires confirmation
55    NeedsConfirmation(String),
56}
57
58impl CommandResult {
59    pub fn is_success(&self) -> bool {
60        matches!(self, Self::Success)
61    }
62
63    pub fn is_failed(&self) -> bool {
64        matches!(self, Self::Failed(_))
65    }
66}
67
68/// Trait for undoable commands
69pub trait Command: Send + Sync {
70    /// Execute the command
71    fn execute(&mut self) -> CommandResult;
72
73    /// Undo the command
74    fn undo(&mut self) -> CommandResult;
75
76    /// Redo the command (default: re-execute)
77    fn redo(&mut self) -> CommandResult {
78        self.execute()
79    }
80
81    /// Get command description
82    fn description(&self) -> &str;
83
84    /// Check if command can be merged with another
85    fn can_merge(&self, _other: &dyn Command) -> bool {
86        false
87    }
88
89    /// Merge with another command (returns merged command)
90    fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
91        None
92    }
93
94    /// Estimate memory usage of this command
95    fn memory_size(&self) -> usize {
96        // Default to a reasonable estimate for trait objects
97        64
98    }
99
100    /// Get command metadata
101    fn metadata(&self) -> Option<&dyn Any> {
102        None
103    }
104}
105
106/// Entry in the history stack
107struct HistoryEntry {
108    #[allow(dead_code)]
109    id: CommandId,
110    command: Box<dyn Command>,
111    group_id: Option<GroupId>,
112    timestamp: u64,
113    #[allow(dead_code)]
114    executed: bool,
115}
116
117/// Configuration for command history
118#[derive(Debug, Clone)]
119pub struct HistoryConfig {
120    /// Maximum number of commands to keep
121    pub max_commands: usize,
122    /// Maximum memory usage in bytes
123    pub max_memory: usize,
124    /// Enable command merging
125    pub enable_merging: bool,
126    /// Automatically group rapid commands
127    pub auto_group_interval_ms: u64,
128}
129
130impl Default for HistoryConfig {
131    fn default() -> Self {
132        Self {
133            max_commands: 1000,
134            max_memory: 10 * 1024 * 1024, // 10 MB
135            enable_merging: true,
136            auto_group_interval_ms: 500,
137        }
138    }
139}
140
141/// Checkpoint in history
142#[derive(Debug, Clone)]
143pub struct Checkpoint {
144    pub id: CheckpointId,
145    pub name: String,
146    pub position: usize,
147    pub timestamp: u64,
148}
149
150/// State change notification
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub enum HistoryEvent {
153    /// Command was executed
154    Executed(CommandId),
155    /// Command was undone
156    Undone(CommandId),
157    /// Command was redone
158    Redone(CommandId),
159    /// History was cleared
160    Cleared,
161    /// Checkpoint was created
162    CheckpointCreated(CheckpointId),
163    /// Restored to checkpoint
164    CheckpointRestored(CheckpointId),
165    /// History limit reached, old commands dropped
166    Trimmed(usize),
167}
168
169/// Callback for history events
170pub type HistoryCallback = Arc<dyn Fn(HistoryEvent) + Send + Sync>;
171
172/// Command history manager
173pub struct CommandHistory {
174    config: HistoryConfig,
175    next_command_id: u64,
176    next_group_id: u64,
177    next_checkpoint_id: u64,
178
179    /// Commands that have been executed (undo stack)
180    undo_stack: Vec<HistoryEntry>,
181    /// Commands that have been undone (redo stack)
182    redo_stack: Vec<HistoryEntry>,
183
184    /// Active command group
185    current_group: Option<GroupId>,
186    /// Saved checkpoints
187    checkpoints: HashMap<CheckpointId, Checkpoint>,
188
189    /// Current timestamp
190    timestamp: u64,
191    /// Last command timestamp for auto-grouping
192    last_command_time: u64,
193
194    /// Event listeners
195    listeners: Vec<HistoryCallback>,
196
197    /// Current memory usage
198    current_memory: usize,
199
200    /// Whether history is recording
201    recording: bool,
202}
203
204impl Default for CommandHistory {
205    fn default() -> Self {
206        Self::new(HistoryConfig::default())
207    }
208}
209
210impl CommandHistory {
211    pub fn new(config: HistoryConfig) -> Self {
212        Self {
213            config,
214            next_command_id: 1,
215            next_group_id: 1,
216            next_checkpoint_id: 1,
217            undo_stack: Vec::new(),
218            redo_stack: Vec::new(),
219            current_group: None,
220            checkpoints: HashMap::new(),
221            timestamp: 0,
222            last_command_time: 0,
223            listeners: Vec::new(),
224            current_memory: 0,
225            recording: true,
226        }
227    }
228
229    /// Execute a command and add to history
230    pub fn execute(&mut self, mut command: Box<dyn Command>) -> CommandResult {
231        if !self.recording {
232            return command.execute();
233        }
234
235        let result = command.execute();
236        if !result.is_success() {
237            return result;
238        }
239
240        // Clear redo stack when new command is executed
241        self.redo_stack.clear();
242
243        let id = CommandId(self.next_command_id);
244        self.next_command_id += 1;
245
246        // Check for auto-grouping
247        let group_id = if self.current_group.is_some() {
248            self.current_group
249        } else if self.config.auto_group_interval_ms > 0
250            && self.timestamp.saturating_sub(self.last_command_time)
251                < self.config.auto_group_interval_ms
252            && !self.undo_stack.is_empty()
253        {
254            // Auto-group with previous command
255            self.undo_stack.last().and_then(|e| e.group_id)
256        } else {
257            None
258        };
259
260        // Check for merging
261        if self.config.enable_merging && !self.undo_stack.is_empty() {
262            let can_merge = self
263                .undo_stack
264                .last()
265                .is_some_and(|last| last.command.can_merge(command.as_ref()));
266
267            if can_merge {
268                if let Some(last_entry) = self.undo_stack.last_mut() {
269                    if let Some(merged) = last_entry.command.merge(command) {
270                        // Update memory tracking
271                        self.current_memory -= last_entry.command.memory_size();
272                        self.current_memory += merged.memory_size();
273                        last_entry.command = merged;
274                        self.emit(&HistoryEvent::Executed(id));
275                        return result;
276                    }
277                }
278                // If merge returned None, we can't use command anymore
279                // This shouldn't happen in practice if can_merge is implemented correctly
280                return result;
281            }
282        }
283
284        let memory = command.memory_size();
285        self.current_memory += memory;
286
287        let entry = HistoryEntry {
288            id,
289            command,
290            group_id,
291            timestamp: self.timestamp,
292            executed: true,
293        };
294
295        self.undo_stack.push(entry);
296        self.last_command_time = self.timestamp;
297
298        // Trim if needed
299        self.trim_if_needed();
300
301        self.emit(&HistoryEvent::Executed(id));
302        result
303    }
304
305    /// Undo the last command
306    pub fn undo(&mut self) -> Option<CommandResult> {
307        let entry = self.undo_stack.pop()?;
308        let id = entry.id;
309        let group_id = entry.group_id;
310
311        let mut command = entry.command;
312        let result = command.undo();
313
314        if result.is_success() {
315            self.current_memory -= command.memory_size();
316
317            let redo_entry = HistoryEntry {
318                id,
319                command,
320                group_id,
321                timestamp: entry.timestamp,
322                executed: false,
323            };
324            self.current_memory += redo_entry.command.memory_size();
325            self.redo_stack.push(redo_entry);
326
327            self.emit(&HistoryEvent::Undone(id));
328
329            // Undo entire group if applicable
330            if let Some(gid) = group_id {
331                while let Some(last) = self.undo_stack.last() {
332                    if last.group_id == Some(gid) {
333                        self.undo();
334                    } else {
335                        break;
336                    }
337                }
338            }
339        }
340
341        Some(result)
342    }
343
344    /// Redo the last undone command
345    pub fn redo(&mut self) -> Option<CommandResult> {
346        let entry = self.redo_stack.pop()?;
347        let id = entry.id;
348        let group_id = entry.group_id;
349
350        let mut command = entry.command;
351        let result = command.redo();
352
353        if result.is_success() {
354            self.current_memory -= command.memory_size();
355
356            let undo_entry = HistoryEntry {
357                id,
358                command,
359                group_id,
360                timestamp: entry.timestamp,
361                executed: true,
362            };
363            self.current_memory += undo_entry.command.memory_size();
364            self.undo_stack.push(undo_entry);
365
366            self.emit(&HistoryEvent::Redone(id));
367
368            // Redo entire group if applicable
369            if let Some(gid) = group_id {
370                while let Some(last) = self.redo_stack.last() {
371                    if last.group_id == Some(gid) {
372                        self.redo();
373                    } else {
374                        break;
375                    }
376                }
377            }
378        }
379
380        Some(result)
381    }
382
383    /// Check if undo is available
384    pub fn can_undo(&self) -> bool {
385        !self.undo_stack.is_empty()
386    }
387
388    /// Check if redo is available
389    pub fn can_redo(&self) -> bool {
390        !self.redo_stack.is_empty()
391    }
392
393    /// Get number of undoable commands
394    pub fn undo_count(&self) -> usize {
395        self.undo_stack.len()
396    }
397
398    /// Get number of redoable commands
399    pub fn redo_count(&self) -> usize {
400        self.redo_stack.len()
401    }
402
403    /// Get description of next undo command
404    pub fn undo_description(&self) -> Option<&str> {
405        self.undo_stack.last().map(|e| e.command.description())
406    }
407
408    /// Get description of next redo command
409    pub fn redo_description(&self) -> Option<&str> {
410        self.redo_stack.last().map(|e| e.command.description())
411    }
412
413    /// Start a command group
414    pub fn begin_group(&mut self) -> GroupId {
415        let id = GroupId(self.next_group_id);
416        self.next_group_id += 1;
417        self.current_group = Some(id);
418        id
419    }
420
421    /// End current command group
422    pub fn end_group(&mut self) {
423        self.current_group = None;
424    }
425
426    /// Execute multiple commands as a group
427    pub fn execute_group<I>(&mut self, commands: I) -> Vec<CommandResult>
428    where
429        I: IntoIterator<Item = Box<dyn Command>>,
430    {
431        let _group = self.begin_group();
432        let results: Vec<_> = commands.into_iter().map(|cmd| self.execute(cmd)).collect();
433        self.end_group();
434        results
435    }
436
437    /// Create a checkpoint at current position
438    pub fn create_checkpoint(&mut self, name: impl Into<String>) -> CheckpointId {
439        let id = CheckpointId(self.next_checkpoint_id);
440        self.next_checkpoint_id += 1;
441
442        let checkpoint = Checkpoint {
443            id,
444            name: name.into(),
445            position: self.undo_stack.len(),
446            timestamp: self.timestamp,
447        };
448
449        self.checkpoints.insert(id, checkpoint);
450        self.emit(&HistoryEvent::CheckpointCreated(id));
451        id
452    }
453
454    /// Restore to a checkpoint
455    pub fn restore_checkpoint(&mut self, id: CheckpointId) -> bool {
456        let checkpoint = match self.checkpoints.get(&id) {
457            Some(c) => c.clone(),
458            None => return false,
459        };
460
461        // Undo until we reach checkpoint position
462        while self.undo_stack.len() > checkpoint.position {
463            if self.undo().is_none() {
464                break;
465            }
466        }
467
468        self.emit(&HistoryEvent::CheckpointRestored(id));
469        true
470    }
471
472    /// Get checkpoint by ID
473    pub fn get_checkpoint(&self, id: CheckpointId) -> Option<&Checkpoint> {
474        self.checkpoints.get(&id)
475    }
476
477    /// List all checkpoints
478    pub fn checkpoints(&self) -> impl Iterator<Item = &Checkpoint> {
479        self.checkpoints.values()
480    }
481
482    /// Clear all history
483    pub fn clear(&mut self) {
484        self.undo_stack.clear();
485        self.redo_stack.clear();
486        self.checkpoints.clear();
487        self.current_memory = 0;
488        self.emit(&HistoryEvent::Cleared);
489    }
490
491    /// Get current memory usage
492    pub fn memory_usage(&self) -> usize {
493        self.current_memory
494    }
495
496    /// Update timestamp (call each frame or periodically)
497    pub fn tick(&mut self, delta_ms: u64) {
498        self.timestamp += delta_ms;
499    }
500
501    /// Pause recording
502    pub fn pause(&mut self) {
503        self.recording = false;
504    }
505
506    /// Resume recording
507    pub fn resume(&mut self) {
508        self.recording = true;
509    }
510
511    /// Check if recording
512    pub fn is_recording(&self) -> bool {
513        self.recording
514    }
515
516    /// Add event listener
517    pub fn on_event(&mut self, callback: HistoryCallback) {
518        self.listeners.push(callback);
519    }
520
521    fn emit(&self, event: &HistoryEvent) {
522        for listener in &self.listeners {
523            listener(event.clone());
524        }
525    }
526
527    fn trim_if_needed(&mut self) {
528        let mut trimmed = 0;
529
530        // Trim by count
531        while self.undo_stack.len() > self.config.max_commands {
532            if let Some(entry) = self.undo_stack.first() {
533                self.current_memory -= entry.command.memory_size();
534            }
535            self.undo_stack.remove(0);
536            trimmed += 1;
537        }
538
539        // Trim by memory
540        while self.current_memory > self.config.max_memory && !self.undo_stack.is_empty() {
541            if let Some(entry) = self.undo_stack.first() {
542                self.current_memory -= entry.command.memory_size();
543            }
544            self.undo_stack.remove(0);
545            trimmed += 1;
546        }
547
548        if trimmed > 0 {
549            // Update checkpoint positions
550            for checkpoint in self.checkpoints.values_mut() {
551                checkpoint.position = checkpoint.position.saturating_sub(trimmed);
552            }
553            self.emit(&HistoryEvent::Trimmed(trimmed));
554        }
555    }
556}
557
558/// Builder for creating composite commands
559pub struct CompositeCommand {
560    description: String,
561    commands: Vec<Box<dyn Command>>,
562    executed: Vec<bool>,
563}
564
565impl CompositeCommand {
566    pub fn new(description: impl Into<String>) -> Self {
567        Self {
568            description: description.into(),
569            commands: Vec::new(),
570            executed: Vec::new(),
571        }
572    }
573
574    pub fn with_command(mut self, command: Box<dyn Command>) -> Self {
575        self.commands.push(command);
576        self
577    }
578
579    pub fn build(self) -> Box<dyn Command> {
580        Box::new(CompositeCommandImpl {
581            description: self.description,
582            commands: self.commands,
583            executed: self.executed,
584        })
585    }
586}
587
588struct CompositeCommandImpl {
589    description: String,
590    commands: Vec<Box<dyn Command>>,
591    executed: Vec<bool>,
592}
593
594impl Command for CompositeCommandImpl {
595    fn execute(&mut self) -> CommandResult {
596        self.executed.clear();
597        for cmd in &mut self.commands {
598            let result = cmd.execute();
599            if !result.is_success() {
600                // Rollback executed commands
601                for (i, was_executed) in self.executed.iter().enumerate().rev() {
602                    if *was_executed {
603                        self.commands[i].undo();
604                    }
605                }
606                return result;
607            }
608            self.executed.push(true);
609        }
610        CommandResult::Success
611    }
612
613    fn undo(&mut self) -> CommandResult {
614        for (i, cmd) in self.commands.iter_mut().enumerate().rev() {
615            if i < self.executed.len() && self.executed[i] {
616                let result = cmd.undo();
617                if !result.is_success() {
618                    return result;
619                }
620            }
621        }
622        self.executed.clear();
623        CommandResult::Success
624    }
625
626    fn description(&self) -> &str {
627        &self.description
628    }
629
630    fn memory_size(&self) -> usize {
631        std::mem::size_of::<Self>() + self.commands.iter().map(|c| c.memory_size()).sum::<usize>()
632    }
633}
634
635/// Simple value change command
636pub struct SetValueCommand<T: Clone + Send + Sync + 'static> {
637    description: String,
638    value: Arc<std::sync::RwLock<T>>,
639    old_value: Option<T>,
640    new_value: T,
641}
642
643impl<T: Clone + Send + Sync + 'static> SetValueCommand<T> {
644    pub fn new(
645        description: impl Into<String>,
646        value: Arc<std::sync::RwLock<T>>,
647        new_value: T,
648    ) -> Self {
649        Self {
650            description: description.into(),
651            value,
652            old_value: None,
653            new_value,
654        }
655    }
656}
657
658impl<T: Clone + Send + Sync + 'static> Command for SetValueCommand<T> {
659    fn execute(&mut self) -> CommandResult {
660        let Ok(mut guard) = self.value.write() else {
661            return CommandResult::Failed("Lock poisoned".into());
662        };
663        self.old_value = Some(guard.clone());
664        *guard = self.new_value.clone();
665        CommandResult::Success
666    }
667
668    fn undo(&mut self) -> CommandResult {
669        let Some(old) = self.old_value.clone() else {
670            return CommandResult::Failed("No old value".into());
671        };
672        let Ok(mut guard) = self.value.write() else {
673            return CommandResult::Failed("Lock poisoned".into());
674        };
675        *guard = old;
676        CommandResult::Success
677    }
678
679    fn description(&self) -> &str {
680        &self.description
681    }
682}
683
684#[cfg(test)]
685#[allow(clippy::unwrap_used, clippy::disallowed_methods)]
686mod tests {
687    use super::*;
688    use std::sync::atomic::{AtomicI32, Ordering};
689
690    // Test command that increments/decrements a counter
691    struct IncrementCommand {
692        counter: Arc<AtomicI32>,
693        amount: i32,
694    }
695
696    impl Command for IncrementCommand {
697        fn execute(&mut self) -> CommandResult {
698            self.counter.fetch_add(self.amount, Ordering::SeqCst);
699            CommandResult::Success
700        }
701
702        fn undo(&mut self) -> CommandResult {
703            self.counter.fetch_sub(self.amount, Ordering::SeqCst);
704            CommandResult::Success
705        }
706
707        fn description(&self) -> &'static str {
708            "Increment counter"
709        }
710    }
711
712    // Test command that can fail
713    struct FailingCommand {
714        should_fail: bool,
715    }
716
717    impl Command for FailingCommand {
718        fn execute(&mut self) -> CommandResult {
719            if self.should_fail {
720                CommandResult::Failed("Intentional failure".into())
721            } else {
722                CommandResult::Success
723            }
724        }
725
726        fn undo(&mut self) -> CommandResult {
727            CommandResult::Success
728        }
729
730        fn description(&self) -> &'static str {
731            "Failing command"
732        }
733    }
734
735    #[test]
736    fn test_basic_execute() {
737        let mut history = CommandHistory::default();
738        let counter = Arc::new(AtomicI32::new(0));
739
740        let cmd = Box::new(IncrementCommand {
741            counter: counter.clone(),
742            amount: 5,
743        });
744
745        let result = history.execute(cmd);
746        assert!(result.is_success());
747        assert_eq!(counter.load(Ordering::SeqCst), 5);
748        assert_eq!(history.undo_count(), 1);
749    }
750
751    #[test]
752    fn test_undo() {
753        let mut history = CommandHistory::default();
754        let counter = Arc::new(AtomicI32::new(0));
755
756        history.execute(Box::new(IncrementCommand {
757            counter: counter.clone(),
758            amount: 10,
759        }));
760        assert_eq!(counter.load(Ordering::SeqCst), 10);
761
762        let result = history.undo();
763        assert!(result.is_some());
764        assert!(result.unwrap().is_success());
765        assert_eq!(counter.load(Ordering::SeqCst), 0);
766    }
767
768    #[test]
769    fn test_redo() {
770        let mut history = CommandHistory::default();
771        let counter = Arc::new(AtomicI32::new(0));
772
773        history.execute(Box::new(IncrementCommand {
774            counter: counter.clone(),
775            amount: 7,
776        }));
777        history.undo();
778        assert_eq!(counter.load(Ordering::SeqCst), 0);
779
780        let result = history.redo();
781        assert!(result.is_some());
782        assert!(result.unwrap().is_success());
783        assert_eq!(counter.load(Ordering::SeqCst), 7);
784    }
785
786    #[test]
787    fn test_multiple_undo_redo() {
788        let mut history = CommandHistory::default();
789        let counter = Arc::new(AtomicI32::new(0));
790
791        history.execute(Box::new(IncrementCommand {
792            counter: counter.clone(),
793            amount: 1,
794        }));
795        history.execute(Box::new(IncrementCommand {
796            counter: counter.clone(),
797            amount: 2,
798        }));
799        history.execute(Box::new(IncrementCommand {
800            counter: counter.clone(),
801            amount: 3,
802        }));
803
804        assert_eq!(counter.load(Ordering::SeqCst), 6);
805        assert_eq!(history.undo_count(), 3);
806
807        history.undo();
808        assert_eq!(counter.load(Ordering::SeqCst), 3);
809
810        history.undo();
811        assert_eq!(counter.load(Ordering::SeqCst), 1);
812
813        history.redo();
814        assert_eq!(counter.load(Ordering::SeqCst), 3);
815    }
816
817    #[test]
818    fn test_can_undo_redo() {
819        let mut history = CommandHistory::default();
820        let counter = Arc::new(AtomicI32::new(0));
821
822        assert!(!history.can_undo());
823        assert!(!history.can_redo());
824
825        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
826
827        assert!(history.can_undo());
828        assert!(!history.can_redo());
829
830        history.undo();
831        assert!(!history.can_undo());
832        assert!(history.can_redo());
833    }
834
835    #[test]
836    fn test_redo_cleared_on_new_execute() {
837        let mut history = CommandHistory::default();
838        let counter = Arc::new(AtomicI32::new(0));
839
840        history.execute(Box::new(IncrementCommand {
841            counter: counter.clone(),
842            amount: 1,
843        }));
844        history.execute(Box::new(IncrementCommand {
845            counter: counter.clone(),
846            amount: 2,
847        }));
848
849        history.undo();
850        assert!(history.can_redo());
851
852        // New command clears redo stack
853        history.execute(Box::new(IncrementCommand { counter, amount: 5 }));
854
855        assert!(!history.can_redo());
856    }
857
858    #[test]
859    fn test_failed_command_not_added() {
860        let mut history = CommandHistory::default();
861
862        let result = history.execute(Box::new(FailingCommand { should_fail: true }));
863        assert!(result.is_failed());
864        assert_eq!(history.undo_count(), 0);
865    }
866
867    #[test]
868    fn test_command_descriptions() {
869        let mut history = CommandHistory::default();
870        let counter = Arc::new(AtomicI32::new(0));
871
872        assert!(history.undo_description().is_none());
873
874        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
875
876        assert_eq!(history.undo_description(), Some("Increment counter"));
877    }
878
879    #[test]
880    fn test_command_groups() {
881        let mut history = CommandHistory::default();
882        let counter = Arc::new(AtomicI32::new(0));
883
884        let _group = history.begin_group();
885        history.execute(Box::new(IncrementCommand {
886            counter: counter.clone(),
887            amount: 1,
888        }));
889        history.execute(Box::new(IncrementCommand {
890            counter: counter.clone(),
891            amount: 2,
892        }));
893        history.execute(Box::new(IncrementCommand {
894            counter: counter.clone(),
895            amount: 3,
896        }));
897        history.end_group();
898
899        assert_eq!(counter.load(Ordering::SeqCst), 6);
900
901        // Undo should undo entire group
902        history.undo();
903        assert_eq!(counter.load(Ordering::SeqCst), 0);
904    }
905
906    #[test]
907    fn test_execute_group() {
908        let mut history = CommandHistory::default();
909        let counter = Arc::new(AtomicI32::new(0));
910
911        let commands: Vec<Box<dyn Command>> = vec![
912            Box::new(IncrementCommand {
913                counter: counter.clone(),
914                amount: 1,
915            }),
916            Box::new(IncrementCommand {
917                counter: counter.clone(),
918                amount: 2,
919            }),
920        ];
921
922        let results = history.execute_group(commands);
923        assert!(results.iter().all(super::CommandResult::is_success));
924        assert_eq!(counter.load(Ordering::SeqCst), 3);
925    }
926
927    #[test]
928    fn test_checkpoints() {
929        let mut history = CommandHistory::default();
930        let counter = Arc::new(AtomicI32::new(0));
931
932        history.execute(Box::new(IncrementCommand {
933            counter: counter.clone(),
934            amount: 5,
935        }));
936
937        let checkpoint = history.create_checkpoint("Initial state");
938
939        history.execute(Box::new(IncrementCommand {
940            counter: counter.clone(),
941            amount: 10,
942        }));
943        history.execute(Box::new(IncrementCommand {
944            counter: counter.clone(),
945            amount: 15,
946        }));
947
948        assert_eq!(counter.load(Ordering::SeqCst), 30);
949
950        // Restore to checkpoint
951        let restored = history.restore_checkpoint(checkpoint);
952        assert!(restored);
953        assert_eq!(counter.load(Ordering::SeqCst), 5);
954    }
955
956    #[test]
957    fn test_get_checkpoint() {
958        let mut history = CommandHistory::default();
959
960        let id = history.create_checkpoint("Test checkpoint");
961        let checkpoint = history.get_checkpoint(id);
962
963        assert!(checkpoint.is_some());
964        assert_eq!(checkpoint.unwrap().name, "Test checkpoint");
965    }
966
967    #[test]
968    fn test_list_checkpoints() {
969        let mut history = CommandHistory::default();
970
971        history.create_checkpoint("First");
972        history.create_checkpoint("Second");
973        history.create_checkpoint("Third");
974
975        assert_eq!(history.checkpoints().count(), 3);
976    }
977
978    #[test]
979    fn test_clear() {
980        let mut history = CommandHistory::default();
981        let counter = Arc::new(AtomicI32::new(0));
982
983        history.execute(Box::new(IncrementCommand {
984            counter: counter.clone(),
985            amount: 1,
986        }));
987        history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
988        history.create_checkpoint("Test");
989
990        history.clear();
991
992        assert!(!history.can_undo());
993        assert!(!history.can_redo());
994        assert_eq!(history.checkpoints().count(), 0);
995    }
996
997    #[test]
998    fn test_max_commands_limit() {
999        let config = HistoryConfig {
1000            max_commands: 3,
1001            ..Default::default()
1002        };
1003        let mut history = CommandHistory::new(config);
1004        let counter = Arc::new(AtomicI32::new(0));
1005
1006        for i in 0..5 {
1007            history.execute(Box::new(IncrementCommand {
1008                counter: counter.clone(),
1009                amount: i + 1,
1010            }));
1011        }
1012
1013        assert_eq!(history.undo_count(), 3);
1014    }
1015
1016    #[test]
1017    fn test_pause_resume_recording() {
1018        let mut history = CommandHistory::default();
1019        let counter = Arc::new(AtomicI32::new(0));
1020
1021        history.execute(Box::new(IncrementCommand {
1022            counter: counter.clone(),
1023            amount: 1,
1024        }));
1025
1026        history.pause();
1027        assert!(!history.is_recording());
1028
1029        history.execute(Box::new(IncrementCommand {
1030            counter: counter.clone(),
1031            amount: 2,
1032        }));
1033
1034        // Command executed but not recorded
1035        assert_eq!(counter.load(Ordering::SeqCst), 3);
1036        assert_eq!(history.undo_count(), 1);
1037
1038        history.resume();
1039        assert!(history.is_recording());
1040
1041        history.execute(Box::new(IncrementCommand { counter, amount: 3 }));
1042        assert_eq!(history.undo_count(), 2);
1043    }
1044
1045    #[test]
1046    fn test_event_callbacks() {
1047        use std::sync::atomic::AtomicUsize;
1048
1049        let mut history = CommandHistory::default();
1050        let counter = Arc::new(AtomicI32::new(0));
1051        let event_count = Arc::new(AtomicUsize::new(0));
1052
1053        let ec = event_count.clone();
1054        history.on_event(Arc::new(move |_event| {
1055            ec.fetch_add(1, Ordering::SeqCst);
1056        }));
1057
1058        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1059        history.undo();
1060        history.redo();
1061
1062        assert_eq!(event_count.load(Ordering::SeqCst), 3);
1063    }
1064
1065    #[test]
1066    fn test_memory_tracking() {
1067        let mut history = CommandHistory::default();
1068        let counter = Arc::new(AtomicI32::new(0));
1069
1070        assert_eq!(history.memory_usage(), 0);
1071
1072        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1073
1074        assert!(history.memory_usage() > 0);
1075    }
1076
1077    #[test]
1078    fn test_tick() {
1079        let mut history = CommandHistory::default();
1080        history.tick(100);
1081        history.tick(50);
1082        // Just verify it doesn't panic
1083    }
1084
1085    #[test]
1086    fn test_command_result_helpers() {
1087        let success = CommandResult::Success;
1088        let failed = CommandResult::Failed("error".into());
1089        let cancelled = CommandResult::Cancelled;
1090
1091        assert!(success.is_success());
1092        assert!(!success.is_failed());
1093
1094        assert!(!failed.is_success());
1095        assert!(failed.is_failed());
1096
1097        assert!(!cancelled.is_success());
1098        assert!(!cancelled.is_failed());
1099    }
1100
1101    #[test]
1102    fn test_command_id() {
1103        let id = CommandId(42);
1104        assert_eq!(id.as_u64(), 42);
1105    }
1106
1107    #[test]
1108    fn test_group_id() {
1109        let id = GroupId(123);
1110        assert_eq!(id.as_u64(), 123);
1111    }
1112
1113    #[test]
1114    fn test_checkpoint_id() {
1115        let id = CheckpointId(456);
1116        assert_eq!(id.as_u64(), 456);
1117    }
1118
1119    #[test]
1120    fn test_composite_command() {
1121        let counter = Arc::new(AtomicI32::new(0));
1122
1123        let composite = CompositeCommand::new("Add 6")
1124            .with_command(Box::new(IncrementCommand {
1125                counter: counter.clone(),
1126                amount: 1,
1127            }))
1128            .with_command(Box::new(IncrementCommand {
1129                counter: counter.clone(),
1130                amount: 2,
1131            }))
1132            .with_command(Box::new(IncrementCommand {
1133                counter: counter.clone(),
1134                amount: 3,
1135            }))
1136            .build();
1137
1138        let mut history = CommandHistory::default();
1139        history.execute(composite);
1140
1141        assert_eq!(counter.load(Ordering::SeqCst), 6);
1142
1143        history.undo();
1144        assert_eq!(counter.load(Ordering::SeqCst), 0);
1145
1146        history.redo();
1147        assert_eq!(counter.load(Ordering::SeqCst), 6);
1148    }
1149
1150    #[test]
1151    fn test_composite_command_rollback_on_failure() {
1152        let counter = Arc::new(AtomicI32::new(0));
1153
1154        let composite = CompositeCommand::new("Should fail")
1155            .with_command(Box::new(IncrementCommand {
1156                counter: counter.clone(),
1157                amount: 5,
1158            }))
1159            .with_command(Box::new(FailingCommand { should_fail: true }))
1160            .build();
1161
1162        let mut history = CommandHistory::default();
1163        let result = history.execute(composite);
1164
1165        assert!(result.is_failed());
1166        // First command should be rolled back
1167        assert_eq!(counter.load(Ordering::SeqCst), 0);
1168    }
1169
1170    #[test]
1171    fn test_set_value_command() {
1172        let value = Arc::new(std::sync::RwLock::new(10));
1173
1174        let cmd = Box::new(SetValueCommand::new("Set to 42", value.clone(), 42));
1175
1176        let mut history = CommandHistory::default();
1177        history.execute(cmd);
1178
1179        assert_eq!(*value.read().unwrap(), 42);
1180
1181        history.undo();
1182        assert_eq!(*value.read().unwrap(), 10);
1183
1184        history.redo();
1185        assert_eq!(*value.read().unwrap(), 42);
1186    }
1187
1188    #[test]
1189    fn test_default_config() {
1190        let config = HistoryConfig::default();
1191        assert_eq!(config.max_commands, 1000);
1192        assert_eq!(config.max_memory, 10 * 1024 * 1024);
1193        assert!(config.enable_merging);
1194        assert_eq!(config.auto_group_interval_ms, 500);
1195    }
1196
1197    #[test]
1198    fn test_undo_on_empty_returns_none() {
1199        let mut history = CommandHistory::default();
1200        assert!(history.undo().is_none());
1201    }
1202
1203    #[test]
1204    fn test_redo_on_empty_returns_none() {
1205        let mut history = CommandHistory::default();
1206        assert!(history.redo().is_none());
1207    }
1208
1209    #[test]
1210    fn test_restore_invalid_checkpoint() {
1211        let mut history = CommandHistory::default();
1212        let invalid_id = CheckpointId(999);
1213        assert!(!history.restore_checkpoint(invalid_id));
1214    }
1215
1216    #[test]
1217    fn test_history_event_variants() {
1218        let events = vec![
1219            HistoryEvent::Executed(CommandId(1)),
1220            HistoryEvent::Undone(CommandId(2)),
1221            HistoryEvent::Redone(CommandId(3)),
1222            HistoryEvent::Cleared,
1223            HistoryEvent::CheckpointCreated(CheckpointId(1)),
1224            HistoryEvent::CheckpointRestored(CheckpointId(1)),
1225            HistoryEvent::Trimmed(5),
1226        ];
1227
1228        // Verify all variants can be created and compared
1229        for event in &events {
1230            assert_eq!(event, event);
1231        }
1232    }
1233
1234    #[test]
1235    fn test_redo_description() {
1236        let mut history = CommandHistory::default();
1237        let counter = Arc::new(AtomicI32::new(0));
1238
1239        assert!(history.redo_description().is_none());
1240
1241        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1242        history.undo();
1243
1244        assert_eq!(history.redo_description(), Some("Increment counter"));
1245    }
1246
1247    #[test]
1248    fn test_redo_count() {
1249        let mut history = CommandHistory::default();
1250        let counter = Arc::new(AtomicI32::new(0));
1251
1252        assert_eq!(history.redo_count(), 0);
1253
1254        history.execute(Box::new(IncrementCommand {
1255            counter: counter.clone(),
1256            amount: 1,
1257        }));
1258        history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
1259
1260        history.undo();
1261        assert_eq!(history.redo_count(), 1);
1262
1263        history.undo();
1264        assert_eq!(history.redo_count(), 2);
1265    }
1266
1267    #[test]
1268    fn test_composite_command_description() {
1269        let counter = Arc::new(AtomicI32::new(0));
1270
1271        let composite = CompositeCommand::new("My Composite")
1272            .with_command(Box::new(IncrementCommand { counter, amount: 1 }))
1273            .build();
1274
1275        assert_eq!(composite.description(), "My Composite");
1276    }
1277
1278    // =========================================================================
1279    // Additional Edge Case Tests
1280    // =========================================================================
1281
1282    #[test]
1283    fn test_command_id_debug() {
1284        let id = CommandId(42);
1285        let debug = format!("{id:?}");
1286        assert!(debug.contains("CommandId"));
1287        assert!(debug.contains("42"));
1288    }
1289
1290    #[test]
1291    fn test_command_id_eq() {
1292        let id1 = CommandId(100);
1293        let id2 = CommandId(100);
1294        let id3 = CommandId(200);
1295        assert_eq!(id1, id2);
1296        assert_ne!(id1, id3);
1297    }
1298
1299    #[test]
1300    fn test_command_id_hash() {
1301        use std::collections::HashSet;
1302        let mut set = HashSet::new();
1303        set.insert(CommandId(1));
1304        set.insert(CommandId(2));
1305        set.insert(CommandId(1)); // Duplicate
1306        assert_eq!(set.len(), 2);
1307    }
1308
1309    #[test]
1310    fn test_group_id_debug() {
1311        let id = GroupId(99);
1312        let debug = format!("{id:?}");
1313        assert!(debug.contains("GroupId"));
1314    }
1315
1316    #[test]
1317    fn test_group_id_hash() {
1318        use std::collections::HashSet;
1319        let mut set = HashSet::new();
1320        set.insert(GroupId(1));
1321        set.insert(GroupId(2));
1322        assert_eq!(set.len(), 2);
1323    }
1324
1325    #[test]
1326    fn test_checkpoint_id_debug() {
1327        let id = CheckpointId(77);
1328        let debug = format!("{id:?}");
1329        assert!(debug.contains("CheckpointId"));
1330    }
1331
1332    #[test]
1333    fn test_checkpoint_id_hash() {
1334        use std::collections::HashSet;
1335        let mut set = HashSet::new();
1336        set.insert(CheckpointId(1));
1337        set.insert(CheckpointId(2));
1338        assert_eq!(set.len(), 2);
1339    }
1340
1341    #[test]
1342    fn test_command_result_debug() {
1343        let result = CommandResult::Success;
1344        let debug = format!("{result:?}");
1345        assert!(debug.contains("Success"));
1346
1347        let failed = CommandResult::Failed("error".to_string());
1348        let debug = format!("{failed:?}");
1349        assert!(debug.contains("Failed"));
1350    }
1351
1352    #[test]
1353    fn test_command_result_clone() {
1354        let result = CommandResult::NeedsConfirmation("confirm?".to_string());
1355        let cloned = result.clone();
1356        assert_eq!(result, cloned);
1357    }
1358
1359    #[test]
1360    fn test_command_result_all_variants() {
1361        let variants = vec![
1362            CommandResult::Success,
1363            CommandResult::Failed("error".to_string()),
1364            CommandResult::Cancelled,
1365            CommandResult::NeedsConfirmation("confirm".to_string()),
1366        ];
1367
1368        for variant in &variants {
1369            let _ = format!("{variant:?}");
1370            let cloned = variant.clone();
1371            assert_eq!(variant, &cloned);
1372        }
1373    }
1374
1375    #[test]
1376    fn test_history_config_debug() {
1377        let config = HistoryConfig::default();
1378        let debug = format!("{config:?}");
1379        assert!(debug.contains("HistoryConfig"));
1380    }
1381
1382    #[test]
1383    fn test_history_config_clone() {
1384        let config = HistoryConfig {
1385            max_commands: 500,
1386            max_memory: 5 * 1024 * 1024,
1387            enable_merging: false,
1388            auto_group_interval_ms: 1000,
1389        };
1390        let cloned = config;
1391        assert_eq!(cloned.max_commands, 500);
1392        assert!(!cloned.enable_merging);
1393    }
1394
1395    #[test]
1396    fn test_checkpoint_debug() {
1397        let checkpoint = Checkpoint {
1398            id: CheckpointId(1),
1399            name: "Test".to_string(),
1400            position: 5,
1401            timestamp: 1000,
1402        };
1403        let debug = format!("{checkpoint:?}");
1404        assert!(debug.contains("Checkpoint"));
1405    }
1406
1407    #[test]
1408    fn test_checkpoint_clone() {
1409        let checkpoint = Checkpoint {
1410            id: CheckpointId(1),
1411            name: "Test".to_string(),
1412            position: 5,
1413            timestamp: 1000,
1414        };
1415        let cloned = checkpoint;
1416        assert_eq!(cloned.name, "Test");
1417        assert_eq!(cloned.position, 5);
1418    }
1419
1420    #[test]
1421    fn test_history_event_debug() {
1422        let event = HistoryEvent::Cleared;
1423        let debug = format!("{event:?}");
1424        assert!(debug.contains("Cleared"));
1425    }
1426
1427    #[test]
1428    fn test_history_event_clone() {
1429        let event = HistoryEvent::Trimmed(10);
1430        let cloned = event.clone();
1431        assert_eq!(event, cloned);
1432    }
1433
1434    #[test]
1435    fn test_memory_limit_trimming() {
1436        let config = HistoryConfig {
1437            max_commands: 1000,
1438            max_memory: 200, // Very small limit
1439            ..Default::default()
1440        };
1441        let mut history = CommandHistory::new(config);
1442        let counter = Arc::new(AtomicI32::new(0));
1443
1444        // Each command takes ~64 bytes by default
1445        for i in 0..10 {
1446            history.execute(Box::new(IncrementCommand {
1447                counter: counter.clone(),
1448                amount: i,
1449            }));
1450        }
1451
1452        // Should have trimmed some commands due to memory limit
1453        assert!(history.undo_count() < 10);
1454    }
1455
1456    #[test]
1457    fn test_default_command_methods() {
1458        let counter = Arc::new(AtomicI32::new(0));
1459        let cmd = IncrementCommand { counter, amount: 1 };
1460
1461        // Test default implementations
1462        assert!(!cmd.can_merge(&cmd));
1463        assert_eq!(cmd.memory_size(), 64);
1464        assert!(cmd.metadata().is_none());
1465    }
1466
1467    #[test]
1468    fn test_composite_command_memory_size() {
1469        let counter = Arc::new(AtomicI32::new(0));
1470
1471        let composite = CompositeCommand::new("Multi")
1472            .with_command(Box::new(IncrementCommand {
1473                counter: counter.clone(),
1474                amount: 1,
1475            }))
1476            .with_command(Box::new(IncrementCommand { counter, amount: 2 }))
1477            .build();
1478
1479        let memory = composite.memory_size();
1480        assert!(memory > 64); // Should include both commands
1481    }
1482
1483    #[test]
1484    fn test_group_redo() {
1485        let mut history = CommandHistory::default();
1486        let counter = Arc::new(AtomicI32::new(0));
1487
1488        let _group = history.begin_group();
1489        history.execute(Box::new(IncrementCommand {
1490            counter: counter.clone(),
1491            amount: 1,
1492        }));
1493        history.execute(Box::new(IncrementCommand {
1494            counter: counter.clone(),
1495            amount: 2,
1496        }));
1497        history.end_group();
1498
1499        assert_eq!(counter.load(Ordering::SeqCst), 3);
1500
1501        history.undo();
1502        assert_eq!(counter.load(Ordering::SeqCst), 0);
1503
1504        history.redo();
1505        assert_eq!(counter.load(Ordering::SeqCst), 3);
1506    }
1507
1508    #[test]
1509    fn test_checkpoint_position_updates_on_trim() {
1510        let config = HistoryConfig {
1511            max_commands: 3,
1512            ..Default::default()
1513        };
1514        let mut history = CommandHistory::new(config);
1515        let counter = Arc::new(AtomicI32::new(0));
1516
1517        history.execute(Box::new(IncrementCommand {
1518            counter: counter.clone(),
1519            amount: 1,
1520        }));
1521
1522        let checkpoint_id = history.create_checkpoint("Early");
1523
1524        // Add more commands to trigger trim
1525        for i in 2..=5 {
1526            history.execute(Box::new(IncrementCommand {
1527                counter: counter.clone(),
1528                amount: i,
1529            }));
1530        }
1531
1532        // Checkpoint position should have been adjusted
1533        let checkpoint = history.get_checkpoint(checkpoint_id);
1534        assert!(checkpoint.is_some());
1535    }
1536
1537    #[test]
1538    fn test_multiple_event_listeners() {
1539        use std::sync::atomic::AtomicUsize;
1540
1541        let mut history = CommandHistory::default();
1542        let counter = Arc::new(AtomicI32::new(0));
1543        let event_count1 = Arc::new(AtomicUsize::new(0));
1544        let event_count2 = Arc::new(AtomicUsize::new(0));
1545
1546        let ec1 = event_count1.clone();
1547        history.on_event(Arc::new(move |_| {
1548            ec1.fetch_add(1, Ordering::SeqCst);
1549        }));
1550
1551        let ec2 = event_count2.clone();
1552        history.on_event(Arc::new(move |_| {
1553            ec2.fetch_add(1, Ordering::SeqCst);
1554        }));
1555
1556        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1557
1558        assert_eq!(event_count1.load(Ordering::SeqCst), 1);
1559        assert_eq!(event_count2.load(Ordering::SeqCst), 1);
1560    }
1561
1562    #[test]
1563    fn test_set_value_command_description() {
1564        let value = Arc::new(std::sync::RwLock::new(0));
1565        let cmd = SetValueCommand::new("Set value", value, 100);
1566        assert_eq!(cmd.description(), "Set value");
1567    }
1568
1569    #[test]
1570    fn test_undo_without_old_value() {
1571        let value = Arc::new(std::sync::RwLock::new(10));
1572        let mut cmd = SetValueCommand::new("Set", value, 42);
1573
1574        // Try undo without execute first
1575        let result = cmd.undo();
1576        assert!(result.is_failed());
1577    }
1578
1579    #[test]
1580    fn test_empty_composite_command() {
1581        let composite = CompositeCommand::new("Empty").build();
1582
1583        let mut history = CommandHistory::default();
1584        let result = history.execute(composite);
1585
1586        assert!(result.is_success());
1587    }
1588
1589    #[test]
1590    fn test_get_nonexistent_checkpoint() {
1591        let history = CommandHistory::default();
1592        let result = history.get_checkpoint(CheckpointId(999));
1593        assert!(result.is_none());
1594    }
1595
1596    #[test]
1597    fn test_history_clear_resets_memory() {
1598        let mut history = CommandHistory::default();
1599        let counter = Arc::new(AtomicI32::new(0));
1600
1601        history.execute(Box::new(IncrementCommand { counter, amount: 1 }));
1602
1603        assert!(history.memory_usage() > 0);
1604
1605        history.clear();
1606        assert_eq!(history.memory_usage(), 0);
1607    }
1608
1609    // Test mergeable command
1610    struct MergeableIncrement {
1611        counter: Arc<AtomicI32>,
1612        total_amount: i32,
1613    }
1614
1615    impl Command for MergeableIncrement {
1616        fn execute(&mut self) -> CommandResult {
1617            self.counter.fetch_add(self.total_amount, Ordering::SeqCst);
1618            CommandResult::Success
1619        }
1620
1621        fn undo(&mut self) -> CommandResult {
1622            self.counter.fetch_sub(self.total_amount, Ordering::SeqCst);
1623            CommandResult::Success
1624        }
1625
1626        fn description(&self) -> &'static str {
1627            "Mergeable increment"
1628        }
1629
1630        fn can_merge(&self, _other: &dyn Command) -> bool {
1631            true
1632        }
1633
1634        fn merge(&mut self, _other: Box<dyn Command>) -> Option<Box<dyn Command>> {
1635            // For testing, just add 10 more
1636            self.total_amount += 10;
1637            None
1638        }
1639    }
1640
1641    #[test]
1642    fn test_command_merging() {
1643        let config = HistoryConfig {
1644            enable_merging: true,
1645            auto_group_interval_ms: 0, // Disable auto-grouping
1646            ..Default::default()
1647        };
1648        let mut history = CommandHistory::new(config);
1649        let counter = Arc::new(AtomicI32::new(0));
1650
1651        history.execute(Box::new(MergeableIncrement {
1652            counter: counter.clone(),
1653            total_amount: 5,
1654        }));
1655
1656        history.execute(Box::new(MergeableIncrement {
1657            counter,
1658            total_amount: 3,
1659        }));
1660
1661        // Second command should be merged, so only 1 command in history
1662        assert_eq!(history.undo_count(), 1);
1663    }
1664
1665    #[test]
1666    fn test_redo_clears_on_new_execute() {
1667        let mut history = CommandHistory::default();
1668        let counter = Arc::new(AtomicI32::new(0));
1669
1670        history.execute(Box::new(IncrementCommand {
1671            counter: counter.clone(),
1672            amount: 1,
1673        }));
1674
1675        history.undo();
1676        assert_eq!(history.redo_count(), 1);
1677
1678        history.execute(Box::new(IncrementCommand { counter, amount: 2 }));
1679
1680        assert_eq!(history.redo_count(), 0);
1681    }
1682}