syntax = "proto3";

package nominal.file_store.v1;

import "google/protobuf/timestamp.proto";
import "nominal/protos/gen/v1/alias.proto";
import "nominal/protos/gen/v1/error.proto";

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

message Attribution {
  google.protobuf.Timestamp time = 1;
  string user_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.authentication.api.UserRid"}];
}

// A workspace-scoped namespace for files.
message Drive {
  string rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  string workspace_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.WorkspaceRid"}];
  string name = 3;
  DriveState state = 4;
  Attribution created = 5;
  DriveKind kind = 6;
}

enum DriveState {
  DRIVE_STATE_UNSPECIFIED = 0;
  DRIVE_STATE_ACTIVE = 1;
  DRIVE_STATE_ARCHIVED = 2;
}

enum DriveKind {
  DRIVE_KIND_UNSPECIFIED = 0;
  DRIVE_KIND_MANAGED = 1;
  DRIVE_KIND_VIRTUAL = 2;
}

// Read-only provider-backed drive extension.
message VirtualDrive {
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  VirtualDriveConnectionDetails connection_details = 2;
  VirtualDriveStatus status = 3;
}

message VirtualDriveStatus {
  VirtualDriveState state = 1;
  google.protobuf.Timestamp last_successful_check_time = 2;
  string message = 3;
}

enum VirtualDriveState {
  VIRTUAL_DRIVE_STATE_UNSPECIFIED = 0;
  VIRTUAL_DRIVE_STATE_ACTIVE = 1;
  VIRTUAL_DRIVE_STATE_AUTH_ERROR = 2;
  VIRTUAL_DRIVE_STATE_UNREACHABLE = 3;
  VIRTUAL_DRIVE_STATE_INVALID_CONFIGURATION = 4;
}

message VirtualDriveConnectionDetails {
  oneof details {
    S3VirtualDriveConnectionDetails s3 = 1;
    GoogleDriveVirtualDriveConnectionDetails google_drive = 2;
  }
}

message S3VirtualDriveConnectionDetails {
  string bucket = 1;
  string prefix = 2;
  // Optional secret containing provider credentials when ambient service credentials are not sufficient.
  string credentials_secret_rid = 3 [(nominal.gen.v1.alias) = {java_name: "io.nominal.secrets.api.SecretRid"}];
}

message GoogleDriveVirtualDriveConnectionDetails {
  // The Google Drive folder whose contents (recursively) back this drive.
  string folder_id = 1;
  // Secret holding the Google service-account JSON used to read the folder (scoped drive.readonly).
  string credentials_secret_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.secrets.api.SecretRid"}];
}

// Drive-relative normalized path, with no leading or trailing slash.
message LogicalPath {
  // Examples:
  // - "data/run-001.csv"
  // - "reports/test-summary.pdf"
  // - "assets/vehicle-42/calibration.json"
  // The empty path identifies the drive root for list requests.
  // Non-empty paths must not start or end with "/", and must not contain empty, ".", or ".." segments.
  string path = 1;
}

// Current listing entry for a logical path in a drive, including the current observed source.
message LogicalFile {
  // Moving-head identifier for this logical file.
  LogicalFileIdentity identity = 1;
  LogicalPath path = 2;
  FileState state = 3;
  // May not be present for files in virtual drives.
  Attribution created = 4;
  uint64 size_bytes = 5;
  Attribution observed = 6;
  FileRevisionRef current_revision = 7;
}

// Uniquely identifies a logical file in a drive, managed or virtual,
// shared across modifications or (for supported providers) path moves.
message LogicalFileIdentity {
  oneof identity {
    ManagedFileIdentity managed = 1;
    VirtualFileIdentity virtual = 2;
  }
}

message ManagedFileIdentity {
  string file_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRid"}];
}

message VirtualFileIdentity {
  oneof kind {
    S3FileIdentity s3 = 1;
    GoogleDriveFileIdentity google_drive = 2;
  }
}

message S3FileIdentity {
  // The virtual drive associated with this file.
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  // S3 object path/key. Note that S3 has no notion of a file identity separate from its key.
  string path = 2;
}

message GoogleDriveFileIdentity {
  string file_id = 1;
}

enum FileState {
  FILE_STATE_UNSPECIFIED = 0;
  FILE_STATE_ACTIVE = 1;
  FILE_STATE_REMOVED = 2;
}

// A reference to a file's state as observed at a given time.
// This may correspond to a managed file revision or a (potentially-transient) virtual file revision.
// This doubles as the natural key / idempotency key for a "resolved" revision.
message FileRevisionRef {
  oneof reference {
    ManagedFileRevisionRef managed = 1;
    VirtualFileRevisionRef virtual = 2;
  }
}

message ManagedFileRevisionRef {
  // Exact managed revision backing this source.
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
}

message VirtualFileRevisionRef {
  oneof kind {
    S3FileRevisionRef s3 = 1;
    GoogleDriveFileRevisionRef google_drive = 2;
  }
}

