package com.bitchat.android.ui import androidx.lifecycle.LifecycleCoroutineScope import com.bitchat.android.mesh.BluetoothMeshDelegate import com.bitchat.android.model.BitchatMessage import com.bitchat.android.model.DeliveryAck import com.bitchat.android.model.DeliveryStatus import com.bitchat.android.model.ReadReceipt import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import java.util.* /** * Handles all BluetoothMeshDelegate callbacks and routes them to appropriate managers */ class MeshDelegateHandler( private val state: ChatState, private val messageManager: MessageManager, private val channelManager: ChannelManager, private val privateChatManager: PrivateChatManager, private val coroutineScope: CoroutineScope, private val onHapticFeedback: () -> Unit, private val getMyPeerID: () -> String ) : BluetoothMeshDelegate { override fun didReceiveMessage(message: BitchatMessage) { coroutineScope.launch { // FIXED: Deduplicate messages from dual connection paths val messageKey = messageManager.generateMessageKey(message) if (messageManager.isMessageProcessed(messageKey)) { return@launch // Duplicate message, ignore } messageManager.markMessageProcessed(messageKey) // Check if sender is blocked message.senderPeerID?.let { senderPeerID -> if (privateChatManager.isPeerBlocked(senderPeerID)) { return@launch } } // Trigger haptic feedback onHapticFeedback() if (message.isPrivate) { // Private message privateChatManager.handleIncomingPrivateMessage(message) } else if (message.channel != null) { // Channel message if (state.getJoinedChannelsValue().contains(message.channel)) { channelManager.addChannelMessage(message.channel, message, message.senderPeerID) } } else { // Public message messageManager.addMessage(message) } // Periodic cleanup if (messageManager.isMessageProcessed("cleanup_check_${System.currentTimeMillis()/30000}")) { messageManager.cleanupDeduplicationCaches() } } } override fun didConnectToPeer(peerID: String) { coroutineScope.launch { // FIXED: Deduplicate connection events from dual connection paths if (messageManager.isDuplicateSystemEvent("connect", peerID)) { return@launch } val systemMessage = BitchatMessage( sender = "system", content = "$peerID connected", timestamp = Date(), isRelay = false ) messageManager.addMessage(systemMessage) } } override fun didDisconnectFromPeer(peerID: String) { coroutineScope.launch { // FIXED: Deduplicate disconnection events from dual connection paths if (messageManager.isDuplicateSystemEvent("disconnect", peerID)) { return@launch } val systemMessage = BitchatMessage( sender = "system", content = "$peerID disconnected", timestamp = Date(), isRelay = false ) messageManager.addMessage(systemMessage) } } override fun didUpdatePeerList(peers: List) { coroutineScope.launch { state.setConnectedPeers(peers) state.setIsConnected(peers.isNotEmpty()) // Clean up channel members who disconnected channelManager.cleanupDisconnectedMembers(peers, getMyPeerID()) // Exit private chat if peer disconnected state.getSelectedPrivateChatPeerValue()?.let { currentPeer -> if (!peers.contains(currentPeer)) { privateChatManager.cleanupDisconnectedPeer(currentPeer) } } } } override fun didReceiveChannelLeave(channel: String, fromPeer: String) { coroutineScope.launch { channelManager.removeChannelMember(channel, fromPeer) } } override fun didReceiveDeliveryAck(ack: DeliveryAck) { coroutineScope.launch { messageManager.updateMessageDeliveryStatus(ack.originalMessageID, DeliveryStatus.Delivered(ack.recipientNickname, ack.timestamp)) } } override fun didReceiveReadReceipt(receipt: ReadReceipt) { coroutineScope.launch { messageManager.updateMessageDeliveryStatus(receipt.originalMessageID, DeliveryStatus.Read(receipt.readerNickname, receipt.timestamp)) } } override fun decryptChannelMessage(encryptedContent: ByteArray, channel: String): String? { return channelManager.decryptChannelMessage(encryptedContent, channel) } override fun getNickname(): String? = state.getNicknameValue() override fun isFavorite(peerID: String): Boolean { return privateChatManager.isFavorite(peerID) } }