syntax = "proto3";

package devvit.plugin.redis;

import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto";

option go_package = "github.snooguts.net/reddit/reddit-devplatform-monorepo/go-common/generated/protos/types/devvit/plugin/redis";
option java_outer_classname = "_Redis";
option java_package = "com.reddit.devvit.plugin.redis";

/**
 * This should match as closely as reasonable to redis's names, arguments, etc.
 *
 * DO NOT CHANGE Redis names.  It's DEL, not DELETE, no matter your preferences.
 *
 * One catch here is that this interface mediates transactions, without exposing
 * the details of connection management.  Therefore, if you're doing an operation
 * in a transaction, two things are different:
 *
 *    1. You should provide a transaction id in your request.
 *    2. You should ignore the response (as it should be a zero value)
 *
 * The Typescript Redis client wrapper is smart enough to handle transactions
 * with a nice chaining API on your behalf.
 */
service RedisAPI {
  // Simple Key-Value operations
  rpc Get(KeyRequest) returns (google.protobuf.StringValue) {}
  rpc GetBytes(KeyRequest) returns (google.protobuf.BytesValue) {}
  rpc Set(SetRequest) returns (google.protobuf.StringValue) {}
  rpc Exists(KeysRequest) returns (ExistsResponse) {}
  rpc Del(KeysRequest) returns (google.protobuf.Int64Value) {}
  rpc Type(KeyRequest) returns (google.protobuf.StringValue) {}
  rpc Rename(RenameRequest) returns (RenameResponse) {}

  // Number operations
  rpc IncrBy(IncrByRequest) returns (google.protobuf.Int64Value) {}

  // Redis Hash operations
  rpc HSet(HSetRequest) returns (google.protobuf.Int64Value) {}
  rpc HGet(HGetRequest) returns (google.protobuf.StringValue) {}
  rpc HMGet(HMGetRequest) returns (RedisValues) {}
  rpc HGetAll(KeyRequest) returns (RedisFieldValues) {}
  rpc HDel(HDelRequest) returns (google.protobuf.Int64Value) {}
  rpc HScan(HScanRequest) returns (HScanResponse);
  rpc HKeys(KeyRequest) returns (KeysResponse);
  rpc HIncrBy(HIncrByRequest) returns (google.protobuf.Int64Value) {}
  rpc HLen(KeyRequest) returns (google.protobuf.Int64Value) {}
  rpc HSetNX(HSetNXRequest) returns (HSetNXResponse) {}

  // Transactions
  rpc Multi(TransactionId) returns (google.protobuf.Empty) {}
  rpc Exec(TransactionId) returns (TransactionResponses) {}
  rpc Discard(TransactionId) returns (google.protobuf.Empty) {}
  rpc Watch(WatchRequest) returns (TransactionId) {}
  rpc Unwatch(TransactionId) returns (google.protobuf.Empty) {}

  // String operations
  rpc GetRange(KeyRangeRequest) returns (google.protobuf.StringValue) {}
  rpc SetRange(SetRangeRequest) returns (google.protobuf.Int64Value) {}
  rpc Strlen(KeyRequest) returns (google.protobuf.Int64Value) {}

  // Batch Key-Value operations
  rpc MGet(KeysRequest) returns (RedisValues) {}
  rpc MSet(KeyValuesRequest) returns (google.protobuf.Empty) {}

  // Key expiration
  rpc Expire(ExpireRequest) returns (google.protobuf.Empty) {}
  rpc ExpireTime(KeyRequest) returns (google.protobuf.Int64Value) {}

  // Sorted sets
  rpc ZAdd(ZAddRequest) returns (google.protobuf.Int64Value) {}
  rpc ZCard(KeyRequest) returns (google.protobuf.Int64Value) {}
  rpc ZRange(ZRangeRequest) returns (ZMembers) {}
  rpc ZRem(ZRemRequest) returns (google.protobuf.Int64Value) {}
  rpc ZRemRangeByLex(ZRemRangeByLexRequest) returns (google.protobuf.Int64Value) {}
  rpc ZRemRangeByRank(ZRemRangeByRankRequest) returns (google.protobuf.Int64Value) {}
  rpc ZRemRangeByScore(ZRemRangeByScoreRequest) returns (google.protobuf.Int64Value) {}
  rpc ZScore(ZScoreRequest) returns (google.protobuf.DoubleValue) {}
  rpc ZRank(ZRankRequest) returns (google.protobuf.Int64Value) {}
  rpc ZIncrBy(ZIncrByRequest) returns (google.protobuf.DoubleValue) {}
  rpc ZScan(ZScanRequest) returns (ZScanResponse);

  // Bitfield
  rpc Bitfield(BitfieldRequest) returns (BitfieldResponse) {}
}

