/*
 * Copyright (c) 2022 Proton AG
 *
 * This file is part of Proton Mail.
 *
 * Proton Mail is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Proton Mail is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Proton Mail. If not, see https://www.gnu.org/licenses/.
 */
package ch.protonmail.android.mailbox.presentation.ui

import android.Manifest
import android.app.AlertDialog
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.Resources
import android.graphics.Canvas
import android.net.Uri
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.ActionMode
import android.view.Gravity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.IdRes
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.os.postDelayed
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.loader.app.LoaderManager
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import ch.protonmail.android.R
import ch.protonmail.android.activities.StartCompose
import ch.protonmail.android.activities.StartOnboarding
import ch.protonmail.android.activities.StartSearch
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
import ch.protonmail.android.adapters.messages.MailboxItemViewHolder.MessageViewHolder
import ch.protonmail.android.adapters.messages.MailboxRecyclerViewAdapter
import ch.protonmail.android.adapters.swipe.ArchiveSwipeHandler
import ch.protonmail.android.adapters.swipe.MarkReadSwipeHandler
import ch.protonmail.android.adapters.swipe.SpamSwipeHandler
import ch.protonmail.android.adapters.swipe.StarSwipeHandler
import ch.protonmail.android.adapters.swipe.SwipeAction
import ch.protonmail.android.adapters.swipe.TrashSwipeHandler
import ch.protonmail.android.api.models.SimpleMessage
import ch.protonmail.android.api.segments.event.AlarmReceiver
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.Constants.DrawerOptionType
import ch.protonmail.android.core.Constants.MessageLocationType
import ch.protonmail.android.core.Constants.MessageLocationType.Companion.fromInt
import ch.protonmail.android.core.Constants.Prefs.PREF_DONT_SHOW_PLAY_SERVICES
import ch.protonmail.android.core.Constants.Prefs.PREF_NEW_USER_ONBOARDING_SHOWN
import ch.protonmail.android.core.Constants.Prefs.PREF_USED_SPACE
import ch.protonmail.android.data.local.model.Message
import ch.protonmail.android.details.presentation.ui.MessageDetailsActivity
import ch.protonmail.android.di.DefaultSharedPreferences
import ch.protonmail.android.events.FetchLabelsEvent
import ch.protonmail.android.events.MailboxLoadedEvent
import ch.protonmail.android.events.MailboxNoMessagesEvent
import ch.protonmail.android.events.SettingsChangedEvent
import ch.protonmail.android.events.Status
import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.feature.rating.StartRateAppFlow
import ch.protonmail.android.labels.domain.model.Label
import ch.protonmail.android.labels.domain.model.LabelId
import ch.protonmail.android.labels.domain.model.LabelType
import ch.protonmail.android.labels.presentation.ui.LabelsActionSheet
import ch.protonmail.android.mailbox.presentation.model.EmptyMailboxUiModel
import ch.protonmail.android.mailbox.presentation.model.MailboxItemUiModel
import ch.protonmail.android.mailbox.presentation.model.MailboxListState
import ch.protonmail.android.mailbox.presentation.model.MailboxState
import ch.protonmail.android.mailbox.presentation.model.UnreadChipState
import ch.protonmail.android.mailbox.presentation.util.ConversationModeEnabled
import ch.protonmail.android.mailbox.presentation.viewmodel.FLOW_START_ACTIVITY
import ch.protonmail.android.mailbox.presentation.viewmodel.FLOW_TRY_COMPOSE
import ch.protonmail.android.mailbox.presentation.viewmodel.FLOW_USED_SPACE_CHANGED
import ch.protonmail.android.mailbox.presentation.viewmodel.MailboxViewModel
import ch.protonmail.android.mailbox.presentation.viewmodel.MailboxViewModel.MaxLabelsReached
import ch.protonmail.android.navigation.presentation.EXTRA_FIRST_LOGIN
import ch.protonmail.android.navigation.presentation.NavigationActivity
import ch.protonmail.android.notifications.data.remote.fcm.MultiUserFcmTokenManager
import ch.protonmail.android.notifications.data.remote.fcm.RegisterDeviceWorker
import ch.protonmail.android.notifications.data.remote.fcm.model.FirebaseToken
import ch.protonmail.android.notifications.presentation.utils.EXTRA_MAILBOX_LOCATION
import ch.protonmail.android.pendingaction.data.PendingActionDao
import ch.protonmail.android.pendingaction.data.PendingActionDatabase
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.settings.domain.usecase.GetMailSettings
import ch.protonmail.android.ui.actionsheet.MessageActionSheet
import ch.protonmail.android.ui.actionsheet.model.ActionSheetTarget
import ch.protonmail.android.utils.Event
import ch.protonmail.android.utils.MessageUtils
import ch.protonmail.android.utils.NetworkSnackBarUtil
import ch.protonmail.android.utils.extensions.app
import ch.protonmail.android.utils.extensions.getColorIdFromAttr
import ch.protonmail.android.utils.extensions.showToast
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showDeleteConfirmationDialog
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showInfoDialog
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showTwoButtonInfoDialog
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showUndoSnackbar
import ch.protonmail.android.utils.ui.selection.SelectionModeEnum
import ch.protonmail.android.views.messageDetails.BottomActionsView
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.iid.FirebaseInstanceId
import com.squareup.otto.Subscribe
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_mailbox.*
import kotlinx.android.synthetic.main.layout_mailbox_status_view.*
import kotlinx.android.synthetic.main.navigation_drawer.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import me.proton.core.domain.entity.UserId
import me.proton.core.mailsettings.domain.entity.MailSettings
import me.proton.core.util.android.sharedpreferences.get
import me.proton.core.util.android.sharedpreferences.observe
import me.proton.core.util.android.sharedpreferences.set
import me.proton.core.util.kotlin.EMPTY_STRING
import me.proton.core.util.kotlin.exhaustive
import timber.log.Timber
import java.lang.ref.WeakReference
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlin.time.DurationUnit
import kotlin.time.toDuration

private const val TAG_MAILBOX_ACTIVITY = "MailboxActivity"
private const val PLAY_SERVICES_RESOLUTION_REQUEST = 9000
private const val STATE_MAILBOX_LOCATION = "mailbox_location"
private const val STATE_MAILBOX_LABEL_LOCATION = "mailbox_label_location"
private const val STATE_MAILBOX_LABEL_LOCATION_NAME = "mailbox_label_location_name"
const val LOADER_ID_LABELS_OFFLINE = 32

