// V2 of the registry API — the sole registry wire surface. Images are keyed by
// (workspace, extractor, tag); the v2 ContainerImage drops the legacy `name` field entirely in
// favour of extractor identity as the stable handle. The leaf types (FileExtractionInput,
// FileExtractionParameter, FileOutputFormat, TimestampMetadata, ContainerImageStatus, FileFilter,
// FileSuffix) live here now that the v1 surface has been deleted.
//
// V2 differs from the retired v1 surface in that:
//   - Images are keyed by (workspace, extractor, tag) instead of (workspace, name, tag).
//     Extractor identity replaces the user-supplied image name as the stable handle, so the
//     v2 ContainerImage type drops `name` entirely.
//   - The OCI repository path is {workspaceUuid}/{extractorUuid}:{tag}, replacing the legacy
//     {workspaceUuid}/{name}:{tag} layout.
//   - CreateImage requires extractor_rid, inputs, file_output_format so the registry has a
//     complete execution contract for the image, rather than relying on a separately persisted
//     record on the extractor that can drift from the image it points at.
//   - SearchFilter has no NameFilter (v2 images have no name to filter on); only TagFilter,
//     StatusFilter, and AndFilter are exposed.
//
// On the Java side, only CreateImage has its own service method (createImageV2): the OCI key,
// DB columns, and required metadata genuinely differ from the shared read path. GetImage /
// DeleteImage / SearchImages reuse the underlying RegistryService Java service methods, which
// now traffic in these v2 types directly.
syntax = "proto3";

package nominal.registry.v2;

import "buf/validate/validate.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.v2";

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];
}

// Container image registered through the V2 (extractor-linked) registry flow. V2 images carry no
// user-supplied `name` — extractor identity replaces it as the stable handle.
message ContainerImage {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}
  ];
  // User-supplied tag, unique within (workspace, extractor).
  string tag = 2;
  // Size of the stored image in bytes once the image reaches READY.
  optional int64 size_bytes = 3;
  // Current lifecycle status (PENDING while pushing, READY after a successful push, FAILED otherwise).
  ContainerImageStatus status = 4;
  // When the image record was created.
  google.protobuf.Timestamp created_at = 5;
  // The containerized extractor this image is built for.
  string extractor_rid = 6 [(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 = 7;
  // Parameters the extractor accepts.
  repeated FileExtractionParameter parameters = 8;
  // Format of the output file produced by the extractor.
  FileOutputFormat file_output_format = 9;
  // 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 = 10;
}

message CreateImageRequest {
  // Workspace that owns the image. Must match the extractor's workspace.
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // User-supplied tag (e.g. "latest", "v1"). Unique within (workspace, extractor).
  string tag = 2 [(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 = 3 [(buf.validate.field).required = true];
  // The containerized extractor this image is built for. Must exist in the same workspace.
  string extractor_rid = 4 [
    (buf.validate.field).required = true,
    (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. At least one input must be declared.
  repeated FileExtractionInput inputs = 5 [(buf.validate.field).repeated.min_items = 1];
  // Parameters the extractor accepts.
  repeated FileExtractionParameter parameters = 6;
  // Format of the output file produced by the extractor.
  FileOutputFormat file_output_format = 7 [(buf.validate.field).enum = {
    defined_only: true
    not_in: [0]
  }];
  // Metadata about the intermediate parquet this extractor will produce. Required so the
  // image carries a complete execution contract: ingest needs timestamp metadata from the
  // image or the request, and an image without a default silently depends on every caller
  // supplying it. Callers may still override it per ingest request.
  TimestampMetadata default_timestamp_metadata = 8 [(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 {}

// V2 search filter types. V2 deliberately omits NameFilter (v2 images have no user-supplied name)
// and may add ExtractorFilter in a follow-up.
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};

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

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"}];
}

// The sole registry wire surface (the legacy v1 RegistryService has been removed). The read
// endpoints (GetImage / DeleteImage / SearchImages) delegate to shared RegistryService Java
// service methods — only CreateImage has a v2-specific service path (different OCI key, different
// DB fields). See file-level comment.
service RegistryService {
  // Register a new container image from a previously uploaded tarball and attach it to the
  // referenced containerized extractor. 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) {}

  // Get the current state of a container image.
  rpc GetImage(GetImageRequest) returns (GetImageResponse) {}

  // Delete a container image. Fails if any containerized extractor references it.
  rpc DeleteImage(DeleteImageRequest) returns (DeleteImageResponse) {}

  // List container images in a workspace, optionally filtered by tag / status.
  rpc SearchImages(SearchImagesRequest) returns (SearchImagesResponse) {}
}
