// This file is automatically generated, so please do not edit it.
// @generated by `flutter_rust_bridge`@ 2.12.0.

// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import

import 'frb_generated.dart';
import 'lib.dart';
import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
import 'package:freezed_annotation/freezed_annotation.dart' hide protected;
part 'api.freezed.dart';

// These functions are ignored because they are not marked as `pub`: `install_panic_diagnostic_hook`, `log_file_path`, `log_sink`, `map_join_error_code`, `map_join_sync_state`, `open_log_file`, `permission_events`, `publish_permission_state`, `runtime`, `session`, `task_join_error`, `transmit_mode_from_u8`
// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `assert_fields_are_eq`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `clone`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `eq`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `fmt`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`, `from`
// These functions are ignored (category: IgnoreBecauseExplicitAttribute): `from_kotlin_str`, `to_permission_gate`

/// Return the platform-conventional log-file path as a string, or
/// an empty string if the platform does not have one (mobile).
String logFilePathStr() => RustLib.instance.api.crateApiLogFilePathStr();

/// Connect to a TeamSpeak-compatible server and return the initial
/// state snapshot. Honours the DEC-006 single-connection invariant
/// via [`BridgeError::AlreadyConnected`].
///
/// `password` is optional — pass an empty string for servers that
/// don't require one.
Future<BridgeSnapshot> connect({
  required String host,
  required String nickname,
  required String password,
}) => RustLib.instance.api.crateApiConnect(
  host: host,
  nickname: nickname,
  password: password,
);

/// Warm server address resolution for the active host field. This is
/// intentionally fire-and-forget from the UI perspective: it schedules
/// Rust-side prefetch work and never opens a TS3 session.
Future<void> prefetchServer({required String host}) =>
    RustLib.instance.api.crateApiPrefetchServer(host: host);

/// Re-fetch a fresh snapshot from the active connection.
Future<BridgeSnapshot> snapshot() => RustLib.instance.api.crateApiSnapshot();

/// Fetch richer profile and live connection details for one online client.
Future<BridgeClientProfile> clientProfile({required BigInt clientId}) =>
    RustLib.instance.api.crateApiClientProfile(clientId: clientId);

/// Disconnect from the server. No-op if not connected.
Future<void> disconnect() => RustLib.instance.api.crateApiDisconnect();

/// True if a connection is currently active.
Future<bool> isConnected() => RustLib.instance.api.crateApiIsConnected();

/// Handle iOS AVAudioSession route changes (SDD-100).
void handleRouteChange({required BridgeAudioRoute route}) =>
    RustLib.instance.api.crateApiHandleRouteChange(route: route);

/// Handle iOS AVAudioSession media-services reset with the current
/// route class. Called by AppDelegate after rebuilding the session.
///
/// `route_class` is the Swift-side route class string (e.g. "Speaker").
void handleMediaServicesResetWithRoute({required String routeClass}) => RustLib
    .instance
    .api
    .crateApiHandleMediaServicesResetWithRoute(routeClass: routeClass);

/// Handle iOS AVAudioSession interruption begin (SDD-101).
void handleInterruptionBegan() =>
    RustLib.instance.api.crateApiHandleInterruptionBegan();

/// Handle iOS AVAudioSession interruption end (SDD-101).
void handleInterruptionEnded({required bool shouldResume}) => RustLib
    .instance
    .api
    .crateApiHandleInterruptionEnded(shouldResume: shouldResume);

/// Set the focused/on-screen push-to-talk hold state.
///
/// Binding capture chooses which physical key drives PTT, while this
/// command carries the actual press/release edge for fallback focused
/// keyboard handling and touch controls.
Future<void> setPtt({required bool active}) =>
    RustLib.instance.api.crateApiSetPtt(active: active);

/// Join a voice channel (SDD-094). Moves the user to `channel_id`,
/// brings up the audio engine if needed, and emits
/// `BridgeEvent::VoiceState`. `password` may be empty.
Future<void> voiceJoin({required BigInt channelId, required String password}) =>
    RustLib.instance.api.crateApiVoiceJoin(
      channelId: channelId,
      password: password,
    );

/// Leave the current voice channel (SDD-094). Tears down the audio
/// engine and emits `BridgeEvent::VoiceState`.
Future<void> voiceLeave() => RustLib.instance.api.crateApiVoiceLeave();

/// Set the active transmit mode (SDD-095).
Future<void> setTransmitMode({required BridgeTransmitMode mode}) =>
    RustLib.instance.api.crateApiSetTransmitMode(mode: mode);

/// Read the active transmit mode.
Future<BridgeTransmitMode> getTransmitMode() =>
    RustLib.instance.api.crateApiGetTransmitMode();

/// Update the release-tail in milliseconds (SDD-096). Values are
/// clamped to `0..=500` on the Rust side; passing anything larger
/// silently saturates.
Future<void> setReleaseTailMs({required int ms}) =>
    RustLib.instance.api.crateApiSetReleaseTailMs(ms: ms);

/// Read the current release-tail in milliseconds.
Future<int> getReleaseTailMs() =>
    RustLib.instance.api.crateApiGetReleaseTailMs();

/// Engage or release the hard-mute clamp (SDD-094). When `true`
/// the audio engine transmits nothing regardless of mode.
Future<void> setHardMute({required bool muted}) =>
    RustLib.instance.api.crateApiSetHardMute(muted: muted);

/// Update the active PTT binding (gen2 v0.9.3 / DEC-026). The
/// platform_key string is opaque to the bridge — it identifies the
/// bound key inside the platform backend and never appears in any
/// log record or diagnostic export (DEC-027 enforced at the
/// diagnostics-sanitizer layer).
Future<void> setPttBinding({
  required BridgePttInputClass inputClass,
  required String platformKey,
}) => RustLib.instance.api.crateApiSetPttBinding(
  inputClass: inputClass,
  platformKey: platformKey,
);

