// ───────────────────────────────────────── proto/lesavka.proto
syntax = "proto3";
package lesavka;

message KeyboardReport { bytes data = 1; }
message MouseReport    { bytes data = 1; }

message MonitorRequest {
  uint32 id = 1;
  uint32 max_bitrate = 2;
  uint32 requested_width = 3;
  uint32 requested_height = 4;
  uint32 requested_fps = 5;
  optional uint32 source_id = 6;
}
message VideoPacket    {
  uint32 id = 1;
  uint64 pts = 2;
  bytes data = 3;
  uint64 seq = 4;
  uint32 effective_fps = 5;
  uint64 dropped_total = 6;
  uint32 queue_depth = 7;
  uint32 server_source_gap_peak_ms = 8;
  uint32 server_send_gap_peak_ms = 9;
  uint32 server_queue_peak = 10;
  string server_encoder_label = 11;
  uint32 server_process_cpu_tenths = 12;
  // Shared client media-clock timestamp for the packet's capture estimate.
  uint64 client_capture_pts_us = 13;
  // Shared client media-clock timestamp when the packet entered the uplink queue.
  uint64 client_send_pts_us = 14;
  uint32 client_queue_depth = 15;
  uint32 client_queue_age_ms = 16;
}
enum AudioEncoding {
  AUDIO_UNSPECIFIED = 0;
  PCM_S16LE = 1;
  OPUS = 2;
}
message AudioPacket    {
  uint32 id = 1;
  uint64 pts = 2;
  bytes data = 3;
  uint64 seq = 4;
  // Shared client media-clock timestamp for the packet's capture estimate.
  uint64 client_capture_pts_us = 5;
  // Shared client media-clock timestamp when the packet entered the uplink queue.
  uint64 client_send_pts_us = 6;
  uint32 client_queue_depth = 7;
  uint32 client_queue_age_ms = 8;
  // Explicit payload encoding. Older/unstamped packets are interpreted as
  // PCM_S16LE so the current stable raw-audio path stays backward compatible.
  AudioEncoding encoding = 9;
  uint32 sample_rate = 10;
  uint32 channels = 11;
  uint32 frame_duration_us = 12;
}

message UpstreamMediaBundle {
  // One client-owned webcam/microphone capture batch. When a webcam is active,
  // this is the authoritative upstream transport so audio and video share one
  // ordered gRPC stream instead of racing through independent channels.
  uint64 session_id = 1;
  uint64 seq = 2;
  uint64 capture_start_us = 3;
  uint64 capture_end_us = 4;
  VideoPacket video = 5;
  repeated AudioPacket audio = 6;
  uint32 audio_sample_rate = 7;
  uint32 audio_channels = 8;
  uint32 video_width = 9;
  uint32 video_height = 10;
  uint32 video_fps = 11;
  // Batch-level audio encoding. This mirrors the packet encoding so receivers
  // can route a bundle before inspecting every audio payload.
  AudioEncoding audio_encoding = 12;
}

message OutputDelayProbeRequest {
  // The server generates the probe locally and feeds UVC/UAC directly. This
  // measures the output-device path only, not client capture or uplink transit.
  uint32 duration_seconds = 1;
  uint32 warmup_seconds = 2;
  uint32 pulse_period_ms = 3;
  uint32 pulse_width_ms = 4;
  string event_width_codes = 5;
  // Probe-local output delays used to test whether server-side scheduling can
  // compensate the raw UVC/UAC path. Positive values delay that stream.
  int64 audio_delay_us = 6;
  int64 video_delay_us = 7;
}

message OutputDelayProbeReply {
  bool ok = 1;
  string detail = 2;
  // Compact JSON timeline of when the server fed each coded signature into
  // UVC/UAC. The workstation script joins this with Tethys-observed events.
  string server_timeline_json = 3;
}

message ResetUsbReply  { bool ok = 1; } // true = success

message PasteRequest   {
  bytes nonce = 1;
  bytes data = 2;
  bool encrypted = 3;
}
message PasteReply     { bool ok = 1; string error = 2; }
message CapturePowerState {
  bool available = 1;
  bool enabled = 2;
  string unit = 3;
  string detail = 4;
  uint32 active_leases = 5;
  string mode = 6;
  uint32 detected_devices = 7;
}
enum CapturePowerCommand {
  CAPTURE_POWER_COMMAND_UNSPECIFIED = 0;
  CAPTURE_POWER_COMMAND_AUTO = 1;
  CAPTURE_POWER_COMMAND_FORCE_ON = 2;
  CAPTURE_POWER_COMMAND_FORCE_OFF = 3;
}
message SetCapturePowerRequest {
  bool enabled = 1;
  CapturePowerCommand command = 2;
}

enum CalibrationAction {
  CALIBRATION_ACTION_UNSPECIFIED = 0;
  CALIBRATION_ACTION_RESTORE_DEFAULT = 1;
  CALIBRATION_ACTION_RESTORE_FACTORY = 2;
  CALIBRATION_ACTION_ADJUST_ACTIVE = 3;
  CALIBRATION_ACTION_BLIND_ESTIMATE = 4;
  CALIBRATION_ACTION_SAVE_ACTIVE_AS_DEFAULT = 5;
}

message CalibrationState {
  string profile = 1;
  int64 factory_audio_offset_us = 2;
  int64 factory_video_offset_us = 3;
  int64 default_audio_offset_us = 4;
  int64 default_video_offset_us = 5;
  int64 active_audio_offset_us = 6;
  int64 active_video_offset_us = 7;
  string source = 8;
  string confidence = 9;
  string updated_at = 10;
  string detail = 11;
}

