/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
syntax = "proto2";

package fluss;
option java_package = "org.apache.fluss.rpc.messages";
option optimize_for = LITE_RUNTIME;

/**
* NOTICE: To ensure a smooth upgrade to Proto3 syntax while maintaining backward compatibility in
* the future, please adhere to the following limitations:
* 1. DO NOT USE "default" keyword, proto3 removes the support of "default"
* 2. DO NOT USE "enum" type
 */

// the message indicates a failed request of a server failure.
message ErrorResponse {
  required int32 error_code = 1;
  optional string error_message = 2;
}

message ApiVersionsRequest {
  required string client_software_name = 1;
  required string client_software_version = 2;
}

message ApiVersionsResponse {
  repeated PbApiVersion api_versions = 1;
  optional int32 server_type = 2;
}

// get schema request and response
message GetTableSchemaRequest {
  required PbTablePath table_path = 1;
  optional int32 schema_id = 2;
}

message GetTableSchemaResponse {
  required int32 schema_id = 1;
  required bytes schema_json = 2;
}

// create database request and response
message CreateDatabaseRequest {
  required string database_name = 1;
  required bool ignore_if_exists = 2;
  optional bytes database_json = 3;
}

message CreateDatabaseResponse {
}

// get table request and response
message GetDatabaseInfoRequest {
  required string database_name = 1;
}

message GetDatabaseInfoResponse {
  required bytes database_json = 3;
  required int64 created_time = 4;
  required int64 modified_time = 5;
}

// drop database request and response
message DropDatabaseRequest {
  required string database_name = 1;
  required bool ignore_if_not_exists = 2;
  required bool cascade = 3;
}
message DropDatabaseResponse {
}

// database exists request and response
message DatabaseExistsRequest {
  required string database_name = 1;
}
message DatabaseExistsResponse {
  required bool exists = 1;
}

// list databases request and response
message ListDatabasesRequest {
  optional bool include_summary = 1;
}

message ListDatabasesResponse {
  repeated string database_name = 1;
  repeated PbDatabaseSummary database_summary = 2;
}

// create table request and response
message CreateTableRequest {
  required PbTablePath table_path = 1;
  required bytes table_json = 2;
  required bool ignore_if_exists = 3;
}

message CreateTableResponse {
}

// alter table request and response
message AlterTableRequest {
  required PbTablePath table_path = 1;
  required bool ignore_if_not_exists = 2;
  repeated PbAlterConfig config_changes = 3;
  repeated PbAddColumn add_columns = 4;
  repeated PbDropColumn drop_columns = 5;
  repeated PbRenameColumn rename_columns = 6;
  repeated PbModifyColumn modify_columns = 7;
}

message AlterTableResponse {
}



// get table request and response
message GetTableInfoRequest {
  required PbTablePath table_path = 1;
}

message GetTableInfoResponse {
  required int64 table_id = 1;
  required int32 schema_id = 2;
  required bytes table_json = 3;
  required int64 created_time = 4;
  required int64 modified_time = 5;
}

// list tables request and response
message ListTablesRequest {
  required string database_name = 1;
}

message ListTablesResponse {
  repeated string table_name = 1;
}

// drop table request and response
message DropTableRequest {
  required PbTablePath table_path = 1;
  required bool ignore_if_not_exists = 2;
}

message DropTableResponse {
}

// table exists request and response
message TableExistsRequest {
  required PbTablePath table_path = 1;
}

message TableExistsResponse {
  required bool exists = 1;
}

// metadata request and response, request send from client to each server.
message MetadataRequest {
  repeated PbTablePath table_path = 1;
  repeated PbPhysicalTablePath partitions_path = 2;

  // note: currently, we assume the partition ids must belong to the table_paths in the
  // metadata request
  // todo: we won't need the assumption after we introduce metadata cache in server
  repeated int64 partitions_id = 3 [packed = true];
}

message MetadataResponse {
  optional PbServerNode coordinator_server = 1;
  repeated PbServerNode tablet_servers = 2;
  repeated PbTableMetadata table_metadata = 3;
  repeated PbPartitionMetadata partition_metadata = 4;
}

