// KyroDB v1 gRPC Service Definition
// High-performance vector database optimized for RAG workloads
// Primary protocol: gRPC (performance-critical), REST secondary (convenience)

syntax = "proto3";

package kyrodb.v1;

// Core KyroDB service - all operations for vector storage and retrieval
service KyroDBService {
  // ============================================================================
  // WRITE OPERATIONS
  // ============================================================================
  
  // Insert a single document with embedding
  // Durability: ACK after WAL append (per fsync policy) and cold-tier update.
  rpc Insert(InsertRequest) returns (InsertResponse);
  
  // Insert multiple documents in a streaming fashion
  // Use for bulk imports (millions of vectors)
  // Throughput: 50K-100K docs/sec
  rpc BulkInsert(stream InsertRequest) returns (InsertResponse);
  
  // Bulk load directly to HNSW index (bypasses hot tier)
  // Use for benchmarks and data migrations - NOT for production traffic!
  // This provides maximum indexing speed at the cost of no hot-tier caching.
  // Warning: Data is only searchable after this call completes.
  rpc BulkLoadHnsw(stream InsertRequest) returns (BulkLoadResponse);
  
  // Delete a document by ID
  rpc Delete(DeleteRequest) returns (DeleteResponse);
  
  // Update document metadata without changing embedding
  // Supports merge (extend existing) or replace (overwrite all) semantics
  rpc UpdateMetadata(UpdateMetadataRequest) returns (UpdateMetadataResponse);
  
  // ============================================================================
  // READ OPERATIONS
  // ============================================================================
  
  // Query for a specific document by ID (point lookup)
  // Latency: <100ns cache hit, <1ms cache miss
  // Query path: Cache → Hot Tier → HNSW
  rpc Query(QueryRequest) returns (QueryResponse);
  
  // k-NN vector similarity search
  // Latency/recall depend on HNSW params, dataset, and hardware.
  // Returns top-k most similar documents
  rpc Search(SearchRequest) returns (SearchResponse);
  
  // Bulk search: multiple queries in one request (streaming)
  // Use for batch inference workloads
  rpc BulkSearch(stream SearchRequest) returns (stream SearchResponse);

  // Bulk query: retrieve multiple documents by ID
  rpc BulkQuery(BulkQueryRequest) returns (BulkQueryResponse);

  // Batch delete: delete documents by ID or metadata filter
  rpc BatchDelete(BatchDeleteRequest) returns (BatchDeleteResponse);
  
  // ============================================================================
  // HEALTH & OBSERVABILITY
  // ============================================================================
  
  // Health check for load balancer / orchestrator
  // Returns component-level health status
  rpc Health(HealthRequest) returns (HealthResponse);
  
  // Metrics for monitoring and alerting
  // Returns cache hit rates, latencies, throughput
  rpc Metrics(MetricsRequest) returns (MetricsResponse);
  
  // ============================================================================
  // ADMIN OPERATIONS
  // ============================================================================
  
  // Force flush hot tier to cold tier (for testing/backups)
  rpc FlushHotTier(FlushRequest) returns (FlushResponse);
  
  // Create snapshot for backup
  rpc CreateSnapshot(SnapshotRequest) returns (SnapshotResponse);
  
  // Get engine configuration (read-only)
  rpc GetConfig(ConfigRequest) returns (ConfigResponse);
}

// ============================================================================
// WRITE OPERATION MESSAGES
// ============================================================================

message InsertRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  // Document ID (must be unique, non-zero)
  // Using uint64 for 18 quintillion possible IDs
  uint64 doc_id = 1;
  
  // Dense embedding vector (e.g., 384 dims for MS MARCO, 1536 for OpenAI)
  // Packed as repeated float for efficient binary encoding
  repeated float embedding = 2;
  
  // Optional metadata for future hybrid queries
  // Phase 1: key-value pairs for filtering
  // Phase 2: Full metadata indexing
  map<string, string> metadata = 3;
  
  // Optional namespace for multi-tenancy 
  string namespace = 4;
}

message InsertResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool success = 1;
  string error = 2;
  
  // Unix timestamp (seconds since epoch)
  uint64 inserted_at = 3;
  
  // Which tier the document landed in
  enum Tier {
    UNKNOWN = 0;
    HOT_TIER = 1;
    COLD_TIER = 2;
  }
  Tier tier = 4;
  
  // Stats (for bulk insert)
  uint64 total_inserted = 5;
  uint64 total_failed = 6;
}

// Response for BulkLoadHnsw - detailed stats for bulk loading
message BulkLoadResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool success = 1;
  string error = 2;
  
  // Number of documents successfully loaded
  uint64 total_loaded = 3;
  
  // Number of documents that failed to load
  uint64 total_failed = 4;
  
  // Total duration of the load operation in milliseconds
  float load_duration_ms = 5;
  
  // Average insertion rate (vectors per second)
  float avg_insert_rate = 6;
  
  // Peak memory usage during load (bytes)
  uint64 peak_memory_bytes = 7;
}

message DeleteRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  uint64 doc_id = 1;
  string namespace = 2;  // Optional namespace
}

message DeleteResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool success = 1;
  string error = 2;
  bool existed = 3;  // Was the document present before deletion?
}

message UpdateMetadataRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  uint64 doc_id = 1;
  map<string, string> metadata = 2;
  bool merge = 3;  // true = merge with existing, false = replace
  string namespace = 4;  // Optional namespace
}

message UpdateMetadataResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool success = 1;
  string error = 2;
  bool existed = 3;  // Was the document present before update?
}

message BulkQueryRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  repeated uint64 doc_ids = 1;
  bool include_embeddings = 2;
  string namespace = 3;
}

message BulkQueryResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  repeated QueryResponse results = 1;
  uint32 total_found = 2;
  uint32 total_requested = 3;
  string error = 4;
}

message BatchDeleteRequest {
  // Keep oneof member tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  oneof delete_criteria {
    // Delete specific IDs
    IdList ids = 1;
    // Delete by metadata filter
    MetadataFilter filter = 2;
  }
  string namespace = 3;
}

message IdList {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  repeated uint64 doc_ids = 1;
}

message BatchDeleteResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool success = 1;
  uint64 deleted_count = 2;
  string error = 3;
}

// ============================================================================
// READ OPERATION MESSAGES
// ============================================================================

message QueryRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  // Document ID to retrieve
  uint64 doc_id = 1;
  
  // Whether to return the embedding vector (default: false)
  // Set to false to save bandwidth if only checking existence
  bool include_embedding = 2;
  
  // Optional namespace
  string namespace = 3;
}

message QueryResponse {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  bool found = 1;
  uint64 doc_id = 2;
  
  // Embedding vector (only if include_embedding=true)
  repeated float embedding = 3;
  
  // Optional metadata
  map<string, string> metadata = 4;
  
  // Which tier served the query (for debugging)
  enum Tier {
    UNKNOWN = 0;
    CACHE = 1;
    HOT_TIER = 2;
    COLD_TIER = 3;
  }
  Tier served_from = 5;
  
  string error = 6;
}

message SearchRequest {
  // Keep field tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  // Query embedding vector
  repeated float query_embedding = 1;
  
  // Number of nearest neighbors to return (k in k-NN)
  // Valid range: 1-1000
  uint32 k = 2;
  
  // Optional score threshold.
  // Only return results with score >= min_score.
  // Score semantics depend on configured distance metric:
  // - cosine / inner_product: similarity in [-1.0, 1.0]
  // - euclidean: transformed similarity in (0.0, 1.0]
  float min_score = 3;
  
  // Optional namespace
  string namespace = 4;
  
  // Whether to include embeddings in results (default: false)
  // Set to false to save bandwidth
  bool include_embeddings = 5;

  // Optional HNSW ef_search override (0 = use server default)
  // Higher values typically improve recall at the cost of latency.
  uint32 ef_search = 7;
  
  // Optional metadata filters (Phase 1)
  MetadataFilter filter = 6;
  map<string, string> metadata_filters = 8 [deprecated = true]; // Deprecated in favor of structured filter at tag 6
}

message MetadataFilter {
  // Keep tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  oneof filter_type {
    ExactMatch exact = 1;
    RangeMatch range = 2;
    InMatch in_match = 3;
    AndFilter and_filter = 4;
    OrFilter or_filter = 5;
    NotFilter not_filter = 6;
  }
}

message ExactMatch {
  // Keep tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  string key = 1;
  string value = 2;
}

message RangeMatch {
  // Keep tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  string key = 1;
  // Keep oneof member tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  oneof bound {
    string gte = 2;  // >=
    string lte = 3;  // <=
    string gt = 4;   // >
    string lt = 5;   // <
  }
}

message InMatch {
  // Keep tag numbers stable once assigned; renumbering breaks protobuf wire compatibility.
  string key = 1;
  repeated string values = 2;
}

message AndFilter {
  // Keep tag number stable once assigned; renumbering breaks protobuf wire compatibility.
  repeated MetadataFilter filters = 1;
}

message OrFilter {
  // Keep tag number stable once assigned; renumbering breaks protobuf wire compatibility.
  repeated MetadataFilter filters = 2;
}

message NotFilter {
  // Keep tag number stable once assigned; renumbering breaks protobuf wire compatibility.
  MetadataFilter filter = 1;
}

message SearchResult {
  uint64 doc_id = 1;
  
  // Similarity score (higher is better).
  // Score semantics depend on configured distance metric:
  // - cosine / inner_product: similarity in [-1.0, 1.0]
  // - euclidean: transformed similarity in (0.0, 1.0]
  float score = 2;
  
  // Optional embedding (if include_embeddings=true)
  repeated float embedding = 3;
  
  // Optional metadata
  map<string, string> metadata = 4;
}

