Source: base-history/app_state_manager.js

/**
 * This manages the base history that runs from the app's root. 
 * 
 * It starts the loading of a main fragment, which is the starting point of any function in the app
 * 
 * It thus helps in main app navigation through the navigational controls
 * The base history manager however allows the toggling of the nav buttons/links of the main navigation controls only
 * That is to allow fragments to manage their own nav buttons/links individually
 * 
 * Also, it assigns states to the window history state manager API providing page, title, and url.
 * page and title can be shared but not url. Should be unique and point to an entire path traceable in routing info
 */

import RoutingInfoUtils from "../router/utils/routing-info/routing_info_utils";
import "./types/type_app_history_state"

class AppStateManager{

    /**
     * 
     * @param {RoutingInfo[]} mainRoutingInfo 
     */
    constructor(mainRoutingInfo){

        this.mainRoutingInfo = mainRoutingInfo;
        this.currentStackEntry = -1;

        this.normalizeCurrentStackEntry();
    }

    /**
     * WORKED WITH IMPLEMENTATION WHERE THIS WAS YET TO BE DEFINED.
     * 
     * Otherwise, will never do anything.
     * 
     * NOTE. If implementation ever changes, then deal with the bug of currentStackEntry never being updated
     * thus polyfilling next back pop with same value, making back determination difficult.
     * 
     * Call when state has been popped (moved back)
     * Help to polyfill in case previous (back state records) have no stack entry
     * Only possible for back movements (cause state was yet to be set with new method)
     */
    polyfillPreviousState(){

        /**
         * @type {AppHistoryState}
         */
        //This should be okay since it was pushed with stackEntry
        let currentBackState = window.history.state;
        if(!currentBackState){

            //This needs polyfilling
            currentBackState = this.generateBaseAppState(document.title, window.location.pathname);
        }
        if(currentBackState.stackEntry === undefined || currentBackState.stackEntry === null){

            //can get to negatives to polyfill
            currentBackState.stackEntry = this.currentStackEntry - 1;
            window.history.replaceState(currentBackState, currentBackState.pageTitle, window.location.pathname);
        }
    }

    
    /**
     * Gets the base page history state for the window history state API using a combination of the 
     * navBtnLinkSelector and basePageUrl
     * 
     * The generated app state's stack entry has not been normalized
     * 
     * Use targetBaseUrl cause of queries
     * 
     * @param {string} navBtnLinkSelector 
     * @param {string} targetBaseUrl
     * @param {string} fullUrl targetBaseUrl with queries
     * 
     * @returns {AppHistoryState}
     */
    getBaseAppStateFromMainNavID(navBtnLinkSelector, targetBaseUrl, fullUrl){

        const targetRouteInfo = RoutingInfoUtils.findMatchingRoutingInfoForUrlAndBaseNavBtn(this.mainRoutingInfo, targetBaseUrl, navBtnLinkSelector);
        if(!targetRouteInfo){

            throw new Error("No routing info found with this main navigation selector " + navBtnLinkSelector + " and route " + targetBaseUrl);
        }

        return this.generateBaseAppState(targetRouteInfo.pageTitle, fullUrl);
    }

    /**
     * Generate the base app state (without stack entry)
     * 
     * @param {string} pageTitle 
     * @param {string} url Must be the full url with queries (shown in address bar)
     * @returns {AppHistoryState}
     */
    generateBaseAppState(pageTitle, url){

        return {

            pageTitle: pageTitle,
            pageUrl: url
        };
    }

    /**
     * Normalize generated app state
     * 
     * Call if routing to new url thus forward records will be trashed by browser
     * 
     * @param {AppHistoryState} generatedState 
     */
    normalizeGeneratedAppState(generatedState){

        this.normalizeCurrentStackEntry();
        generatedState.stackEntry = ++this.currentStackEntry;
        return generatedState;
    }

    /**
     * @private
     * 
     * WARNING
     * 
     * Called internally. 
     * 
     * Overriding this will lead to unwanted App behaviour
     * 
     * Normalizes the current stack entry to what the browser has. At this point, there's no forward record 
     * or its about to be erased because of a full page reload or routing not triggered by a pop event
     */
    normalizeCurrentStackEntry(){

        /**
         * @type {AppHistoryState}
         */
        let currentState = window.history.state;
        if(!currentState){

            currentState = this.generateBaseAppState(document.title, window.location.pathname);
        }
        if(currentState.stackEntry !== null && currentState.stackEntry !== undefined){

            this.currentStackEntry = currentState.stackEntry;
        } //otherwise, default -1. Should only be so if app is yet to page, working with old API
    }
}

export default AppStateManager;