// update metadata request and response, only send between server.
message UpdateMetadataRequest {
  optional PbServerNode coordinator_server = 1;
  repeated PbServerNode tablet_servers = 2;
  repeated PbTableMetadata table_metadata = 3;
  repeated PbPartitionMetadata partition_metadata = 4;
  optional int32 coordinator_epoch = 5;
}

message UpdateMetadataResponse {
}

// produce log request and response
message ProduceLogRequest {
  required int32 acks = 1;
  required int64 table_id = 2;
  required int32 timeout_ms = 3;
  repeated PbProduceLogReqForBucket buckets_req = 4;
}

message ProduceLogResponse {
  repeated PbProduceLogRespForBucket buckets_resp = 1;
}

// fetch log request and response
message FetchLogRequest {
  required int32 follower_server_id = 1;  // value -1 indicate the request from client.
  required int32 max_bytes = 2;
  repeated PbFetchLogReqForTable tables_req = 3;
  optional int32 max_wait_ms = 4;
  optional int32 min_bytes = 5;
}

message FetchLogResponse {
  repeated PbFetchLogRespForTable tables_resp = 1;
}

// Aggregation mode constants for write operations (using int32 instead of enum for proto3 compatibility)
// AGG_MODE_AGGREGATE = 0: Data is aggregated through server-side merge engine (default)
// AGG_MODE_OVERWRITE = 1: Bypass merge engine, directly replace values (for undo recovery)
// AGG_MODE_LOCAL_AGGREGATE = 2: Client-side local aggregation (reserved for future implementation)

// put kv request and response
message PutKvRequest {
  required int32 acks = 1;
  required int64 table_id = 2;
  required int32 timeout_ms = 3;
  // the indexes for the columns to write,
  // if empty, means write all columns
  repeated int32 target_columns = 4 [packed = true];
  repeated PbPutKvReqForBucket buckets_req = 5;
  // Aggregation mode for this request (see AGG_MODE_* constants above)
  // 0 = AGGREGATE (default), 1 = OVERWRITE, 2 = LOCAL_AGGREGATE (not yet supported)
  optional int32 agg_mode = 6;
}

message PutKvResponse {
  repeated PbPutKvRespForBucket buckets_resp = 1;
}

// lookup request and response
message LookupRequest {
  required int64 table_id = 1;
  repeated PbLookupReqForBucket buckets_req = 2;
  optional bool insert_if_not_exists = 3;
  optional int32 acks = 4;
  optional int32 timeout_ms = 5;
}

message LookupResponse {
  repeated PbLookupRespForBucket buckets_resp = 1;
}

// Prefix Lookup request and response
message PrefixLookupRequest {
  required int64 table_id = 1;
  repeated PbPrefixLookupReqForBucket buckets_req = 2;
}

message PrefixLookupResponse {
  repeated PbPrefixLookupRespForBucket buckets_resp = 1;
}


// limit scan request and response
message LimitScanRequest {
  required int64 table_id = 2;
  optional int64 partition_id = 3;
  required int32 bucket_id = 4;
  required int32 limit = 5;
}

message LimitScanResponse{
  optional int32 error_code = 1;
  optional string error_message = 2;
  // flag to indicate the table type
  optional bool is_log_table = 3;
  // LogRecordBatch if is_log_table is true, otherwise KvRecordBatch
  optional bytes records = 4;
}


// Get table statistics request and response.
// Sent to TabletServer to get per-bucket statistics.
message GetTableStatsRequest {
  required int64 table_id = 1;
  repeated PbTableStatsReqForBucket buckets_req = 2;
  // Column indexes for column-level stats (e.g., ndv, sum).
  // If empty, column stats are not collected.
  // Reserved for future use.
  repeated int32 target_columns = 3 [packed = true];
}

message GetTableStatsResponse {
  repeated PbTableStatsRespForBucket buckets_resp = 1;
}

// notify bucket leader and isr request
message NotifyLeaderAndIsrRequest {
  required int32 coordinator_epoch = 1;
  repeated PbNotifyLeaderAndIsrReqForBucket notify_buckets_leader_req = 2;
}

// response for notify bucket leader and isr request
message NotifyLeaderAndIsrResponse {
  repeated PbNotifyLeaderAndIsrRespForBucket notify_buckets_leader_resp = 1;
}

// stop bucket replica request
message StopReplicaRequest {
  required int32 coordinator_epoch = 1;
  repeated PbStopReplicaReqForBucket stop_replicas_req = 2;
}

