syntax = "proto3";
package guruconnect;

// ============================================================================
// Session Management
// ============================================================================

message SessionRequest {
    string agent_id = 1;
    string session_token = 2;
    SessionType session_type = 3;
    string client_version = 4;
}

message SessionResponse {
    bool success = 1;
    string session_id = 2;
    string error = 3;
    DisplayInfo display_info = 4;
    // Negotiated video codec for this session (Task 7). The plan models the
    // codec selection here; however the LIVE server->agent handshake in v2 is
    // done over WebSocket query params + StartStream (SessionRequest/
    // SessionResponse are not exchanged on the wire today), so the codec the
    // agent actually applies is carried on StartStream.video_codec below. This
    // field is kept for spec parity / future use; raw is the safe default.
    VideoCodec video_codec = 5;
}

// Negotiated video codec (Task 7). RAW = salvaged BGRA+Zstd+dirty-rects, the
// guaranteed fallback and current default. H264 = hardware Media Foundation
// encode, the negotiated upgrade (compile-verified; validated on hardware in
// plan Task 8). H265/HEVC is future opt-in (TODO).
enum VideoCodec {
    VIDEO_CODEC_RAW = 0;    // Raw BGRA + Zstd (safe default / fallback)
    VIDEO_CODEC_H264 = 1;   // Hardware H.264 via Media Foundation
    VIDEO_CODEC_H265 = 2;   // Future / opt-in (not implemented)
}

enum SessionType {
    SCREEN_CONTROL = 0;
    VIEW_ONLY = 1;
    BACKSTAGE = 2;
    FILE_TRANSFER = 3;
}

// ============================================================================
// Display Information
// ============================================================================

message DisplayInfo {
    repeated Display displays = 1;
    int32 primary_display = 2;
}

message Display {
    int32 id = 1;
    string name = 2;
    int32 x = 3;
    int32 y = 4;
    int32 width = 5;
    int32 height = 6;
    bool is_primary = 7;
}

message SwitchDisplay {
    int32 display_id = 1;
}

// ============================================================================
// Video Frames
// ============================================================================

message VideoFrame {
    int64 timestamp = 1;
    int32 display_id = 2;
    int32 sequence = 3;

    oneof encoding {
        RawFrame raw = 10;
        EncodedFrame vp9 = 11;
        EncodedFrame h264 = 12;
        EncodedFrame h265 = 13;
    }
}

message RawFrame {
    int32 width = 1;
    int32 height = 2;
    bytes data = 3;             // Zstd compressed BGRA
    bool compressed = 4;
    repeated DirtyRect dirty_rects = 5;
    bool is_keyframe = 6;       // Full frame vs incremental
}

message DirtyRect {
    int32 x = 1;
    int32 y = 2;
    int32 width = 3;
    int32 height = 4;
}

message EncodedFrame {
    bytes data = 1;
    bool keyframe = 2;
    int64 pts = 3;
    int64 dts = 4;
}

message VideoAck {
    int32 sequence = 1;
    int64 timestamp = 2;
}

// ============================================================================
// Cursor
// ============================================================================

message CursorShape {
    uint64 id = 1;
    int32 hotspot_x = 2;
    int32 hotspot_y = 3;
    int32 width = 4;
    int32 height = 5;
    bytes data = 6;             // BGRA bitmap
}

message CursorPosition {
    int32 x = 1;
    int32 y = 2;
    bool visible = 3;
}

// ============================================================================
// Input Events
// ============================================================================

message MouseEvent {
    int32 x = 1;
    int32 y = 2;
    MouseButtons buttons = 3;
    int32 wheel_delta_x = 4;
    int32 wheel_delta_y = 5;
    MouseEventType event_type = 6;
}

enum MouseEventType {
    MOUSE_MOVE = 0;
    MOUSE_DOWN = 1;
    MOUSE_UP = 2;
    MOUSE_WHEEL = 3;
}

message MouseButtons {
    bool left = 1;
    bool right = 2;
    bool middle = 3;
    bool x1 = 4;
    bool x2 = 5;
}

message KeyEvent {
    bool down = 1;              // true = key down, false = key up
    KeyEventType key_type = 2;
    uint32 vk_code = 3;         // Virtual key code (Windows VK_*)
    uint32 scan_code = 4;       // Hardware scan code
    string unicode = 5;         // Unicode character (for text input)
    Modifiers modifiers = 6;
    // True when the originating key is an "extended" key (right Ctrl/Alt, arrows,
    // Insert/Home/End/PageUp/Down, NumLock, Win keys, numpad Divide). The viewer
    // captures this from the low-level hook (KBDLLHOOKSTRUCT.flags & LLKHF_EXTENDED);
    // the agent injects with KEYEVENTF_EXTENDEDKEY when set. Field added in Task 6
    // (v2 full key fidelity); older agents that ignore it fall back to deriving the
    // flag from vk_code/scan_code.
    bool is_extended = 7;
}

