/// A single operation within a write batch.
#[derive(Debug, Clone)]
pub(crate) enum BatchOp {
    Put { key: Vec<u8>, value: Vec<u8> },
    Delete { key: Vec<u8> },
}

/// An atomic batch of write operations.
///
/// Collects put and delete operations that are applied atomically
/// to a storage backend via `StorageEngine::write_batch`.
#[derive(Debug, Clone, Default)]
pub struct WriteBatch {
    pub(crate) ops: Vec<BatchOp>,
}

impl WriteBatch {
    #[must_use]
    pub const fn new() -> Self {
        Self { ops: Vec::new() }
    }

    /// Pre-allocate capacity for `n` operations.
    #[must_use]
    pub fn with_capacity(n: usize) -> Self {
        Self {
            ops: Vec::with_capacity(n),
        }
    }

    /// Add a put operation to the batch.
    pub fn put(&mut self, key: Vec<u8>, value: Vec<u8>) {
        self.ops.push(BatchOp::Put { key, value });
    }

    /// Add a delete operation to the batch.
    pub fn delete(&mut self, key: Vec<u8>) {
        self.ops.push(BatchOp::Delete { key });
    }

    /// Number of operations in the batch.
    #[must_use]
    pub const fn len(&self) -> usize {
        self.ops.len()
    }

    /// Whether the batch is empty.
    #[must_use]
    pub const fn is_empty(&self) -> bool {
        self.ops.is_empty()
    }
}

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

    #[test]
    fn new_batch_is_empty() {
        let batch = WriteBatch::new();
        assert!(batch.is_empty());
        assert_eq!(batch.len(), 0);
    }

    #[test]
    fn put_and_delete() {
        let mut batch = WriteBatch::new();
        batch.put(b"key1".to_vec(), b"val1".to_vec());
        batch.delete(b"key2".to_vec());
        assert_eq!(batch.len(), 2);
        assert!(!batch.is_empty());
    }

    #[test]
    fn with_capacity() {
        let batch = WriteBatch::with_capacity(100);
        assert!(batch.is_empty());
    }

    #[test]
    fn default_is_empty() {
        let batch = WriteBatch::default();
        assert!(batch.is_empty());
    }
}
