Source: data-manager/paginators/list_data_paginator.js

//@ts-check

import IntersectionDetectionUtils from "./utils/intersection_detection_utils.js";

/**
 * Just get what to observe at intersect, know the markers, cascade API interface calls (for this scope strictly)
 * allow or deny pagination based on markers
 * 
 * And detachObservers on delete of target observer or flushAll or bulk delete 
 * (update per cycle of delete, but batch update on attach (return set of attached views))
 * Or, just after the attach cycle completes, run the correct get
 * 
 * Also, when told to setUpObservers, pass in data length and viewNodePos as well, to match
 * to requested trigger position and doPagination
 * 
 * when doing pagination, just direct calls loadData() for dataManagerInstance;
 * 
 */

/**
 * @template M
 * @template {ArrayOnlyNestedKeys<M>} VMS
 */
class ListDataPaginator{

    /**
     * 
     * @param {import("./list_data_paginator.d.ts.js").ListDataPaginatorOptions<M, VMS>} args 
     */
    constructor(args){

        /**
         * @type {VMS}
         */
        this.scope = args.scope;
        /**
         * @type {string}
         */
        this.soleModelId = args.soleModelId;
        /**
         * @type {number}
         */
        this.paginationTriggerPos = args.triggerPos_ToLast ? args.triggerPos_ToLast : 6;
        /**
         * @type {DataManagerInstance<M>}
         */
        this.dataManager = args.dataManagerInstance;
        /**
         * @type {ListViewManagerInstance<M, VMS>}
         */
        this.listViewManager = args.listViewManagerInstance;
        this.rootViewOptions = args.viewOptions;
        /**
         * @type {import("DataManager").DataOperationsNetworkInterface<ValueTypeOfNested<M, VMS>, VMS, M>}
         */
        this.loadNewNetworkInterface = args.networkInterface;
        this.loadDataOptions = args.loadDataOptions;
        this.overrideNetworkInterface = this.initOverrideNetworkInterface();
        this.finalCallInterface = args.finalCallInterface;
        /**
         * @type {boolean}
         */
        this.doneFullPagination = args?.serverSidePaginationEnd;

        /**
         * @type {Element}
         */
        this.currentIntersectionTarget = null;
        /**
         * @type {string}
         */
        this.overrideLoadAddr = null;
    }

    /**
     * @type {ListDataPaginatorInstance<M, VMS>['initOverrideNetworkInterface']}
     */
    initOverrideNetworkInterface(){

        const getLoadNewNetworkInterface = () => {

            return this.loadNewNetworkInterface;
        }

        const getSelf = () => this;

        return {

            async getReqBody(addr, updatedModel, mutation, oldCompleteModel){

                return await getLoadNewNetworkInterface().getReqBody(addr, updatedModel, mutation, oldCompleteModel);
            },

            async onDataLoadPostProcess(reqAddr, response, newData, oldData, mutation, mappedDataId, extras){

                const data = await getLoadNewNetworkInterface().onDataLoadPostProcess(reqAddr, response, newData, oldData, mutation, mappedDataId, extras);
                if(data.extras?.paginationInfo[getSelf().scope]?.stopPagination !== undefined){

                    getSelf().setPaginationComplete(data.extras.paginationInfo[getSelf().scope].stopPagination);
                }
                return data;
            },

            onDataLoadError(reqAddr, response, newData, oldData, mutation){

                return getLoadNewNetworkInterface().onDataLoadError(reqAddr, response, newData, oldData, mutation);
            }
        }
    }

    /**
     * @type {ListDataPaginatorInstance<M, VMS>['setSoleModelId']} 
     */
    setSoleModelId(modelId){

        if(!this.soleModelId){

            this.soleModelId = modelId;
        }
    }

    /**
     */
    doPagination(){

        this.dataManager.loadData(this.scope, { modelID: this.soleModelId, mappedDataId: this.listViewManager.childOptions.parentMappedDataId }, this.loadDataOptions, this.overrideNetworkInterface, this.overrideLoadAddr).then((result) => {

            this.finalCallInterface?.onCompleteSuccess(result)
        }).catch((err) => {

            this.finalCallInterface?.onCompleteFail(err);
        });
    }

    /**
     * Called to refresh paginated list and load a new one
     * 
     * Flushes the right scope data then triggers a new load for the scope
     * 
     * NOTE: As long as you call this, and you actively set the doneFullPagination value at on data post process
     * the newly loaded data should have its pagination reset
     * 
     * @type {ListDataPaginatorInstance<M, VMS>['refreshList']}
     */
    refreshList(overrideLoadAddr){

        //Here, clear the scoped model first and then load a new list based on given params 
        //(accessible in interface)
        //So, introducing a new data manager method, flushScopedData(modelId, scope), 
        //modelID and scope accepting MODEL_ROOT AT THE SAME TIME to flushAllData

        //flush scopedData will just create a new mutation, with power to cancel all loadNewOperations for that scope
        //overwrite model for the scope to null and commit that
        //Then, you can trigger a loadNewMutation for that scope
        this.dataManager.flushScopedData(this.soleModelId, this.scope);
        this.overrideLoadAddr = overrideLoadAddr;
        this.doPagination();
    }

    /**
     * @type {ListDataPaginatorInstance<M, VMS>['setUpPaginationIntersector']}
     */
    setUpPaginationIntersector(){

        /**
         * Setting intersection point
        */
       //Remove currently being observed
        if(this.currentIntersectionObserver){
    
            this.currentIntersectionObserver.unobserve(this.currentIntersectionTarget);
            this.currentIntersectionObserver = null;
            this.currentIntersectionTarget = null;
        }

        //observe new
        const observerViewPort = this.listViewManager.getIntersectionObserverViewPort();
        const allViewNodes = observerViewPort.getElementsByClassName(this.rootViewOptions.componentViewClass);
        if(!this.doneFullPagination && allViewNodes?.length > this.paginationTriggerPos){

            this.currentIntersectionTarget = allViewNodes[allViewNodes.length - this.paginationTriggerPos];
            this.currentIntersectionObserver = IntersectionDetectionUtils.initIntersector(observerViewPort, 0.4, this.currentIntersectionTarget, this.doPagination.bind(this));
        }
    }

    /**
     * @type {ListDataPaginatorInstance<M, VMS>['updatePaginationIntersectorOnDelete']}
     */
    updatePaginationIntersectorOnDelete(deletedNode){

        if(this.currentIntersectionTarget === deletedNode){

            this.setUpPaginationIntersector();
        }
    }

    /**
     * @type {ListDataPaginatorInstance<M, VMS>['setPaginationComplete']}
     */
    setPaginationComplete(completed){

        this.doneFullPagination = completed;
    }
}

if(false){

    /**
     * @type {import("./list_data_paginator.d.ts.js").ListDataPaginatorInstance<*, *>}
     */
    const check = new ListDataPaginator(null);
}

export default ListDataPaginator;