WorkflowHistoryApiModels.java

package org.hammer.audio.workflow.editor.http;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.time.Instant;
import java.util.List;
import java.util.Objects;
import org.hammer.audio.workflow.collaboration.CollaborationMode;
import org.hammer.audio.workflow.collaboration.WorkflowHistoryCapabilities;
import org.hammer.audio.workflow.collaboration.WorkflowHistoryDescriptor;
import org.hammer.audio.workflow.collaboration.WorkflowHistoryPage;
import org.hammer.audio.workflow.collaboration.WorkflowRedoPreview;
import org.hammer.audio.workflow.collaboration.WorkflowUndoPreview;
import org.hammer.audio.workflow.editor.http.WorkflowSessionApiModels.ActorRequest;

/** Request and response models for durable semantic workflow history discovery. */
public final class WorkflowHistoryApiModels {

  static final int DEFAULT_HISTORY_LIMIT = 50;

  private WorkflowHistoryApiModels() {
    // Utility class.
  }

  /**
   * Request for one stable newest-first history page.
   *
   * @param actor joined actor requesting history
   * @param beforeRevision optional exclusive semantic-revision cursor
   * @param limit optional page size between 1 and 100
   */
  public record HistoryQueryRequest(
      @Valid @NotNull ActorRequest actor,
      @Positive Long beforeRevision,
      @Min(1) @Max(100) Integer limit) {

    public HistoryQueryRequest {
      Objects.requireNonNull(actor, "actor");
    }

    int resolvedLimit() {
      return limit == null ? DEFAULT_HISTORY_LIMIT : limit;
    }
  }

  /**
   * Request for actor-scoped undo and redo capabilities.
   *
   * @param actor joined actor requesting current capabilities
   */
  public record HistoryCapabilitiesRequest(@Valid @NotNull ActorRequest actor) {
    public HistoryCapabilitiesRequest {
      Objects.requireNonNull(actor, "actor");
    }
  }

  /**
   * Request for a revision-bound redo preview.
   *
   * @param actor joined actor requesting the preview
   * @param targetUndoOperationId accepted actor-owned undo operation to preview
   */
  public record RedoPreviewRequest(
      @Valid @NotNull ActorRequest actor, @NotBlank String targetUndoOperationId) {
    public RedoPreviewRequest {
      Objects.requireNonNull(actor, "actor");
    }
  }

  /**
   * Stable transport representation of one accepted semantic operation.
   *
   * @param operationId stable accepted-operation identifier
   * @param operationType semantic operation type
   * @param actorId actor that authored the operation
   * @param occurredAt operation occurrence timestamp
   * @param revision semantic revision produced by the operation
   * @param sequence durable event sequence assigned to the operation
   * @param commandKind normal, undo or redo command category
   * @param commandId stable command idempotency identity
   * @param targetOperationId undo/redo target, otherwise {@code null}
   * @param affectedObjectIds affected semantic object identifiers
   * @param reconstructible whether the complete semantic body is available
   * @param activeUndoTarget whether the operation is currently an active undo target
   * @param activeRedoTarget whether the operation is currently an active redo target
   */
  public record HistoryEntryResponse(
      String operationId,
      String operationType,
      String actorId,
      Instant occurredAt,
      long revision,
      long sequence,
      WorkflowHistoryDescriptor.CommandKind commandKind,
      String commandId,
      String targetOperationId,
      List<String> affectedObjectIds,
      boolean reconstructible,
      boolean activeUndoTarget,
      boolean activeRedoTarget) {

    public HistoryEntryResponse {
      affectedObjectIds =
          List.copyOf(Objects.requireNonNull(affectedObjectIds, "affectedObjectIds"));
    }

    static HistoryEntryResponse from(WorkflowHistoryDescriptor descriptor) {
      return new HistoryEntryResponse(
          descriptor.operationId(),
          descriptor.operationType(),
          descriptor.actorId(),
          descriptor.occurredAt(),
          descriptor.revision(),
          descriptor.sequence(),
          descriptor.commandKind(),
          descriptor.commandId(),
          descriptor.targetOperationId(),
          descriptor.affectedObjectIds(),
          descriptor.reconstructible(),
          descriptor.activeUndoTarget(),
          descriptor.activeRedoTarget());
    }
  }

  /**
   * Stable newest-first durable history page.
   *
   * @param operations immutable history entries
   * @param nextBeforeRevision exclusive cursor for the next older page, or {@code null}
   * @param currentRevision current session revision at query time
   */
  public record HistoryPageResponse(
      List<HistoryEntryResponse> operations, Long nextBeforeRevision, long currentRevision) {

    public HistoryPageResponse {
      operations = List.copyOf(Objects.requireNonNull(operations, "operations"));
    }

    static HistoryPageResponse from(WorkflowHistoryPage page) {
      return new HistoryPageResponse(
          page.operations().stream().map(HistoryEntryResponse::from).toList(),
          page.nextBeforeRevision(),
          page.currentRevision());
    }
  }

