// V2 of the containerized extractor service. Speaks gRPC/proto so it can be exposed alongside
// the existing v1 Conjure-based ContainerizedExtractorService without breaking existing clients.
//
// V2 differs from v1 in that:
//   - The transport is gRPC + proto rather than Conjure HTTP, matching the rest of the modern
//     scout surface (e.g. nominal.registry.v2.RegistryService).
//   - The extractor record itself only carries identity, ownership and lifecycle state. Schema
//     fields (inputs, parameters, output_file_format, default_timestamp_metadata) live on the
//     ContainerImage rows registered through nominal.registry.v2.RegistryService.CreateImage,
//     and the extractor exposes them indirectly through `active_container_image`. This avoids the
//     v1 mismatch where extractor- and image-level schemas could drift.
//   - Self-hosted images are the only supported source — the deprecated `image: DockerImageSource`
//     field from v1 is gone.
//   - Archive/unarchive happen through Update by setting `is_archived`, rather than through
//     dedicated RPCs.
//
// V1 remains the canonical API while clients migrate.
syntax = "proto3";

package nominal.ingest.v2;

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/registry/v2/registry.proto";

option java_multiple_files = true;
option java_package = "io.nominal.ingest.v2";

// A containerized extractor that processes input files using a self-hosted container image.
//
// Schema fields (inputs, parameters, output format, timestamp metadata) live on the underlying
// ContainerImage rows; clients should read them off `active_container_image`. The extractor
// itself carries only identity, workspace ownership, and lifecycle state.
message ContainerizedExtractor {
  // Unique resource identifier for this extractor.
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.ContainerizedExtractorRid"}
  ];
  // Workspace that owns this extractor.
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // User-supplied name. Need not be unique within a workspace.
  string name = 3;
  // Free-form description shown in the UI.
  optional string description = 4;
  // Timestamp at which this extractor was first registered.
  google.protobuf.Timestamp created_at = 5;
  // True once the extractor has been archived. Archived extractors are hidden from default search
  // results and reject new ingests; flip back to false on UpdateContainerizedExtractor to restore.
  bool is_archived = 6;
  // The container image currently selected to run this extractor. Unset until the first image
  // for this extractor reaches READY status; ingest requests that arrive before then will fail.
  // The schema fields (inputs, parameters, output_file_format, default_timestamp_metadata) live
  // on this nested image.
  optional nominal.registry.v2.ContainerImage active_container_image = 7;
}

// Request to create a new self-hosted containerized extractor in a workspace. Schema fields
// (inputs / parameters / output format / timestamp metadata) are supplied with each container
// image via RegistryService.CreateImage and not on the extractor itself.
message CreateContainerizedExtractorRequest {
  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];
  optional string description = 3;
}

message CreateContainerizedExtractorResponse {
  ContainerizedExtractor extractor = 1;
}

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

message GetContainerizedExtractorResponse {
  ContainerizedExtractor extractor = 1;
}

// Partial update. Only fields explicitly set on the request are modified; unset fields are left
// unchanged on the existing extractor. Archive / unarchive are expressed through this request by
// setting `is_archived`. The active container image is selected by setting
// `active_container_image_rid`.
message UpdateContainerizedExtractorRequest {
  string rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.ingest.api.ContainerizedExtractorRid"}
  ];
  string workspace_rid = 2 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  optional string name = 3;
  optional string description = 4;
  // Set to true to archive, false to unarchive. Leave unset to leave archive state unchanged.
  optional bool is_archived = 5;
  // RID of the container image to set as the active one for this extractor. Must reference a
  // ContainerImage that is owned by the same workspace, was built for this extractor, and has
  // reached READY status. Leave unset to leave the active image unchanged.
  optional string active_container_image_rid = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.ContainerImageRid"}];
}

message UpdateContainerizedExtractorResponse {
  ContainerizedExtractor extractor = 1;
}

// Search extractors within a workspace, with cursor-based pagination. Filter fields are intended
// to be added here over time; calling with only the workspace_rid set returns the default listing.
message SearchContainerizedExtractorsRequest {
  string workspace_rid = 1 [
    (buf.validate.field).required = true,
    (nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}
  ];
  // When true, archived extractors are also returned.
  bool include_archived = 2;
  int32 page_size = 3 [
    (buf.validate.field).required = true,
    (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"}];
  // When set, restricts results to extractors whose active container image is READY and declares
  // at least one FileExtractionInput whose file_filters include this suffix. Example values:
  // "csv", "parquet", "mcap" — no leading dot, case-sensitive against FileSuffix.suffix.
  optional string file_extension = 5 [
    (buf.validate.field).string.min_len = 1,
    (buf.validate.field).string.max_len = 32
  ];
}

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

// Manages containerized extractors backed by self-hosted images in the internal registry.
service ContainerizedExtractorService {
  // Create a new extractor in a workspace. Container images are attached separately via
  // RegistryService.CreateImage.
  rpc CreateContainerizedExtractor(CreateContainerizedExtractorRequest) returns (CreateContainerizedExtractorResponse) {
    option (google.api.http) = {
      post: "/extractors/v2"
      body: "*"
    };
  }

  // Fetch a single extractor by RID.
  rpc GetContainerizedExtractor(GetContainerizedExtractorRequest) returns (GetContainerizedExtractorResponse) {
    option (google.api.http) = {get: "/extractors/v2/{rid}"};
  }

  // Partial update of an existing extractor. Use the `is_archived` field to archive / unarchive.
  rpc UpdateContainerizedExtractor(UpdateContainerizedExtractorRequest) returns (UpdateContainerizedExtractorResponse) {
    option (google.api.http) = {
      patch: "/extractors/v2/{rid}"
      body: "*"
    };
  }

  // Search extractors within a workspace, with cursor-based pagination.
  rpc SearchContainerizedExtractors(SearchContainerizedExtractorsRequest) returns (SearchContainerizedExtractorsResponse) {
    option (google.api.http) = {
      post: "/extractors/v2/search"
      body: "*"
    };
  }
}
