/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.fenix.home.sessioncontrol

import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tab.collections.ext.invoke
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.service.nimbus.messaging.Message
import mozilla.telemetry.glean.private.NoExtras
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.HomeBookmarks
import org.mozilla.fenix.GleanMetrics.RecentTabs
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.accounts.FenixFxAEntryPoint
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.appstate.AppState
import org.mozilla.fenix.components.appstate.setup.checklist.ChecklistItem
import org.mozilla.fenix.components.usecases.FenixBrowserUseCases
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.home.HomeFragment
import org.mozilla.fenix.home.HomeFragmentDirections
import org.mozilla.fenix.messaging.MessageController
import org.mozilla.fenix.onboarding.WallpaperOnboardingDialogFragment.Companion.THUMBNAILS_SELECTION_COUNT
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.wallpapers.Wallpaper
import org.mozilla.fenix.wallpapers.WallpaperState
import java.lang.ref.WeakReference
import mozilla.components.feature.tab.collections.Tab as ComponentTab

/**
 * [HomeFragment] controller. An interface that handles the view manipulation of the Tabs triggered
 * by the Interactor.
 */
@Suppress("TooManyFunctions")
interface SessionControlController {
    /**
     * @see [CollectionInteractor.onCollectionAddTabTapped]
     */
    fun handleCollectionAddTabTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onCollectionOpenTabClicked]
     */
    fun handleCollectionOpenTabClicked(tab: ComponentTab)

    /**
     * @see [CollectionInteractor.onCollectionOpenTabsTapped]
     */
    fun handleCollectionOpenTabsTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onCollectionRemoveTab]
     */
    fun handleCollectionRemoveTab(collection: TabCollection, tab: ComponentTab)

    /**
     * @see [CollectionInteractor.onCollectionShareTabsClicked]
     */
    fun handleCollectionShareTabsClicked(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onDeleteCollectionTapped]
     */
    fun handleDeleteCollectionTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onRenameCollectionTapped]
     */
    fun handleRenameCollectionTapped(collection: TabCollection)

    /**
     * @see [CollectionInteractor.onToggleCollectionExpanded]
     */
    fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean)

    /**
     * @see [CollectionInteractor.onAddTabsToCollectionTapped]
     */
    fun handleCreateCollection()

    /**
     * @see [CollectionInteractor.onRemoveCollectionsPlaceholder]
     */
    fun handleRemoveCollectionsPlaceholder()

    /**
     * @see [MessageCardInteractor.onMessageClicked]
     */
    fun handleMessageClicked(message: Message)

    /**
     * @see [MessageCardInteractor.onMessageClosedClicked]
     */
    fun handleMessageClosed(message: Message)

    /**
     * @see [WallpaperInteractor.showWallpapersOnboardingDialog]
     */
    fun handleShowWallpapersOnboardingDialog(state: WallpaperState): Boolean

    /**
     * @see [SessionControlInteractor.reportSessionMetrics]
     */
    fun handleReportSessionMetrics(state: AppState)

    /**
     * @see [SetupChecklistInteractor.onChecklistItemClicked]
     */
    fun onChecklistItemClicked(item: ChecklistItem)

    /**
     * @see [SetupChecklistInteractor.onRemoveChecklistButtonClicked]
     */
    fun onRemoveChecklistButtonClicked()

    /**
     * Registers a [SessionControlControllerCallback] to handle callbacks that are implemented in the UI layer.
     */
    fun registerCallback(callback: SessionControlControllerCallback)

    /**
     * Unregisters the callback is typically called as part of cleaning up.
     */
    fun unregisterCallback()
}

/**
 * Interface for [SessionControlController] callbacks that are implemented in the UI.
 */
interface SessionControlControllerCallback {

    /**
     * Callback to register the [TabCollectionStorage.Observer].
     */
    fun registerCollectionStorageObserver()

    /**
     * Callback to remove collection.
     */
    fun removeCollection(tabCollection: TabCollection)

    /**
     * Callback to show tab tray.
     */
    fun showTabTray()
}

