syntax = "proto3";

package nominal.procedures.executions.v1;

import "buf/validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
import "nominal/gen/v1/alias.proto";
import "nominal/gen/v1/error.proto";
import "nominal/procedures/v1/procedures.proto";
import "nominal/types/types.proto";
import "nominal/versioning/v1/versioning.proto";

option java_multiple_files = true;
option java_package = "io.nominal.procedures.executions.v1";

///////////////////////////////
// Procedure Execution Nodes //
///////////////////////////////

// A procedure execution node is either a section or a step.
message ProcedureExecutionNode {
  oneof node {
    option (buf.validate.oneof) = {required: true};
    // A section node is a visual grouping of other sections and steps.
    // Execution sections are static and do not have execution state.
    ProcedureExecutionSectionNode section = 1;
    // Execution steps are the interactive steps in a procedure.
    ProcedureExecutionStepNode step = 2;
  }
}

// A section node is a visual grouping of other sections and steps.
message ProcedureExecutionSectionNode {
  // Unique identifier of this execution node.
  string id = 1 [(buf.validate.field).string.min_len = 1];
  // Unique identifier of the template node this execution node is based on.
  string template_node_id = 2 [(buf.validate.field).string.min_len = 1];
  // Title of this section. Uses the template section's title and cannot be changed in execution.
  string title = 3 [(buf.validate.field).string.min_len = 1];
  // Description of this section. Uses the template section's description and cannot be changed in execution.
  optional string description = 4;
  // If set, the commit ID of the procedure template this section is based on.
  // Otherwise, the section is based on the template commit specified at the top level of the execution.
  optional string template_commit_id = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
}

// Execution steps are the interactive steps in a procedure.
message ProcedureExecutionStepNode {
  // Unique identifier of this execution node.
  string id = 1 [(buf.validate.field).string.min_len = 1];
  // Unique identifier of the template node this execution node is based on.
  string template_node_id = 2 [(buf.validate.field).string.min_len = 1];
  // Execution steps become "outdated" once any upstream steps are re-run
  // An outdated execution step should not be further modified.
  bool is_outdated = 3;
  // The current status of the step along with metadata about the status transitions.
  ExecutionStepState state = 4;
  // The current value for the content of the step.
  StepContentValue value = 5;
  // Whether the step has auto-proceed enabled.
  nominal.procedures.v1.AutoProceedConfig auto_proceed_config = 6;
  // Completion_condition_status is the status of the completion condition for the step
  // The backend manages this property and will update it while it monitors the
  // completion_condition if set.
  // This property will be unset if no completion_condition is set on the step or
  // if the execution step's contents has not been submitted yet.
  optional SuccessConditionStatus success_condition_status = 7;
  // Completion_action_statuses are the status of the completion actions for the step
  // The backend manages this property and will update it after performing
  // completion_actions.
  repeated CompletionActionStatus completion_action_statuses = 8;
  // Outputs are the values of the fields within this step, set by the backend after the step has reached
  // the SUCCEEDED state.
  // The backend will know how to introspect the node and determine these values, for example looking for
  // form fields with ids in a FormStep.
  // These outputs can be consumed by other steps later in the procedure by referencing `{output_id}:{field_id}`
  map<string, FieldOutput> outputs = 10;
  // If set, the commit ID of the procedure template this step is based on.
  // Otherwise, the step is based on the template commit specified at the top level of the execution.
  optional string template_commit_id = 11 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
}

///////////////////////////////////
// Execution Step Configurations //
///////////////////////////////////

//////////// State machine

// All execution steps start in the NOT_STARTED state.
// In this state, the step's values can be modified by the user.
message ExecutionStepNotStarted {}

// An execution step is in the SKIPPED state if it was skipped by a user.
// An execution step cannot be skipped if it is in a terminal state (SUCCEEDED or ERRORED).
// This is a terminal state, the step's values cannot be modified by the user.
message ExecutionStepSkipped {
  google.protobuf.Timestamp skipped_at = 1;
  string skipped_by = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  optional string skip_reason = 3;
  // Optional because a step can be skipped from the NOT_STARTED state before these are set
  optional google.protobuf.Timestamp started_at = 4;
  optional string started_by = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // Optional because a step can be skipped from the IN_PROGRESS state before these are set
  optional google.protobuf.Timestamp submitted_at = 6;
  optional string submitted_by = 7 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
}

