package ru.forbion.f7cloud.feature.talk

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.isActive
import ru.forbion.f7cloud.core.auth.AuthSession
import ru.forbion.f7cloud.core.designsystem.AppForegroundTracker
import ru.forbion.f7cloud.core.network.UnauthorizedException

data class TalkUiState(
    val loading: Boolean = false,
    val rooms: List<TalkRoom> = emptyList(),
    val messages: List<TalkMessage> = emptyList(),
    val selectedRoomToken: String? = null,
    val searchQuery: String = "",
    val roomFilter: TalkRoomFilter = TalkRoomFilter.ALL,
    val showFilterSheet: Boolean = false,
    val collapseSystemMessages: Boolean = true,
    val openRoomLastReadMessageId: Long = 0L,
    val sending: Boolean = false,
    val uploading: Boolean = false,
    val error: String? = null,
    val unauthorized: Boolean = false,
    val participants: List<TalkParticipant> = emptyList(),
    val participantsLoading: Boolean = false,
    val showParticipants: Boolean = false,
    val showCreateRoom: Boolean = false,
    val createRoomMode: CreateRoomMode = CreateRoomMode.ONE_TO_ONE,
    val createRoomName: String = "",
    val userSearchQuery: String = "",
    val userSearchResults: List<TalkUserCandidate> = emptyList(),
    val selectedUsers: List<TalkUserCandidate> = emptyList(),
    val userSearchLoading: Boolean = false,
    val creatingRoom: Boolean = false,
    val replyToMessage: TalkMessage? = null,
    val showRoomInfo: Boolean = false,
    val messageSearchQuery: String = "",
    val messageSearchResults: List<TalkMessage> = emptyList(),
    val messageSearchLoading: Boolean = false,
    val offlineCached: Boolean = false,
    val highlightMessageId: Long? = null,
    val mentionCandidates: List<TalkMention> = emptyList(),
) {
    val filteredRooms: List<TalkRoom>
        get() {
            val q = searchQuery.trim()
            return rooms.filter { room ->
                room.matchesFilter(roomFilter) &&
                    (q.isEmpty() ||
                        room.displayName.contains(q, ignoreCase = true) ||
                        room.lastMessagePreview.contains(q, ignoreCase = true))
            }
        }

    val activeFilterCount: Int
        get() = if (roomFilter == TalkRoomFilter.ALL) 0 else 1

    val selectedRoom: TalkRoom?
        get() = selectedRoomToken?.let { token -> rooms.find { it.token == token } }
}

