syntax = "proto3";

package imagebuilder.v1;

import "image/v1/image.proto";

option go_package = "github.com/sailresearchco/sail/backend/internal/sailbox/imagebuilder/pb;pb";

// ImageBuilderService is the customer-visible imagebuilder API for sailbox
// custom images. It is served by the imagebuilder-dispatcher.
service ImageBuilderService {
  // BuildImage submits or resumes a custom image build and returns the current
  // build state. Callers poll GetImageBuildStatus until the status reaches
  // READY or FAILED.
  rpc BuildImage(BuildImageRequest) returns (BuildImageResponse);

  // GetImageBuildStatus returns the latest build state for one image ID.
  rpc GetImageBuildStatus(GetImageBuildStatusRequest) returns (GetImageBuildStatusResponse);

  // PrepareLocalFileUpload starts (or short-circuits) an upload of one
  // content-addressed local file for the caller's org. The response is a
  // tagged plan: AlreadyExists when the asset is already cached, or
  // SinglePart with a short-lived presigned PUT URL the SDK uploads the
  // bytes against. Files above S3's single-PUT ceiling (5 GiB) are
  // rejected up front.
  rpc PrepareLocalFileUpload(PrepareLocalFileUploadRequest) returns (PrepareLocalFileUploadResponse);
}

message BuildImageRequest {
  image.v1.ImageSpec image = 1;
}

message BuildImageResponse {
  string image_id = 1;
  image.v1.ImageBuildStatus status = 2;
  string error_message = 3;
}

message GetImageBuildStatusRequest {
  string image_id = 1;
}

message GetImageBuildStatusResponse {
  string image_id = 1;
  image.v1.ImageBuildStatus status = 2;
  string error_message = 3;
}

message PrepareLocalFileUploadRequest {
  // Lowercase 64-char hex sha256 of the file contents.
  string content_sha256 = 1;
  // Total byte length of the file. Used to (a) probe whether the file is
  // already cached, (b) bind Content-Length into the presigned PUT so S3
  // enforces the size at upload time, and (c) reject requests above the
  // local-file size cap up front.
  uint64 content_length = 2;
}

message PrepareLocalFileUploadResponse {
  oneof outcome {
    LocalFileAlreadyExists already_exists = 1;
    SinglePartUploadPlan single_part = 2;
  }
}

// LocalFileAlreadyExists signals that the asset's bytes are already in the
// content-addressed store and the SDK should skip uploading. Empty by design;
// the discriminator is the oneof tag.
message LocalFileAlreadyExists {}

message SinglePartUploadPlan {
  // HTTPS URL the client should issue a PUT request against with the asset
  // bytes as the request body.
  string upload_url = 1;
  // Headers the client MUST send verbatim with the PUT (Content-Length and
  // Content-Type at minimum). Clients that omit these will be rejected.
  map<string, string> required_headers = 2;
  // Unix-epoch second after which upload_url is no longer valid. Purely
  // informational — clients should issue the PUT immediately.
  int64 expires_at_unix = 3;
}