// An execution step is in the IN_PROGRESS state once it has been started from NOT_STARTED,
// either manually or automatically.
// In this state, the step's values can be modified by the user.
message ExecutionStepInProgress {
  google.protobuf.Timestamp started_at = 1;
  string started_by = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
}

// An execution step is in the SUBMITTED state once it has been submitted from the IN_PROGRESS state,
// either manually or automatically.
// In this state, the step's values are considered submitted and cannot be modified by the user.
// In this state, the backend will monitor the completion condition (if any) and execute the completion
// actions (if any) once the condition is satisfied.
message ExecutionStepSubmitted {
  google.protobuf.Timestamp started_at = 1;
  string started_by = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp submitted_at = 3;
  string submitted_by = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
}

// An execution step reaches the SUCCEEDED state if all conditions were met and all actions successfully ran.
// This is a terminal state, the step's values cannot be modified by the user.
message ExecutionStepSucceeded {
  google.protobuf.Timestamp started_at = 1;
  string started_by = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp submitted_at = 3;
  string submitted_by = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp succeeded_at = 5;
  string succeeded_by = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
}

// An execution step reaches the ERRORED state if something went wrong while trying to complete the step.
// This could be due to a badly configured procedure, an error/timeout in the completion condition,
// or an error/timeout in one of the completion actions.
// This is a terminal state, the step's values cannot be modified by the user.
message ExecutionStepErrored {
  // Optinal because a step can be skipped -> errored before being started
  optional google.protobuf.Timestamp started_at = 1;
  optional string started_by = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // Optional because a step can be errored before being submitted
  optional google.protobuf.Timestamp submitted_at = 3;
  optional string submitted_by = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp errored_at = 5;
  string errored_by = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  string error = 7;
  // Optional because a step could be skipped but then error while running completion actions
  optional google.protobuf.Timestamp skipped_at = 8;
  optional string skipped_by = 9 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  optional string skip_reason = 10;
  // Optional because a step can succeed but then error while running completion actions
  optional google.protobuf.Timestamp succeeded_at = 11;
  optional string succeeded_by = 12 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
}

// The read-side state for an execution step.
// Here is the state machine:
// All steps start in the NOT_STARTED state.
// From NOT_STARTED, a step can transition to IN_PROGRESS or SKIPPED.
// - Transitioning to IN_PROGRESS can be done manually by a user or automatically if auto-start is enabled.
//   This transition can only happen if the step's dependencies are in a completed state (SUCCEEDED or SKIPPED)
// From IN_PROGRESS, a step can transition to SUBMITTED or SKIPPED.
// - Transitioning to SUBMITTED can be done manually by a user or automatically if auto-proceed is enabled.
//   This transition can only happen if the step's content has its required values set.
// - Transitioning to SKIPPED can be done manually by a user.
// From SUBMITTED, a step can transition to SUCCEEDED, ERRORED, or SKIPPED.
// - Transitioning to SUCCEEDED or ERRORED is done by the backend after monitoring the completion
//   condition (if any) and executing the completion actions (if any).
// - Transitioning to SKIPPED can be done manually by a user.
message ExecutionStepState {
  oneof state {
    option (buf.validate.oneof) = {required: true};
    ExecutionStepNotStarted not_started = 1;
    ExecutionStepInProgress in_progress = 2;
    ExecutionStepSubmitted submitted = 3;
    // Terminal states below
    ExecutionStepSkipped skipped = 4;
    ExecutionStepSucceeded succeeded = 5;
    ExecutionStepErrored errored = 6;
  }
}

message StepInProgressRequest {}
message StepSubmittedRequest {}
message StepSkippedRequest {
  optional string skip_reason = 1;
}
message StepErroredRequest {
  optional string error_reason = 1;
}

// A request to update the state of an execution step.
message TargetStepStateRequest {
  oneof target_state {
    option (buf.validate.oneof) = {required: true};
    StepInProgressRequest in_progress = 1;
    StepSubmittedRequest submitted = 2;
    StepSkippedRequest skipped = 3;
    StepErroredRequest errored = 4;
  }
}

//////////// ProcedureTemporal objects
// These are needed for the Temporal object mapper to work correctly,
// it failed to handle a Java record that contained a proto message.

message ProcedureAsyncTask {
  oneof task {
    option (buf.validate.oneof) = {required: true};
    ConditionObservation condition_observation = 1;
  }
}