/// Read the current PTT capability descriptor. Matches the privacy-safe
/// `BridgeEvent::PttCapability` event shape; useful for the initial UI
/// render before the first event arrives.
Future<BridgePttDescriptor> pttDescriptor() =>
    RustLib.instance.api.crateApiPttDescriptor();

/// Return the persisted PTT binding so the UI can hydrate its display
/// state at launch (e.g. show "PTT: Space" next to the badge before the
/// user re-opens the binding dialog). Empty strings mean no binding has
/// been persisted yet.
Future<BridgePttBinding> getPttBinding() =>
    RustLib.instance.api.crateApiGetPttBinding();

/// Move our own client to `channel_id`. Optional channel password
/// for password-protected channels — pass an empty string when not
/// required.
Future<void> moveToChannel({
  required BigInt channelId,
  required String password,
}) => RustLib.instance.api.crateApiMoveToChannel(
  channelId: channelId,
  password: password,
);

/// Toggle self input-mute (microphone) on the server. Independent
/// of push-to-talk: a muted client never transmits regardless of
/// PTT state.
Future<void> setInputMuted({required bool muted}) =>
    RustLib.instance.api.crateApiSetInputMuted(muted: muted);

/// Toggle self output-mute (speaker). Mutes locally *and* informs
/// the server. The server uses this for the channel icon next to
/// the client name; the local mute kicks in immediately even
/// before the server acknowledges.
Future<void> setOutputMuted({required bool muted}) =>
    RustLib.instance.api.crateApiSetOutputMuted(muted: muted);

/// Set master output gain. `1.0` is unity, `0.0` is silent. Values
/// above `1.0` amplify and can clip downstream. Errors when audio
/// is not started.
Future<void> setOutputGain({required double gain}) =>
    RustLib.instance.api.crateApiSetOutputGain(gain: gain);

/// Set per-client output volume (SRS-075). `1.0` is unity, `0.0`
/// mutes. No-op when client has no active voice queue. Volume is
/// applied directly to the tsclientlib AudioQueue and takes effect
/// immediately on the next render callback.
Future<void> setClientVolume({
  required BigInt clientId,
  required double volume,
}) => RustLib.instance.api.crateApiSetClientVolume(
  clientId: clientId,
  volume: volume,
);

/// Send a text message to the specified target.
Future<void> sendChatMessage({
  required String message,
  required BridgeMessageTarget target,
}) => RustLib.instance.api.crateApiSendChatMessage(
  message: message,
  target: target,
);

/// User-initiated diagnostic export. Returns a multi-line text
/// blob, redacted per the production policy, that the user can
/// share or copy. DEC-016 forbids automatic uploads — this is the
/// only path that surfaces logs.
///
/// SDD-112 item 10 / SDD-113 item 7 / SDD-116 item 3: when an
/// Android voice session is open this export embeds the
/// `[audio.android]` verification-matrix fragment (requested /
/// achieved stream config, per-effect engagement, latency tier).
/// On non-Android targets or before a voice session opens the
/// section is omitted. Per SDD-090 every field in that section is
/// a device-side technical scalar — no PII admitted.
String exportDiagnostics() => RustLib.instance.api.crateApiExportDiagnostics();

/// Wire the identity persistence store to a platform-private
/// directory. Should be called once on app start after Flutter has
/// resolved `getApplicationSupportDirectory()` (or equivalent).
///
/// Subsequent [`connect`] calls will reuse the persisted identity,
/// or generate-and-persist a fresh one on first use. This keeps the
/// server-visible UID stable across app restarts.
///
/// Beta caveat: the identity is stored as a plain file (mode 0600
/// on Unix). It is *not* encrypted at rest. RISK-PoC-002 documents
/// this gap; the v0.4 storage rework lands the proper Secret
/// Service + Android Keystore + iOS Keychain backends.
Future<void> initStorage({required String dir}) =>
    RustLib.instance.api.crateApiInitStorage(dir: dir);

/// List persisted bookmarks.
Future<List<BridgeBookmark>> listBookmarks() =>
    RustLib.instance.api.crateApiListBookmarks();

/// Insert a bookmark and return its assigned id. The `id` field on
/// the input is ignored.
Future<PlatformInt64> addBookmark({required BridgeBookmark b}) =>
    RustLib.instance.api.crateApiAddBookmark(b: b);

/// Update an existing bookmark.
Future<void> updateBookmark({required BridgeBookmark b}) =>
    RustLib.instance.api.crateApiUpdateBookmark(b: b);

/// Delete a bookmark by id.
Future<void> deleteBookmark({required PlatformInt64 id}) =>
    RustLib.instance.api.crateApiDeleteBookmark(id: id);

/// Notify the core of the latest OS-reported connectivity state.
/// Called by the Flutter side from `connectivity_plus` callbacks.
/// The core's supervisor uses this to (a) pre-charge the watchdog
/// on Offline and (b) short-circuit reconnect backoff on Online.
void setNetworkState({required BridgeNetworkState state}) =>
    RustLib.instance.api.crateApiSetNetworkState(state: state);

/// Subscribe to lifecycle events. Each call yields a fresh
/// subscription; multiple subscribers are supported. On slow
/// consumers, events are dropped rather than blocking the supervisor
/// (consistent with `tokio::sync::broadcast::Receiver` semantics).
Stream<BridgeEvent> eventsStream() =>
    RustLib.instance.api.crateApiEventsStream();

