/* 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.tabstray

import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.tabstray.navigation.TabManagerNavDestination
import org.mozilla.fenix.tabstray.syncedtabs.SyncedTabsListItem

/**
 * Value type that represents the state of the tabs tray.
 *
 * @property selectedPage The current page in the tray can be on.
 * @property mode Whether the browser tab list is in multi-select mode or not with the set of
 * currently selected tabs.
 * @property inactiveTabs The list of tabs are considered inactive.
 * @property inactiveTabsExpanded A flag to know if the Inactive Tabs section of the Tabs Tray
 * should be expanded when the tray is opened.
 * @property normalTabs The list of normal tabs that do not fall under [inactiveTabs].
 * @property privateTabs The list of tabs that are [ContentState.private].
 * @property syncedTabs The list of synced tabs.
 * @property syncing Whether the Synced Tabs feature should fetch the latest tabs from paired devices.
 * @property selectedTabId The ID of the currently selected (active) tab.
 * @property backStack The navigation history of the Tab Manager feature.
 */
data class TabsTrayState(
    val selectedPage: Page = Page.NormalTabs,
    val mode: Mode = Mode.Normal,
    val inactiveTabs: List<TabSessionState> = emptyList(),
    val inactiveTabsExpanded: Boolean = false,
    val normalTabs: List<TabSessionState> = emptyList(),
    val privateTabs: List<TabSessionState> = emptyList(),
    val syncedTabs: List<SyncedTabsListItem> = emptyList(),
    val syncing: Boolean = false,
    val selectedTabId: String? = null,
    val backStack: List<TabManagerNavDestination> = listOf(TabManagerNavDestination.Root),
) : State {

    /**
     * The current mode that the tabs list is in.
     */
    sealed class Mode {

        /**
         * A set of selected tabs which we would want to perform an action on.
         */
        open val selectedTabs = emptySet<TabSessionState>()

        /**
         * The default mode the tabs list is in.
         */
        object Normal : Mode()

        /**
         * The multi-select mode that the tabs list is in containing the set of currently
         * selected tabs.
         */
        data class Select(override val selectedTabs: Set<TabSessionState>) : Mode()
    }

    /**
     * Whether the Tab Search button is visible.
     */
    val searchIconVisible: Boolean
        get() = selectedPage != Page.SyncedTabs

    /**
     * Whether the Tab Search button is enabled.
     */
    val searchIconEnabled: Boolean
        get() = when {
            selectedPage == Page.NormalTabs && normalTabs.isNotEmpty() -> true
            selectedPage == Page.PrivateTabs && privateTabs.isNotEmpty() -> true
            else -> false
        }
}

/**
 * The different pagers in the tray that we can switch between in the [TrayPagerAdapter].
 */
enum class Page {

    /**
     * The pager position that displays normal tabs.
     */
    NormalTabs,

    /**
     * The pager position that displays private tabs.
     */
    PrivateTabs,

    /**
     * The pager position that displays Synced Tabs.
     */
    SyncedTabs,

    ;

    companion object {
        /**
         * Returns the [Page] that corresponds to the [position].
         *
         * @param position The index of the page.
         * @param enhancementsEnabled Whether this function is being invoked from the Tab Manager enhancements.
         */
        fun positionToPage(
            position: Int,
            enhancementsEnabled: Boolean = true,
        ): Page {
            return if (enhancementsEnabled) {
                when (position) {
                    0 -> PrivateTabs
                    1 -> NormalTabs
                    else -> SyncedTabs
                }
            } else {
                when (position) {
                    0 -> NormalTabs
                    1 -> PrivateTabs
                    else -> SyncedTabs
                }
            }
        }

        /**
         * Returns the visual index that corresponds to the [page].
         *
         * @param page The [Page] whose visual index is being looked-up.
         * @param enhancementsEnabled Whether this function is being invoked from the Tab Manager enhancements.
         */
        fun pageToPosition(
            page: Page,
            enhancementsEnabled: Boolean = true,
        ): Int {
            return if (enhancementsEnabled) {
                when (page) {
                    PrivateTabs -> 0
                    NormalTabs -> 1
                    SyncedTabs -> 2
                }
            } else {
                page.ordinal
            }
        }
    }
}

/**
 * [Action] implementation related to [TabsTrayStore].
 */
sealed class TabsTrayAction : Action {

    /**
     * Entered multi-select mode.
     */
    object EnterSelectMode : TabsTrayAction()

    /**
     * Exited multi-select mode.
     */
    object ExitSelectMode : TabsTrayAction()

    /**
     * Added a new [TabSessionState] to the selection set.
     */
    data class AddSelectTab(val tab: TabSessionState) : TabsTrayAction()

    /**
     * Removed a [TabSessionState] from the selection set.
     */
    data class RemoveSelectTab(val tab: TabSessionState) : TabsTrayAction()

    /**
     * The active page in the tray that is now in focus.
     */
    data class PageSelected(val page: Page) : TabsTrayAction()

    /**
     * A request to perform a "sync" action.
     */
    object SyncNow : TabsTrayAction()

    /**
     * When a "sync" action has completed; this can be triggered immediately after [SyncNow] if
     * no sync action was able to be performed.
     */
    object SyncCompleted : TabsTrayAction()