  /**
   * Actor-scoped current undo or redo target.
   *
   * @param operation selected immutable history operation
   * @param status authoritative current availability
   * @param available whether the action is currently executable
   * @param blockingOperations later semantic operations blocking execution
   */
  public record HistoryActionResponse(
      HistoryEntryResponse operation,
      WorkflowHistoryCapabilities.ActionStatus status,
      boolean available,
      List<BlockingOperationResponse> blockingOperations) {

    public HistoryActionResponse {
      Objects.requireNonNull(operation, "operation");
      Objects.requireNonNull(status, "status");
      blockingOperations =
          List.copyOf(Objects.requireNonNull(blockingOperations, "blockingOperations"));
    }

    static HistoryActionResponse from(WorkflowHistoryCapabilities.Action action) {
      if (action == null) {
        return null;
      }
      return new HistoryActionResponse(
          HistoryEntryResponse.from(action.operation()),
          action.status(),
          action.available(),
          action.blockingOperations().stream().map(BlockingOperationResponse::from).toList());
    }
  }

  /**
   * Actor-scoped current history capabilities.
   *
   * @param mode immutable collaboration mode
   * @param revision semantic revision at query time
   * @param personalUndoPermitted whether the mode permits personal undo
   * @param personalUndo current personal-undo target, or {@code null}
   * @param redo current actor-owned redo target, or {@code null}
   * @param sharedUndoPermitted whether explicit shared-target undo is permitted
   */
  public record HistoryCapabilitiesResponse(
      CollaborationMode mode,
      long revision,
      boolean personalUndoPermitted,
      HistoryActionResponse personalUndo,
      HistoryActionResponse redo,
      boolean sharedUndoPermitted) {

    static HistoryCapabilitiesResponse from(WorkflowHistoryCapabilities capabilities) {
      return new HistoryCapabilitiesResponse(
          capabilities.mode(),
          capabilities.revision(),
          capabilities.personalUndoPermitted(),
          HistoryActionResponse.from(capabilities.personalUndo()),
          HistoryActionResponse.from(capabilities.redo()),
          capabilities.sharedUndoPermitted());
    }
  }

  /**
   * Machine-readable later operation blocking undo or redo.
   *
   * @param operationId stable accepted-operation identifier
   * @param actorId actor that authored the blocker
   * @param conflictingObjectIds semantic object identifiers shared with the target
   */
  public record BlockingOperationResponse(
      String operationId, String actorId, List<String> conflictingObjectIds) {

    public BlockingOperationResponse {
      conflictingObjectIds =
          List.copyOf(Objects.requireNonNull(conflictingObjectIds, "conflictingObjectIds"));
    }

    static BlockingOperationResponse from(WorkflowUndoPreview.BlockingOperation blocker) {
      return new BlockingOperationResponse(
          blocker.operationId(), blocker.actorId(), blocker.conflictingObjectIds());
    }
  }

  /**
   * Stable timestamp-aware undo preview response.
   *
   * @param previewId identity bound to revision, target and blockers
   * @param targetOperationId selected accepted operation
   * @param targetActorId actor that authored the target
   * @param operationType semantic operation type
   * @param targetOccurredAt target operation occurrence timestamp
   * @param affectedObjectIds affected semantic object identifiers
   * @param revision revision at which the preview is valid
   * @param safe whether no later operation blocks execution
   * @param blockingOperations later semantic blockers
   */
  public record UndoPreviewResponse(
      String previewId,
      String targetOperationId,
      String targetActorId,
      String operationType,
      Instant targetOccurredAt,
      List<String> affectedObjectIds,
      long revision,
      boolean safe,
      List<BlockingOperationResponse> blockingOperations) {

    public UndoPreviewResponse {
      affectedObjectIds =
          List.copyOf(Objects.requireNonNull(affectedObjectIds, "affectedObjectIds"));
      blockingOperations =
          List.copyOf(Objects.requireNonNull(blockingOperations, "blockingOperations"));
    }

    static UndoPreviewResponse from(WorkflowUndoPreview preview) {
      return new UndoPreviewResponse(
          preview.previewId(),
          preview.targetOperationId(),
          preview.targetActorId(),
          preview.operationType(),
          preview.targetOccurredAt(),
          preview.affectedObjectIds(),
          preview.revision(),
          preview.safe(),
          preview.blockingOperations().stream().map(BlockingOperationResponse::from).toList());
    }
  }

  /**
   * Stable timestamp-aware redo preview response.
   *
   * @param previewId identity bound to revision, target and blockers
   * @param targetUndoOperationId selected accepted undo operation
   * @param targetActorId actor that authored the undo operation
   * @param operationType semantic operation type of the undo operation
   * @param targetOccurredAt undo operation occurrence timestamp
   * @param affectedObjectIds affected semantic object identifiers
   * @param revision revision at which the preview is valid
   * @param safe whether no later operation blocks execution
   * @param blockingOperations later semantic blockers
   */
  public record RedoPreviewResponse(
      String previewId,
      String targetUndoOperationId,
      String targetActorId,
      String operationType,
      Instant targetOccurredAt,
      List<String> affectedObjectIds,
      long revision,
      boolean safe,
      List<BlockingOperationResponse> blockingOperations) {

    public RedoPreviewResponse {
      affectedObjectIds =
          List.copyOf(Objects.requireNonNull(affectedObjectIds, "affectedObjectIds"));
      blockingOperations =
          List.copyOf(Objects.requireNonNull(blockingOperations, "blockingOperations"));
    }

    static RedoPreviewResponse from(WorkflowRedoPreview preview) {
      return new RedoPreviewResponse(
          preview.previewId(),
          preview.targetUndoOperationId(),
          preview.targetActorId(),
          preview.operationType(),
          preview.targetOccurredAt(),
          preview.affectedObjectIds(),
          preview.revision(),
          preview.safe(),
          preview.blockingOperations().stream().map(BlockingOperationResponse::from).toList());
    }
  }
}