// response for stop bucket replica request
message StopReplicaResponse {
  repeated PbStopReplicaRespForBucket stop_replicas_resp = 1;
}

// adjust isr request and response
message AdjustIsrRequest {
  required int32 serverId = 1;
  repeated PbAdjustIsrReqForTable tables_req = 2;
}

message AdjustIsrResponse {
  repeated PbAdjustIsrRespForTable tables_resp = 1;
}

// list offsets request and response
message ListOffsetsRequest {
  required int32 follower_server_id = 1;  // value -1 indicate the request from client.
  required int32 offset_type = 2; // value can be 0,1,2 (see ListOffsetsParam for more details)
  required int64 table_id = 3;
  optional int64 partition_id = 4;
  repeated int32 bucket_id = 5 [packed = true]; // it is recommended to use packed for repeated numerics to get more efficient encoding
  optional int64 startTimestamp = 6;
}
message ListOffsetsResponse {
  repeated PbListOffsetsRespForBucket buckets_resp = 1;
}

// commit kv snapshot request and response
message CommitKvSnapshotRequest {
  required bytes completed_snapshot = 1;
  required int32 coordinator_epoch = 2;
  required int32 bucket_leader_epoch = 3;
}

message CommitKvSnapshotResponse {
}

// notify the log offset about kv snapshot
message NotifyKvSnapshotOffsetRequest {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
  required int32 coordinator_epoch = 4;
  required int64 min_retain_offset = 5;
}

message NotifyKvSnapshotOffsetResponse {
}

message GetLatestKvSnapshotsRequest {
  required PbTablePath table_path = 1;
  optional string partition_name = 2;
}

message GetLatestKvSnapshotsResponse {
  required int64 table_id = 1;
  // null if it is a non-partitioned table, otherwise, it must be not null
  optional int64 partition_id = 2;
  repeated PbKvSnapshot latest_snapshots = 3;
}

message GetKvSnapshotMetadataRequest {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
  required int64 snapshot_id = 4;
}

message GetKvSnapshotMetadataResponse {
  required int64 log_offset = 1;
  repeated PbRemotePathAndLocalFile snapshot_files = 2;
}

message AcquireKvSnapshotLeaseRequest {
  required string lease_id = 1;
  required int64 lease_duration_ms = 2;
  repeated PbKvSnapshotLeaseForTable snapshots_to_lease = 3;
}

message AcquireKvSnapshotLeaseResponse {
  repeated PbKvSnapshotLeaseForTable unavailable_snapshots = 1;
}

message ReleaseKvSnapshotLeaseRequest {
  required string lease_id = 1;
  repeated PbTableBucket buckets_to_release = 2;
}

message ReleaseKvSnapshotLeaseResponse {}

message DropKvSnapshotLeaseRequest {
  required string lease_id = 1;
}

message DropKvSnapshotLeaseResponse {}

message GetLakeSnapshotRequest {
  required PbTablePath table_path = 1;
  optional int64 snapshot_id = 2; // If not set, returns the latest snapshot
  optional bool readable = 3; // If true, returns the latest readable snapshot
}

message GetLakeSnapshotResponse {
  required int64 table_id = 1;
  required int64 snapshotId = 2;
  repeated PbLakeSnapshotForBucket bucket_snapshots = 3;
}

message GetFileSystemSecurityTokenRequest {
}

message GetFileSystemSecurityTokenResponse {
  required string schema = 1;
  required bytes token = 2;
  optional int64 expiration_time = 3;
  repeated PbKeyValue addition_info = 4;
}

// init writer request and response
message InitWriterRequest {
  repeated PbTablePath table_path = 1;
}

message InitWriterResponse {
  required int64 writer_id = 1;
}

message ListPartitionInfosRequest {
  required PbTablePath table_path = 1;
  optional PbPartitionSpec partial_partition_spec = 2;
}

message ListPartitionInfosResponse {
  repeated PbPartitionInfo partitions_info = 1;
}

// create partition request and response
message CreatePartitionRequest {
  required PbTablePath table_path = 1;
  required PbPartitionSpec partition_spec = 2;
  required bool ignore_if_not_exists = 3;
}

message CreatePartitionResponse {}

