package com.bitchat.android.ui import android.util.Log import com.bitchat.android.model.BitchatMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn /** * Centralized state definitions and data classes for the chat system */ // Command suggestion data class data class CommandSuggestion( val command: String, val aliases: List = emptyList(), val syntax: String? = null, val description: String ) /** * Contains all the observable state for the chat system */ class ChatState( scope: CoroutineScope ) { // Core messages and peer state private val _messages = MutableStateFlow>(emptyList()) val messages: StateFlow> = _messages.asStateFlow() private val _connectedPeers = MutableStateFlow>(emptyList()) val connectedPeers: StateFlow> = _connectedPeers.asStateFlow() private val _nickname = MutableStateFlow("") val nickname: StateFlow = _nickname.asStateFlow() private val _isConnected = MutableStateFlow(false) val isConnected: StateFlow = _isConnected.asStateFlow() // Private chats private val _privateChats = MutableStateFlow>>(emptyMap()) val privateChats: StateFlow>> = _privateChats.asStateFlow() private val _selectedPrivateChatPeer = MutableStateFlow(null) val selectedPrivateChatPeer: StateFlow = _selectedPrivateChatPeer.asStateFlow() private val _unreadPrivateMessages = MutableStateFlow>(emptySet()) val unreadPrivateMessages: StateFlow> = _unreadPrivateMessages.asStateFlow() // Channels private val _joinedChannels = MutableStateFlow>(emptySet()) val joinedChannels: StateFlow> = _joinedChannels.asStateFlow() private val _currentChannel = MutableStateFlow(null) val currentChannel: StateFlow = _currentChannel.asStateFlow() private val _channelMessages = MutableStateFlow>>(emptyMap()) val channelMessages: StateFlow>> = _channelMessages.asStateFlow() private val _unreadChannelMessages = MutableStateFlow>(emptyMap()) val unreadChannelMessages: StateFlow> = _unreadChannelMessages.asStateFlow() private val _passwordProtectedChannels = MutableStateFlow>(emptySet()) val passwordProtectedChannels: StateFlow> = _passwordProtectedChannels.asStateFlow() private val _showPasswordPrompt = MutableStateFlow(false) val showPasswordPrompt: StateFlow = _showPasswordPrompt.asStateFlow() private val _passwordPromptChannel = MutableStateFlow(null) val passwordPromptChannel: StateFlow = _passwordPromptChannel.asStateFlow() // Sidebar state private val _showSidebar = MutableStateFlow(false) val showSidebar: StateFlow = _showSidebar.asStateFlow() // Command autocomplete private val _showCommandSuggestions = MutableStateFlow(false) val showCommandSuggestions: StateFlow = _showCommandSuggestions.asStateFlow() private val _commandSuggestions = MutableStateFlow>(emptyList()) val commandSuggestions: StateFlow> = _commandSuggestions.asStateFlow() // Mention autocomplete private val _showMentionSuggestions = MutableStateFlow(false) val showMentionSuggestions: StateFlow = _showMentionSuggestions.asStateFlow() private val _mentionSuggestions = MutableStateFlow>(emptyList()) val mentionSuggestions: StateFlow> = _mentionSuggestions.asStateFlow() // Favorites private val _favoritePeers = MutableStateFlow>(emptySet()) val favoritePeers: StateFlow> = _favoritePeers.asStateFlow() // Noise session states for peers (for reactive UI updates) private val _peerSessionStates = MutableStateFlow>(emptyMap()) val peerSessionStates: StateFlow> = _peerSessionStates.asStateFlow() // Peer fingerprint state for reactive favorites (for reactive UI updates) private val _peerFingerprints = MutableStateFlow>(emptyMap()) val peerFingerprints: StateFlow> = _peerFingerprints.asStateFlow() private val _peerNicknames = MutableStateFlow>(emptyMap()) val peerNicknames: StateFlow> = _peerNicknames.asStateFlow() private val _peerRSSI = MutableStateFlow>(emptyMap()) val peerRSSI: StateFlow> = _peerRSSI.asStateFlow() // Direct connection status per peer (for live UI updates) private val _peerDirect = MutableStateFlow>(emptyMap()) val peerDirect: StateFlow> = _peerDirect.asStateFlow() // peerIDToPublicKeyFingerprint REMOVED - fingerprints now handled centrally in PeerManager // Navigation state private val _showAppInfo = MutableStateFlow(false) val showAppInfo: StateFlow = _showAppInfo.asStateFlow() // Location channels state (for Nostr geohash features) private val _selectedLocationChannel = MutableStateFlow(com.bitchat.android.geohash.ChannelID.Mesh) val selectedLocationChannel: StateFlow = _selectedLocationChannel.asStateFlow() private val _isTeleported = MutableStateFlow(false) val isTeleported: StateFlow = _isTeleported.asStateFlow() // Geohash people state (iOS-compatible) private val _geohashPeople = MutableStateFlow>(emptyList()) val geohashPeople: StateFlow> = _geohashPeople.asStateFlow() private val _teleportedGeo = MutableStateFlow>(emptySet()) val teleportedGeo: StateFlow> = _teleportedGeo.asStateFlow() // Geohash participant counts reactive state (for real-time location channel counts) private val _geohashParticipantCounts = MutableStateFlow>(emptyMap()) val geohashParticipantCounts: StateFlow> = _geohashParticipantCounts.asStateFlow() val hasUnreadChannels: StateFlow = _unreadChannelMessages .map { unreadMap -> unreadMap.values.any { it > 0 } } .stateIn( scope = scope, started = WhileSubscribed(5_000), initialValue = false ) val hasUnreadPrivateMessages: StateFlow = _unreadPrivateMessages .map { unreadSet -> unreadSet.isNotEmpty() } .stateIn( scope = scope, started = WhileSubscribed(5_000), initialValue = false ) // Getters for internal state access fun getMessagesValue() = _messages.value fun getConnectedPeersValue() = _connectedPeers.value fun getNicknameValue() = _nickname.value fun getPrivateChatsValue() = _privateChats.value fun getSelectedPrivateChatPeerValue() = _selectedPrivateChatPeer.value fun getUnreadPrivateMessagesValue() = _unreadPrivateMessages.value fun getJoinedChannelsValue() = _joinedChannels.value fun getCurrentChannelValue() = _currentChannel.value fun getChannelMessagesValue() = _channelMessages.value fun getUnreadChannelMessagesValue() = _unreadChannelMessages.value fun getPasswordProtectedChannelsValue() = _passwordProtectedChannels.value fun getShowPasswordPromptValue() = _showPasswordPrompt.value fun getPasswordPromptChannelValue() = _passwordPromptChannel.value fun getShowSidebarValue() = _showSidebar.value fun getShowCommandSuggestionsValue() = _showCommandSuggestions.value fun getCommandSuggestionsValue() = _commandSuggestions.value fun getShowMentionSuggestionsValue() = _showMentionSuggestions.value fun getMentionSuggestionsValue() = _mentionSuggestions.value fun getFavoritePeersValue() = _favoritePeers.value fun getPeerSessionStatesValue() = _peerSessionStates.value fun getPeerFingerprintsValue() = _peerFingerprints.value fun getShowAppInfoValue() = _showAppInfo.value fun getGeohashPeopleValue() = _geohashPeople.value fun getTeleportedGeoValue() = _teleportedGeo.value fun getGeohashParticipantCountsValue() = _geohashParticipantCounts.value // Setters for state updates fun setMessages(messages: List) { _messages.value = messages } fun setConnectedPeers(peers: List) { _connectedPeers.value = peers } fun postTeleportedGeo(teleported: Set) { _teleportedGeo.value = teleported } fun setNickname(nickname: String) { _nickname.value = nickname } fun setIsConnected(connected: Boolean) { _isConnected.value = connected } fun setPrivateChats(chats: Map>) { _privateChats.value = chats } fun setSelectedPrivateChatPeer(peerID: String?) { _selectedPrivateChatPeer.value = peerID } fun setUnreadPrivateMessages(unread: Set) { _unreadPrivateMessages.value = unread } fun setJoinedChannels(channels: Set) { _joinedChannels.value = channels } fun setCurrentChannel(channel: String?) { _currentChannel.value = channel } fun setChannelMessages(messages: Map>) { _channelMessages.value = messages } fun setUnreadChannelMessages(unread: Map) { _unreadChannelMessages.value = unread } fun setPasswordProtectedChannels(channels: Set) { _passwordProtectedChannels.value = channels } fun setShowPasswordPrompt(show: Boolean) { _showPasswordPrompt.value = show } fun setPasswordPromptChannel(channel: String?) { _passwordPromptChannel.value = channel } fun setShowSidebar(show: Boolean) { _showSidebar.value = show } fun setShowCommandSuggestions(show: Boolean) { _showCommandSuggestions.value = show } fun setCommandSuggestions(suggestions: List) { _commandSuggestions.value = suggestions } fun setShowMentionSuggestions(show: Boolean) { _showMentionSuggestions.value = show } fun setMentionSuggestions(suggestions: List) { _mentionSuggestions.value = suggestions } fun setFavoritePeers(favorites: Set) { val currentValue = _favoritePeers.value Log.d("ChatState", "setFavoritePeers called with ${favorites.size} favorites: $favorites") Log.d("ChatState", "Current value: $currentValue") Log.d("ChatState", "Values equal: ${currentValue == favorites}") Log.d("ChatState", "Setting on thread: ${Thread.currentThread().name}") // Always set the value - even if equal, this ensures observers are triggered _favoritePeers.value = favorites Log.d("ChatState", "StateFlow value after set: ${_favoritePeers.value}") } fun setPeerSessionStates(states: Map) { _peerSessionStates.value = states } fun setPeerFingerprints(fingerprints: Map) { _peerFingerprints.value = fingerprints } fun setPeerNicknames(nicknames: Map) { _peerNicknames.value = nicknames } fun setPeerRSSI(rssi: Map) { _peerRSSI.value = rssi } fun setPeerDirect(direct: Map) { _peerDirect.value = direct } fun setShowAppInfo(show: Boolean) { _showAppInfo.value = show } fun setSelectedLocationChannel(channel: com.bitchat.android.geohash.ChannelID?) { _selectedLocationChannel.value = channel } fun setIsTeleported(teleported: Boolean) { _isTeleported.value = teleported } fun setGeohashPeople(people: List) { _geohashPeople.value = people } fun setTeleportedGeo(teleported: Set) { _teleportedGeo.value = teleported } fun setGeohashParticipantCounts(counts: Map) { _geohashParticipantCounts.value = counts } }