@AndroidEntryPoint
internal class MailboxActivity :
    NavigationActivity(),
    ActionMode.Callback,
    OnRefreshListener {

    private lateinit var pendingActionDao: PendingActionDao

    @Inject
    lateinit var messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory

    @Inject
    lateinit var networkSnackBarUtil: NetworkSnackBarUtil

    @Inject
    lateinit var registerDeviceWorkerEnqueuer: RegisterDeviceWorker.Enqueuer

    @Inject
    lateinit var multiUserFcmTokenManager: MultiUserFcmTokenManager

    @Inject
    lateinit var isConversationModeEnabled: ConversationModeEnabled

    @Inject
    lateinit var startRateAppFlow: StartRateAppFlow

    @Inject
    @DefaultSharedPreferences
    lateinit var defaultSharedPreferences: SharedPreferences

    private lateinit var mailboxAdapter: MailboxRecyclerViewAdapter
    private var swipeController: SwipeController = SwipeController()

    private val isLoadingMore = AtomicBoolean(false)
    private var scrollStateChanged = false
    private var actionMode: ActionMode? = null
    // For the time being we don't show it at all; will be re-added in MAILAND-2320
    private var swipeCustomizeSnack: Snackbar? = null
    private var mailboxLabelId: String? = null
        set(value) {
            field = value
            // we need to set it on view model for filtering by label
            setNewLabel(value ?: EMPTY_STRING)
        }
    private var mailboxLabelName: String? = null
    private var lastFetchedMailboxItemsIds = emptyList<String>()
    private var refreshMailboxJobRunning = false
    private lateinit var syncUUID: String
    private var customizeSwipeSnackShown = false
    private val mailboxViewModel: MailboxViewModel by viewModels()
    private var storageLimitApproachingAlertDialog: AlertDialog? = null
    private val handler = Handler(Looper.getMainLooper())
    private var isOnline = true

    private val startMessageDetailsLauncher = registerForActivityResult(MessageDetailsActivity.Launcher()) {}
    private val startComposeLauncher = registerForActivityResult(StartCompose()) { messageId ->
        messageId?.let {
            val snack = Snackbar.make(
                findViewById(R.id.drawer_layout),
                R.string.snackbar_message_draft_saved,
                Snackbar.LENGTH_LONG
            )
            snack.setAction(R.string.move_to_trash) {
                mailboxViewModel.moveToFolder(
                    listOf(messageId),
                    userManager.requireCurrentUserId(),
                    MessageLocationType.DRAFT,
                    MessageLocationType.TRASH.asLabelIdString()
                )
                Snackbar.make(
                    findViewById(R.id.drawer_layout),
                    R.string.snackbar_message_draft_moved_to_trash,
                    Snackbar.LENGTH_LONG
                ).show()
            }
            snack.show()
        }
    }
    private val startSearchLauncher = registerForActivityResult(StartSearch()) {}

    private val requestPermissionLauncher =
        registerForActivityResult(
            ActivityResultContracts.RequestPermission()
        ) { isGranted: Boolean ->
            if (!isGranted) {
                showInfoDialog(
                    this, getString(R.string.need_permissions_title),
                    getString(R.string.need_notification_permissions_to_receive_notifications)
                ) { unit: Unit -> unit }
            }
        }

    private val startOnboardingActivityLauncher = registerForActivityResult(StartOnboarding()) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            requestPermissionLauncher.launch(
                Manifest.permission.POST_NOTIFICATIONS
            )
        }

    }

    override val currentLabelId get() = mailboxLabelId

    override fun getLayoutId(): Int = R.layout.activity_mailbox

    override fun onCreate(savedInstanceState: Bundle?) {
        installSplashScreen().setKeepOnScreenCondition {
            accountStateManager.state.value == AccountStateManager.State.Processing
        }
        super.onCreate(savedInstanceState)

        // TODO if we decide to use special flag for switching (and not login), change this
        if (intent.getBooleanExtra(EXTRA_FIRST_LOGIN, false)) {
            multiUserFcmTokenManager.setTokenUnsentForAllSavedUsersBlocking() // force FCM to re-register
        }
        val extras = intent.extras

        // Set the padding to match the Status Bar height
        if (savedInstanceState != null) {
            val locationInt = savedInstanceState.getInt(STATE_MAILBOX_LOCATION)
            mailboxLabelId = savedInstanceState.getString(STATE_MAILBOX_LABEL_LOCATION)
            mailboxLabelName = savedInstanceState.getString(STATE_MAILBOX_LABEL_LOCATION_NAME)
            setMailboxLocation(fromInt(locationInt))
        }
        if (extras != null && extras.containsKey(EXTRA_MAILBOX_LOCATION)) {
            switchToMailboxLocation(extras.getInt(EXTRA_MAILBOX_LOCATION))
        }

        startObserving()
        startObservingPendingActions()
        startObservingUsedSpace()

        mailboxViewModel.toastMessageMaxLabelsReached.observe(this) { event: Event<MaxLabelsReached?> ->
            val maxLabelsReached = event.getContentIfNotHandled()
            if (maxLabelsReached != null) {
                val message = getString(
                    R.string.max_labels_exceeded,
                    maxLabelsReached.subject,
                    maxLabelsReached.maxAllowedLabels
                )
                showToast(message, Toast.LENGTH_SHORT)
            }
        }

        mailboxViewModel.hasConnectivity.observe(this, ::onConnectivityEvent)

        var actionModeAux: ActionMode? = null
        mailboxAdapter = MailboxRecyclerViewAdapter(this) { selectionModeEvent ->
            when (selectionModeEvent) {
                SelectionModeEnum.STARTED -> {
                    actionModeAux = startActionMode(this@MailboxActivity)
                    mailboxActionsView.layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
                    mailboxActionsView.visibility = View.VISIBLE
                }
                SelectionModeEnum.ENDED -> {
                    val actionModeEnd = actionModeAux
                    if (actionModeEnd != null) {
                        actionModeEnd.finish()
                        actionModeAux = null
                    }
                    mailboxActionsView.visibility = View.GONE
                }
            }
        }

        mailboxViewModel.pendingSendsLiveData.observe(this, mailboxAdapter::setPendingForSendingList)
        mailboxViewModel.pendingUploadsLiveData.observe(this, mailboxAdapter::setPendingUploadsList)

        mailboxViewModel.hasSuccessfullyDeletedMessages.observe(this) { isSuccess ->
            Timber.v("Delete message status is success $isSuccess")
            if (!isSuccess) {
                showToast(R.string.message_deleted_error)
            }
        }

        checkUserAndFetchNews()

        setTitle()

        mailboxRecyclerView.adapter = mailboxAdapter
        mailboxRecyclerView.layoutManager = LinearLayoutManager(this)
        // Set the list divider
        val itemDecoration = DividerItemDecoration(mailboxRecyclerView.context, DividerItemDecoration.VERTICAL)
        itemDecoration.setDrawable(getDrawable(R.drawable.list_divider)!!)
        mailboxRecyclerView.addItemDecoration(itemDecoration)

        buildSwipeProcessor()
        initializeSwipeRefreshLayout(mailboxSwipeRefreshLayout)

        if (userManager.isFirstMailboxLoad) {
            userManager.firstMailboxLoadDone()
        }

        mailboxAdapter.setItemClick { mailboxUiItem: MailboxItemUiModel ->
            OnMessageClickTask(
                WeakReference(this@MailboxActivity),
                messageDetailsRepositoryFactory,
                mailboxUiItem.itemId,
                mailboxUiItem.subject,
                currentMailboxLocation,
                userManager.requireCurrentUserId()
            ).execute()
        }

        mailboxAdapter.setOnItemSelectionChangedListener {
            val checkedItems = mailboxAdapter.checkedMailboxItems.size
            actionMode?.title = "$checkedItems ${getString(R.string.selected)}"

            mailboxActionsView.setAction(
                BottomActionsView.ActionPosition.ACTION_FIRST,
                true,
                if (MessageUtils.areAllUnRead(
                        selectedMessages
                    )
                ) R.drawable.ic_proton_envelope_open_text else R.drawable.ic_proton_envelope_dot,
                if (MessageUtils.areAllUnRead(
                        selectedMessages
                    )
                ) getString(R.string.mark_as_read) else getString(R.string.mark_as_unread)
            )
        }

        checkRegistration()

        mailboxRecyclerView.addOnScrollListener(listScrollListener)

        fetchOrganizationData()

        with(mailboxViewModel) {

            mailboxState
                .onEach(::renderState)
                .launchIn(lifecycleScope)

            mailboxLocation
                .onEach(mailboxAdapter::setNewLocation)
                .launchIn(lifecycleScope)

            drawerLabels
                .onEach { sideDrawer.setFoldersAndLabelsSection(it) }
                .launchIn(lifecycleScope)

            unreadCounters
                .flowWithLifecycle(lifecycle, Lifecycle.State.STARTED)
                .onEach(sideDrawer::setUnreadCounters)
                .launchIn(lifecycleScope)

            exitSelectionModeSharedFlow
                .onEach { if (it) actionMode?.finish() }
                .launchIn(lifecycleScope)

        }

        setUpMailboxActionsView()

        lifecycleScope.launch {
            mailboxViewModel.getMailSettingsState().onEach {
                it.fold(
                    ifLeft = {},
                    ifRight = { result ->
                        when (result) {
                            is GetMailSettings.Result.Error -> {
                                showToast(result.message.toString(), Toast.LENGTH_LONG)
                            }
                            is GetMailSettings.Result.Success -> {
                                swipeController.setCurrentMailSetting(result.mailSettings)
                                ItemTouchHelper(swipeController).attachToRecyclerView(mailboxRecyclerView)
                                mailboxViewModel.refreshMessages()
                            }
                        }.exhaustive
                    }
                )
            }.launchIn(lifecycleScope)
        }

        mailboxViewModel.startRateAppFlow
            .onEach { startRateAppFlow(this) }
            .launchIn(lifecycleScope)
    }

    private fun startObserving() {
        val owner = this
        mailboxViewModel.run {
            usedSpaceActionEvent(FLOW_START_ACTIVITY)
            manageLimitReachedWarning.observe(owner, setupUpLimitReachedObserver)
            manageLimitApproachingWarning.observe(owner, setupUpLimitApproachingObserver)
            manageLimitBelowCritical.observe(owner, setupUpLimitBelowCriticalObserver)
            manageLimitReachedWarningOnTryCompose.observe(owner, setupUpLimitReachedTryComposeObserver)
        }
    }

    private fun startObservingPendingActions() {
        val owner = this
        mailboxViewModel.run {
            pendingSendsLiveData.removeObservers(owner)
            pendingUploadsLiveData.removeObservers(owner)
            pendingSendsLiveData.observe(owner) { mailboxAdapter.setPendingForSendingList(it) }
            pendingUploadsLiveData.observe(owner) { mailboxAdapter.setPendingUploadsList(it) }
        }
    }

    private fun startObservingUsedSpace() {
        mailboxViewModel.primaryUserId
            .flatMapLatest { primaryUserId ->
                val preferences = SecureSharedPreferences.getPrefsForUser(this, primaryUserId)
                preferences.observe<Long>(PREF_USED_SPACE)
            }
            .onEach { mailboxViewModel.usedSpaceActionEvent(FLOW_USED_SPACE_CHANGED) }
            .launchIn(lifecycleScope)
    }

    private val setupUpLimitReachedObserver = Observer { limitReached: Event<Boolean> ->
        if (limitReached.getContentIfNotHandled() == true) {
            if (storageLimitApproachingAlertDialog != null) {
                storageLimitApproachingAlertDialog!!.dismiss()
                storageLimitApproachingAlertDialog = null
            }
            if (userManager.canShowStorageLimitReached()) {

                showTwoButtonInfoDialog(
                    titleStringId = R.string.storage_limit_warning_title,
                    messageStringId = R.string.storage_limit_reached_text,
                    positiveStringId = R.string.ok,
                    negativeStringId = R.string.learn_more,
                    onNegativeButtonClicked = {
                        val browserIntent = Intent(
                            Intent.ACTION_VIEW,
                            Uri.parse(getString(R.string.limit_reached_learn_more))
                        )
                        startActivity(browserIntent)
                        userManager.setShowStorageLimitReached(false)
                    }
                ) {
                    userManager.setShowStorageLimitReached(false)
                }
            }
            userManager.setShowStorageLimitWarning(true)
            storageLimitAlert.apply {
                visibility = View.VISIBLE
                setIcon(getDrawable(R.drawable.ic_proton_inbox)!!)
                setText(getString(R.string.storage_limit_alert))
            }
        }
    }

    private fun showStorageLimitApproachingAlertDialog() {
        storageLimitApproachingAlertDialog = showTwoButtonInfoDialog(
            titleStringId = R.string.storage_limit_warning_title,
            messageStringId = R.string.storage_limit_approaching_text,
            negativeStringId = R.string.dont_remind_again,
            onNegativeButtonClicked = {
                userManager.setShowStorageLimitWarning(false)
                storageLimitApproachingAlertDialog = null
            }
        ) { storageLimitApproachingAlertDialog = null }
    }

    private val setupUpLimitApproachingObserver = { limitApproaching: Event<Boolean> ->

        if (limitApproaching.getContentIfNotHandled() == true) {
            if (userManager.canShowStorageLimitWarning()) {
                if (storageLimitApproachingAlertDialog == null || !storageLimitApproachingAlertDialog!!.isShowing) {
                    // This is the first time the dialog is going to be showed or
                    // the dialog is not showing and had previously been dismissed by clicking the positive
                    // or negative button or the dialog is not showing and had previously been dismissed on touch
                    // outside or by clicking the back button
                    showStorageLimitApproachingAlertDialog()
                }
            }
            userManager.setShowStorageLimitReached(true)
            storageLimitAlert.visibility = View.GONE
        }
    }

    private val setupUpLimitBelowCriticalObserver = { limitReached: Event<Boolean> ->
        if (limitReached.getContentIfNotHandled() == true) {
            userManager.setShowStorageLimitWarning(true)
            userManager.setShowStorageLimitReached(true)
            storageLimitAlert.visibility = View.GONE
        }
    }

    private val setupUpLimitReachedTryComposeObserver = Observer { limitReached: Event<Boolean> ->
        if (limitReached.getContentIfNotHandled() == true) {
            showTwoButtonInfoDialog(
                titleStringId = R.string.storage_limit_warning_title,
                messageStringId = R.string.storage_limit_reached_text,
                negativeStringId = R.string.learn_more,
                onNegativeButtonClicked = {
                    val browserIntent = Intent(
                        Intent.ACTION_VIEW,
                        Uri.parse(getString(R.string.limit_reached_learn_more))
                    )
                    startActivity(browserIntent)
                }
            )
        } else {
            startComposeLauncher.launch(StartCompose.Input())
        }
    }

    private val selectedMessages: List<SimpleMessage>
        get() = mailboxAdapter.checkedMailboxItems.map { SimpleMessage(it) }

    private var firstLogin: Boolean? = null

    override fun onPrimaryUserId(userId: UserId) {
        super.onPrimaryUserId(userId)

        mJobManager.start()
        pendingActionDao = PendingActionDatabase.getInstance(this, userId).getDao()

        checkRegistration()
        switchToMailboxLocation(DrawerOptionType.INBOX.drawerOptionTypeValue)

        // Set the elevation to 0 since after account switch the list is scrolled to the top
        setElevationOnToolbarAndStatusView(false)
    }

    private fun renderState(state: MailboxState) {
        Timber.v("New mailbox state: ${state.javaClass.canonicalName}")
        renderUnreadChipState(state.unreadChip)
        renderListState(state.list)
    }

    private fun renderUnreadChipState(state: UnreadChipState) {
        when (state) {
            UnreadChipState.Loading -> {}
            is UnreadChipState.Data -> {
                unreadMessagesStatusChip.bind(
                    model = state.model,
                    onEnableFilter = mailboxViewModel::enableUnreadFilter,
                    onDisableFilter = mailboxViewModel::disableUnreadFilter
                )
            }
        }
    }

    private fun renderListState(state: MailboxListState) {
        setLoadingMore(false)

        when (state) {
            is MailboxListState.Loading -> setRefreshing(true)
            is MailboxListState.DataRefresh -> {
                lastFetchedMailboxItemsIds = state.lastFetchedItemsIds
                setRefreshing(false)
                include_mailbox_no_messages.isVisible =
                    state.lastFetchedItemsIds.isEmpty() && mailboxAdapter.itemCount == 0
            }
            is MailboxListState.Data -> {
                Timber.v("Data state items count: ${state.items.size}")
                include_mailbox_error.isVisible = false
                include_mailbox_no_messages.isVisible = state.isFreshData && state.items.isEmpty()
                mailboxRecyclerView.isVisible != state.items.isEmpty()

                mailboxAdapter.submitList(state.items) {
                    if (state.shouldResetPosition) mailboxRecyclerView.scrollToPosition(0)
                }
            }
            is MailboxListState.Error -> {
                setRefreshing(false)
                Timber.e(state.throwable, "Mailbox error ${state.error}")
                include_mailbox_no_messages.isVisible = false

                if (mailboxAdapter.itemCount > 0) {
                    include_mailbox_error.isVisible = false
                    if (state.isOffline.not()) showToast(R.string.inbox_could_not_retrieve_messages)
                } else {
                    include_mailbox_error.isVisible = true
                }
            }
        }
    }

    private val listScrollListener: RecyclerView.OnScrollListener = object : RecyclerView.OnScrollListener() {
        override fun onScrollStateChanged(view: RecyclerView, scrollState: Int) {
            scrollStateChanged =
                scrollState == RecyclerView.SCROLL_STATE_DRAGGING || scrollState == RecyclerView.SCROLL_STATE_SETTLING
        }

        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            if (!scrollStateChanged) {
                return
            }
            val layoutManager = recyclerView.layoutManager as LinearLayoutManager

            val firstCompletelyVisibleItemPosition = layoutManager.findFirstCompletelyVisibleItemPosition()

            // Load more when showing last fetched messages or at the end of the list
            if (dy > 0 && isLoadingMore.get().not()) {
                val lastCompletelyVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition()
                val isAtBottomOfTheList = lastCompletelyVisibleItemPosition == mailboxAdapter.itemCount - 1
                fun inAnyLastFetchedMailboxItemVisible() =
                    mailboxAdapter.isAnyMailboxItemWithinPositions(
                        mailboxItemsIds = lastFetchedMailboxItemsIds,
                        startPosition = firstCompletelyVisibleItemPosition,
                        endPosition = lastCompletelyVisibleItemPosition
                    )
                if (isAtBottomOfTheList || inAnyLastFetchedMailboxItemVisible()) {
                    setLoadingMore(true)
                    mailboxViewModel.loadMore()
                    lastFetchedMailboxItemsIds = emptyList()
                }
            }

            // Increase the elevation if the list is scrolled down and decrease if it is scrolled to the top
            if (firstCompletelyVisibleItemPosition == 0) {
                setElevationOnToolbarAndStatusView(false)
            } else {
                setElevationOnToolbarAndStatusView(true)
            }
        }
    }

    private fun loadMailboxItems() {
        mailboxViewModel.loadMore()
    }

    private fun setElevationOnToolbarAndStatusView(shouldIncreaseElevation: Boolean) {
        val elevation = if (shouldIncreaseElevation) {
            resources.getDimensionPixelSize(R.dimen.action_bar_elevation)
        } else {
            resources.getDimensionPixelSize(R.dimen.action_bar_no_elevation)
        }.toFloat()
        supportActionBar?.elevation = elevation
        mailboxStatusLayout.elevation = elevation
    }

    private fun registerFcmReceiver() {
        val filter = IntentFilter(getString(R.string.action_notification))
        filter.priority = 2
        LocalBroadcastManager.getInstance(this).registerReceiver(fcmBroadcastReceiver, filter)
    }

    private fun onConnectivityCheckRetry() {
        mConnectivitySnackLayout?.let {
            networkSnackBarUtil.getCheckingConnectionSnackBar(it, mailboxActionsView.id).show()
        }
        syncUUID = UUID.randomUUID().toString()
        lifecycleScope.launch {
            delay(3.toDuration(DurationUnit.SECONDS))
            mailboxViewModel.loadMore()
        }
        mailboxViewModel.checkConnectivityDelayed()
    }

    override fun onDohFailed() {
        super.onDohFailed()
        lifecycleScope.launch(Dispatchers.Main) {
            setAsOffline(Constants.ConnectionState.CANT_REACH_SERVER)
        }
    }

    private fun checkRegistration() {
        // Check device for Play Services APK.
        lifecycleScope.launchWhenCreated {
            if (checkPlayServices()) {
                val tokenSent = multiUserFcmTokenManager.isTokenSentForAllLoggedUsers()
                if (!tokenSent) {
                    runCatching {
                        FirebaseInstanceId.getInstance().instanceId.addOnCompleteListener { task ->
                            if (task.isSuccessful && task.result != null) {
                                multiUserFcmTokenManager.saveTokenBlocking(FirebaseToken(task.result!!.token))
                                registerDeviceWorkerEnqueuer()
                            } else {
                                Timber.d(task.exception, "Could not retrieve FirebaseInstanceId")
                            }
                        }
                    }.onFailure {
                        showToast(R.string.invalid_firebase_api_key_message)
                        Timber.d(it, "Invalid Firebase API key. Push notifications will not work.")
                    }
                }
            }
        }
    }

    private fun checkUserAndFetchNews(): Boolean {
        syncUUID = UUID.randomUUID().toString()

        if (firstLogin == null) {
            firstLogin = intent.getBooleanExtra(EXTRA_FIRST_LOGIN, false)
        }
        return if (!firstLogin!!) {
            val alarmReceiver = AlarmReceiver()
            alarmReceiver.setAlarm(this, true)
            false
        } else {
            firstLogin = false
            refreshMailboxJobRunning = true
            app.updateDone()
            // TODO: remove?
            loadMailboxItems()
            true
        }
    }

    override fun onNewIntent(intent: Intent) {
        super.onNewIntent(intent)

        checkRegistration()
        checkUserAndFetchNews()
        switchToMailboxLocation(DrawerOptionType.INBOX.drawerOptionTypeValue)
    }

    override fun onResume() {
        super.onResume()
        if (userManager.currentUserId != null &&
            !defaultSharedPreferences.getBoolean(PREF_NEW_USER_ONBOARDING_SHOWN, false)
        ) {
            startOnboardingActivityLauncher.launch(Unit)
        }

        registerFcmReceiver()
        checkDelinquency()
        mailboxViewModel.checkConnectivity()
        val mailboxLocation = mailboxViewModel.mailboxLocation.value
        if (mailboxLocation == MessageLocationType.INBOX) {
            userManager.currentUserId?.let { mailboxViewModel.clearNotifications(it) }
        }

        mailboxViewModel.startRateAppFlowIfNeeded()
    }

    override fun onPause() {
        runCatching {
            LocalBroadcastManager.getInstance(this).unregisterReceiver(fcmBroadcastReceiver)
        }
        networkSnackBarUtil.hideAllSnackBars()
        super.onPause()
    }

    public override fun onSaveInstanceState(outState: Bundle) {
        outState.putInt(
            STATE_MAILBOX_LOCATION,
            mailboxViewModel.mailboxLocation.value.messageLocationTypeValue
        )
        outState.putString(STATE_MAILBOX_LABEL_LOCATION, mailboxLabelId)
        outState.putString(STATE_MAILBOX_LABEL_LOCATION_NAME, mailboxLabelName)
        super.onSaveInstanceState(outState)
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_mailbox_options, menu)
        val mailboxLocation = mailboxViewModel.mailboxLocation.value
        menu.findItem(R.id.empty).isVisible =
            mailboxLocation in listOf(MessageLocationType.DRAFT, MessageLocationType.SPAM, MessageLocationType.TRASH)
        return true
    }

    override fun onPrepareOptionsMenu(menu: Menu): Boolean {
        menu.clear()
        menuInflater.inflate(R.menu.menu_mailbox_options, menu)
        val mailboxLocation = mailboxViewModel.mailboxLocation.value
        menu.findItem(R.id.empty).isVisible =
            mailboxLocation in listOf(
            MessageLocationType.DRAFT,
            MessageLocationType.ALL_DRAFT,
            MessageLocationType.SPAM,
            MessageLocationType.TRASH,
            MessageLocationType.LABEL,
            MessageLocationType.LABEL_FOLDER
        )
        return super.onPrepareOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(menuItem: MenuItem): Boolean =
        onOptionsItemSelected(menuItem.itemId) || super.onOptionsItemSelected(menuItem)

    private fun onOptionsItemSelected(@IdRes itemId: Int): Boolean {
        when (itemId) {
            R.id.compose -> mailboxViewModel.usedSpaceActionEvent(FLOW_TRY_COMPOSE)
            R.id.search -> startSearchLauncher.launch(Unit)
            R.id.empty -> showTwoButtonInfoDialog(
                titleStringId = R.string.empty_folder,
                messageStringId = R.string.are_you_sure_empty,
                negativeStringId = R.string.no
            ) {
                mailboxViewModel.emptyFolderAction(
                    userManager.requireCurrentUserId(),
                    LabelId(currentLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString())
                )
                setLoadingMore(false)
            }
            else -> return false
        }
        return true
    }

    override fun onBackPressed() {
        val drawerClosed = closeDrawer()
        if (!drawerClosed && mailboxViewModel.mailboxLocation.value != MessageLocationType.INBOX) {
            switchToMailboxLocation(DrawerOptionType.INBOX.drawerOptionTypeValue)
        } else if (!drawerClosed) {
            moveTaskToBack(true)
        }
    }

    private fun initializeSwipeRefreshLayout(swipeRefreshLayoutAux: SwipeRefreshLayout) {
        swipeRefreshLayoutAux.setColorSchemeResources(getColorIdFromAttr(R.attr.brand_norm))
        swipeRefreshLayoutAux.setOnRefreshListener(this)
    }

    private fun setRefreshing(shouldRefresh: Boolean) {
        Timber.v("setRefreshing shouldRefresh: $shouldRefresh")
        updatedStatusTextView.setText(
            when {
                isOnline.not() -> R.string.you_are_offline
                shouldRefresh -> R.string.mailbox_updating
                else -> R.string.mailbox_updated_recently
            }
        )
        mailboxSwipeRefreshLayout.isRefreshing = shouldRefresh
    }

    private fun setLoadingMore(loadingMore: Boolean): Boolean =
        isLoadingMore.getAndSet(loadingMore)

    override fun onInbox(type: DrawerOptionType) {
        mailboxViewModel.clearNotifications(userManager.requireCurrentUserId())
        switchToMailboxLocation(type.drawerOptionTypeValue)
    }

    override fun onOtherMailBox(type: DrawerOptionType) {
        switchToMailboxLocation(type.drawerOptionTypeValue)
    }

    public override fun onLabelMailBox(
        type: DrawerOptionType,
        labelId: String,
        labelName: String,
        isFolder: Boolean
    ) {
        switchToMailboxCustomLocation(type.drawerOptionTypeValue, labelId, labelName, isFolder)
    }

    override val currentMailboxLocation: MessageLocationType
        get() = mailboxViewModel.mailboxLocation.value

    private fun setTitle() {
        val titleRes: Int = when (mailboxViewModel.mailboxLocation.value) {
            MessageLocationType.INBOX -> R.string.inbox_option
            MessageLocationType.STARRED -> R.string.starred_option
            MessageLocationType.DRAFT, MessageLocationType.ALL_DRAFT -> R.string.drafts_option
            MessageLocationType.SENT, MessageLocationType.ALL_SENT -> R.string.sent_option
            MessageLocationType.ARCHIVE -> R.string.archive_option
            MessageLocationType.TRASH -> R.string.trash_option
            MessageLocationType.SPAM -> R.string.spam_option
            MessageLocationType.ALL_MAIL -> R.string.allmail_option
            MessageLocationType.ALL_SCHEDULED -> R.string.drawer_scheduled
            else -> R.string.app_name
        }
        supportActionBar?.setTitle(titleRes)
    }

    private fun setAsOffline(connectivity: Constants.ConnectionState) {
        isOnline = false
        showNoConnSnackAndScheduleRetry(connectivity)
        updatedStatusTextView.setText(R.string.you_are_offline)
    }

    private fun setAsOnline() {
        isOnline = true
        hideNoConnSnack()
    }

    private fun showNoConnSnackAndScheduleRetry(connectivity: Constants.ConnectionState) {
        Timber.v("show NoConnection Snackbar ${mConnectivitySnackLayout != null}")
        mConnectivitySnackLayout?.let { snackBarLayout ->
            lifecycleScope.launchWhenCreated {

                networkSnackBarUtil.getNoConnectionSnackBar(
                    parentView = snackBarLayout,
                    user = userManager.currentLegacyUser,
                    netConfiguratorCallback = this@MailboxActivity,
                    onRetryClick = ::onConnectivityCheckRetry,
                    isOffline = connectivity == Constants.ConnectionState.NO_INTERNET,
                    anchorViewId = mailboxActionsView.id
                ).show()
            }
        }
    }

    private fun hideNoConnSnack() {
        Timber.v("hideNoConnSnack")
        networkSnackBarUtil.hideCheckingConnectionSnackBar()
        networkSnackBarUtil.hideNoConnectionSnackBar()
    }

    @Subscribe
    fun onSettingsChangedEvent(event: SettingsChangedEvent) {
        if (!event.success) {
            showToast(R.string.saving_failed_no_conn, Toast.LENGTH_LONG, Gravity.CENTER)
        }
    }

    @Subscribe
    fun onMailboxLoaded(event: MailboxLoadedEvent?) {
        Timber.v("Mailbox loaded status ${event?.status}")
        if (event == null || event.uuid != null && event.uuid != syncUUID) {
            return
        }
        refreshMailboxJobRunning = false
        setLoadingMore(false)
        setRefreshing(false)
        if (!isDohOngoing) {
            showToast(event.status)
        }
        mNetworkResults.setMailboxLoaded(MailboxLoadedEvent(Status.SUCCESS, null))
    }

    private fun onConnectivityEvent(connectivity: Constants.ConnectionState) {
        Timber.v("onConnectivityEvent hasConnection: ${connectivity.name}")
        if (!isDohOngoing) {
            Timber.d("DoH NOT ongoing showing UI")
            if (connectivity != Constants.ConnectionState.CONNECTED) {
                setAsOffline(connectivity)
            } else {
                setAsOnline()
            }
        } else {
            Timber.d("DoH ongoing, not showing UI")
        }
    }

    @Subscribe
    fun onMailboxNoMessages(event: MailboxNoMessagesEvent?) {
        // show toast only if user initiated load more
        if (isLoadingMore.get()) {
            showToast(R.string.no_more_messages, Toast.LENGTH_SHORT)
            mailboxAdapter.notifyDataSetChanged()
        }
        setLoadingMore(false)
    }

    private fun showToast(status: Status) {
        when (status) {
            Status.UNAUTHORIZED -> setAsOffline(Constants.ConnectionState.CANT_REACH_SERVER)
            Status.NO_NETWORK -> setAsOffline(Constants.ConnectionState.NO_INTERNET)
            Status.SUCCESS -> setAsOnline()
            else -> return
        }
    }

    @Subscribe
    fun onLabelsLoadedEvent(event: FetchLabelsEvent) {
        if (event.status == Status.SUCCESS) {
            mailboxAdapter.notifyDataSetChanged()
        }
    }

    // region Action mode
    override fun onCreateActionMode(mode: ActionMode, menu: Menu): Boolean {
        actionMode = mode
        mailboxSwipeRefreshLayout.isEnabled = false
        return true
    }

    override fun onPrepareActionMode(mode: ActionMode, menu: Menu) = true

    override fun onActionModeStarted(mode: ActionMode?) {
        super.onActionModeStarted(mode)
        // We need to set a solid color for status bar during Action mode, or it will be black
        window.statusBarColor = getColor(R.color.background_norm)
    }

    override fun onActionItemClicked(mode: ActionMode, menuItem: MenuItem) = true

    override fun onDestroyActionMode(mode: ActionMode) {
        actionMode = null
        mailboxActionsView.visibility = View.GONE
        mailboxSwipeRefreshLayout.isEnabled = true
        mailboxAdapter.endSelectionMode()
    }
    // endregion

    private fun setUpMailboxActionsView() {
        val actionsUiModel = BottomActionsView.UiModel(
            R.drawable.ic_proton_envelope_dot,
            if (currentMailboxLocation in arrayOf(
                    MessageLocationType.DRAFT,
                    MessageLocationType.ALL_DRAFT,
                    MessageLocationType.SENT,
                    MessageLocationType.ALL_SENT,
                    MessageLocationType.TRASH,
                    MessageLocationType.SPAM
                )
            ) R.drawable.ic_proton_trash_cross else R.drawable.ic_proton_trash,
            R.drawable.ic_proton_folder_arrow_in,
            R.drawable.ic_proton_tag
        )
        mailboxActionsView.bind(actionsUiModel)


        mailboxActionsView.setAction(
            BottomActionsView.ActionPosition.ACTION_SECOND, true,
            if (currentMailboxLocation in arrayOf(
                    MessageLocationType.DRAFT,
                    MessageLocationType.ALL_DRAFT,
                    MessageLocationType.SENT,
                    MessageLocationType.ALL_SENT,
                    MessageLocationType.TRASH,
                    MessageLocationType.SPAM
                )
            ) R.drawable.ic_proton_trash_cross else R.drawable.ic_proton_trash,
            if (currentMailboxLocation in arrayOf(
                    MessageLocationType.DRAFT,
                    MessageLocationType.ALL_DRAFT,
                    MessageLocationType.SENT,
                    MessageLocationType.ALL_SENT,
                    MessageLocationType.TRASH,
                    MessageLocationType.SPAM
                )
            ) getString(R.string.delete) else getString(
                R.string.trash
            )
        )
        mailboxActionsView.setAction(
            BottomActionsView.ActionPosition.ACTION_THIRD, true,
            R.drawable.ic_proton_folder_arrow_in,
            getString(R.string.move_to)
        )

        mailboxActionsView.setOnFirstActionClickListener {
            val messageIds = getSelectedMessageIds()
            if (MessageUtils.areAllUnRead(selectedMessages)) {
                mailboxViewModel.markRead(
                    messageIds,
                    UserId(userManager.requireCurrentUserId().id),
                    currentMailboxLocation,
                    mailboxLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString()
                )
            } else {
                mailboxViewModel.markUnRead(
                    messageIds,
                    UserId(userManager.requireCurrentUserId().id),
                    currentMailboxLocation,
                    mailboxLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString()
                )
            }
        }
        mailboxActionsView.setOnSecondActionClickListener {
            val messageIds = getSelectedMessageIds()
            if (currentMailboxLocation in arrayOf(
                    MessageLocationType.DRAFT,
                    MessageLocationType.ALL_DRAFT,
                    MessageLocationType.SENT,
                    MessageLocationType.ALL_SENT,
                    MessageLocationType.TRASH,
                    MessageLocationType.SPAM
                )
            ) {
                showDeleteConfirmationDialog(
                    this,
                    getString(R.string.delete_messages),
                    getString(R.string.confirm_destructive_action)
                ) {
                    mailboxViewModel.deleteAction(
                        messageIds,
                        UserId(userManager.requireCurrentUserId().id),
                        currentMailboxLocation
                    )
                    actionMode?.finish()
                }
            } else {
                if (isScheduledMessageSelected()) {
                    showTwoButtonInfoDialog(
                        titleStringId = R.string.scheduled_message_moved_to_trash_title,
                        messageStringId = R.string.scheduled_message_moved_to_trash_desc,
                        negativeStringId = R.string.cancel,
                        onPositiveButtonClicked = {
                            moveMessagesToTrashFolder(messageIds)
                        }
                    )
                } else {
                    moveMessagesToTrashFolder(messageIds)
                }
            }
        }
        mailboxActionsView.setOnThirdActionClickListener {
            showFoldersManager(getSelectedMessageIds())
        }
        mailboxActionsView.setOnFourthActionClickListener {
            showLabelsManager(getSelectedMessageIds())
        }
        mailboxActionsView.setOnMoreActionClickListener {
            lifecycleScope.launch {
                showActionSheet(getSelectedMessageIds(), isConversationModeEnabled(currentMailboxLocation))
            }
        }
    }

    private fun getSelectedMessageIds(): List<String> = selectedMessages.map { it.messageId }
    private fun isScheduledMessageSelected(): Boolean = selectedMessages.any { it.isScheduled }

    private fun showActionSheet(
        messagesIds: List<String>,
        isConversationsModeOn: Boolean
    ) {
        val messagesStringRes = if (isConversationsModeOn) {
            R.plurals.x_conversations_count
        } else {
            R.plurals.x_messages_count
        }

        MessageActionSheet.newInstance(
            actionSheetTarget = ActionSheetTarget.MAILBOX_ITEMS_IN_MAILBOX_SCREEN,
            messagesIds = messagesIds,
            currentFolderLocationId = currentMailboxLocation.messageLocationTypeValue,
            mailboxLabelId = mailboxLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString(),
            title = resources.getQuantityString(
                messagesStringRes,
                messagesIds.size,
                messagesIds.size
            ),
            isScheduled = isScheduledMessageSelected()
        )
            .show(supportFragmentManager, MessageActionSheet::class.qualifiedName)
    }

    private fun showFoldersManager(messageIds: List<String>) {
        LabelsActionSheet.newInstance(
            messageIds = messageIds,
            currentFolderLocation = currentMailboxLocation.messageLocationTypeValue,
            currentLocationId = mailboxLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString(),
            labelType = LabelType.FOLDER,
            actionSheetTarget = ActionSheetTarget.MAILBOX_ITEMS_IN_MAILBOX_SCREEN
        )
            .show(supportFragmentManager, LabelsActionSheet::class.qualifiedName)
    }

    private fun showLabelsManager(messageIds: List<String>) {
        LabelsActionSheet.newInstance(
            messageIds = messageIds,
            currentFolderLocation = currentMailboxLocation.messageLocationTypeValue,
            currentLocationId = mailboxLabelId ?: currentMailboxLocation.messageLocationTypeValue.toString(),
            actionSheetTarget = ActionSheetTarget.MAILBOX_ITEMS_IN_MAILBOX_SCREEN
        )
            .show(supportFragmentManager, LabelsActionSheet::class.qualifiedName)
    }

    private fun moveMessagesToTrashFolder(messageIds: List<String>) {
        undoSnack = showUndoSnackbar(
            this@MailboxActivity,
            findViewById(R.id.drawer_layout),
            resources.getQuantityString(R.plurals.action_move_to_trash, messageIds.size),
            { },
            false
        ).apply { anchorView = mailboxActionsView }
        undoSnack!!.show()
        mailboxViewModel.moveToFolder(
            messageIds,
            UserId(userManager.requireCurrentUserId().id),
            currentMailboxLocation,
            MessageLocationType.TRASH.messageLocationTypeValue.toString()
        )

        if (currentMailboxLocation != MessageLocationType.ALL_MAIL) actionMode?.finish()
    }

    /* SwipeRefreshLayout.OnRefreshListener */
    override fun onRefresh() {
        mailboxViewModel.checkConnectivity()
        syncUUID = UUID.randomUUID().toString()
        mailboxViewModel.refreshMessages()
    }

    private fun switchToMailboxLocation(newLocation: Int) {
        val newMessageLocationType = fromInt(newLocation)
        setElevationOnToolbarAndStatusView(false)
        LoaderManager.getInstance(this).destroyLoader(LOADER_ID_LABELS_OFFLINE)
        if (actionMode != null) {
            actionMode!!.finish()
        }
        mailboxLabelId = null
        invalidateOptionsMenu()
        syncUUID = UUID.randomUUID().toString()
        setMailboxLocation(newMessageLocationType)
        setTitle()
        closeDrawer(animate = false)
        mailboxRecyclerView.clearFocus()
        mailboxRecyclerView.scrollToPosition(0)
        setUpMailboxActionsView()
        include_mailbox_no_messages.apply {
            isVisible = false
            bind(EmptyMailboxUiModel.fromLocation(newMessageLocationType))
        }
    }

    private fun switchToMailboxCustomLocation(
        newLocation: Int,
        labelId: String,
        labelName: String?,
        isFolder: Boolean
    ) {
        val newMessageLocationType = fromInt(newLocation)
        SetUpNewMessageLocationTask(
            WeakReference(this),
            messageDetailsRepositoryFactory,
            labelId,
            isFolder,
            newLocation,
            labelName,
            userManager.requireCurrentUserId()
        ).execute()
        include_mailbox_no_messages.apply {
            isVisible = false
            bind(EmptyMailboxUiModel.fromLocation(newMessageLocationType))
        }
    }

    private var undoSnack: Snackbar? = null
    private fun buildSwipeProcessor() {
        mSwipeProcessor.apply {
            addHandler(SwipeAction.TRASH, TrashSwipeHandler())
            addHandler(SwipeAction.SPAM, SpamSwipeHandler())
            addHandler(SwipeAction.UPDATE_STAR, StarSwipeHandler())
            addHandler(SwipeAction.ARCHIVE, ArchiveSwipeHandler())
            addHandler(SwipeAction.MARK_READ, MarkReadSwipeHandler())
        }
    }

    private fun checkPlayServices(): Boolean {
        val googleAPI = GoogleApiAvailability.getInstance()
        val result = googleAPI.isGooglePlayServicesAvailable(this)
        if (result != ConnectionResult.SUCCESS) {
            if (googleAPI.isUserResolvableError(result)) {
                val setOfConnectionResults =
                    setOf(
                        ConnectionResult.SERVICE_MISSING,
                        ConnectionResult.SERVICE_INVALID,
                        ConnectionResult.SERVICE_DISABLED,
                        ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED
                    )
                if (setOfConnectionResults.any { it == result }) {
                    val dontShowPlayServices = defaultSharedPreferences[PREF_DONT_SHOW_PLAY_SERVICES] ?: false
                    if (!dontShowPlayServices) {
                        showTwoButtonInfoDialog(
                            titleStringId = R.string.push_notifications_alert_title,
                            messageStringId = R.string.push_notifications_alert_subtitle,
                            negativeStringId = R.string.dont_remind_again,
                            onNegativeButtonClicked = { defaultSharedPreferences[PREF_DONT_SHOW_PLAY_SERVICES] = true }
                        )
                    }
                } else {
                    googleAPI.getErrorDialog(
                        this,
                        result,
                        PLAY_SERVICES_RESOLUTION_REQUEST
                    ) {
                        showToast("cancel", Toast.LENGTH_SHORT)
                    }?.show()
                }
            } else {
                Timber.d("%s: This device is not GCM supported.", TAG_MAILBOX_ACTIVITY)
            }
            return false
        }
        return true
    }

    private fun setMailboxLocation(locationToSet: MessageLocationType) {
        mailboxViewModel.disableUnreadFilter()
        mailboxViewModel.setNewMailboxLocation(locationToSet)
    }

    private fun setNewLabel(labelId: String) {
        mailboxViewModel.disableUnreadFilter()
        mailboxViewModel.setNewMailboxLabel(labelId)
    }

    private val fcmBroadcastReceiver: BroadcastReceiver = FcmBroadcastReceiver()

    private class OnMessageClickTask internal constructor(
        private val mailboxActivity: WeakReference<MailboxActivity>,
        messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
        private val messageId: String,
        private val messageSubject: String,
        private val currentMailboxLocationType: MessageLocationType,
        userId: UserId
    ) : AsyncTask<Unit, Unit, Message>() {

        private val messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)

        override fun doInBackground(vararg params: Unit): Message? =
            messageDetailsRepository.findMessageByIdBlocking(messageId)

        public override fun onPostExecute(savedMessage: Message?) {
            val mailboxActivity = mailboxActivity.get()
            val messageLocation = savedMessage?.locationFromLabel()
            if (messageLocation == MessageLocationType.DRAFT || messageLocation == MessageLocationType.ALL_DRAFT) {
                TryToOpenMessageTask(
                    this.mailboxActivity,
                    mailboxActivity?.pendingActionDao,
                    savedMessage.messageId,
                    savedMessage.isInline,
                    savedMessage.addressID
                ).execute()
            } else {
                mailboxActivity?.startMessageDetailsLauncher?.launch(
                    MessageDetailsActivity.Input(
                        messageId = messageId,
                        locationType = currentMailboxLocationType,
                        labelId = mailboxActivity.mailboxLabelId?.let(::LabelId),
                        messageSubject = messageSubject
                    )
                )
            }
        }
    }

    private class TryToOpenMessageTask internal constructor(
        private val mailboxActivity: WeakReference<MailboxActivity>,
        private val pendingActionDao: PendingActionDao?,
        private val messageId: String?,
        private val isInline: Boolean,
        private val addressId: String?
    ) : AsyncTask<Unit, Unit, Boolean>() {

        override fun doInBackground(vararg params: Unit): Boolean {
            // return true if message is not in sending process and can be opened
            val pendingForSending = pendingActionDao?.findPendingSendByMessageIdBlocking(messageId!!)
            return pendingForSending == null ||
                pendingForSending.sent != null &&
                !pendingForSending.sent!!
        }

        override fun onPostExecute(openMessage: Boolean) {
            val mailboxActivity = mailboxActivity.get()
            if (!openMessage) {
                mailboxActivity?.showToast(R.string.cannot_open_message_while_being_sent, Toast.LENGTH_SHORT)
                return
            }
            mailboxActivity?.startComposeLauncher?.launch(
                StartCompose.Input(
                    messageId = messageId,
                    isInline = isInline,
                    addressId = addressId
                )
            )
        }
    }

    private class SetUpNewMessageLocationTask internal constructor(
        private val mailboxActivity: WeakReference<MailboxActivity>,
        private val messageDetailsRepositoryFactory: MessageDetailsRepository.AssistedFactory,
        private val labelId: String,
        private val isFolder: Boolean,
        private val newLocation: Int,
        private val labelName: String?,
        private val userId: UserId
    ) : AsyncTask<Unit, Unit, Label?>() {

        override fun doInBackground(vararg params: Unit): Label? {
            return runBlocking {
                val messageDetailsRepository = messageDetailsRepositoryFactory.create(userId)
                val labels = messageDetailsRepository.findLabelsWithIds(listOf(labelId))
                if (labels.isEmpty()) null else labels[0]
            }
        }

        override fun onPostExecute(label: Label?) {
            val mailboxActivity = mailboxActivity.get() ?: return
            mailboxActivity.setElevationOnToolbarAndStatusView(false)
            if (mailboxActivity.actionMode != null) {
                mailboxActivity.actionMode!!.finish()
            }
            mailboxActivity.invalidateOptionsMenu()
            val locationToSet: MessageLocationType = if (isFolder) {
                MessageLocationType.LABEL_FOLDER
            } else {
                fromInt(newLocation)
            }
            mailboxActivity.mailboxLabelId = labelId
            mailboxActivity.mailboxLabelName = labelName
            mailboxActivity.setMailboxLocation(locationToSet)
            if (label != null) {
                val actionBar = mailboxActivity.supportActionBar
                if (actionBar != null) {
                    actionBar.title = label.name
                }
            }
            mailboxActivity.closeDrawer()
            mailboxActivity.mailboxRecyclerView.scrollToPosition(0)
        }
    }

    private inner class FcmBroadcastReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.extras != null
            ) {
                syncUUID = UUID.randomUUID().toString()
                checkUserAndFetchNews()
                mailboxAdapter.notifyDataSetChanged()
            }
        }
    }

    private inner class SwipeController : ItemTouchHelper.Callback() {

        private var mailSettings: MailSettings? = null

        @Deprecated("Subscribe for changes instead of reloading on current User/MailSettings changed.")
        fun setCurrentMailSetting(mailSettings: MailSettings) {
            this.mailSettings = mailSettings
        }

        override fun getMovementFlags(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder
        ): Int {
            if (viewHolder is MessageViewHolder) {
                val mailboxLocation = currentMailboxLocation
                return if (mailboxLocation == MessageLocationType.DRAFT ||
                    mailboxLocation == MessageLocationType.ALL_DRAFT
                ) {
                    makeMovementFlags(0, 0)
                } else {
                    makeMovementFlags(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT)
                }
            }
            return makeMovementFlags(0, 0)
        }

        override fun onMove(
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            target: RecyclerView.ViewHolder
        ): Boolean {
            throw UnsupportedOperationException("Not implemented")
        }

        fun normalise(swipeAction: SwipeAction, mailboxLocation: MessageLocationType?): SwipeAction {
            return if (mailboxLocation == MessageLocationType.DRAFT ||
                mailboxLocation == MessageLocationType.ALL_DRAFT && swipeAction != SwipeAction.UPDATE_STAR
            ) {
                SwipeAction.TRASH
            } else swipeAction
        }

        override fun isItemViewSwipeEnabled(): Boolean {
            return if (actionMode != null) {
                false
            } else {
                super.isItemViewSwipeEnabled()
            }
        }

        override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
            val position = viewHolder.adapterPosition
            val mailboxItem = mailboxAdapter.getMailboxItem(position)
            val messageSwiped = SimpleMessage(mailboxItem)
            val mailboxLocation = currentMailboxLocation
            val settings = mailSettings ?: return
            val swipeActionOrdinal: Int = when (direction) {
                ItemTouchHelper.RIGHT -> settings.swipeRight?.value ?: SwipeAction.TRASH.ordinal
                ItemTouchHelper.LEFT -> settings.swipeLeft?.value ?: SwipeAction.ARCHIVE.ordinal
                else -> throw IllegalArgumentException("Unrecognised direction: $direction")
            }
            val swipeAction = normalise(SwipeAction.values()[swipeActionOrdinal], currentMailboxLocation)
            val currentLocationId = mailboxLabelId ?: mailboxLocation.messageLocationTypeValue.toString()
            if (isConversationModeEnabled(mailboxLocation)) {
                mailboxViewModel.handleConversationSwipe(
                    swipeAction,
                    mailboxItem,
                    mailboxLocation,
                    currentLocationId
                )
            } else {
                if (messageSwiped.isScheduled && swipeAction == SwipeAction.TRASH) {
                    showTwoButtonInfoDialog(
                        titleStringId = R.string.scheduled_message_moved_to_trash_title,
                        messageStringId = R.string.scheduled_message_moved_to_trash_desc,
                        negativeStringId = R.string.cancel,
                        onPositiveButtonClicked = {
                            mSwipeProcessor.handleSwipe(swipeAction, messageSwiped, mJobManager, currentLocationId)
                        }
                    )
                } else
                    mSwipeProcessor.handleSwipe(swipeAction, messageSwiped, mJobManager, currentLocationId)
            }
            if (undoSnack != null && undoSnack!!.isShownOrQueued) {
                undoSnack!!.dismiss()
            }
            undoSnack = showUndoSnackbar(
                this@MailboxActivity,
                findViewById(R.id.drawer_layout),
                getString(swipeAction.actionDescription),
                {
                    mSwipeProcessor.handleUndo(
                        swipeAction, messageSwiped, mJobManager, mailboxLocation, currentLocationId
                    )
                    mailboxAdapter.notifyDataSetChanged()
                },
                false
            ).apply { anchorView = mailboxActionsView }
            val isDraftLocation =
                currentMailboxLocation in listOf(MessageLocationType.DRAFT, MessageLocationType.ALL_DRAFT)
            if (!(swipeAction == SwipeAction.TRASH && (isDraftLocation || messageSwiped.isScheduled))) {
                undoSnack!!.show()
            }
            if (swipeCustomizeSnack != null && !customizeSwipeSnackShown) {
                handler.postDelayed(2750) {
                    swipeCustomizeSnack!!.show()
                    customizeSwipeSnackShown = true
                }
            }
            mailboxAdapter.notifyDataSetChanged()
        }

        override fun onChildDraw(
            canvas: Canvas,
            recyclerView: RecyclerView,
            viewHolder: RecyclerView.ViewHolder,
            deltaX: Float,
            deltaY: Float,
            actionState: Int,
            isCurrentlyActive: Boolean
        ) {
            if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
                val itemView = viewHolder.itemView
                val height = itemView.bottom - itemView.top
                val width = itemView.right - itemView.left
                val layoutId: Int = when {
                    currentMailboxLocation in listOf(MessageLocationType.DRAFT, MessageLocationType.ALL_DRAFT) -> {
                        SwipeAction.TRASH.getActionBackgroundResource(deltaX < 0)
                    }
                    deltaX < 0 -> {
                        mailSettings?.swipeLeft?.let {
                            SwipeAction.values()[it.value].getActionBackgroundResource(false)
                        } ?: Resources.ID_NULL
                    }
                    else -> {
                        mailSettings?.swipeRight?.let {
                            SwipeAction.values()[it.value].getActionBackgroundResource(true)
                        } ?: Resources.ID_NULL
                    }
                }
                val view = layoutInflater.inflate(layoutId, null)
                val widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY)
                val heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
                view.measure(widthSpec, heightSpec)
                view.layout(0, 0, width, height)
                canvas.save()
                canvas.translate(itemView.left.toFloat(), itemView.top.toFloat())
                view.draw(canvas)
                canvas.restore()
            }
            super.onChildDraw(canvas, recyclerView, viewHolder, deltaX, deltaY, actionState, isCurrentlyActive)
        }

        override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
            super.onSelectedChanged(viewHolder, actionState)
            val isSwiping = actionState == ItemTouchHelper.ACTION_STATE_SWIPE
            mailboxSwipeRefreshLayout.isEnabled = isSwiping.not()
        }
    }

}