/// Read audio statistics. Errors if no connection or audio not started.
Future<BridgeAudioStats> audioStats() =>
    RustLib.instance.api.crateApiAudioStats();

/// Apply the P1 audio-processing config.
Future<void> setAudioProcessingConfig({
  required BridgeAudioProcessingConfig config,
}) => RustLib.instance.api.crateApiSetAudioProcessingConfig(config: config);

/// Read the current audio-processing config.
///
/// Returns the live config as last applied to the audio engine.
/// Returns a default config when no session is active.
Future<BridgeAudioProcessingConfig> getAudioProcessingConfig() =>
    RustLib.instance.api.crateApiGetAudioProcessingConfig();

/// Read P1 audio-processing diagnostics.
Future<BridgeAudioProcessingStats> audioProcessingStats() =>
    RustLib.instance.api.crateApiAudioProcessingStats();

/// List available audio input and output devices from the platform.
Future<BridgeAudioDeviceList> listAudioDevices() =>
    RustLib.instance.api.crateApiListAudioDevices();

/// Set the preferred input device by id. Takes effect on next
/// `start_audio`.
Future<void> setInputDevice({String? id}) =>
    RustLib.instance.api.crateApiSetInputDevice(id: id);

/// Set the preferred output device by id.
Future<void> setOutputDevice({String? id}) =>
    RustLib.instance.api.crateApiSetOutputDevice(id: id);

/// Configure the VAD model path.
Future<void> setVadModelPath({required String path}) =>
    RustLib.instance.api.crateApiSetVadModelPath(path: path);

/// Enable or disable audio debug WAV dumping.
Future<void> enableAudioDebugWavDump({required bool enabled}) =>
    RustLib.instance.api.crateApiEnableAudioDebugWavDump(enabled: enabled);

/// Select the iOS voice-processing mode.
Future<void> setIosVoiceProcessingMode({
  required BridgeIosVoiceProcessingMode mode,
}) => RustLib.instance.api.crateApiSetIosVoiceProcessingMode(mode: mode);

/// Set the preferred audio output route (Android/iOS).
void setAudioOutputRoute({required BridgeAudioRoute route}) =>
    RustLib.instance.api.crateApiSetAudioOutputRoute(route: route);

/// Called from Flutter when the app enters background/foreground.
void recordLifecycleEvent({required String state}) =>
    RustLib.instance.api.crateApiRecordLifecycleEvent(state: state);

/// Bridge processing backend.
enum BridgeAudioBackend {
  /// Platform voice processing.
  platformVoiceProcessing,

  /// Sonora backend.
  sonora,

  /// WebRTC APM backend.
  webrtcApm,

  /// No-op backend.
  noop,
}

/// Audio device info from the platform.
class BridgeAudioDevice {
  /// Stable platform-reported device identifier.
  final String id;

  /// Human-readable device name.
  final String name;

  /// Additional device details useful for disambiguation.
  final String details;

  /// True if the OS reports this as the default device.
  final bool isDefault;

  /// True if Chanora currently has this device pinned.
  final bool isSelected;

  const BridgeAudioDevice({
    required this.id,
    required this.name,
    required this.details,
    required this.isDefault,
    required this.isSelected,
  });

  @override
  int get hashCode =>
      id.hashCode ^
      name.hashCode ^
      details.hashCode ^
      isDefault.hashCode ^
      isSelected.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeAudioDevice &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          name == other.name &&
          details == other.details &&
          isDefault == other.isDefault &&
          isSelected == other.isSelected;
}

/// List of available audio devices.
class BridgeAudioDeviceList {
  /// Available input devices.
  final List<BridgeAudioDevice> inputDevices;

  /// Available output devices.
  final List<BridgeAudioDevice> outputDevices;

  const BridgeAudioDeviceList({
    required this.inputDevices,
    required this.outputDevices,
  });

  @override
  int get hashCode => inputDevices.hashCode ^ outputDevices.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeAudioDeviceList &&
          runtimeType == other.runtimeType &&
          inputDevices == other.inputDevices &&
          outputDevices == other.outputDevices;
}

/// P1 audio-processing configuration DTO.
class BridgeAudioProcessingConfig {
  /// Route class.
  final BridgeAudioRoute route;

  /// iOS voice-processing mode.
  final BridgeIosVoiceProcessingMode iosMode;

  /// Processing backend.
  final BridgeAudioBackend processingBackend;

  /// VAD backend.
  final BridgeVadBackend vadBackend;

  /// AEC owner.
  final BridgeEffectOwner aec;

  /// Noise suppression owner.
  final BridgeEffectOwner ns;

  /// AGC owner.
  final BridgeEffectOwner agc;

  /// High-pass filter enabled.
  final bool hpfEnabled;

  /// Limiter enabled.
  final bool limiterEnabled;

  /// VAD hangover in ms.
  final int vadHangoverMs;

  /// VAD pre-roll in ms.
  final int vadPreRollMs;

  /// Minimum transmit duration in ms.
  final int vadMinTxMs;

  /// Debug WAV dump enabled.
  final bool debugWavDumpEnabled;

  const BridgeAudioProcessingConfig({
    required this.route,
    required this.iosMode,
    required this.processingBackend,
    required this.vadBackend,
    required this.aec,
    required this.ns,
    required this.agc,
    required this.hpfEnabled,
    required this.limiterEnabled,
    required this.vadHangoverMs,
    required this.vadPreRollMs,
    required this.vadMinTxMs,
    required this.debugWavDumpEnabled,
  });