// drop partition request and response
message DropPartitionRequest {
  required PbTablePath table_path = 1;
  required PbPartitionSpec partition_spec = 2;
  required bool ignore_if_not_exists = 3;
}

message DropPartitionResponse {}

// commit remote log manifest request and response
message CommitRemoteLogManifestRequest {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
  required string remote_log_manifest_path = 4;
  required int64 remote_log_start_offset = 5;
  required int64 remote_log_end_offset = 6;
  required int32 coordinator_epoch = 7;
  required int32 bucket_leader_epoch = 8;
}

message CommitRemoteLogManifestResponse {
  required bool commitSuccess = 1;
}

// notify remote log offsets request and response
message NotifyRemoteLogOffsetsRequest {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
  required int32 coordinator_epoch = 4;
  required int64 remote_start_offset = 5;
  required int64 remote_end_offset = 6;
}

message NotifyRemoteLogOffsetsResponse {
}

message PrepareLakeTableSnapshotRequest {
  repeated PbTableOffsets bucket_offsets = 1;
  // If true, ignore previous table offsets and do not merge with existing lake table offsets.
  // If false (default), the system will merge the provided bucket offsets with the previous
  // lake table offsets to ensure continuity. This is useful when you want to replace all offsets
  // instead of merging with existing ones.
  optional bool ignore_previous_table_offsets = 2;
}

message PrepareLakeTableSnapshotResponse {
  repeated PbPrepareLakeTableRespForTable prepare_lake_table_resp = 1;
}

message CommitLakeTableSnapshotRequest {
  // Deprecated: PbLakeTableSnapshotInfo is no longer used for committing lake table snapshots.
  // Currently, it is only used to allow the coordinator to notify tablet servers about the current
  // synchronized log end offsets, which are then reported to metrics. In the future, we plan to
  // have tiering directly report to metrics, and this field will be fully removed.
  // Still reserve it for protocol compatibility.
  repeated PbLakeTableSnapshotInfo tables_req = 1;
  // The metadata for lake table snapshots to be committed. Each entry contains the table ID,
  // snapshot ID, and the file paths where the snapshot data (containing bucket log end offset
  // information) is stored. The tiered_snapshot_file_path points to the file storing tiered log
  // end offsets, while readable_snapshot_file_path (if present) points to the file storing readable
  // log end offsets.
  repeated PbLakeTableSnapshotMetadata lake_table_snapshot_metadata = 2;
}

message CommitLakeTableSnapshotResponse {
  repeated PbCommitLakeTableSnapshotRespForTable table_resp = 1;
}

message PbCommitLakeTableSnapshotRespForTable {
  optional int32 error_code = 1;
  optional string error_message = 2;
  required int64 table_id = 3;
}

message NotifyLakeTableOffsetRequest {
  required int32 coordinator_epoch = 1;
  repeated PbNotifyLakeTableOffsetReqForBucket notify_buckets_req = 2;
}

message PbNotifyLakeTableOffsetReqForBucket {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
  required int64 snapshot_id = 4;
  optional int64 log_start_offset = 5;
  optional int64 log_end_offset = 6;
  optional int64 max_timestamp = 7;
}

message NotifyLakeTableOffsetResponse {
}

message AuthenticateRequest {
  required string protocol = 1;
  required bytes token = 2;
}

message AuthenticateResponse {
  optional bytes challenge = 1;
}

// acl related requests and responses
message ListAclsRequest{
  required PbAclFilter acl_filter = 1;
}

message ListAclsResponse{
  repeated PbAclInfo acl = 1;
}

message CreateAclsRequest{
  repeated PbAclInfo acl = 1;
}

message CreateAclsResponse{
  repeated PbCreateAclRespInfo aclRes = 1;
}

message DropAclsRequest{
  repeated PbAclFilter acl_filter = 1;
}

message DropAclsResponse{
  repeated PbDropAclsFilterResult filter_results = 1;
}