// This key scope determines the key namespacing in Redis storage
// By default, all keys in Redis are namespaced to an installation
// Global redis enables apps to persist and access state across subreddit installations
// Default key format - installation:app:custom-key
// Global redis key format - global:app:custom-key
enum RedisKeyScope {
  INSTALLATION = 0;
  GLOBAL = 1;
}

message TransactionResponses {
  repeated TransactionResponse response = 1;
}

message TransactionResponse {
  oneof value {
    google.protobuf.StringValue str = 1;
    google.protobuf.Empty nil = 2;
    google.protobuf.Int64Value num = 3;
    ZMembers members = 4;
    RedisValues values = 5;
    google.protobuf.DoubleValue dbl = 6;
    google.protobuf.BoolValue bool = 7;
  }
}

message ZRangeRequest {
  KeyRequest key = 1;
  string start = 2;
  string stop = 3;
  bool by_score = 4;
  bool by_lex = 5;
  bool rev = 6;
  int32 offset = 7;
  int32 count = 8;
  optional RedisKeyScope scope = 100;
}

message ZRangeByLexRequest {
  KeyRequest key = 1;
  string min = 2;
  string max = 3;
  optional RedisKeyScope scope = 100;
}

message ZRangeByScoreRequest {
  KeyRequest key = 1;
  double min = 2;
  double max = 3;
  bool with_scores = 4;
  optional RedisKeyScope scope = 100;
}

message ZRemRequest {
  KeyRequest key = 1;
  repeated string members = 2;
  optional RedisKeyScope scope = 100;
}

message ZRemRangeByLexRequest {
  KeyRequest key = 1;
  string min = 2;
  string max = 3;
  optional RedisKeyScope scope = 100;
}

message ZRemRangeByRankRequest {
  KeyRequest key = 1;
  int32 start = 2;
  int32 stop = 3;
  optional RedisKeyScope scope = 100;
}

message ZRemRangeByScoreRequest {
  KeyRequest key = 1;
  double min = 2;
  double max = 3;
  optional RedisKeyScope scope = 100;
}

message ZScoreRequest {
  KeyRequest key = 1;
  string member = 2;
  optional RedisKeyScope scope = 100;
}

message ZRankRequest {
  KeyRequest key = 1;
  string member = 2;
  optional RedisKeyScope scope = 100;
}

message RedisRankScore {
  int64 rank = 1;
  double score = 2;
}

