syntax = "proto3";
package angzarr;
option go_package = "github.com/angzarr/angzarr/proto/angzarr";
option java_package = "dev.angzarr";
option java_multiple_files = true;
option csharp_namespace = "Angzarr";
option ruby_package = "Angzarr";

import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";

message UUID {
  bytes value = 1;
}

message Cover {
  string domain = 2;
  UUID root = 1;
  string correlation_id = 3;  // Workflow correlation - flows through all commands/events
  Edition edition = 4;        // Edition for diverged timelines; empty name = main timeline
}

// Edition identifier with optional explicit divergence points.
//
// Two modes:
// - Implicit (divergences empty): Divergence derived from first edition event's sequence
// - Explicit (divergences populated): Per-domain divergence points for historical branching,
//  saga coordination, or speculative execution
message Edition {
  string name = 1;                              // Edition name, e.g., "v2"; empty = main timeline
  repeated DomainDivergence divergences = 2;   // Optional: explicit per-domain divergence points
}

// Explicit divergence point for a specific domain.
// Used when creating historical branches or coordinating saga writes across domains.
message DomainDivergence {
  string domain = 1;    // Domain name
  uint32 sequence = 2;  // Divergence sequence number
}

// Delete all events for an edition+domain combination.
// Main timeline ('angzarr' or empty edition name) cannot be deleted.
message DeleteEditionEvents {
  string edition = 1;   // Edition name to delete from
  string domain = 2;    // Domain to delete from
}

// Response from edition event deletion.
message EditionEventsDeleted {
  string edition = 1;
  string domain = 2;
  uint32 deleted_count = 3;
  string deleted_at = 4;
}

// Controls synchronous processing behavior
enum SyncMode {
  SYNC_MODE_UNSPECIFIED = 0;  // Async: fire and forget (default)
  SYNC_MODE_SIMPLE = 1;       // Sync projectors only, no saga cascade
  SYNC_MODE_CASCADE = 2;      // Full sync: projectors + saga cascade (expensive)
}

message EventPage {
  oneof sequence {
    uint32 num = 1;
    bool force = 2;
  }
  google.protobuf.Timestamp created_at = 3;
  google.protobuf.Any event = 4;
}

// Snapshot of aggregate state at a given sequence number.
// State must be a protobuf Message to serialize into Any.
message Snapshot {
  uint32 sequence = 2;
  google.protobuf.Any state = 3;
}

message EventBook {
  Cover cover = 1;
  Snapshot snapshot = 2;                    // Snapshot state; sequence computed by framework on persist
  repeated EventPage pages = 3;
  // Field 4 removed: correlation_id moved to Cover
  // Field 5 removed: snapshot_state unified into snapshot field
  uint32 next_sequence = 6;                 // Computed on load, never stored: (last page seq OR snapshot seq if no pages) + 1
}

message SyncEventBook {
  EventBook events = 1;
  SyncMode sync_mode = 2;
}

message Projection {
  Cover cover = 1;
  string projector = 2;
  uint32 sequence = 3;
  google.protobuf.Any projection = 4;
}

// Response from entity - aggregate events + sync projector results
message CommandResponse {
  EventBook events = 1;               // Events from the target aggregate
  repeated Projection projections = 2; // Synchronous projector results
}

// Response from saga - commands for other aggregates
message SagaResponse {
  repeated CommandBook commands = 1;  // Commands to execute on other aggregates
  repeated EventBook events = 2;      // Events to publish directly
}

// Two-phase saga protocol messages
message SagaPrepareRequest {
  EventBook source = 1;  // Source events that triggered the saga
}

message SagaPrepareResponse {
  repeated Cover destinations = 1;  // Destination aggregates the saga needs to read
}

message SagaExecuteRequest {
  EventBook source = 1;             // Source events (same as prepare)
  repeated EventBook destinations = 2;  // Fetched destination state
}

message SagaRetryRequest {
  EventBook source = 1;
  repeated EventBook destinations = 2;
  CommandBook rejected_command = 3;
  string rejection_reason = 4;
  uint32 attempt = 5;
}

// Track saga command origin for compensation flow
message SagaCommandOrigin {
  string saga_name = 1;                 // Name of the saga that issued the command
  Cover triggering_aggregate = 2;       // Domain+root of aggregate that triggered the saga
  uint32 triggering_event_sequence = 3; // Sequence number of the triggering event
}