message LakeTieringHeartbeatRequest {
  repeated PbHeartbeatReqForTable tiering_tables = 1;
  repeated PbHeartbeatReqForTable finished_tables = 2;
  repeated PbHeartbeatReqForTable failed_tables = 3;
  // whether to request a table
  optional bool request_table = 4;
  // Table IDs that were force finished due to reaching the maximum tiering duration.
  // When a table's tiering operation exceeds the max duration (data lake freshness),
  // it will be force finished to prevent it from blocking other tables' tiering operations.
  // These table IDs must also appear in finished_tables. The coordinator uses this field to
  // distinguish between normally finished tables and force finished tables.
  repeated int64 force_finished_tables = 5;
}

message LakeTieringHeartbeatResponse {
  // coordinator epoch
  required int32 coordinator_epoch = 1;
  // the returned table to tier, empty when no table is needed by the lake tiering service
  optional PbLakeTieringTableInfo tiering_table = 2;
  repeated PbHeartbeatRespForTable tiering_table_resp = 3;
  repeated PbHeartbeatRespForTable finished_table_resp = 4;
  repeated PbHeartbeatRespForTable failed_table_resp = 5;
}

message ControlledShutdownRequest {
  required int32 tablet_server_id = 1;
  required int32 tablet_server_epoch = 2;
}

message ControlledShutdownResponse {
  repeated PbTableBucket remaining_leader_buckets = 1;
}

message DescribeClusterConfigsRequest{
}


message DescribeClusterConfigsResponse{
  repeated PbDescribeConfig configs = 1;
}

message AlterClusterConfigsRequest{
  repeated PbAlterConfig alter_configs = 1;
}

message AlterClusterConfigsResponse{
}

message AddServerTagRequest {
  repeated int32 server_ids = 1 [packed = true];
  required int32 server_tag = 2;
}

message AddServerTagResponse {
}

message RemoveServerTagRequest {
  repeated int32 server_ids = 1 [packed = true];
  required int32 server_tag = 2;
}

message RemoveServerTagResponse {
}

message RebalanceRequest {
  repeated int32 goals = 1 [packed = true];
}

message RebalanceResponse {
  required string rebalance_id = 1;
}

message ListRebalanceProgressRequest {
  optional string rebalance_id = 1;
}

message ListRebalanceProgressResponse {
  optional string rebalance_id = 1;
  optional int32 rebalance_status = 2;
  repeated PbRebalanceProgressForTable table_progress = 3;
}

message CancelRebalanceRequest {
  optional string rebalance_id = 1;
}

message CancelRebalanceResponse {
}

// ------------------------------------------------------------------------------------------
// Producer Offset Management
// ------------------------------------------------------------------------------------------

// Register producer offsets request and response
message RegisterProducerOffsetsRequest {
  required string producer_id = 1;
  repeated PbProducerTableOffsets table_offsets = 2;
  optional int64 ttl_ms = 3;
}

message RegisterProducerOffsetsResponse {
  // Result of registration: 0 = CREATED (new snapshot), 1 = ALREADY_EXISTS (snapshot existed)
  optional int32 result = 1;
}

// Get producer offsets request and response
message GetProducerOffsetsRequest {
  required string producer_id = 1;
}

message GetProducerOffsetsResponse {
  optional string producer_id = 1;
  optional int64 expiration_time = 2;
  repeated PbProducerTableOffsets table_offsets = 3;
}

// Delete producer offsets request and response
message DeleteProducerOffsetsRequest {
  required string producer_id = 1;
}

message DeleteProducerOffsetsResponse {
}



// --------------- Inner classes ----------------
message PbApiVersion {
  required int32 api_key = 1;
  required int32 min_version = 2;
  required int32 max_version = 3;
}

message PbTablePath {
  required string database_name = 1;
  required string table_name = 2;
}

message PbPhysicalTablePath {
  required string database_name = 1;
  required string table_name = 2;
  optional string partition_name = 3;
}

// For MetadataResponse, host and port are still used for all versions.
// For UpdateMetadataRequest,
//   * versions <= 0.6: host and port are used.
//   * versions >= 0.7: listeners is used to replace host and port.
// For MetadataResponse and UpdateMetadataRequest: Fluss versions >= 0.7: we add rack for each tabletServer
message PbServerNode {
  required int32 node_id = 1;
  required string host = 2;
  required int32 port = 3;
  optional string listeners = 4;
  optional string rack = 5;
}