  @override
  int get hashCode =>
      route.hashCode ^
      iosMode.hashCode ^
      processingBackend.hashCode ^
      vadBackend.hashCode ^
      aec.hashCode ^
      ns.hashCode ^
      agc.hashCode ^
      hpfEnabled.hashCode ^
      limiterEnabled.hashCode ^
      vadHangoverMs.hashCode ^
      vadPreRollMs.hashCode ^
      vadMinTxMs.hashCode ^
      debugWavDumpEnabled.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeAudioProcessingConfig &&
          runtimeType == other.runtimeType &&
          route == other.route &&
          iosMode == other.iosMode &&
          processingBackend == other.processingBackend &&
          vadBackend == other.vadBackend &&
          aec == other.aec &&
          ns == other.ns &&
          agc == other.agc &&
          hpfEnabled == other.hpfEnabled &&
          limiterEnabled == other.limiterEnabled &&
          vadHangoverMs == other.vadHangoverMs &&
          vadPreRollMs == other.vadPreRollMs &&
          vadMinTxMs == other.vadMinTxMs &&
          debugWavDumpEnabled == other.debugWavDumpEnabled;
}

/// P1 audio-processing stats DTO.
class BridgeAudioProcessingStats {
  /// Input dBFS.
  final double inputDbfs;

  /// Render dBFS.
  final double renderDbfs;

  /// Processed capture dBFS.
  final double processedDbfs;

  /// Latest VAD probability.
  final double vadProbability;

  /// VAD active.
  final bool vadActive;

  /// Currently transmitting.
  final bool transmitting;

  /// VAD backend.
  final BridgeVadBackend vadBackend;

  /// Fallback VAD active.
  final bool vadFallbackActive;

  /// Processing backend.
  final BridgeAudioBackend processingBackend;

  /// iOS voice-processing mode.
  final BridgeIosVoiceProcessingMode iosVoiceProcessingMode;

  /// Audio route.
  final BridgeAudioRoute audioRoute;

  /// Actual sample rate.
  final int actualSampleRateHz;

  /// Actual IO buffer frames.
  final int actualIoBufferFrames;

  /// Input overruns.
  final BigInt inputOverruns;

  /// Output underruns.
  final BigInt outputUnderruns;

  /// Callback xruns.
  final BigInt callbackXruns;

  /// Clipped samples.
  final BigInt clippedSamples;

  /// Number of effectively silent processed capture frames.
  final BigInt zeroFrames;

  /// Number of processed capture frames.
  final BigInt captureFrames;

  /// Input callbacks carrying 10 ms of audio.
  final BigInt callbacks10Ms;

  /// Input callbacks carrying 20 ms of audio.
  final BigInt callbacks20Ms;

  /// Input callbacks carrying other sizes.
  final BigInt callbacksOther;

  /// Sonora enabled.
  final bool sonoraEnabled;

  /// Platform voice processing enabled.
  final bool platformVoiceProcessingEnabled;

  const BridgeAudioProcessingStats({
    required this.inputDbfs,
    required this.renderDbfs,
    required this.processedDbfs,
    required this.vadProbability,
    required this.vadActive,
    required this.transmitting,
    required this.vadBackend,
    required this.vadFallbackActive,
    required this.processingBackend,
    required this.iosVoiceProcessingMode,
    required this.audioRoute,
    required this.actualSampleRateHz,
    required this.actualIoBufferFrames,
    required this.inputOverruns,
    required this.outputUnderruns,
    required this.callbackXruns,
    required this.clippedSamples,
    required this.zeroFrames,
    required this.captureFrames,
    required this.callbacks10Ms,
    required this.callbacks20Ms,
    required this.callbacksOther,
    required this.sonoraEnabled,
    required this.platformVoiceProcessingEnabled,
  });

  @override
  int get hashCode =>
      inputDbfs.hashCode ^
      renderDbfs.hashCode ^
      processedDbfs.hashCode ^
      vadProbability.hashCode ^
      vadActive.hashCode ^
      transmitting.hashCode ^
      vadBackend.hashCode ^
      vadFallbackActive.hashCode ^
      processingBackend.hashCode ^
      iosVoiceProcessingMode.hashCode ^
      audioRoute.hashCode ^
      actualSampleRateHz.hashCode ^
      actualIoBufferFrames.hashCode ^
      inputOverruns.hashCode ^
      outputUnderruns.hashCode ^
      callbackXruns.hashCode ^
      clippedSamples.hashCode ^
      zeroFrames.hashCode ^
      captureFrames.hashCode ^
      callbacks10Ms.hashCode ^
      callbacks20Ms.hashCode ^
      callbacksOther.hashCode ^
      sonoraEnabled.hashCode ^
      platformVoiceProcessingEnabled.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeAudioProcessingStats &&
          runtimeType == other.runtimeType &&
          inputDbfs == other.inputDbfs &&
          renderDbfs == other.renderDbfs &&
          processedDbfs == other.processedDbfs &&
          vadProbability == other.vadProbability &&
          vadActive == other.vadActive &&
          transmitting == other.transmitting &&
          vadBackend == other.vadBackend &&
          vadFallbackActive == other.vadFallbackActive &&
          processingBackend == other.processingBackend &&
          iosVoiceProcessingMode == other.iosVoiceProcessingMode &&
          audioRoute == other.audioRoute &&
          actualSampleRateHz == other.actualSampleRateHz &&
          actualIoBufferFrames == other.actualIoBufferFrames &&
          inputOverruns == other.inputOverruns &&
          outputUnderruns == other.outputUnderruns &&
          callbackXruns == other.callbackXruns &&
          clippedSamples == other.clippedSamples &&
          zeroFrames == other.zeroFrames &&
          captureFrames == other.captureFrames &&
          callbacks10Ms == other.callbacks10Ms &&
          callbacks20Ms == other.callbacks20Ms &&
          callbacksOther == other.callbacksOther &&
          sonoraEnabled == other.sonoraEnabled &&
          platformVoiceProcessingEnabled ==
              other.platformVoiceProcessingEnabled;
}