message ConditionObservation {
  string user_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  string org_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.authentication.api.OrgRid"}];
  string procedure_execution_rid = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  string step_id = 4 [(buf.validate.field).string.min_len = 1];
  nominal.procedures.v1.SuccessCondition success_condition = 5;
}

//////////// Completion condition status

// Represents the current status of one or more nested completion conditions.
// Mirrors the recursive SuccessCondition in nominal.procedures.v1
message SuccessConditionStatus {
  // The type of condition being monitored.
  oneof condition {
    option (buf.validate.oneof) = {required: true};
    AndSuccessCondition and = 1;
    nominal.procedures.v1.TimerSuccessCondition timer = 6;
    nominal.procedures.v1.IngestJobSuccessCondition ingest_job = 7;
    // ChannelValidationSuccessCondition channel_validation = 4;
    // OrSuccessConditionOrStatus or = 5;
  }
  // The status of this condition.
  oneof status {
    option (buf.validate.oneof) = {required: true};
    SuccessConditionInProgress in_progress = 2;
    SuccessConditionSatisfied satisfied = 3;
    SuccessConditionFailed failed = 4;
    SuccessConditionCanceled canceled = 5;
    SuccessConditionSubmitted submitted = 8;
  }
}

message AndSuccessCondition {
  repeated SuccessConditionStatus conditions = 1;
}

message SuccessConditionSubmitted {
  google.protobuf.Timestamp submitted_at = 1;
}
message SuccessConditionInProgress {
  google.protobuf.Timestamp started_at = 1;
}
message SuccessConditionSatisfied {
  google.protobuf.Timestamp started_at = 1;
  google.protobuf.Timestamp satisfied_at = 2;
}
message SuccessConditionFailed {
  google.protobuf.Timestamp started_at = 1;
  google.protobuf.Timestamp failed_at = 2;
  string failure_reason = 3;
}
message SuccessConditionCanceled {
  google.protobuf.Timestamp started_at = 1;
  google.protobuf.Timestamp canceled_at = 2;
}

//////////// Completion action status

// The status of a completion action and its result (if any).
message CompletionActionStatus {
  CompletionActionState state = 1;
  // This is intentionally not a required oneof
  oneof result {
    CreateEventResult create_event = 2;
    CreateRunResult create_run = 3;
    ApplyWorkbookTemplatesResult apply_workbook_templates = 4;
    ApplyChecklistsResult apply_checklists = 5;
    // ...
  }
}

// A CreateEvent action results in an EventRid if successful.
message CreateEventResult {
  string event_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.EventRid"}];
}

// A CreateRun action results in a RunRid if successful.
message CreateRunResult {
  string run_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.run.api.RunRid"}];
}

// An ApplyWorkbookTemplates action results in a list of workbook RIDs if successful.
message ApplyWorkbookTemplatesResult {
  repeated string workbook_rids = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.NotebookRid"}];
}

// An ApplyChecklists action results in a list of DataReviewRids if successful.
message ApplyChecklistsResult {
  repeated string data_review_rids = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.DataReviewRid"}];
}

message CompletionActionState {
  oneof state {
    option (buf.validate.oneof) = {required: true};
    // The action was not run
    NotRun not_run = 1;
    // The action was run and succeeded. The result is available in CompletionActionStatus.result
    Succeeded succeeded = 2;
    // The action was run and errored. This field contains the error message.
    string error = 3;
  }
  message NotRun {}
  message Succeeded {}
}

/////////////////////////////////////
// Execution Step Content Values   //
// - FormStepValue and FieldValues //
/////////////////////////////////////

// The value for a step's content. Should match the ProcedureStepNode type.
message StepContentValue {
  oneof value {
    option (buf.validate.oneof) = {required: true};
    FormStepValue form = 1;
    StartIngestStepValue start_ingest = 2;
    SelectOrCreateAssetStepValue select_or_create_asset = 3;
    // ...
  }
}

message FormStepValue {
  repeated FormFieldValue fields = 1;
}

message StartIngestStepValue {
  // The RID of the ingest job that was started by the client.
  // This must be populated for the step to be submitted.
  optional string ingest_job_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.IngestJobRid"}];
}

message SelectOrCreateAssetStepValue {
  // The asset that was selected / created by the client.
  // This must be populated for the step to be submitted.
  optional nominal.procedures.v1.AssetReference asset_reference = 1;
}

