BrowserStateReducer.kt

/* 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 mozilla.components.browser.state.reducer

import mozilla.components.browser.state.action.AppLifecycleAction
import mozilla.components.browser.state.action.AwesomeBarAction
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContainerAction
import mozilla.components.browser.state.action.ContentAction
import mozilla.components.browser.state.action.CookieBannerAction
import mozilla.components.browser.state.action.CopyInternetResourceAction
import mozilla.components.browser.state.action.CrashAction
import mozilla.components.browser.state.action.CustomTabListAction
import mozilla.components.browser.state.action.DebugAction
import mozilla.components.browser.state.action.DefaultDesktopModeAction
import mozilla.components.browser.state.action.DownloadAction
import mozilla.components.browser.state.action.EngineAction
import mozilla.components.browser.state.action.ExtensionsProcessAction
import mozilla.components.browser.state.action.HistoryMetadataAction
import mozilla.components.browser.state.action.InitAction
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.action.LocaleAction
import mozilla.components.browser.state.action.MediaSessionAction
import mozilla.components.browser.state.action.ReaderAction
import mozilla.components.browser.state.action.RecentlyClosedAction
import mozilla.components.browser.state.action.RestoreCompleteAction
import mozilla.components.browser.state.action.SearchAction
import mozilla.components.browser.state.action.ShareResourceAction
import mozilla.components.browser.state.action.SystemAction
import mozilla.components.browser.state.action.TabGroupAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.action.TrackingProtectionAction
import mozilla.components.browser.state.action.TranslationsAction
import mozilla.components.browser.state.action.UndoAction
import mozilla.components.browser.state.action.UpdateDistribution
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.Action

/**
 * Reducers for [BrowserStore].
 *
 * A reducer is a function that receives the current [BrowserState] and an [Action] and then returns a new
 * [BrowserState].
 */
internal object BrowserStateReducer {
    fun reduce(state: BrowserState, action: BrowserAction): BrowserState {
        return when (action) {
            is InitAction -> state
            is AppLifecycleAction -> state
            is RestoreCompleteAction -> state.copy(restoreComplete = true)
            is ContainerAction -> ContainerReducer.reduce(state, action)
            is RecentlyClosedAction -> RecentlyClosedReducer.reduce(state, action)
            is ContentAction -> ContentStateReducer.reduce(state, action)
            is CustomTabListAction -> CustomTabListReducer.reduce(state, action)
            is EngineAction -> EngineStateReducer.reduce(state, action)
            is ReaderAction -> ReaderStateReducer.reduce(state, action)
            is SystemAction -> SystemReducer.reduce(state, action)
            is TabListAction -> TabListReducer.reduce(state, action)
            is TabGroupAction -> TabGroupReducer.reduce(state, action)
            is TrackingProtectionAction -> TrackingProtectionStateReducer.reduce(state, action)
            is TranslationsAction -> TranslationsStateReducer.reduce(state, action)
            is CookieBannerAction -> CookieBannerStateReducer.reduce(state, action)
            is WebExtensionAction -> WebExtensionReducer.reduce(state, action)
            is MediaSessionAction -> MediaSessionReducer.reduce(state, action)
            is DownloadAction -> DownloadStateReducer.reduce(state, action)
            is SearchAction -> SearchReducer.reduce(state, action)
            is CrashAction -> CrashReducer.reduce(state, action)
            is LastAccessAction -> LastAccessReducer.reduce(state, action)
            is UndoAction -> UndoReducer.reduce(state, action)
            is ShareResourceAction -> ShareResourceStateReducer.reduce(state, action)
            is CopyInternetResourceAction -> CopyInternetResourceStateReducer.reduce(state, action)
            is LocaleAction -> LocaleStateReducer.reduce(state, action)
            is HistoryMetadataAction -> HistoryMetadataReducer.reduce(state, action)
            is DebugAction -> DebugReducer.reduce(state, action)
            is ExtensionsProcessAction -> ExtensionsProcessStateReducer.reduce(state, action)
            is AwesomeBarAction -> AwesomeBarStateReducer.reduce(state, action)
            is UpdateDistribution -> state.copy(distributionId = action.distributionId)
            is DefaultDesktopModeAction -> DesktopModeReducer.reduce(state = state, action = action)
        }
    }
}

/**
 * Finds the corresponding tab or custom tab in the [BrowserState] and updates it using [update].
 *
 * Consider using the specialized [updateTabState] or [updateCustomTabState] to limit the tabs to be updated
 * if the properties you want changed are not common to both [SessionState] implementations.
 *
 * @param tabId ID of the tab to change.
 * @param update Returns a new version of the tab state. Must be the same class,
 * preferably using [SessionState.createCopy].
 */
@Suppress("Unchecked_Cast")
internal fun BrowserState.updateTabOrCustomTabState(
    tabId: String,
    update: (SessionState) -> SessionState,
): BrowserState {
    val newTabs = tabs.updateTabs(tabId, update) as List<TabSessionState>?
    if (newTabs != null) return copy(tabs = newTabs)

    val newCustomTabs = customTabs.updateTabs(tabId, update) as List<CustomTabSessionState>?
    if (newCustomTabs != null) return copy(customTabs = newCustomTabs)

    return this
}

/**
 * Finds the corresponding tab in the [BrowserState] and replaces it using [update].
 *
 * This will only update a [TabSessionState] if such exists with the given [tabId].
 * Consider using the other specialized [updateCustomTabState] method for updating only [CustomTabSessionState]
 * or the general [updateTabOrCustomTabState] to update any tab or custom tab with a given [tabId].
 *
 * @param tabId ID of the tab to change.
 * @param update Returns a new version of [TabSessionState].
 */
internal fun BrowserState.updateTabState(
    tabId: String,
    update: (TabSessionState) -> TabSessionState,
): BrowserState {
    return tabs.updateTabs(tabId, update)?.let {
        copy(tabs = it)
    } ?: this
}

/**
 * Finds the corresponding custom tab in the [BrowserState] and replaces it using [update].
 *
 * This will only update a [CustomTabSessionState] if such exists with the given [tabId].
 * Consider using the other specialized [updateTabState] method for updating only [TabSessionState]
 * or the general [updateTabOrCustomTabState] to update any tab or custom tab with a given [tabId].
 *
 * @param tabId ID of the tab to change.
 * @param update Returns a new version of [CustomTabSessionState].
 */
internal fun BrowserState.updateCustomTabState(
    tabId: String,
    update: (CustomTabSessionState) -> CustomTabSessionState,
): BrowserState {
    return customTabs.updateTabs(tabId, update)?.let {
        copy(customTabs = it)
    } ?: this
}

/**
 * Finds the corresponding tab in the list and replaces it using [update].
 * @param tabId ID of the tab to change.
 * @param update Returns a new version of the tab state.
 */
internal fun <T : SessionState> List<T>.updateTabs(
    tabId: String,
    update: (T) -> T,
): List<T>? {
    val tabIndex = indexOfFirst { it.id == tabId }
    if (tabIndex == -1) return null

    return subList(0, tabIndex) + update(get(tabIndex)) + subList(tabIndex + 1, size)
}