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: "*"
    };
  }

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

// A path to place a file at.
message PathTarget {
  LogicalPath path = 1;
  // Reserved for a future, optional conflict policy that applies only to path placement:
  //   FAIL_IF_EXISTS (default, and today's only behavior) — expect no active file at the path.
  //   REPLACE_IF_EXISTS — replace an active file already at the path (append a revision, advance
  //     its head) by path alone, without naming its current head via Destination.file_revision_rid.
  // ConflictPolicy conflict_policy = 2;
}

// Where a change places a file, and the precondition that target must currently satisfy.
message Destination {
  oneof target {
    // Expect no active file at this path; place the file into it (create / move-into / restore-into).
    PathTarget path = 1;
    // Expect a file whose current head is this revision; replace it (append a new revision and advance the head).
    string file_revision_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  }
}

message PutFile {
  UploadedObjectRef object = 1;
  uint64 size_bytes = 2;
  Destination destination = 3;
}

message MoveFile {
  // The file to move, identified by its current head revision; rejected if this is not the current head.
  string source_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  Destination destination = 2;
}

message RemoveFile {
  // The file to soft-delete, identified by its current head revision; rejected if this is not the current head.
  string revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message RestoreFile {
  // Any past revision of a file to reinstate (the file may be active or already removed).
  string restore_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  // If the file owning restore_revision_rid is currently active, the destination must replace its current head
  // (the file_revision_rid arm set to that head); if the file is removed, this may be any active file's `revision_rid`.
  Destination destination = 2;
}

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 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 {}