// The value chosen for a form field.
// This differs from FieldOutput in that this should map 1:1 with FormField types,
// while FieldOutput contains the resolved values after the execution step is complete.
message FormFieldValue {
  oneof field {
    option (buf.validate.oneof) = {required: true};
    AssetFieldValue asset = 1;
    CheckboxFieldValue checkbox = 2;
    TextFieldValue text = 3;
    IntFieldValue int = 4;
    DoubleFieldValue double = 5;
    SingleEnumFieldValue single_enum = 6;
    MultiEnumFieldValue multi_enum = 7;
    FileUploadFieldValue file_upload = 8;
    MultiFileUploadFieldValue multi_file_upload = 9;
  }
}

message AssetFieldValue {
  // This oneof will be unset if no asset is set on the field.
  oneof asset {
    nominal.procedures.v1.AssetReference asset_reference = 1;
  }
}

message CheckboxFieldValue {
  // Default to false if not set
  bool value = 1;
}

message TextFieldValue {
  // Default to empty string if not set
  optional string value = 1;
}

message IntFieldValue {
  optional int64 value = 1;
}

message DoubleFieldValue {
  optional double value = 1;
}

message SingleEnumFieldValue {
  // Default to empty string if not set
  optional string value = 1;
}

message MultiEnumFieldValue {
  // Default to empty list if not set
  repeated string value = 1;
}

// Corresponds to usage of the upload api
message FileUploadFieldValue {
  oneof upload_type {
    S3UploadFileValue s3_upload = 1;
  }
}

message S3UploadFileValue {
  // The S3 path where the uploaded file is stored
  string s3_path = 1 [(buf.validate.field).string.min_len = 1];
  // The original filename of the uploaded file
  string file_name = 2 [(buf.validate.field).string.min_len = 1];
  // The MIME type of the uploaded file
  // Following the convention of https://developer.mozilla.org/en-US/docs/Web/API/Blob/type (from which the File api inherits, we will accept an empty string)
  optional string file_type = 3;
}

message MultiFileUploadFieldValue {
  // The list of uploads
  repeated FileUploadFieldValue uploads = 1;
}

///////////////////////////////
// Top-level Procedure Types //
///////////////////////////////

message ProcedureExecution {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  ProcedureExecutionMetadata metadata = 2;
  ProcedureExecutionState state = 3;
}

message ProcedureExecutionMetadata {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  // The procedure template rid this execution is based on.
  string procedure_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureRid"}];
  // The commit ID of the procedure template this execution is based on.
  // This can be overridden at the node level for individual nodes.
  string procedure_commit_id = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
  string title = 4 [(buf.validate.field).string.min_len = 1];
  optional string description = 5;
  repeated string labels = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Label"}];
  map<string, string> properties = 7 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.PropertyName"
    value_java_name: "io.nominal.api.PropertyValue"
  }];
  string created_by = 8 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp created_at = 9;
  string updated_by = 10 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp updated_at = 11;

  // Execution metadata to derive the current state of the execution.
  // This "state" refers to the state machine of the overall execution
  // and is distinct from the ProcedureExecutionState which contains the
  // detailed state of each node in the execution graph. TODO(niwong to pchen): rename something?

  // Will be unset until the execution transitions from NOT_STARTED to IN_PROGRESS.
  optional google.protobuf.Timestamp started_at = 12;
  optional string started_by = 13 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // Will be unset until the execution transitions from IN_PROGRESS to a terminal state.
  optional google.protobuf.Timestamp finished_at = 14;
  optional string finished_by = 15 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // Will be true/set if the execution is the ABORTED terminal state.
  optional google.protobuf.Timestamp aborted_at = 16;
  optional string aborted_by = 17 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  // Will be set if the execution is in the ERRORED terminal state.
  optional google.protobuf.Timestamp failed_at = 18;
  optional string failed_by = 19 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  optional string failed_reason = 20;
}

