eazygit 0.5.1

A fast TUI for Git with staging, conflicts, rebase, and palette-first UX
Documentation
//! State snapshot for debugging and time-travel debugging.
//!
//! Captures state snapshots that can be serialized for debugging
//! and potentially replayed.

use std::time::{Duration, Instant};

/// A snapshot of application state at a point in time.
#[derive(Debug, Clone)]
pub struct Snapshot {
    /// Unique ID for this snapshot
    pub id: u64,
    /// When the snapshot was taken (relative to app start)
    pub timestamp: Duration,
    /// Action that caused this snapshot (if any)
    pub action: Option<String>,
    /// Key state values for debugging
    pub state_summary: StateSummary,
}

/// Summary of key state values (without full serialization).
#[derive(Debug, Clone, Default)]
pub struct StateSummary {
    pub focus: String,
    pub status_count: usize,
    pub commit_count: usize,
    pub branch_count: usize,
    pub feedback: Option<String>,
    pub error: Option<String>,
    pub running: bool,
}

/// Manages state snapshots for debugging.
pub struct SnapshotManager {
    snapshots: Vec<Snapshot>,
    max_snapshots: usize,
    start_time: Instant,
    next_id: u64,
}

impl SnapshotManager {
    /// Create a new snapshot manager.
    pub fn new(max_snapshots: usize) -> Self {
        Self {
            snapshots: Vec::with_capacity(max_snapshots),
            max_snapshots,
            start_time: Instant::now(),
            next_id: 0,
        }
    }
    
    /// Take a snapshot.
    pub fn take_snapshot(&mut self, action: Option<String>, summary: StateSummary) -> u64 {
        let id = self.next_id;
        self.next_id += 1;
        
        let snapshot = Snapshot {
            id,
            timestamp: self.start_time.elapsed(),
            action,
            state_summary: summary,
        };
        
        // Evict oldest if at capacity
        if self.snapshots.len() >= self.max_snapshots {
            self.snapshots.remove(0);
        }
        
        self.snapshots.push(snapshot);
        id
    }
    
    /// Get recent snapshots.
    pub fn recent(&self, count: usize) -> Vec<&Snapshot> {
        self.snapshots.iter().rev().take(count).collect()
    }
    
    /// Get snapshot by ID.
    pub fn get(&self, id: u64) -> Option<&Snapshot> {
        self.snapshots.iter().find(|s| s.id == id)
    }
    
    /// Get snapshot count.
    pub fn count(&self) -> usize {
        self.snapshots.len()
    }
    
    /// Clear all snapshots.
    pub fn clear(&mut self) {
        self.snapshots.clear();
    }
    
    /// Create a summary from AppState.
    pub fn summarize(state: &crate::app::AppState) -> StateSummary {
        StateSummary {
            focus: format!("{:?}", state.focus),
            status_count: state.status_entries.len(),
            commit_count: state.commits.len(),
            branch_count: state.branches.len(),
            feedback: state.feedback_message.clone(),
            error: state.last_status_error.clone(),
            running: state.running,
        }
    }
}

impl Default for SnapshotManager {
    fn default() -> Self {
        Self::new(100) // Keep last 100 snapshots
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    
    #[test]
    fn test_snapshot_creation() {
        let mut manager = SnapshotManager::new(10);
        
        let summary = StateSummary {
            focus: "Status".to_string(),
            status_count: 5,
            ..Default::default()
        };
        
        let id = manager.take_snapshot(Some("Tick".to_string()), summary);
        assert_eq!(id, 0);
        assert_eq!(manager.count(), 1);
    }
    
    #[test]
    fn test_snapshot_eviction() {
        let mut manager = SnapshotManager::new(3);
        
        for i in 0..5 {
            manager.take_snapshot(
                Some(format!("Action {}", i)),
                StateSummary::default(),
            );
        }
        
        // Should only keep last 3
        assert_eq!(manager.count(), 3);
        
        // First snapshot should be action 2 (0 and 1 evicted)
        let recent = manager.recent(10);
        assert_eq!(recent.len(), 3);
    }
}