// Copyright 2026 The Drasi Authors.
//
// 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";

// Wire contract for the Drasi gRPC reaction.
//
// Consumed by `drasi-reaction-grpc`. This file is intentionally narrow:
// it defines only what the reaction puts on the wire when forwarding
// continuous-query results to a downstream gRPC service. It carries no
// source-side, bootstrap, streaming, or subscription types — those are
// out of scope and live in their own components' protos when needed.
package drasi.v1;

import "google/protobuf/struct.proto";
import "google/protobuf/timestamp.proto";

// Service the gRPC reaction calls on its configured downstream
// endpoint. One unary `ProcessResults` RPC is sent per batch.
service ReactionService {
    rpc ProcessResults(ProcessResultsRequest) returns (ProcessResultsResponse);
}

// One ProcessResults call. Carries a single-query batch in `results`
// plus configured per-call request metadata in `metadata`.
//
// The reaction sends `metadata` entries both here as a body field and
// as actual gRPC request headers via `tonic::Request::metadata_mut()`,
// so receivers can read either. Entries with header names or values
// that are not valid HTTP/2 metadata are sent only in this body field.
message ProcessResultsRequest {
    QueryResult results = 1;
    map<string, string> metadata = 2;
}

message ProcessResultsResponse {
    bool success = 1;
    string message = 2;
    string error = 3;
    uint32 items_processed = 4;
}

// One coalesced batch of result-change items for a single continuous
// query. Multiple query ids or per-template metadata values in the same
// batching window arrive as separate `ProcessResultsRequest` RPCs —
// `query_id` is singular. `timestamp` is the latest event timestamp in
// `results`; the item-level timestamp is authoritative.
message QueryResult {
    string query_id = 1;
    repeated QueryResultItem results = 2;
    google.protobuf.Timestamp timestamp = 3;
}

// Operation captured by a single QueryResultItem.
//
// Aggregations surface as UPDATE on the wire (consistent with the
// other Drasi reactions). Noop results are dropped at the runner
// before reaching the wire and have no enum value here.
enum QueryResultItemType {
    // Required by proto3 (every enum must have a zero-valued first
    // variant). Never emitted by the reaction; receiving this value
    // indicates a contract violation.
    QUERY_RESULT_ITEM_TYPE_UNSPECIFIED = 0;
    QUERY_RESULT_ITEM_TYPE_ADD         = 1;
    QUERY_RESULT_ITEM_TYPE_UPDATE      = 2;
    QUERY_RESULT_ITEM_TYPE_DELETE      = 3;
}

// One result-change item.
//
// `before` and `after` carry the row state on either side of the
// change. Per op:
//   ADD     — only `after` set (the row did not exist before).
//   DELETE  — only `before` set (the row no longer exists).
//   UPDATE  — both set (row content changed).
//
// `before` and `after` always carry the unmodified raw row state for
// the change regardless of templating, so receivers can always recover
// the change. Output templates do not rewrite `before` / `after`; when
// a body template matches the item's `(query_id, op)` it is rendered
// once into `payload` (see below). Events are never dropped because of
// a render failure.
//
// `timestamp` is the original event timestamp from the `QueryResult`
// this item originated from. `metadata` is the query-result metadata
// supplied by Drasi for that emission. `payload` carries the canonical
// guide-aligned JSON payload, or — when a body template matches the
// item — the rendered template output, as a protobuf Struct. On render
// failure `payload` falls back to the canonical item. In `proto`
// output mode `payload` is omitted unless a body template applies.
//
// `sequence` is the monotonic per-query emission number of the
// `QueryResult` this item originated from. Because one
// `ProcessResultsRequest` may coalesce items from several emissions of
// the same query, the sequence is carried per item rather than on the
// envelope. Receivers can use sequence + timestamp to order or
// de-duplicate emissions.
message QueryResultItem {
    QueryResultItemType item_type = 1;
    uint64 row_signature = 2;
    google.protobuf.Struct before = 3;
    google.protobuf.Struct after  = 4;
    uint64 sequence = 5;
    google.protobuf.Timestamp timestamp = 6;
    google.protobuf.Struct metadata = 7;
    google.protobuf.Struct payload = 8;
}
