import { HTML_ATTRIBUTE_BLOCKER_ID, HTML_ATTRIBUTE_INLINE } from "./htmlAttributes.js";
import { isJavaScript } from "./putHtmlCodeToDom.js";

const NO_UNBLOCKED_BLOCKED_CONTENT_SELECT = `:not([${HTML_ATTRIBUTE_BLOCKER_ID}]):not([${HTML_ATTRIBUTE_INLINE}])`;
const WAIT_SCRIPTS_SELECTOR_SYNC = `script[src]:not([async]):not([defer])${NO_UNBLOCKED_BLOCKED_CONTENT_SELECT}`;
const WAIT_SCRIPTS_SELECTOR_ASYNC = `script[src][async]${NO_UNBLOCKED_BLOCKED_CONTENT_SELECT}`;

/**
 * Create this instance at the time before adding content or scripts to your DOM,
 * and call the `.wait` function after adding content or scripts to your DOM.
 */
class WaitSynchronousScripts {
    public scriptsBefore: HTMLScriptElement[];

    private selector: string;

    public constructor(selector: string) {
        this.selector = selector;
        this.scriptsBefore = this.snapshotScripts();
    }

    public snapshotScripts() {
        const scripts = Array.prototype.slice.call(document.querySelectorAll(this.selector)) as HTMLScriptElement[];
        return scripts.filter((script) => isJavaScript(script.getAttribute("type")));
    }

    /**
     * Returns the diff of scripts as Promises and are resolved when script is loaded.
     */
    public diff() {
        const scriptsAfter = this.snapshotScripts();
        const scriptsDiff = scriptsAfter.filter((a) => this.scriptsBefore.indexOf(a) === -1);

        const promises = scriptsDiff.map(
            (script) =>
                new Promise<void>((resolve) => {
                    // Check if the script is already loaded or in browser queue (https://stackoverflow.com/a/67184038/5506547)
                    const performanceEntries = performance
                        .getEntriesByType("resource")
                        .filter(({ name }) => name === script.src); // `getEntriesByName` is not available in all browsers

                    if (performanceEntries.length > 0) {
                        resolve();
                    }

                    script.addEventListener("load", () => {
                        resolve();
                    });

                    script.addEventListener("error", () => {
                        // At the moment, we just ignore errors
                        resolve();
                    });
                }),
        );

        return promises;
    }
}

export { WaitSynchronousScripts, WAIT_SCRIPTS_SELECTOR_SYNC, WAIT_SCRIPTS_SELECTOR_ASYNC };
