wlk 0.1.0

File-centric, event-sourced version control system with implicit branching
Documentation
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;

/// Unique identifier for a delta
pub type DeltaId = String;

/// Operations that can be performed on file content
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
pub enum DeltaOperation {
    /// Insert text at a specific byte offset
    Insert { index: usize, value: String },

    /// Delete a range of bytes
    Delete { index: usize, length: usize },

    /// Replace a range of bytes with new content
    Replace {
        index: usize,
        length: usize,
        value: String,
    },

    /// Full snapshot of file content (used for initial state and periodic checkpoints)
    Snapshot { content: String },
}

/// A single delta in the version history
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Delta {
    /// Unique identifier for this delta
    pub id: DeltaId,

    /// Parent delta ID (forms a tree structure)
    pub parent: DeltaId,

    /// When this delta was created
    pub timestamp: DateTime<Utc>,

    /// The operation performed
    pub operation: DeltaOperation,

    /// Optional human-readable annotation
    #[serde(skip_serializing_if = "Option::is_none")]
    pub annotation: Option<String>,

    /// Optional author information (for future collaboration features)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub author: Option<String>,
}

impl Delta {
    /// Create a new delta with a generated ID
    pub fn new(parent: DeltaId, operation: DeltaOperation, annotation: Option<String>) -> Self {
        Delta {
            id: Self::generate_id(),
            parent,
            timestamp: Utc::now(),
            operation,
            annotation,
            author: None,
        }
    }

    /// Create the initial snapshot delta (root of the tree)
    pub fn initial_snapshot(content: String) -> Self {
        Delta {
            id: "d0".to_string(),
            parent: "root".to_string(),
            timestamp: Utc::now(),
            operation: DeltaOperation::Snapshot { content },
            annotation: Some("Initial snapshot".to_string()),
            author: None,
        }
    }

    /// Generate a unique delta ID
    fn generate_id() -> DeltaId {
        format!("d-{}", Uuid::new_v4())
    }

    /// Check if this is a snapshot delta
    pub fn is_snapshot(&self) -> bool {
        matches!(self.operation, DeltaOperation::Snapshot { .. })
    }
}

impl fmt::Display for Delta {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let op_desc = match &self.operation {
            DeltaOperation::Insert { index, value } => {
                format!("Insert {} bytes at offset {}", value.len(), index)
            }
            DeltaOperation::Delete { index, length } => {
                format!("Delete {} bytes at offset {}", length, index)
            }
            DeltaOperation::Replace {
                index,
                length,
                value,
            } => {
                format!(
                    "Replace {} bytes at offset {} with {} bytes",
                    length,
                    index,
                    value.len()
                )
            }
            DeltaOperation::Snapshot { content } => {
                format!("Snapshot ({} bytes)", content.len())
            }
        };

        write!(
            f,
            "[{}] {}: {}{}",
            self.timestamp.format("%Y-%m-%d %H:%M:%S"),
            self.id,
            op_desc,
            self.annotation
                .as_ref()
                .map(|a| format!(" - {}", a))
                .unwrap_or_default()
        )
    }
}

/// Apply a delta operation to content
pub fn apply_operation(content: &str, operation: &DeltaOperation) -> Result<String, String> {
    match operation {
        DeltaOperation::Insert { index, value } => {
            if *index > content.len() {
                return Err(format!(
                    "Insert index {} out of bounds (content length: {})",
                    index,
                    content.len()
                ));
            }
            let mut result = content.to_string();
            result.insert_str(*index, value);
            Ok(result)
        }

        DeltaOperation::Delete { index, length } => {
            if *index + *length > content.len() {
                return Err(format!(
                    "Delete range {}..{} out of bounds (content length: {})",
                    index,
                    index + length,
                    content.len()
                ));
            }
            let mut result = content.to_string();
            result.replace_range(*index..*index + *length, "");
            Ok(result)
        }

        DeltaOperation::Replace {
            index,
            length,
            value,
        } => {
            if *index + *length > content.len() {
                return Err(format!(
                    "Replace range {}..{} out of bounds (content length: {})",
                    index,
                    index + length,
                    content.len()
                ));
            }
            let mut result = content.to_string();
            result.replace_range(*index..*index + *length, value);
            Ok(result)
        }

        DeltaOperation::Snapshot { content } => Ok(content.clone()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_insert_operation() {
        let content = "Hello world";
        let op = DeltaOperation::Insert {
            index: 5,
            value: " beautiful".to_string(),
        };
        let result = apply_operation(content, &op).unwrap();
        assert_eq!(result, "Hello beautiful world");
    }

    #[test]
    fn test_delete_operation() {
        let content = "Hello beautiful world";
        let op = DeltaOperation::Delete {
            index: 5,
            length: 10,
        };
        let result = apply_operation(content, &op).unwrap();
        assert_eq!(result, "Hello world");
    }

    #[test]
    fn test_replace_operation() {
        let content = "Hello world";
        let op = DeltaOperation::Replace {
            index: 6,
            length: 5,
            value: "Rust".to_string(),
        };
        let result = apply_operation(content, &op).unwrap();
        assert_eq!(result, "Hello Rust");
    }

    #[test]
    fn test_snapshot_operation() {
        let content = "old content";
        let new_content = "completely new content";
        let op = DeltaOperation::Snapshot {
            content: new_content.to_string(),
        };
        let result = apply_operation(content, &op).unwrap();
        assert_eq!(result, new_content);
    }

    #[test]
    fn test_out_of_bounds_insert() {
        let content = "Hello";
        let op = DeltaOperation::Insert {
            index: 10,
            value: "!".to_string(),
        };
        assert!(apply_operation(content, &op).is_err());
    }

    #[test]
    fn test_delta_display() {
        let delta = Delta::new(
            "d0".to_string(),
            DeltaOperation::Insert {
                index: 0,
                value: "test".to_string(),
            },
            Some("Test annotation".to_string()),
        );
        let display = format!("{}", delta);
        assert!(display.contains("Insert 4 bytes at offset 0"));
        assert!(display.contains("Test annotation"));
    }
}