syntax = "proto3";

package nominal.registry.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/types/time/timestamp_parsers.proto";

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

enum ContainerImageStatus {
  CONTAINER_IMAGE_STATUS_UNSPECIFIED = 0;
  CONTAINER_IMAGE_STATUS_PENDING = 1;
  CONTAINER_IMAGE_STATUS_READY = 2;
  CONTAINER_IMAGE_STATUS_FAILED = 3;
}

// Output file format emitted by the extractor.
// Currently PARQUET, CSV, and PARQUET_TAR are the primary supported formats.
enum FileOutputFormat {
  FILE_OUTPUT_FORMAT_UNSPECIFIED = 0;
  FILE_OUTPUT_FORMAT_PARQUET = 1;
  FILE_OUTPUT_FORMAT_CSV = 2;
  FILE_OUTPUT_FORMAT_PARQUET_TAR = 3;
  FILE_OUTPUT_FORMAT_AVRO_STREAM = 4;
  FILE_OUTPUT_FORMAT_JSON_L = 5;
  FILE_OUTPUT_FORMAT_MANIFEST = 6;
}

// The expected suffix of the file. For example, "parquet", "json", "csv", etc.
message FileSuffix {
  string suffix = 1 [(buf.validate.field).required = true];
}

// Optional filter applied during file selection for a file input.
message FileFilter {
  oneof filter {
    option (buf.validate.oneof) = {required: true};

    FileSuffix suffix = 1;
  }
}

// Defines an input file to be provided to the extractor.
message FileExtractionInput {
  // The environment variable that stores the path to the input file.
  string environment_variable = 1 [(buf.validate.field).required = true];
  // Name of the input file which users will be prompted with.
  string name = 2 [(buf.validate.field).required = true];
  // Description of the input file which users will be prompted with.
  optional string description = 3;
  // Optionally filter files for file selection.
  repeated FileFilter file_filters = 4;
  // Whether the input file is required for the extractor to run.
  optional bool required = 5;
}

// Defines an input parameter to be provided to the extractor.
message FileExtractionParameter {
  // The environment variable that stores the argument.
  string environment_variable = 1 [(buf.validate.field).required = true];
  // Name of the parameter which users will be prompted with.
  string name = 2 [(buf.validate.field).required = true];
  // Description of the parameter which users will be prompted with.
  optional string description = 3;
  // Whether the parameter is required for the extractor to run.
  optional bool required = 4;
}

// Describes how to derive a row timestamp from the extracted output.
message TimestampMetadata {
  // Name of the series within the output whose values are used as the row timestamp.
  string series_name = 1 [(buf.validate.field).required = true];
  // How the values in `series_name` should be interpreted as timestamps.
  nominal.types.time.TimestampType timestamp_type = 2 [(buf.validate.field).required = true];
}

message ContainerImage {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  string name = 2;
  string tag = 3;
  optional int64 size_bytes = 4;
  ContainerImageStatus status = 5;
  google.protobuf.Timestamp created_at = 6;
  // RID of the containerized extractor this image was built for. Empty for legacy rows that
  // predate the extractor-link schema.
  string extractor_rid = 7 [(nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.ContainerizedExtractorRid"}];
  // The input files this extractor requires, mapped to environment variables that store the path to each file.
  repeated FileExtractionInput inputs = 8;
  // Parameters the extractor accepts.
  repeated FileExtractionParameter parameters = 9;
  // Format of the output file produced by the extractor.
  FileOutputFormat file_output_format = 10;
  // Metadata about the intermediate parquet this extractor will produce.
  // If not set, timestamp metadata must be provided at ingest time.
  optional TimestampMetadata default_timestamp_metadata = 11;
}

message CreateImageRequest {
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  string name = 2 [(buf.validate.field).required = true];
  string tag = 3 [(buf.validate.field).required = true];
  // Full object-storage path ("s3://bucket/key") returned by the existing multipart upload
  // flow (UploadService). Must reference an object in the configured upload bucket.
  string object_path = 4 [(buf.validate.field).required = true];
}

message CreateImageResponse {
  ContainerImage image = 1;
}

message GetImageRequest {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
}

message GetImageResponse {
  ContainerImage image = 1;
}

message DeleteImageRequest {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
}

message DeleteImageResponse {}

message NameFilter {
  string name = 1 [(buf.validate.field).required = true];
}

message TagFilter {
  string tag = 1 [(buf.validate.field).required = true];
}

message StatusFilter {
  ContainerImageStatus status = 1 [(buf.validate.field).required = true];
}

message AndFilter {
  repeated SearchFilter clauses = 1;
}

message SearchFilter {
  oneof filter {
    option (buf.validate.oneof) = {required: true};

    NameFilter name = 1;
    TagFilter tag = 2;
    StatusFilter status = 3;
    AndFilter and = 4;
  }
}

message SearchImagesRequest {
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // Optional composable filter. Omit to list all images in the workspace.
  optional SearchFilter filter = 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 SearchImagesResponse {
  repeated ContainerImage images = 1;
  optional string next_page_token = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

// Manages container images in the internal registry.
service RegistryService {
  // Create a container image from a previously uploaded tarball, triggering the push to the
  // internal registry. The client should first upload the tarball via the existing UploadService
  // multipart flow, then call this endpoint with the object key to create the image record and
  // start the push.
  rpc CreateImage(CreateImageRequest) returns (CreateImageResponse) {
    option (google.api.http) = {
      post: "/registry/v1/images"
      body: "*"
    };
  }

  // Get the current status of a container image.
  rpc GetImage(GetImageRequest) returns (GetImageResponse) {
    option (google.api.http) = {get: "/registry/v1/images/{rid}"};
  }

  // Delete a container image.
  rpc DeleteImage(DeleteImageRequest) returns (DeleteImageResponse) {
    option (google.api.http) = {delete: "/registry/v1/images/{rid}"};
  }

  // Search for container images within a workspace, with optional filters and cursor-based pagination.
  rpc SearchImages(SearchImagesRequest) returns (SearchImagesResponse) {
    option (google.api.http) = {
      post: "/registry/v1/images/search"
      body: "*"
    };
  }
}