/// Bridge route class for P1 audio-processing policy.
enum BridgeAudioRoute {
  /// Built-in speakerphone.
  speaker,

  /// Built-in receiver/earpiece.
  earpiece,

  /// Wired or USB headset.
  wiredHeadset,

  /// Bluetooth HFP duplex route.
  bluetoothHfp,

  /// Bluetooth A2DP output-only route.
  bluetoothA2Dp,

  /// Unknown route.
  unknown,
}

/// Statistics from the audio engine.
class BridgeAudioStats {
  /// Number of Opus frames sent since audio started.
  final int framesSent;

  /// Number of inbound voice packets decoded.
  final int framesReceived;

  /// Current push-to-talk state.
  final bool pttActive;

  const BridgeAudioStats({
    required this.framesSent,
    required this.framesReceived,
    required this.pttActive,
  });

  @override
  int get hashCode =>
      framesSent.hashCode ^ framesReceived.hashCode ^ pttActive.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeAudioStats &&
          runtimeType == other.runtimeType &&
          framesSent == other.framesSent &&
          framesReceived == other.framesReceived &&
          pttActive == other.pttActive;
}

/// Bookmark DTO mirroring [`chanora_core::Bookmark`].
class BridgeBookmark {
  /// Row id assigned by SQLite. Use `0` when adding new rows;
  /// the returned id is then meaningful.
  final PlatformInt64 id;

  /// User-facing label.
  final String displayName;

  /// `hostname[:port]` or TSDNS name.
  final String host;

  /// Nickname to use for this bookmark.
  final String nickname;

  /// Optional remembered password. Empty string = none.
  final String password;

  const BridgeBookmark({
    required this.id,
    required this.displayName,
    required this.host,
    required this.nickname,
    required this.password,
  });

  @override
  int get hashCode =>
      id.hashCode ^
      displayName.hashCode ^
      host.hashCode ^
      nickname.hashCode ^
      password.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeBookmark &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          displayName == other.displayName &&
          host == other.host &&
          nickname == other.nickname &&
          password == other.password;
}

/// Channel as seen by Dart. Matches `chanora_protocol::ChannelInfo`
/// but with primitive `u64` ids so the Dart side gets `BigInt`s
/// without any wrapper-type ceremony.
class BridgeChannel {
  /// Stable channel id.
  final BigInt id;

  /// Parent channel id; 0 means top-level.
  final BigInt parent;

  /// Display name.
  final String name;

  /// Server-side ordering hint.
  final PlatformInt64 order;

  /// True when the server marks the channel as password-protected.
  final bool hasPassword;

  /// Talk power threshold required to speak in this channel.
  /// None means no talk-power restriction.
  final int? neededTalkPower;

  const BridgeChannel({
    required this.id,
    required this.parent,
    required this.name,
    required this.order,
    required this.hasPassword,
    this.neededTalkPower,
  });

  @override
  int get hashCode =>
      id.hashCode ^
      parent.hashCode ^
      name.hashCode ^
      order.hashCode ^
      hasPassword.hashCode ^
      neededTalkPower.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeChannel &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          parent == other.parent &&
          name == other.name &&
          order == other.order &&
          hasPassword == other.hasPassword &&
          neededTalkPower == other.neededTalkPower;
}

/// Client as seen by Dart.
class BridgeClient {
  /// Stable client id.
  final BigInt id;

  /// Channel id the client is currently in.
  final BigInt channel;

  /// Nickname.
  final String name;

  /// True when this client has muted microphone/input capture.
  final bool inputMuted;

  /// True when this client has muted speaker/output audio.
  final bool outputMuted;

  /// True when recent inbound voice activity was observed for this client.
  final bool isSpeaking;

  /// True for TeamSpeak ServerQuery clients.
  final bool isServerQuery;

  /// Current talk power value assigned by the server.
  final int talkPower;

  /// True when the server has granted talk power regardless of numeric value.
  final bool talkPowerGranted;

  const BridgeClient({
    required this.id,
    required this.channel,
    required this.name,
    required this.inputMuted,
    required this.outputMuted,
    required this.isSpeaking,
    required this.isServerQuery,
    required this.talkPower,
    required this.talkPowerGranted,
  });

  @override
  int get hashCode =>
      id.hashCode ^
      channel.hashCode ^
      name.hashCode ^
      inputMuted.hashCode ^
      outputMuted.hashCode ^
      isSpeaking.hashCode ^
      isServerQuery.hashCode ^
      talkPower.hashCode ^
      talkPowerGranted.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeClient &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          channel == other.channel &&
          name == other.name &&
          inputMuted == other.inputMuted &&
          outputMuted == other.outputMuted &&
          isSpeaking == other.isSpeaking &&
          isServerQuery == other.isServerQuery &&
          talkPower == other.talkPower &&
          talkPowerGranted == other.talkPowerGranted;
}

/// Rich profile and live connection details for one online client.
class BridgeClientProfile {
  /// Stable online client id.
  final BigInt id;

  /// Channel the client is currently in.
  final BigInt channel;

  /// Nickname.
  final String name;

  /// TeamSpeak unique id.
  final String uniqueId;

  /// Stable TeamSpeak database id, when visible.
  final BigInt? databaseId;

  /// ISO country code, when visible.
  final String countryCode;

  /// User description, when visible.
  final String description;

  /// Client version string.
  final String version;

  /// Client platform string.
  final String platform;

  /// Account creation time as Unix seconds.
  final PlatformInt64? createdUnixSeconds;

