syntax = "proto3";

package nominal.file_store.v1;

import "google/api/annotations.proto";
import "nominal/protos/file_store/v1/file_store.proto";
import "nominal/protos/gen/v1/alias.proto";

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

// Manages File Store files and revisions.
service InternalFilesService {
  // Gets a file by path.
  rpc GetFile(GetFileRequest) returns (GetFileResponse) {
    option (google.api.http) = {
      post: "/file-store/v1/files/get"
      body: "*"
    };
  }

  // Resolves a potentially-transient file reference to a stable content target.
  rpc ResolveFileRevision(ResolveFileRevisionRequest) returns (ResolveFileRevisionResponse) {
    option (google.api.http) = {
      post: "/file-store/v1/files/resolve"
      body: "*"
    };
  }

  // Streams resolved file contents.
  rpc ReadFile(ReadFileRequest) returns (stream ReadFileResponse);

  // Lists files under a drive path.
  rpc ListFiles(ListFilesRequest) returns (ListFilesResponse) {
    option (google.api.http) = {get: "/file-store/v1/drives/{drive_rid}/files"};
  }

  // Applies file mutations to a managed drive. Virtual drives are read-only.
  rpc ApplyFileChanges(ApplyFileChangesRequest) returns (ApplyFileChangesResponse) {
    option (google.api.http) = {
      post: "/file-store/v1/files/apply-changes"
      body: "*"
    };
  }

  // Lists revisions for a managed file. Virtual drives do not expose file history.
  rpc ListFileRevisions(ListFileRevisionsRequest) returns (ListFileRevisionsResponse) {
    option (google.api.http) = {
      post: "/file-store/v1/file-revisions/list"
      body: "*"
    };
  }

  // Currently admin-only: permanently removes a managed file revision and its backing object.
  rpc PurgeFile(PurgeFileRequest) returns (PurgeFileResponse) {
    option (google.api.http) = {
      post: "/file-store/v1/file-revisions/{file_revision_rid}/purge"
      body: "*"
    };
  }
}

// Object returned by the existing upload flow and committed as a new revision.
message UploadedObjectRef {
  string s3_path = 1;
}

enum FileConflictPolicy {
  // Invalid in requests. The server rejects mutations with an unspecified conflict policy.
  FILE_CONFLICT_POLICY_UNSPECIFIED = 0;
  // Rejects the change if the target, destination, or restore path currently resolves to an active file.
  FILE_CONFLICT_POLICY_FAIL_IF_EXISTS = 1;
  // Put: creates a new revision of the active file currently at the target path. If no active file exists,
  // creates a new active file.
  // Move: marks the destination file removed, then moves the source file to the destination path.
  // Restore: marks the destination file removed, then restores the requested revision at the destination path.
  FILE_CONFLICT_POLICY_REPLACE_IF_EXISTS = 2;
}

message PutFile {
  LogicalPath path = 1;
  UploadedObjectRef object = 2;
  uint64 size_bytes = 3;
  FileConflictPolicy conflict_policy = 4;
  // If set, the server rejects the put unless the current revision at path matches this revision.
  string expected_revision_rid = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message MoveFile {
  LogicalPath source_path = 1;
  LogicalPath destination_path = 2;
  FileConflictPolicy conflict_policy = 3;
  // If set, the server rejects the move unless the current revision at source_path matches this revision.
  string expected_source_revision_rid = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  // If set, the server rejects the move unless the current revision at destination_path matches this revision.
  string expected_destination_revision_rid = 5 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message RemoveFile {
  LogicalPath path = 1;
  // If set, the server rejects the remove unless the current revision at path matches this revision.
  string expected_revision_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message RestoreFile {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  LogicalPath destination_path = 2;
  FileConflictPolicy conflict_policy = 3;
  // If set, the server rejects the restore unless the current revision at destination_path matches this revision.
  string expected_destination_revision_rid = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message FileChange {
  oneof change {
    PutFile put = 1;
    MoveFile move = 2;
    RemoveFile remove = 3;
    RestoreFile restore = 4;
  }
}

message GetFileRequest {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  LogicalPath path = 2;
  bool include_removed = 3;
}

message GetFileResponse {
  LogicalFile file = 1;
}

message ResolveFileRevisionRequest {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  // Revision reference returned by GetFile or ListFiles.
  FileRevisionRef source_ref = 2;
}

message ResolveFileRevisionResponse {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message ReadFileRequest {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message ReadFileResponse {
  bytes content = 1;
}

message ListFilesRequest {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  LogicalPath path_prefix = 2;
  bool recursive = 3;
  bool include_removed = 4;
  optional int32 page_size = 5;
  optional string page_token = 6 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

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

message ApplyFileChangesRequest {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  // At most 1,000 changes per request. Changes are processed in order. Successful earlier changes are visible
  // to later changes in the same batch. Failed changes do not stop later changes.
  repeated FileChange changes = 2;
}

message ApplyFileChangesResponse {
  // One result per requested change, in the same order as ApplyFileChangesRequest.changes.
  repeated FileChangeResult results = 1;
}

message FileChangeSuccess {
  LogicalFile file = 1;
  ManagedFileRevision file_revision = 2;
}
message FileChangeFailure {
  FileStoreError code = 1;
  string message = 2;
}

message FileChangeResult {
  oneof result {
    FileChangeSuccess success = 1;
    FileChangeFailure failure = 2;
  }
}

message ListFileRevisionsRequest {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  string file_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRid"}];
  optional int32 page_size = 3;
  optional string page_token = 4 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.Token"}];
}

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

message PurgeFileRequest {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message PurgeFileResponse {}