message CalibrationRequest {
  CalibrationAction action = 1;
  int64 audio_delta_us = 2;
  int64 video_delta_us = 3;
  float observed_delivery_skew_ms = 4;
  float observed_enqueue_skew_ms = 5;
  string note = 6;
}

message UpstreamSyncState {
  uint64 session_id = 1;
  string phase = 2;
  optional uint64 latest_camera_remote_pts_us = 3;
  optional uint64 latest_microphone_remote_pts_us = 4;
  optional uint64 last_video_presented_pts_us = 5;
  optional uint64 last_audio_presented_pts_us = 6;
  optional float live_lag_ms = 7;
  optional float planner_skew_ms = 8;
  uint64 stale_audio_drops = 9;
  uint64 stale_video_drops = 10;
  uint64 skew_video_drops = 11;
  uint64 freshness_reanchors = 12;
  uint64 startup_timeouts = 13;
  uint64 video_freezes = 14;
  string last_reason = 15;
  optional float client_capture_skew_ms = 16;
  optional float client_send_skew_ms = 17;
  optional float server_receive_skew_ms = 18;
  optional float camera_client_queue_age_ms = 19;
  optional float microphone_client_queue_age_ms = 20;
  optional float camera_server_receive_age_ms = 21;
  optional float microphone_server_receive_age_ms = 22;
  optional float client_capture_abs_skew_p95_ms = 23;
  optional float client_send_abs_skew_p95_ms = 24;
  optional float server_receive_abs_skew_p95_ms = 25;
  optional float camera_client_queue_age_p95_ms = 26;
  optional float microphone_client_queue_age_p95_ms = 27;
  optional float sink_handoff_skew_ms = 28;
  optional float sink_handoff_abs_skew_p95_ms = 29;
  optional float camera_sink_late_ms = 30;
  optional float microphone_sink_late_ms = 31;
  optional float camera_sink_late_p95_ms = 32;
  optional float microphone_sink_late_p95_ms = 33;
  uint64 client_timing_window_samples = 34;
  uint64 sink_handoff_window_samples = 35;
  string uvc_contract_status = 36;
  string uvc_transport = 37;
  optional uint32 uvc_enforced_frame_cap_bytes = 38;
  optional uint32 uvc_advertised_frame_bytes = 39;
  optional uint32 uvc_committed_frame_bytes = 40;
  optional uint32 uvc_committed_payload_bytes = 41;
  bool uvc_contract_divergent = 42;
  string uvc_contract_detail = 43;
  optional uint64 uvc_contract_generated_unix_ms = 44;
  uint64 uvc_contract_missing_reads = 45;
  uint64 uvc_contract_divergent_reads = 46;
  string uvc_integrity_status = 47;
  string uvc_integrity_detail = 48;
  uint64 uvc_rejected_oversize = 49;
  uint64 uvc_rejected_invalid = 50;
  uint64 uvc_fallback_idle = 51;
  uint64 uvc_held_last_good = 52;
  uint64 uvc_strict_validation_failures = 53;
  uint64 uvc_dqbuf_errors = 54;
  uint64 uvc_qbuf_errors = 55;
  uint64 uvc_kernel_errors = 56;
  optional uint64 uvc_integrity_generated_unix_ms = 57;
  uint64 uvc_stale_replay = 58;
  uint64 uvc_read_errors = 59;
}

message HandshakeSet  {
  bool camera = 1;
  bool microphone = 2;
  string camera_output = 3;
  string camera_codec = 4;
  uint32 camera_width = 5;
  uint32 camera_height = 6;
  uint32 camera_fps = 7;
  uint32 eye_width = 8;
  uint32 eye_height = 9;
  uint32 eye_fps = 10;
  string server_version = 11;
  string server_revision = 12;
  bool bundled_webcam_media = 13;
}

message Empty          {}

service Relay {
  rpc StreamKeyboard   (stream KeyboardReport)   returns (stream KeyboardReport);
  rpc StreamMouse      (stream MouseReport)      returns (stream MouseReport);
  rpc CaptureVideo     (MonitorRequest)          returns (stream VideoPacket);
  rpc CaptureAudio     (MonitorRequest)          returns (stream AudioPacket);
  rpc StreamMicrophone (stream AudioPacket)      returns (stream Empty);
  rpc StreamCamera     (stream VideoPacket)      returns (stream Empty);
  rpc StreamWebcamMedia(stream UpstreamMediaBundle) returns (stream Empty);
  rpc RunOutputDelayProbe(OutputDelayProbeRequest) returns (stream OutputDelayProbeReply);

  rpc PasteText        (PasteRequest)            returns (PasteReply);
  rpc RecoverUsb       (Empty)                   returns (ResetUsbReply);
  rpc RecoverUac       (Empty)                   returns (ResetUsbReply);
  rpc RecoverUvc       (Empty)                   returns (ResetUsbReply);
  rpc ResetUsb         (Empty)                   returns (ResetUsbReply);
  rpc GetCapturePower  (Empty)                   returns (CapturePowerState);
  rpc SetCapturePower  (SetCapturePowerRequest)  returns (CapturePowerState);
  rpc GetCalibration   (Empty)                   returns (CalibrationState);
  rpc Calibrate        (CalibrationRequest)       returns (CalibrationState);
  rpc GetUpstreamSync  (Empty)                   returns (UpstreamSyncState);
}

service Handshake {
  rpc GetCapabilities (Empty)                    returns (HandshakeSet);
}