// ProcedureExecutionState is the state of an executing procedure.
message ProcedureExecutionState {
  // The current values for the global fields in the procedure.
  map<string, FieldOutput> global_fields = 1;
  // A map from execution node ID to the execution node.
  map<string, ProcedureExecutionNode> nodes = 2;
  // Section edges represent the visual grouping of sections and steps.
  // All keys are IDs for section nodes, and all values are lists of IDs for section or step nodes.
  // { A, [B, C] } means that section A visually groups nodes B and C together.
  map<string, nominal.procedures.v1.NodeList> section_edges = 3;
  // Step edges represent the dependencies between steps.
  // All keys and values are IDs for step nodes.
  // { A, [B, C] } means that steps B and C depend on step A to complete to be started
  map<string, nominal.procedures.v1.NodeList> step_edges = 4;
}

message Strings {
  repeated string values = 1;
}

// TODO(pchen): how to handle the value typing here
// The outputs of a field in a step.
// This is different from FormFieldValue in that this represents the resolved value after the step has
// been completed. Eg, this would contain an AssetRid while the FormFieldValue could contain an AssetReference
message FieldOutput {
  // The oneof can be unset if the field has not been set yet.
  oneof output {
    // TODO(niwong): I think this could actually be buf validated to be required. If a global field is
    // not yet defined, I think it's just not present in the global_fields map
    //option (buf.validate.oneof) = {required: true};
    string asset_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.AssetRid"}];
    string string_value = 2;
    double double_value = 3;
    bool boolean_value = 4;
    int64 int_value = 5;
    Strings strings_value = 6;
    string ingest_job_rid = 7 [(nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.IngestJobRid"}];
    string run_rid = 8 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.run.api.RunRid"}];
    FileUploadFieldValue file_upload_value = 9;
    MultiFileUploadFieldValue multi_file_upload_value = 10;
  }
}

/////////////////////
// Req/Res Objects //
/////////////////////

message CreateProcedureExecutionRequest {
  string procedure_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureRid"}];
  string procedure_commit_id = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
  string title = 3 [(buf.validate.field).string.min_len = 1];
  optional string description = 4;
}

message CreateProcedureExecutionResponse {
  ProcedureExecution procedure_execution = 1;
}

message StartProcedureExecutionRequest {
  string procedure_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureRid"}];
  nominal.versioning.v1.BranchOrCommit branch_or_commit = 2;
  string title = 3 [(buf.validate.field).string.min_len = 1];
  optional string description = 4;
}

message StartProcedureExecutionResponse {
  ProcedureExecution procedure_execution = 1;
}

message GetProcedureExecutionRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  // If true, the display graph will be included in the response.
  // Default is false.
  bool include_display_graph = 2;
}

message GetProcedureExecutionResponse {
  optional ProcedureExecution procedure_execution = 1;
  // Included if include_display_graph was set to true. Note that this is the
  // procedure template's display graph, not the execution graph.
  optional nominal.procedures.v1.ProcedureDisplayGraph display_graph = 2;
}

message UpdateProcedureExecutionMetadataRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  optional string title = 2;
  optional string description = 3;
  optional string commit_id = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
  optional nominal.types.LabelUpdateWrapper labels = 5;
  optional nominal.types.PropertyUpdateWrapper properties = 6;
}

message UpdateProcedureExecutionMetadataResponse {
  ProcedureExecutionMetadata metadata = 1;
}

// TODO(niwong to pchen): I made an "update metadata" endpoint separately above,
// I think it'd be nice to turn this into a dedicated endpoint for changing the state of
// the overall execution (start, succeed, abort, fail)
// I also think in general we shouldn't be updating the entire ProcedureExecutionState
// but rather leveraging updateStep.
// TODO(WB-1879): allow to just update the global fields within the ProcExecState.
message UpdateProcedureExecutionRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  optional string title = 2;
  optional string description = 3;
  optional string commit_id = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
  optional nominal.types.LabelUpdateWrapper labels = 5;
  optional nominal.types.PropertyUpdateWrapper properties = 6;
  optional ProcedureExecutionState state = 7;
  optional bool is_aborted = 8;
  optional google.protobuf.Timestamp started_at = 9;
  optional google.protobuf.Timestamp finished_at = 10;
}

message UpdateProcedureExecutionResponse {
  ProcedureExecution procedure_execution = 1;
}

message UpdateStepRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  // The unique identifier of the execution step to update.
  string step_id = 2 [(buf.validate.field).string.min_len = 1];
  // Setting the value of the step's content. This is only allowed if the step is NOT_STARTED
  // or IN_PROGRESS.
  optional StepContentValue value = 3;
  // Set whether the step has auto-proceed enabled. This is only allowed if the step is NOT_STARTED.
  // or IN_PROGRESS.
  optional nominal.procedures.v1.AutoProceedConfig auto_proceed_config = 4;
  // Setting the state of the step. This will occur after the other updates in this request.
  optional TargetStepStateRequest target_state = 5;
}

