using ColaFlow.Shared.Kernel.Common; namespace ColaFlow.Modules.Mcp.Domain.ValueObjects; /// /// Value object representing a preview of changes proposed by an AI agent /// Immutable - once created, cannot be modified /// public sealed class DiffPreview : ValueObject { /// /// The type of operation: CREATE, UPDATE, DELETE /// public string Operation { get; private set; } /// /// The type of entity being changed (e.g., "Epic", "Story", "Task") /// public string EntityType { get; private set; } /// /// The ID of the entity being changed (null for CREATE operations) /// public Guid? EntityId { get; private set; } /// /// Human-readable key for the entity (e.g., "COLA-146") /// public string? EntityKey { get; private set; } /// /// Snapshot of the entity state before the change (null for CREATE) /// Stored as JSON for flexibility /// public string? BeforeData { get; private set; } /// /// Snapshot of the entity state after the change (null for DELETE) /// Stored as JSON for flexibility /// public string? AfterData { get; private set; } /// /// List of individual field changes (for UPDATE operations) /// public IReadOnlyList ChangedFields { get; private set; } /// /// Private constructor for EF Core /// private DiffPreview() { Operation = string.Empty; EntityType = string.Empty; ChangedFields = new List().AsReadOnly(); } /// /// Create a new DiffPreview /// public DiffPreview( string operation, string entityType, Guid? entityId, string? entityKey, string? beforeData, string? afterData, IReadOnlyList? changedFields = null) { // Validation if (string.IsNullOrWhiteSpace(operation)) throw new ArgumentException("Operation cannot be empty", nameof(operation)); if (string.IsNullOrWhiteSpace(entityType)) throw new ArgumentException("EntityType cannot be empty", nameof(entityType)); // Normalize operation to uppercase operation = operation.ToUpperInvariant(); // Validate operation type if (operation != "CREATE" && operation != "UPDATE" && operation != "DELETE") throw new ArgumentException( "Operation must be CREATE, UPDATE, or DELETE", nameof(operation)); // Validate operation-specific requirements if (operation == "UPDATE" && entityId == null) throw new ArgumentException( "UPDATE operation requires EntityId", nameof(entityId)); if (operation == "UPDATE" && (changedFields == null || changedFields.Count == 0)) throw new ArgumentException( "UPDATE operation must have at least one changed field", nameof(changedFields)); if (operation == "DELETE" && entityId == null) throw new ArgumentException( "DELETE operation requires EntityId", nameof(entityId)); if (operation == "CREATE" && string.IsNullOrWhiteSpace(afterData)) throw new ArgumentException( "CREATE operation requires AfterData", nameof(afterData)); Operation = operation; EntityType = entityType; EntityId = entityId; EntityKey = entityKey; BeforeData = beforeData; AfterData = afterData; ChangedFields = changedFields ?? new List().AsReadOnly(); } /// /// Factory method to create a CREATE operation diff /// public static DiffPreview ForCreate( string entityType, string afterData, string? entityKey = null) { return new DiffPreview( operation: "CREATE", entityType: entityType, entityId: null, entityKey: entityKey, beforeData: null, afterData: afterData, changedFields: null); } /// /// Factory method to create an UPDATE operation diff /// public static DiffPreview ForUpdate( string entityType, Guid entityId, string beforeData, string afterData, IReadOnlyList changedFields, string? entityKey = null) { return new DiffPreview( operation: "UPDATE", entityType: entityType, entityId: entityId, entityKey: entityKey, beforeData: beforeData, afterData: afterData, changedFields: changedFields); } /// /// Factory method to create a DELETE operation diff /// public static DiffPreview ForDelete( string entityType, Guid entityId, string beforeData, string? entityKey = null) { return new DiffPreview( operation: "DELETE", entityType: entityType, entityId: entityId, entityKey: entityKey, beforeData: beforeData, afterData: null, changedFields: null); } /// /// Value object equality - compare all atomic values /// protected override IEnumerable GetAtomicValues() { yield return Operation; yield return EntityType; yield return EntityId ?? Guid.Empty; yield return EntityKey ?? string.Empty; yield return BeforeData ?? string.Empty; yield return AfterData ?? string.Empty; foreach (var field in ChangedFields) yield return field; } /// /// Check if this is a CREATE operation /// public bool IsCreate() => Operation == "CREATE"; /// /// Check if this is an UPDATE operation /// public bool IsUpdate() => Operation == "UPDATE"; /// /// Check if this is a DELETE operation /// public bool IsDelete() => Operation == "DELETE"; /// /// Get a human-readable summary of the change /// public string GetSummary() { var identifier = EntityKey ?? EntityId?.ToString() ?? "new entity"; return $"{Operation} {EntityType} ({identifier})"; } /// /// Get the count of changed fields /// public int GetChangedFieldCount() => ChangedFields.Count; }