Source: fragments/utils/transitions/transitions_manager.js

//@ts-check

import Queue from "../../../utils/abstract-data-types/queue/queue";

/**
 * This class manages all transition workers. Can queue them and help coordinate their firing for nice effects
 * Will also coordinate workers to pass data between fragments or view panels for transitions
 * 
 * SO, HOW YOU SHOULD WORK*******************
 * 
 * Transitions Manager manages transitions between various workers. 
 * Main job is to trigger each worker to start transitions and coordinate intertransition worker jobs in that
 * developer can ask manager to trigger another transition at a certain percentage of another worker's progress
 * for cool effects. 
 * 
 * Manager however is not responsible for interpolation. That is specific to worker.
 * And, note on nature of transitions, can have class triggered changes (addition or removal of classes to element)
 * or attribute triggered changes (addition or removal of attributes to trigger animations)
 * **Two listed need no interpolator. Developer use css to do actual transitions.
 * Then, others are purely numbers based for various effects
 * 
 * Developer flexibility as wished.
 * 
 * So, developer passes two values. TransitionsDataCollection and TransitionWorkersQueue. 
 * Former holds unique data for each transition. Can have worker have a static method called CollectData (GetTargetViewCurrentProperties(node)) that 
 * collects the data it needs to transition that element and have it as "before" value.
 * Latter holds the transition workers that will be used to make transitions. Each will collect its data from
 * TransitionsDataCollection when passed to it, use it as before and transition to after specified. 
 * 
 * So, TransitionsDataCollection has for each, "before" and "after". Can specify direction for transitions
 * by switching before and after since flow is from before to after.
 * 
 * OKAY
 * 
 * Look at Queues. That's where we specify order of transitions and applicable delays. If delay 0, next item in queue triggered
 * immediately.
 * 
 * Have a progress hook for each transitionworker for manager to use to coordinate queues. 
 * 
 * Deal with element flashing? Happens before "before" properties applied. Developer can mitigate by ensuring they
 * already at before? Or have opacity 0 and reset once "before", if not part of animated properties?
 * 
 * Interesting.....
 * 
 * Fragment and view panel have calls for getTransitionsData_Workers_Queue() that manager uses to build everything
 * 
 * Each worker has a DataCollectionsModel static (or just def? - YES) to return model of how it references values in data collection
 * 
 * On consent, does the same and saved in saved state for reference later.
 * 
 * So, need savedState in bind
 * 
 * STOP TRANSITIONS IF DESTROYING VIEW (on cancel or destroy and transitions was running)
 * 
 * Defer view destroys if needed for transition? Mmmm.....pass node as data? Need to consider effect on cancel.
 * 
 * 
 * @type {import("TransitionsManager").TransitionsManagerConstructor}
 */
const TransitionsManager = class TransitionsManager{

    constructor(){

        /**
         * @type {QueueInstance<TransitionsManagerQueueData<{}>>}
         */
        //@ts-ignore
        this.currentTransitionsQueue = new Queue();
        
        /**
         * @type {NodeJS.Timeout}
         */
        this.completeQueueTimeoutID = null;
    }

    /**
     * 
     * @param {TransitionsManagerRunArgs<{}>} args 
     * @param {genericFunction} cb
     */
    runTransitions(args, cb){

        const startRunningTransitions = () => {

            triggerTransitions(() => {

                this.currentTransitionsQueue.clear();
                if(args.completeQueueDelay){

                    //Delay for as long as needed to mark complete
                    this.completeQueueTimeoutID = setTimeout(() => {

                        this.completeQueueTimeoutID = null;
                        cb();
                    }, args.completeQueueDelay);
                } else {

                    cb();
                }
            });
        }

        if(!this.currentTransitionsQueue.isEmpty()){

            console.error(`Cannot request new transitions while current one still running`);
        } else {

            this.currentTransitionsQueue = args?.queue ? args.queue : this.currentTransitionsQueue;
            if(args.preStartDelay_ms){

                //Using this var because it tells when a transitions queue is being handled
                this.completeQueueTimeoutID = setTimeout(() => {

                    startRunningTransitions();
                }, args.preStartDelay_ms)
            } else {

                startRunningTransitions();
            }
        }

        /**
         * 
         * @param {genericFunction} callback 
         */
        function triggerTransitions(callback){

            if(!args?.queue?.isEmpty()){

                let queueData = args.queue.dequeue();
                queueData.worker.runViewTransition(queueData.data, (progress) => {
    
                    //Run next once we hit or pass trigger (allow for errors to pass 100)
                    if(queueData.nextPercentTrigger && progress >= queueData.nextPercentTrigger){
    
                        triggerTransitions(callback);
                    }
                });
    
                //Call for next immediately if don't have to wait. Else wait for cb
                //Also, if last in queue, then wait till it completes cause this is last recursive call and want to wait for all transitions to complete
                //Specify this value using the flag completeQueueDelay
                /**
                 * And this option won't work. last nextPercentTrigger cannot effectively be completeQueueDelay
                 * Cause can have a different transitions combo that last in queue finishes earlier than latter.
                 * So still have this flag
                 */
                if(!queueData.nextPercentTrigger){
    
                    triggerTransitions(callback);
                }
            } else {
    
                
                callback();
            }
        }
    }

    /**
     * Cancels running transitions
     */
    cancelTransitions(){

        if(this.completeQueueTimeoutID){

            clearTimeout(this.completeQueueTimeoutID);
            this.completeQueueTimeoutID = null;
        }
        while(!this.currentTransitionsQueue.isEmpty()){

            this.currentTransitionsQueue.dequeue().worker.cancelViewTransition()
        }
    }
}

export default TransitionsManager;