message UpdateStepResponse {
  ProcedureExecution procedure_execution = 1;
}

message UpdateStepSuccessConditionStatusRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  // The unique identifier of the execution step to update.
  string step_id = 2 [(buf.validate.field).string.min_len = 1];
  // Setting the success_condition_status is only allowed if the step is SUBMITTED
  // and should only be done by the backend while monitoring the condition.
  // If the step is SKIPPED, the success_condition_status will still be set but
  // no completion actions will be run and the step's state will not be updated.
  SuccessConditionStatus success_condition_status = 3;
}

message UpdateStepSuccessConditionStatusResponse {
  ProcedureExecution procedure_execution = 1;
}

enum RepeatStepBehavior {
  REPEAT_STEP_BEHAVIOR_UNSPECIFIED = 0;
  // The targeted step will be repeated, but the original graph will be preserved
  // and only direct children will be checked for state updates
  REPEAT_STEP_BEHAVIOR_ISOLATED = 1;
}

message RepeatStepRequest {
  string procedure_execution_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
  // The unique identifier of the execution step to repeat/duplicate.
  string step_id = 2 [(buf.validate.field).string.min_len = 1];
  // Behavior for how repeating the step will affect dependants.
  RepeatStepBehavior behavior = 6;
  // Setting the value of the step's content.
  optional StepContentValue value = 3;
  // Set whether the step has auto-proceed enabled.
  optional nominal.procedures.v1.AutoProceedConfig auto_proceed_config = 4;
  // Setting the state of the step. This will occur after the other updates in this request.
  optional TargetStepStateRequest target_state = 5;
}

message RepeatStepResponse {
  ProcedureExecution procedure_execution = 1;
}

enum SearchProcedureExecutionsSortField {
  SEARCH_PROCEDURE_EXECUTIONS_SORT_FIELD_UNSPECIFIED = 0;
  SEARCH_PROCEDURE_EXECUTIONS_SORT_FIELD_CREATED_AT = 1;
  SEARCH_PROCEDURE_EXECUTIONS_SORT_FIELD_STARTED_AT = 2;
  SEARCH_PROCEDURE_EXECUTIONS_SORT_FIELD_FINISHED_AT = 3;
  SEARCH_PROCEDURE_EXECUTIONS_SORT_FIELD_UPDATED_AT = 4;
}

message ProcedureExecutionSearchQuery {
  oneof query {
    option (buf.validate.oneof) = {required: true};
    string search_text = 1;
    string label = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Label"}];
    nominal.types.Property property = 3;
    ProcedureExecutionSearchAndQuery and = 4;
    ProcedureExecutionSearchOrQuery or = 5;
    string workspace = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
    string procedure_rid = 7 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureRid"}];
    string commit_id = 8 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
    string created_by = 9 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  }

  message ProcedureExecutionSearchAndQuery {
    repeated ProcedureExecutionSearchQuery queries = 1;
  }

  message ProcedureExecutionSearchOrQuery {
    repeated ProcedureExecutionSearchQuery queries = 1;
  }
}

message ProcedureExecutionSortOptions {
  bool is_descending = 1 [(buf.validate.field).required = true];
  SearchProcedureExecutionsSortField sort_field = 2 [(buf.validate.field).required = true];
}

message SearchProcedureExecutionsRequest {
  ProcedureExecutionSearchQuery query = 1;
  optional ProcedureExecutionSortOptions sort_options = 2;
  // Page sizes greater than 1_000 will be rejected. Default pageSize is 100.
  optional int32 page_size = 3 [
    (buf.validate.field).int32.gte = 1,
    (buf.validate.field).int32.lte = 1000
  ];
  optional string next_page_token = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

message SearchProcedureExecutionsResponse {
  repeated ProcedureExecutionMetadata procedure_executions = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

message BatchGetProcedureExecutionMetadataRequest {
  repeated string procedure_execution_rids = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ProcedureExecutionRid"}];
}

message BatchGetProcedureExecutionMetadataResponse {
  repeated ProcedureExecutionMetadata procedure_executions = 1;
}

////////////
// Errors //
////////////

enum ProcedureExecutionsServiceError {
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_UNSPECIFIED = 0;
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_NOT_FOUND = 1 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Procedure execution not found"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_PROC_NOT_FOUND = 2 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Procedure not found"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_NODE_NOT_FOUND = 3 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Node not found"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_INVALID_NODE = 4 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Invalid node"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_INVALID_GRAPH = 5 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid procedure graph"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_INVALID_STEP_TRANSITION = 6 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid step transition"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_INVALID_SEARCH_TOKEN = 7 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid search token"
  }];
  PROCEDURE_EXECUTIONS_SERVICE_ERROR_INVALID_STEP_UPDATE = 8 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid step update"
  }];
}