message PbTableMetadata {
  required PbTablePath table_path = 1;
  required int64 table_id = 2;
  required int32 schema_id = 3;
  // TODO Starting from version 0.9, fluss-client no longer get table info from metadata response. However, for backward
  //  compatibility, we will still retain the field in the request. Once metadataV2 is introduced, table info will be
  //  officially omitted from the new metadata response.
  required bytes table_json = 4;
  repeated PbBucketMetadata bucket_metadata = 5;
  required int64 created_time = 6;
  required int64 modified_time = 7;

  // TODO add a new filed 'deleted_table' to indicate this table is deleted in UpdateMetadataRequest.
  // trace by: https://github.com/apache/fluss/issues/981
}

message PbPartitionMetadata {
  required int64 table_id = 1;
  // the partition name and id for the partition
  required string partition_name = 2;
  required int64 partition_id = 3;
  repeated PbBucketMetadata bucket_metadata = 4;
}

message PbBucketMetadata {
  required int32 bucket_id = 1;
  // optional as some time the leader may not elected yet
  optional int32 leader_id = 2;
  repeated int32 replica_id = 3 [packed = true];
  // TODO: Add isr here.
  optional int32 leader_epoch = 4;
}

message PbProduceLogReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  required bytes records = 3;
}

message PbProduceLogRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  optional int64 base_offset = 5;
}

message PbFetchLogReqForTable {
  required int64 table_id = 1;
  required bool projection_pushdown_enabled = 2;
  repeated int32 projected_fields = 3 [packed = true];
  repeated PbFetchLogReqForBucket buckets_req = 4;
}

message PbFetchLogReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  // TODO leader epoch
  required int64 fetch_offset = 3;
  required int32 max_fetch_bytes = 4;
}

message PbFetchLogRespForTable {
  required int64 table_id = 1;
  repeated PbFetchLogRespForBucket buckets_resp = 2;
}
message PbFetchLogRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  optional int64 high_watermark = 5;
  optional int64 log_start_offset = 6; // TODO now we don't introduce log start offset, but remain it in protobuf
  optional PbRemoteLogFetchInfo remote_log_fetch_info = 7;
  optional bytes records = 8;
}

message PbPutKvReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  required bytes records = 3;
}

message PbPutKvRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  // the log end offset (LEO) of the changelog after this write
  // this is the offset of the next record to be written, used for exactly-once semantics
  optional int64 log_end_offset = 5;
}

message PbLookupReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  repeated bytes keys = 3;
}

message PbLookupRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  repeated PbValue values = 5;
}

message PbValue {
  // optional, if empty, means no value
  optional bytes values = 1;
}

message PbValueList {
  repeated bytes values = 1;
}

message PbPrefixLookupReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  repeated bytes keys = 3;
}

message PbPrefixLookupRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  repeated PbValueList value_lists = 5;
}

message PbTableBucket {
  required int64 table_id = 1;
  optional int64 partition_id = 2;
  required int32 bucket_id = 3;
}

message PbAdjustIsrReqForTable {
  required int64 table_id = 1;
  repeated PbAdjustIsrReqForBucket buckets_req = 2;
}


message PbAdjustIsrReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  required int32 leader_epoch = 3;
  repeated int32 new_isr = 4 [packed = true];
  required int32 coordinator_epoch = 5;
  required int32 bucket_epoch = 6;
}

message PbAdjustIsrRespForTable {
  required int64 table_id = 1;
  repeated PbAdjustIsrRespForBucket buckets_resp = 2;
}

message PbAdjustIsrRespForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 error_code = 3;
  optional string error_message = 4;
  optional int32 leader_id = 5;
  optional int32 leader_epoch = 6;
  repeated int32 isr = 7 [packed = true];
  optional int32 bucket_epoch = 8;
  optional int32 coordinator_epoch = 9;
}

message PbListOffsetsRespForBucket {
  required int32 bucket_id = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;
  optional int64 offset = 4;
}

message PbNotifyLeaderAndIsrReqForBucket {
  // we need table path for tablet server to create data dir for the bucket,
  // although tablet server don't need it after it has created data dir.
  // For simplicity, we always pass table path.
  required PbPhysicalTablePath physical_table_path = 1;
  required PbTableBucket table_bucket = 2;
  required int32 leader = 3;
  required int32 leader_epoch = 4;
  repeated int32 replicas = 5 [packed = true];
  repeated int32 isr = 6 [packed = true];
  required int32 bucket_epoch = 7;
}