enum KeyEventType {
    KEY_VK = 0;                 // Virtual key code
    KEY_SCAN = 1;               // Scan code
    KEY_UNICODE = 2;            // Unicode character
}

message Modifiers {
    bool ctrl = 1;
    bool alt = 2;
    bool shift = 3;
    bool meta = 4;              // Windows key
    bool caps_lock = 5;
    bool num_lock = 6;
}

message SpecialKeyEvent {
    SpecialKey key = 1;
}

enum SpecialKey {
    CTRL_ALT_DEL = 0;
    LOCK_SCREEN = 1;
    PRINT_SCREEN = 2;
}

// ============================================================================
// Clipboard
// ============================================================================

message ClipboardData {
    ClipboardFormat format = 1;
    bytes data = 2;
    string mime_type = 3;
}

enum ClipboardFormat {
    CLIPBOARD_TEXT = 0;
    CLIPBOARD_HTML = 1;
    CLIPBOARD_RTF = 2;
    CLIPBOARD_IMAGE = 3;
    CLIPBOARD_FILES = 4;
}

message ClipboardRequest {
    // Request current clipboard content
}

// ============================================================================
// Quality Control
// ============================================================================

message QualitySettings {
    QualityPreset preset = 1;
    int32 custom_fps = 2;       // 1-60
    int32 custom_bitrate = 3;   // kbps
    CodecPreference codec = 4;
}

enum QualityPreset {
    QUALITY_AUTO = 0;
    QUALITY_LOW = 1;            // Low bandwidth
    QUALITY_BALANCED = 2;
    QUALITY_HIGH = 3;           // Best quality
}

enum CodecPreference {
    CODEC_AUTO = 0;
    CODEC_RAW = 1;              // Raw + Zstd (LAN)
    CODEC_VP9 = 2;
    CODEC_H264 = 3;
    CODEC_H265 = 4;
}

message LatencyReport {
    int64 rtt_ms = 1;
    int32 fps = 2;
    int32 bitrate_kbps = 3;
}

// ============================================================================
// Chat Messages
// ============================================================================

message ChatMessage {
    string id = 1;              // Unique message ID
    string sender = 2;          // "technician" or "client"
    string content = 3;         // Message text
    int64 timestamp = 4;        // Unix timestamp
}

// ============================================================================
// Control Messages
// ============================================================================

message Heartbeat {
    int64 timestamp = 1;
}

message HeartbeatAck {
    int64 client_timestamp = 1;
    int64 server_timestamp = 2;
}

message Disconnect {
    string reason = 1;
}

// Server commands agent to start streaming video
message StartStream {
    string viewer_id = 1;           // ID of viewer requesting stream
    int32 display_id = 2;           // Which display to stream (0 = primary)
    // Negotiated codec for this stream (Task 7). The server selects this from
    // the agent's advertised capability (AgentStatus.supports_h264) and its
    // policy (DEFAULT_PREFER_H264, currently false so we never ship unvalidated
    // H.264 as the default). The agent builds its encoder from this value;
    // VIDEO_CODEC_RAW (0, the default for older servers) keeps the salvaged
    // raw+Zstd path. On any H.264 init failure the agent falls back to raw.
    VideoCodec video_codec = 3;
}

// Server commands agent to stop streaming
message StopStream {
    string viewer_id = 1;           // Which viewer disconnected
}

// Agent reports its status periodically when idle
message AgentStatus {
    string hostname = 1;
    string os_version = 2;
    bool is_elevated = 3;
    int64 uptime_secs = 4;
    int32 display_count = 5;
    bool is_streaming = 6;
    string agent_version = 7;           // Agent version (e.g., "0.1.0-abc123")
    string organization = 8;            // Company/organization name
    string site = 9;                    // Site/location name
    repeated string tags = 10;          // Tags for categorization
    // HW-encode capability (Task 7). True when the agent enumerated a HARDWARE
    // H.264 encoder MFT at startup (MFTEnumEx, MFT_CATEGORY_VIDEO_ENCODER,
    // MFVideoFormat_H264, hardware flag). The server uses this for codec
    // negotiation (see StartStream.video_codec). Detected once and cached;
    // false on non-Windows / no HW encoder / MF unavailable.
    bool supports_h264 = 11;
    // Deterministic, recomputable hardware identity (v2 stable-identity Task 1).
    // Opaque "muid_<hex>" derived by SHA-256 hashing the OS machine GUID
    // (Windows: HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid); non-Windows /
    // registry-failure falls back to a persisted random UUID. Reported ALONGSIDE
    // agent_id (which is unchanged). The server-side dedup that consumes this is a
    // separate task; until then it is informational. Empty only if the agent
    // predates this field.
    string machine_uid = 12;
}

