
// Copyright 2025 PingCAP, Inc.
//
// Licensed 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 = "proto3";

package tici_internal;

import "tici.proto";
import "import.proto";
import "schema.proto";

// Fragment metadata primarily stores all data files under a
// ​​fragment​​
message FragProperty {
  // Total file size of fragment.
  uint64 size = 1;
  // Distinct handle count in fragment.
  uint64 count = 2;
  // Min handle included in fragment.
  bytes min_handle = 3;
  repeated bytes split_handles = 4;
  // Max handle included in fragment.
  bytes max_handle = 5;
  uint64 delete_count = 6;
}

message FragStorageRef {
  // full_key = frag_path/sub_key
  string sub_key = 1;
  uint64 offset = 2;
  uint64 size = 3;
}

// Represent metadata for an uploaded fragment on S3
message FragMeta {
  // Data format version​​, used for future iterative upgrades
  uint64 format_version = 1;
  // Root path of the fragment​​
  string frag_path = 2;
  FragProperty property = 3;
  // Object storage references for uploaded fragment bundles. Must not be empty.
  repeated FragStorageRef storage_refs = 4;
  // Number of index segments in the fragment.
  uint32 seg_count = 5;
}

message DebugGetShardInfoRequest {
  repeated uint64 shard_ids = 1;
}

message ShardInfo {
  schema.TableInfo table_info = 1;
  schema.IndexInfo index_info = 2;
  tici.ShardManifestHeader shard_info = 3;
  repeated FragMeta frag_metas = 4;
  string s3_bucket = 5;
}

message DebugGetShardInfoResponse {
  int32 status = 1;
  repeated ShardInfo shards = 2;
}

service MetaServiceInternal {
  // Maintains heartbeat from reader nodes to meta service.
  rpc ReaderNodeHeartbeat(ReaderNodeHeartbeatRequest)
      returns (ReaderNodeHeartbeatResponse);

  // Maintains heartbeat from worker nodes(such as writers, compactors) to meta
  // service.
  rpc WorkerNodeHeartbeat(WorkerNodeHeartbeatRequest)
      returns (stream WorkerNodeHeartbeatResponse);

  rpc FinishCompactFragments(FinishCompactFragRequest)
      returns (FinishCompactFragResponse);

  rpc AppendDeltaFragMeta(AppendDeltaFragMetaRequest)
      returns (AppendFragMetaResponse);

  // TODO: this method should be replace by `FinishPartitionBuild`
  /// Append base fragment metadata, used for baseline shards
  /// MetaService is responsible for
  /// 1. Find the overlapped Shards for the given table_id, index_id and
  /// key_range
  /// 2. Append the frags to all the overlapped shards' baseline frags
  rpc AppendBaseFragMeta(AppendBaseFragMetaRequest)
      returns (AppendFragMetaResponse);

  // Debug usage: get shard manifest from meta service writer
  rpc DebugGetShardManifest(DebugGetShardManifestRequest)
      returns (DebugGetShardManifestResponse);

  // Get shard manifest from shard cache manager
  rpc GetShardManifest(GetShardManifestRequest)
      returns (GetShardManifestResponse);

  // Called by TiCI worker to notify Meta Service after a partition for
  // the given index is built.
  rpc FinishImportPartitionBuild(FinishImportPartitionBuildRequest)
      returns (tici.FinishImportResponse);

  rpc DebugGetShardInfo(DebugGetShardInfoRequest) returns (DebugGetShardInfoResponse);

  // GetIndexMeta returns TableInfo and IndexInfo for the given keyspace_id,
  // table_id and index_id.
  rpc GetIndexMeta(GetIndexMetaRequest) returns (GetIndexMetaResponse);
}

// GetIndexMetaRequest is a request to get TableInfo and IndexInfo by keyspace_id,
// table_id and index_id.
message GetIndexMetaRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
}

// GetIndexMetaResponse is a response containing TableInfo and IndexInfo.
message GetIndexMetaResponse {
  tici.ErrorCode status = 1;
  // Error message, only valid when status is non-zero
  string error_message = 2;
  schema.TableInfo table_info = 3;
  schema.IndexInfo index_info = 4;
}

// Reader node status information
message ReaderNodeStatus {
  // CPU usage percentage (0.0 to 100.0)
  double cpu_usage = 1;
  // Memory usage in bytes
  uint64 memory_usage = 2;
  // Number of requests processed in current heartbeat cycle
  uint32 requests_in_cycle = 3;
  // Cache hit rate percentage (0.0 to 100.0)
  double cache_hit_rate = 4;
}

message ReaderCachedShard {
  uint64 shard_id = 1;
  uint64 seq = 2;
}

// Reader node heartbeat request
message ReaderNodeHeartbeatRequest {
  // Reader node address
  string addr = 1;
  // Reader node status
  ReaderNodeStatus status = 2;
  // TiFlash server address
  string tiflash_server_addr = 3;
  // Legacy list of cached shard IDs on this reader.
  repeated uint64 cached_shard_ids = 4;
  // Cached shards on this reader with locally observed seq.
  repeated ReaderCachedShard cached_shards = 5;
}

// Reader node heartbeat response
message ReaderNodeHeartbeatResponse {
  tici.ErrorCode status = 1;
  // Error message, only valid when status is non-zero
  string error_message = 2;
  // List of shard headers that should be kept in cache
  repeated tici.ShardManifestHeader shards_to_keep = 3;
  // List of shard headers that should be removed from cache
  repeated tici.ShardManifestHeader shards_to_remove = 4;
}

