using System;
using System.Threading;
using System.Threading.Tasks;
namespace Svrnty.CQRS.Events.Abstractions.Projections;
///
/// Manages the lifecycle and execution of event stream projections.
///
///
///
/// Phase 7 Feature - Projection Engine:
/// The projection engine subscribes to event streams and dispatches events to registered
/// projections. It handles checkpointing, error recovery, and projection rebuilding.
///
///
/// Execution Model:
/// - Projections run continuously in background tasks
/// - Each projection maintains its own checkpoint independently
/// - Failed events are retried with exponential backoff
/// - Projections can be stopped, started, or rebuilt dynamically
///
///
public interface IProjectionEngine
{
///
/// Starts a projection, consuming events from the specified stream.
///
/// The unique name of the projection.
/// The name of the stream to consume from.
/// Cancellation token to stop the projection.
/// A task that completes when the projection stops or fails.
///
/// The projection will start from its last checkpoint (or offset 0 if new).
/// This method runs continuously until cancelled or an unrecoverable error occurs.
///
Task RunAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
///
/// Rebuilds a projection by resetting it and replaying all events.
///
/// The unique name of the projection.
/// The name of the stream to replay.
/// Cancellation token.
/// A task representing the rebuild operation.
///
///
/// This will:
/// 1. Stop the projection if running
/// 2. Call ResetAsync() if projection implements IResettableProjection
/// 3. Reset the checkpoint to offset 0
/// 4. Replay all events from the beginning
/// 5. Resume normal operation
///
///
/// Use this to fix bugs in projection logic or migrate schema changes.
///
///
Task RebuildAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
///
/// Gets the current status of a projection.
///
/// The unique name of the projection.
/// The name of the stream being consumed.
/// Cancellation token.
/// The projection status including checkpoint and health information.
Task GetStatusAsync(
string projectionName,
string streamName,
CancellationToken cancellationToken = default);
}
///
/// Represents the current status of a projection.
///
public sealed record ProjectionStatus
{
///
/// The unique name of the projection.
///
public required string ProjectionName { get; init; }
///
/// The name of the stream being consumed.
///
public required string StreamName { get; init; }
///
/// Whether the projection is currently running.
///
public bool IsRunning { get; init; }
///
/// The current state of the projection.
///
public ProjectionState State { get; init; }
///
/// The last processed offset.
///
public long LastProcessedOffset { get; init; }
///
/// The current stream length (head position).
///
public long StreamLength { get; init; }
///
/// The number of events the projection is behind the stream head.
///
public long Lag => StreamLength - LastProcessedOffset;
///
/// Whether the projection is caught up with the stream.
///
public bool IsCaughtUp => Lag <= 0;
///
/// The timestamp when the checkpoint was last updated.
///
public DateTimeOffset LastUpdated { get; init; }
///
/// Total number of events processed by this projection.
///
public long EventsProcessed { get; init; }
///
/// The last error message if the projection failed.
///
public string? LastError { get; init; }
///
/// When the last error occurred.
///
public DateTimeOffset? LastErrorAt { get; init; }
}
///
/// Represents the execution state of a projection.
///
public enum ProjectionState
{
///
/// Projection has never been started.
///
NotStarted = 0,
///
/// Projection is actively processing events.
///
Running = 1,
///
/// Projection is caught up and waiting for new events.
///
CaughtUp = 2,
///
/// Projection has been manually stopped.
///
Stopped = 3,
///
/// Projection is being rebuilt (reset + replay).
///
Rebuilding = 4,
///
/// Projection failed with an unrecoverable error.
///
Failed = 5
}