syntax = "proto3";
package volume;
option go_package = "github.com/yaoapp/yao/tai/volume/pb";

// Volume provides bulk file synchronization, real-time filesystem I/O,
// and archive/compression operations.
// Shares gRPC port :19100 with Yao Gateway.
service Volume {

  // --- Bulk Sync ---

  // SyncPush: Yao sends code to Tai (before container start).
  // Bidirectional stream:
  //   1. Yao sends SyncManifest (file list with mtime+size)
  //   2. Tai diffs, replies with SyncDiff (which files to send)
  //   3. Yao sends only needed FileChunks
  //   4. Tai replies with SyncResult
  rpc SyncPush(stream SyncMessage) returns (stream SyncMessage);

  // SyncPull: Yao pulls changes from Tai (after container stop).
  // Yao sends its file manifest; Tai diffs internally and streams back changed files.
  rpc SyncPull(SyncManifest) returns (stream SyncMessage);

  // --- Real-Time FS IO ---

  rpc ReadFile(FSReadRequest) returns (stream FSDataChunk);
  rpc WriteFile(stream FSWriteChunk) returns (FSWriteResponse);
  rpc Stat(FSRequest) returns (FileInfo);
  rpc ListDir(FSRequest) returns (FSListResponse);
  rpc Remove(FSRemoveRequest) returns (FSOpResponse);
  rpc Rename(FSRenameRequest) returns (FSOpResponse);
  rpc MkdirAll(FSRequest) returns (FSOpResponse);
  // Abs: resolve a session-relative path to its absolute path on the host.
  rpc Abs(FSRequest) returns (FSAbsResponse);
  // Copy: copy src to dst within the same workspace (server-side when remote).
  rpc Copy(FSCopyRequest) returns (SyncResult);

  // --- Archive / Compression ---

  rpc Zip(ArchiveRequest) returns (ArchiveResponse);
  rpc Unzip(ArchiveRequest) returns (ArchiveResponse);
  rpc Gzip(ArchiveRequest) returns (ArchiveResponse);
  rpc Gunzip(ArchiveRequest) returns (ArchiveResponse);
  rpc Tar(ArchiveRequest) returns (ArchiveResponse);
  rpc Untar(ArchiveRequest) returns (ArchiveResponse);
  rpc Tgz(ArchiveRequest) returns (ArchiveResponse);
  rpc Untgz(ArchiveRequest) returns (ArchiveResponse);
}

// --- File Metadata ---

message FileInfo {
  string path   = 1;
  int64  size   = 2;
  int64  mtime  = 3;           // unix timestamp (nanoseconds)
  uint32 mode   = 4;
  bool   is_dir = 5;
}

// --- Sync Messages ---

message SyncManifest {
  string session_id       = 1;
  repeated FileInfo files = 2;
  bool   force_full       = 3;  // skip snapshot cache, diff against actual disk
  string remote_path      = 4;  // sub-path within workspace root; empty = root
}

message SyncMessage {
  oneof payload {
    SyncManifest manifest = 1;
    SyncDiff     diff     = 2;
    FileChunk    chunk    = 3;
    SyncResult   result   = 4;
  }
}

message SyncDiff {
  repeated string need_files   = 1;  // paths needing full transfer
  repeated string delete_files = 2;  // paths Tai should delete
}

message FileChunk {
  string    path  = 1;
  ChunkType type  = 2;
  bytes     data  = 3;          // lz4 compressed (V1: always FULL)
  uint32    mode  = 4;          // file mode (first chunk only)
  int64     mtime = 5;          // modification time (first chunk only)
  bool      eof   = 6;

  enum ChunkType {
    FULL   = 0;
    DELTA  = 1;                 // reserved for future rsync delta
    DELETE = 2;
    MKDIR  = 3;
  }
}

message SyncResult {
  int32 files_synced      = 1;
  int64 bytes_transferred = 2;
  int64 duration_ms       = 3;
}

// --- FS IO Messages ---

message FSRequest {
  string session_id = 1;
  string path       = 2;
}

message FSOpResponse {
  bool   ok    = 1;
  string error = 2;
}

message FSAbsResponse {
  string path = 1;              // absolute path on the host filesystem
}

message FSReadRequest {
  string session_id = 1;
  string path       = 2;
}

message FSDataChunk {
  bytes  data  = 1;             // up to 64KB per message
  uint32 mode  = 2;             // first chunk only
  int64  size  = 3;             // total file size (first chunk only)
  int64  mtime = 4;             // modification time (first chunk only)
}

message FSWriteChunk {
  string session_id  = 1;       // first chunk only
  string path        = 2;       // first chunk only
  bytes  data        = 3;
  uint32 mode        = 4;       // first chunk only, 0 = keep existing
  bool   create_dirs = 5;       // auto-create parent directories (first chunk only)
}

message FSWriteResponse {
  int64 size = 1;
}

message FSListResponse {
  repeated FileInfo entries = 1;
}

message FSRemoveRequest {
  string session_id = 1;
  string path       = 2;
  bool   recursive  = 3;       // true = RemoveAll, false = Remove
}

message FSRenameRequest {
  string session_id = 1;
  string old_path   = 2;
  string new_path   = 3;
}

message FSCopyRequest {
  string session_id       = 1;
  string src_path         = 2;
  string dst_path         = 3;
  repeated string excludes = 4;  // glob patterns
  bool force              = 5;   // overwrite even if mtime/size match
}

// --- Archive / Compression Messages ---

message ArchiveRequest {
  string session_id      = 1;
  string src_path        = 2;  // relative to workspace root
  string dst_path        = 3;  // relative to workspace root
  repeated string excludes = 4; // glob patterns (pack ops only)
}

message ArchiveResponse {
  int64 size_bytes  = 1;  // output file size (pack) or total extracted size (unpack)
  int32 files_count = 2;  // number of files processed
}
