import { DynamicCSSInclude } from "./DynamicCSSInclude.js";
import { DynamicJSInclude }  from "./DynamicJSInclude.js";


/**
 * @class DynamicDepsLoader
 *  
 * @description
 *  Responsibilities:
 *  > looks at the DOM and find the component's dependencies 
 *  > loads the dependencies and caches them in the LocalStorage
 *
 * 
 * HOW IT WORKS
 * ------------
 * 
 * The dependecies are inserted into the component's markup
 * as data- attribute 
 * 
 * single JS dependency
 * --------------------
 * <span data-js-dependency="file.min.js"></span>
 * 
 * 
 * double (and nested) JS dependencies
 * -----------------------------------
 * in this case the second js is loaded and launched after the 
 * execution of the first one
 * <span data-js-dependency="mylib.min.js, file_that_uses_mylib.min.js"></span>
 * 
 * 
 * deferred JS dependencies
 * ------------------------
 * <span data-js-deferred-dependency="mylib.min.js, file_that_uses_mylib.min.js"></span>
 * 
 * they work the same as the other dependencies but they are
 * loaded just after the first scroll.
 * 
 * 
 * single CSS dependency
 * ---------------------
 * <span data-css-dependency='style.min.css'></span>
 * 
 * multiple CSS dependencies: in this case add more than one dependency
 * in priority order. ex:
 * <span data-css-dependency='first.css'></span>
 * <span data-css-dependency='second.css'></span>  
 * 
 * NOTE: we could add other feature like cache expiration times
 * 
 * @param {dictionary} args { jsdir, cssdir }
 */
 export class DynamicDepsLoader {

    constructor( args = { 
        // set defaults
        jsdir  : '/js/components/',
        cssdir : '/css/components/'
        
    }) {

        this.base_url = `${window.location.protocol}//${window.location.host}`;
        
        /// write the properties
        this.js_dir              = args.jsdir;
        this.css_dir             = `${this.base_url}${args.cssdir}`;

        this.js_dep_selector     = '[data-js-dependency]';
        this.def_js_dep_selector = '[data-js-deferred-dependency]';
        this.css_dep_selector    = '[data-css-dependency]';
        
        /// feature detection IntersectionObserver
        this.fallback = ( typeof window.IntersectionObserver == 'undefined' );
        
    }   

    scanDependencies() {

        // load dependecies
        if(this.fallback)
            this.FallbackLoadDeps();
        else 
            this.getDeps();

    }


    scanJS() { 

        return this.scan( this.js_dep_selector, 'jsDependency', this.js_dir );
    };


    scanCSS() {        

        return this.scan( this.css_dep_selector, 'cssDependency', this.css_dir );
    }

    /**
     * Scan the markup to find js deferred deps     
     */
    scanDeferredJS() {

        return this.scan( this.def_js_dep_selector, 'jsDeferredDependency', this.js_dir );      
    }
        

    /**
     * Takes the dependencies 
     * and istance them all
     */
    getDeps() {
        
        // scan components and find dependencies
        this.js_deps   = this.scanJS();
        this.def_deps  = this.scanDeferredJS();
        this.css_deps  = this.scanCSS();

        // defer dependencies loading after the first scroll
        this.firstScrollTrigger( () => {

            new DynamicJSInclude( this.def_deps );
        } );

        
        // appends js and styles
        return {

            jsinc  : new DynamicJSInclude(  this.js_deps ),
            cssinc : new DynamicCSSInclude( this.css_deps)
        };
            
    }

    
    /**
     * Scan the markup to find deps     
     */
    scan( selector, datafield, prefix) {
        
        // find the elements with dependencies
        let elements = [].map.call(document.querySelectorAll( selector ), i => i );
    
        // extract the dependencies
        return elements.reduce( ( dependencies, element ) => {

            let deps = element.dataset[ datafield ].split(',')
                        /// remove empty spaces
                        .map( dep => dep.trim() )
                        /// remove empty deps
                        .filter( dep => dep )
                        /// add prefix
                        .map( dep => prefix + dep )
                        /// remove already taken deps
                        .filter( dep => !dependencies.flat().includes( dep ) );

            // remove dependency attribute
            delete element.dataset[ datafield ];

            if( deps.length ) dependencies.push( deps );

            return dependencies;

        }, [] );
    }


    /** 
     * set the trigger to 
     * sense the first scroll and executes the callback
     */
    firstScrollTrigger( callback ) {
        
        /// create the trigger element
        var trigger = document.createElement('span');
        trigger.style.position = 'absolute';
        trigger.style.display  = 'block';
        trigger.style.top  = ( window.innerHeight + 10 ) + 'px';
        trigger.style.left = '0';
        trigger.style.width = '1px';
        trigger.style.height = '1px';
        trigger.style.backgroundColor = 'transparent';
        
        /// put the trigger on the page
        document.body.style.position = 'relative';
        document.body.appendChild(trigger);

        /// setup the observer
        var observer = new IntersectionObserver( function (entries, observer) {

                entries.forEach( function( entry ) {
                  
                    if( entry.isIntersecting ) {
                        
                        /// call the callback
                        callback();

                        /// stop observing
                        observer.unobserve(trigger);
                    }
                    
                }.bind(this));            
        }.bind(this));

        /// start observing
        observer.observe(trigger);
    }
}