message ZIncrByRequest {
  string key = 1;
  string member = 2;
  double value = 3;
  TransactionId transaction_id = 6; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message KeyRequest {
  string key = 1;
  TransactionId transaction_id = 2; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message IncrByRequest {
  string key = 1;
  int64 value = 2;
  TransactionId transaction_id = 6; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message SetRequest {
  string key = 1;
  string value = 2;
  int64 expiration = 3; // Expiration time in seconds
  bool nx = 4; // Set only if key does not exist
  bool xx = 5; // Set only if key already exists
  TransactionId transaction_id = 6; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message KeyRangeRequest {
  string key = 1;
  int32 start = 2;
  int32 end = 3;
  TransactionId transaction_id = 4; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message SetRangeRequest {
  string key = 1;
  int32 offset = 2;
  string value = 3;
  TransactionId transaction_id = 4; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message KeysRequest {
  repeated string keys = 1;
  TransactionId transaction_id = 2; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message KeysResponse {
  repeated string keys = 1;
}

message ExistsResponse {
  int64 existing_keys = 1;
}

message HGetRequest {
  string key = 1;
  string field = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message HMGetRequest {
  string key = 1;
  repeated string fields = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message HSetRequest {
  string key = 1;
  repeated RedisFieldValue fv = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message HSetNXResponse {
  int64 success = 1;
}

message HDelRequest {
  string key = 1;
  repeated string fields = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message HScanRequest {
  string key = 1;
  uint64 cursor = 2;
  optional string pattern = 3;
  optional int64 count = 4;
  TransactionId transaction_id = 5; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message HScanResponse {
  uint64 cursor = 1;
  repeated RedisFieldValue field_values = 2;
}

message HIncrByRequest {
  string key = 1;
  string field = 2;
  int64 value = 3;
  TransactionId transaction_id = 6; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message RedisFieldValue {
  string field = 1;
  string value = 2;
}

message RedisFieldValues {
  map<string, string> field_values = 1;
}

message KeyValuesRequest {
  repeated RedisKeyValue kv = 1;
  TransactionId transaction_id = 2; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message RedisKeyValue {
  string key = 1;
  string value = 2;
}

message RedisValues {
  repeated google.protobuf.StringValue values = 1;
}

message ExpireRequest {
  string key = 1;
  int32 seconds = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message ZAddRequest {
  string key = 1;
  repeated ZMember members = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message ZScanRequest {
  string key = 1;
  uint64 cursor = 2;
  optional string pattern = 3;
  optional int64 count = 4;
  TransactionId transaction_id = 5; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message ZScanResponse {
  uint64 cursor = 1;
  repeated ZMember members = 2;
}

message ZMembers {
  repeated ZMember members = 1;
}

message ZMember {
  double score = 1;
  string member = 2;
}

message TransactionId {
  string id = 1;
}

message WatchRequest {
  TransactionId transaction_id = 1;
  repeated string keys = 2;
}

message BitfieldRequest {
  string key = 1;
  repeated BitfieldCommand commands = 2;
  TransactionId transaction_id = 3; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message BitfieldCommand {
  oneof command {
    BitfieldGet get = 1;
    BitfieldSet set = 2;
    BitfieldIncrBy incr_by = 3;
    BitfieldOverflow overflow = 4;
  }
}

message BitfieldGet {
  string encoding = 1;
  string offset = 2;
}

message BitfieldSet {
  string encoding = 1;
  string offset = 2;
  string value = 3;
}

message BitfieldIncrBy {
  string encoding = 1;
  string offset = 2;
  string increment = 3;
}

message BitfieldOverflow {
  BitfieldOverflowBehavior behavior = 1;
}

enum BitfieldOverflowBehavior {
  BITFIELD_OVERFLOW_BEHAVIOR_UNSPECIFIED = 0;
  BITFIELD_OVERFLOW_BEHAVIOR_WRAP = 1;
  BITFIELD_OVERFLOW_BEHAVIOR_SAT = 2;
  BITFIELD_OVERFLOW_BEHAVIOR_FAIL = 3;
}

message BitfieldResponse {
  repeated int64 results = 1;
}

message HSetNXRequest {
  string key = 1;
  string field = 2;
  string value = 3;
  TransactionId transaction_id = 4; // Optional transaction id
  optional RedisKeyScope scope = 100;
}

message RenameRequest {
  string key = 1;
  string new_key = 2;
  optional RedisKeyScope scope = 100;
}

message RenameResponse {
  string result = 1;
}
