namespace w4c_workflows.Services.Messaging;
/// A single entry pulled off a Redis stream / (later) RabbitMQ queue.
public sealed record StreamMessage(string Id, IReadOnlyDictionary Fields);
///
/// Default event stream for handler-mode subscriptions when the YAML
/// trigger does not name an explicit stream.
///
public static class Streams
{
public const string DefaultEvents = "wf:{tenant}:events";
/// Worker → control-plane result events (task.result).
public const string Results = "wf:{tenant}:results";
}
///
/// Job dispatch for task.run messages. The v1 implementation is Redis
/// Streams with consumer groups; a RabbitMQ transport can be swapped in behind
/// this interface without touching the engine.
///
public interface IJobQueue
{
/// Ensures the tenant's jobs consumer group exists.
Task EnsureGroupAsync(string tenantId, CancellationToken ct);
/// Enqueues a job; returns the stream entry id.
Task EnqueueAsync(string tenantId, IReadOnlyDictionary fields, CancellationToken ct);
/// Reads new (undelivered) jobs for a consumer.
Task> ReadGroupAsync(string tenantId, string consumer, int count, CancellationToken ct);
/// Claims jobs pending for longer than (redelivery).
Task> ClaimPendingAsync(string tenantId, string consumer, TimeSpan minIdle, int count, CancellationToken ct);
Task AckAsync(string tenantId, string messageId, CancellationToken ct);
/// Copies a failed job to the DLQ and acks it out of the group.
Task DeadLetterAsync(string tenantId, string messageId, IReadOnlyDictionary fields, string reason, CancellationToken ct);
///
/// Writes a dead-letter entry without a source job to ack. Used by the
/// control-plane saga path: a task that exhausted retries (or a failed
/// compensation) has no live jobs-stream entry left to acknowledge.
///
Task PublishDeadLetterAsync(string tenantId, IReadOnlyDictionary fields, string reason, CancellationToken ct);
}
///
/// Event publish/subscribe (Redis Streams now, RabbitMQ later). A
/// handler-mode workflow subscribes to a named stream via
/// trigger.stream; the trigger engine consumes it through a consumer
/// group and runs the downstream nextId chain per event.
///
public interface IEventBus
{
Task PublishAsync(string tenantId, string stream, IReadOnlyDictionary fields, CancellationToken ct);
Task EnsureGroupAsync(string tenantId, string stream, CancellationToken ct);
Task> ReadGroupAsync(string tenantId, string stream, string consumer, int count, CancellationToken ct);
Task> ClaimPendingAsync(string tenantId, string stream, string consumer, TimeSpan minIdle, int count, CancellationToken ct);
Task AckAsync(string tenantId, string stream, string messageId, CancellationToken ct);
}