    /**
     * Updates the [TabsTrayState.inactiveTabsExpanded] boolean
     *
     * @property expanded The updated boolean to [TabsTrayState.inactiveTabsExpanded]
     */
    data class UpdateInactiveExpanded(val expanded: Boolean) : TabsTrayAction()

    /**
     * Updates the list of tabs in [TabsTrayState.inactiveTabs].
     */
    data class UpdateInactiveTabs(val tabs: List<TabSessionState>) : TabsTrayAction()

    /**
     * Updates the list of tabs in [TabsTrayState.normalTabs].
     */
    data class UpdateNormalTabs(val tabs: List<TabSessionState>) : TabsTrayAction()

    /**
     * Updates the list of tabs in [TabsTrayState.privateTabs].
     */
    data class UpdatePrivateTabs(val tabs: List<TabSessionState>) : TabsTrayAction()

    /**
     * Updates the list of synced tabs in [TabsTrayState.syncedTabs].
     */
    data class UpdateSyncedTabs(val tabs: List<SyncedTabsListItem>) : TabsTrayAction()

    /**
     * Updates the selected tab id.
     *
     * @property tabId The ID of the tab that is currently selected.
     */
    data class UpdateSelectedTabId(val tabId: String?) : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the tab auto close dialog is shown.
     */
    object TabAutoCloseDialogShown : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user requests to share all of their normal tabs.
     */
    object ShareAllNormalTabs : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user requests to share all of their private tabs.
     */
    object ShareAllPrivateTabs : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user requests to close all normal tabs.
     */
    object CloseAllNormalTabs : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user requests to close all private tabs.
     */
    object CloseAllPrivateTabs : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the three-dot menu is displayed to the user.
     */
    object ThreeDotMenuShown : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user requests to bookmark selected tabs.
     */
    data class BookmarkSelectedTabs(val tabCount: Int) : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user clicks on the Tab Search icon.
     */
    object TabSearchClicked : TabsTrayAction()

    /**
     * [TabsTrayAction] fired when the user clicks on the back button or swipes to navigate back.
     */
    object NavigateBackInvoked : TabsTrayAction()
}

/**
 * Reducer for [TabsTrayStore].
 */
internal object TabsTrayReducer {
    fun reduce(state: TabsTrayState, action: TabsTrayAction): TabsTrayState {
        return when (action) {
            is TabsTrayAction.EnterSelectMode ->
                state.copy(mode = TabsTrayState.Mode.Select(emptySet()))
            is TabsTrayAction.ExitSelectMode ->
                state.copy(mode = TabsTrayState.Mode.Normal)
            is TabsTrayAction.AddSelectTab ->
                state.copy(mode = TabsTrayState.Mode.Select(state.mode.selectedTabs + action.tab))
            is TabsTrayAction.RemoveSelectTab -> {
                val selected = state.mode.selectedTabs.filter { it.id != action.tab.id }.toSet()
                state.copy(
                    mode = if (selected.isEmpty()) {
                        TabsTrayState.Mode.Normal
                    } else {
                        TabsTrayState.Mode.Select(selected)
                    },
                )
            }
            is TabsTrayAction.PageSelected ->
                state.copy(selectedPage = action.page)
            is TabsTrayAction.SyncNow ->
                state.copy(syncing = true)
            is TabsTrayAction.SyncCompleted ->
                state.copy(syncing = false)
            is TabsTrayAction.UpdateInactiveExpanded ->
                state.copy(inactiveTabsExpanded = action.expanded)
            is TabsTrayAction.UpdateInactiveTabs ->
                state.copy(inactiveTabs = action.tabs)
            is TabsTrayAction.UpdateNormalTabs ->
                state.copy(normalTabs = action.tabs)
            is TabsTrayAction.UpdatePrivateTabs ->
                state.copy(privateTabs = action.tabs)
            is TabsTrayAction.UpdateSyncedTabs ->
                state.copy(syncedTabs = action.tabs)
            is TabsTrayAction.UpdateSelectedTabId ->
                state.copy(selectedTabId = action.tabId)
            is TabsTrayAction.TabAutoCloseDialogShown -> state
            is TabsTrayAction.ShareAllNormalTabs -> state
            is TabsTrayAction.ShareAllPrivateTabs -> state
            is TabsTrayAction.CloseAllNormalTabs -> state
            is TabsTrayAction.CloseAllPrivateTabs -> state
            is TabsTrayAction.BookmarkSelectedTabs -> state
            is TabsTrayAction.ThreeDotMenuShown -> state
            is TabsTrayAction.TabSearchClicked ->
                state.copy(backStack = state.backStack + TabManagerNavDestination.TabSearch)
            is TabsTrayAction.NavigateBackInvoked -> {
                when {
                    state.mode is TabsTrayState.Mode.Select -> state.copy(mode = TabsTrayState.Mode.Normal)
                    else -> state.copy(backStack = state.backStack.dropLast(1))
                }
            }
        }
    }
}

/**
 * A [Store] that holds the [TabsTrayState] for the tabs tray and reduces [TabsTrayAction]s
 * dispatched to the store.
 */
class TabsTrayStore(
    initialState: TabsTrayState = TabsTrayState(),
    middlewares: List<Middleware<TabsTrayState, TabsTrayAction>> = emptyList(),
) : Store<TabsTrayState, TabsTrayAction>(
    initialState,
    TabsTrayReducer::reduce,
    middlewares,
)