// Server commands agent to uninstall itself
message AdminCommand {
    AdminCommandType command = 1;
    string reason = 2;              // Why the command was issued
}

enum AdminCommandType {
    ADMIN_UNINSTALL = 0;            // Uninstall agent and remove from startup
    ADMIN_RESTART = 1;              // Restart the agent process
    ADMIN_UPDATE = 2;               // Download and install update
}

// ============================================================================
// Attended-mode Consent (Task 5)
// ============================================================================
//
// For an ATTENDED (support-code) session, the end user on the managed machine
// must explicitly accept a consent prompt before the technician's session goes
// live. The server holds the session in `consent_state = pending` and does NOT
// surface it to the technician (no viewer join / no streaming) until the agent
// returns a ConsentResponse with granted = true. A denial or timeout tears the
// session down. Managed/unattended sessions are `not_required` and never see a
// ConsentRequest (Phase-1 default policy).

// Server/relay -> Agent: ask the end user to allow this support session.
message ConsentRequest {
    string session_id = 1;          // Session awaiting consent (UUID string)
    string technician_name = 2;     // Display name of the requesting technician
    ConsentAccessMode access_mode = 3; // View vs. control, so the prompt is honest
    int32 timeout_secs = 4;         // How long the agent should wait before auto-deny
}

// Agent -> Server: the end user's decision.
message ConsentResponse {
    string session_id = 1;          // Echoes the ConsentRequest session_id
    bool granted = 2;               // true = allow, false = deny (incl. dialog closed)
    string reason = 3;              // Optional human-readable reason (e.g. "timeout")
}

// Whether the technician is requesting view-only or full control. Mirrors the
// viewer access mode the server already enforces; used only to phrase the prompt.
enum ConsentAccessMode {
    CONSENT_VIEW = 0;               // Technician will VIEW the screen
    CONSENT_CONTROL = 1;            // Technician will VIEW and CONTROL
}

// ============================================================================
// Auto-Update Messages
// ============================================================================

// Update command details (sent with AdminCommand or standalone)
message UpdateInfo {
    string version = 1;             // Target version (e.g., "0.2.0")
    string download_url = 2;        // HTTPS URL to download new binary
    string checksum_sha256 = 3;     // SHA-256 hash for verification
    bool mandatory = 4;             // If true, agent must update immediately
}

// Update status report (agent -> server)
message UpdateStatus {
    string current_version = 1;     // Current running version
    UpdateState state = 2;          // Current update state
    string error_message = 3;       // Error details if state is FAILED
    int32 progress_percent = 4;     // Download progress (0-100)
}

enum UpdateState {
    UPDATE_IDLE = 0;                // No update in progress
    UPDATE_CHECKING = 1;            // Checking for updates
    UPDATE_DOWNLOADING = 2;         // Downloading new binary
    UPDATE_VERIFYING = 3;           // Verifying checksum
    UPDATE_INSTALLING = 4;          // Installing (rename/copy)
    UPDATE_RESTARTING = 5;          // About to restart
    UPDATE_COMPLETE = 6;            // Update successful (after restart)
    UPDATE_FAILED = 7;              // Update failed
}

// ============================================================================
// Top-Level Message Wrapper
// ============================================================================

message Message {
    oneof payload {
        // Session
        SessionRequest session_request = 1;
        SessionResponse session_response = 2;

        // Video
        VideoFrame video_frame = 10;
        VideoAck video_ack = 11;
        SwitchDisplay switch_display = 12;

        // Cursor
        CursorShape cursor_shape = 15;
        CursorPosition cursor_position = 16;

        // Input
        MouseEvent mouse_event = 20;
        KeyEvent key_event = 21;
        SpecialKeyEvent special_key = 22;

        // Clipboard
        ClipboardData clipboard_data = 30;
        ClipboardRequest clipboard_request = 31;

        // Quality
        QualitySettings quality_settings = 40;
        LatencyReport latency_report = 41;

        // Control
        Heartbeat heartbeat = 50;
        HeartbeatAck heartbeat_ack = 51;
        Disconnect disconnect = 52;
        StartStream start_stream = 53;
        StopStream stop_stream = 54;
        AgentStatus agent_status = 55;

        // Chat
        ChatMessage chat_message = 60;

        // Admin commands (server -> agent)
        AdminCommand admin_command = 70;

        // Auto-update messages
        UpdateInfo update_info = 75;        // Server -> Agent: update available
        UpdateStatus update_status = 76;    // Agent -> Server: update progress

        // Attended-mode consent (Task 5)
        ConsentRequest consent_request = 80;   // Server/relay -> Agent: prompt end user
        ConsentResponse consent_response = 81; // Agent -> Server: end user's decision
    }
}