  /// Last connection time as Unix seconds.
  final PlatformInt64? lastConnectedUnixSeconds;

  /// Total historical connections.
  final BigInt? connectionsTotal;

  /// Current online duration in seconds.
  final PlatformInt64? onlineSeconds;

  /// Current idle time in milliseconds.
  final PlatformInt64? idleMilliseconds;

  /// Current ping in milliseconds.
  final PlatformInt64? pingMilliseconds;

  /// Client address. Empty when permission-gated.
  final String clientAddress;

  /// Resolved server group names.
  final List<String> serverGroups;

  /// Resolved channel group name.
  final String channelGroup;

  /// TeamSpeak avatar file path suffix.
  final String avatarPath;

  /// Downloaded bytes this month.
  final BigInt? bytesDownloadedMonth;

  /// Uploaded bytes this month.
  final BigInt? bytesUploadedMonth;

  /// Downloaded bytes across all time.
  final BigInt? bytesDownloadedTotal;

  /// Uploaded bytes across all time.
  final BigInt? bytesUploadedTotal;

  /// Client-to-server total packet loss ratio.
  final double? packetLossClientToServerTotal;

  /// Server-to-client total packet loss ratio.
  final double? packetLossServerToClientTotal;

  const BridgeClientProfile({
    required this.id,
    required this.channel,
    required this.name,
    required this.uniqueId,
    this.databaseId,
    required this.countryCode,
    required this.description,
    required this.version,
    required this.platform,
    this.createdUnixSeconds,
    this.lastConnectedUnixSeconds,
    this.connectionsTotal,
    this.onlineSeconds,
    this.idleMilliseconds,
    this.pingMilliseconds,
    required this.clientAddress,
    required this.serverGroups,
    required this.channelGroup,
    required this.avatarPath,
    this.bytesDownloadedMonth,
    this.bytesUploadedMonth,
    this.bytesDownloadedTotal,
    this.bytesUploadedTotal,
    this.packetLossClientToServerTotal,
    this.packetLossServerToClientTotal,
  });

  @override
  int get hashCode =>
      id.hashCode ^
      channel.hashCode ^
      name.hashCode ^
      uniqueId.hashCode ^
      databaseId.hashCode ^
      countryCode.hashCode ^
      description.hashCode ^
      version.hashCode ^
      platform.hashCode ^
      createdUnixSeconds.hashCode ^
      lastConnectedUnixSeconds.hashCode ^
      connectionsTotal.hashCode ^
      onlineSeconds.hashCode ^
      idleMilliseconds.hashCode ^
      pingMilliseconds.hashCode ^
      clientAddress.hashCode ^
      serverGroups.hashCode ^
      channelGroup.hashCode ^
      avatarPath.hashCode ^
      bytesDownloadedMonth.hashCode ^
      bytesUploadedMonth.hashCode ^
      bytesDownloadedTotal.hashCode ^
      bytesUploadedTotal.hashCode ^
      packetLossClientToServerTotal.hashCode ^
      packetLossServerToClientTotal.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeClientProfile &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          channel == other.channel &&
          name == other.name &&
          uniqueId == other.uniqueId &&
          databaseId == other.databaseId &&
          countryCode == other.countryCode &&
          description == other.description &&
          version == other.version &&
          platform == other.platform &&
          createdUnixSeconds == other.createdUnixSeconds &&
          lastConnectedUnixSeconds == other.lastConnectedUnixSeconds &&
          connectionsTotal == other.connectionsTotal &&
          onlineSeconds == other.onlineSeconds &&
          idleMilliseconds == other.idleMilliseconds &&
          pingMilliseconds == other.pingMilliseconds &&
          clientAddress == other.clientAddress &&
          serverGroups == other.serverGroups &&
          channelGroup == other.channelGroup &&
          avatarPath == other.avatarPath &&
          bytesDownloadedMonth == other.bytesDownloadedMonth &&
          bytesUploadedMonth == other.bytesUploadedMonth &&
          bytesDownloadedTotal == other.bytesDownloadedTotal &&
          bytesUploadedTotal == other.bytesUploadedTotal &&
          packetLossClientToServerTotal ==
              other.packetLossClientToServerTotal &&
          packetLossServerToClientTotal == other.packetLossServerToClientTotal;
}

/// Bridge effect owner for AEC/NS/AGC.
enum BridgeEffectOwner {
  /// Platform-owned effect.
  platform,

  /// Sonora-owned effect.
  sonora,

  /// WebRTC APM-owned effect.
  webrtcApm,

  /// Conservative route-managed setting.
  conservative,

  /// Disabled.
  off,
}

@freezed
sealed class BridgeEvent with _$BridgeEvent {
  const BridgeEvent._();

  /// Initial connect succeeded, or a reconnect attempt succeeded.
  const factory BridgeEvent.connected({
    /// Server name reported by the server snapshot.
    required String serverName,
  }) = BridgeEvent_Connected;

  /// Connection lost; supervisor will retry.
  const factory BridgeEvent.lost({
    /// Reason classification from the protocol layer.
    required String reason,
  }) = BridgeEvent_Lost;

  /// Supervisor is sleeping before its next reconnect attempt.
  const factory BridgeEvent.reconnecting({
    /// 1-based attempt counter for the current outage.
    required int attempt,

    /// Seconds the supervisor will sleep before this attempt.
    required int delaySecs,
  }) = BridgeEvent_Reconnecting;

  /// Session ended (user-requested disconnect or unrecoverable).
  const factory BridgeEvent.disconnected({
    /// Reason classification.
    required String reason,
  }) = BridgeEvent_Disconnected;

  /// Audio engine started.
  const factory BridgeEvent.audioStarted() = BridgeEvent_AudioStarted;