// Service for creating and managing procedure executions.
service ProcedureExecutionsService {
  // Create a new procedure execution from a procedure
  rpc CreateProcedureExecution(CreateProcedureExecutionRequest) returns (CreateProcedureExecutionResponse) {
    option (google.api.http) = {
      post: "/procedures/v1/executions/procedures/{procedure_rid}"
      body: "*"
    };
  }

  // Create a new procedure execution from a procedure and starts the first node, if possible
  rpc StartProcedureExecution(StartProcedureExecutionRequest) returns (StartProcedureExecutionResponse) {
    option (google.api.http) = {
      post: "/procedures/v1/executions/procedures/{procedure_rid}/start"
      body: "*"
    };
  }

  // Get a procedure execution by its RID
  rpc GetProcedureExecution(GetProcedureExecutionRequest) returns (GetProcedureExecutionResponse) {
    option (google.api.http) = {get: "/procedures/v1/executions/{procedure_execution_rid}"};
  }

  // Update the metadata of a procedure execution
  rpc UpdateProcedureExecutionMetadata(UpdateProcedureExecutionMetadataRequest) returns (UpdateProcedureExecutionMetadataResponse) {
    option (google.api.http) = {
      put: "/procedures/v1/executions/{procedure_execution_rid}/metadata"
      body: "*"
    };
  }

  // Heavy-handed update method for procedure execution
  // TODO(pchen): This isn't the expected final endpoint, this is just for editing the graph while we iterate
  // There likely be additional fine-grained methods for editing the procedure
  rpc UpdateProcedureExecution(UpdateProcedureExecutionRequest) returns (UpdateProcedureExecutionResponse) {
    option (google.api.http) = {
      put: "/procedures/v1/executions/{procedure_execution_rid}/update"
      body: "*"
    };
  }

  // Update the status of a step in a procedure execution. This can only be done if the step is NOT_STARTED,
  // IN_PROGRESS, or SUBMITTED. Other state transitions are managed by the backend.
  rpc UpdateStep(UpdateStepRequest) returns (UpdateStepResponse) {
    option (google.api.http) = {
      put: "/procedures/v1/executions/{procedure_execution_rid}/steps/{step_id}/update"
      body: "*"
    };
  }

  // This should only be called by the backend to update the success_condition_status of a step.
  // This can only be done if the step is SUBMITTED.
  rpc UpdateStepSuccessConditionStatus(UpdateStepSuccessConditionStatusRequest) returns (UpdateStepSuccessConditionStatusResponse) {
    option (google.api.http) = {
      put: "/procedures/v1/executions/{procedure_execution_rid}/steps/{step_id}/success-condition-status"
      body: "*"
    };
  }

  // Repeat a previously finished step in a procedure execution. This can only be done if the given step
  // is in state SUCCEEDED, SKIPPED or ERRORED. Other state transitions are managed by the backend.
  rpc RepeatStep(RepeatStepRequest) returns (RepeatStepResponse) {
    option (google.api.http) = {
      put: "/procedures/v1/executions/{procedure_execution_rid}/steps/{step_id}/repeat"
      body: "*"
    };
  }

  // Search for procedure executions
  rpc SearchProcedureExecutions(SearchProcedureExecutionsRequest) returns (SearchProcedureExecutionsResponse) {
    option (google.api.http) = {get: "/procedures/v1/executions/search"};
  }

  // Batch get procedure execution metadata
  rpc BatchGetProcedureExecutionMetadata(BatchGetProcedureExecutionMetadataRequest) returns (BatchGetProcedureExecutionMetadataResponse) {
    option (google.api.http) = {
      post: "/procedures/v1/executions/batch-get-metadata"
      body: "*"
    };
  }
}