message S3FileRevisionRef {
  // The virtual drive associated with this file revision.
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  // S3 object path/key.
  string path = 2;
  // The "ETag" associated with the file when this revision was observed. This uniquely identifies the version of
  // the file when a version_id is not available (non-versioned buckets).
  string etag = 3;
  // Present when the upstream bucket supports versioning.
  string version_id = 4;
}

message GoogleDriveFileRevisionRef {
  // The virtual drive associated with this file revision.
  string drive_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveRid"}];
  // The ID of the file in the backing Google Drive.
  string file_id = 2;
  // The `version` field corresponding to this revision in the backing Google Drive.
  string version = 3;
}

// Linear history entry for a managed File Store file.
// Revisions can represent content changes or non-content events such as moves, removes, and restores.
message ManagedFileRevision {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  string file_rid = 2 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRid"}];
  // Path where this revision became current.
  LogicalPath path = 3;
  uint32 revision_number = 4;
  uint64 size_bytes = 5;
  Attribution created = 6;
}

// Persistent entry for a virtual file as observed at a given time.
message VirtualFileRevision {
  string file_revision_rid = 1 [(nominal.gen.v1.alias) = {java_name: "io.nominal.api.rids.DriveFileRevisionRid"}];
  // Identifier shared by any revisions of the same virtual file.
  VirtualFileIdentity file_identity = 2;
  // Path in the virtual drive where this revision was observed.
  LogicalPath path = 3;
  uint64 size_bytes = 4;
  Attribution observed = 5;
}

message Directory {
  LogicalPath path = 1;
}

message FileEntry {
  oneof entry {
    LogicalFile file = 1;
    Directory directory = 2;
  }
}

enum FileStoreError {
  FILE_STORE_ERROR_UNSPECIFIED = 0;
  FILE_STORE_ERROR_DRIVE_NOT_FOUND = 1 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Drive not found"
  }];
  FILE_STORE_ERROR_FILE_NOT_FOUND = 2 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "File not found"
  }];
  FILE_STORE_ERROR_FILE_REVISION_NOT_FOUND = 3 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "File revision not found"
  }];
  FILE_STORE_ERROR_PATH_ALREADY_EXISTS = 4 [(nominal.gen.v1.error) = {
    code: "ALREADY_EXISTS"
    message: "Path already exists"
  }];
  FILE_STORE_ERROR_INVALID_LOGICAL_PATH = 5 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid logical path"
  }];
  FILE_STORE_ERROR_PERMISSION_DENIED = 6 [(nominal.gen.v1.error) = {
    code: "PERMISSION_DENIED"
    message: "Permission denied"
  }];
  FILE_STORE_ERROR_REVISION_PRECONDITION_FAILED = 7 [(nominal.gen.v1.error) = {
    code: "FAILED_PRECONDITION"
    message: "File revision precondition failed"
  }];
  FILE_STORE_ERROR_VIRTUAL_DRIVE_NOT_FOUND = 8 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Virtual drive not found"
  }];
  FILE_STORE_ERROR_INVALID_VIRTUAL_DRIVE_CONFIGURATION = 9 [(nominal.gen.v1.error) = {
    code: "INVALID_ARGUMENT"
    message: "Invalid virtual drive configuration"
  }];
  FILE_STORE_ERROR_READ_ONLY_DRIVE = 10 [(nominal.gen.v1.error) = {
    code: "FAILED_PRECONDITION"
    message: "Drive is read-only"
  }];
  FILE_STORE_ERROR_FILE_HISTORY_NOT_AVAILABLE = 11 [(nominal.gen.v1.error) = {
    code: "FAILED_PRECONDITION"
    message: "File history is not available"
  }];
  // The upstream provider service is unreachable or failing (network, outage, throttling). Transient
  // and possibly retryable — distinct from UPSTREAM_FILE_NOT_FOUND, where the provider is reachable
  // but no longer has the referenced file.
  FILE_STORE_ERROR_UPSTREAM_UNAVAILABLE = 12 [(nominal.gen.v1.error) = {
    code: "UNAVAILABLE"
    message: "Upstream file provider is unavailable"
  }];
  // The revision record still exists, but its backing bytes have been reclaimed (by retention or an
  // admin purge) and can no longer be served — HTTP "410 Gone" in spirit, which google.rpc.Code
  // can't express, so NOT_FOUND is the closest coarse status. The distinct error code (and a proxy
  // that can surface 410) preserves the meaning, separate from FILE_REVISION_NOT_FOUND, where no
  // such revision exists at all.
  FILE_STORE_ERROR_REVISION_BYTES_RECLAIMED = 13 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "File revision bytes have been reclaimed"
  }];
  // The upstream provider is reachable but no longer has the referenced file/version at the pinned
  // fingerprint (deleted, the version expired, or moved). Permanent for this revision, unlike the
  // transient UPSTREAM_UNAVAILABLE.
  FILE_STORE_ERROR_UPSTREAM_FILE_NOT_FOUND = 14 [(nominal.gen.v1.error) = {
    code: "NOT_FOUND"
    message: "Upstream provider no longer has the referenced file"
  }];
}