  /// Audio engine stopped.
  const factory BridgeEvent.audioStopped() = BridgeEvent_AudioStopped;

  /// Snapshot probe observed a change in channel/client counts.
  /// UI uses this to drive an auto-refresh without active
  /// polling.
  const factory BridgeEvent.snapshotChanged({
    /// Latest channel count.
    required int channels,

    /// Latest client count.
    required int clients,
  }) = BridgeEvent_SnapshotChanged;

  /// Detected desktop Push-to-Talk capability (gen2 v0.9.3,
  /// DEC-023..028). The fields carry only privacy-safe values per
  /// DEC-027: the capability level, a stable backend identifier,
  /// and the bound input class. No key codes, scan codes, or
  /// virtual-key values cross this boundary.
  const factory BridgeEvent.pttCapability({
    /// Stable capability identifier (`"L0Focused"`,
    /// `"L1GlobalShortcut"`, `"L2GlobalHoldToTalk"`,
    /// `"L3GlobalWithMouseButtons"`, or `"L4DeviceAware"`).
    required String level,

    /// Stable backend identifier (e.g. `"focused"`).
    required String backendId,

    /// Coarse bound input class (e.g. `"keyboard"`,
    /// `"mouse-side-button"`); empty when no binding is active.
    required String boundInputClass,
  }) = BridgeEvent_PttCapability;

  /// Voice subsystem state snapshot (SDD-094). The Flutter
  /// VoiceBar listens to this stream.
  const factory BridgeEvent.voiceState({
    /// True when the session is currently joined to a voice
    /// channel and the audio engine is running.
    required bool inChannel,

    /// Active transmit mode.
    required BridgeTransmitMode transmitMode,

    /// True when the hard-mute clamp is engaged.
    required bool mute,

    /// Current release-tail in milliseconds (0..=500).
    required int releaseTailMs,

    /// Last confirmed authoritative channel id.
    BigInt? currentChannelId,

    /// Non-authoritative pending join target channel id.
    BigInt? pendingTargetChannelId,

    /// Whether a new join intent is currently allowed.
    required bool canJoin,

    /// Whether leave intent is currently allowed.
    required bool canLeave,

    /// Join projection sync state.
    required BridgeVoiceJoinSyncState joinSyncState,

    /// Last stable join error code, if any.
    BridgeVoiceJoinErrorCode? joinErrorCode,
  }) = BridgeEvent_VoiceState;

  /// iOS audio interruption state (SDD-101).
  const factory BridgeEvent.interruptionState({
    /// True when interruption began, false when it ended.
    required bool began,

    /// Resume recommendation from the platform. False on begin.
    required bool shouldResume,
  }) = BridgeEvent_InterruptionState;

  /// SDD-106 §5: resolved platform-level permission state. On
  /// Android this is published by the JNI hook in
  /// [`crate::permission_jni`] whenever
  /// `AndroidPermissionRequester` reports a state transition
  /// (initial grant, denial, permanent denial, or mid-session
  /// revocation). The audio engine's `TransmitModeSelector`
  /// observes the `RECORD_AUDIO` variant of this event as an
  /// authoritative clamp on the transmit gate per SDD-106 §6
  /// (and SRS-209's listen-only fail-safe).
  ///
  /// Carries the canonical Android permission string in
  /// `permission` (e.g. `"android.permission.RECORD_AUDIO"`)
  /// and the resolved state in `state`. No raw user input,
  /// timestamps, or other PII cross this boundary.
  const factory BridgeEvent.permissionState({
    /// Canonical Android permission identifier.
    required String permission,

    /// Resolved permission state.
    required PermissionStateKind state,
  }) = BridgeEvent_PermissionState;

  /// A text message received from the server.
  const factory BridgeEvent.chatMessage({
    /// Client id of the sender.
    required BigInt senderId,

    /// Nickname of the sender.
    required String senderName,

    /// Message content.
    required String message,

    /// Target scope (server/channel/private/poke).
    required BridgeMessageTarget target,
  }) = BridgeEvent_ChatMessage;

  /// Human-readable server activity surfaced from protocol bookkeeping events.
  const factory BridgeEvent.serverActivity({
    /// TeamSpeak-style activity line.
    required String message,
  }) = BridgeEvent_ServerActivity;

  /// Audio route changed (speaker/earpiece/BT/wired).
  const factory BridgeEvent.audioRouteChanged({
    /// The new audio route.
    required BridgeAudioRoute route,
  }) = BridgeEvent_AudioRouteChanged;
}

/// Bridge iOS voice-processing mode.
enum BridgeIosVoiceProcessingMode {
  /// Shipping VPIO path.
  platformVoiceProcessing,

  /// Experimental Sonora path.
  sonoraExperimental,
}

@freezed
sealed class BridgeMessageTarget with _$BridgeMessageTarget {
  const BridgeMessageTarget._();

  /// Broadcast to entire server.
  const factory BridgeMessageTarget.server() = BridgeMessageTarget_Server;

  /// Broadcast to current channel.
  const factory BridgeMessageTarget.channel() = BridgeMessageTarget_Channel;

  /// Private message to a specific client.
  const factory BridgeMessageTarget.client(BigInt field0) =
      BridgeMessageTarget_Client;

  /// Poke a specific client.
  const factory BridgeMessageTarget.poke(BigInt field0) =
      BridgeMessageTarget_Poke;
}

/// Coarse OS-reported network state. Mirrors
/// [`chanora_core::NetworkState`] across the bridge.
enum BridgeNetworkState {
  /// No signal seen yet.
  unknown,

  /// OS reports a usable network.
  online,

  /// OS reports no network.
  offline,
}

