syntax = "proto3";

package nominal.connect.v1;

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

option java_multiple_files = true;
option java_package = "io.nominal.connect.v1";

message CreateConnectAppRequest {
  // separate from the bundle title to allow for creating empty apps
  string display_name = 1 [(buf.validate.field).string.min_len = 1];
  optional string description = 2;
  repeated string labels = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Label"}];
  map<string, string> properties = 4 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.PropertyName"
    value_java_name: "io.nominal.api.PropertyValue"
  }];
  optional bool is_published = 5;
  string workspace = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
  // optional to allow for creating "empty" in-progress connect apps
  optional ConnectAppBundle bundle = 7;
  string commit_message = 8;
}

message CreateConnectAppResponse {
  ConnectApp app = 1;
}

message UpdateConnectAppMetadataRequest {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  optional string display_name = 2;
  optional string description = 3;
  optional nominal.types.LabelUpdateWrapper labels = 4;
  optional nominal.types.PropertyUpdateWrapper properties = 5;
  optional bool is_archived = 6;
  optional bool is_published = 7;
}

message ConnectAppBundle {
  nominal.types.Handle s3_path = 1;
  ConnectAppBundleMetadata metadata = 2;
}

// wrapper message for metadata that will probably be extended in the future
message ConnectAppBundleMetadata {
  string title = 1;
  bool contains_experimental_features = 2;
  string sha256_checksum = 3;
  // name of the .connect file inside the bundle. Will default to app.connect
  optional string connect_file_path = 4;
}

message ConnectAppSearchQuery {
  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;
    ConnectAppSearchAndQuery and = 4;
    ConnectAppSearchOrQuery or = 5;
    string workspace = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
    bool is_archived = 7;
  }

  message ConnectAppSearchAndQuery {
    repeated ConnectAppSearchQuery queries = 1;
  }

  message ConnectAppSearchOrQuery {
    repeated ConnectAppSearchQuery queries = 1;
  }
}

message ConnectAppMetadata {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  string display_name = 2;
  optional string description = 3;
  repeated string labels = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Label"}];
  map<string, string> properties = 5 [(nominal.gen.v1.alias) = {
    key_java_name: "io.nominal.api.PropertyName"
    value_java_name: "io.nominal.api.PropertyValue"
  }];
  bool is_archived = 6;
  bool is_published = 7;
  google.protobuf.Timestamp created_at = 8;
  string created_by = 9 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.rids.api.UserRid"}];
  google.protobuf.Timestamp updated_at = 10;
  string workspace = 11 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
}

message ConnectApp {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  string commit = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.scout.versioning.api.CommitId"}];
  ConnectAppMetadata metadata = 3;
  optional ConnectAppBundle bundle = 4;
  uint64 version_number = 5;
}

message GetConnectAppRequest {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  nominal.versioning.v1.BranchOrCommit branch_or_commit = 2;
}

message GetConnectAppBundleDownloadUrlRequest {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  nominal.versioning.v1.BranchOrCommit branch_or_commit = 2;
}

message GetConnectAppBundleDownloadUrlResponse {
  string url = 1;
}

message GetConnectAppResponse {
  ConnectApp app = 1;
}

message UpdateConnectAppMetadataResponse {
  ConnectAppMetadata app_metadata = 1 [(buf.validate.field) = {required: true}];
}

// only allow commits to the main branch
// no latest commit id field to bypass conflict detection
message CommitRequest {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ConnectAppRid"}];
  ConnectAppBundle bundle = 2;
  string message = 3;
}

message CommitResponse {
  ConnectApp app = 1;
}

enum SearchConnectAppsSortField {
  SEARCH_CONNECT_APPS_SORT_FIELD_UNSPECIFIED = 0;
  SEARCH_CONNECT_APPS_SORT_FIELD_TITLE = 1;
  SEARCH_CONNECT_APPS_SORT_FIELD_CREATED_AT = 2;
  SEARCH_CONNECT_APPS_SORT_FIELD_DISPLAY_NAME = 3;
}

message SearchConnectAppsOptions {
  // default is true
  optional bool is_descending = 1;

  // default is title
  optional SearchConnectAppsSortField sort_field = 2;
}

message SearchConnectAppsRequest {
  ConnectAppSearchQuery query = 1;
  optional SearchConnectAppsOptions options = 2;

  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 SearchConnectAppsResponse {
  repeated ConnectAppMetadata app_metadata = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

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

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

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

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

enum ConnectAppsServiceError {
  CONNECT_APPS_SERVICE_ERROR_UNSPECIFIED = 0;
  CONNECT_APPS_SERVICE_ERROR_APP_NOT_FOUND = 1 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Connect app not found"
  }];
  CONNECT_APPS_SERVICE_ERROR_COMMIT_NOT_FOUND = 2 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Connect app commit not found"
  }];
  CONNECT_APPS_SERVICE_ERROR_CANNOT_COMMIT_TO_ARCHIVED_CONNECT_APP = 3 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Cannot commit to an archived connect app"
  }];
  CONNECT_APPS_SERVICE_ERROR_INVALID_SEARCH_TOKEN = 4 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid search token"
  }];
  CONNECT_APPS_SERVICE_ERROR_MISSING_BUNDLE = 5 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Connect app bundle is missing in this commit"
  }];
}

// Service for creating and managing Connect Apps
service ConnectAppsService {
  // Create a new Connect App, either with a bundle file or as an empty app
  rpc CreateConnectApp(CreateConnectAppRequest) returns (CreateConnectAppResponse) {
    option (google.api.http) = {
      post: "/connect/v1/apps"
      body: "*"
    };
  }

  // Get a Connect App by its RID
  rpc GetConnectApp(GetConnectAppRequest) returns (GetConnectAppResponse) {
    option (google.api.http) = {get: "/connect/v1/apps/{rid}"};
  }

  // Update metadata fields of a Connect App
  rpc UpdateConnectAppMetadata(UpdateConnectAppMetadataRequest) returns (UpdateConnectAppMetadataResponse) {
    option (google.api.http) = {
      put: "/connect/v1/apps/{rid}/metadata"
      body: "*"
    };
  }

  // Commit a bundle to the main branch
  rpc Commit(CommitRequest) returns (CommitResponse) {
    option (google.api.http) = {
      post: "/connect/v1/apps/{rid}/commit"
      body: "*"
    };
  }

  // Searches for Connect Apps, returning paged results
  rpc SearchConnectApps(SearchConnectAppsRequest) returns (SearchConnectAppsResponse) {
    option (google.api.http) = {
      post: "/connect/v1/apps/search"
      body: "*"
    };
  }

  // Archive Connect Apps
  rpc ArchiveConnectApps(ArchiveConnectAppsRequest) returns (ArchiveConnectAppsResponse) {
    option (google.api.http) = {
      post: "/connect/v1/apps/archive"
      body: "*"
    };
  }

  // Unarchive Connect Apps
  rpc UnarchiveConnectApps(UnarchiveConnectAppsRequest) returns (UnarchiveConnectAppsResponse) {
    option (google.api.http) = {
      post: "/connect/v1/apps/unarchive"
      body: "*"
    };
  }

  // Get a pre-signed URL for downloading the bundle of a Connect App at a specific commit or the latest commit on the main branch
  rpc GetConnectAppBundleDownloadUrl(GetConnectAppBundleDownloadUrlRequest) returns (GetConnectAppBundleDownloadUrlResponse) {
    option (google.api.http) = {get: "/connect/v1/apps/{rid}/bundle-download"};
  }
}