// Command sent to original aggregate when saga command is rejected
message RevokeEventCommand {
  uint32 triggering_event_sequence = 1; // Which event triggered the failed saga flow
  string saga_name = 2;                  // Saga that issued the rejected command
  string rejection_reason = 3;           // Why the command was rejected
  CommandBook rejected_command = 4;      // The command that was rejected
}

// client logic requests framework to handle revocation
message RevocationResponse {
  bool emit_system_revocation = 1;      // Emit SagaCompensationFailed event
  bool send_to_dead_letter_queue = 2;   // Send to DLQ
  bool escalate = 3;                    // Flag for alerting/human intervention
  bool abort = 4;                       // Stop saga chain, propagate error to caller
  string reason = 5;                    // Context/reason
}

// Wrapper response for BusinessLogic.Handle
message BusinessResponse {
  oneof result {
    EventBook events = 1;               // Business provides compensation events
    RevocationResponse revocation = 2;  // Business requests framework action
  }
}

// System event when compensation fails/requested
message SagaCompensationFailed {
  Cover triggering_aggregate = 1;
  uint32 triggering_event_sequence = 2;
  string saga_name = 3;
  string rejection_reason = 4;
  string compensation_failure_reason = 5;
  CommandBook rejected_command = 6;
  google.protobuf.Timestamp occurred_at = 7;
}

message CommandPage {
  // Expected sequence number for this command's events.
  // Must match the aggregate's current next sequence (i.e., events.len()).
  // For new aggregates, use 0.
  uint32 sequence = 1;
  google.protobuf.Any command = 3;
}

message CommandBook {
  Cover cover = 1;
  repeated CommandPage pages = 2;
  // Field 3 removed: correlation_id moved to Cover
  SagaCommandOrigin saga_origin = 4;  // Tracks origin for compensation flow
  // Field 5 removed: 'fact' was unused
}

message SyncCommandBook {
  CommandBook command = 1;
  SyncMode sync_mode = 2;
}

message ContextualCommand {
  // Passed from aggregate coordinator to aggregate, consists of everything needed to execute/evaluate the command
  EventBook events = 1;
  CommandBook command = 2;
}

message SyncContextualCommand {
  ContextualCommand command = 1;
  SyncMode sync_mode = 2;
}

// Query types
message SequenceRange {
  uint32 lower = 1;
  optional uint32 upper = 2;  // If not set, query to latest
}

message SequenceSet {
  repeated uint32 values = 1;
}

// Temporal query: retrieve aggregate state at a point in history.
// Replays events from sequence 0 (no snapshots) to the specified point.
message TemporalQuery {
  oneof point_in_time {
    google.protobuf.Timestamp as_of_time = 1;     // Events with created_at <= this
    uint32 as_of_sequence = 2;                     // Events with sequence <= this
  }
}

message Query {
  // Cover identifies the aggregate: domain + (root | correlation_id | both)
  // Query by root: Cover { domain, root }
  // Query by correlation: Cover { domain, correlation_id }
  Cover cover = 1;
  // Field 2 removed: now use cover.root
  oneof selection {
    SequenceRange range = 3;
    SequenceSet sequences = 4;
    TemporalQuery temporal = 5;
  }
}

message AggregateRoot {
  string domain = 1;
  UUID root = 2;
}

// Request for dry-run command execution against temporal state.
message DryRunRequest {
  CommandBook command = 1;
  TemporalQuery point_in_time = 2;
}

// Subscription filter for event streaming
message EventStreamFilter {
  string correlation_id = 1;
}

// Domain subscription with event types.
// Types are auto-derived from router registrations, never manually configured.
message Target {
  string domain = 1;
  repeated string types = 2;
}

// Self-description of a runtime component (aggregate, saga, projector, process_manager).
// Topology edges derived from inputs: if A subscribes to domain X, edge X→A exists.
message ComponentDescriptor {
  string name = 1;
  string component_type = 2;
  repeated Target inputs = 3;   // Domains I subscribe to (event types I consume)
}

// Request for GetDescriptor RPC.
message GetDescriptorRequest {}

// ============================================================================
// Meta Domain (_angzarr) - Infrastructure commands/events
// ============================================================================

// Command to register a component in the topology registry.
// Root UUID is derived from descriptor.name for idempotent registration.
message RegisterComponent {
  ComponentDescriptor descriptor = 1;
  string pod_id = 2;  // K8s pod name or hostname for standalone
}

// Event emitted when a component registers or re-registers.
message ComponentRegistered {
  ComponentDescriptor descriptor = 1;
  string pod_id = 2;
  google.protobuf.Timestamp registered_at = 3;
}

