Torn Rigs Layout Switcher

Adds "Save current rig layout" & "Empty rig layout" quick actions to a Cracking crime.

// ==UserScript==
// @name         Torn Rigs Layout Switcher
// @namespace    https://github.com/SOLiNARY
// @version      0.7
// @description  Adds "Save current rig layout" & "Empty rig layout" quick actions to a Cracking crime.
// @author       Ramin Quluzade, Silmaril [2665762]
// @license      MIT License
// @match        https://www.torn.com/loader.php?sid=crimes*
// @match        https://www.torn.com/loader.php?sid=crimes#/cracking
// @match        https://sliw.co/rig/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=torn.com
// @grant        unsafeWindow
// @grant        GM_addStyle
// @run-at       document-start
// ==/UserScript==

(async function() {
    'use strict';

    const IS_DEBUG = false;
    const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined';

    setScriptVersion();
    if (!checkCurrentSite()){
        return;
    }
    let rigLayouts = localStorage.getItem("silmaril-rigs-layout-switcher-layouts") ?? "";
    let rigLayoutsArray = rigLayouts.split(',');
    checkUrlForImport();

    class ComponentInfo {
        constructor(x = null, y = null, item = null, x2 = null, y2 = null, action = null) {
            this.x = x;
            this.y = y;
            this.item = item;
            this.x2 = x2;
            this.y2 = y2;
            this.action = action;
        }
    }

    const rfcvArg = "rfcv=";
    let rfcv = localStorage.getItem("silmaril-rigs-layout-switcher-rfcv") ?? null;
    const crimesReadUrl = '/page.php?sid=crimesData&step=crimesList&';
    const crimesWriteUrl = '/page.php?sid=crimesData&step=prepare&';
    let rfcvUpdatedThisSession = false;
    let mutationFound = false;
    let panelAdded = false;
    IS_DEBUG ? console.log('rigLayouts', rigLayouts, rigLayoutsArray) : null;
    let currentRig = 0;
    let rigsInfo = {0: [], 1: [], 2: []};
    let requestsQueue = [];

    const { fetch: originalFetch } = isTampermonkeyEnabled ? unsafeWindow : window;

    const customFetch = async (...args) => {
        let [resource, config] = args;
        let response = await originalFetch(resource, config);

        IS_DEBUG ? console.log('response', response) : null;
        let fetchUrl = response.url;
        if (fetchUrl.indexOf(crimesReadUrl) >= 0 || fetchUrl.indexOf(crimesWriteUrl) >= 0) {
            try {
                const jsonData = await response.clone().json();
                const rig = fetchUrl.indexOf(crimesReadUrl) >= 0 ? jsonData.DB.crimesByType.rig : jsonData.DB.additionalInfo.prepareInfo.rig;
                IS_DEBUG ? console.log('rig.chassis', rig.chassis) : null;
                rig.chassis.forEach((rigData, rigId) => {
                    IS_DEBUG ? console.log('rigData', rigData) : null;
                    IS_DEBUG ? console.log('rigId', rigId) : null;
                    IS_DEBUG ? console.log('rigData.components', rigData.components) : null;
                    let items = [];
                    if (rigData.components == null) {
                        rigsInfo[rigId] = [];
                        return;
                    }
                    rigData.components.forEach((componentData) => {
                        if (componentData.ID == 0) {
                            return;
                        }
                        let componentInfo = new ComponentInfo(componentData.coords[0].x, componentData.coords[0].y, componentData.ID);
                        if (componentData.coords[1] != null) {
                            componentInfo.x2 = componentData.coords[1].x;
                            componentInfo.y2 = componentData.coords[1].y;
                        }
                        items.push(componentInfo);
                    });
                    rigsInfo[rigId] = items;
                });
                IS_DEBUG ? console.log('rigsInfo', rigsInfo) : null;
            } catch (error) {
                console.log('[TornRigsLayoutSwitcher] No targets, skipping the script init', error);
            }
        }

        if (rfcvUpdatedThisSession) {
            return response;
        }

        if (!rfcvUpdatedThisSession){
            let rfcvIdx = fetchUrl.indexOf(rfcvArg);
            if (rfcvIdx >= 0){
                rfcv = fetchUrl.substr(rfcvIdx + rfcvArg.length);
                localStorage.setItem("silmaril-loadout-switcher-rfcv", rfcv);
                document.querySelectorAll("div.silmaril-torn-rigs-layout-switcher-container button").forEach((button) => button.classList.remove("disabled"));
                rfcvUpdatedThisSession = true;
            }
        }

        return response;
    };

    if (isTampermonkeyEnabled){
        unsafeWindow.fetch = customFetch;
    } else {
        window.fetch = customFetch;
    }

    const styles = `.wave-animation{position:relative;overflow:hidden}.wave{pointer-events:none;position:absolute;width:100%;height:33px;background-color:transparent;opacity:0;transform:translateX(-100%);animation:3s cubic-bezier(0,0,0,1) waveAnimation}@keyframes waveAnimation{0%{opacity:1;transform:translateX(-100%)}100%{opacity:0;transform:translateX(100%)}}`;

    if (isTampermonkeyEnabled){
        GM_addStyle(styles);
    } else {
        let style = document.createElement("style");
        style.type = "text/css";
        style.innerHTML = styles;
        while (document.head == null){
            await sleep(50);
        }
        document.head.appendChild(style);
    }

    const setLayoutUrl = "/page.php?sid=crimesData&step=prepare&rfcv={rfcv}";
    const resetTemplate = `{"step":"resetRig","chassisID":{rig}}`;
    const layoutTemplate = `{"step":"{step}","chassisID":{rig},"ID":{component},"coords":[{shortComponent}{longComponent}]}`;
    const coordinatesTemplate = `{"x":{xCoordinate},"y":{yCoordinate}}`;
    const add = 'add';
    const remove = 'remove';
    const componentIds = {
        "eCPU": 1,
        "CPU": 2,
        "HPCPU": 3,
        "Fan": 4,
        "Water Block": 5,
        "Heat Sink": 6,
        "PSU": 7,
        "None": 0
    };

    const observerTarget = document.querySelector("html");
    const observerConfig = { attributes: false, childList: true, characterData: false, subtree: true };

    const observer = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutationItem) {
            if (mutationFound || panelAdded){
                observer.disconnect();
                return;
            }
            let mutation = mutationItem.target;
            if (mutation.classList == "crime-root cracking-root" || mutation.querySelector('div[class*=rig___]')) {
                IS_DEBUG ? console.log('MATCHED RIG', mutation.querySelector('div[class*=rig___]')) : null;
                const rigDiv = mutation.querySelector('div[class*=rig___]');
                if (rigDiv == null)
                {
                    IS_DEBUG ? console.log('no rig found') : null;
                    return;
                }
                IS_DEBUG ? console.log('Rig Found!') : null;
                mutationFound = true;
                observer.disconnect();
                const buttonContainer = document.createElement('div');
                buttonContainer.className = 'silmaril-rigs-layout-switcher-templates';

                const waveDiv = document.createElement('div');
                waveDiv.className = 'wave';

                buttonContainer.appendChild(waveDiv);
                addEmptyAndLayoutButtons(buttonContainer);

                const spamButton = document.createElement('button');
                spamButton.id = 'silmaril-rigs-layout-switcher-spam-button';
                spamButton.type = 'button';
                spamButton.className = 'torn-btn non-deletable disabled';
                spamButton.textContent = 'No layout chosen';
                spamButton.addEventListener('click', async event => {
                    if (event.target.classList.contains('disabled')){
                        return;
                    }
                    const poppedItems = requestsQueue.shift();
                    const urlToProcess = poppedItems[0];
                    const layoutUrl = poppedItems[1];
                    await sendSetLayoutRequest(urlToProcess, layoutUrl);
                    if (requestsQueue.length == 0) {
                        event.target.classList.add('disabled');
                        event.target.textContent = 'All requests completed!';
                    } else {
                        event.target.textContent = `Spam click ${requestsQueue.length} more times!`;
                    }
                });

                if (!panelAdded){
                    document.querySelector('div[class*=currentCrime___]').append(getRigChoices(), getLayoutActions(document.querySelector('div[class*=cracking-root]')), buttonContainer, spamButton);
                    panelAdded = true;
                }
            }
        });
    });
    observer.observe(observerTarget, observerConfig);

    function setScriptVersion() {
        try {
            if (isTampermonkeyEnabled) {
                unsafeWindow.tornRigLayoutSwitcherVersion = GM_info.script.version;
            } else {
                window.tornRigLayoutSwitcherVersion = '0.5';
            }
        } catch (e) {
            console.error('[TornRigsLayoutSwitcher] Failed to get script version!', e);
        }
    }

    function checkCurrentSite() {
        try {
            const currentUrl = window.location.href;
            const url = new URL(currentUrl);
            const domain = url.hostname;
            return domain.indexOf('torn.com') > 0;
        } catch (e) {
            console.error('[TornRigsLayoutSwitcher] Not Torn domain!', e);
            return false;
        }
    }

    function checkUrlForImport() {
        try {
            const importString = getUrlParameter('rig-import');

            console.log('Initial value of rig-import:', importString);

            if (importString === null || importString.length < 75) {
                return;
            };

            removeUrlParameter('rig-import');
            console.log('Updated URL:', window.location.href);

            let rigsToImport = {};
            for (let i = 0; i < 3; i++) {
                let rigToImport = [];
                for (let y = 0; y < 5; y++) {
                    for (let x = 0; x < 5; x++) {
                        const currentChar = importString[x + y * 5 + i * 25];
                        switch (currentChar){
                            case 'b':
                            case 'P':
                            case 'V':
                                console.log(`[${x},${y}] Empty Slot`, currentChar);
                                break;
                            case 'e':
                                console.log(`[${x},${y}] eCPU`);
                                rigToImport.push({'x': x, 'y': y, 'item': 1});
                                break;
                            case 'c':
                                console.log(`[${x},${y}] CPU`);
                                rigToImport.push({'x': x, 'y': y, 'item': 2});
                                break;
                            case 'h':
                                console.log(`[${x},${y}] HPCPU`);
                                rigToImport.push({'x': x, 'y': y, 'item': 3});
                                break;
                            case 'f':
                                console.log(`[${x},${y}] Fan`);
                                rigToImport.push({'x': x, 'y': y, 'item': 4});
                                break;
                            case 'w':
                                console.log(`[${x},${y}] Water Block`);
                                rigToImport.push({'x': x, 'y': y, 'item': 5});
                                break;
                            case 's':
                                console.log(`[${x},${y}] Heat Sink`);
                                rigToImport.push({'x': x, 'y': y, 'item': 6});
                                break;
                            case 'p':
                                console.log(`[${x},${y}] PSU Horizontal`);
                                rigToImport.push({'x': x, 'y': y, 'item': 7, 'x2': x + 1, 'y2': y});
                                break;
                            case 'v':
                                console.log(`[${x},${y}] PSU Vertical`);
                                rigToImport.push({'x': x, 'y': y, 'item': 7, 'x2': x, 'y2': y + 1});
                                break;
                            default:
                                console.log(`[${x},${y}] Unknown character`, currentChar);
                                break;
                        }
                    }
                }
                if (rigToImport.length > 0) {
                    rigsToImport[`rig-case.${i}`] = rigToImport;
                }
            }
            console.log('rigsToImport', rigsToImport);
            importLayout(rigsToImport);
        }
        catch (e) {
            console.error('[TornRigsLayoutSwitcher] Failed to import from URL!', e);
        }
    }

    function addEmptyAndLayoutButtons(root){
        const empty = document.createElement('button');
        empty.type = 'button';
        empty.className = 'torn-btn non-deletable';
        empty.textContent = 'Empty';
        empty.setAttribute('data-action', 'remove');
        empty.addEventListener('click', () => {handleLoadoutClick(root)});
        root.appendChild(empty);

        addLayoutButtons(root);
    }

    function getRigChoices(){
        const rigChoiceWrapper = document.createElement('ul');
        rigChoiceWrapper.role = 'tablist';
        rigChoiceWrapper.className = 'torn-tabs tabs-dark ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all';

        const rigChoice1 = document.createElement('li');
        rigChoice1.id = 'silmaril-rigs-layout-switcher-choice-1';
        rigChoice1.role = 'button';
        rigChoice1.ariaSelected = 'true';
        rigChoice1.setAttribute('data-disable', '0');
        rigChoice1.className = 'ui-state-default ui-corner-top ui-tabs-active ui-state-active';
        rigChoice1.addEventListener('click', event => {
            IS_DEBUG ? console.log('event', event) : null;
            currentRig = 0;
            event.target.parentNode.classList.add('ui-tabs-active');
            event.target.parentNode.classList.add('ui-state-active');
            let rig2 = document.getElementById('silmaril-rigs-layout-switcher-choice-2');
            rig2.classList.remove('ui-tabs-active');
            rig2.classList.remove('ui-state-active');
            let rig3 = document.getElementById('silmaril-rigs-layout-switcher-choice-3');
            rig3.classList.remove('ui-tabs-active');
            rig3.classList.remove('ui-state-active');
            IS_DEBUG ? console.log('currentRig', currentRig) : null;
        });
        const rigChoiceLink1 = document.createElement('a');
        rigChoiceLink1.innerText = 'Chassis #1';
        rigChoice1.append(rigChoiceLink1);

        const rigChoice2 = document.createElement('li');
        rigChoice2.id = 'silmaril-rigs-layout-switcher-choice-2';
        rigChoice2.role = 'button';
        rigChoice2.ariaSelected = 'false';
        rigChoice2.setAttribute('data-disable', '0');
        rigChoice2.className = 'ui-state-default ui-corner-top';
        rigChoice2.addEventListener('click', event => {
            IS_DEBUG ? console.log('event', event) : null;
            currentRig = 1;
            event.target.parentNode.classList.add('ui-tabs-active');
            event.target.parentNode.classList.add('ui-state-active');
            let rig1 = document.getElementById('silmaril-rigs-layout-switcher-choice-1');
            rig1.classList.remove('ui-tabs-active');
            rig1.classList.remove('ui-state-active');
            let rig3 = document.getElementById('silmaril-rigs-layout-switcher-choice-3');
            rig3.classList.remove('ui-tabs-active');
            rig3.classList.remove('ui-state-active');
            IS_DEBUG ? console.log('currentRig', currentRig) : null;
        });
        const rigChoiceLink2 = document.createElement('a');
        rigChoiceLink2.innerText = 'Chassis #2';
        rigChoice2.append(rigChoiceLink2);

        const rigChoice3 = document.createElement('li');
        rigChoice3.id = 'silmaril-rigs-layout-switcher-choice-3';
        rigChoice3.role = 'button';
        rigChoice3.ariaSelected = 'false';
        rigChoice3.setAttribute('data-disable', '0');
        rigChoice3.className = 'ui-state-default ui-corner-top';
        rigChoice3.addEventListener('click', event => {
            IS_DEBUG ? console.log('event', event) : null;
            currentRig = 2;
            event.target.parentNode.classList.add('ui-tabs-active');
            event.target.parentNode.classList.add('ui-state-active');
            let rig1 = document.getElementById('silmaril-rigs-layout-switcher-choice-1');
            rig1.classList.remove('ui-tabs-active');
            rig1.classList.remove('ui-state-active');
            let rig2 = document.getElementById('silmaril-rigs-layout-switcher-choice-2');
            rig2.classList.remove('ui-tabs-active');
            rig2.classList.remove('ui-state-active');
            IS_DEBUG ? console.log('currentRig', currentRig) : null;
        });
        const rigChoiceLink3 = document.createElement('a');
        rigChoiceLink3.innerText = 'Chassis #3';
        rigChoice3.append(rigChoiceLink3);

        rigChoiceWrapper.append(rigChoice1, rigChoice2, rigChoice3);
        IS_DEBUG ? console.log('rigChoiceWrapper', rigChoiceWrapper) : null;
        return rigChoiceWrapper;
    }

    function getLayoutActions(root){
        const actionsContainer = document.createElement('div');
        actionsContainer.className = 'silmaril-torn-rigs-layout-switcher-container';

        const saveCurrentLayout = document.createElement('button');
        saveCurrentLayout.type = 'button';
        saveCurrentLayout.className = 'torn-btn';
        saveCurrentLayout.textContent = 'Save current layout';
        saveCurrentLayout.addEventListener('click', () => {
            IS_DEBUG ? console.log('save current layout') : null;
            let userInput = prompt("Please enter a unique name for a layout (DO NOT USE COMMAS ',')" ?? '').toLowerCase();
            if (userInput !== null && userInput.length > 0) {
                rigLayouts = localStorage.getItem("silmaril-rigs-layout-switcher-layouts") ?? "";
                rigLayoutsArray = rigLayouts.split(',');
                if (!rigLayoutsArray.includes(userInput)) {
                    rigLayoutsArray.push(userInput);
                    rigLayouts = rigLayoutsArray.join(',');
                    localStorage.setItem("silmaril-rigs-layout-switcher-layouts", rigLayouts);
                }
                localStorage.setItem(`silmaril-rigs-layout-switcher-layout-${userInput}`, JSON.stringify(rigsInfo[currentRig], customReplacer));
                root.querySelectorAll("div.silmaril-rigs-layout-switcher-templates > button:not(.non-deletable)").forEach((item) => item.remove());
                addLayoutButtons(root.querySelector('div[class*=currentCrime___] > div.silmaril-rigs-layout-switcher-templates'));
            } else {
                console.error("[TornRigsLayoutSwitcher] User cancelled the layout naming input.");
            }
        });

        const deleteLayout = document.createElement('button');
        deleteLayout.type = 'button';
        deleteLayout.className = 'torn-btn';
        deleteLayout.textContent = 'Delete layout';
        deleteLayout.addEventListener('click', () => {
            IS_DEBUG ? console.log('delete layout') : null;
            let userInput = prompt("Please enter a name of a layout to delete" ?? '').toLowerCase();
            if (userInput !== null && userInput.length > 0) {
                rigLayouts = localStorage.getItem("silmaril-rigs-layout-switcher-layouts") ?? "";
                rigLayoutsArray = rigLayouts.split(',');
                rigLayoutsArray = rigLayoutsArray.filter(item => item !== userInput);
                rigLayouts = rigLayoutsArray.join(',');
                localStorage.setItem("silmaril-rigs-layout-switcher-layouts", rigLayouts);
                localStorage.removeItem(`silmaril-rigs-layout-switcher-layout-${userInput}`);
                root.querySelectorAll("div.silmaril-rigs-layout-switcher-templates > button:not(.non-deletable)").forEach((item) => item.remove());
                addLayoutButtons(root.querySelector('div[class*=currentCrime___] > div.silmaril-rigs-layout-switcher-templates'));
            } else {
                console.error("[TornRigsLayoutSwitcher] User cancelled the layout naming input.");
            }
        });

        const disableRig = document.createElement('button');
        disableRig.type = 'button';
        disableRig.className = 'torn-btn';
        disableRig.textContent = 'Disable rig';
        disableRig.addEventListener('click', () => {
            IS_DEBUG ? console.log('disable rig') : null;
            saveSetLayoutRequestsToQueue(remove, 0, rigsInfo[0].filter(x => x.item === componentIds.PSU), null);
            saveSetLayoutRequestsToQueue(remove, 1, rigsInfo[1].filter(x => x.item === componentIds.PSU), null, false);
            saveSetLayoutRequestsToQueue(remove, 2, rigsInfo[2].filter(x => x.item === componentIds.PSU), null, false);
        });

        const exportAllLayouts = document.createElement('button');
        exportAllLayouts.type = 'button';
        exportAllLayouts.className = 'torn-btn';
        exportAllLayouts.textContent = 'Export all layouts';
        exportAllLayouts.addEventListener('click', () => {
            IS_DEBUG ? console.log('export all layouts') : null;
            rigLayouts = localStorage.getItem("silmaril-rigs-layout-switcher-layouts") ?? "";
            rigLayoutsArray = rigLayouts.split(',');
            let allLayoutsToExport = {};

            rigLayoutsArray.forEach((layoutName) => {
                if (layoutName == ''){
                    return;
                }
                try {
                    const layoutInfo = localStorage.getItem(`silmaril-rigs-layout-switcher-layout-${layoutName}`);
                    IS_DEBUG ? console.log(`layout ${layoutName}`, layoutInfo) : null;
                    allLayoutsToExport[layoutName] = JSON.parse(layoutInfo);
                    IS_DEBUG ? console.log('allLayoutsToExport step', allLayoutsToExport) : null;
                } catch (e) {
                    return;
                }
            });

            IS_DEBUG ? console.log('allLayoutsToExport final', allLayoutsToExport) : null;
            const allLayoutsToExportRaw = JSON.stringify(allLayoutsToExport, customReplacer);
            IS_DEBUG ? console.log('allLayoutsToExport stringify', allLayoutsToExportRaw) : null;

            prompt('Copy the text below & share it with friends!', allLayoutsToExportRaw);
        });

        const exportLayout = document.createElement('button');
        exportLayout.type = 'button';
        exportLayout.className = 'torn-btn';
        exportLayout.textContent = 'Export layout';
        exportLayout.addEventListener('click', () => {
            IS_DEBUG ? console.log('export layout') : null;
            let userInput = prompt("Please enter a name of a layout to export" ?? '').toLowerCase();
            if (userInput !== null && userInput.length > 0) {
                let layoutToExport = {};
                try {
                    const layoutInfo = localStorage.getItem(`silmaril-rigs-layout-switcher-layout-${userInput}`);
                    if (layoutInfo == null) {
                        console.log('[TornRigsLayoutSwitcher] No layout with this name!', userInput);
                        return;
                    }
                    IS_DEBUG ? console.log(`layout ${userInput}`, layoutInfo) : null;
                    layoutToExport[userInput] = JSON.parse(layoutInfo);
                } catch (e) {
                    return;
                }

                IS_DEBUG ? console.log('layoutToExport final', layoutToExport) : null;
                const layoutToExportRaw = JSON.stringify(layoutToExport, customReplacer);
                IS_DEBUG ? console.log('layoutToExport stringify', layoutToExportRaw) : null;

                prompt('Copy the text below & share it with friends!', layoutToExportRaw);
            } else {
                console.error("[TornRigsLayoutSwitcher] User cancelled the layout naming input.");
            }
        });

        const importLayouts = document.createElement('button');
        importLayouts.type = 'button';
        importLayouts.className = 'torn-btn';
        importLayouts.textContent = 'Import layouts';
        importLayouts.addEventListener('click', () => {
            IS_DEBUG ? console.log('save current layout') : null;
            let userInput = prompt("Please copy-paste the string which has been shared with you" ?? '').toLowerCase();
            if (userInput !== null && userInput.length > 0) {
                try {
                    const allLayoutsToImport = JSON.parse(userInput);
                    importLayout(allLayoutsToImport);
                }
                catch (e) {
                    return;
                }

                root.querySelectorAll("div.silmaril-rigs-layout-switcher-templates > button:not(.non-deletable)").forEach((item) => item.remove());
                addLayoutButtons(root.querySelector('div[class*=currentCrime___] > div.silmaril-rigs-layout-switcher-templates'));
            } else {
                console.error("[TornRigsLayoutSwitcher] User cancelled the layout naming input.");
            }
        });

        actionsContainer.append(saveCurrentLayout, deleteLayout, disableRig, exportAllLayouts, exportLayout, importLayouts);
        return actionsContainer;
    }

    async function addLayoutButtons(root){
        rigLayoutsArray.forEach((layout) => {
            if (layout == ''){
                return;
            }
            const button = document.createElement('button');
            button.type = 'button';
            button.className = rfcv === null ? 'torn-btn disabled' : 'torn-btn';
            button.textContent = layout;
            button.setAttribute('data-action', 'add');
            button.addEventListener('click', () => {handleLoadoutClick(root, layout)});

            root.appendChild(button);
        })
    }

    function importLayout(allLayoutsToImport) {
        IS_DEBUG ? console.log('allLayoutsToImport', allLayoutsToImport) : null;
        const allLayoutNamesToImport = Object.keys(allLayoutsToImport);
        rigLayouts = localStorage.getItem("silmaril-rigs-layout-switcher-layouts") ?? "";
        rigLayoutsArray = rigLayouts.split(',');

        allLayoutNamesToImport.forEach((layoutName) => {
            if (layoutName == ''){
                return;
            }
            IS_DEBUG ? console.log('layoutName', layoutName) : null;
            IS_DEBUG ? console.log('layout', allLayoutsToImport[layoutName]) : null;
            try {
                if (!rigLayoutsArray.includes(layoutName)) {
                    rigLayoutsArray.push(layoutName);
                }
                localStorage.setItem(`silmaril-rigs-layout-switcher-layout-${layoutName}`, JSON.stringify(allLayoutsToImport[layoutName], customReplacer));
                IS_DEBUG ? console.log('layougetLayout', localStorage.getItem(`silmaril-rigs-layout-switcher-layout-${layoutName}`)) : null;
            } catch (e) {
                console.error("[TornRigsLayoutSwitcher] Error while importing Layout", e);
                return;
            }
        });

        rigLayouts = rigLayoutsArray.join(',');
        localStorage.setItem("silmaril-rigs-layout-switcher-layouts", rigLayouts);
    }

    async function handleLoadoutClick(root, layoutName = null){
        let action = event.target.getAttribute('data-action');
        if (event.target.classList.contains('disabled')){
            return;
        }
        IS_DEBUG ? console.log('rigsInfo', rigsInfo) : null;
        let currentLayout = rigsInfo[currentRig];
        currentLayout.forEach(cell => {
            if (cell.x2 != null && cell.y2 != null) {
                // Swap the values
                if (cell.x == cell.x2 && cell.y > cell.y2) {
                    cell.y2 = [cell.y, cell.y = cell.y2][0];
                }
                if (cell.y == cell.y2 && cell.x > cell.x2) {
                    cell.x2 = [cell.x, cell.x = cell.x2][0];
                }
            }
        });
        let layoutItemsToBeRemoved = [];
        let layoutItemsToBeAdded = [];
        if (layoutName == null) {
            let resetRig = new ComponentInfo();
            resetRig.action = 'resetRig';
            action = 'resetRig';
            layoutItemsToBeRemoved.push(resetRig);
        } else {
            let newLayout = JSON.parse(localStorage.getItem(`silmaril-rigs-layout-switcher-layout-${layoutName}`));
            newLayout.forEach(cell => {
                if (cell.x2 != null && cell.y2 != null) {
                    // Swap the values
                    if (cell.x == cell.x2 && cell.y > cell.y2) {
                        cell.y2 = [cell.y, cell.y = cell.y2][0];
                    }
                    if (cell.y == cell.y2 && cell.x > cell.x2) {
                        cell.x2 = [cell.x, cell.x = cell.x2][0];
                    }
                }
            });
            for (let x = 0; x < 5; x++) {
                for (let y = 0; y < 5; y++) {
                    let currentCell = currentLayout.filter(cell => cell.x == x && cell.y == y);
                    let newCell = newLayout.filter(cell => cell.x == x && cell.y == y);
                    if (currentCell.length == 0) {
                        if (newCell.length == 0) {
                            IS_DEBUG ? console.log(`[${x},${y}] no cells, no conflict`) : null;
                            continue;
                        } else {
                            IS_DEBUG ? console.log(`[${x},${y}] adding new cell`) : null;
                            IS_DEBUG ? console.log('newCell[0]', newCell[0]) : null;
                            let clonedCell = newCell[0];
                            clonedCell.action = 'add';
                            layoutItemsToBeAdded.push(clonedCell);
                        }
                    } else {
                        if (newCell.length == 0) {
                            IS_DEBUG ? console.log(`[${x},${y}] removing old cell`) : null;
                            IS_DEBUG ? console.log('currentCell[0]', currentCell[0]) : null;
                            let clonedCell = currentCell[0];
                            clonedCell.action = 'remove';
                            layoutItemsToBeRemoved.push(clonedCell);
                        } else {
                            IS_DEBUG ? console.log(`[${x},${y}] comparing cells`) : null;
                            IS_DEBUG ? console.log('currentCell[0]', currentCell[0]) : null;
                            IS_DEBUG ? console.log('newCell[0]', newCell[0]) : null;
                            if (currentCell[0].item == newCell[0].item && currentCell[0].x2 == newCell[0].x2 && currentCell[0].y2 == newCell[0].y2) {
                                IS_DEBUG ? console.log(`[${x},${y}] same cells, no conflict`) : null;
                                continue;
                            } else {
                                IS_DEBUG ? console.log(`[${x},${y}] diff cells, remove & add`) : null;
                                let oldClonedCell = currentCell[0];
                                oldClonedCell.action = 'remove';
                                layoutItemsToBeRemoved.push(oldClonedCell);
                                let newClonedCell = newCell[0];
                                newClonedCell.action = 'add';
                                layoutItemsToBeAdded.push(newClonedCell);
                            }
                        }
                    }
                }
            }
        }
        const layoutItems = [...layoutItemsToBeRemoved, ...layoutItemsToBeAdded];
        IS_DEBUG ? console.log('currentLayout', currentLayout) : null;
        IS_DEBUG ? console.log('layoutItems', layoutItems) : null;

        await saveSetLayoutRequestsToQueue(action, currentRig, layoutItems, root);
    }

    function saveSetLayoutRequestsToQueue(action, rig, items, root, resetQueue = true){
        // console.log('items', items);
        if (resetQueue) {
            requestsQueue = [];
        }
        const urlWithRfcv = setLayoutUrl.replace("{rfcv}", rfcv);
        if (action == 'resetRig') {
            const layoutUrl = resetTemplate.replace("{rig}", rig);
            const url = urlWithRfcv.replace("{layout}", layoutUrl);
            requestsQueue.push([url, layoutUrl]);
        } else {
            items.forEach((item) => {
                const coordinates = coordinatesTemplate.replace("{xCoordinate}", item.x).replace("{yCoordinate}", item.y);
                let layoutUrl2 = layoutTemplate.replace("{step}", item.action === null ? action : item.action).replace("{rig}", rig).replace("{component}", item.item).replace("{shortComponent}", coordinates);
                let layoutUrl;
                if (item.x2 != null) {
                    const coordinatesLong = coordinatesTemplate.replace("{xCoordinate}", item.x2).replace("{yCoordinate}", item.y2);
                    layoutUrl = layoutUrl2.replace("{longComponent}", `,${coordinatesLong}`);
                } else {
                    layoutUrl = layoutUrl2.replace("{longComponent}", "");
                }
                const url = urlWithRfcv.replace("{layout}", layoutUrl);
                requestsQueue.push([url, layoutUrl]);
            });
        }
        let spamButton = document.getElementById('silmaril-rigs-layout-switcher-spam-button');
        spamButton.textContent = `Spam click ${requestsQueue.length} more times!`;
        spamButton.classList.remove('disabled');
    }

    async function sendSetLayoutRequest(url, layoutItems){
        let formData = new FormData();
        formData.append('typeID', 10);
        formData.append('crimeID', 204);
        formData.append('value1', encodeURIComponent(layoutItems));
        await fetch(url, {
            method: 'POST',
            body: formData
        })
            .then(response => {
            if (response.ok) {
                // console.log('component change request OK');
            } else {
                console.error("[TornRigsLayoutSwitcher] Set Layout request failed:", response);
            }
        })
            .catch(error => {
            console.error("[TornRigsLayoutSwitcher] Error setting Layout:", error);
        });
    }

    function customReplacer(key, value) {
        // Omit properties with null values or specific strings
        if (value === null || value === 'add' || value === 'remove' || value === 'move') {
            return undefined;
        }
        return value;
    }

    // Function to get the value of a URL parameter by name
    function getUrlParameter(name) {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get(name);
    }

    // Function to remove a URL parameter by name
    function removeUrlParameter(name) {
        const urlParams = new URLSearchParams(window.location.search);
        urlParams.delete(name);
        const searchParamsString = urlParams.toString();

        // Handle the presence of a fragment identifier
        const hash = window.location.hash;

        // Create the updated URL
        const newUrl = window.location.pathname + (searchParamsString ? '?' + searchParamsString : '') + hash;

        // Replace the current state to update the URL without triggering a page reload
        history.replaceState({}, document.title, newUrl);
    }

    function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
})();