/// Persisted PTT binding display state for the UI.
class BridgePttBinding {
  /// Stable input category string (`""`, `"keyboard"`, or
  /// `"mouse-side-button"`).
  final String inputClass;

  /// Display-only key label; empty when no binding is active.
  final String keyLabel;

  const BridgePttBinding({required this.inputClass, required this.keyLabel});

  @override
  int get hashCode => inputClass.hashCode ^ keyLabel.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgePttBinding &&
          runtimeType == other.runtimeType &&
          inputClass == other.inputClass &&
          keyLabel == other.keyLabel;
}

/// Privacy-safe PTT capability descriptor for the UI.
class BridgePttDescriptor {
  /// Stable capability level name.
  final String level;

  /// Stable backend identifier.
  final String backendId;

  /// Coarse bound input class; empty when no binding is active.
  final String boundInputClass;

  const BridgePttDescriptor({
    required this.level,
    required this.backendId,
    required this.boundInputClass,
  });

  @override
  int get hashCode =>
      level.hashCode ^ backendId.hashCode ^ boundInputClass.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgePttDescriptor &&
          runtimeType == other.runtimeType &&
          level == other.level &&
          backendId == other.backendId &&
          boundInputClass == other.boundInputClass;
}

/// Coarse PTT input class (gen2 v0.9.3 / DEC-026). Stable strings;
/// the bridge never carries raw key codes.
enum BridgePttInputClass {
  /// No binding is active.
  none,

  /// A keyboard key.
  keyboard,

  /// A mouse side button (Mouse4 / Mouse5).
  mouseSideButton,
}

/// Server snapshot as seen by Dart.
class BridgeSnapshot {
  /// Server name.
  final String serverName;

  /// Welcome banner text.
  final String welcomeMessage;

  /// Server platform (e.g. "Linux").
  final String platform;

  /// Server version string.
  final String version;

  /// Channels currently known.
  final List<BridgeChannel> channels;

  /// Clients currently known.
  final List<BridgeClient> clients;

  /// Our own client id. Useful for the UI to highlight our row
  /// in the client list and to know which channel we are in
  /// without trusting the optimistic local state.
  final BigInt ownClientId;

  const BridgeSnapshot({
    required this.serverName,
    required this.welcomeMessage,
    required this.platform,
    required this.version,
    required this.channels,
    required this.clients,
    required this.ownClientId,
  });

  @override
  int get hashCode =>
      serverName.hashCode ^
      welcomeMessage.hashCode ^
      platform.hashCode ^
      version.hashCode ^
      channels.hashCode ^
      clients.hashCode ^
      ownClientId.hashCode;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is BridgeSnapshot &&
          runtimeType == other.runtimeType &&
          serverName == other.serverName &&
          welcomeMessage == other.welcomeMessage &&
          platform == other.platform &&
          version == other.version &&
          channels == other.channels &&
          clients == other.clients &&
          ownClientId == other.ownClientId;
}

/// Voice transmit mode mirror (SDD-095). Schema-controlled enum;
/// the wire encoding matches [`chanora_core::TransmitMode::as_u8`].
enum BridgeTransmitMode {
  /// Push-to-talk (default).
  ptt,

  /// Continuous transmission while in channel and not muted.
  continuous,

  /// Voice-activity detection — reserved per DEC-030; v1 behaves
  /// as `Continuous`.
  voiceActivity,
}

/// Bridge VAD backend.
enum BridgeVadBackend {
  /// Silero ONNX VAD.
  sileroOnnx,

  /// WebRTC fallback VAD.
  webrtcVad,

  /// Debug energy VAD.
  energyDebug,

  /// VAD disabled.
  disabled,
}

/// Bridge mirror of stable join error/status codes.
enum BridgeVoiceJoinErrorCode {
  /// Duplicate same-target join intent was coalesced.
  duplicateSameTargetCoalesced,

  /// A different target was requested while one is already pending.
  joinAlreadyPendingDifferentTarget,

  /// Join denied by server policy/permission.
  joinDenied,

  /// Join failed due to protocol-level error.
  joinProtocolFailure,

  /// Join failed due to transport/network error.
  joinNetworkFailure,

  /// Join timed out awaiting confirmation.
  joinTimeout,

  /// Pending join was superseded by user leave.
  joinSupersededByLeave,

  /// Stale join outcome was ignored.
  joinStaleOutcomeIgnored,

  /// Authoritative membership reconciled to different channel.
  joinReconciledDifferentChannel,

  /// Join command was rejected before send acceptance.
  joinCommandRejectedBeforeSend,

  /// Join intent rejected while reducer synchronizing.
  joinCannotStartWhileSynchronizing,
}

/// Bridge mirror of core join projection sync state.
enum BridgeVoiceJoinSyncState {
  /// Reducer is ready to accept channel actions.
  ready,

  /// Reducer is waiting on initial snapshot reconciliation.
  synchronizingInitialSnapshot,

  /// Reducer is waiting on reconnect snapshot reconciliation.
  synchronizingReconnect,
}

/// Schema-controlled mirror of the Kotlin
/// `AndroidPermissionRequester.PermissionState` sealed class
/// (SDD-106 §5). Crosses the bridge as an enum so the Dart side can
/// `switch` on it exhaustively without parsing strings.
enum PermissionStateKind {
  /// Permission granted by the user; capture may proceed.
  granted,

  /// Permission denied (re-promptable).
  denied,

  /// Permission permanently denied — the UI is expected to
  /// deep-link to system settings (SDD-106 §3).
  permanentlyDenied,

  /// Any state string that did not match the contract above.
  /// Treated identically to `Denied` by the transmit clamp
  /// (fail-safe per SRS-209).
  unknown,
}