class TalkViewModel(
    private val repository: TalkRepository = TalkRepository(),
    private val offlineRepository: TalkOfflineRepository? = null,
) : ViewModel() {
    private val _state = MutableStateFlow(TalkUiState())
    val state: StateFlow<TalkUiState> = _state.asStateFlow()
    private var pollingJob: Job? = null
    private var userSearchJob: Job? = null
    private var mentionSearchJob: Job? = null
    private var lastKnownMessageId: Long = 0L

    fun load(session: AuthSession) {
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null)
            val cached = offlineRepository?.cachedRooms().orEmpty()
            if (cached.isNotEmpty()) {
                _state.value = _state.value.copy(rooms = cached, offlineCached = true, loading = true)
            }
            runCatching { repository.listRooms(session) }
                .onSuccess { rooms ->
                    offlineRepository?.cacheRooms(rooms)
                    _state.value = _state.value.copy(
                        loading = false,
                        rooms = rooms,
                        error = null,
                        offlineCached = false,
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        loading = false,
                        error = if (cached.isEmpty()) t.message else null,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun setSearchQuery(query: String) {
        _state.value = _state.value.copy(searchQuery = query)
    }

    fun setRoomFilter(filter: TalkRoomFilter) {
        _state.value = _state.value.copy(roomFilter = filter, showFilterSheet = false)
    }

    fun openFilterSheet() {
        _state.value = _state.value.copy(showFilterSheet = true)
    }

    fun closeFilterSheet() {
        _state.value = _state.value.copy(showFilterSheet = false)
    }

    fun setReplyTo(message: TalkMessage?) {
        _state.value = _state.value.copy(replyToMessage = message)
    }

    fun setMentionQuery(session: AuthSession, query: String?) {
        mentionSearchJob?.cancel()
        val token = _state.value.selectedRoomToken
        if (query == null || token == null) {
            _state.value = _state.value.copy(mentionCandidates = emptyList())
            return
        }
        mentionSearchJob = viewModelScope.launch(Dispatchers.IO) {
            delay(200)
            runCatching { repository.searchMentions(session, token, query) }
                .onSuccess { results ->
                    _state.value = _state.value.copy(mentionCandidates = results)
                }
                .onFailure {
                    _state.value = _state.value.copy(mentionCandidates = emptyList())
                }
        }
    }

    fun clearMentionCandidates() {
        mentionSearchJob?.cancel()
        _state.value = _state.value.copy(mentionCandidates = emptyList())
    }

    fun openRoomInfo() {
        _state.value = _state.value.copy(showRoomInfo = true)
    }

    fun closeRoomInfo() {
        _state.value = _state.value.copy(showRoomInfo = false)
    }

    fun setMessageSearchQuery(query: String) {
        _state.value = _state.value.copy(messageSearchQuery = query)
    }

    fun searchMessagesInRoom(session: AuthSession) {
        val token = _state.value.selectedRoomToken ?: return
        val query = _state.value.messageSearchQuery.trim()
        if (query.length < 2) {
            _state.value = _state.value.copy(messageSearchResults = emptyList())
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(messageSearchLoading = true)
            runCatching { repository.searchMessages(session, token, query) }
                .onSuccess { results ->
                    _state.value = _state.value.copy(
                        messageSearchResults = results,
                        messageSearchLoading = false,
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        messageSearchLoading = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun addReaction(session: AuthSession, messageId: Long, emoji: String) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.addReaction(session, token, messageId, emoji) }
                .onSuccess { refreshMessages(session) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun addParticipantToRoom(session: AuthSession, userId: String) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.addParticipant(session, token, userId) }
                .onSuccess { openParticipants(session) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun setRoomNotificationLevel(session: AuthSession, level: Int) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.setNotificationLevel(session, token, level) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun toggleSystemMessagesCollapse() {
        _state.value = _state.value.copy(collapseSystemMessages = !_state.value.collapseSystemMessages)
    }

    fun toggleFavorite(session: AuthSession, roomToken: String) {
        val room = _state.value.rooms.find { it.token == roomToken } ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.setFavorite(session, roomToken, !room.isFavorite) }
                .onSuccess {
                    _state.value = _state.value.copy(
                        rooms = _state.value.rooms.map {
                            if (it.token == roomToken) it.copy(isFavorite = !room.isFavorite) else it
                        },
                    )
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(error = t.message, unauthorized = t is UnauthorizedException)
                }
        }
    }

    fun markRoomUnread(session: AuthSession, roomToken: String) {
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.markUnread(session, roomToken) }
                .onSuccess {
                    runCatching { repository.listRooms(session) }
                        .onSuccess { rooms -> _state.value = _state.value.copy(rooms = rooms) }
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(error = t.message, unauthorized = t is UnauthorizedException)
                }
        }
    }

    fun openCreateRoom() {
        _state.value = _state.value.copy(
            showCreateRoom = true,
            createRoomMode = CreateRoomMode.ONE_TO_ONE,
            createRoomName = "",
            userSearchQuery = "",
            userSearchResults = emptyList(),
            selectedUsers = emptyList(),
            error = null,
        )
    }

    fun closeCreateRoom() {
        userSearchJob?.cancel()
        _state.value = _state.value.copy(
            showCreateRoom = false,
            userSearchQuery = "",
            userSearchResults = emptyList(),
            selectedUsers = emptyList(),
            createRoomName = "",
            creatingRoom = false,
        )
    }

    fun setCreateRoomMode(mode: CreateRoomMode) {
        _state.value = _state.value.copy(
            createRoomMode = mode,
            selectedUsers = emptyList(),
            createRoomName = if (mode == CreateRoomMode.ONE_TO_ONE) "" else _state.value.createRoomName,
        )
    }

    fun setCreateRoomName(name: String) {
        _state.value = _state.value.copy(createRoomName = name)
    }

    fun setUserSearchQuery(session: AuthSession, query: String) {
        _state.value = _state.value.copy(userSearchQuery = query)
        userSearchJob?.cancel()
        if (query.trim().length < 2) {
            _state.value = _state.value.copy(userSearchResults = emptyList(), userSearchLoading = false)
            return
        }
        userSearchJob = viewModelScope.launch(Dispatchers.IO) {
            delay(300)
            _state.value = _state.value.copy(userSearchLoading = true)
            runCatching { repository.searchUsers(session, query) }
                .onSuccess { results ->
                    _state.value = _state.value.copy(userSearchResults = results, userSearchLoading = false)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        userSearchResults = emptyList(),
                        userSearchLoading = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun toggleUserSelection(user: TalkUserCandidate) {
        val current = _state.value
        val selected = if (current.createRoomMode == CreateRoomMode.ONE_TO_ONE) {
            listOf(user)
        } else {
            if (current.selectedUsers.any { it.userId == user.userId }) {
                current.selectedUsers.filter { it.userId != user.userId }
            } else {
                current.selectedUsers + user
            }
        }
        _state.value = current.copy(selectedUsers = selected)
    }

    fun createRoom(session: AuthSession) {
        val current = _state.value
        val selected = current.selectedUsers
        if (selected.isEmpty()) {
            _state.value = current.copy(error = "Выберите участника")
            return
        }
        if (current.createRoomMode == CreateRoomMode.GROUP && current.createRoomName.trim().isEmpty()) {
            _state.value = current.copy(error = "Введите название группы")
            return
        }
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(creatingRoom = true, error = null)
            runCatching {
                when (current.createRoomMode) {
                    CreateRoomMode.ONE_TO_ONE -> repository.createOneToOneRoom(session, selected.first().userId)
                    CreateRoomMode.GROUP -> repository.createGroupRoom(
                        session = session,
                        name = current.createRoomName.trim(),
                        userIds = selected.map { it.userId },
                    )
                }
            }
                .onSuccess { room ->
                    val rooms = runCatching { repository.listRooms(session) }
                        .getOrDefault(listOf(room) + current.rooms.filter { it.token != room.token })
                    _state.value = _state.value.copy(
                        rooms = rooms,
                        creatingRoom = false,
                        showCreateRoom = false,
                        userSearchQuery = "",
                        userSearchResults = emptyList(),
                        selectedUsers = emptyList(),
                        createRoomName = "",
                    )
                    openRoom(session, room.token)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        creatingRoom = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun openParticipants(session: AuthSession) {
        val token = _state.value.selectedRoomToken ?: return
        _state.value = _state.value.copy(showParticipants = true, participantsLoading = true, error = null)
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.listParticipants(session, token) }
                .onSuccess { participants ->
                    _state.value = _state.value.copy(participants = participants, participantsLoading = false)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        participantsLoading = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun closeParticipants() {
        _state.value = _state.value.copy(showParticipants = false)
    }

    fun openRoom(session: AuthSession, roomToken: String, scrollToMessageId: Long? = null) {
        lastKnownMessageId = 0L
        val lastRead = _state.value.rooms.find { it.token == roomToken }?.lastReadMessage ?: 0L
        viewModelScope.launch(Dispatchers.IO) {
            val cached = offlineRepository?.cachedMessages(roomToken).orEmpty()
            _state.value = _state.value.copy(
                selectedRoomToken = roomToken,
                messages = cached,
                participants = emptyList(),
                showParticipants = false,
                openRoomLastReadMessageId = lastRead,
                error = null,
                replyToMessage = null,
                messageSearchQuery = "",
                messageSearchResults = emptyList(),
                highlightMessageId = scrollToMessageId,
            )
            refreshMessages(session)
            startPolling(session)
        }
    }

    fun closeRoom(session: AuthSession) {
        stopPolling()
        val token = _state.value.selectedRoomToken
        lastKnownMessageId = 0L
        _state.value = _state.value.copy(
            selectedRoomToken = null,
            messages = emptyList(),
            participants = emptyList(),
            showParticipants = false,
            error = null,
        )
        if (token != null) {
            viewModelScope.launch(Dispatchers.IO) {
                runCatching { repository.setReadMarker(session, token) }
                runCatching { repository.listRooms(session) }
                    .onSuccess { rooms -> _state.value = _state.value.copy(rooms = rooms) }
            }
        }
    }

    fun refreshMessages(session: AuthSession) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(loading = true, error = null)
            runCatching {
                repository.loadMessages(
                    session = session,
                    roomToken = token,
                    lastKnownMessageId = 0L,
                    lookIntoFuture = false,
                    setReadMarker = true,
                )
            }
                .onSuccess { msgs ->
                    lastKnownMessageId = msgs.maxOfOrNull { it.id } ?: 0L
                    offlineRepository?.cacheMessages(token, msgs)
                    _state.value = _state.value.copy(loading = false, messages = msgs, error = null)
                    markRoomReadLocally(token)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        loading = false,
                        error = if (_state.value.messages.isEmpty()) t.message else null,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun syncFromPush(session: AuthSession, roomToken: String?) {
        viewModelScope.launch(Dispatchers.IO) {
            val openToken = _state.value.selectedRoomToken
            if (!roomToken.isNullOrBlank() && roomToken == openToken) {
                runCatching {
                    repository.loadMessages(
                        session = session,
                        roomToken = roomToken,
                        lastKnownMessageId = 0L,
                        lookIntoFuture = false,
                        setReadMarker = true,
                    )
                }.onSuccess { msgs ->
                    lastKnownMessageId = msgs.maxOfOrNull { it.id } ?: lastKnownMessageId
                    offlineRepository?.cacheMessages(roomToken, msgs)
                    _state.value = _state.value.copy(messages = msgs, error = null)
                    markRoomReadLocally(roomToken)
                }
            }
            runCatching { repository.listRooms(session) }
                .onSuccess { rooms ->
                    offlineRepository?.cacheRooms(rooms)
                    _state.value = _state.value.copy(rooms = rooms, error = null)
                }
        }
    }

    fun sendMessage(session: AuthSession, text: String) {
        val token = _state.value.selectedRoomToken ?: return
        val trimmed = text.trim()
        if (trimmed.isEmpty()) return
        viewModelScope.launch(Dispatchers.IO) {
            val localId = -System.currentTimeMillis()
            val pendingMessage = TalkMessage(
                id = localId,
                actorDisplayName = "Вы",
                text = trimmed,
                timestamp = System.currentTimeMillis() / 1000L,
                status = TalkMessage.Status.SENDING,
            )
            _state.value = _state.value.copy(
                sending = true,
                error = null,
                messages = _state.value.messages + pendingMessage,
            )
            runCatching {
                repository.sendMessage(session, token, trimmed, _state.value.replyToMessage?.id)
            }
                .onSuccess {
                    val msgs = runCatching {
                        repository.loadMessages(
                            session = session,
                            roomToken = token,
                            lastKnownMessageId = 0L,
                            lookIntoFuture = false,
                            setReadMarker = true,
                        )
                    }.getOrDefault(_state.value.messages.filter { it.id != localId })
                    lastKnownMessageId = msgs.maxOfOrNull { it.id } ?: lastKnownMessageId
                    offlineRepository?.cacheMessages(token, msgs)
                    _state.value = _state.value.copy(
                        sending = false,
                        messages = msgs,
                        error = null,
                        replyToMessage = null,
                    )
                    markRoomReadLocally(token)
                }
                .onFailure { t ->
                    val updated = _state.value.messages.map { msg ->
                        if (msg.id == localId) msg.copy(status = TalkMessage.Status.FAILED) else msg
                    }
                    _state.value = _state.value.copy(
                        sending = false,
                        messages = updated,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    private fun startPolling(session: AuthSession) {
        stopPolling()
        pollingJob = viewModelScope.launch(Dispatchers.IO) {
            while (isActive) {
                if (!AppForegroundTracker.isForeground) {
                    delay(BACKGROUND_POLL_DELAY_MS)
                    continue
                }
                val token = _state.value.selectedRoomToken ?: break
                try {
                    val incoming = repository.loadMessages(
                        session = session,
                        roomToken = token,
                        lastKnownMessageId = lastKnownMessageId,
                        lookIntoFuture = true,
                        setReadMarker = true,
                        timeoutSeconds = 25,
                    )
                    if (incoming.isNotEmpty()) {
                        val merged = mergeMessages(_state.value.messages, incoming)
                        lastKnownMessageId = merged.maxOfOrNull { it.id } ?: lastKnownMessageId
                        offlineRepository?.cacheMessages(token, merged)
                        _state.value = _state.value.copy(messages = merged)
                        markRoomReadLocally(token)
                    }
                } catch (t: Throwable) {
                    if (t is UnauthorizedException) {
                        _state.value = _state.value.copy(unauthorized = true, error = t.message)
                        return@launch
                    }
                    delay(POLL_ERROR_DELAY_MS)
                }
            }
        }
    }

    fun deleteMessage(session: AuthSession, messageId: Long) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.deleteMessage(session, token, messageId) }
                .onSuccess { refreshMessages(session) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun editMessage(session: AuthSession, messageId: Long, newText: String) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            runCatching { repository.editMessage(session, token, messageId, newText) }
                .onSuccess { refreshMessages(session) }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun uploadVoiceRecording(
        session: AuthSession,
        file: java.io.File,
    ) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(uploading = true, error = null)
            runCatching {
                val bytes = file.readBytes()
                repository.uploadAttachment(
                    session = session,
                    roomToken = token,
                    fileName = file.name,
                    mimeType = "audio/mp4",
                    bytes = bytes,
                    replyTo = _state.value.replyToMessage?.id,
                )
                file.delete()
            }
                .onSuccess {
                    _state.value = _state.value.copy(uploading = false, replyToMessage = null)
                    refreshMessages(session)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        uploading = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    fun uploadAttachment(
        context: android.content.Context,
        session: AuthSession,
        uri: android.net.Uri,
        caption: String = "",
    ) {
        val token = _state.value.selectedRoomToken ?: return
        viewModelScope.launch(Dispatchers.IO) {
            _state.value = _state.value.copy(uploading = true, error = null)
            runCatching {
                val resolver = context.contentResolver
                val mimeType = resolver.getType(uri).orEmpty()
                val fileName = resolveDisplayName(context, uri, mimeType)
                val bytes = resolver.openInputStream(uri)?.use { it.readBytes() }
                    ?: error("Не удалось прочитать файл")
                repository.uploadAttachment(
                    session = session,
                    roomToken = token,
                    fileName = fileName,
                    mimeType = mimeType,
                    bytes = bytes,
                    caption = caption,
                    replyTo = _state.value.replyToMessage?.id,
                )
            }
                .onSuccess {
                    _state.value = _state.value.copy(uploading = false, replyToMessage = null)
                    refreshMessages(session)
                }
                .onFailure { t ->
                    _state.value = _state.value.copy(
                        uploading = false,
                        error = t.message,
                        unauthorized = t is UnauthorizedException,
                    )
                }
        }
    }

    private fun resolveDisplayName(context: android.content.Context, uri: android.net.Uri, mimeType: String): String {
        context.contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.DISPLAY_NAME), null, null, null)
            ?.use { cursor ->
                if (cursor.moveToFirst()) {
                    val idx = cursor.getColumnIndex(android.provider.OpenableColumns.DISPLAY_NAME)
                    if (idx >= 0) {
                        cursor.getString(idx)?.trim()?.takeIf { it.isNotBlank() }?.let { return it }
                    }
                }
            }
        val ext = android.webkit.MimeTypeMap.getSingleton()
            .getExtensionFromMimeType(mimeType)
            ?.takeIf { it.isNotBlank() }
        return if (ext != null) "upload.$ext" else "upload.bin"
    }

    fun clearHighlightMessage() {
        _state.value = _state.value.copy(highlightMessageId = null)
    }

    private fun markRoomReadLocally(token: String) {
        _state.value = _state.value.copy(
            rooms = _state.value.rooms.map { room ->
                if (room.token == token) {
                    room.copy(unreadMessages = 0, unreadMention = false)
                } else {
                    room
                }
            },
        )
    }

    private fun mergeMessages(current: List<TalkMessage>, incoming: List<TalkMessage>): List<TalkMessage> {
        if (incoming.isEmpty()) return current
        val byId = LinkedHashMap<Long, TalkMessage>()
        current.filter { it.id > 0 }.forEach { byId[it.id] = it }
        incoming.forEach { byId[it.id] = it }
        return byId.values.sortedBy { it.id }
    }

    private fun stopPolling() {
        pollingJob?.cancel()
        pollingJob = null
    }

    override fun onCleared() {
        stopPolling()
        userSearchJob?.cancel()
        mentionSearchJob?.cancel()
        super.onCleared()
    }

    companion object {
        private const val BACKGROUND_POLL_DELAY_MS = 5_000L
        private const val POLL_ERROR_DELAY_MS = 5_000L
    }
}