@Suppress("TooManyFunctions", "LargeClass", "LongParameterList")
class DefaultSessionControlController(
    private val activityRef: WeakReference<HomeActivity>,
    private val settings: Settings,
    private val engine: Engine,
    private val messageController: MessageController,
    private val store: BrowserStore,
    private val tabCollectionStorage: TabCollectionStorage,
    private val addTabUseCase: TabsUseCases.AddNewTabUseCase,
    private val restoreUseCase: TabsUseCases.RestoreUseCase,
    private val selectTabUseCase: TabsUseCases.SelectTabUseCase,
    private val reloadUrlUseCase: SessionUseCases.ReloadUrlUseCase,
    private val fenixBrowserUseCases: FenixBrowserUseCases,
    private val appStore: AppStore,
    private val navControllerRef: WeakReference<NavController>,
    private val viewLifecycleScope: CoroutineScope,
    private val showAddSearchWidgetPrompt: () -> Unit,
) : SessionControlController {

    private var callback: SessionControlControllerCallback? = null
    private val activity: HomeActivity
        get() = requireNotNull(activityRef.get())

    private val navController: NavController
        get() = requireNotNull(navControllerRef.get())

    override fun registerCallback(callback: SessionControlControllerCallback) {
        this.callback = callback
    }

    override fun unregisterCallback() {
        this.callback = null
    }

    override fun handleCollectionAddTabTapped(collection: TabCollection) {
        Collections.addTabButton.record(NoExtras())
        showCollectionCreationFragment(
            step = SaveCollectionStep.SelectTabs,
            selectedTabCollectionId = collection.id,
        )
    }

    override fun handleCollectionOpenTabClicked(tab: ComponentTab) {
        restoreUseCase.invoke(
            activity.filesDir,
            engine,
            tab,
            onTabRestored = {
                navController.navigate(R.id.browserFragment)
                selectTabUseCase.invoke(it)
                reloadUrlUseCase.invoke(it)
            },
            onFailure = {
                navController.navigate(R.id.browserFragment)
                fenixBrowserUseCases.loadUrlOrSearch(
                    searchTermOrURL = tab.url,
                    newTab = !settings.enableHomepageAsNewTab,
                    private = appStore.state.mode.isPrivate,
                )
            },
        )

        Collections.tabRestored.record(NoExtras())
    }

    override fun handleCollectionOpenTabsTapped(collection: TabCollection) {
        restoreUseCase.invoke(
            activity.filesDir,
            engine,
            collection,
            onFailure = { url ->
                addTabUseCase(url)
            },
        )

        callback?.showTabTray()
        Collections.allTabsRestored.record(NoExtras())
    }

    override fun handleCollectionRemoveTab(
        collection: TabCollection,
        tab: ComponentTab,
    ) {
        Collections.tabRemoved.record(NoExtras())

        // collection tabs hold a reference to the initial collection that could have changed since
        val updatedCollection =
            tabCollectionStorage.cachedTabCollections.firstOrNull {
                it.id == collection.id
            }

        if (updatedCollection?.tabs?.size == 1) {
            callback?.removeCollection(collection)
        } else {
            viewLifecycleScope.launch {
                tabCollectionStorage.removeTabFromCollection(collection, tab)
            }
        }
    }

    override fun handleCollectionShareTabsClicked(collection: TabCollection) {
        showShareFragment(
            collection.title,
            collection.tabs.map { ShareData(url = it.url, title = it.title) },
        )
        Collections.shared.record(NoExtras())
    }

    override fun handleDeleteCollectionTapped(collection: TabCollection) {
        callback?.removeCollection(collection)
        Collections.removed.record(NoExtras())
    }

    override fun handleRenameCollectionTapped(collection: TabCollection) {
        showCollectionCreationFragment(
            step = SaveCollectionStep.RenameCollection,
            selectedTabCollectionId = collection.id,
        )
        Collections.renameButton.record(NoExtras())
    }

    override fun handleShowWallpapersOnboardingDialog(state: WallpaperState): Boolean {
        return if (appStore.state.mode.isPrivate) {
            false
        } else {
            state.availableWallpapers.filter { wallpaper ->
                wallpaper.thumbnailFileState == Wallpaper.ImageFileState.Downloaded
            }.size.let { downloadedCount ->
                // We only display the dialog if enough thumbnails have been downloaded for it.
                downloadedCount >= THUMBNAILS_SELECTION_COUNT
            }.also { showOnboarding ->
                if (showOnboarding) {
                    navController.nav(
                        R.id.homeFragment,
                        HomeFragmentDirections.actionGlobalWallpaperOnboardingDialog(),
                    )
                }
            }
        }
    }
    override fun handleToggleCollectionExpanded(collection: TabCollection, expand: Boolean) {
        appStore.dispatch(AppAction.CollectionExpanded(collection, expand))
    }

    private fun showTabTrayCollectionCreation() {
        val directions = if (settings.tabManagerEnhancementsEnabled) {
            HomeFragmentDirections.actionGlobalTabManagementFragment(
                enterMultiselect = true,
            )
        } else {
            HomeFragmentDirections.actionGlobalTabsTrayFragment(
                enterMultiselect = true,
            )
        }
        navController.nav(R.id.homeFragment, directions)
    }

    private fun showCollectionCreationFragment(
        step: SaveCollectionStep,
        selectedTabIds: Array<String>? = null,
        selectedTabCollectionId: Long? = null,
    ) {
        if (navController.currentDestination?.id == R.id.collectionCreationFragment) return

        // Only register the observer right before moving to collection creation
        callback?.registerCollectionStorageObserver()

        val tabIds = store.state
            .getNormalOrPrivateTabs(private = activity.browsingModeManager.mode.isPrivate)
            .map { session -> session.id }
            .toList()
            .toTypedArray()
        val directions = HomeFragmentDirections.actionGlobalCollectionCreationFragment(
            tabIds = tabIds,
            saveCollectionStep = step,
            selectedTabIds = selectedTabIds,
            selectedTabCollectionId = selectedTabCollectionId ?: -1,
        )
        navController.nav(R.id.homeFragment, directions)
    }

    override fun handleCreateCollection() {
        showTabTrayCollectionCreation()
    }

    override fun handleRemoveCollectionsPlaceholder() {
        settings.showCollectionsPlaceholderOnHome = false
        Collections.placeholderCancel.record()
        appStore.dispatch(AppAction.RemoveCollectionsPlaceholder)
    }

    private fun showShareFragment(shareSubject: String, data: List<ShareData>) {
        val directions = HomeFragmentDirections.actionGlobalShareFragment(
            sessionId = store.state.selectedTabId,
            shareSubject = shareSubject,
            data = data.toTypedArray(),
        )
        navController.nav(R.id.homeFragment, directions)
    }

    override fun handleMessageClicked(message: Message) {
        messageController.onMessagePressed(message)
    }

    override fun handleMessageClosed(message: Message) {
        messageController.onMessageDismissed(message)
    }

    override fun handleReportSessionMetrics(state: AppState) {
        if (state.recentTabs.isEmpty()) {
            RecentTabs.sectionVisible.set(false)
        } else {
            RecentTabs.sectionVisible.set(true)
        }

        HomeBookmarks.bookmarksCount.set(state.bookmarks.size.toLong())
    }

    override fun onChecklistItemClicked(item: ChecklistItem) {
        if (item is ChecklistItem.Task) {
            // The navigation actions are required to be called on the main thread.
            navigationActionFor(item)
        }

        appStore.dispatch(AppAction.SetupChecklistAction.ChecklistItemClicked(item))
    }

    @VisibleForTesting
    internal fun navigationActionFor(item: ChecklistItem.Task) = when (item.type) {
        ChecklistItem.Task.Type.SET_AS_DEFAULT -> activity.showSetDefaultBrowserPrompt()

        ChecklistItem.Task.Type.SIGN_IN ->
            navigateTo(HomeFragmentDirections.actionGlobalTurnOnSync(FenixFxAEntryPoint.NewUserOnboarding))

        ChecklistItem.Task.Type.SELECT_THEME ->
            navigateTo(HomeFragmentDirections.actionGlobalCustomizationFragment())

        ChecklistItem.Task.Type.CHANGE_TOOLBAR_PLACEMENT ->
            navigateTo(HomeFragmentDirections.actionGlobalCustomizationFragment())

        ChecklistItem.Task.Type.INSTALL_SEARCH_WIDGET -> showAddSearchWidgetPrompt()

        ChecklistItem.Task.Type.EXPLORE_EXTENSION ->
            navigateTo(HomeFragmentDirections.actionGlobalAddonsManagementFragment())
    }

    private fun navigateTo(directions: NavDirections) =
        navController.nav(R.id.homeFragment, directions)

    override fun onRemoveChecklistButtonClicked() {
        appStore.dispatch(AppAction.SetupChecklistAction.Closed)
    }
}