// - If the index is built successfully, the `status` field should be 0,
//  and the `frag_metas` field should contain the metadata of the uploaded
//  baseline fragments. MetaService is responsible for
//  1. Find the overlapped Shards for the given table_id, index_id and key_range
//  2. Append the frags to all the overlapped shards' baseline frags
//  3. Update the ImportTask with task_id status as finished
// - If the index building meet unrecoverable errors, then MetaService should
//  update the ImportTask with task_id status as failed
message FinishImportPartitionBuildRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
  uint64 task_id = 4;
  tici.ErrorCode status = 5;
  // The uploaded fragment metadata when status is 0.
  repeated FragMeta frag_metas = 6;
  // The error message if the status is not SUCCESS.
  string error_message = 7;
  // The number of rows processed by this task.
  uint64 rows_processed = 8;
  // The bytes processed by this task.
  uint64 bytes_processed = 9;
  // The original import file size in bytes.
  uint64 input_file_size = 10;
  string owner_addr = 11;
  uint64 owner_seq = 12;
}

message DebugGetShardManifestRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
  tici.KeyRange key_range = 4;
}

message DebugGetShardManifestResponse {
  int32 status = 1;
  repeated FragMeta frag_metas = 2;
  uint64 shard_id = 3;
  uint64 seq = 4;
}

message GetShardManifestRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
  uint64 shard_id = 4;
}

message GetShardManifestResponse {
  tici.ErrorCode status = 1;
  repeated FragMeta frag_metas = 2;
  uint64 shard_id = 3;
  uint64 seq = 4;
}

message ShardProgress {
  string last_cdc_file = 1;
}

message AppendDeltaFragMetaRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
  uint64 shard_id = 4;
  string worker_node_addr = 5;
  repeated FragMeta frag_metas = 6;
  ShardProgress progress = 7;
}

message AppendFragMetaResponse { tici.ErrorCode status = 1; }

message AppendBaseFragMetaRequest {
  uint32 keyspace_id = 1;
  int64 table_id = 2;
  int64 index_id = 3;
  repeated FragMeta frag_metas = 4;
}

message FinishCompactFragRequest {
  string addr = 1;
  tici.ShardManifestHeader shard = 2;
  uint32 keyspace_id = 3;
  int64 table_id = 4;
  int64 index_id = 5;
  uint64 seq = 6;
  uint64 timestamp = 7;
  string leader_term = 8;
  repeated FragMeta input_frags = 9;
  repeated FragMeta output_frags = 10;
}

message FinishCompactFragResponse { tici.ErrorCode status = 1; }

message WorkerNodeStatus {
  // TODO Need more metrics, such as disk usage, quotas, cache miss rates
  double cpu_usage = 1;
  uint64 memory_usage = 2;
}

message WorkerNodeHeartbeatRequest {
  string addr = 1;
  WorkerNodeStatus status = 2;
  repeated WorkerNodeShardStatus shards = 3;
  repeated WorkerNodeCompactionStatus compactions = 4;
  repeated import.ImportWriteStatus import_writes = 5;
}

message WorkerNodeHeartbeatResponse {
  tici.ErrorCode status = 1;
  repeated AddShardRequest to_add_shards = 2;
  // We assume the `shard_id`s are unique across tables,
  // So using `shard_id` is enough here.
  repeated uint64 to_remove_shards = 3;
}


message WorkerNodeShardStatus {
  bytes start_key = 1;
  reserved 2;
  reserved "end_key";
  uint64 shard_id = 5;
}

// AddShardRequest is sent from meta service to worker node
message AddShardRequest {
  tici.ShardManifestHeader shard = 1;
  // Index information
  schema.IndexInfo index_info = 2;
  // Table information
  schema.TableInfo table_info = 3;
  // If the shard is created from scratch, this field is empty
  string cdc_s3_last_file = 4;
  tici.S3Location cdc_location = 5;
  tici.S3Location upload_location = 6;
}

enum CompactType {
  Delta = 0;
  Base = 1;
  Full = 2;
}

message CompactFragRequest {
  tici.ShardManifestHeader shard = 1;
  uint32 keyspace_id = 2;
  int64 table_id = 3;
  int64 index_id = 4;
  uint64 seq = 5;
  uint64 timestamp = 6;
  string leader_term = 7;
  repeated FragMeta input_frags = 8;
  tici.S3Location upload_location = 9;
  CompactType compact_type = 10;
  string worker = 11;
  // min_split_row_count means the minimum row count in a split.
  // For example, if min_split_row_count=5000, then a shard with 15000 lines can only be splitted into 3 shards at most.
  uint64 min_split_row_count = 12;
  // max_split_point_count means the maximum shards we can split.
  // For example, if min_split_row_count=1 and max_split_point_count=4, then a shard with 15000 lines can only be splitted into 4 shards at most.
  uint64 max_split_point_count = 13;
  schema.IndexInfo index_info = 14;
  schema.TableInfo table_info = 15;
}

message WorkerNodeCompactionStatus { CompactFragRequest req = 1; }

// WorkerService provides index creation and deletion functionality
service WorkerService {
  // Add shard for an index
  rpc AddShard(AddShardRequest) returns (AddShardResponse);
  // Compact a shard
  rpc CompactFragments(CompactFragRequest) returns (CompactFragResponse);
  // Write import data to a shard
  rpc ImportWrite(import.ImportWriteRequest) returns (import.ImportWriteResponse);
}


// AddShardResponse
message AddShardResponse {
  // Operation result status code, 0 means success
  tici.ErrorCode status = 1;
}

message CompactFragResponse { tici.ErrorCode status = 1; }
