namespace ZB.MOM.WW.LocalDb.Replication;
///
/// Configuration for the bidirectional 2-node replication engine. All durations are wall-clock UTC.
///
public sealed class ReplicationOptions
{
/// gRPC address of the peer to dial. Required for the initiator role (validated in the sync host).
public string PeerAddress { get; set; } = "";
/// API key presented to / expected from the peer; null disables key auth.
public string? ApiKey { get; set; }
/// How often the change pump flushes pending oplog deltas to the peer.
public TimeSpan FlushInterval { get; set; } = TimeSpan.FromMilliseconds(250);
///
/// Secondary, row-count cap on an outbound batch (and the snapshot streamer's DB page size).
/// is the primary cap.
///
///
/// Historically this was the ONLY cap, which made it a proxy for a byte budget that consumers
/// had to tune by hand against their widest row — ScadaBridge pinned it to 16 because a 70 KB
/// config_json times the 500 default is ~35 MB against a 4 MB gRPC limit. With
/// doing that job directly, this is back to being what its name
/// says: a bound on how many rows one message may carry.
///
public int MaxBatchSize { get; set; } = 500;
///
/// Primary cap on an outbound batch: the summed serialized size of the entries in one
/// DeltaBatch / SnapshotBatch message, in bytes. Default 2 MB.
///
///
///
/// Sized at half the default 4 MB gRPC receive limit, so the message framing, the oneof wrapper
/// and any per-entry overhead the estimate does not model all fit inside the margin.
///
///
/// A single row larger than the budget is still sent, alone. Skipping it would stall the
/// stream permanently on one oversized row; sending it gives the transport the chance to accept
/// it (gRPC limits are configurable) and, if it refuses, a loud transport error naming the size
/// rather than silent non-convergence.
///
///
/// Wire compatibility: none required. Batch size is a sender-side choice the protocol
/// already leaves free — DeltaBatch/SnapshotBatch carry a repeated field with no
/// declared count — so a node on this version interoperates with a peer on any earlier one in
/// both directions, with no proto change and no lib_schema_version bump. (ScadaBridge
/// site pairs stop and start together regardless; this simply does not depend on it.)
///
///
public int MaxBatchBytes { get; set; } = 2 * 1024 * 1024;
/// Backlog ceiling: exceeding it flags a snapshot resync and prunes the oldest oplog rows.
public long MaxOplogRows { get; set; } = 1_000_000;
/// Age ceiling: an unpruned oplog entry older than this flags a snapshot resync.
public TimeSpan MaxOplogAge { get; set; } = TimeSpan.FromDays(7);
/// How long deleted-row tombstones are retained before pruning.
public TimeSpan TombstoneRetention { get; set; } = TimeSpan.FromDays(7);
/// Upper bound on the exponential reconnect backoff.
public TimeSpan ReconnectBackoffMax { get; set; } = TimeSpan.FromSeconds(60);
///
/// How long the initiator's HTTP/2 connection may sit without a keepalive PING before one is
/// sent. Default 60s.
///
///
///
/// A sync stream is long-lived and, on a converged pair, almost entirely silent — which is
/// exactly the shape a half-open TCP connection hides behind. Without pings, a hard crash or a
/// partition of one node leaves the other holding a stream that will never produce another byte
/// and never fail either: the initiator sits in a session it believes is healthy, and the
/// passive node's handler sits inside its single-stream guard, refusing the reconnect that would
/// heal the pair. Pings turn that into a detected failure on both ends.
///
///
/// Sent with KeepAlivePingPolicy.Always, so they flow on an idle connection too — an
/// active-streams-only policy would not fire in precisely the silent-pair case above. Safe over
/// h2c: a PING is an HTTP/2 frame, nothing to do with TLS.
///
///
public TimeSpan KeepAlivePingDelay { get; set; } = TimeSpan.FromSeconds(60);
///
/// How long the initiator waits for a keepalive PING ack before tearing the connection down.
/// Default 20s. Detection therefore lands within roughly
/// + this.
///
public TimeSpan KeepAlivePingTimeout { get; set; } = TimeSpan.FromSeconds(20);
/// How often the maintenance service enforces the oplog caps and prunes expired tombstones.
public TimeSpan MaintenanceInterval { get; set; } = TimeSpan.FromSeconds(60);
/// Maximum tolerated HLC drift where a peer's clock runs ahead of ours.
public TimeSpan MaxHlcDriftAhead { get; set; } = TimeSpan.FromMinutes(5);
///
/// When true, entries whose HLC physical time exceeds are
/// dead-lettered individually (the rest of the batch still applies); when false, drifted
/// entries are applied and a single warning is logged per session.
///
public bool FailClosedOnDrift { get; set; }
}