// VoiceCat control-plane wire format. SOURCE OF TRUTH for the protocol.
// Spec: docs/protocol.md. Each TCP/TLS frame is [u32 length][encoded Envelope].
// Media frames (voice) are NOT here — they use the fixed binary header in docs/voice.md §2.
//
// Extensibility rules (docs/protocol.md §8): never reuse/renumber tags; new oneof arms and
// fields are additive; gate new features behind capability strings in ClientHello/ServerHello.
syntax = "proto3";
package voicecat.v1;

// ── Envelope ──────────────────────────────────────────────────────────────────
message Envelope {
  // Nonzero on a request; echoed in the matching response for correlation. 0 = unsolicited.
  uint64 request_id = 1;

  oneof body {
    // Session / handshake (tags 10–19)
    ClientHello          client_hello           = 10;
    ServerHello          server_hello           = 11;
    AuthRequest          auth_request           = 12;
    AuthResult           auth_result            = 13;
    Disconnect           disconnect             = 14;
    Ping                 ping                   = 15;
    Pong                 pong                   = 16;

    // State sync (20–29)
    ServerStateSnapshot  server_state           = 20;
    ChannelEvent         channel_event          = 21;
    UserEvent            user_event             = 22;
    SubscribeRequest     subscribe              = 23;

    // Channel operations (30–39)
    JoinChannelRequest   join_channel           = 30;
    JoinChannelResult    join_channel_result    = 31;
    LeaveChannelRequest  leave_channel          = 32;
    CreateChannelRequest create_channel         = 33;
    EditChannelRequest   edit_channel           = 34;
    DeleteChannelRequest delete_channel         = 35;
    MoveUserRequest      move_user              = 36;
    GenericResult        generic_result         = 37;

    // Voice signaling — media is on UDP (40–49)
    StreamAnnounce       stream_announce        = 40;
    StreamAnnounceResult stream_announce_result = 41;
    StreamStop           stream_stop            = 42;
    StreamStateUpdate    stream_state           = 43;
    UdpBinding           udp_binding            = 44;

    // Text (50–59)
    TextMessage          text_message           = 50;
    TextMessageAck       text_message_ack       = 51;
    TypingIndicator      typing                 = 52;

    // Moderation / permissions (60–69)
    KickRequest          kick                   = 60;
    BanRequest           ban                    = 61;
    SetPermissionRequest set_permission         = 62;
    ServerMuteRequest    server_mute            = 63;

    // Admin account management — privileged; accounts are admin-provisioned (70–79)
    CreateAccountRequest create_account         = 70;
    ResetPasswordRequest reset_password         = 71;
    DeleteAccountRequest delete_account         = 72;
    ListAccountsRequest  list_accounts          = 73;
    ListAccountsResult   list_accounts_result   = 74;

    // Future families: file transfer = 100–109. Extension escape hatch = 200+.
    Extension            extension              = 200;
  }
}

// ── Enums ──────────────────────────────────────────────────────────────────────
enum ChannelType  { CHANNEL_PERMANENT = 0; CHANNEL_TEMPORARY = 1; }
enum ChannelMode  { MODE_MONO = 0; MODE_STEREO = 1; }
enum StreamKind   { STREAM_MIC = 0; STREAM_SCREEN_AUDIO = 1; STREAM_AUX_DEVICE = 2; }
enum TextScope    { TEXT_CHANNEL = 0; TEXT_PRIVATE = 1; TEXT_SERVER = 2; }
enum OpusApplication { OPUS_VOIP = 0; OPUS_AUDIO = 1; OPUS_LOWDELAY = 2; }

// ── Common types ────────────────────────────────────────────────────────────────
message AudioConfig {
  uint32 codec = 1;                 // 0 = OPUS
  ChannelMode mode = 2;
  uint32 sample_rate = 3;           // 48000 recommended
  uint32 bitrate_bps = 4;
  uint32 frame_ms = 5;              // 2.5/5/10/20/40/60
  OpusApplication application = 6;
  bool   fec = 7;
  uint32 expected_packet_loss = 8;  // %
  bool   dtx = 9;
  uint32 complexity = 10;           // 0..10
  bool   dred = 11;                 // Deep REDundancy (Opus 1.6); off by default
}

message StreamInfo {
  uint32 stream_id = 1;             // unique within the user
  uint32 ssrc = 2;                  // media-plane id assigned by server
  StreamKind kind = 3;
  AudioConfig audio = 4;
  string label = 5;
}

message Channel {
  uint32 id = 1;
  uint32 parent_id = 2;             // 0 = root
  string name = 3;
  string topic = 4;
  bool   password_protected = 5;
  uint32 max_users = 6;
  ChannelType type = 7;
  AudioConfig audio = 8;
  int32  order = 9;
}

message User {
  uint32 id = 1;
  string nickname = 2;
  bool   is_guest = 3;
  uint32 channel_id = 4;
  bool   self_mic_muted = 5;
  bool   self_deafened = 6;
  bool   server_muted = 7;
  repeated StreamInfo streams = 8;
  bool   server_deafened = 9;  // M5: server-imposed deafen
}

