///|
/// Voice gateway lifecycle state.
pub(all) enum VoiceGatewayState {
  Disconnected(reconnect_attempts~ : Int)
  Connecting
  Identifying
  SelectingProtocol
  Active
  Resuming
  FatallyClosed(code~ : Int)
} derive(Debug, Eq)

///|
/// Action after a voice WebSocket close: resume the session, rejoin from
/// scratch, or give up because reconnecting cannot succeed.
pub(all) enum VoiceReconnectPolicy {
  Resume
  Rejoin
  Fatal
} derive(Debug, Eq)

///|
/// Classify a voice WebSocket close code. A resumable transport failure still
/// requires a fresh join when no established voice session exists.
pub fn voice_on_close(code : Int?, has_session : Bool) -> VoiceReconnectPolicy {
  let policy = match code {
    // Unknown opcode: the client and server cannot speak the same protocol.
    Some(4001)
    // Failed payload decode: reconnecting would resend the same invalid data.
    | Some(4002)
    // Not authenticated: identify/resume credentials are invalid.
    | Some(4003)
    // Authentication failed: the voice token is invalid.
    | Some(4004)
    // Already authenticated: the client state machine is invalid.
    | Some(4005)
    // Server not found: the requested voice server is invalid.
    | Some(4011)
    // Unknown protocol: the selected transport protocol is unsupported.
    | Some(4012)
    // Disconnected: channel access or voice state is no longer valid.
    | Some(4014)
    // Unknown encryption mode: protocol selection cannot succeed unchanged.
    | Some(4016)
    // Bad request: the client sent a structurally invalid request.
    | Some(4020)
    // Disconnected rate limited: retrying this session cannot succeed safely.
    | Some(4021)
    // Call terminated: the server has ended this voice session.
    | Some(4022) => Fatal
    // Session no longer valid: the main gateway must issue a fresh voice join.
    Some(4006)
    // Session timed out: buffered resume state has expired.
    | Some(4009)
    // A normal/going-away close invalidates the current voice session.
    | Some(1000)
    | Some(1001) => Rejoin
    // Voice server crashed: Discord explicitly permits buffered resume.
    Some(4015) => Resume
    // Abnormal and unclassified failures are treated as transient.
    _ => Resume
  }
  if policy is Resume && !has_session {
    Rejoin
  } else {
    policy
  }
}