/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable no-undef */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
let auth_pending = false,
    did_initial_auth = false;
const sendAuth = isRegister => {
    if (localStorage.getItem("email") && localStorage.getItem("password")) {
        auth_pending = true;
        window.ws.send(
            JSON.stringify({
                auth: {
                    email: localStorage.getItem("email"),
                    password: wp.encSync(localStorage.getItem("password")),
                    isRegister
                }
            })
        );
    }
};
function openWebSocket() {
    window.ws = new WebSocket("/custom/ws");
    window.ws.onopen = () => {
        sendAuth(false);
    };
    window.ws.onmessage = e => {
        const msg = JSON.parse(e.data);
        if ("ports" in msg) {
            location.port = location.protocol == "https:" ? msg.ports.https : msg.ports.http;
        }
        if ("config_reloaded" in msg) {
            //window.is_admin = undefined;
            if (single.getCurrentPath() == "/webui/cheats") {
                single.loadRoute("/webui/cheats");
            }
        }
        if ("auth_succ" in msg) {
            auth_pending = false;
            const data = msg.auth_succ;
            if (single.getCurrentPath() == "/webui/") {
                single.loadRoute("/webui/inventory");
            }
            $(".displayname").text(data.DisplayName);
            window.accountId = data.id;
            window.authz = "accountId=" + data.id + "&nonce=" + data.Nonce;
            if (window.dict) {
                updateLocElements();
            }
            if (!did_initial_auth) {
                did_initial_auth = true;
                updateInventory();
            }
        }
        if ("auth_fail" in msg) {
            auth_pending = false;
            logout();
            if (single.getCurrentPath() == "/webui/") {
                alert(loc(msg.auth_fail.isRegister ? "code_regFail" : "code_loginFail"));
            } else {
                single.loadRoute("/webui/");
            }
        }
        if ("logged_out" in msg) {
            sendAuth();
        }
    };
    window.ws.onclose = function () {
        window.ws = undefined;
        setTimeout(openWebSocket, 3000);
    };
}
openWebSocket();
function getWebSocket() {
    return new Promise(resolve => {
        let interval;
        interval = setInterval(() => {
            if (window.ws) {
                clearInterval(interval);
                resolve(window.ws);
            }
        }, 10);
    });
}
window.registerSubmit = false;
function doLogin() {
    if (auth_pending) {
        return;
    }
    localStorage.setItem("email", $("#email").val());
    localStorage.setItem("password", $("#password").val());
    sendAuth(registerSubmit);
    window.registerSubmit = false;
}
function revalidateAuthz(succ_cb) {
    getWebSocket().then(() => {
        // We have a websocket connection, so authz should be good.
        succ_cb();
    });
}
function logout() {
    localStorage.removeItem("email");
    localStorage.removeItem("password");
    did_initial_auth = false;
}
function doLogout() {
    logout();
    if (window.ws) {
        // Unsubscribe from notifications about nonce invalidation
        window.ws.send(JSON.stringify({ logout: true }));
    }
}
function renameAccount() {
    const newname = window.prompt(loc("code_changeNameConfirm"));
    if (newname) {
        revalidateAuthz(() => {
            fetch("/custom/renameAccount?" + window.authz + "&newname=" + newname).then(() => {
                $(".displayname").text(newname);
                updateLocElements();
            });
        });
    }
}
function deleteAccount() {
    if (window.confirm(loc("code_deleteAccountConfirm"))) {
        revalidateAuthz(() => {
            fetch("/custom/deleteAccount?" + window.authz).then(() => {
                logout();
                single.loadRoute("/webui/"); // Show login screen
            });
        });
    }
}
single.on("route_load", function (event) {
    if (event.route.paths[0] != "/webui/") {
        // Authorised route?
        if (!localStorage.getItem("email")) {
            // Not logged in?
            return single.loadRoute("/webui/"); // Show login screen
        }
        $("body").addClass("logged-in");
    } else {
        $("body").removeClass("logged-in");
    }
    $(".nav-link").removeClass("active");
    const navLink = document.querySelector(".nav-link[href='" + event.route.paths[0] + "']");
    if (navLink) {
        navLink.classList.add("active");
    }
});
function loc(tag) {
    return ((window.dict ?? {})[tag] ?? tag)
        .split("|DISPLAYNAME|")
        .join(document.querySelector(".displayname").textContent)
        .split("|EMAIL|")
        .join(localStorage.getItem("email"));
}
function updateLocElements() {
    document.querySelectorAll("[data-loc]").forEach(elm => {
        elm.innerHTML = loc(elm.getAttribute("data-loc"));
    });
    document.querySelectorAll("[data-loc-placeholder]").forEach(elm => {
        elm.placeholder = loc(elm.getAttribute("data-loc-placeholder"));
    });
}
function setActiveLanguage(lang) {
    window.lang = lang;
    const lang_name = document.querySelector("[data-lang=" + lang + "]").textContent;
    document.getElementById("active-lang-name").textContent = lang_name;
    document.querySelector("[data-lang].active").classList.remove("active");
    document.querySelector("[data-lang=" + lang + "]").classList.add("active");
    window.dictPromise = new Promise(resolve => {
        const webui_lang = ["en", "ru", "fr", "de", "zh", "es"].indexOf(lang) == -1 ? "en" : lang;
        let script = document.getElementById("translations");
        if (script) document.documentElement.removeChild(script);
        script = document.createElement("script");
        script.id = "translations";
        script.src = "/translations/" + webui_lang + ".js";
        script.onload = function () {
            updateLocElements();
            resolve(window.dict);
        };
        document.documentElement.appendChild(script);
    });
}
setActiveLanguage(localStorage.getItem("lang") ?? "en");
function setLanguage(lang) {
    setActiveLanguage(lang);
    localStorage.setItem("lang", lang);
    if (window.authz) {
        // Not in prelogin state?
        fetchItemList();
        updateInventory();
    }
}
const webUiModularWeapons = [
    "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
    "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
    "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
    "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
];
const permanentEvolutionWeapons = new Set([
    "/Lotus/Weapons/Tenno/Zariman/LongGuns/PumpShotgun/ZarimanPumpShotgun",
    "/Lotus/Weapons/Tenno/Zariman/LongGuns/SemiAutoRifle/ZarimanSemiAutoRifle",
    "/Lotus/Weapons/Tenno/Zariman/Melee/Dagger/ZarimanDaggerWeapon",
    "/Lotus/Weapons/Tenno/Zariman/Melee/Tonfas/ZarimanTonfaWeapon",
    "/Lotus/Weapons/Tenno/Zariman/Pistols/HeavyPistol/ZarimanHeavyPistol",
    "/Lotus/Weapons/Thanotech/EntFistIncarnon/EntFistIncarnon",
    "/Lotus/Weapons/Thanotech/EntratiWristGun/EntratiWristGunWeapon"
]);
let uniqueLevelCaps = {};
function fetchItemList() {
    window.itemListPromise = new Promise(resolve => {
        const req = $.get("/custom/getItemLists?lang=" + window.lang);
        req.done(async data => {
            await dictPromise;
            document.querySelectorAll('[id^="datalist-"]').forEach(datalist => {
                datalist.innerHTML = "";
            });
            const syndicateNone = document.createElement("option");
            syndicateNone.textContent = loc("cheats_none");
            document.getElementById("changeSyndicate").innerHTML = "";
            document.getElementById("changeSyndicate").appendChild(syndicateNone);
            // prettier-ignore
            data.archonCrystalUpgrades = {
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibrium": loc("upgrade_Equilibrium").split("|VAL|").join("20"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeEquilibriumMythic": loc("upgrade_Equilibrium").split("|VAL|").join("30"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamage": loc("upgrade_MeleeCritDamage").split("|VAL|").join("25"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeMeleeCritDamageMythic": loc("upgrade_MeleeCritDamage").split("|VAL|").join("37.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChance": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("25"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradePrimaryStatusChanceMythic": loc("upgrade_PrimaryStatusChance").split("|VAL|").join("37.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChance": loc("upgrade_SecondaryCritChance").split("|VAL|").join("25"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeSecondaryCritChanceMythic": loc("upgrade_SecondaryCritChance").split("|VAL|").join("37.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDuration": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityDurationMythic": loc("upgrade_WarframeAbilityDuration").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrength": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeAbilityStrengthMythic": loc("upgrade_WarframeAbilityStrength").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMax": loc("upgrade_WarframeArmourMax").split("|VAL|").join("150"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeArmourMaxMythic": loc("upgrade_WarframeArmourMax").split("|VAL|").join("225"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProc": loc("upgrade_WarframeBlastProc").split("|VAL|").join("5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeBlastProcMythic": loc("upgrade_WarframeBlastProc").split("|VAL|").join("7.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeed": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("25"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCastingSpeedMythic": loc("upgrade_WarframeCastingSpeed").split("|VAL|").join("37.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoost": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveDamageBoostMythic": loc("upgrade_WarframeCorrosiveDamageBoost").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStack": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("2"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCorrosiveStackMythic": loc("upgrade_WarframeCorrosiveStack").split("|VAL|").join("3"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoost": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("25"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeCritDamageBoostMythic": loc("upgrade_WarframeCritDamageBoost").split("|VAL|").join("37"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamage": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("30").split("|VAL2|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageMythic": loc("upgrade_WarframeElectricDamage").split("|VAL1|").join("45").split("|VAL2|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoost": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeElectricDamageBoostMythic": loc("upgrade_WarframeElectricDamageBoost").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMax": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("50"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeEnergyMaxMythic": loc("upgrade_WarframeEnergyMax").split("|VAL|").join("75"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergy": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("50"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectEnergyMythic": loc("upgrade_WarframeGlobeEffectEnergy").split("|VAL|").join("75"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealth": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("100"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeGlobeEffectHealthMythic": loc("upgrade_WarframeGlobeEffectHealth").split("|VAL|").join("150"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMax": loc("upgrade_WarframeHealthMax").split("|VAL|").join("150"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHealthMaxMythic": loc("upgrade_WarframeHealthMax").split("|VAL|").join("225"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpact": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("1").split("|VAL2|").join("300"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeHPBoostFromImpactMythic": loc("upgrade_WarframeHPBoostFromImpact").split("|VAL1|").join("2").split("|VAL2|").join("450"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocity": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeParkourVelocityMythic": loc("upgrade_WarframeParkourVelocity").split("|VAL|").join("22.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoost": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("10"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRadiationDamageBoostMythic": loc("upgrade_WarframeRadiationDamageBoost").split("|VAL|").join("15"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegen": loc("upgrade_WarframeRegen").split("|VAL|").join("5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeRegenMythic": loc("upgrade_WarframeRegen").split("|VAL|").join("7.5"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMax": loc("upgrade_WarframeShieldMax").split("|VAL|").join("150"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeShieldMaxMythic": loc("upgrade_WarframeShieldMax").split("|VAL|").join("225"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergy": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("30"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeStartingEnergyMythic": loc("upgrade_WarframeStartingEnergy").split("|VAL|").join("45"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamage": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("30"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinDamageMythic": loc("upgrade_WarframeToxinDamage").split("|VAL|").join("45"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHeal": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("2"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWarframeToxinHealMythic": loc("upgrade_WarframeToxinHeal").split("|VAL|").join("3"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeat": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1").split("|VAL2|").join("50"),
                "/Lotus/Upgrades/Invigorations/ArchonCrystalUpgrades/ArchonCrystalUpgradeWeaponCritBoostFromHeatMythic": loc("upgrade_WeaponCritBoostFromHeat").split("|VAL1|").join("1.5").split("|VAL2|").join("75"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityRangeMod": loc("upgrade_AvatarAbilityRange"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarAbilityEfficiencyMod": loc("upgrade_AvatarAbilityEfficiency"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarEnergyRegenMod": loc("upgrade_AvatarEnergyRegen"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarEnemyRadarMod": loc("upgrade_AvatarEnemyRadar"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarLootRadarMod": loc("upgrade_AvatarLootRadar"),
                "/Lotus/Upgrades/Mods/Rifle/WeaponAmmoMaxMod": loc("upgrade_WeaponAmmoMax"),
                "/Lotus/Upgrades/Mods/Aura/EnemyArmorReductionAuraMod": loc("upgrade_EnemyArmorReductionAura"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionAmmoMod": loc("upgrade_OnExecutionAmmo"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionHealthDropMod": loc("upgrade_OnExecutionHealthDrop"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionEnergyDropMod": loc("upgrade_OnExecutionEnergyDrop"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnFailHackResetMod": loc("upgrade_OnFailHackReset"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/DamageReductionOnHackMod": loc("upgrade_DamageReductionOnHack"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionReviveCompanionMod": loc("upgrade_OnExecutionReviveCompanion"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionParkourSpeedMod": loc("upgrade_OnExecutionParkourSpeed"),
                "/Lotus/Upgrades/Mods/Warframe/AvatarTimeLimitIncreaseMod": loc("upgrade_AvatarTimeLimitIncrease"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/ElectrifyOnHackMod": loc("upgrade_ElectrifyOnHack"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionTerrifyMod": loc("upgrade_OnExecutionTerrify"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackLockersMod": loc("upgrade_OnHackLockers"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionBlindMod": loc("upgrade_OnExecutionBlind"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/OnExecutionDrainPowerMod": loc("upgrade_OnExecutionDrainPower"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackSprintSpeedMod": loc("upgrade_OnHackSprintSpeed"),
                "/Lotus/Upgrades/Mods/DataSpike/Assassin/SwiftExecuteMod": loc("upgrade_SwiftExecute"),
                "/Lotus/Upgrades/Mods/DataSpike/Cipher/OnHackInvisMod": loc("upgrade_OnHackInvis"),
            };
            window.archonCrystalUpgrades = data.archonCrystalUpgrades;
            // Add mods mising in data sources
            data.mods.push({
                uniqueName: "/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser",
                name: loc("code_legendaryCore")
            });
            data.mods.push({
                uniqueName: "/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod",
                name: loc("code_traumaticPeculiar")
            });
            // Add modular weapons
            data.OperatorAmps.push({
                uniqueName: "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
                name: loc("code_amp")
            });
            data.Melee.push({
                uniqueName: "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
                name: loc("code_zaw")
            });
            data.LongGuns.push({
                uniqueName: "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
                name: loc("code_kitgun")
            });
            data.Pistols.push({
                uniqueName: "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
                name: loc("code_kitgun")
            });
            data.MoaPets ??= [];
            data.MoaPets.push({
                uniqueName: "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
                name: loc("code_moa")
            });
            data.MoaPets.push({
                uniqueName: "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit",
                name: loc("code_zanuka")
            });
            data.miscitems.push({
                uniqueName: "/Lotus/Types/Items/Research/DojoColors/GenericDojoColorPigment",
                name: loc("code_pigment")
            });
            const itemMap = {
                // Generics for rivens
                "/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: loc("code_archgun") },
                "/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: loc("code_melee") },
                "/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: loc("code_pistol") },
                "/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: loc("code_rifle") },
                "/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: loc("code_shotgun") },
                // Modular weapons
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam": { name: loc("code_kitgun") },
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher": { name: loc("code_kitgun") },
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun": { name: loc("code_kitgun") },
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper": { name: loc("code_kitgun") },
                "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam": { name: loc("code_kitgun") },
                "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun": { name: loc("code_kitgun") },
                "/Lotus/Weapons/Sentients/OperatorAmplifiers/SentTrainingAmplifier/OperatorTrainingAmpWeapon": {
                    name: loc("code_moteAmp")
                },
                "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit": { name: loc("code_kDrive") },
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit": {
                    name: data.ModularParts.find(
                        i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA"
                    ).name
                },
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit": {
                    name: data.ModularParts.find(
                        i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB"
                    ).name
                },
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit": {
                    name: data.ModularParts.find(
                        i => i.uniqueName === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC"
                    ).name
                }
            };
            for (const [type, items] of Object.entries(data)) {
                if (type == "archonCrystalUpgrades") {
                    Object.entries(items).forEach(([uniqueName, name]) => {
                        const option = document.createElement("option");
                        option.setAttribute("data-key", uniqueName);
                        option.value = name;
                        document.getElementById("datalist-" + type).appendChild(option);
                    });
                } else if (type == "uniqueLevelCaps") {
                    uniqueLevelCaps = items;
                } else if (type == "Syndicates") {
                    items.forEach(item => {
                        if (item.uniqueName.startsWith("RadioLegion")) {
                            item.name += " (" + item.uniqueName + ")";
                        }
                        const option = document.createElement("option");
                        option.value = item.uniqueName;
                        option.textContent = item.name;
                        document.getElementById("changeSyndicate").appendChild(option);
                    });
                } else {
                    const nameToItems = {};
                    items.forEach(item => {
                        item.name = item.name.replace(/<.+>/g, "").trim();
                        if ("badReason" in item) {
                            if (item.badReason == "starter") {
                                item.name = loc("code_starter").split("|MOD|").join(item.name);
                            } else {
                                item.name += " " + loc("code_badItem");
                            }
                        }
                        nameToItems[item.name] ??= [];
                        nameToItems[item.name].push(item);
                    });
                    items.forEach(item => {
                        if (type == "ModularParts") {
                            const supportedModularParts = [
                                "LWPT_HB_DECK",
                                "LWPT_HB_ENGINE",
                                "LWPT_HB_FRONT",
                                "LWPT_HB_JET",
                                "LWPT_AMP_OCULUS",
                                "LWPT_AMP_CORE",
                                "LWPT_AMP_BRACE",
                                "LWPT_BLADE",
                                "LWPT_HILT",
                                "LWPT_HILT_WEIGHT",
                                "LWPT_GUN_PRIMARY_HANDLE",
                                "LWPT_GUN_SECONDARY_HANDLE",
                                "LWPT_GUN_BARREL",
                                "LWPT_GUN_CLIP",
                                "LWPT_MOA_ENGINE",
                                "LWPT_MOA_PAYLOAD",
                                "LWPT_MOA_HEAD",
                                "LWPT_MOA_LEG",
                                "LWPT_ZANUKA_BODY",
                                "LWPT_ZANUKA_HEAD",
                                "LWPT_ZANUKA_LEG",
                                "LWPT_ZANUKA_TAIL",
                                "LWPT_CATBROW_ANTIGEN",
                                "LWPT_CATBROW_MUTAGEN",
                                "LWPT_KUBROW_ANTIGEN",
                                "LWPT_KUBROW_MUTAGEN"
                            ];
                            if (supportedModularParts.includes(item.partType)) {
                                const option = document.createElement("option");
                                option.setAttribute("data-key", item.uniqueName);
                                option.value = item.name;
                                document
                                    .getElementById("datalist-" + type + "-" + item.partType.slice(5))
                                    .appendChild(option);
                            }
                        } else if (item.badReason != "notraw") {
                            const ambiguous = nameToItems[item.name].length > 1;
                            let canDisambiguate = true;
                            if (ambiguous) {
                                for (const i2 of nameToItems[item.name]) {
                                    if (!i2.subtype) {
                                        canDisambiguate = false;
                                        break;
                                    }
                                }
                            }
                            if (!ambiguous || canDisambiguate || nameToItems[item.name][0] == item) {
                                const option = document.createElement("option");
                                option.setAttribute("data-key", item.uniqueName);
                                option.value = item.name;
                                if (ambiguous && canDisambiguate) {
                                    option.value += " (" + item.subtype + ")";
                                }
                                document.getElementById("datalist-" + type).appendChild(option);
                            } else {
                                //console.log(`Not adding ${item.uniqueName} to datalist for ${type} due to duplicate display name: ${item.name}`);
                            }
                        }
                        itemMap[item.uniqueName] = { ...item, type };
                    });
                }
            }
            resolve(itemMap);
        });
    });
}
fetchItemList();
// Assumes that caller revalidates authz
function updateInventory() {
    const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
    req.done(data => {
        window.itemListPromise.then(itemMap => {
            window.didInitialInventoryUpdate = true;
            const modularWeapons = [
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryLauncher",
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
                "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimarySniper",
                "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
                "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
                "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
                "/Lotus/Weapons/Ostron/Melee/LotusModularWeapon",
                "/Lotus/Weapons/Sentients/OperatorAmplifiers/OperatorAmpWeapon",
                "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit",
                "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
                "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
            ];
            // Populate inventory route
            ["RegularCredits", "PremiumCredits", "FusionPoints", "PrimeTokens"].forEach(currency => {
                document.getElementById(currency + "-owned").textContent = loc("currency_owned")
                    .split("|COUNT|")
                    .join(data[currency].toLocaleString());
            });
            [
                "Suits",
                "SpaceSuits",
                "Sentinels",
                "LongGuns",
                "Pistols",
                "Melee",
                "SpaceGuns",
                "SpaceMelee",
                "SentinelWeapons",
                "Hoverboards",
                "OperatorAmps",
                "MechSuits",
                "MoaPets",
                "KubrowPets"
            ].forEach(category => {
                document.getElementById(category + "-list").innerHTML = "";
                data[category].forEach(item => {
                    const tr = document.createElement("tr");
                    tr.setAttribute("data-item-type", item.ItemType);
                    {
                        const td = document.createElement("td");
                        td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
                        if (item.ItemName) {
                            const pipeIndex = item.ItemName.indexOf("|");
                            if (pipeIndex != -1) {
                                td.textContent = item.ItemName.substr(1 + pipeIndex) + " " + td.textContent;
                            } else {
                                td.textContent = item.ItemName + " (" + td.textContent + ")";
                            }
                        }
                        if (item.Details?.Name) {
                            td.textContent = item.Details.Name + " (" + td.textContent + ")";
                        }
                        if (item.ModularParts && item.ModularParts.length) {
                            td.textContent += " [";
                            item.ModularParts.forEach(part => {
                                td.textContent += " " + (itemMap[part]?.name ?? part) + ",";
                            });
                            td.textContent = td.textContent.slice(0, -1) + " ]";
                        }
                        tr.appendChild(td);
                    }
                    {
                        const td = document.createElement("td");
                        td.classList = "text-end text-nowrap";
                        let maxXP = Math.pow(uniqueLevelCaps[item.ItemType] ?? 30, 2) * 1000;
                        if (
                            category != "Suits" &&
                            category != "SpaceSuits" &&
                            category != "Sentinels" &&
                            category != "Hoverboards" &&
                            category != "MechSuits" &&
                            category != "MoaPets" &&
                            category != "KubrowPets"
                        ) {
                            maxXP /= 2;
                        }
                        let anyExaltedMissingXP = false;
                        if (item.XP >= maxXP && item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
                            for (const exaltedType of itemMap[item.ItemType].exalted) {
                                const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
                                if (exaltedItem) {
                                    const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
                                    if (exaltedItem.XP < exaltedCap) {
                                        anyExaltedMissingXP = true;
                                        break;
                                    }
                                }
                            }
                        }
                        if (item.XP < maxXP || anyExaltedMissingXP) {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                revalidateAuthz(() => {
                                    const promises = [];
                                    if (item.XP < maxXP) {
                                        promises.push(addGearExp(category, item.ItemId.$oid, maxXP - item.XP));
                                    }
                                    if ("exalted" in itemMap[item.ItemType]) {
                                        for (const exaltedType of itemMap[item.ItemType].exalted) {
                                            const exaltedItem = data.SpecialItems.find(x => x.ItemType == exaltedType);
                                            if (exaltedItem) {
                                                const exaltedCap =
                                                    itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
                                                if (exaltedItem.XP < exaltedCap) {
                                                    promises.push(
                                                        addGearExp(
                                                            "SpecialItems",
                                                            exaltedItem.ItemId.$oid,
                                                            exaltedCap - exaltedItem.XP
                                                        )
                                                    );
                                                }
                                            }
                                        }
                                    }
                                    Promise.all(promises).then(() => {
                                        updateInventory();
                                    });
                                });
                            };
                            a.title = loc("code_maxRank");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        if (!(item.Features & 8) && modularWeapons.includes(item.ItemType)) {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                gildEquipment(category, item.ItemId.$oid);
                            };
                            a.title = loc("code_gild");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        if (category == "KubrowPets") {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                maturePet(item.ItemId.$oid, !item.Details.IsPuppy);
                            };
                            if (item.Details.IsPuppy) {
                                a.title = loc("code_mature");
                                a.innerHTML = ``;
                            } else {
                                a.title = loc("code_unmature");
                                a.innerHTML = ``;
                            }
                            td.appendChild(a);
                        }
                        if (category == "Suits") {
                            const a = document.createElement("a");
                            a.href = "/webui/powersuit/" + item.ItemId.$oid;
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                const name = prompt(loc("code_renamePrompt"));
                                if (name !== null) {
                                    renameGear(category, item.ItemId.$oid, name);
                                }
                            };
                            a.title = loc("code_rename");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                document.getElementById(category + "-list").removeChild(tr);
                                disposeOfGear(category, item.ItemId.$oid);
                            };
                            a.title = loc("code_remove");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        tr.appendChild(td);
                    }
                    document.getElementById(category + "-list").appendChild(tr);
                });
            });
            document.getElementById("EvolutionProgress-list").innerHTML = "";
            data.EvolutionProgress?.forEach(item => {
                const datalist = document.getElementById("datalist-EvolutionProgress");
                const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
                if (optionToRemove) {
                    datalist.removeChild(optionToRemove);
                }
                const tr = document.createElement("tr");
                tr.setAttribute("data-item-type", item.ItemType);
                {
                    const td = document.createElement("td");
                    td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
                    if (item.Rank != null) {
                        td.textContent += " | " + loc("code_rank") + ": [" + item.Rank + "/5]";
                    }
                    tr.appendChild(td);
                }
                {
                    const td = document.createElement("td");
                    td.classList = "text-end text-nowrap";
                    if (item.Rank < 5) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: 5 }]);
                        };
                        a.title = loc("code_maxRank");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if ((permanentEvolutionWeapons.has(item.ItemType) && item.Rank > 0) || item.Rank > 1) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank - 1 }]);
                        };
                        a.title = loc("code_rankDown");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if (item.Rank < 5) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            setEvolutionProgress([{ ItemType: item.ItemType, Rank: item.Rank + 1 }]);
                        };
                        a.title = loc("code_rankUp");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    tr.appendChild(td);
                }
                document.getElementById("EvolutionProgress-list").appendChild(tr);
            });
            const datalistEvolutionProgress = document.querySelectorAll("#datalist-EvolutionProgress option");
            const formEvolutionProgress = document.querySelector('form[onsubmit*="doAcquireEvolution()"]');
            const giveAllQEvolutionProgress = document.querySelector(
                'button[onclick*="addMissingEvolutionProgress()"]'
            );
            if (datalistEvolutionProgress.length === 0) {
                formEvolutionProgress.classList.add("disabled");
                formEvolutionProgress.querySelector("input").disabled = true;
                formEvolutionProgress.querySelector("button").disabled = true;
                giveAllQEvolutionProgress.disabled = true;
            }
            if (data.CrewShipHarnesses?.length) {
                window.plexus = {
                    id: data.CrewShipHarnesses[0].ItemId.$oid,
                    xp: data.CrewShipHarnesses[0].XP
                };
            }
            // Populate quests route
            document.getElementById("QuestKeys-list").innerHTML = "";
            data.QuestKeys.forEach(item => {
                const tr = document.createElement("tr");
                tr.setAttribute("data-item-type", item.ItemType);
                const stage = item.Progress?.length ?? 0;
                const datalist = document.getElementById("datalist-QuestKeys");
                const optionToRemove = datalist.querySelector(`option[data-key="${item.ItemType}"]`);
                if (optionToRemove) {
                    datalist.removeChild(optionToRemove);
                }
                {
                    const td = document.createElement("td");
                    td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
                    if (!item.Completed) {
                        td.textContent +=
                            " | " + loc("code_stage") + ": [" + stage + "/" + itemMap[item.ItemType].chainLength + "]";
                    } else {
                        td.textContent += " | " + loc("code_completed");
                    }
                    if (data.ActiveQuest == item.ItemType) td.textContent += " | " + loc("code_active");
                    tr.appendChild(td);
                }
                {
                    const td = document.createElement("td");
                    td.classList = "text-end text-nowrap";
                    if (data.ActiveQuest == item.ItemType && !item.Completed) {
                        console.log(data.ActiveQuest);
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            doQuestUpdate("setInactive", item.ItemType);
                        };
                        a.title = loc("code_setInactive");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if (stage > 0) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            doQuestUpdate("resetKey", item.ItemType);
                        };
                        a.title = loc("code_reset");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if (itemMap[item.ItemType].chainLength > stage && !item.Completed) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            doQuestUpdate("completeKey", item.ItemType);
                        };
                        a.title = loc("code_complete");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if (stage > 0 && itemMap[item.ItemType].chainLength > 1) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            doQuestUpdate("prevStage", item.ItemType);
                        };
                        a.title = loc("code_prevStage");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    if (
                        itemMap[item.ItemType].chainLength > stage &&
                        !item.Completed &&
                        itemMap[item.ItemType].chainLength > 1
                    ) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            doQuestUpdate("nextStage", item.ItemType);
                        };
                        a.title = loc("code_nextStage");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            const option = document.createElement("option");
                            option.setAttribute("data-key", item.ItemType);
                            option.value = itemMap[item.ItemType]?.name ?? item.ItemType;
                            document.getElementById("datalist-QuestKeys").appendChild(option);
                            doQuestUpdate("deleteKey", item.ItemType);
                        };
                        a.title = loc("code_remove");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    tr.appendChild(td);
                }
                document.getElementById("QuestKeys-list").appendChild(tr);
            });
            const datalistQuestKeys = document.querySelectorAll("#datalist-QuestKeys option");
            const formQuestKeys = document.querySelector("form[onsubmit*=\"doAcquireEquipment('QuestKeys')\"]");
            const giveAllQuestButton = document.querySelector("button[onclick*=\"doBulkQuestUpdate('giveAll')\"]");
            if (datalistQuestKeys.length === 0) {
                formQuestKeys.classList.add("disabled");
                formQuestKeys.querySelector("input").disabled = true;
                formQuestKeys.querySelector("button").disabled = true;
                giveAllQuestButton.disabled = true;
            } else {
                formQuestKeys.classList.remove("disabled");
                formQuestKeys.querySelector("input").disabled = false;
                formQuestKeys.querySelector("button").disabled = false;
                giveAllQuestButton.disabled = false;
            }
            // Populate mods route
            document.getElementById("riven-list").innerHTML = "";
            document.getElementById("mods-list").innerHTML = "";
            data.Upgrades.forEach(item => {
                if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
                    const rivenType = item.ItemType.substr(32);
                    const fingerprint = JSON.parse(item.UpgradeFingerprint);
                    if (fingerprint.buffs) {
                        // Riven has been revealed?
                        const tr = document.createElement("tr");
                        {
                            const td = document.createElement("td");
                            td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
                            td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
                            td.innerHTML +=
                                " ▲ " +
                                fingerprint.buffs.length +
                                "";
                            td.innerHTML +=
                                " ▼ " +
                                fingerprint.curses.length +
                                "";
                            td.innerHTML +=
                                " ⟳ " +
                                (fingerprint.rerolls ?? 0) +
                                "";
                            tr.appendChild(td);
                        }
                        {
                            const td = document.createElement("td");
                            td.classList = "text-end text-nowrap";
                            {
                                const a = document.createElement("a");
                                a.href =
                                    "riven-tool/#" +
                                    encodeURIComponent(
                                        JSON.stringify({
                                            rivenType: rivenType,
                                            omegaAttenuation: 1,
                                            fingerprint: fingerprint
                                        })
                                    );
                                a.target = "_blank";
                                a.title = loc("code_viewStats");
                                a.innerHTML = ``;
                                td.appendChild(a);
                            }
                            {
                                const a = document.createElement("a");
                                a.href = "#";
                                a.onclick = function (event) {
                                    event.preventDefault();
                                    document.getElementById("riven-list").removeChild(tr);
                                    disposeOfGear("Upgrades", item.ItemId.$oid);
                                };
                                a.title = loc("code_remove");
                                a.innerHTML = ``;
                                td.appendChild(a);
                            }
                            tr.appendChild(td);
                        }
                        document.getElementById("riven-list").appendChild(tr);
                        return;
                    }
                }
                const tr = document.createElement("tr");
                const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
                const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
                {
                    const td = document.createElement("td");
                    td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
                    td.innerHTML += " ★ " + rank + "/" + maxRank + "";
                    tr.appendChild(td);
                }
                {
                    const td = document.createElement("td");
                    td.classList = "text-end text-nowrap";
                    if (rank < maxRank) {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
                        };
                        a.title = loc("code_maxRank");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    {
                        const a = document.createElement("a");
                        a.href = "#";
                        a.onclick = function (event) {
                            event.preventDefault();
                            document.getElementById("mods-list").removeChild(tr);
                            disposeOfGear("Upgrades", item.ItemId.$oid);
                        };
                        a.title = loc("code_remove");
                        a.innerHTML = ``;
                        td.appendChild(a);
                    }
                    tr.appendChild(td);
                }
                document.getElementById("mods-list").appendChild(tr);
            });
            data.RawUpgrades.forEach(item => {
                if (item.ItemCount > 0) {
                    const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
                    const tr = document.createElement("tr");
                    {
                        const td = document.createElement("td");
                        td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
                        td.innerHTML += " ★ 0/" + maxRank + "";
                        if (item.ItemCount > 1) {
                            td.innerHTML += " 🗍 " + parseInt(item.ItemCount) + "";
                        }
                        tr.appendChild(td);
                    }
                    {
                        const td = document.createElement("td");
                        td.classList = "text-end text-nowrap";
                        if (maxRank != 0) {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
                            };
                            a.title = loc("code_maxRank");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        {
                            const a = document.createElement("a");
                            a.href = "#";
                            a.onclick = function (event) {
                                event.preventDefault();
                                document.getElementById("mods-list").removeChild(tr);
                                disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
                            };
                            a.title = loc("code_remove");
                            a.innerHTML = ``;
                            td.appendChild(a);
                        }
                        tr.appendChild(td);
                    }
                    document.getElementById("mods-list").appendChild(tr);
                }
            });
            // Populate powersuit route
            if (single.getCurrentPath().substr(0, 17) == "/webui/powersuit/") {
                const oid = single.getCurrentPath().substr(17);
                const item = data.Suits.find(x => x.ItemId.$oid == oid);
                if (item) {
                    if (item.ItemName) {
                        $("#powersuit-route h3").text(item.ItemName);
                        $("#powersuit-route .text-body-secondary").text(itemMap[item.ItemType]?.name ?? item.ItemType);
                    } else {
                        $("#powersuit-route h3").text(itemMap[item.ItemType]?.name ?? item.ItemType);
                        $("#powersuit-route .text-body-secondary").text("");
                    }
                    const uniqueUpgrades = {};
                    (item.ArchonCrystalUpgrades ?? []).forEach(upgrade => {
                        if (upgrade && upgrade.UpgradeType) {
                            uniqueUpgrades[upgrade.UpgradeType] ??= 0;
                            uniqueUpgrades[upgrade.UpgradeType] += 1;
                        }
                    });
                    document.getElementById("crystals-list").innerHTML = "";
                    Object.entries(uniqueUpgrades).forEach(([upgradeType, count]) => {
                        const tr = document.createElement("tr");
                        {
                            const td = document.createElement("td");
                            td.textContent = count + "x " + (archonCrystalUpgrades[upgradeType] ?? upgradeType);
                            tr.appendChild(td);
                        }
                        {
                            const td = document.createElement("td");
                            td.classList = "text-end text-nowrap";
                            {
                                const a = document.createElement("a");
                                a.href = "#";
                                a.onclick = function (event) {
                                    event.preventDefault();
                                    doPopArchonCrystalUpgrade(upgradeType);
                                };
                                a.title = loc("code_remove");
                                a.innerHTML = ``;
                                td.appendChild(a);
                            }
                            tr.appendChild(td);
                        }
                        document.getElementById("crystals-list").appendChild(tr);
                    });
                } else {
                    single.loadRoute("/webui/inventory");
                }
            }
            document.getElementById("changeSyndicate").value = data.SupportedSyndicate ?? "";
            document.getElementById("Boosters-list").innerHTML = "";
            const now = Math.floor(Date.now() / 1000);
            data.Boosters.forEach(({ ItemType, ExpiryDate }) => {
                if (ExpiryDate < now) {
                    // Booster has expired, skip it
                    return;
                }
                const tr = document.createElement("tr");
                {
                    const td = document.createElement("td");
                    td.textContent = itemMap[ItemType]?.name ?? ItemType;
                    tr.appendChild(td);
                }
                {
                    const td = document.createElement("td");
                    td.classList = "text-end text-nowrap";
                    const timeString = formatDatetime("%Y-%m-%d %H:%M:%s", ExpiryDate * 1000);
                    const inlineForm = document.createElement("form");
                    const input = document.createElement("input");
                    inlineForm.style.display = "inline-block";
                    inlineForm.onsubmit = function (event) {
                        event.preventDefault();
                        doChangeBoosterExpiry(ItemType, input);
                    };
                    input.type = "datetime-local";
                    input.classList.add("form-control");
                    input.classList.add("form-control-sm");
                    input.value = timeString;
                    let changed = false;
                    input.onchange = function () {
                        changed = true;
                    };
                    input.onblur = function () {
                        if (changed) {
                            doChangeBoosterExpiry(ItemType, input);
                        }
                    };
                    inlineForm.appendChild(input);
                    td.appendChild(inlineForm);
                    const removeButton = document.createElement("a");
                    removeButton.title = loc("code_remove");
                    removeButton.innerHTML = ``;
                    removeButton.href = "#";
                    removeButton.onclick = function (event) {
                        event.preventDefault();
                        setBooster(ItemType, 0);
                    };
                    td.appendChild(removeButton);
                    tr.appendChild(td);
                }
                document.getElementById("Boosters-list").appendChild(tr);
            });
        });
    });
}
function getKey(input) {
    return document
        .getElementById(input.getAttribute("list"))
        .querySelector("[value='" + input.value.split("'").join("\\'") + "']")
        ?.getAttribute("data-key");
}
function doAcquireEquipment(category) {
    const uniqueName = getKey(document.getElementById("acquire-type-" + category));
    if (!uniqueName) {
        $("#acquire-type-" + category)
            .addClass("is-invalid")
            .focus();
        return;
    }
    revalidateAuthz(() => {
        const req = $.post({
            url: "/custom/addItems?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify([
                {
                    ItemType: uniqueName,
                    ItemCount: 1
                }
            ])
        });
        req.done(() => {
            document.getElementById("acquire-type-" + category).value = "";
            updateInventory();
        });
    });
}
function doAcquireModularEquipment(category, WeaponType) {
    let requiredParts;
    let Parts = [];
    switch (category) {
        case "HoverBoards":
            WeaponType = "/Lotus/Types/Vehicles/Hoverboard/HoverboardSuit";
            requiredParts = ["HB_DECK", "HB_ENGINE", "HB_FRONT", "HB_JET"];
            break;
        case "OperatorAmps":
            requiredParts = ["AMP_OCULUS", "AMP_CORE", "AMP_BRACE"];
            break;
        case "Melee":
            requiredParts = ["BLADE", "HILT", "HILT_WEIGHT"];
            break;
        case "LongGuns":
            requiredParts = ["GUN_BARREL", "GUN_PRIMARY_HANDLE", "GUN_CLIP"];
            break;
        case "Pistols":
            requiredParts = ["GUN_BARREL", "GUN_SECONDARY_HANDLE", "GUN_CLIP"];
            break;
        case "MoaPets":
            if (WeaponType == "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
                requiredParts = ["MOA_ENGINE", "MOA_PAYLOAD", "MOA_HEAD", "MOA_LEG"];
            } else {
                requiredParts = ["ZANUKA_BODY", "ZANUKA_HEAD", "ZANUKA_LEG", "ZANUKA_TAIL"];
            }
            break;
        case "KubrowPets":
            if (
                [
                    "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
                    "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
                    "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
                ].includes(WeaponType)
            ) {
                requiredParts = ["CATBROW_ANTIGEN", "CATBROW_MUTAGEN"];
            } else {
                requiredParts = ["KUBROW_ANTIGEN", "KUBROW_MUTAGEN"];
            }
            break;
    }
    requiredParts.forEach(part => {
        const partName = getKey(document.getElementById("acquire-type-" + category + "-" + part));
        if (partName) {
            Parts.push(partName);
        }
    });
    if (Parts.length != requiredParts.length) {
        let isFirstPart = true;
        requiredParts.forEach(part => {
            const partSelector = document.getElementById("acquire-type-" + category + "-" + part);
            if (!getKey(partSelector)) {
                if (isFirstPart) {
                    isFirstPart = false;
                    $("#acquire-type-" + category + "-" + part)
                        .addClass("is-invalid")
                        .focus();
                } else {
                    $("#acquire-type-" + category + "-" + part).addClass("is-invalid");
                }
            }
        });
    } else {
        const mapping = {
            LongGuns: {
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
                "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryShotgun",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimary",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam",
                "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
                    "/Lotus/Weapons/SolarisUnited/Primary/LotusModularPrimaryBeam"
            },
            Pistols: {
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelAPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
                "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelEgg/InfModularBarrelEggPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryShotgun",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelBPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelCPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondary",
                "/Lotus/Weapons/SolarisUnited/Secondary/SUModularSecondarySet1/Barrel/SUModularSecondaryBarrelDPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam",
                "/Lotus/Weapons/Infested/Pistols/InfKitGun/Barrels/InfBarrelBeam/InfModularBarrelBeamPart":
                    "/Lotus/Weapons/SolarisUnited/Secondary/LotusModularSecondaryBeam"
            },
            MoaPets: {
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadA":
                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetAPowerSuit",
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadB":
                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetBPowerSuit",
                "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetParts/ZanukaPetPartHeadC":
                    "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetCPowerSuit"
            }
        };
        Parts.forEach(part => {
            const categoryMap = mapping[category];
            if (categoryMap && categoryMap[part]) {
                WeaponType = categoryMap[part];
            }
        });
        if (category == "KubrowPets") Parts.unshift(WeaponType);
        revalidateAuthz(() => {
            const req = $.post({
                url: "/api/modularWeaponCrafting.php?" + window.authz,
                contentType: "application/octet-stream",
                data: JSON.stringify({
                    WeaponType,
                    Parts,
                    isWebUi: true
                })
            });
            req.done(() => {
                const mainInput = document.getElementById("acquire-type-" + category);
                if (mainInput) {
                    mainInput.value = "";
                    if (category === "MoaPets") {
                        const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
                        const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
                        modularFieldsZanuka.style.display = "none";
                        modularFieldsMoa.style.display = "none";
                    } else if (category === "KubrowPets") {
                        const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
                        const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
                        modularFieldsCatbrow.style.display = "none";
                        modularFieldsKubrow.style.display = "none";
                    } else {
                        const modularFields = document.getElementById("modular-" + category);
                        modularFields.style.display = "none";
                    }
                }
                requiredParts.forEach(part => {
                    document.getElementById("acquire-type-" + category + "-" + part).value = "";
                });
                updateInventory();
            });
        });
    }
}
function doAcquireEvolution() {
    const uniqueName = getKey(document.getElementById("acquire-type-EvolutionProgress"));
    if (!uniqueName) {
        $("#acquire-type-EvolutionProgress").addClass("is-invalid").focus();
        return;
    }
    setEvolutionProgress([{ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 }]);
}
$("input[list]").on("input", function () {
    $(this).removeClass("is-invalid");
});
function dispatchAddItemsRequestsBatch(requests) {
    revalidateAuthz(() => {
        const req = $.post({
            url: "/custom/addItems?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify(requests)
        });
        req.done(() => {
            updateInventory();
        });
    });
}
function addMissingEquipment(categories) {
    const requests = [];
    categories.forEach(category => {
        document.querySelectorAll("#datalist-" + category + " option").forEach(elm => {
            if (
                !document.querySelector(
                    "#" + category + "-list [data-item-type='" + elm.getAttribute("data-key") + "']"
                )
            ) {
                if (!webUiModularWeapons.includes(elm.getAttribute("data-key"))) {
                    requests.push({ ItemType: elm.getAttribute("data-key"), ItemCount: 1 });
                }
            }
        });
    });
    if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
        dispatchAddItemsRequestsBatch(requests);
    }
}
function addMissingEvolutionProgress() {
    const requests = [];
    document.querySelectorAll("#datalist-EvolutionProgress option").forEach(elm => {
        const uniqueName = elm.getAttribute("data-key");
        requests.push({ ItemType: uniqueName, Rank: permanentEvolutionWeapons.has(uniqueName) ? 0 : 1 });
    });
    if (requests.length != 0 && window.confirm(loc("code_addItemsConfirm").split("|COUNT|").join(requests.length))) {
        setEvolutionProgress(requests);
    }
}
function maxRankAllEvolutions() {
    revalidateAuthz(() => {
        const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
        req.done(data => {
            const requests = [];
            data.EvolutionProgress.forEach(item => {
                if (item.Rank < 5) {
                    requests.push({
                        ItemType: item.ItemType,
                        Rank: 5
                    });
                }
            });
            if (Object.keys(requests).length > 0) {
                return setEvolutionProgress(requests);
            }
            toast(loc("code_noEquipmentToRankUp"));
        });
    });
}
function maxRankAllEquipment(categories) {
    revalidateAuthz(() => {
        const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
        req.done(data => {
            window.itemListPromise.then(itemMap => {
                const batchData = {};
                categories.forEach(category => {
                    data[category].forEach(item => {
                        const maxXP =
                            category === "Suits" ||
                            category === "SpaceSuits" ||
                            category === "Sentinels" ||
                            category === "Hoverboards"
                                ? 1_600_000
                                : 800_000;
                        if (item.XP < maxXP) {
                            if (!batchData[category]) {
                                batchData[category] = [];
                            }
                            batchData[category].push({
                                ItemId: { $oid: item.ItemId.$oid },
                                XP: maxXP
                            });
                        }
                        if (item.ItemType in itemMap && "exalted" in itemMap[item.ItemType]) {
                            for (const exaltedType of itemMap[item.ItemType].exalted) {
                                const exaltedItem = data["SpecialItems"].find(x => x.ItemType == exaltedType);
                                if (exaltedItem) {
                                    const exaltedCap = itemMap[exaltedType]?.type == "weapons" ? 800_000 : 1_600_000;
                                    if (exaltedItem.XP < exaltedCap) {
                                        batchData["SpecialItems"] ??= [];
                                        batchData["SpecialItems"].push({
                                            ItemId: { $oid: exaltedItem.ItemId.$oid },
                                            XP: exaltedCap
                                        });
                                    }
                                }
                            }
                        }
                    });
                });
                if (Object.keys(batchData).length > 0) {
                    return sendBatchGearExp(batchData);
                }
                toast(loc("code_noEquipmentToRankUp"));
            });
        });
    });
}
// Assumes that caller revalidates authz
function addGearExp(category, oid, xp) {
    const data = {};
    data[category] = [
        {
            ItemId: { $oid: oid },
            XP: xp
        }
    ];
    return new Promise((resolve, reject) => {
        $.post({
            url: "/custom/addXp?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify(data)
        })
            .done(resolve)
            .fail(reject);
    });
}
function sendBatchGearExp(data) {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/addXp?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify(data)
        }).done(() => {
            toast(loc("code_succRankUp"));
            updateInventory();
        });
    });
}
function renameGear(category, oid, name) {
    revalidateAuthz(() => {
        if (category == "KubrowPets") {
            $.post({
                url: "/api/renamePet.php?" + window.authz + "&webui=1",
                contentType: "text/plain",
                data: JSON.stringify({
                    petId: oid,
                    name: name
                })
            }).done(function () {
                updateInventory();
            });
        } else {
            $.post({
                url: "/api/nameWeapon.php?" + window.authz + "&Category=" + category + "&ItemId=" + oid + "&webui=1",
                contentType: "text/plain",
                data: JSON.stringify({
                    ItemName: name
                })
            }).done(function () {
                updateInventory();
            });
        }
    });
}
function disposeOfGear(category, oid) {
    if (category == "KubrowPets") {
        revalidateAuthz(() => {
            $.post({
                url: "/api/releasePet.php?" + window.authz,
                contentType: "application/octet-stream",
                data: JSON.stringify({
                    Recipe: "webui",
                    petId: oid
                })
            });
        });
    } else {
        const data = {
            SellCurrency: "SC_RegularCredits",
            SellPrice: 0,
            Items: {}
        };
        data.Items[category] = [
            {
                String: oid,
                Count: 0
            }
        ];
        revalidateAuthz(() => {
            $.post({
                url: "/api/sell.php?" + window.authz,
                contentType: "text/plain",
                data: JSON.stringify(data)
            });
        });
    }
}
function disposeOfItems(category, type, count) {
    const data = {
        SellCurrency: "SC_RegularCredits",
        SellPrice: 0,
        Items: {}
    };
    data.Items[category] = [
        {
            String: type,
            Count: count
        }
    ];
    revalidateAuthz(() => {
        $.post({
            url: "/api/sell.php?" + window.authz,
            contentType: "text/plain",
            data: JSON.stringify(data)
        });
    });
}
function gildEquipment(category, oid) {
    revalidateAuthz(() => {
        $.post({
            url: "/api/gildWeapon.php?" + window.authz + "&ItemId=" + oid + "&Category=" + category,
            contentType: "application/octet-stream",
            data: JSON.stringify({
                Recipe: "webui"
            })
        }).done(function () {
            updateInventory();
        });
    });
}
function maturePet(oid, revert) {
    revalidateAuthz(() => {
        $.post({
            url: "/api/maturePet.php?" + window.authz,
            contentType: "application/octet-stream",
            data: JSON.stringify({
                petId: oid,
                revert
            })
        }).done(function () {
            updateInventory();
        });
    });
}
function setEvolutionProgress(requests) {
    revalidateAuthz(() => {
        const req = $.post({
            url: "/custom/setEvolutionProgress?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify(requests)
        });
        req.done(() => {
            updateInventory();
        });
    });
}
function doAcquireMiscItems() {
    const uniqueName = getKey(document.getElementById("miscitem-type"));
    if (!uniqueName) {
        $("#miscitem-type").addClass("is-invalid").focus();
        return;
    }
    const count = parseInt($("#miscitem-count").val());
    if (count != 0) {
        revalidateAuthz(() => {
            $.post({
                url: "/custom/addItems?" + window.authz,
                contentType: "application/json",
                data: JSON.stringify([
                    {
                        ItemType: uniqueName,
                        ItemCount: count
                    }
                ])
            }).done(function () {
                if (count > 0) {
                    toast(loc("code_succAdded"));
                } else {
                    toast(loc("code_succRemoved"));
                }
            });
        });
    }
}
function doAcquireRiven() {
    let fingerprint;
    try {
        fingerprint = JSON.parse($("#addriven-fingerprint").val());
        if (typeof fingerprint !== "object") {
            fingerprint = JSON.parse(fingerprint);
        }
    } catch (e) {
        /* empty */
    }
    if (
        typeof fingerprint !== "object" ||
        !("compat" in fingerprint) ||
        !("pol" in fingerprint) ||
        !("buffs" in fingerprint)
    ) {
        $("#addriven-fingerprint").addClass("is-invalid").focus();
        return;
    }
    const uniqueName = "/Lotus/Upgrades/Mods/Randomized/" + $("#addriven-type").val();
    revalidateAuthz(() => {
        // Add riven type to inventory
        $.post({
            url: "/custom/addItems?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify([
                {
                    ItemType: uniqueName,
                    ItemCount: 1
                }
            ])
        }).done(function () {
            // Get riven's assigned id
            $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(data => {
                for (const rawUpgrade of data.RawUpgrades) {
                    if (rawUpgrade.ItemType === uniqueName) {
                        // Add fingerprint to riven
                        $.post({
                            url: "/api/artifacts.php?" + window.authz,
                            contentType: "text/plain",
                            data: JSON.stringify({
                                Upgrade: {
                                    ItemType: uniqueName,
                                    UpgradeFingerprint: JSON.stringify(fingerprint),
                                    ItemId: rawUpgrade.LastAdded
                                },
                                LevelDiff: 0,
                                Cost: 0,
                                FusionPointCost: 0
                            })
                        }).done(function () {
                            $("#addriven-fingerprint").val("");
                            updateInventory();
                        });
                        break;
                    }
                }
            });
        });
    });
}
$("#addriven-fingerprint").on("input", () => {
    $("#addriven-fingerprint").removeClass("is-invalid");
});
function setFingerprint(ItemType, ItemId, fingerprint) {
    revalidateAuthz(() => {
        $.post({
            url: "/api/artifacts.php?" + window.authz,
            contentType: "text/plain",
            data: JSON.stringify({
                Upgrade: {
                    ItemType,
                    ItemId,
                    UpgradeFingerprint: JSON.stringify(fingerprint)
                },
                LevelDiff: 0,
                Cost: 0,
                FusionPointCost: 0
            })
        }).done(function () {
            updateInventory();
        });
    });
}
function doAcquireMod() {
    const uniqueName = getKey(document.getElementById("mod-to-acquire"));
    if (!uniqueName) {
        $("#mod-to-acquire").addClass("is-invalid").focus();
        return;
    }
    const count = parseInt($("#mod-count").val());
    if (count != 0) {
        revalidateAuthz(() => {
            $.post({
                url: "/custom/addItems?" + window.authz,
                contentType: "application/json",
                data: JSON.stringify([
                    {
                        ItemType: uniqueName,
                        ItemCount: count
                    }
                ])
            }).done(function () {
                if (count > 0) {
                    toast(loc("code_succAdded"));
                } else {
                    toast(loc("code_succRemoved"));
                }
                updateInventory();
            });
        });
    }
}
const uiConfigs = [...$("#server-settings input[id]")].map(x => x.id);
for (const id of uiConfigs) {
    const elm = document.getElementById(id);
    if (elm.type == "checkbox") {
        elm.onchange = function () {
            $.post({
                url: "/custom/config?" + window.authz,
                contentType: "application/json",
                data: JSON.stringify({ key: id, value: this.checked })
            }).then(() => {
                if (["infiniteCredits", "infinitePlatinum", "infiniteEndo", "infiniteRegalAya"].indexOf(id) != -1) {
                    updateInventory();
                }
            });
        };
    }
}
function doSaveConfig(id) {
    const elm = document.getElementById(id);
    $.post({
        url: "/custom/config?" + window.authz,
        contentType: "application/json",
        data: JSON.stringify({ key: id, value: parseInt(elm.value) })
    });
}
// Cheats route
single.getRoute("/webui/cheats").on("beforeload", function () {
    let interval;
    interval = setInterval(() => {
        if (window.authz) {
            clearInterval(interval);
            fetch("/custom/config?" + window.authz).then(async res => {
                if (res.status == 200) {
                    //window.is_admin = true;
                    $("#server-settings-no-perms").addClass("d-none");
                    $("#server-settings").removeClass("d-none");
                    res.json().then(json =>
                        Object.entries(json).forEach(entry => {
                            const [key, value] = entry;
                            var x = document.getElementById(`${key}`);
                            if (x != null) {
                                if (x.type == "checkbox") {
                                    x.checked = value;
                                } else if (x.type == "number") {
                                    x.setAttribute("value", `${value}`);
                                }
                            }
                        })
                    );
                } else {
                    if ((await res.text()) == "Log-in expired") {
                        revalidateAuthz(() => {
                            if (single.getCurrentPath() == "/webui/cheats") {
                                single.loadRoute("/webui/cheats");
                            }
                        });
                    } else {
                        //window.is_admin = false;
                        $("#server-settings-no-perms").removeClass("d-none");
                        $("#server-settings").addClass("d-none");
                    }
                }
            });
        }
    }, 10);
});
function doUnlockAllFocusSchools() {
    revalidateAuthz(() => {
        $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1").done(async data => {
            const missingFocusUpgrades = {
                "/Lotus/Upgrades/Focus/Attack/AttackFocusAbility": true,
                "/Lotus/Upgrades/Focus/Tactic/TacticFocusAbility": true,
                "/Lotus/Upgrades/Focus/Ward/WardFocusAbility": true,
                "/Lotus/Upgrades/Focus/Defense/DefenseFocusAbility": true,
                "/Lotus/Upgrades/Focus/Power/PowerFocusAbility": true
            };
            if (data.FocusUpgrades) {
                for (const focusUpgrade of data.FocusUpgrades) {
                    if (focusUpgrade.ItemType in missingFocusUpgrades) {
                        delete missingFocusUpgrades[focusUpgrade.ItemType];
                    }
                }
            }
            for (const upgradeType of Object.keys(missingFocusUpgrades)) {
                await unlockFocusSchool(upgradeType);
            }
            if (Object.keys(missingFocusUpgrades).length == 0) {
                toast(loc("code_focusAllUnlocked"));
            } else {
                toast(loc("code_focusUnlocked").split("|COUNT|").join(Object.keys(missingFocusUpgrades).length));
            }
        });
    });
}
function unlockFocusSchool(upgradeType) {
    return new Promise(resolve => {
        // Deselect current FocusAbility so we will be able to unlock the way for free
        $.post({
            url: "/api/focus.php?" + window.authz + "&op=5",
            contentType: "text/plain",
            data: "{}"
        }).done(function () {
            // Unlock the way now
            $.post({
                url: "/api/focus.php?" + window.authz + "&op=2",
                contentType: "text/plain",
                data: JSON.stringify({
                    FocusType: upgradeType
                })
            }).done(function () {
                resolve();
            });
        });
    });
}
function doHelminthUnlockAll() {
    revalidateAuthz(() => {
        $.post("/api/infestedFoundry.php?" + window.authz + "&mode=custom_unlockall");
    });
}
function doIntrinsicsUnlockAll() {
    revalidateAuthz(() => {
        $.get("/custom/unlockAllIntrinsics?" + window.authz);
    });
}
function doAddAllMods() {
    let modsAll = new Set();
    for (const child of document.getElementById("datalist-mods").children) {
        modsAll.add(child.getAttribute("data-key"));
    }
    modsAll.delete("/Lotus/Upgrades/Mods/Fusers/LegendaryModFuser");
    revalidateAuthz(() => {
        const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
        req.done(data => {
            for (const modOwned of data.RawUpgrades) {
                if ((modOwned.ItemCount ?? 1) > 0) {
                    modsAll.delete(modOwned.ItemType);
                }
            }
            modsAll = Array.from(modsAll);
            if (
                modsAll.length != 0 &&
                window.confirm(loc("code_addModsConfirm").split("|COUNT|").join(modsAll.length))
            ) {
                $.post({
                    url: "/custom/addItems?" + window.authz,
                    contentType: "application/json",
                    data: JSON.stringify(
                        modsAll.map(mod => ({
                            ItemType: mod,
                            ItemCount: 21 // To fully upgrade certain arcanes
                        }))
                    )
                }).done(function () {
                    updateInventory();
                });
            }
        });
    });
}
function doRemoveUnrankedMods() {
    revalidateAuthz(() => {
        const req = $.get("/api/inventory.php?" + window.authz + "&xpBasedLevelCapDisabled=1");
        req.done(inventory => {
            window.itemListPromise.then(itemMap => {
                $.post({
                    url: "/api/sell.php?" + window.authz,
                    contentType: "text/plain",
                    data: JSON.stringify({
                        SellCurrency: "SC_RegularCredits",
                        SellPrice: 0,
                        Items: {
                            Upgrades: inventory.RawUpgrades.filter(
                                x => !itemMap[x.ItemType]?.parazon && x.ItemCount > 0
                            ).map(x => ({ String: x.ItemType, Count: x.ItemCount }))
                        }
                    })
                }).done(function () {
                    updateInventory();
                });
            });
        });
    });
}
function doAddMissingMaxRankMods() {
    revalidateAuthz(() => {
        fetch("/custom/addMissingMaxRankMods?" + window.authz).then(() => {
            updateInventory();
        });
    });
}
// Powersuit Route
single.getRoute("#powersuit-route").on("beforeload", function () {
    this.element.querySelector("h3").textContent = "Loading...";
    if (window.didInitialInventoryUpdate) {
        updateInventory();
    }
});
function doPushArchonCrystalUpgrade() {
    const uniqueName = getKey(document.querySelector("[list='datalist-archonCrystalUpgrades']"));
    if (!uniqueName) {
        $("[list='datalist-archonCrystalUpgrades']").addClass("is-invalid").focus();
        return;
    }
    revalidateAuthz(() => {
        $.get(
            "/custom/pushArchonCrystalUpgrade?" +
                window.authz +
                "&oid=" +
                single.getCurrentPath().substr(17) +
                "&type=" +
                uniqueName +
                "&count=" +
                $("#archon-crystal-add-count").val()
        ).done(function () {
            $("[list='datalist-archonCrystalUpgrades']").val("");
            updateInventory();
        });
    });
}
function doPopArchonCrystalUpgrade(type) {
    revalidateAuthz(() => {
        $.get(
            "/custom/popArchonCrystalUpgrade?" +
                window.authz +
                "&oid=" +
                single.getCurrentPath().substr(17) +
                "&type=" +
                type
        ).done(function () {
            updateInventory();
        });
    });
}
function doImport() {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/import?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify({
                inventory: JSON.parse($("#import-inventory").val())
            })
        }).then(function () {
            toast(loc("code_succImport"));
            updateInventory();
        });
    });
}
function doChangeSupportedSyndicate() {
    const uniqueName = document.getElementById("changeSyndicate").value;
    revalidateAuthz(() => {
        $.get("/api/setSupportedSyndicate.php?" + window.authz + "&syndicate=" + uniqueName).done(function () {
            updateInventory();
        });
    });
}
function doAddCurrency(currency) {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/addCurrency?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify({
                currency,
                delta: document.getElementById(currency + "-delta").valueAsNumber
            })
        }).then(function () {
            updateInventory();
        });
    });
}
function doQuestUpdate(operation, itemType) {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/manageQuests?" + window.authz + "&operation=" + operation + "&itemType=" + itemType,
            contentType: "application/json"
        }).then(function () {
            updateInventory();
        });
    });
}
function doBulkQuestUpdate(operation) {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/manageQuests?" + window.authz + "&operation=" + operation,
            contentType: "application/json"
        }).then(function () {
            updateInventory();
        });
    });
}
function toast(text) {
    const toast = document.createElement("div");
    toast.className = "toast align-items-center text-bg-primary border-0";
    const div = document.createElement("div");
    div.className = "d-flex";
    const body = document.createElement("div");
    body.className = "toast-body";
    body.textContent = text;
    div.appendChild(body);
    const button = document.createElement("button");
    button.className = "btn-close btn-close-white me-2 m-auto";
    button.setAttribute("data-bs-dismiss", "toast");
    div.appendChild(button);
    toast.appendChild(div);
    new bootstrap.Toast(document.querySelector(".toast-container").appendChild(toast)).show();
}
function handleModularSelection(category) {
    const itemType = getKey(document.getElementById("acquire-type-" + category));
    if (webUiModularWeapons.includes(itemType)) {
        doAcquireModularEquipment(category, itemType);
    } else {
        doAcquireEquipment(category);
    }
}
{
    const supportedModularInventoryCategory = ["OperatorAmps", "Melee", "LongGuns", "Pistols", "MoaPets", "KubrowPets"];
    supportedModularInventoryCategory.forEach(inventoryCategory => {
        document.getElementById("acquire-type-" + inventoryCategory).addEventListener("input", function () {
            const modularFields = document.getElementById("modular-" + inventoryCategory);
            const modularFieldsMoa = document.getElementById("modular-MoaPets-Moa");
            const modularFieldsZanuka = document.getElementById("modular-MoaPets-Zanuka");
            const modularFieldsCatbrow = document.getElementById("modular-KubrowPets-Catbrow");
            const modularFieldsKubrow = document.getElementById("modular-KubrowPets-Kubrow");
            const key = getKey(this);
            if (webUiModularWeapons.includes(key)) {
                if (inventoryCategory === "MoaPets") {
                    if (key === "/Lotus/Types/Friendly/Pets/ZanukaPets/ZanukaPetPowerSuit") {
                        modularFieldsMoa.style.display = "none";
                        modularFieldsZanuka.style.display = "";
                    } else if (key === "/Lotus/Types/Friendly/Pets/MoaPets/MoaPetPowerSuit") {
                        modularFieldsMoa.style.display = "";
                        modularFieldsZanuka.style.display = "none";
                    }
                } else if (inventoryCategory === "KubrowPets") {
                    if (
                        [
                            "/Lotus/Types/Friendly/Pets/CreaturePets/VulpineInfestedCatbrowPetPowerSuit",
                            "/Lotus/Types/Friendly/Pets/CreaturePets/HornedInfestedCatbrowPetPowerSuit",
                            "/Lotus/Types/Friendly/Pets/CreaturePets/ArmoredInfestedCatbrowPetPowerSuit"
                        ].includes(key)
                    ) {
                        modularFieldsCatbrow.style.display = "";
                        modularFieldsKubrow.style.display = "none";
                    } else if (
                        [
                            "/Lotus/Types/Friendly/Pets/CreaturePets/VizierPredatorKubrowPetPowerSuit",
                            "/Lotus/Types/Friendly/Pets/CreaturePets/PharaohPredatorKubrowPetPowerSuit",
                            "/Lotus/Types/Friendly/Pets/CreaturePets/MedjayPredatorKubrowPetPowerSuit"
                        ].includes(key)
                    ) {
                        modularFieldsCatbrow.style.display = "none";
                        modularFieldsKubrow.style.display = "";
                    } else {
                        modularFieldsCatbrow.style.display = "none";
                        modularFieldsKubrow.style.display = "none";
                    }
                } else {
                    modularFields.style.display = "";
                }
            } else {
                if (inventoryCategory === "MoaPets") {
                    modularFieldsZanuka.style.display = "none";
                    modularFieldsMoa.style.display = "none";
                } else if (inventoryCategory === "KubrowPets") {
                    modularFieldsCatbrow.style.display = "none";
                    modularFieldsKubrow.style.display = "none";
                } else {
                    modularFields.style.display = "none";
                }
            }
        });
    });
}
function setBooster(ItemType, ExpiryDate, callback) {
    revalidateAuthz(() => {
        $.post({
            url: "/custom/setBooster?" + window.authz,
            contentType: "application/json",
            data: JSON.stringify([
                {
                    ItemType,
                    ExpiryDate
                }
            ])
        }).done(function () {
            updateInventory();
            if (callback) callback();
        });
    });
}
function doAcquireBoosters() {
    const uniqueName = getKey(document.getElementById("acquire-type-Boosters"));
    if (!uniqueName) {
        $("#acquire-type-Boosters").addClass("is-invalid").focus();
        return;
    }
    const ExpiryDate = Date.now() / 1000 + 3 * 24 * 60 * 60; // default 3 days
    setBooster(uniqueName, ExpiryDate, () => {
        $("#acquire-type-Boosters").val("");
        updateInventory();
    });
}
function doChangeBoosterExpiry(ItemType, ExpiryDateInput) {
    console.log("Changing booster expiry for", ItemType, "to", ExpiryDateInput.value);
    // cast local datetime string to unix timestamp
    const ExpiryDate = new Date(ExpiryDateInput.value).getTime() / 1000;
    if (isNaN(ExpiryDate)) {
        ExpiryDateInput.addClass("is-invalid").focus();
        return false;
    }
    setBooster(ItemType, ExpiryDate);
    return true;
}
function formatDatetime(fmt, date) {
    if (typeof date === "number") date = new Date(date);
    return fmt.replace(/(%[yY]|%m|%[Dd]|%H|%h|%M|%[Ss]|%[Pp])/g, match => {
        switch (match) {
            case "%Y":
                return date.getFullYear().toString();
            case "%y":
                return date.getFullYear().toString().slice(-2);
            case "%m":
                return (date.getMonth() + 1).toString().padStart(2, "0");
            case "%D":
            case "%d":
                return date.getDate().toString().padStart(2, "0");
            case "%H":
                return date.getHours().toString().padStart(2, "0");
            case "%h":
                return (date.getHours() % 12).toString().padStart(2, "0");
            case "%M":
                return date.getMinutes().toString().padStart(2, "0");
            case "%S":
            case "%s":
                return date.getSeconds().toString().padStart(2, "0");
            case "%P":
            case "%p":
                return date.getHours() < 12 ? "am" : "pm";
            default:
                return match;
        }
    });
}
const calls_in_flight = new Set();
async function debounce(func, ...args) {
    calls_in_flight.add(func);
    await func(...args);
    calls_in_flight.delete(func);
}
async function doMaxPlexus() {
    if ((window.plexus?.xp ?? 0) < 900_000) {
        await addGearExp("CrewShipHarnesses", window.plexus.id, 900_000 - window.plexus.xp);
        window.plexus.xp = 900_000;
        toast(loc("code_succRankUp"));
    } else {
        toast(loc("code_noEquipmentToRankUp"));
    }
}