message Permissions {
  // Minimal v1 flag set; the moderation milestone expands this. Server-side is authoritative.
  bool can_create_temp_channel = 1;
  bool can_kick = 2;
  bool can_ban = 3;
  bool can_move_users = 4;
  bool can_admin_accounts = 5;
  bool is_admin = 6;
}

// ── Session / handshake ─────────────────────────────────────────────────────────
message ClientHello {
  uint32 proto_version = 1;
  repeated string features = 2;     // "opus", "fec", "screen-audio", ...
  string client_name = 3;
  string client_version = 4;
  string preferred_locale = 5;
}

message ServerHello {
  uint32 proto_version = 1;
  repeated string features = 2;
  string server_name = 3;
  string server_version = 4;
  repeated string auth_methods = 5; // "guest", "password"
  uint32 udp_port = 6;
  bytes  server_identity_fingerprint = 7;  // Ed25519 fp for TOFU
}

message GuestAuth    { string nickname = 1; }
message PasswordAuth { string username = 1; string password = 2; }

message AuthRequest {
  oneof method {
    GuestAuth guest = 1;
    PasswordAuth password = 2;
  }
}

message AuthResult {
  bool   ok = 1;
  string error = 2;
  uint64 session_id = 3;
  User   self = 4;
  Permissions permissions = 5;
  bytes  udp_token = 6;             // bind the UDP 5-tuple with this (security.md §3)
}

message Disconnect { uint32 code = 1; string reason = 2; }
message Ping { uint64 nonce = 1; }
message Pong { uint64 nonce = 1; }

// ── State sync ──────────────────────────────────────────────────────────────────
message ServerStateSnapshot {
  repeated Channel channels = 1;
  repeated User users = 2;
}
message ChannelEvent {
  enum Kind { CREATED = 0; UPDATED = 1; DELETED = 2; }
  Kind kind = 1;
  Channel channel = 2;
  uint32 deleted_id = 3;
}
message UserEvent {
  enum Kind { JOINED = 0; LEFT = 1; UPDATED = 2; }
  Kind kind = 1;
  User user = 2;
  uint32 left_id = 3;
  string reason = 4;  // M5: kick/ban reason for LEFT events
}
message SubscribeRequest { repeated uint32 channel_ids = 1; bool presence = 2; }

// ── Channel operations ──────────────────────────────────────────────────────────
message JoinChannelRequest { uint32 channel_id = 1; string password = 2; }
message JoinChannelResult {
  bool ok = 1; string error = 2;
  uint32 channel_id = 3;
  repeated User members = 4;
  AudioConfig audio = 5;            // authoritative channel Opus params
}
message LeaveChannelRequest {}
message CreateChannelRequest { Channel channel = 1; string password = 2; }
message EditChannelRequest   { Channel channel = 1; string password = 2; }
message DeleteChannelRequest { uint32 channel_id = 1; }
message MoveUserRequest      { uint32 user_id = 1; uint32 channel_id = 2; }
message GenericResult        { bool ok = 1; uint32 code = 2; string message = 3; }

// ── Voice signaling ─────────────────────────────────────────────────────────────
message StreamAnnounce      { StreamKind kind = 1; AudioConfig requested_audio = 2; string label = 3; }
message StreamAnnounceResult{ bool ok = 1; string error = 2; uint32 stream_id = 3; uint32 ssrc = 4; AudioConfig effective_audio = 5; }
message StreamStop          { uint32 stream_id = 1; }
message StreamStateUpdate   { uint32 user_id = 1; uint32 stream_id = 2; bool muted = 3; bool talking = 4; }
message UdpBinding          { bytes udp_token = 1; bool ack = 2; }

// ── Text (ephemeral — server does not persist history, docs/protocol.md §5) ──────
message TextMessage {
  TextScope scope = 1;
  uint32 target_id = 2;             // channel_id or user_id per scope
  uint32 sender_id = 3;             // set by server on relay
  string body = 4;                  // UTF-8, server-bounded length
  uint64 sent_at_unix_ms = 5;       // server timestamp on relay
  string client_msg_id = 6;         // echoed in ack (dedup)
}
message TextMessageAck   { string client_msg_id = 1; bool ok = 2; }
message TypingIndicator  { TextScope scope = 1; uint32 target_id = 2; uint32 user_id = 3; }

// ── Moderation / permissions ─────────────────────────────────────────────────────
message KickRequest          { uint32 user_id = 1; string reason = 2; }
message BanRequest           { uint32 user_id = 1; string reason = 2; uint64 expires_unix_ms = 3; }
message SetPermissionRequest { uint32 user_id = 1; Permissions permissions = 2; }
message ServerMuteRequest    { uint32 user_id = 1; bool muted = 2; bool deafened = 3; }

// ── Admin account management (privileged) ────────────────────────────────────────
message CreateAccountRequest { string username = 1; string password = 2; }
message ResetPasswordRequest { string username = 1; string new_password = 2; }
message DeleteAccountRequest { string username = 1; }
message ListAccountsRequest  {}
message AccountEntry         { string username = 1; bool is_admin = 2; uint64 created_at_unix_ms = 3; uint64 last_login_unix_ms = 4; }
message ListAccountsResult   { repeated AccountEntry accounts = 1; }

// ── Extension escape hatch ───────────────────────────────────────────────────────
message Extension { string ns = 1; bytes payload = 2; }