message PbNotifyLeaderAndIsrRespForBucket {
  required PbTableBucket table_bucket = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;
}

message PbStopReplicaReqForBucket {
  required PbTableBucket table_bucket = 1;
  required int32 leader_epoch = 2;
  // delete means remove local replica data (i.e., data stored on the current node).
  required bool delete = 3;
  // deleteRemote means remove remote replica data (e.g., data in object storage) and was introduced in v0.9.
  // For backward compatibility, if a request does not include the deleteRemote flag, the system treats delete as
  // deleteRemote (i.e., it falls back to remote deletion). This ensures older CoordinatorServer continues to function
  // correctly with newer TabletServers.
  optional bool deleteRemote = 4;
}

message PbStopReplicaRespForBucket {
  required PbTableBucket table_bucket = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;
}

message PbKvSnapshot {
  required int32 bucket_id = 1;
  // null if there is no snapshot for this bucket
  optional int64 snapshot_id = 2;
  // null if there is no snapshot for this bucket, then read from EARLIEST
  optional int64 log_offset = 3;
}

message PbLakeSnapshotForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int64 log_offset = 3;
  optional string partition_name = 4;
}

message PbRemotePathAndLocalFile {
  required string remote_path = 1;
  required string local_file_name = 2;
}

message PbKeyValue {
  required string key = 1;
  required string value = 2;
}

message PbRemoteLogFetchInfo {
  required string remote_log_tablet_dir = 1;
  optional string partition_name = 2;
  repeated PbRemoteLogSegment remote_log_segments = 3;
  optional int32 first_start_pos = 4;
}

message PbRemoteLogSegment {
  required string remote_log_segment_id = 1;
  required int64 remote_log_start_offset = 2;
  required int64 remote_log_end_offset = 3;
  required int32 segment_size_in_bytes = 4;
  optional int64 max_timestamp = 5;
}

message PbPartitionInfo {
  required int64 partition_id = 1;
  required PbPartitionSpec partition_spec = 2;
}

message PbPartitionSpec {
  repeated PbKeyValue partition_key_values = 1;
}

message PbCreateAclRespInfo {
  required PbAclInfo acl = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;
}

message PbAclInfo {
  required string resource_name = 1;
  required int32 resource_type = 2;
  required string principal_name = 3;
  required string principal_type = 4;
  required string host = 5;
  required int32 operation_type = 6;
  required int32 permission_type = 7;
}

message PbAclFilter {
  optional string resource_name = 1;
  required int32 resource_type = 2;
  optional string principal_name = 3;
  optional string principal_type = 4;
  optional string host = 5;
  required int32 operation_type = 6;
  required int32 permission_type = 7;
}

message PbDropAclsFilterResult {
  repeated PbDropAclsMatchingAcl matching_acls = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;

}

message PbDropAclsMatchingAcl {
  required PbAclInfo acl = 1;
  optional int32 error_code = 2;
  optional string error_message = 3;
}

message PbLakeTieringTableInfo {
  required int64 table_id = 1;
  required PbTablePath table_path = 2;
  required int64 tiering_epoch = 3;
}

message PbHeartbeatReqForTable {
  required int64 table_id = 1;
  // the coordinator epoch when the table is assigned to be tiering service
  required int32 coordinator_epoch = 2;
  // the tiering epoch when the table is assigned to be tiering service
  required int64 tiering_epoch = 3;
}

message PbHeartbeatRespForTable {
  required int64 table_id = 1;
  optional ErrorResponse error = 2;
}

message PbAlterConfig {
  required string config_key = 1;
  optional string config_value = 2;
  required int32 op_type = 3; // SET=0, DELETE=1, APPEND=2, SUBTRACT=3
}

message PbAddColumn {
  required string column_name = 1;
  required bytes data_type_json = 2;
  optional string comment = 3;
  required int32 column_position_type = 4; // LAST=0,FIRST=1,AFTER=3
}

message PbDropColumn {
  required string column_name = 1;
}

message PbRenameColumn {
  required string old_column_name = 1;
  required string new_column_name = 2;
}

message PbModifyColumn {
  required string column_name = 1;
  optional bytes data_type_json = 2;
  optional string comment = 3;
  optional int32 column_position_type = 4; // LAST=0,FIRST=1,AFTER=3
}

