const modapi_modloader = "(" + (() => {
    globalThis.promisifyIDBRequest = function promisifyIDBRequest(request) {
        return new Promise((resolve, reject) => {
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }

    globalThis.getDatabase = async function getDatabase() {
        const dbRequest = indexedDB.open("EF_MODS");
        const db = await promisifyIDBRequest(dbRequest);

        if (!db.objectStoreNames.contains("filesystem")) {
            db.close();
            const version = db.version + 1;
            const upgradeRequest = indexedDB.open("EF_MODS", version);
            upgradeRequest.onupgradeneeded = (event) => {
                const upgradedDb = event.target.result;
                upgradedDb.createObjectStore("filesystem");
            };
            return promisifyIDBRequest(upgradeRequest);
        }

        return db;
    }

    globalThis.getMods = async function getMods() {
        const db = await getDatabase();
        const transaction = db.transaction(["filesystem"], "readonly");
        const objectStore = transaction.objectStore("filesystem");
        const object = await promisifyIDBRequest(objectStore.get("mods.txt"));
        var out = object ? (await object.text()).split("|").toSorted() : [];
        db.close();
        return out;
    }

    globalThis.getMod = async function getMod(mod) {
        const db = await getDatabase();
        const transaction = db.transaction(["filesystem"], "readonly");
        const objectStore = transaction.objectStore("filesystem");
        const object = await promisifyIDBRequest(objectStore.get("mods/" + mod));
        var out =  object ? (await object.text()) : "";
        db.close();
        return out;
    }

    globalThis.saveMods = async function saveMods(mods) {
        const db = await getDatabase();
        const transaction = db.transaction(["filesystem"], "readwrite");
        const objectStore = transaction.objectStore("filesystem");
        const encoder = new TextEncoder();
        const modsData = encoder.encode(mods.toSorted().join("|"));
        const modsBlob = new Blob([modsData], { type: "text/plain" });
        await promisifyIDBRequest(objectStore.put(modsBlob, "mods.txt"));
        db.close();
    }

    globalThis.addMod = async function addMod(mod) {
        const mods = await getMods();
        mods.push("web@" + mod);
        await saveMods(mods);
    }

    globalThis.addFileMod = async function addFileMod(mod, textContents) {
        const mods = await getMods();
        if (mods.includes(mod)) {
            await removeMod(mods.indexOf(mod));
        } else {
            mods.push(mod);
        }
        await saveMods(mods);

        const db = await getDatabase();
        const transaction = db.transaction(["filesystem"], "readwrite");
        const objectStore = transaction.objectStore("filesystem");
        const encoder = new TextEncoder();
        const modsData = encoder.encode(textContents);
        const modsBlob = new Blob([modsData], { type: "text/plain" });
        await promisifyIDBRequest(objectStore.put(modsBlob, "mods/" + mod));
        db.close();
    }

    globalThis.removeMod = async function removeMod(index) {
        const mods = await getMods();
        if (index >= 0 && index < mods.length) {
            var deleted = mods.splice(index, 1)[0];
            await saveMods(mods);
            if (!deleted.startsWith("web@")) {
                const db = await getDatabase();
                const transaction = db.transaction(["filesystem"], "readwrite");
                const objectStore = transaction.objectStore("filesystem");
                await promisifyIDBRequest(objectStore.delete("mods/" + deleted));
                db.close();
            }
        }
    }

    globalThis.resetMods = async function resetMods() {
        console.log("Resetting mods...");
        const db = await getDatabase();
        const transaction = db.transaction(["filesystem"], "readwrite");
        const objectStore = transaction.objectStore("filesystem");
        await promisifyIDBRequest(objectStore.clear());
        console.log("Mods reset");
        db.close();
    }

    globalThis.modLoader = async function modLoader(modsArr = []) {
        if (!window.eaglerMLoaderMainRun) {
            var searchParams = new URLSearchParams(location.search);
            searchParams.getAll("mod").forEach((modToAdd) => {
                console.log(
                    "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
                );
                modsArr.push("web@" + modToAdd);
            });
            searchParams.getAll("plugin").forEach((modToAdd) => {
                console.log(
                    "[EaglerML] Adding mod to loadlist from search params: " + modToAdd
                );
                modsArr.push("web@" + modToAdd);
            });
            if (
                !!eaglercraftXOpts &&
                !!eaglercraftXOpts.Mods &&
                Array.isArray(eaglercraftXOpts.Mods)
            ) {
                eaglercraftXOpts.Mods.forEach((modToAdd) => {
                    console.log(
                        "[EaglerML] Adding mod to loadlist from eaglercraftXOpts: " +
                        modToAdd
                    );
                    modsArr.push("web@" + modToAdd);
                });
            }

            console.log("[EaglerML] Searching in iDB");
            try {
                var idbMods = await getMods();
                modsArr = modsArr.concat(idbMods
                    .filter(x => { return x && x.length > 0 })
                );
            } catch (error) {
                console.error(error);
            }

            window.eaglerMLoaderMainRun = true;
        }
        if (window.noLoadMods === true) {
            modsArr.splice(0, modsArr.length);
        }
        function checkModsLoaded(totalLoaded, identifier) {
            console.log(
                "[EaglerML] Checking if mods are finished :: " +
                totalLoaded +
                "/" +
                modsArr.length
            );
            if (totalLoaded >= modsArr.length) {
                clearInterval(identifier);
                window.ModGracePeriod = false;
                if (
                    window.eaglerMLoaderMainRun &&
                    ModAPI &&
                    ModAPI.events &&
                    ModAPI.events.callEvent
                ) {
                    ModAPI.events.callEvent("load", {});
                }
                console.log(
                    "[EaglerML] Checking if mods are finished :: All mods loaded! Grace period off."
                );
            }
        }
        function methodB(currentMod) {
            try {
                console.log("[EaglerML] Loading " + currentMod + " via method B.");
                var script = document.createElement("script");
                script.setAttribute("data-hash", ModAPI.util.hashCode("web@" + currentMod));
                script.src = currentMod;
                script.setAttribute("data-isMod", "true");
                script.onerror = () => {
                    console.log(
                        "[EaglerML] Failed to load " + currentMod + " via method B!"
                    );
                    script.remove();
                    totalLoaded++;
                };
                script.onload = () => {
                    console.log(
                        "[EaglerML] Successfully loaded " + currentMod + " via method B."
                    );
                    totalLoaded++;
                };
                document.body.appendChild(script);
            } catch (error) {
                console.log(
                    "[EaglerML] Oh no! The mod " + currentMod + " failed to load!"
                );
                totalLoaded++;
            }
        }
        window.ModGracePeriod = true;
        var totalLoaded = 0;
        var loaderCheckInterval = null;
        modsArr.sort();
        for (let i = 0; i < modsArr.length; i++) {
            let currentMod = modsArr[i];
            var isIDBMod = !currentMod.startsWith("web@");
            if (!isIDBMod) {
                currentMod = currentMod.replace("web@", "");
            }
            console.log("[EaglerML] Starting " + currentMod);
            try {
                var responseText = isIDBMod ? await getMod(currentMod) : await (await fetch(currentMod)).text();
                console.log("[EaglerML] Loading " + currentMod + " via method A.");
                var script = document.createElement("script");
                script.setAttribute("data-hash", ModAPI.util.hashCode((isIDBMod ? "" : "web@") + currentMod));
                try {
                    script.src =
                        "data:text/javascript," + encodeURIComponent(responseText);
                } catch (error) {
                    methodB(currentMod);
                    return;
                }
                script.setAttribute("data-isMod", "true");
                script.onerror = () => {
                    console.log(
                        "[EaglerML] Failed to load " + currentMod + " via method A!"
                    );
                    script.remove();
                    totalLoaded++;
                };
                script.onload = () => {
                    console.log(
                        "[EaglerML] Successfully loaded " + currentMod + " via method A."
                    );
                    totalLoaded++;
                };
                document.body.appendChild(script);
            } catch (error) {
                methodB(currentMod);
            }
        }
        loaderCheckInterval = setInterval(() => {
            checkModsLoaded(totalLoaded, loaderCheckInterval);
        }, 500);
        console.log(
            "[EaglerML] Starting to load " + modsArr.length + " mods..."
        );
        window.returnTotalLoadedMods = function returnTotalLoadedMods() {
            return totalLoaded;
        };
    };
}).toString() + ")();"

if (globalThis.process) {
    module.exports = {
        modapi_modloader: modapi_modloader
    }
}