message SearchResponse {
  // Top-k results, sorted by score (descending)
  repeated SearchResult results = 1;
  
  // Total number of results found (before filtering)
  uint32 total_found = 2;
  
  // Search latency in milliseconds
  float search_latency_ms = 3;
  
  // Which tiers were accessed during search
  enum SearchPath {
    UNKNOWN = 0;
    CACHE_HIT = 1;          // Found in cache
    HOT_TIER_ONLY = 2;      // Searched hot tier only
    COLD_TIER_ONLY = 3;     // Searched cold tier only
    HOT_AND_COLD = 4;       // Searched both tiers
  }
  SearchPath search_path = 4;
  
  string error = 5;
}

// ============================================================================
// HEALTH & OBSERVABILITY MESSAGES
// ============================================================================

message HealthRequest {
  // Optional: check specific component
  string component = 1;  // "cache", "hot_tier", "cold_tier", "wal"
}

message HealthResponse {
  enum Status {
    UNKNOWN = 0;
    HEALTHY = 1;      // All systems operational
    DEGRADED = 2;     // Some issues but functional
    UNHEALTHY = 3;    // Critical failure
  }
  Status status = 1;
  
  // Server version (e.g., "0.1.0")
  string version = 2;
  
  // Component-level health
  // Key: component name, Value: status string
  map<string, string> components = 3;
  
  // Uptime in seconds
  uint64 uptime_seconds = 4;
  
  // Git commit hash (for debugging)
  string git_commit = 5;
}

message MetricsRequest {
  // Optional: filter metrics by category
  // Categories: "cache", "performance", "system"
  repeated string categories = 1;
}

message MetricsResponse {
  // ========== CACHE METRICS (Layer 1) ==========
  uint64 cache_hits = 1;
  uint64 cache_misses = 2;
  double cache_hit_rate = 3;  // Percentage: 0.0 to 100.0
  uint64 cache_size = 4;      // Number of documents in cache
  
  // ========== HOT TIER METRICS (Layer 2) ==========
  uint64 hot_tier_hits = 5;
  uint64 hot_tier_misses = 6;
  double hot_tier_hit_rate = 7;
  uint64 hot_tier_size = 8;
  uint64 hot_tier_flushes = 9;
  
  // ========== COLD TIER METRICS (Layer 3) ==========
  uint64 cold_tier_searches = 10;
  uint64 cold_tier_size = 11;  // Total documents in HNSW
  
  // ========== PERFORMANCE METRICS ==========
  double p50_latency_ms = 12;
  double p95_latency_ms = 13;
  double p99_latency_ms = 14;
  uint64 total_queries = 15;
  uint64 total_inserts = 16;
  double queries_per_second = 17;
  double inserts_per_second = 18;
  
  // ========== SYSTEM METRICS ==========
  uint64 memory_usage_bytes = 19;
  uint64 disk_usage_bytes = 20;
  double cpu_usage_percent = 21;
  
  // ========== OVERALL HEALTH ==========
  double overall_hit_rate = 22;  // (cache_hits + hot_tier_hits) / total_queries
  
  // Timestamp when metrics were collected
  uint64 collected_at = 23;
}

// ============================================================================
// ADMIN OPERATION MESSAGES
// ============================================================================

message FlushRequest {
  // Force immediate flush regardless of hot tier size/age
  bool force = 1;
}

message FlushResponse {
  bool success = 1;
  string error = 2;
  uint64 documents_flushed = 3;
  float flush_duration_ms = 4;
}

message SnapshotRequest {
  // Optional snapshot path (default: data_dir/snapshots/)
  string path = 1;
}

message SnapshotResponse {
  bool success = 1;
  string error = 2;
  string snapshot_path = 3;
  uint64 documents_snapshotted = 4;
  uint64 snapshot_size_bytes = 5;
}

message ConfigRequest {}

message ConfigResponse {
  // Engine configuration (read-only)
  uint64 hot_tier_max_size = 1;
  uint64 hot_tier_max_age_seconds = 2;
  uint64 hnsw_max_elements = 3;
  string data_dir = 4;
  string fsync_policy = 5;  // "Always", "Periodic", "Never"
  uint64 snapshot_interval = 6;
  uint64 flush_interval_seconds = 7;
  
  // Runtime info
  uint64 embedding_dimension = 8;
  string version = 9;

  // HNSW parameters (critical for benchmark traceability)
  uint64 hnsw_m = 10;
  uint64 hnsw_ef_construction = 11;
  uint64 hnsw_ef_search = 12;
  string hnsw_distance = 13;
  bool hnsw_disable_normalization_check = 14;
}