message PbDescribeConfig {
  required string config_key = 1;
  optional string config_value = 2;
  required string config_source = 3;
}

message PbRebalancePlanForTable {
  required int64 table_id = 1;
  repeated PbRebalancePlanForBucket buckets_plan = 2;
}

message PbRebalanceProgressForTable {
  required int64 table_id = 1;
  repeated PbRebalanceProgressForBucket buckets_progress = 2;
}

message PbRebalanceProgressForBucket {
  required PbRebalancePlanForBucket rebalance_plan = 1;
  required int32 rebalance_status = 2;
}

message PbRebalancePlanForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int32 original_leader = 3;
  optional int32 new_leader = 4;
  repeated int32 original_replicas = 5 [packed = true];
  repeated int32 new_replicas = 6 [packed = true];
}

message PbLakeTableSnapshotMetadata {
  required int64 table_id = 1;
  required int64 snapshot_id = 2;
  required string tiered_bucket_offsets_file_path = 3;
  optional string readable_bucket_offsets_file_path = 4;
  // Snapshot retention policy: The earliest snapshot ID to retain.
  // 1. If set, the system will keep all snapshots in the range [earliest_snapshot_id_to_keep, current_snapshot_id].
  // 2. If not set, the system defaults to a "Single Snapshot Retention" policy, keeping only the snapshot specified in this request.
  optional int64 earliest_snapshot_id_to_keep = 5;
}

message PbLakeTableSnapshotInfo {
  optional int64 table_id = 1;
  required int64 snapshot_id = 2;
  repeated PbLakeTableOffsetForBucket buckets_req = 3;
  // add table path to reduce get table_path by table id
  optional PbTablePath table_path = 4;
}

message PbLakeTableOffsetForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  // Deprecated: log_start_offset is no longer used. Field number 3 is reserved for protocol compatibility.
  // optional int64 log_start_offset = 3;
  optional int64 log_end_offset = 4;
  // Deprecated: partition_name is no longer used. Field number 5 is reserved for protocol compatibility.
  // optional string partition_name = 5;
  optional int64 max_timestamp = 6;
}

message PbPrepareLakeTableRespForTable {
  optional int32 error_code = 1;
  optional string error_message = 2;
  optional int64 table_id = 3;
  optional string lake_table_offsets_path = 4;
}

message PbTableOffsets {
  required int64 table_id = 1;
  required PbTablePath table_path = 2;
  repeated PbBucketOffset bucket_offsets = 3;
}

message PbBucketOffset {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  optional int64 log_end_offset = 4;
}

// Helper message for table offsets in producer offsets (without table_path)
message PbProducerTableOffsets {
  required int64 table_id = 1;
  repeated PbBucketOffset bucket_offsets = 2;
}

message PbDatabaseSummary {
  required string database_name = 1;
  required int64 created_time = 2;
  required int32 table_count = 3;
}

message PbKvSnapshotLeaseForTable {
  required int64 table_id = 1;
  repeated PbKvSnapshotLeaseForBucket bucket_snapshots = 2;
}

message PbKvSnapshotLeaseForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
  required int64 snapshot_id = 3;
}

message PbTableStatsReqForBucket {
  optional int64 partition_id = 1;
  required int32 bucket_id = 2;
}

message PbTableStatsRespForBucket {
  optional int32 error_code = 1;
  optional string error_message = 2;
  optional int64 partition_id = 3;
  required int32 bucket_id = 4;

  // --- Table-level stats ---
  // The number of rows in this bucket.
  // For KV tables: the number of unique keys (live rows).
  // For Log tables: total number of log records (highWatermark - logStartOffset).
  // Absent if row count is not available (e.g., WAL changelog mode or legacy tables).
  optional int64 row_count = 5;

  // --- data size stats (future) ---
  // The data size in bytes of this bucket.
  // For KV tables: the size of the KV store.
  // For Log tables: the size of the log segments.
  // Reserved for future use.
  // optional int64 data_size_bytes = 6;

  // --- Column-level stats (future) ---
  // Per-column statistics, keyed by column index.
  // Only populated when target_columns is specified in the request.
  // repeated PbColumnStats column_stats = 10;
}