/ app / lib / src / rust / api.dart
api.dart
   1  // This file is automatically generated, so please do not edit it.
   2  // @generated by `flutter_rust_bridge`@ 2.11.1.
   3  
   4  // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import
   5  
   6  import 'frb_generated.dart';
   7  import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart';
   8  import 'transport.dart';
   9  import 'transport/dht.dart';
  10  import 'transport/relay.dart';
  11  
  12  // These functions are ignored because they are not marked as `pub`: `get_db`, `get_dht_transport`, `get_exchanges`, `get_gossip_exchanges`, `get_gossip_peer_keys`, `get_gossip_transports`, `get_handshakes`, `get_iroh_relay_cache`, `get_iroh_transport`, `get_runtime`, `get_transport_preference`, `handle_group_management`, `next_handle`, `process_gossip_messages_for_us`, `queue_delivery_receipt`, `queue_reaction_message`, `search_result_to_info`, `send_message_internal`
  13  // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `ExchangeState`, `HandshakeState`
  14  // 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_receiver_is_total_eq`, `assert_receiver_is_total_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`, `clone`, `clone`, `clone`, `clone`, `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`, `fmt`, `fmt`, `fmt`, `fmt`
  15  
  16  /// Initialize the library with storage path and passphrase.
  17  ///
  18  /// Must be called before any other API functions.
  19  ///
  20  /// # Arguments
  21  ///
  22  /// * `storage_path` - Path to the database file
  23  /// * `passphrase` - User's passphrase for encryption
  24  ///
  25  /// # Errors
  26  ///
  27  /// Returns an error if the database cannot be opened or the passphrase is wrong.
  28  void initialize({required String storagePath, required String passphrase}) =>
  29      RustLib.instance.api.crateApiInitialize(
  30        storagePath: storagePath,
  31        passphrase: passphrase,
  32      );
  33  
  34  /// Check if the library has been initialized.
  35  bool isInitialized() => RustLib.instance.api.crateApiIsInitialized();
  36  
  37  /// Check if an identity exists.
  38  bool hasIdentity() => RustLib.instance.api.crateApiHasIdentity();
  39  
  40  /// Create a new identity.
  41  ///
  42  /// Generates new Ed25519 and X25519 key pairs.
  43  IdentityInfo createIdentity() => RustLib.instance.api.crateApiCreateIdentity();
  44  
  45  /// Get the current identity.
  46  IdentityInfo getIdentity() => RustLib.instance.api.crateApiGetIdentity();
  47  
  48  /// Generate QR code payload for sharing identity.
  49  String generateQrPayload() => RustLib.instance.api.crateApiGenerateQrPayload();
  50  
  51  /// Process a scanned QR code.
  52  ///
  53  /// Returns a pending contact that can be confirmed with `confirm_contact`.
  54  PendingContact processQrCode({required String payload}) =>
  55      RustLib.instance.api.crateApiProcessQrCode(payload: payload);
  56  
  57  /// Confirm a pending contact.
  58  ///
  59  /// Adds the contact to the database.
  60  void confirmContact({required PendingContact pending, String? nickname}) =>
  61      RustLib.instance.api.crateApiConfirmContact(
  62        pending: pending,
  63        nickname: nickname,
  64      );
  65  
  66  /// List all contacts.
  67  List<ContactInfo> listContacts() => RustLib.instance.api.crateApiListContacts();
  68  
  69  /// Get a specific contact.
  70  ContactInfo getContact({required List<int> contactId}) =>
  71      RustLib.instance.api.crateApiGetContact(contactId: contactId);
  72  
  73  /// Get a contact's identity public key (ed25519, 32 bytes).
  74  ///
  75  /// Used for transport-layer authentication when the transport doesn't
  76  /// have its own identity handshake (e.g., iroh).
  77  Uint8List getContactIdentityKey({required List<int> contactId}) =>
  78      RustLib.instance.api.crateApiGetContactIdentityKey(contactId: contactId);
  79  
  80  /// Update a contact's nickname.
  81  void updateContactNickname({required List<int> contactId, String? nickname}) =>
  82      RustLib.instance.api.crateApiUpdateContactNickname(
  83        contactId: contactId,
  84        nickname: nickname,
  85      );
  86  
  87  /// Block a contact.
  88  void blockContact({required List<int> contactId}) =>
  89      RustLib.instance.api.crateApiBlockContact(contactId: contactId);
  90  
  91  /// Unblock a contact.
  92  void unblockContact({required List<int> contactId}) =>
  93      RustLib.instance.api.crateApiUnblockContact(contactId: contactId);
  94  
  95  /// Refresh a contact's public keys from a new QR code payload.
  96  ///
  97  /// This is used when a contact's identity has changed (e.g., app reinstall)
  98  /// and they've shared a new QR code with their updated keys.
  99  ///
 100  /// # Arguments
 101  ///
 102  /// * `contact_id` - The existing contact ID to update
 103  /// * `qr_payload` - The new QR code payload containing updated keys
 104  ///
 105  /// # Returns
 106  ///
 107  /// Ok if keys were updated, Err if validation failed.
 108  void refreshContactKeys({
 109    required List<int> contactId,
 110    required String qrPayload,
 111  }) => RustLib.instance.api.crateApiRefreshContactKeys(
 112    contactId: contactId,
 113    qrPayload: qrPayload,
 114  );
 115  
 116  /// Delete a contact and all associated messages.
 117  void deleteContact({required List<int> contactId}) =>
 118      RustLib.instance.api.crateApiDeleteContact(contactId: contactId);
 119  
 120  /// Set the disappearing message duration for a contact.
 121  ///
 122  /// Duration is in seconds. Pass 0 to disable.
 123  /// Presets: 300 (5min), 3600 (1hr), 86400 (1 day), 604800 (1 week).
 124  void setDisappearingDuration({
 125    required List<int> contactId,
 126    required BigInt durationSecs,
 127  }) => RustLib.instance.api.crateApiSetDisappearingDuration(
 128    contactId: contactId,
 129    durationSecs: durationSecs,
 130  );
 131  
 132  /// Get the disappearing message duration for a contact.
 133  ///
 134  /// Returns duration in seconds (0 = off).
 135  BigInt getDisappearingDuration({required List<int> contactId}) =>
 136      RustLib.instance.api.crateApiGetDisappearingDuration(contactId: contactId);
 137  
 138  /// Set the muted state for a contact's notifications.
 139  void setContactMuted({required List<int> contactId, required bool muted}) =>
 140      RustLib.instance.api.crateApiSetContactMuted(
 141        contactId: contactId,
 142        muted: muted,
 143      );
 144  
 145  /// Get the muted state for a contact's notifications.
 146  bool getContactMuted({required List<int> contactId}) =>
 147      RustLib.instance.api.crateApiGetContactMuted(contactId: contactId);
 148  
 149  /// Compose and queue a message for delivery.
 150  ///
 151  /// Returns the message ID.
 152  /// Note: Async to avoid blocking UI during encryption of large messages.
 153  Future<Uint8List> sendMessage({
 154    required List<int> recipientId,
 155    required List<int> content,
 156    required int contentType,
 157    String? filename,
 158    int? ttlDays,
 159  }) => RustLib.instance.api.crateApiSendMessage(
 160    recipientId: recipientId,
 161    content: content,
 162    contentType: contentType,
 163    filename: filename,
 164    ttlDays: ttlDays,
 165  );
 166  
 167  /// Send a document, automatically chunking if larger than MAX_CHUNK_SIZE.
 168  ///
 169  /// For small files (<= 30KB), sends as a single Document message.
 170  /// For large files (> 30KB), splits into DocumentChunk messages.
 171  ///
 172  /// Returns the document ID (for single messages, this is the message ID;
 173  /// for chunked documents, this is the shared document_id across all chunks).
 174  Future<Uint8List> sendDocument({
 175    required List<int> recipientId,
 176    required List<int> content,
 177    required String filename,
 178    int? ttlDays,
 179  }) => RustLib.instance.api.crateApiSendDocument(
 180    recipientId: recipientId,
 181    content: content,
 182    filename: filename,
 183    ttlDays: ttlDays,
 184  );
 185  
 186  /// Reassemble a chunked document from received chunks.
 187  ///
 188  /// Returns the document content and filename if all chunks are present.
 189  /// Returns an error if the document is incomplete.
 190  DocumentInfo reassembleDocument({required List<int> documentId}) =>
 191      RustLib.instance.api.crateApiReassembleDocument(documentId: documentId);
 192  
 193  /// Check if all chunks have been received for a document.
 194  bool isDocumentComplete({required List<int> documentId}) =>
 195      RustLib.instance.api.crateApiIsDocumentComplete(documentId: documentId);
 196  
 197  /// Get document info (filename and size) without reassembling.
 198  (String, BigInt)? getDocumentInfo({required List<int> documentId}) =>
 199      RustLib.instance.api.crateApiGetDocumentInfo(documentId: documentId);
 200  
 201  /// Get outbound message queue for a contact.
 202  List<MessageInfo> getOutboundQueue({required List<int> contactId}) =>
 203      RustLib.instance.api.crateApiGetOutboundQueue(contactId: contactId);
 204  
 205  /// Get messages from a contact (inbound).
 206  List<MessageInfo> getMessages({required List<int> contactId}) =>
 207      RustLib.instance.api.crateApiGetMessages(contactId: contactId);
 208  
 209  /// Get conversation (both inbound and outbound) with a contact.
 210  List<MessageInfo> getConversation({required List<int> contactId}) =>
 211      RustLib.instance.api.crateApiGetConversation(contactId: contactId);
 212  
 213  /// Mark a message as read.
 214  void markRead({required List<int> messageId}) =>
 215      RustLib.instance.api.crateApiMarkRead(messageId: messageId);
 216  
 217  /// Delete a message.
 218  void deleteMessage({required List<int> messageId}) =>
 219      RustLib.instance.api.crateApiDeleteMessage(messageId: messageId);
 220  
 221  /// Clear all messages for a contact without deleting the contact.
 222  int clearConversation({required List<int> contactId}) =>
 223      RustLib.instance.api.crateApiClearConversation(contactId: contactId);
 224  
 225  /// Delete all user data from the database (contacts, messages, identity, groups, etc).
 226  void deleteAllData() => RustLib.instance.api.crateApiDeleteAllData();
 227  
 228  /// Get count of unread messages.
 229  int getUnreadCount() => RustLib.instance.api.crateApiGetUnreadCount();
 230  
 231  /// Retry sending a failed message.
 232  ///
 233  /// Requeues the message for transmission.
 234  void retryMessage({required List<int> messageId}) =>
 235      RustLib.instance.api.crateApiRetryMessage(messageId: messageId);
 236  
 237  /// Retry messages stuck in Transmitting state by resetting to Queued.
 238  ///
 239  /// Messages stuck for longer than `timeout_secs` are retried (max 5 attempts).
 240  /// Returns the number of messages retried.
 241  int retryStuckMessages({required PlatformInt64 timeoutSecs}) =>
 242      RustLib.instance.api.crateApiRetryStuckMessages(timeoutSecs: timeoutSecs);
 243  
 244  /// Expire stale outbound messages stuck in Queued or Transmitting state.
 245  ///
 246  /// Messages older than `max_age_secs` are marked as Failed.
 247  /// Returns the number of expired messages.
 248  int expireStaleMessages({required PlatformInt64 maxAgeSecs}) =>
 249      RustLib.instance.api.crateApiExpireStaleMessages(maxAgeSecs: maxAgeSecs);
 250  
 251  /// Promote outbound messages stuck in Queued to gossip relay.
 252  ///
 253  /// Messages that have been Queued for longer than `min_age_secs` are
 254  /// ensured to exist in the forwarding store and transitioned to PendingRelay.
 255  /// Returns the number of messages promoted.
 256  int promoteToGossipRelay({required PlatformInt64 minAgeSecs}) =>
 257      RustLib.instance.api.crateApiPromoteToGossipRelay(minAgeSecs: minAgeSecs);
 258  
 259  /// Expire PendingRelay messages older than `max_age_secs` (e.g. 48h).
 260  /// These have exceeded the forwarding store TTL and will never be delivered.
 261  /// Returns the number of messages marked Failed.
 262  int expirePendingRelay({required PlatformInt64 maxAgeSecs}) =>
 263      RustLib.instance.api.crateApiExpirePendingRelay(maxAgeSecs: maxAgeSecs);
 264  
 265  /// Create a ForwardedMessage from an outbound message that failed direct delivery.
 266  /// Sets the outbound state to PendingRelay so it's deliverable via BLE gossip mesh.
 267  bool queueForGossipRelay({required List<int> messageId}) =>
 268      RustLib.instance.api.crateApiQueueForGossipRelay(messageId: messageId);
 269  
 270  /// Add a reaction to a message.
 271  ///
 272  /// Returns the reaction ID.
 273  void addReaction({required List<int> messageId, required String emoji}) =>
 274      RustLib.instance.api.crateApiAddReaction(
 275        messageId: messageId,
 276        emoji: emoji,
 277      );
 278  
 279  /// Remove a reaction from a message.
 280  void removeReaction({required List<int> messageId, required String emoji}) =>
 281      RustLib.instance.api.crateApiRemoveReaction(
 282        messageId: messageId,
 283        emoji: emoji,
 284      );
 285  
 286  /// Toggle a reaction on a message (add if not present, remove if present).
 287  bool toggleReaction({required List<int> messageId, required String emoji}) =>
 288      RustLib.instance.api.crateApiToggleReaction(
 289        messageId: messageId,
 290        emoji: emoji,
 291      );
 292  
 293  /// Get transport delivery statistics.
 294  TransportStatsInfo getTransportStats() =>
 295      RustLib.instance.api.crateApiGetTransportStats();
 296  
 297  /// Get send progress for a chunked document.
 298  ///
 299  /// Returns (delivered_chunks, total_chunks) or None if not a chunked document.
 300  (int, int)? getSendProgress({required List<int> documentId}) =>
 301      RustLib.instance.api.crateApiGetSendProgress(documentId: documentId);
 302  
 303  /// Get receive progress for a chunked document.
 304  ///
 305  /// Returns (received_chunks, total_chunks) or None if not found.
 306  (int, int)? getReceiveProgress({required List<int> documentId}) =>
 307      RustLib.instance.api.crateApiGetReceiveProgress(documentId: documentId);
 308  
 309  /// Search messages within a conversation.
 310  ///
 311  /// Returns messages matching the query string (case-insensitive).
 312  List<SearchResultInfo> searchConversation({
 313    required List<int> contactId,
 314    required String query,
 315    required int limit,
 316  }) => RustLib.instance.api.crateApiSearchConversation(
 317    contactId: contactId,
 318    query: query,
 319    limit: limit,
 320  );
 321  
 322  /// Search all messages across all conversations.
 323  ///
 324  /// Returns messages matching the query string (case-insensitive).
 325  List<SearchResultInfo> searchAllMessages({
 326    required String query,
 327    required int limit,
 328  }) =>
 329      RustLib.instance.api.crateApiSearchAllMessages(query: query, limit: limit);
 330  
 331  /// Get my current rotating IDs for BLE advertising.
 332  ///
 333  /// Returns a list of rotating IDs, one for each active contact.
 334  /// Each ID is 16 bytes. Advertise all of these for contacts to discover you.
 335  List<Uint8List> getMyRotatingIds() =>
 336      RustLib.instance.api.crateApiGetMyRotatingIds();
 337  
 338  /// Check if a scanned rotating ID matches any contact.
 339  ///
 340  /// Returns the contact ID if found, None otherwise.
 341  Uint8List? checkRotatingId({required List<int> scannedId}) =>
 342      RustLib.instance.api.crateApiCheckRotatingId(scannedId: scannedId);
 343  
 344  /// Create a handshake initiator for connecting to a contact.
 345  HandshakeHandle createHandshakeInitiator({required List<int> contactId}) =>
 346      RustLib.instance.api.crateApiCreateHandshakeInitiator(contactId: contactId);
 347  
 348  /// Create an anonymous handshake initiator for gossip-only connections.
 349  ///
 350  /// Unlike `create_handshake_initiator`, this does not require a contact ID.
 351  /// Used for gossip relay with non-contact Dead Drop peers.
 352  HandshakeHandle createHandshakeInitiatorAnonymous() =>
 353      RustLib.instance.api.crateApiCreateHandshakeInitiatorAnonymous();
 354  
 355  /// Create a handshake responder for incoming connections.
 356  HandshakeHandle createHandshakeResponder() =>
 357      RustLib.instance.api.crateApiCreateHandshakeResponder();
 358  
 359  /// Generate the next handshake message to send.
 360  Uint8List handshakeGenerate({required HandshakeHandle handle}) =>
 361      RustLib.instance.api.crateApiHandshakeGenerate(handle: handle);
 362  
 363  /// Process a received handshake message.
 364  HandshakeResult handshakeProcess({
 365    required HandshakeHandle handle,
 366    required List<int> data,
 367  }) => RustLib.instance.api.crateApiHandshakeProcess(handle: handle, data: data);
 368  
 369  /// Finalize a completed handshake into an exchange session.
 370  ExchangeHandle handshakeFinalize({required HandshakeHandle handle}) =>
 371      RustLib.instance.api.crateApiHandshakeFinalize(handle: handle);
 372  
 373  /// Generate exchange data to send.
 374  Uint8List? exchangeSend({required ExchangeHandle handle}) =>
 375      RustLib.instance.api.crateApiExchangeSend(handle: handle);
 376  
 377  /// Process received exchange data.
 378  ExchangeResultDto exchangeReceive({
 379    required ExchangeHandle handle,
 380    required List<int> data,
 381  }) => RustLib.instance.api.crateApiExchangeReceive(handle: handle, data: data);
 382  
 383  /// Finalize an exchange and get results.
 384  ExchangeComplete exchangeFinalize({required ExchangeHandle handle}) =>
 385      RustLib.instance.api.crateApiExchangeFinalize(handle: handle);
 386  
 387  /// Clean up a handshake handle without finalizing.
 388  void handshakeCancel({required HandshakeHandle handle}) =>
 389      RustLib.instance.api.crateApiHandshakeCancel(handle: handle);
 390  
 391  /// Clean up an exchange handle without finalizing.
 392  void exchangeCancel({required ExchangeHandle handle}) =>
 393      RustLib.instance.api.crateApiExchangeCancel(handle: handle);
 394  
 395  /// Finalize a direct exchange AND create a gossip exchange session.
 396  ///
 397  /// Does everything `exchange_finalize` does (update delivered states, decrypt
 398  /// received messages, handle reactions/chunks) PLUS recovers the Noise
 399  /// transport and creates a `GossipExchangeOwned` for the gossip phase.
 400  (ExchangeComplete, GossipExchangeHandle) exchangeFinalizeForGossip({
 401    required ExchangeHandle handle,
 402  }) => RustLib.instance.api.crateApiExchangeFinalizeForGossip(handle: handle);
 403  
 404  /// Get the next gossip data to send over BLE.
 405  ///
 406  /// Returns encrypted bytes to send, or None if there's nothing to send
 407  /// (which means we're waiting for peer data).
 408  Uint8List? gossipExchangeSend({required GossipExchangeHandle handle}) =>
 409      RustLib.instance.api.crateApiGossipExchangeSend(handle: handle);
 410  
 411  /// Process encrypted gossip data received from BLE.
 412  bool gossipExchangeReceive({
 413    required GossipExchangeHandle handle,
 414    required List<int> data,
 415  }) => RustLib.instance.api.crateApiGossipExchangeReceive(
 416    handle: handle,
 417    data: data,
 418  );
 419  
 420  /// Finalize a gossip exchange and get results.
 421  GossipExchangeComplete gossipExchangeFinalize({
 422    required GossipExchangeHandle handle,
 423  }) => RustLib.instance.api.crateApiGossipExchangeFinalize(handle: handle);
 424  
 425  /// Cancel a gossip exchange and clean up resources.
 426  void gossipExchangeCancel({required GossipExchangeHandle handle}) =>
 427      RustLib.instance.api.crateApiGossipExchangeCancel(handle: handle);
 428  
 429  /// Finalize a gossip exchange but preserve the Noise transport for reuse.
 430  ///
 431  /// Returns the exchange results plus a transport handle that can be passed to
 432  /// `gossip_start_new_round()` to create a new gossip round over the same
 433  /// encrypted channel.
 434  GossipFinalizeKeepTransportResult gossipExchangeFinalizeKeepTransport({
 435    required GossipExchangeHandle handle,
 436  }) => RustLib.instance.api.crateApiGossipExchangeFinalizeKeepTransport(
 437    handle: handle,
 438  );
 439  
 440  /// Create a new gossip exchange round from a previously stored transport.
 441  ///
 442  /// Reuses the Noise transport from a prior `gossip_exchange_finalize_keep_transport()`
 443  /// call, creating a fresh gossip state machine for a new exchange round.
 444  GossipExchangeHandle gossipStartNewRound({required BigInt transportHandle}) =>
 445      RustLib.instance.api.crateApiGossipStartNewRound(
 446        transportHandle: transportHandle,
 447      );
 448  
 449  /// Release a stored gossip transport (cleanup on disconnect).
 450  void gossipTransportRelease({required BigInt transportHandle}) => RustLib
 451      .instance
 452      .api
 453      .crateApiGossipTransportRelease(transportHandle: transportHandle);
 454  
 455  /// Finalize a completed handshake directly into a gossip exchange, skipping Exchange.
 456  ///
 457  /// Used for anonymous gossip-only connections with non-contact peers.
 458  /// Extracts the NoiseTransport from the handshake and creates a GossipExchangeOwned
 459  /// for store-and-forward relay without requiring a contact relationship.
 460  GossipExchangeHandle handshakeFinalizeForGossip({
 461    required HandshakeHandle handle,
 462  }) => RustLib.instance.api.crateApiHandshakeFinalizeForGossip(handle: handle);
 463  
 464  /// Delete expired messages and clean up replay cache.
 465  /// Returns the total number of messages deleted.
 466  int runMaintenance() => RustLib.instance.api.crateApiRunMaintenance();
 467  
 468  /// Get database statistics.
 469  String getStatistics() => RustLib.instance.api.crateApiGetStatistics();
 470  
 471  /// Get a list of all transport types (available or not).
 472  ///
 473  /// This returns all transport types that Dead Drop supports, along with
 474  /// their availability status on this device.
 475  List<TransportInfoDto> getAvailableTransports() =>
 476      RustLib.instance.api.crateApiGetAvailableTransports();
 477  
 478  /// Check if a specific transport is available.
 479  ///
 480  /// # Arguments
 481  ///
 482  /// * `transport_type` - The transport type to check
 483  ///
 484  /// # Returns
 485  ///
 486  /// `true` if the transport is available and initialized.
 487  bool isTransportAvailable({required TransportTypeDto transportType}) => RustLib
 488      .instance
 489      .api
 490      .crateApiIsTransportAvailable(transportType: transportType);
 491  
 492  /// Get the human-readable name for a transport type.
 493  String getTransportName({required TransportTypeDto transportType}) =>
 494      RustLib.instance.api.crateApiGetTransportName(transportType: transportType);
 495  
 496  /// Get the current state of the relay transport.
 497  ///
 498  /// Note: This is a placeholder that returns disconnected state.
 499  /// Full relay integration requires transport manager setup.
 500  RelayStateDto getRelayState() => RustLib.instance.api.crateApiGetRelayState();
 501  
 502  /// Get the current state of the DHT transport.
 503  DhtStateDto getDhtState() => RustLib.instance.api.crateApiGetDhtState();
 504  
 505  /// Connect to a relay server.
 506  ///
 507  /// # Arguments
 508  ///
 509  /// * `url` - WebSocket URL of the relay server
 510  ///
 511  /// Note: This is a placeholder. Full implementation requires transport manager.
 512  void connectRelay({required String url}) =>
 513      RustLib.instance.api.crateApiConnectRelay(url: url);
 514  
 515  /// Disconnect from the current relay server.
 516  ///
 517  /// Note: This is a placeholder. Full implementation requires transport manager.
 518  void disconnectRelay() => RustLib.instance.api.crateApiDisconnectRelay();
 519  
 520  /// Bootstrap the DHT with a list of nodes.
 521  ///
 522  /// # Arguments
 523  ///
 524  /// * `nodes` - List of bootstrap node addresses (host:port format)
 525  ///
 526  /// Example nodes:
 527  /// - `router.bittorrent.com:6881`
 528  /// - `router.utorrent.com:6881`
 529  /// - `dht.transmissionbt.com:6881`
 530  ///
 531  /// Note: This is async to avoid blocking the UI thread.
 532  Future<void> bootstrapDht({required List<String> nodes}) =>
 533      RustLib.instance.api.crateApiBootstrapDht(nodes: nodes);
 534  
 535  /// Disconnect from the DHT network.
 536  ///
 537  /// Note: This is async to avoid blocking the UI thread.
 538  Future<void> disconnectDht() => RustLib.instance.api.crateApiDisconnectDht();
 539  
 540  /// Set the network transport preference.
 541  ///
 542  /// This controls which transport is preferred when multiple are available.
 543  ///
 544  /// # Arguments
 545  ///
 546  /// * `preference` - The transport preference to use
 547  void setTransportPreference({required NetworkTransportPreference preference}) =>
 548      RustLib.instance.api.crateApiSetTransportPreference(preference: preference);
 549  
 550  /// Get the current network transport preference.
 551  NetworkTransportPreference getTransportPreferenceValue() =>
 552      RustLib.instance.api.crateApiGetTransportPreferenceValue();
 553  
 554  /// Publish queued messages to the DHT for a contact.
 555  ///
 556  /// This takes all queued messages for the specified contact, packages them
 557  /// into DHT-storable format, and publishes them to the DHT network.
 558  ///
 559  /// # Arguments
 560  ///
 561  /// * `contact_id` - The contact to publish messages for
 562  ///
 563  /// # Returns
 564  ///
 565  /// The number of messages published and their IDs.
 566  /// Note: Truly async to avoid blocking UI - DB ops run on blocking thread,
 567  /// network ops run async.
 568  Future<DhtPublishResult> publishMessagesToDht({required List<int> contactId}) =>
 569      RustLib.instance.api.crateApiPublishMessagesToDht(contactId: contactId);
 570  
 571  /// Fetch messages from the DHT for our public key.
 572  ///
 573  /// This queries the DHT for messages stored for our public key,
 574  /// decrypts and validates them, and stores them as inbound messages.
 575  ///
 576  /// # Returns
 577  ///
 578  /// Messages and contacts with reaction updates.
 579  /// Note: Truly async to avoid blocking UI - phases structured to minimize lock time.
 580  Future<DhtFetchResult> fetchMessagesFromDht() =>
 581      RustLib.instance.api.crateApiFetchMessagesFromDht();
 582  
 583  /// Acknowledge DHT messages (mark as received).
 584  ///
 585  /// After successfully storing messages locally, call this to remove
 586  /// them from the DHT to prevent re-fetching.
 587  ///
 588  /// # Arguments
 589  ///
 590  /// * `message_ids` - IDs of messages to acknowledge
 591  void acknowledgeDhtMessages({required List<Uint8List> messageIds}) =>
 592      RustLib.instance.api.crateApiAcknowledgeDhtMessages(messageIds: messageIds);
 593  
 594  /// Get queued encrypted messages for a contact, ready for Tor P2P delivery.
 595  ///
 596  /// Returns serialized encrypted payloads that can be sent directly over
 597  /// a WebSocket connection. The messages remain queued until explicitly
 598  /// marked as delivered via [mark_delivered_via_tor].
 599  Future<List<EncryptedPayload>> getQueuedEncrypted({
 600    required List<int> contactId,
 601  }) => RustLib.instance.api.crateApiGetQueuedEncrypted(contactId: contactId);
 602  
 603  /// Re-queue any Failed messages for a contact so they can be retried.
 604  ///
 605  /// Resets OutboundState::Failed back to OutboundState::Queued.
 606  Future<int> requeueFailedMessages({required List<int> contactId}) =>
 607      RustLib.instance.api.crateApiRequeueFailedMessages(contactId: contactId);
 608  
 609  /// Mark messages as delivered via Tor P2P transport.
 610  ///
 611  /// Call after successfully sending messages over a WebSocket connection
 612  /// to update their delivery status.
 613  Future<void> markDeliveredViaTor({required List<Uint8List> messageIds}) =>
 614      RustLib.instance.api.crateApiMarkDeliveredViaTor(messageIds: messageIds);
 615  
 616  /// Set the delivery transport for outbound messages (only if not already set).
 617  ///
 618  /// Used to record which transport sent the message, even when the transport
 619  /// doesn't confirm delivery (e.g. iroh relay).
 620  Future<void> setOutboundTransport({
 621    required List<Uint8List> messageIds,
 622    required String transport,
 623  }) => RustLib.instance.api.crateApiSetOutboundTransport(
 624    messageIds: messageIds,
 625    transport: transport,
 626  );
 627  
 628  /// Process an encrypted message received via Tor P2P.
 629  ///
 630  /// Decrypts and stores the message, similar to how DHT-received messages
 631  /// are processed. Returns the message info if successfully processed.
 632  /// Look up a contact's ID by their identity public key (ed25519).
 633  ///
 634  /// Used to map P2P server connections (which provide identity keys during
 635  /// handshake) back to contact IDs for bidirectional messaging.
 636  Future<Uint8List?> getContactIdByIdentityKey({
 637    required List<int> identityKey,
 638  }) => RustLib.instance.api.crateApiGetContactIdByIdentityKey(
 639    identityKey: identityKey,
 640  );
 641  
 642  Future<MessageInfo?> processTorMessage({
 643    required List<int> senderIdentityKey,
 644    required List<int> payload,
 645  }) => RustLib.instance.api.crateApiProcessTorMessage(
 646    senderIdentityKey: senderIdentityKey,
 647    payload: payload,
 648  );
 649  
 650  /// Publish our .onion address to the DHT for peer discovery.
 651  ///
 652  /// Other peers who know our public key can fetch this address to establish
 653  /// direct Tor P2P connections. Re-publish periodically as addresses may change.
 654  Future<void> publishOnionAddress({required String onionAddress}) => RustLib
 655      .instance
 656      .api
 657      .crateApiPublishOnionAddress(onionAddress: onionAddress);
 658  
 659  /// Fetch a contact's .onion address from the DHT.
 660  ///
 661  /// Returns the onion address if the contact has published one recently
 662  /// (within 1 hour). Returns None if not found or stale.
 663  Future<String?> fetchContactOnionAddress({required List<int> contactId}) =>
 664      RustLib.instance.api.crateApiFetchContactOnionAddress(contactId: contactId);
 665  
 666  /// Get all cached onion addresses for contacts.
 667  ///
 668  /// Returns a list of (contact_id_hex, onion_address) pairs for contacts
 669  /// that have a previously discovered onion address. Used on startup to
 670  /// enable instant Tor P2P connections without waiting for DHT rendezvous.
 671  Future<List<CachedOnionAddress>> getCachedOnionAddresses() =>
 672      RustLib.instance.api.crateApiGetCachedOnionAddresses();
 673  
 674  /// Get the current reputation score and level.
 675  ReputationInfo getReputation() => RustLib.instance.api.crateApiGetReputation();
 676  
 677  /// Get all achievements with their unlock status.
 678  List<AchievementInfo> getAchievements() =>
 679      RustLib.instance.api.crateApiGetAchievements();
 680  
 681  /// Get forwarding statistics.
 682  ForwardingInfo getForwardingStats() =>
 683      RustLib.instance.api.crateApiGetForwardingStats();
 684  
 685  /// Record that we forwarded a message (for reputation tracking).
 686  ///
 687  /// Call this after successfully storing a message for forwarding.
 688  List<String> recordMessageForwarded({required List<int> recipientKeyHash}) =>
 689      RustLib.instance.api.crateApiRecordMessageForwarded(
 690        recipientKeyHash: recipientKeyHash,
 691      );
 692  
 693  /// Record a delivery confirmation (for reputation tracking).
 694  ///
 695  /// Call this when we receive confirmation that a message was delivered.
 696  List<String> recordDeliveryConfirmation({required List<int> messageHash}) =>
 697      RustLib.instance.api.crateApiRecordDeliveryConfirmation(
 698        messageHash: messageHash,
 699      );
 700  
 701  /// Cleanup expired forwarded messages.
 702  ///
 703  /// Returns the number of messages deleted.
 704  int cleanupForwardedMessages() =>
 705      RustLib.instance.api.crateApiCleanupForwardedMessages();
 706  
 707  /// Clear all forwarded messages from the relay store.
 708  ///
 709  /// Used to reset stale relay data. Returns the number of messages deleted.
 710  int clearForwardedMessages() =>
 711      RustLib.instance.api.crateApiClearForwardedMessages();
 712  
 713  /// Enforce forwarding storage limit.
 714  ///
 715  /// Deletes oldest messages until under the limit.
 716  /// Returns the number of messages deleted.
 717  int enforceForwardingLimit() =>
 718      RustLib.instance.api.crateApiEnforceForwardingLimit();
 719  
 720  /// Generate a gossip digest for BLE mesh exchange.
 721  ///
 722  /// Returns serialized `GossipDigest` message bytes containing a bloom filter
 723  /// of recipient key hashes for messages we're holding for forwarding.
 724  Uint8List generateGossipDigest() =>
 725      RustLib.instance.api.crateApiGenerateGossipDigest();
 726  
 727  /// Process a peer's gossip digest and generate a request.
 728  ///
 729  /// Takes serialized `GossipDigest` bytes from a peer.
 730  /// Returns serialized `GossipRequest` bytes to send back, or None if
 731  /// we're not interested in any of the messages they have.
 732  Uint8List? processGossipDigest({required List<int> digestBytes}) =>
 733      RustLib.instance.api.crateApiProcessGossipDigest(digestBytes: digestBytes);
 734  
 735  /// Handle a gossip request from a peer and generate a response.
 736  ///
 737  /// Takes serialized `GossipRequest` bytes.
 738  /// Returns serialized `GossipResponse` bytes containing matching messages.
 739  Uint8List handleGossipRequest({required List<int> requestBytes}) => RustLib
 740      .instance
 741      .api
 742      .crateApiHandleGossipRequest(requestBytes: requestBytes);
 743  
 744  /// Process a gossip response from a peer - store forwarded messages.
 745  ///
 746  /// Takes serialized `GossipResponse` bytes.
 747  /// Returns stats about what was stored.
 748  GossipResult processGossipResponse({required List<int> responseBytes}) =>
 749      RustLib.instance.api.crateApiProcessGossipResponse(
 750        responseBytes: responseBytes,
 751      );
 752  
 753  /// Get the current state of the iroh transport.
 754  IrohStateDto getIrohState() => RustLib.instance.api.crateApiGetIrohState();
 755  
 756  /// Start the iroh P2P transport.
 757  ///
 758  /// Initializes an iroh QUIC endpoint with NAT traversal via n0.computer relays.
 759  /// The endpoint accepts incoming connections using the custom Dead Drop ALPN protocol.
 760  Future<void> startIroh() => RustLib.instance.api.crateApiStartIroh();
 761  
 762  /// Stop the iroh P2P transport.
 763  Future<void> stopIroh() => RustLib.instance.api.crateApiStopIroh();
 764  
 765  /// Publish our iroh EndpointId to the DHT for peer discovery.
 766  ///
 767  /// Other peers who know our public key can fetch this EndpointId to establish
 768  /// direct iroh QUIC connections. The published value includes our relay URL
 769  /// so peers can connect without relying on iroh's built-in discovery.
 770  Future<void> publishIrohEndpointId() =>
 771      RustLib.instance.api.crateApiPublishIrohEndpointId();
 772  
 773  /// Fetch a contact's iroh EndpointId from the DHT.
 774  ///
 775  /// Returns the EndpointId hex string if the contact has published one recently.
 776  /// Caches the result in the contacts table for instant reconnect on next startup.
 777  Future<String?> fetchContactIrohEndpointId({required List<int> contactId}) =>
 778      RustLib.instance.api.crateApiFetchContactIrohEndpointId(
 779        contactId: contactId,
 780      );
 781  
 782  /// Send an encrypted payload to a peer via iroh QUIC transport.
 783  Future<void> sendViaIroh({
 784    required String endpointId,
 785    required List<int> payload,
 786  }) => RustLib.instance.api.crateApiSendViaIroh(
 787    endpointId: endpointId,
 788    payload: payload,
 789  );
 790  
 791  /// Manually set a contact's iroh endpoint ID (bypasses DHT discovery).
 792  ///
 793  /// Useful when DHT propagation is slow and the endpoint ID is known through
 794  /// other means (e.g., out-of-band exchange).
 795  void setContactIrohEndpoint({
 796    required List<int> contactId,
 797    required String endpointId,
 798  }) => RustLib.instance.api.crateApiSetContactIrohEndpoint(
 799    contactId: contactId,
 800    endpointId: endpointId,
 801  );
 802  
 803  /// Process an encrypted message received via iroh P2P.
 804  ///
 805  /// Decrypts and stores the message, identical to process_tor_message but
 806  /// with iroh delivery transport. The sender_identity_key is the ed25519
 807  /// public key exchanged during the P2P handshake.
 808  Future<MessageInfo?> processIrohMessage({
 809    required List<int> senderIdentityKey,
 810    required List<int> payload,
 811  }) => RustLib.instance.api.crateApiProcessIrohMessage(
 812    senderIdentityKey: senderIdentityKey,
 813    payload: payload,
 814  );
 815  
 816  /// Process an iroh message when the sender's endpoint ID is unknown.
 817  ///
 818  /// Tries all contacts to find which one can decrypt the message.
 819  /// Slower than process_iroh_message but handles cases where the peer's
 820  /// endpoint ID hasn't been cached/discovered yet (e.g., group invites
 821  /// from contacts whose iroh rendezvous hasn't been fetched).
 822  Future<MessageInfo?> processIrohMessageUnknownSender({
 823    required List<int> payload,
 824  }) => RustLib.instance.api.crateApiProcessIrohMessageUnknownSender(
 825    payload: payload,
 826  );
 827  
 828  /// Poll for incoming iroh messages (non-blocking).
 829  ///
 830  /// Returns the next available message as `(sender_node_id_bytes, payload_bytes)`,
 831  /// or None if no messages are waiting.
 832  Future<IrohIncomingMessage?> pollIrohIncoming() =>
 833      RustLib.instance.api.crateApiPollIrohIncoming();
 834  
 835  /// Get all cached iroh endpoint IDs for contacts.
 836  ///
 837  /// Returns a list of (contact_id, endpoint_id) pairs for contacts
 838  /// that have a previously discovered iroh EndpointId.
 839  Future<List<CachedIrohEndpoint>> getCachedIrohEndpointIds() =>
 840      RustLib.instance.api.crateApiGetCachedIrohEndpointIds();
 841  
 842  /// Create a new group chat.
 843  ///
 844  /// Returns the group ID. Sends Invite messages to all members.
 845  Future<Uint8List> createGroup({
 846    required String name,
 847    required List<Uint8List> memberIds,
 848  }) =>
 849      RustLib.instance.api.crateApiCreateGroup(name: name, memberIds: memberIds);
 850  
 851  /// List all groups with metadata.
 852  List<GroupInfo> listGroups() => RustLib.instance.api.crateApiListGroups();
 853  
 854  /// Get members of a group.
 855  List<GroupMemberInfoDto> getGroupMembers({required List<int> groupId}) =>
 856      RustLib.instance.api.crateApiGetGroupMembers(groupId: groupId);
 857  
 858  /// Send a message to all members of a group (fan-out).
 859  Future<Uint8List> sendGroupMessage({
 860    required List<int> groupId,
 861    required List<int> content,
 862    required int contentType,
 863    String? filename,
 864  }) => RustLib.instance.api.crateApiSendGroupMessage(
 865    groupId: groupId,
 866    content: content,
 867    contentType: contentType,
 868    filename: filename,
 869  );
 870  
 871  /// Send a document to a group, automatically chunking if larger than MAX_CHUNK_SIZE.
 872  ///
 873  /// Small files (≤30KB) are sent as a single Document message via send_group_message.
 874  /// Large files are chunked and each chunk is fan-out to all group members.
 875  /// Returns the document ID.
 876  Future<Uint8List> sendGroupDocument({
 877    required List<int> groupId,
 878    required List<int> content,
 879    required String filename,
 880  }) => RustLib.instance.api.crateApiSendGroupDocument(
 881    groupId: groupId,
 882    content: content,
 883    filename: filename,
 884  );
 885  
 886  /// Get conversation messages for a group.
 887  List<MessageInfo> getGroupConversation({required List<int> groupId}) =>
 888      RustLib.instance.api.crateApiGetGroupConversation(groupId: groupId);
 889  
 890  /// Add a member to a group and notify existing members.
 891  Future<void> addGroupMember({
 892    required List<int> groupId,
 893    required List<int> contactId,
 894  }) => RustLib.instance.api.crateApiAddGroupMember(
 895    groupId: groupId,
 896    contactId: contactId,
 897  );
 898  
 899  /// Remove a member from a group and notify remaining members.
 900  Future<void> removeGroupMember({
 901    required List<int> groupId,
 902    required List<int> contactId,
 903  }) => RustLib.instance.api.crateApiRemoveGroupMember(
 904    groupId: groupId,
 905    contactId: contactId,
 906  );
 907  
 908  /// Leave a group and notify remaining members.
 909  Future<void> leaveGroup({required List<int> groupId}) =>
 910      RustLib.instance.api.crateApiLeaveGroup(groupId: groupId);
 911  
 912  /// Rename a group and notify members.
 913  Future<void> renameGroup({
 914    required List<int> groupId,
 915    required String newName,
 916  }) => RustLib.instance.api.crateApiRenameGroup(
 917    groupId: groupId,
 918    newName: newName,
 919  );
 920  
 921  /// Set group muted status.
 922  void setGroupMuted({required List<int> groupId, required bool isMuted}) =>
 923      RustLib.instance.api.crateApiSetGroupMuted(
 924        groupId: groupId,
 925        isMuted: isMuted,
 926      );
 927  
 928  /// Set group disappearing message duration.
 929  void setGroupDisappearing({
 930    required List<int> groupId,
 931    required BigInt duration,
 932  }) => RustLib.instance.api.crateApiSetGroupDisappearing(
 933    groupId: groupId,
 934    duration: duration,
 935  );
 936  
 937  /// Accept a group invite.
 938  void acceptGroupInvite({required List<int> groupId}) =>
 939      RustLib.instance.api.crateApiAcceptGroupInvite(groupId: groupId);
 940  
 941  /// Decline a group invite – notify members with a Leave action, then delete locally.
 942  Future<void> declineGroupInvite({required List<int> groupId}) =>
 943      RustLib.instance.api.crateApiDeclineGroupInvite(groupId: groupId);
 944  
 945  /// Check if a group invite has been accepted.
 946  bool isGroupAccepted({required List<int> groupId}) =>
 947      RustLib.instance.api.crateApiIsGroupAccepted(groupId: groupId);
 948  
 949  /// DTO for achievement information.
 950  class AchievementInfo {
 951    /// Achievement ID.
 952    final int id;
 953  
 954    /// Display name.
 955    final String name;
 956  
 957    /// Description.
 958    final String description;
 959  
 960    /// Whether unlocked.
 961    final bool unlocked;
 962  
 963    const AchievementInfo({
 964      required this.id,
 965      required this.name,
 966      required this.description,
 967      required this.unlocked,
 968    });
 969  
 970    @override
 971    int get hashCode =>
 972        id.hashCode ^ name.hashCode ^ description.hashCode ^ unlocked.hashCode;
 973  
 974    @override
 975    bool operator ==(Object other) =>
 976        identical(this, other) ||
 977        other is AchievementInfo &&
 978            runtimeType == other.runtimeType &&
 979            id == other.id &&
 980            name == other.name &&
 981            description == other.description &&
 982            unlocked == other.unlocked;
 983  }
 984  
 985  /// DTO for a cached iroh endpoint ID.
 986  class CachedIrohEndpoint {
 987    final Uint8List contactId;
 988    final String endpointId;
 989  
 990    const CachedIrohEndpoint({required this.contactId, required this.endpointId});
 991  
 992    @override
 993    int get hashCode => contactId.hashCode ^ endpointId.hashCode;
 994  
 995    @override
 996    bool operator ==(Object other) =>
 997        identical(this, other) ||
 998        other is CachedIrohEndpoint &&
 999            runtimeType == other.runtimeType &&
1000            contactId == other.contactId &&
1001            endpointId == other.endpointId;
1002  }
1003  
1004  /// DTO for cached onion address.
1005  class CachedOnionAddress {
1006    final Uint8List contactId;
1007    final String onionAddress;
1008  
1009    const CachedOnionAddress({
1010      required this.contactId,
1011      required this.onionAddress,
1012    });
1013  
1014    @override
1015    int get hashCode => contactId.hashCode ^ onionAddress.hashCode;
1016  
1017    @override
1018    bool operator ==(Object other) =>
1019        identical(this, other) ||
1020        other is CachedOnionAddress &&
1021            runtimeType == other.runtimeType &&
1022            contactId == other.contactId &&
1023            onionAddress == other.onionAddress;
1024  }
1025  
1026  /// Contact information.
1027  class ContactInfo {
1028    /// Unique contact identifier.
1029    final Uint8List contactId;
1030  
1031    /// User-assigned nickname.
1032    final String? nickname;
1033  
1034    /// Contact state (0=Pending, 1=Active, 2=Blocked, 3=Revoked).
1035    final int state;
1036  
1037    /// Human-readable fingerprint.
1038    final String fingerprint;
1039  
1040    /// Creation timestamp (Unix seconds).
1041    final PlatformInt64 createdAt;
1042  
1043    /// Last seen timestamp (Unix seconds).
1044    final PlatformInt64? lastSeen;
1045  
1046    /// Number of queued outbound messages.
1047    final int queuedCount;
1048  
1049    /// Number of unread inbound messages.
1050    final int unreadCount;
1051  
1052    /// Disappearing message duration in seconds (0 = off).
1053    final BigInt disappearingDuration;
1054  
1055    /// Whether notifications are muted for this contact.
1056    final bool isMuted;
1057  
1058    const ContactInfo({
1059      required this.contactId,
1060      this.nickname,
1061      required this.state,
1062      required this.fingerprint,
1063      required this.createdAt,
1064      this.lastSeen,
1065      required this.queuedCount,
1066      required this.unreadCount,
1067      required this.disappearingDuration,
1068      required this.isMuted,
1069    });
1070  
1071    @override
1072    int get hashCode =>
1073        contactId.hashCode ^
1074        nickname.hashCode ^
1075        state.hashCode ^
1076        fingerprint.hashCode ^
1077        createdAt.hashCode ^
1078        lastSeen.hashCode ^
1079        queuedCount.hashCode ^
1080        unreadCount.hashCode ^
1081        disappearingDuration.hashCode ^
1082        isMuted.hashCode;
1083  
1084    @override
1085    bool operator ==(Object other) =>
1086        identical(this, other) ||
1087        other is ContactInfo &&
1088            runtimeType == other.runtimeType &&
1089            contactId == other.contactId &&
1090            nickname == other.nickname &&
1091            state == other.state &&
1092            fingerprint == other.fingerprint &&
1093            createdAt == other.createdAt &&
1094            lastSeen == other.lastSeen &&
1095            queuedCount == other.queuedCount &&
1096            unreadCount == other.unreadCount &&
1097            disappearingDuration == other.disappearingDuration &&
1098            isMuted == other.isMuted;
1099  }
1100  
1101  /// Result of fetching messages from DHT.
1102  class DhtFetchResult {
1103    /// Newly received messages.
1104    final List<MessageInfo> messages;
1105  
1106    /// Contact IDs that had reaction updates (need UI refresh).
1107    final List<Uint8List> contactsWithReactions;
1108  
1109    /// Whether any GroupManagement messages were processed (need groups UI refresh).
1110    final bool hadGroupManagement;
1111  
1112    const DhtFetchResult({
1113      required this.messages,
1114      required this.contactsWithReactions,
1115      required this.hadGroupManagement,
1116    });
1117  
1118    @override
1119    int get hashCode =>
1120        messages.hashCode ^
1121        contactsWithReactions.hashCode ^
1122        hadGroupManagement.hashCode;
1123  
1124    @override
1125    bool operator ==(Object other) =>
1126        identical(this, other) ||
1127        other is DhtFetchResult &&
1128            runtimeType == other.runtimeType &&
1129            messages == other.messages &&
1130            contactsWithReactions == other.contactsWithReactions &&
1131            hadGroupManagement == other.hadGroupManagement;
1132  }
1133  
1134  /// Result of publishing messages to the DHT.
1135  class DhtPublishResult {
1136    /// Number of messages published.
1137    final int publishedCount;
1138  
1139    /// IDs of messages that were published.
1140    final List<Uint8List> publishedIds;
1141  
1142    const DhtPublishResult({
1143      required this.publishedCount,
1144      required this.publishedIds,
1145    });
1146  
1147    @override
1148    int get hashCode => publishedCount.hashCode ^ publishedIds.hashCode;
1149  
1150    @override
1151    bool operator ==(Object other) =>
1152        identical(this, other) ||
1153        other is DhtPublishResult &&
1154            runtimeType == other.runtimeType &&
1155            publishedCount == other.publishedCount &&
1156            publishedIds == other.publishedIds;
1157  }
1158  
1159  /// State of the DHT transport.
1160  class DhtStateDto {
1161    /// Current state: "disconnected", "bootstrapping", "connected", "failed".
1162    final String state;
1163  
1164    /// Number of connected peers (if connected).
1165    final int peerCount;
1166  
1167    /// Error message (if failed).
1168    final String? error;
1169  
1170    const DhtStateDto({required this.state, required this.peerCount, this.error});
1171  
1172    /// Create from internal DhtState.
1173    static Future<DhtStateDto> fromInternal({required DhtState state}) =>
1174        RustLib.instance.api.crateApiDhtStateDtoFromInternal(state: state);
1175  
1176    @override
1177    int get hashCode => state.hashCode ^ peerCount.hashCode ^ error.hashCode;
1178  
1179    @override
1180    bool operator ==(Object other) =>
1181        identical(this, other) ||
1182        other is DhtStateDto &&
1183            runtimeType == other.runtimeType &&
1184            state == other.state &&
1185            peerCount == other.peerCount &&
1186            error == other.error;
1187  }
1188  
1189  /// Information about a reassembled document.
1190  class DocumentInfo {
1191    /// Unique document ID.
1192    final Uint8List documentId;
1193  
1194    /// Original filename.
1195    final String filename;
1196  
1197    /// Total file size in bytes.
1198    final BigInt size;
1199  
1200    /// Complete file content.
1201    final Uint8List content;
1202  
1203    const DocumentInfo({
1204      required this.documentId,
1205      required this.filename,
1206      required this.size,
1207      required this.content,
1208    });
1209  
1210    @override
1211    int get hashCode =>
1212        documentId.hashCode ^
1213        filename.hashCode ^
1214        size.hashCode ^
1215        content.hashCode;
1216  
1217    @override
1218    bool operator ==(Object other) =>
1219        identical(this, other) ||
1220        other is DocumentInfo &&
1221            runtimeType == other.runtimeType &&
1222            documentId == other.documentId &&
1223            filename == other.filename &&
1224            size == other.size &&
1225            content == other.content;
1226  }
1227  
1228  /// DTO for a queued encrypted message ready for P2P delivery.
1229  class EncryptedPayload {
1230    /// Message ID (16 bytes).
1231    final Uint8List messageId;
1232  
1233    /// Serialized encrypted message bytes (ready to send over WebSocket).
1234    final Uint8List payload;
1235  
1236    const EncryptedPayload({required this.messageId, required this.payload});
1237  
1238    @override
1239    int get hashCode => messageId.hashCode ^ payload.hashCode;
1240  
1241    @override
1242    bool operator ==(Object other) =>
1243        identical(this, other) ||
1244        other is EncryptedPayload &&
1245            runtimeType == other.runtimeType &&
1246            messageId == other.messageId &&
1247            payload == other.payload;
1248  }
1249  
1250  /// Final result of a completed exchange.
1251  class ExchangeComplete {
1252    /// The contact ID this exchange was with.
1253    final Uint8List contactId;
1254  
1255    /// IDs of messages successfully delivered to the contact.
1256    final List<Uint8List> deliveredIds;
1257  
1258    /// Messages received from the contact.
1259    final List<MessageInfo> receivedMessages;
1260  
1261    const ExchangeComplete({
1262      required this.contactId,
1263      required this.deliveredIds,
1264      required this.receivedMessages,
1265    });
1266  
1267    @override
1268    int get hashCode =>
1269        contactId.hashCode ^ deliveredIds.hashCode ^ receivedMessages.hashCode;
1270  
1271    @override
1272    bool operator ==(Object other) =>
1273        identical(this, other) ||
1274        other is ExchangeComplete &&
1275            runtimeType == other.runtimeType &&
1276            contactId == other.contactId &&
1277            deliveredIds == other.deliveredIds &&
1278            receivedMessages == other.receivedMessages;
1279  }
1280  
1281  /// Opaque handle to an exchange session.
1282  class ExchangeHandle {
1283    /// Internal handle ID.
1284    final BigInt id;
1285  
1286    const ExchangeHandle({required this.id});
1287  
1288    @override
1289    int get hashCode => id.hashCode;
1290  
1291    @override
1292    bool operator ==(Object other) =>
1293        identical(this, other) ||
1294        other is ExchangeHandle &&
1295            runtimeType == other.runtimeType &&
1296            id == other.id;
1297  }
1298  
1299  /// Result of processing exchange data.
1300  class ExchangeResultDto {
1301    /// True if exchange is complete.
1302    final bool complete;
1303  
1304    /// Response data to send (if any).
1305    final Uint8List? response;
1306  
1307    const ExchangeResultDto({required this.complete, this.response});
1308  
1309    @override
1310    int get hashCode => complete.hashCode ^ response.hashCode;
1311  
1312    @override
1313    bool operator ==(Object other) =>
1314        identical(this, other) ||
1315        other is ExchangeResultDto &&
1316            runtimeType == other.runtimeType &&
1317            complete == other.complete &&
1318            response == other.response;
1319  }
1320  
1321  /// DTO for forwarding statistics.
1322  class ForwardingInfo {
1323    /// Total number of messages stored.
1324    final int messageCount;
1325  
1326    /// Total storage used in bytes.
1327    final BigInt storageUsed;
1328  
1329    /// Storage limit in bytes.
1330    final BigInt storageLimit;
1331  
1332    /// Number of unique recipients we have messages for.
1333    final int uniqueRecipients;
1334  
1335    /// Fill percentage (0-100).
1336    final double fillPercentage;
1337  
1338    const ForwardingInfo({
1339      required this.messageCount,
1340      required this.storageUsed,
1341      required this.storageLimit,
1342      required this.uniqueRecipients,
1343      required this.fillPercentage,
1344    });
1345  
1346    @override
1347    int get hashCode =>
1348        messageCount.hashCode ^
1349        storageUsed.hashCode ^
1350        storageLimit.hashCode ^
1351        uniqueRecipients.hashCode ^
1352        fillPercentage.hashCode;
1353  
1354    @override
1355    bool operator ==(Object other) =>
1356        identical(this, other) ||
1357        other is ForwardingInfo &&
1358            runtimeType == other.runtimeType &&
1359            messageCount == other.messageCount &&
1360            storageUsed == other.storageUsed &&
1361            storageLimit == other.storageLimit &&
1362            uniqueRecipients == other.uniqueRecipients &&
1363            fillPercentage == other.fillPercentage;
1364  }
1365  
1366  /// Final result of a completed gossip exchange.
1367  class GossipExchangeComplete {
1368    /// Number of forwarded messages stored for future relay.
1369    final int storedCount;
1370  
1371    /// Number of messages that were addressed to us.
1372    final int messagesForUsCount;
1373  
1374    const GossipExchangeComplete({
1375      required this.storedCount,
1376      required this.messagesForUsCount,
1377    });
1378  
1379    @override
1380    int get hashCode => storedCount.hashCode ^ messagesForUsCount.hashCode;
1381  
1382    @override
1383    bool operator ==(Object other) =>
1384        identical(this, other) ||
1385        other is GossipExchangeComplete &&
1386            runtimeType == other.runtimeType &&
1387            storedCount == other.storedCount &&
1388            messagesForUsCount == other.messagesForUsCount;
1389  }
1390  
1391  /// Opaque handle to a gossip exchange session.
1392  class GossipExchangeHandle {
1393    /// Internal handle ID.
1394    final BigInt id;
1395  
1396    const GossipExchangeHandle({required this.id});
1397  
1398    @override
1399    int get hashCode => id.hashCode;
1400  
1401    @override
1402    bool operator ==(Object other) =>
1403        identical(this, other) ||
1404        other is GossipExchangeHandle &&
1405            runtimeType == other.runtimeType &&
1406            id == other.id;
1407  }
1408  
1409  /// Result of finalizing a gossip exchange while keeping the transport alive.
1410  class GossipFinalizeKeepTransportResult {
1411    /// The gossip exchange result (stored count, messages for us).
1412    final GossipExchangeComplete result;
1413  
1414    /// Handle to the preserved transport for creating new gossip rounds.
1415    final BigInt transportHandle;
1416  
1417    const GossipFinalizeKeepTransportResult({
1418      required this.result,
1419      required this.transportHandle,
1420    });
1421  
1422    @override
1423    int get hashCode => result.hashCode ^ transportHandle.hashCode;
1424  
1425    @override
1426    bool operator ==(Object other) =>
1427        identical(this, other) ||
1428        other is GossipFinalizeKeepTransportResult &&
1429            runtimeType == other.runtimeType &&
1430            result == other.result &&
1431            transportHandle == other.transportHandle;
1432  }
1433  
1434  /// Result of processing a gossip response.
1435  class GossipResult {
1436    /// Number of messages stored for future forwarding.
1437    final int storedCount;
1438  
1439    /// Number of messages that were for us (to be processed).
1440    final int messagesForUs;
1441  
1442    const GossipResult({required this.storedCount, required this.messagesForUs});
1443  
1444    @override
1445    int get hashCode => storedCount.hashCode ^ messagesForUs.hashCode;
1446  
1447    @override
1448    bool operator ==(Object other) =>
1449        identical(this, other) ||
1450        other is GossipResult &&
1451            runtimeType == other.runtimeType &&
1452            storedCount == other.storedCount &&
1453            messagesForUs == other.messagesForUs;
1454  }
1455  
1456  /// Group information.
1457  class GroupInfo {
1458    /// Unique group identifier.
1459    final Uint8List groupId;
1460  
1461    /// Group name.
1462    final String name;
1463  
1464    /// Creation timestamp (Unix seconds).
1465    final PlatformInt64 createdAt;
1466  
1467    /// Number of members.
1468    final int memberCount;
1469  
1470    /// Number of unread messages.
1471    final int unreadCount;
1472  
1473    /// Disappearing message duration in seconds (0 = off).
1474    final BigInt disappearingDuration;
1475  
1476    /// Whether notifications are muted.
1477    final bool isMuted;
1478  
1479    /// Most recent message timestamp (for sorting).
1480    final PlatformInt64? lastMessageAt;
1481  
1482    /// Whether the user has accepted the group invite.
1483    final bool accepted;
1484  
1485    /// Whether the current user is the group creator (can remove members).
1486    final bool isCreator;
1487  
1488    const GroupInfo({
1489      required this.groupId,
1490      required this.name,
1491      required this.createdAt,
1492      required this.memberCount,
1493      required this.unreadCount,
1494      required this.disappearingDuration,
1495      required this.isMuted,
1496      this.lastMessageAt,
1497      required this.accepted,
1498      required this.isCreator,
1499    });
1500  
1501    @override
1502    int get hashCode =>
1503        groupId.hashCode ^
1504        name.hashCode ^
1505        createdAt.hashCode ^
1506        memberCount.hashCode ^
1507        unreadCount.hashCode ^
1508        disappearingDuration.hashCode ^
1509        isMuted.hashCode ^
1510        lastMessageAt.hashCode ^
1511        accepted.hashCode ^
1512        isCreator.hashCode;
1513  
1514    @override
1515    bool operator ==(Object other) =>
1516        identical(this, other) ||
1517        other is GroupInfo &&
1518            runtimeType == other.runtimeType &&
1519            groupId == other.groupId &&
1520            name == other.name &&
1521            createdAt == other.createdAt &&
1522            memberCount == other.memberCount &&
1523            unreadCount == other.unreadCount &&
1524            disappearingDuration == other.disappearingDuration &&
1525            isMuted == other.isMuted &&
1526            lastMessageAt == other.lastMessageAt &&
1527            accepted == other.accepted &&
1528            isCreator == other.isCreator;
1529  }
1530  
1531  /// Group member information.
1532  class GroupMemberInfoDto {
1533    /// Contact ID.
1534    final Uint8List contactId;
1535  
1536    /// Nickname.
1537    final String? nickname;
1538  
1539    /// Role (0=member, 1=admin).
1540    final int role;
1541  
1542    const GroupMemberInfoDto({
1543      required this.contactId,
1544      this.nickname,
1545      required this.role,
1546    });
1547  
1548    @override
1549    int get hashCode => contactId.hashCode ^ nickname.hashCode ^ role.hashCode;
1550  
1551    @override
1552    bool operator ==(Object other) =>
1553        identical(this, other) ||
1554        other is GroupMemberInfoDto &&
1555            runtimeType == other.runtimeType &&
1556            contactId == other.contactId &&
1557            nickname == other.nickname &&
1558            role == other.role;
1559  }
1560  
1561  /// Opaque handle to a handshake session.
1562  class HandshakeHandle {
1563    /// Internal handle ID.
1564    final BigInt id;
1565  
1566    const HandshakeHandle({required this.id});
1567  
1568    @override
1569    int get hashCode => id.hashCode;
1570  
1571    @override
1572    bool operator ==(Object other) =>
1573        identical(this, other) ||
1574        other is HandshakeHandle &&
1575            runtimeType == other.runtimeType &&
1576            id == other.id;
1577  }
1578  
1579  /// Result of processing a handshake message.
1580  class HandshakeResult {
1581    /// True if handshake is complete.
1582    final bool complete;
1583  
1584    /// Response message to send (if any).
1585    final Uint8List? response;
1586  
1587    /// Remote party's identity public key (available when complete).
1588    final Uint8List? remoteIdentity;
1589  
1590    const HandshakeResult({
1591      required this.complete,
1592      this.response,
1593      this.remoteIdentity,
1594    });
1595  
1596    @override
1597    int get hashCode =>
1598        complete.hashCode ^ response.hashCode ^ remoteIdentity.hashCode;
1599  
1600    @override
1601    bool operator ==(Object other) =>
1602        identical(this, other) ||
1603        other is HandshakeResult &&
1604            runtimeType == other.runtimeType &&
1605            complete == other.complete &&
1606            response == other.response &&
1607            remoteIdentity == other.remoteIdentity;
1608  }
1609  
1610  /// Identity information.
1611  class IdentityInfo {
1612    /// Ed25519 identity public key.
1613    final Uint8List identityPublic;
1614  
1615    /// X25519 exchange public key.
1616    final Uint8List exchangePublic;
1617  
1618    /// Human-readable fingerprint.
1619    final String fingerprint;
1620  
1621    /// Creation timestamp (Unix seconds).
1622    final PlatformInt64 createdAt;
1623  
1624    const IdentityInfo({
1625      required this.identityPublic,
1626      required this.exchangePublic,
1627      required this.fingerprint,
1628      required this.createdAt,
1629    });
1630  
1631    @override
1632    int get hashCode =>
1633        identityPublic.hashCode ^
1634        exchangePublic.hashCode ^
1635        fingerprint.hashCode ^
1636        createdAt.hashCode;
1637  
1638    @override
1639    bool operator ==(Object other) =>
1640        identical(this, other) ||
1641        other is IdentityInfo &&
1642            runtimeType == other.runtimeType &&
1643            identityPublic == other.identityPublic &&
1644            exchangePublic == other.exchangePublic &&
1645            fingerprint == other.fingerprint &&
1646            createdAt == other.createdAt;
1647  }
1648  
1649  /// DTO for an incoming iroh message.
1650  class IrohIncomingMessage {
1651    /// The sender's iroh EndpointId bytes (32 bytes).
1652    final Uint8List senderNodeId;
1653  
1654    /// The encrypted message payload.
1655    final Uint8List payload;
1656  
1657    const IrohIncomingMessage({
1658      required this.senderNodeId,
1659      required this.payload,
1660    });
1661  
1662    @override
1663    int get hashCode => senderNodeId.hashCode ^ payload.hashCode;
1664  
1665    @override
1666    bool operator ==(Object other) =>
1667        identical(this, other) ||
1668        other is IrohIncomingMessage &&
1669            runtimeType == other.runtimeType &&
1670            senderNodeId == other.senderNodeId &&
1671            payload == other.payload;
1672  }
1673  
1674  /// DTO for iroh transport state.
1675  class IrohStateDto {
1676    /// State string: "disconnected", "starting", "running", "failed"
1677    final String state;
1678  
1679    /// Our iroh EndpointId (hex string), if running.
1680    final String? endpointId;
1681  
1682    /// Error message, if failed.
1683    final String? error;
1684  
1685    const IrohStateDto({required this.state, this.endpointId, this.error});
1686  
1687    @override
1688    int get hashCode => state.hashCode ^ endpointId.hashCode ^ error.hashCode;
1689  
1690    @override
1691    bool operator ==(Object other) =>
1692        identical(this, other) ||
1693        other is IrohStateDto &&
1694            runtimeType == other.runtimeType &&
1695            state == other.state &&
1696            endpointId == other.endpointId &&
1697            error == other.error;
1698  }
1699  
1700  /// Message information.
1701  class MessageInfo {
1702    /// Unique message identifier.
1703    final Uint8List messageId;
1704  
1705    /// Contact ID (sender or recipient).
1706    final Uint8List contactId;
1707  
1708    /// Content type (1=Text, 2=Document).
1709    final int contentType;
1710  
1711    /// Message content.
1712    final Uint8List content;
1713  
1714    /// Filename (for documents).
1715    final String? filename;
1716  
1717    /// True if outbound, false if inbound.
1718    final bool isOutbound;
1719  
1720    /// Message state.
1721    final int state;
1722  
1723    /// Creation/received timestamp (Unix seconds).
1724    final PlatformInt64 timestamp;
1725  
1726    /// Expiration timestamp (Unix seconds).
1727    final PlatformInt64? expiresAt;
1728  
1729    /// Transport used for delivery ("ble", "dht", "relay").
1730    final String? deliveryTransport;
1731  
1732    /// Delivery timestamp (Unix seconds).
1733    final PlatformInt64? deliveredAt;
1734  
1735    /// Last error message (if delivery failed).
1736    final String? lastError;
1737  
1738    /// Reaction summaries (emoji -> count).
1739    final List<ReactionSummaryDto> reactions;
1740  
1741    /// Disappearing message duration in seconds (0 = standard).
1742    final BigInt disappearingDuration;
1743  
1744    /// Sender contact ID (for group inbound messages).
1745    final Uint8List? senderId;
1746  
1747    /// Group ID (if group message).
1748    final Uint8List? groupId;
1749  
1750    const MessageInfo({
1751      required this.messageId,
1752      required this.contactId,
1753      required this.contentType,
1754      required this.content,
1755      this.filename,
1756      required this.isOutbound,
1757      required this.state,
1758      required this.timestamp,
1759      this.expiresAt,
1760      this.deliveryTransport,
1761      this.deliveredAt,
1762      this.lastError,
1763      required this.reactions,
1764      required this.disappearingDuration,
1765      this.senderId,
1766      this.groupId,
1767    });
1768  
1769    @override
1770    int get hashCode =>
1771        messageId.hashCode ^
1772        contactId.hashCode ^
1773        contentType.hashCode ^
1774        content.hashCode ^
1775        filename.hashCode ^
1776        isOutbound.hashCode ^
1777        state.hashCode ^
1778        timestamp.hashCode ^
1779        expiresAt.hashCode ^
1780        deliveryTransport.hashCode ^
1781        deliveredAt.hashCode ^
1782        lastError.hashCode ^
1783        reactions.hashCode ^
1784        disappearingDuration.hashCode ^
1785        senderId.hashCode ^
1786        groupId.hashCode;
1787  
1788    @override
1789    bool operator ==(Object other) =>
1790        identical(this, other) ||
1791        other is MessageInfo &&
1792            runtimeType == other.runtimeType &&
1793            messageId == other.messageId &&
1794            contactId == other.contactId &&
1795            contentType == other.contentType &&
1796            content == other.content &&
1797            filename == other.filename &&
1798            isOutbound == other.isOutbound &&
1799            state == other.state &&
1800            timestamp == other.timestamp &&
1801            expiresAt == other.expiresAt &&
1802            deliveryTransport == other.deliveryTransport &&
1803            deliveredAt == other.deliveredAt &&
1804            lastError == other.lastError &&
1805            reactions == other.reactions &&
1806            disappearingDuration == other.disappearingDuration &&
1807            senderId == other.senderId &&
1808            groupId == other.groupId;
1809  }
1810  
1811  /// Network transport preference for message delivery.
1812  ///
1813  /// Controls the priority order when multiple transports are available.
1814  enum NetworkTransportPreference {
1815    /// Prefer DHT for decentralized delivery (default).
1816    dhtPreferred,
1817  
1818    /// Prefer relay server for faster delivery.
1819    relayPreferred,
1820  
1821    /// Use DHT only, never use relays.
1822    dhtOnly,
1823  
1824    /// Use relay only, never use DHT.
1825    relayOnly;
1826  
1827    static Future<NetworkTransportPreference> default_() =>
1828        RustLib.instance.api.crateApiNetworkTransportPreferenceDefault();
1829  }
1830  
1831  /// Pending contact from QR scan (not yet confirmed).
1832  class PendingContact {
1833    /// Generated contact ID.
1834    final Uint8List contactId;
1835  
1836    /// Ed25519 identity public key.
1837    final Uint8List identityPublic;
1838  
1839    /// X25519 exchange public key.
1840    final Uint8List exchangePublic;
1841  
1842    /// Human-readable fingerprint.
1843    final String fingerprint;
1844  
1845    const PendingContact({
1846      required this.contactId,
1847      required this.identityPublic,
1848      required this.exchangePublic,
1849      required this.fingerprint,
1850    });
1851  
1852    @override
1853    int get hashCode =>
1854        contactId.hashCode ^
1855        identityPublic.hashCode ^
1856        exchangePublic.hashCode ^
1857        fingerprint.hashCode;
1858  
1859    @override
1860    bool operator ==(Object other) =>
1861        identical(this, other) ||
1862        other is PendingContact &&
1863            runtimeType == other.runtimeType &&
1864            contactId == other.contactId &&
1865            identityPublic == other.identityPublic &&
1866            exchangePublic == other.exchangePublic &&
1867            fingerprint == other.fingerprint;
1868  }
1869  
1870  /// Summary of reactions for a message.
1871  class ReactionSummaryDto {
1872    /// The emoji.
1873    final String emoji;
1874  
1875    /// Number of this reaction.
1876    final int count;
1877  
1878    /// Whether the current user has added this reaction.
1879    final bool includesSelf;
1880  
1881    const ReactionSummaryDto({
1882      required this.emoji,
1883      required this.count,
1884      required this.includesSelf,
1885    });
1886  
1887    @override
1888    int get hashCode => emoji.hashCode ^ count.hashCode ^ includesSelf.hashCode;
1889  
1890    @override
1891    bool operator ==(Object other) =>
1892        identical(this, other) ||
1893        other is ReactionSummaryDto &&
1894            runtimeType == other.runtimeType &&
1895            emoji == other.emoji &&
1896            count == other.count &&
1897            includesSelf == other.includesSelf;
1898  }
1899  
1900  /// State of the relay transport.
1901  class RelayStateDto {
1902    /// Current state: "disconnected", "connecting", "connected", "failed".
1903    final String state;
1904  
1905    /// URL of the connected relay (if connected).
1906    final String? connectedRelay;
1907  
1908    /// Error message (if failed).
1909    final String? error;
1910  
1911    const RelayStateDto({required this.state, this.connectedRelay, this.error});
1912  
1913    /// Create from internal RelayState.
1914    static Future<RelayStateDto> fromInternal({required RelayState state}) =>
1915        RustLib.instance.api.crateApiRelayStateDtoFromInternal(state: state);
1916  
1917    @override
1918    int get hashCode => state.hashCode ^ connectedRelay.hashCode ^ error.hashCode;
1919  
1920    @override
1921    bool operator ==(Object other) =>
1922        identical(this, other) ||
1923        other is RelayStateDto &&
1924            runtimeType == other.runtimeType &&
1925            state == other.state &&
1926            connectedRelay == other.connectedRelay &&
1927            error == other.error;
1928  }
1929  
1930  /// DTO for reputation information.
1931  class ReputationInfo {
1932    /// Total messages forwarded.
1933    final BigInt messagesForwarded;
1934  
1935    /// Total messages successfully delivered (confirmed).
1936    final BigInt messagesDelivered;
1937  
1938    /// Unique recipients helped.
1939    final int recipientsHelped;
1940  
1941    /// Total XP earned.
1942    final BigInt totalXp;
1943  
1944    /// Current level (1-5).
1945    final int currentLevel;
1946  
1947    /// Level title (e.g., "Courier").
1948    final String levelTitle;
1949  
1950    /// Progress to next level (0.0 to 1.0).
1951    final double levelProgress;
1952  
1953    /// XP needed for next level.
1954    final BigInt xpForNextLevel;
1955  
1956    /// Current streak in days.
1957    final int currentStreak;
1958  
1959    const ReputationInfo({
1960      required this.messagesForwarded,
1961      required this.messagesDelivered,
1962      required this.recipientsHelped,
1963      required this.totalXp,
1964      required this.currentLevel,
1965      required this.levelTitle,
1966      required this.levelProgress,
1967      required this.xpForNextLevel,
1968      required this.currentStreak,
1969    });
1970  
1971    @override
1972    int get hashCode =>
1973        messagesForwarded.hashCode ^
1974        messagesDelivered.hashCode ^
1975        recipientsHelped.hashCode ^
1976        totalXp.hashCode ^
1977        currentLevel.hashCode ^
1978        levelTitle.hashCode ^
1979        levelProgress.hashCode ^
1980        xpForNextLevel.hashCode ^
1981        currentStreak.hashCode;
1982  
1983    @override
1984    bool operator ==(Object other) =>
1985        identical(this, other) ||
1986        other is ReputationInfo &&
1987            runtimeType == other.runtimeType &&
1988            messagesForwarded == other.messagesForwarded &&
1989            messagesDelivered == other.messagesDelivered &&
1990            recipientsHelped == other.recipientsHelped &&
1991            totalXp == other.totalXp &&
1992            currentLevel == other.currentLevel &&
1993            levelTitle == other.levelTitle &&
1994            levelProgress == other.levelProgress &&
1995            xpForNextLevel == other.xpForNextLevel &&
1996            currentStreak == other.currentStreak;
1997  }
1998  
1999  /// Search result from message search.
2000  class SearchResultInfo {
2001    /// Message ID.
2002    final Uint8List messageId;
2003  
2004    /// Contact ID.
2005    final Uint8List contactId;
2006  
2007    /// Whether the message was sent by us.
2008    final bool isOutbound;
2009  
2010    /// Content type byte.
2011    final int contentType;
2012  
2013    /// Message content (text or filename for documents).
2014    final Uint8List content;
2015  
2016    /// Filename for document messages.
2017    final String? filename;
2018  
2019    /// Unix timestamp.
2020    final PlatformInt64 timestamp;
2021  
2022    const SearchResultInfo({
2023      required this.messageId,
2024      required this.contactId,
2025      required this.isOutbound,
2026      required this.contentType,
2027      required this.content,
2028      this.filename,
2029      required this.timestamp,
2030    });
2031  
2032    @override
2033    int get hashCode =>
2034        messageId.hashCode ^
2035        contactId.hashCode ^
2036        isOutbound.hashCode ^
2037        contentType.hashCode ^
2038        content.hashCode ^
2039        filename.hashCode ^
2040        timestamp.hashCode;
2041  
2042    @override
2043    bool operator ==(Object other) =>
2044        identical(this, other) ||
2045        other is SearchResultInfo &&
2046            runtimeType == other.runtimeType &&
2047            messageId == other.messageId &&
2048            contactId == other.contactId &&
2049            isOutbound == other.isOutbound &&
2050            contentType == other.contentType &&
2051            content == other.content &&
2052            filename == other.filename &&
2053            timestamp == other.timestamp;
2054  }
2055  
2056  /// Transport address for FFI.
2057  class TransportAddressDto {
2058    /// The transport type.
2059    final TransportTypeDto transportType;
2060  
2061    /// The raw address bytes.
2062    final Uint8List address;
2063  
2064    const TransportAddressDto({
2065      required this.transportType,
2066      required this.address,
2067    });
2068  
2069    /// Create from internal TransportAddress.
2070    static Future<TransportAddressDto?> fromInternal({
2071      required TransportAddress addr,
2072    }) =>
2073        RustLib.instance.api.crateApiTransportAddressDtoFromInternal(addr: addr);
2074  
2075    @override
2076    int get hashCode => transportType.hashCode ^ address.hashCode;
2077  
2078    @override
2079    bool operator ==(Object other) =>
2080        identical(this, other) ||
2081        other is TransportAddressDto &&
2082            runtimeType == other.runtimeType &&
2083            transportType == other.transportType &&
2084            address == other.address;
2085  }
2086  
2087  /// Information about an available transport.
2088  class TransportInfoDto {
2089    /// The transport type.
2090    final TransportTypeDto transportType;
2091  
2092    /// Human-readable name.
2093    final String name;
2094  
2095    /// Whether the transport is currently available.
2096    final bool available;
2097  
2098    /// Transport priority (lower = higher priority).
2099    final int priority;
2100  
2101    const TransportInfoDto({
2102      required this.transportType,
2103      required this.name,
2104      required this.available,
2105      required this.priority,
2106    });
2107  
2108    @override
2109    int get hashCode =>
2110        transportType.hashCode ^
2111        name.hashCode ^
2112        available.hashCode ^
2113        priority.hashCode;
2114  
2115    @override
2116    bool operator ==(Object other) =>
2117        identical(this, other) ||
2118        other is TransportInfoDto &&
2119            runtimeType == other.runtimeType &&
2120            transportType == other.transportType &&
2121            name == other.name &&
2122            available == other.available &&
2123            priority == other.priority;
2124  }
2125  
2126  /// Transport delivery statistics.
2127  class TransportStatsInfo {
2128    /// Messages delivered via BLE.
2129    final int bleCount;
2130  
2131    /// Messages delivered via DHT.
2132    final int dhtCount;
2133  
2134    /// Messages delivered via Relay.
2135    final int relayCount;
2136  
2137    /// Messages delivered via Iroh.
2138    final int irohCount;
2139  
2140    const TransportStatsInfo({
2141      required this.bleCount,
2142      required this.dhtCount,
2143      required this.relayCount,
2144      required this.irohCount,
2145    });
2146  
2147    static Future<TransportStatsInfo> default_() =>
2148        RustLib.instance.api.crateApiTransportStatsInfoDefault();
2149  
2150    @override
2151    int get hashCode =>
2152        bleCount.hashCode ^
2153        dhtCount.hashCode ^
2154        relayCount.hashCode ^
2155        irohCount.hashCode;
2156  
2157    @override
2158    bool operator ==(Object other) =>
2159        identical(this, other) ||
2160        other is TransportStatsInfo &&
2161            runtimeType == other.runtimeType &&
2162            bleCount == other.bleCount &&
2163            dhtCount == other.dhtCount &&
2164            relayCount == other.relayCount &&
2165            irohCount == other.irohCount;
2166  }
2167  
2168  /// Transport type for FFI.
2169  ///
2170  /// Maps to the internal TransportType enum.
2171  enum TransportTypeDto {
2172    /// Bluetooth Low Energy - proximity-based messaging.
2173    ble,
2174  
2175    /// Tor onion services - anonymous global messaging.
2176    tor,
2177  
2178    /// Distributed Hash Table - decentralized message storage.
2179    dht,
2180  
2181    /// Invisible Internet Project - anonymous overlay network.
2182    i2P,
2183  
2184    /// Relay server - push-based message delivery.
2185    relay,
2186  
2187    /// Iroh - QUIC-based P2P with NAT traversal.
2188    iroh;
2189  
2190    /// Convert from the internal TransportType.
2191    static Future<TransportTypeDto?> fromInternal({required TransportType tt}) =>
2192        RustLib.instance.api.crateApiTransportTypeDtoFromInternal(tt: tt);
2193  
2194    /// Convert to the internal TransportType.
2195    Future<TransportType> toInternal() =>
2196        RustLib.instance.api.crateApiTransportTypeDtoToInternal(that: this);
2197  }