ICTime

Show item time cost and related helper info in Milky Way Idle.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         ICTime
// @name:en      ICTime
// @name:zh-CN   ICTime 时间计算
// @namespace    http://tampermonkey.net/
// @version      1.0.3
// @description  Show item time cost and related helper info in Milky Way Idle.
// @description:en  Show item time cost and related helper info in Milky Way Idle.
// @description:zh-CN  在 Milky Way Idle 中显示物品时间成本及相关辅助信息。
// @author       dakonglong
// @license      MIT
// @match        https://www.milkywayidle.com/*
// @match        https://test.milkywayidle.com/*
// @match        https://www.milkywayidlecn.com/*
// @match        https://test.milkywayidlecn.com/*
// @match        https://shykai.github.io/MWICombatSimulatorTest/dist/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/libs/lz-string.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// ==/UserScript==

(function () {
    "use strict";

    const SIMULATOR_IMPORT_STORAGE_KEY = "ICTime_SimulatorImport_v1";
    const SIMULATOR_IMPORT_REQUEST_KEY = "ICTime_SimulatorImport_Request_v1";
    const SIMULATOR_SNAPSHOT_EVENT = "__ICTIME_SIMULATOR_SNAPSHOT__";

    async function sharedGetValue(key, fallbackValue) {
        try {
            if (typeof GM_getValue === "function") {
                const value = GM_getValue(key, fallbackValue);
                return value instanceof Promise ? await value : value;
            }
        } catch (_error) {
            // Fall back to localStorage.
        }
        try {
            const raw = localStorage.getItem(key);
            return raw ? JSON.parse(raw) : fallbackValue;
        } catch (_error) {
            return fallbackValue;
        }
    }

    async function sharedSetValue(key, value) {
        try {
            if (typeof GM_setValue === "function") {
                const result = GM_setValue(key, value);
                if (result instanceof Promise) {
                    await result;
                }
                return;
            }
        } catch (_error) {
            // Fall back to localStorage.
        }
        localStorage.setItem(key, JSON.stringify(value));
    }

    function dispatchNativeChange(element) {
        if (!element) {
            return;
        }
        element.dispatchEvent(new Event("input", { bubbles: true }));
        element.dispatchEvent(new Event("change", { bubbles: true }));
    }

    function findSimulatorResultRoot() {
        const heading = Array.from(document.querySelectorAll("div,span,b,h1,h2,h3,h4,h5,h6,button"))
            .find((node) => (node.textContent || "").trim() === "模拟结果");
        let node = heading instanceof HTMLElement ? heading.parentElement : null;
        let depth = 0;
        while (node && depth < 6) {
            const text = (node.textContent || "").replace(/\s+/g, " ");
            if (text.includes("每小时使用的消耗品") && (text.includes("非随机掉落物") || text.includes("掉落物合计"))) {
                return node;
            }
            node = node.parentElement;
            depth += 1;
        }
        return Array.from(document.querySelectorAll("div")).find((candidate) => {
            const text = (candidate.textContent || "").replace(/\s+/g, " ");
            return text.includes("模拟结果") && text.includes("每小时使用的消耗品") && (text.includes("非随机掉落物") || text.includes("掉落物合计"));
        }) || null;
    }

    function findSimulatorSelectByOptions(expectedOptions) {
        return Array.from(document.querySelectorAll("select")).find((select) => {
            const texts = Array.from(select.options).map((option) => (option.textContent || "").trim());
            return expectedOptions.every((optionText) => texts.includes(optionText));
        }) || null;
    }

    function findLabeledValue(root, labelText) {
        const labelNode = Array.from(root?.querySelectorAll("div") || []).find((node) => (node.textContent || "").trim() === labelText);
        if (!labelNode?.parentElement) {
            return "";
        }
        const siblings = Array.from(labelNode.parentElement.children).filter((node) => node instanceof HTMLElement);
        const valueNode = siblings[siblings.length - 1];
        return valueNode && valueNode !== labelNode ? (valueNode.textContent || "").trim() : "";
    }

    function parseSimulatorConsumables(root) {
        const labelNode = Array.from(root?.querySelectorAll("div") || []).find((node) => (node.textContent || "").trim() === "每小时使用的消耗品");
        const section = labelNode?.nextElementSibling;
        if (!section) {
            return [];
        }
        return Array.from(section.children || [])
            .map((row) => {
                const children = Array.from(row.children || []);
                const name = (children[0]?.textContent || "").trim();
                const perHour = Number((children[1]?.textContent || "").trim() || 0);
                return name ? { name, perHour } : null;
            })
            .filter(Boolean);
    }

    function findSimulatorDurationHours() {
        const input = Array.from(document.querySelectorAll('input[type="number"]')).find((element) => {
            const nearby = ((element.parentElement?.textContent || "") + " " + (element.closest("div")?.textContent || ""))
                .replace(/\s+/g, " ")
                .trim();
            return nearby === "小时" || nearby.startsWith("小时 ");
        });
        return parseNonNegativeDecimal(input?.value || 24) || 24;
    }

    function parseSimulatorNonRandomDrops(root) {
        const heading = Array.from(root?.querySelectorAll("h1, h2, h3, h4, h5, h6, button, div, span, b") || [])
            .find((node) => (node.textContent || "").trim() === "非随机掉落物");
        const accordionItem = heading?.closest(".accordion-item");
        const body = accordionItem?.querySelector(".accordion-body");
        if (!body) {
            return [];
        }
        const rows = body.querySelectorAll("#noRngDrops > .row, #noRngDrops .row");
        return Array.from(rows || [])
            .map((row) => {
                const children = Array.from(row.children || []).filter((node) => node instanceof HTMLElement);
                const name = (children[0]?.textContent || "").trim();
                const count = parseNonNegativeDecimal(children[1]?.textContent || 0);
                return name ? { name, count } : null;
            })
            .filter(Boolean);
    }

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

    function installSimulatorPageBridge() {
        if (document.getElementById("ictime-simulator-page-bridge")) {
            return;
        }
        const script = document.createElement("script");
        script.id = "ictime-simulator-page-bridge";
        script.textContent = `
            (function () {
                if (window.__ICTIME_SIMULATOR_PAGE_BRIDGE__) {
                    return;
                }
                window.__ICTIME_SIMULATOR_PAGE_BRIDGE__ = true;

                const EVENT_NAME = ${JSON.stringify(SIMULATOR_SNAPSHOT_EVENT)};

                function parseNumber(value) {
                    const text = String(value == null ? "" : value).trim();
                    if (!text) {
                        return 0;
                    }
                    let normalized = text.replace(/\\s+/g, "");
                    if (normalized.includes(",") && normalized.includes(".")) {
                        normalized = normalized.replace(/,/g, "");
                    } else if (normalized.includes(",")) {
                        normalized = normalized.replace(/,/g, ".");
                    }
                    const number = Number(normalized);
                    return Number.isFinite(number) ? number : 0;
                }

                function parseNoRngDropsFromDom() {
                    const rows = document.querySelectorAll("#noRngDrops > .row, #noRngDrops .row");
                    return Array.from(rows || []).map((row) => {
                        const cells = Array.from(row.children || []).filter((node) => node instanceof HTMLElement);
                        const name = (cells[0]?.textContent || "").trim();
                        const count = parseNumber(cells[1]?.textContent || 0);
                        return name ? { name, count } : null;
                    }).filter(Boolean);
                }

                function computeAverageMinutes(simResult) {
                    try {
                        if (simResult?.isDungeon) {
                            const completed = parseNumber(simResult.dungeonsCompleted || 0);
                            if (completed <= 0) {
                                return 0;
                            }
                            const totalTime = parseNumber(simResult.lastDungeonFinishTime || 0) > 0
                                ? parseNumber(simResult.lastDungeonFinishTime)
                                : parseNumber(simResult.simulatedTime || 0);
                            return (totalTime / ONE_HOUR) * 60 / completed;
                        }
                        const encounters = parseNumber(simResult?.encounters || 0);
                        if (encounters <= 0) {
                            return 0;
                        }
                        const totalTime = parseNumber(simResult.lastEncounterFinishTime || 0) > 0
                            ? parseNumber(simResult.lastEncounterFinishTime)
                            : parseNumber(simResult.simulatedTime || 0);
                        return (totalTime / ONE_HOUR) * 60 / encounters;
                    } catch (_error) {
                        return 0;
                    }
                }

                function parseDungeonTier(simResult, dungeonName) {
                    const numericCandidates = [
                        simResult?.dungeonTier,
                        simResult?.tier,
                        simResult?.rewardTier,
                        simResult?.zoneTier,
                        simResult?.difficultyTier,
                    ];
                    for (const candidate of numericCandidates) {
                        const numeric = Number(candidate);
                        if (Number.isFinite(numeric)) {
                            return numeric >= 2 ? 2 : numeric >= 1 ? 1 : 0;
                        }
                    }
                    const textCandidates = [
                        simResult?.tierName,
                        simResult?.difficultyName,
                        simResult?.zoneName,
                        dungeonName,
                    ];
                    for (const textCandidate of textCandidates) {
                        const match = String(textCandidate || "").match(/T\\s*([012])/i);
                        if (match) {
                            const numeric = Number(match[1]);
                            return numeric >= 2 ? 2 : numeric >= 1 ? 1 : 0;
                        }
                    }
                    return 0;
                }

                function buildSnapshot() {
                    try {
                        if (typeof currentSimResults === "undefined" || !currentSimResults || !Object.keys(currentSimResults).length) {
                            return null;
                        }
                        const simResult = currentSimResults;
                        const itemMap = typeof itemDetailMap !== "undefined" ? itemDetailMap : {};
                        const tabEntries = Array.from(document.querySelectorAll("#playerTab .nav-link")).map((tab, index) => ({
                            playerKey: "player" + (index + 1),
                            name: (tab.textContent || "").trim(),
                        })).filter((entry) => entry.name);
                        const durationHours = Math.max(0, parseNumber(simResult.simulatedTime || 0) / ONE_HOUR);
                        const averageMinutes = computeAverageMinutes(simResult);
                        const nonRandomDrops = parseNoRngDropsFromDom();
                        const selectedCharacterName = (document.querySelector("#playerTab .nav-link.active")?.textContent || "").trim();
                        const dungeonName = String(simResult.zoneName || document.querySelector("#selectZone")?.selectedOptions?.[0]?.textContent || "").trim();
                        const dungeonTier = parseDungeonTier(simResult, dungeonName);
                        const characters = tabEntries.map((entry) => {
                            const consumablesUsed = simResult.consumablesUsed?.[entry.playerKey] || {};
                            const consumables = Object.entries(consumablesUsed).map(([itemHrid, amount]) => ({
                                itemHrid,
                                name: itemMap[itemHrid]?.name || itemHrid,
                                perHour: durationHours > 0 ? parseNumber(amount) / durationHours : 0,
                            })).sort((left, right) => right.perHour - left.perHour);
                            return {
                                id: entry.playerKey,
                                name: entry.name,
                                averageMinutes,
                                durationHours: durationHours || 24,
                                consumables,
                                nonRandomDrops,
                            };
                        });
                        return {
                            dungeonName,
                            dungeonTier,
                            selectedCharacterName,
                            characters,
                            capturedAt: Date.now(),
                        };
                    } catch (_error) {
                        return null;
                    }
                }

                function dispatchSnapshot() {
                    const snapshot = buildSnapshot();
                    if (!snapshot?.characters?.length) {
                        return;
                    }
                    window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: snapshot }));
                }

                function wrapFunction(name) {
                    const original = window[name];
                    if (typeof original !== "function" || original.__ictimeWrapped) {
                        return;
                    }
                    const wrapped = function (...args) {
                        const result = original.apply(this, args);
                        setTimeout(dispatchSnapshot, 0);
                        return result;
                    };
                    wrapped.__ictimeWrapped = true;
                    window[name] = wrapped;
                }

                function start() {
                    wrapFunction("showSimulationResult");
                    wrapFunction("showAllSimulationResults");
                    wrapFunction("onTabChange");
                    setTimeout(dispatchSnapshot, 0);
                    setInterval(dispatchSnapshot, 2000);
                }

                if (document.readyState === "loading") {
                    document.addEventListener("DOMContentLoaded", start, { once: true });
                } else {
                    start();
                }
            })();
        `;
        (document.documentElement || document.head || document.body).appendChild(script);
        script.remove();
    }

    function getSimulatorPlayerTabs() {
        return Array.from(document.querySelectorAll("#playerTab .nav-link"))
            .map((tab, index) => ({
                tab,
                playerId: String(index + 1),
                name: (tab.textContent || "").trim(),
                active: tab.classList.contains("active"),
                enabled: !!document.getElementById(`player${index + 1}`)?.checked,
            }))
            .filter((entry) => entry.name);
    }

    function getCurrentSimulatorCharacterName() {
        const activeTab = document.querySelector("#playerTab .nav-link.active");
        const activeText = (activeTab?.textContent || "").trim();
        if (activeText) {
            return activeText;
        }
        const fallback = Array.from(document.querySelectorAll("#playerTab .nav-link"))
            .map((node) => (node.textContent || "").trim())
            .find(Boolean);
        return fallback || "";
    }

    function readSimulatorRenderedResult() {
        const resultRoot = findSimulatorResultRoot();
        if (!resultRoot) {
            return null;
        }
        return {
            averageMinutes: parseNonNegativeDecimal(findLabeledValue(resultRoot, "平均完成时间") || 0),
            durationHours: findSimulatorDurationHours(),
            consumables: parseSimulatorConsumables(resultRoot),
            nonRandomDrops: parseSimulatorNonRandomDrops(resultRoot),
        };
    }

    function captureCurrentSimulatorSnapshot() {
        const dungeonSelect = findSimulatorSelectByOptions(["奇幻洞穴", "阴森马戏团", "秘法要塞", "海盗基地"]);
        if (!dungeonSelect) {
            return null;
        }
        const rendered = readSimulatorRenderedResult();
        if (!rendered) {
            return null;
        }
        const selectedCharacterName = getCurrentSimulatorCharacterName();
        const snapshot = {
            dungeonName: (dungeonSelect.selectedOptions[0]?.textContent || "").trim(),
            dungeonTier: parseSimulatorDungeonTierValue(
                dungeonSelect.selectedOptions[0]?.textContent,
                document.body?.innerText || ""
            ),
            selectedCharacterName,
            characters: [{
                id: selectedCharacterName || "player1",
                name: selectedCharacterName || "player1",
                averageMinutes: rendered.averageMinutes,
                durationHours: rendered.durationHours,
                consumables: rendered.consumables,
                nonRandomDrops: rendered.nonRandomDrops,
            }],
            capturedAt: Date.now(),
        };
        return snapshot;
    }

    async function captureAllSimulatorCharactersSnapshot() {
        const dungeonSelect = findSimulatorSelectByOptions(["奇幻洞穴", "阴森马戏团", "秘法要塞", "海盗基地"]);
        if (!dungeonSelect) {
            return null;
        }
        const tabs = getSimulatorPlayerTabs();
        if (!tabs.length) {
            return captureCurrentSimulatorSnapshot();
        }
        const originalActive = tabs.find((entry) => entry.active) || tabs[0];
        const characters = [];
        for (const entry of tabs) {
            const currentActiveName = getCurrentSimulatorCharacterName();
            if (currentActiveName !== entry.name) {
                entry.tab.click();
                await sleep(180);
            }
            const rendered = readSimulatorRenderedResult();
            if (!rendered) {
                continue;
            }
            characters.push({
                id: `player${entry.playerId}`,
                name: entry.name,
                averageMinutes: rendered.averageMinutes,
                durationHours: rendered.durationHours,
                consumables: rendered.consumables,
                nonRandomDrops: rendered.nonRandomDrops,
            });
        }
        if (originalActive && getCurrentSimulatorCharacterName() !== originalActive.name) {
            originalActive.tab.click();
            await sleep(180);
        }
        if (!characters.length) {
            return null;
        }
        return {
            dungeonName: (dungeonSelect.selectedOptions[0]?.textContent || "").trim(),
            dungeonTier: parseSimulatorDungeonTierValue(
                dungeonSelect.selectedOptions[0]?.textContent,
                document.body?.innerText || ""
            ),
            selectedCharacterName: originalActive?.name || getCurrentSimulatorCharacterName(),
            characters,
            capturedAt: Date.now(),
        };
    }

    async function captureSimulatorSnapshot() {
        const snapshot = await captureAllSimulatorCharactersSnapshot();
        if (!snapshot) {
            return null;
        }
        await sharedSetValue(SIMULATOR_IMPORT_STORAGE_KEY, snapshot);
        return snapshot;
    }

    async function startSimulatorBridge() {
        let isCapturing = false;
        let lastHandledRequestAt = 0;
        let lastPublishedSignature = "";
        let publishQueued = false;
        let lastBridgeSnapshot = null;
        let requestBaselineInitialized = false;

        const handleBridgeSnapshot = async (event) => {
            const snapshot = event?.detail || null;
            if (!snapshot?.characters?.length) {
                return;
            }
            lastBridgeSnapshot = snapshot;
            const signature = JSON.stringify({
                dungeonName: snapshot.dungeonName,
                dungeonTier: snapshot.dungeonTier,
                selectedCharacterName: snapshot.selectedCharacterName,
                characters: snapshot.characters.map((entry) => ({
                    name: entry.name,
                    averageMinutes: entry.averageMinutes,
                    durationHours: entry.durationHours,
                    consumables: entry.consumables,
                    nonRandomDrops: entry.nonRandomDrops,
                })),
            });
            if (signature === lastPublishedSignature) {
                return;
            }
            lastPublishedSignature = signature;
            await sharedSetValue(SIMULATOR_IMPORT_STORAGE_KEY, snapshot);
        };

        const publishVisibleSnapshot = async () => {
            if (isCapturing) {
                return;
            }
            if (lastBridgeSnapshot?.characters?.length) {
                await handleBridgeSnapshot({ detail: lastBridgeSnapshot });
                return;
            }
            const snapshot = captureCurrentSimulatorSnapshot();
            if (!snapshot?.characters?.length) {
                return;
            }
            const signature = JSON.stringify({
                dungeonName: snapshot.dungeonName,
                dungeonTier: snapshot.dungeonTier,
                selectedCharacterName: snapshot.selectedCharacterName,
                averageMinutes: snapshot.characters[0]?.averageMinutes || 0,
                durationHours: snapshot.characters[0]?.durationHours || 0,
                consumables: snapshot.characters[0]?.consumables || [],
                nonRandomDrops: snapshot.characters[0]?.nonRandomDrops || [],
            });
            if (signature === lastPublishedSignature) {
                return;
            }
            lastPublishedSignature = signature;
            await sharedSetValue(SIMULATOR_IMPORT_STORAGE_KEY, snapshot);
        };

        const queuePublish = () => {
            if (publishQueued) {
                return;
            }
            publishQueued = true;
            setTimeout(async () => {
                publishQueued = false;
                try {
                    await publishVisibleSnapshot();
                } catch (error) {
                    console.error("[ICTime] Failed to publish visible simulator snapshot.", error);
                }
            }, 150);
        };

        const tick = async () => {
            if (isCapturing) {
                return;
            }
            const request = await sharedGetValue(SIMULATOR_IMPORT_REQUEST_KEY, null);
            const requestedAt = Number(request?.requestedAt || 0);
            if (!requestBaselineInitialized) {
                lastHandledRequestAt = requestedAt;
                requestBaselineInitialized = true;
                return;
            }
            if (!requestedAt || requestedAt <= lastHandledRequestAt) {
                return;
            }
            isCapturing = true;
            try {
                await captureSimulatorSnapshot();
                lastHandledRequestAt = requestedAt;
            } catch (error) {
                console.error("[ICTime] Failed to capture simulator snapshot.", error);
            } finally {
                isCapturing = false;
            }
        };
        installSimulatorPageBridge();
        window.addEventListener(SIMULATOR_SNAPSHOT_EVENT, handleBridgeSnapshot);
        setInterval(tick, 500);
        setInterval(() => {
            queuePublish();
        }, 2000);
        document.addEventListener("change", queuePublish, true);
        document.addEventListener("click", queuePublish, true);
        const observer = new MutationObserver(() => {
            queuePublish();
        });
        observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true });
        queuePublish();
    }

    if (location.hostname === "shykai.github.io" && location.pathname.startsWith("/MWICombatSimulatorTest/dist/")) {
        startSimulatorBridge();
        return;
    }

    window.__ICTIME_VERSION__ = "1.0.3";
    const previousController = window.__ICTIME_CONTROLLER__;
    if (previousController && typeof previousController.shutdown === "function") {
        previousController.shutdown();
    }
    const instanceId = `ictime-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

    const SUPPORTED_ACTION_TYPES = new Set([
        "/action_types/milking",
        "/action_types/foraging",
        "/action_types/woodcutting",
        "/action_types/cheesesmithing",
        "/action_types/crafting",
        "/action_types/tailoring",
        "/action_types/cooking",
        "/action_types/brewing",
        "/action_types/alchemy",
        "/action_types/enhancing",
    ]);

    const ACTION_TO_TOOL_STAT = {
        "/action_types/alchemy": "alchemySpeed",
        "/action_types/brewing": "brewingSpeed",
        "/action_types/cheesesmithing": "cheesesmithingSpeed",
        "/action_types/cooking": "cookingSpeed",
        "/action_types/crafting": "craftingSpeed",
        "/action_types/enhancing": "enhancingSpeed",
        "/action_types/foraging": "foragingSpeed",
        "/action_types/milking": "milkingSpeed",
        "/action_types/tailoring": "tailoringSpeed",
        "/action_types/woodcutting": "woodcuttingSpeed",
    };

    const ACTION_TO_HOUSE = {
        "/action_types/alchemy": "/house_rooms/laboratory",
        "/action_types/brewing": "/house_rooms/brewery",
        "/action_types/cheesesmithing": "/house_rooms/forge",
        "/action_types/cooking": "/house_rooms/kitchen",
        "/action_types/crafting": "/house_rooms/workshop",
        "/action_types/enhancing": "/house_rooms/observatory",
        "/action_types/foraging": "/house_rooms/garden",
        "/action_types/milking": "/house_rooms/dairy_barn",
        "/action_types/tailoring": "/house_rooms/sewing_parlor",
        "/action_types/woodcutting": "/house_rooms/log_shed",
    };

    const ENHANCEMENT_BONUS = {
        0: 0, 1: 2, 2: 4.2, 3: 6.6, 4: 9.2, 5: 12, 6: 15, 7: 18.2, 8: 21.6, 9: 25.2,
        10: 29, 11: 33.4, 12: 38.4, 13: 44, 14: 50.2, 15: 57, 16: 64.4, 17: 72.4, 18: 81, 19: 90.2, 20: 100,
    };

    const PROCESSABLE_ITEM_MAP = new Map([
        ["/items/milk", "/items/cheese"],
        ["/items/verdant_milk", "/items/verdant_cheese"],
        ["/items/azure_milk", "/items/azure_cheese"],
        ["/items/burble_milk", "/items/burble_cheese"],
        ["/items/crimson_milk", "/items/crimson_cheese"],
        ["/items/rainbow_milk", "/items/rainbow_cheese"],
        ["/items/holy_milk", "/items/holy_cheese"],
        ["/items/log", "/items/lumber"],
        ["/items/birch_log", "/items/birch_lumber"],
        ["/items/cedar_log", "/items/cedar_lumber"],
        ["/items/purpleheart_log", "/items/purpleheart_lumber"],
        ["/items/ginkgo_log", "/items/ginkgo_lumber"],
        ["/items/redwood_log", "/items/redwood_lumber"],
        ["/items/arcane_log", "/items/arcane_lumber"],
        ["/items/cotton", "/items/cotton_fabric"],
        ["/items/flax", "/items/linen_fabric"],
        ["/items/bamboo_branch", "/items/bamboo_fabric"],
        ["/items/cocoon", "/items/silk_fabric"],
        ["/items/radiant_fiber", "/items/radiant_fabric"],
        ["/items/rough_hide", "/items/rough_leather"],
        ["/items/reptile_hide", "/items/reptile_leather"],
        ["/items/gobo_hide", "/items/gobo_leather"],
        ["/items/beast_hide", "/items/beast_leather"],
        ["/items/umbral_hide", "/items/umbral_leather"],
    ]);

    const ESSENCE_DECOMPOSE_RULES = {
        "/items/alchemy_essence": { type: "fixed_source", sourceItemHrid: "/items/catalyst_of_decomposition" },
        "/items/milking_essence": { type: "fixed_source", sourceItemHrid: "/items/holy_milk" },
        "/items/foraging_essence": { type: "fixed_source", sourceItemHrid: "/items/star_fruit" },
        "/items/woodcutting_essence": { type: "fixed_source", sourceItemHrid: "/items/arcane_log" },
        "/items/cheesesmithing_essence": { type: "fixed_source", sourceItemHrid: "/items/holy_cheese" },
        "/items/crafting_essence": { type: "fixed_source", sourceItemHrid: "/items/arcane_lumber" },
        "/items/tailoring_essence": { type: "fixed_source", sourceItemHrid: "/items/umbral_hide" },
        "/items/cooking_essence": { type: "fixed_source", sourceItemHrid: "/items/star_fruit_yogurt" },
        "/items/brewing_essence": { type: "fixed_source", sourceItemHrid: "/items/emp_tea_leaf" },
    };
    const TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS = {
        "/items/brewing_essence": "/items/emp_tea_leaf",
        "/items/tailoring_essence": "/items/umbral_hide",
    };

    const FIXED_DECOMPOSE_SOURCE_RULES = {
        "/items/cheese": "/items/cheese_sword",
        "/items/lumber": "/items/wooden_bow",
    };

    const FIXED_ENHANCING_ESSENCE_RULES = {
        "/items/enhancing_essence": {
            sourceItemHrid: "/items/cheese_boots",
            enhancementLevel: 14,
            catalystItemHrid: "",
        },
    };

    const FIXED_TRANSMUTE_SOURCE_RULES = {
        "/items/prime_catalyst": {
            sourceItemHrid: "/items/catalyst_of_coinification",
            actionHrid: "/actions/alchemy/transmute",
        },
    };

    const FIXED_ATTACHED_RARE_TOOLTIP_SOURCE_RULES = {
        "/items/butter_of_proficiency": {
            sourceItemHrid: "/items/holy_sword",
            fallbackSourceItemHrids: ["/items/holy_bulwark"],
            catalystItemHrid: "/items/catalyst_of_transmutation",
            catalystSuccessBonus: 0.075,
        },
        "/items/thread_of_expertise": {
            sourceItemHrid: "/items/umbral_tunic",
            fallbackSourceItemHrids: ["/items/radiant_robe_top"],
            catalystItemHrid: "/items/catalyst_of_transmutation",
            catalystSuccessBonus: 0.075,
        },
        "/items/branch_of_insight": {
            sourceItemHrid: "/items/arcane_crossbow",
            fallbackSourceItemHrids: ["/items/arcane_bow"],
            catalystItemHrid: "/items/catalyst_of_transmutation",
            catalystSuccessBonus: 0.075,
        },
    };

    const TRANSMUTE_CATALYST_SUCCESS_BONUSES = {
        "/items/catalyst_of_transmutation": 0.075,
        "/items/prime_catalyst": 0.125,
    };

    const ATTACHED_RARE_TARGET_ITEM_HRIDS = [
        "/items/butter_of_proficiency",
        "/items/thread_of_expertise",
        "/items/branch_of_insight",
    ];
    const CONSUMABLE_VALUE_ATTACHED_RARE_ITEM_HRIDS = [
        "/items/butter_of_proficiency",
        "/items/thread_of_expertise",
    ];
    const ATTACHED_RARE_TARGET_ITEM_HRID_SET = new Set(ATTACHED_RARE_TARGET_ITEM_HRIDS);
    const ATTACHED_RARE_LABEL_ZH = {
        "/items/butter_of_proficiency": "油",
        "/items/thread_of_expertise": "线",
        "/items/branch_of_insight": "树枝",
    };
    const ATTACHED_RARE_LABEL_EN = {
        "/items/butter_of_proficiency": "oil",
        "/items/thread_of_expertise": "thread",
        "/items/branch_of_insight": "branch",
    };

    const ESSENCE_SOURCE_NAME_ZH = {
        "/items/alchemy_tea": "炼金茶",
        "/items/apple": "苹果",
        "/items/apple_gummy": "苹果软糖",
        "/items/apple_yogurt": "苹果酸奶",
        "/items/arabica_coffee_bean": "低级咖啡豆",
        "/items/arcane_log": "神秘原木",
        "/items/arcane_lumber": "神秘木板",
        "/items/artisan_tea": "工匠茶",
        "/items/attack_coffee": "攻击咖啡",
        "/items/azure_cheese": "蔚蓝奶酪",
        "/items/azure_milk": "蔚蓝牛奶",
        "/items/bamboo_branch": "竹子",
        "/items/bamboo_fabric": "竹子布料",
        "/items/basic_brewing_charm": "基础冲泡护符",
        "/items/basic_cheesesmithing_charm": "基础奶酪锻造护符",
        "/items/basic_cooking_charm": "基础烹饪护符",
        "/items/basic_crafting_charm": "基础制作护符",
        "/items/basic_foraging_charm": "基础采摘护符",
        "/items/basic_milking_charm": "基础挤奶护符",
        "/items/basic_tailoring_charm": "基础缝纫护符",
        "/items/basic_woodcutting_charm": "基础伐木护符",
        "/items/beast_hide": "野兽皮",
        "/items/beast_leather": "野兽皮革",
        "/items/birch_log": "白桦原木",
        "/items/birch_lumber": "白桦木板",
        "/items/black_tea_leaf": "黑茶叶",
        "/items/blackberry": "黑莓",
        "/items/blackberry_cake": "黑莓蛋糕",
        "/items/blackberry_donut": "黑莓甜甜圈",
        "/items/blessed_tea": "福气茶",
        "/items/blueberry": "蓝莓",
        "/items/blueberry_cake": "蓝莓蛋糕",
        "/items/blueberry_donut": "蓝莓甜甜圈",
        "/items/brewers_bottoms": "饮品师下装",
        "/items/brewers_top": "饮品师上衣",
        "/items/brewing_tea": "冲泡茶",
        "/items/burble_cheese": "深紫奶酪",
        "/items/burble_milk": "深紫牛奶",
        "/items/burble_tea_leaf": "紫茶叶",
        "/items/catalytic_tea": "催化茶",
        "/items/cedar_log": "雪松原木",
        "/items/cedar_lumber": "雪松木板",
        "/items/celestial_brush": "星空刷子",
        "/items/celestial_chisel": "星空凿子",
        "/items/celestial_hammer": "星空锤子",
        "/items/celestial_hatchet": "星空斧头",
        "/items/celestial_needle": "星空针",
        "/items/celestial_pot": "星空壶",
        "/items/celestial_shears": "星空剪刀",
        "/items/celestial_spatula": "星空锅铲",
        "/items/channeling_coffee": "吟唱咖啡",
        "/items/cheese": "奶酪",
        "/items/chimerical_chest": "奇幻宝箱",
        "/items/chimerical_chest_key": "奇幻宝箱钥匙",
        "/items/cheesemakers_bottoms": "奶酪师下装",
        "/items/cheesemakers_top": "奶酪师上衣",
        "/items/cheesesmithing_tea": "奶酪锻造茶",
        "/items/chefs_bottoms": "厨师下装",
        "/items/chefs_top": "厨师上衣",
        "/items/cocoon": "蚕茧",
        "/items/cooking_tea": "烹饪茶",
        "/items/cotton": "棉花",
        "/items/cotton_fabric": "棉花布料",
        "/items/crafters_bottoms": "工匠下装",
        "/items/crafters_top": "工匠上衣",
        "/items/crafting_tea": "制作茶",
        "/items/crimson_cheese": "绛红奶酪",
        "/items/crimson_milk": "绛红牛奶",
        "/items/critical_coffee": "暴击咖啡",
        "/items/cupcake": "纸杯蛋糕",
        "/items/dairyhands_bottoms": "挤奶工下装",
        "/items/dairyhands_top": "挤奶工上衣",
        "/items/defense_coffee": "防御咖啡",
        "/items/donut": "甜甜圈",
        "/items/dragon_fruit": "火龙果",
        "/items/dragon_fruit_gummy": "火龙果软糖",
        "/items/dragon_fruit_yogurt": "火龙果酸奶",
        "/items/efficiency_tea": "效率茶",
        "/items/egg": "鸡蛋",
        "/items/emp_tea_leaf": "虚空茶叶",
        "/items/enhancing_tea": "强化茶",
        "/items/enchanted_chest": "秘法宝箱",
        "/items/enchanted_chest_key": "秘法宝箱钥匙",
        "/items/blue_key_fragment": "蓝钥匙碎片",
        "/items/white_key_fragment": "白钥匙碎片",
        "/items/green_key_fragment": "绿钥匙碎片",
        "/items/orange_key_fragment": "橙钥匙碎片",
        "/items/brown_key_fragment": "棕钥匙碎片",
        "/items/purple_key_fragment": "紫钥匙碎片",
        "/items/burning_key_fragment": "燃烧钥匙碎片",
        "/items/dark_key_fragment": "暗钥匙碎片",
        "/items/stone_key_fragment": "石钥匙碎片",
        "/items/excelsa_coffee_bean": "特级咖啡豆",
        "/items/fieriosa_coffee_bean": "火山咖啡豆",
        "/items/flax": "亚麻",
        "/items/foragers_bottoms": "采摘者下装",
        "/items/foragers_top": "采摘者上衣",
        "/items/foraging_tea": "采摘茶",
        "/items/gathering_tea": "采集茶",
        "/items/ginkgo_log": "银杏原木",
        "/items/ginkgo_lumber": "银杏木板",
        "/items/gobo_hide": "哥布林皮",
        "/items/gobo_leather": "哥布林皮革",
        "/items/gourmet_tea": "美食茶",
        "/items/green_tea_leaf": "绿茶叶",
        "/items/gummy": "软糖",
        "/items/holy_cheese": "神圣奶酪",
        "/items/holy_milk": "神圣牛奶",
        "/items/intelligence_coffee": "智力咖啡",
        "/items/liberica_coffee_bean": "高级咖啡豆",
        "/items/linen_fabric": "亚麻布料",
        "/items/log": "原木",
        "/items/lucky_coffee": "幸运咖啡",
        "/items/lumber": "木板",
        "/items/lumberjacks_bottoms": "伐木工下装",
        "/items/lumberjacks_top": "伐木工上衣",
        "/items/magic_coffee": "魔法咖啡",
        "/items/marsberry": "火星莓",
        "/items/marsberry_cake": "火星莓蛋糕",
        "/items/marsberry_donut": "火星莓甜甜圈",
        "/items/melee_coffee": "近战咖啡",
        "/items/milk": "牛奶",
        "/items/milking_tea": "挤奶茶",
        "/items/mooberry": "哞莓",
        "/items/mooberry_cake": "哞莓蛋糕",
        "/items/mooberry_donut": "哞莓甜甜圈",
        "/items/moolong_tea_leaf": "哞龙茶叶",
        "/items/orange": "橙子",
        "/items/orange_gummy": "橙子软糖",
        "/items/orange_yogurt": "橙子酸奶",
        "/items/peach": "桃子",
        "/items/peach_gummy": "桃子软糖",
        "/items/peach_yogurt": "桃子酸奶",
        "/items/plum": "李子",
        "/items/plum_gummy": "李子软糖",
        "/items/plum_yogurt": "李子酸奶",
        "/items/pirate_chest": "海盗宝箱",
        "/items/pirate_chest_key": "海盗宝箱钥匙",
        "/items/processing_tea": "加工茶",
        "/items/purpleheart_log": "紫心原木",
        "/items/purpleheart_lumber": "紫心木板",
        "/items/radiant_fabric": "光辉布料",
        "/items/radiant_fiber": "光辉纤维",
        "/items/rainbow_cheese": "彩虹奶酪",
        "/items/rainbow_milk": "彩虹牛奶",
        "/items/ranged_coffee": "远程咖啡",
        "/items/red_tea_leaf": "红茶叶",
        "/items/redwood_log": "红杉原木",
        "/items/redwood_lumber": "红杉木板",
        "/items/reptile_hide": "爬行动物皮",
        "/items/reptile_leather": "爬行动物皮革",
        "/items/robusta_coffee_bean": "中级咖啡豆",
        "/items/rough_hide": "粗糙兽皮",
        "/items/rough_leather": "粗糙皮革",
        "/items/silk_fabric": "丝绸",
        "/items/sinister_chest": "阴森宝箱",
        "/items/sinister_chest_key": "阴森宝箱钥匙",
        "/items/spaceberry": "太空莓",
        "/items/spaceberry_cake": "太空莓蛋糕",
        "/items/spaceberry_donut": "太空莓甜甜圈",
        "/items/spacia_coffee_bean": "太空咖啡豆",
        "/items/stamina_coffee": "耐力咖啡",
        "/items/star_fruit": "杨桃",
        "/items/star_fruit_gummy": "杨桃软糖",
        "/items/star_fruit_yogurt": "杨桃酸奶",
        "/items/strawberry": "草莓",
        "/items/strawberry_cake": "草莓蛋糕",
        "/items/strawberry_donut": "草莓甜甜圈",
        "/items/sugar": "糖",
        "/items/super_alchemy_tea": "超级炼金茶",
        "/items/super_attack_coffee": "超级攻击咖啡",
        "/items/super_brewing_tea": "超级冲泡茶",
        "/items/super_cheesesmithing_tea": "超级奶酪锻造茶",
        "/items/super_cooking_tea": "超级烹饪茶",
        "/items/super_crafting_tea": "超级制作茶",
        "/items/super_defense_coffee": "超级防御咖啡",
        "/items/super_enhancing_tea": "超级强化茶",
        "/items/super_foraging_tea": "超级采摘茶",
        "/items/super_intelligence_coffee": "超级智力咖啡",
        "/items/super_magic_coffee": "超级魔法咖啡",
        "/items/super_melee_coffee": "超级近战咖啡",
        "/items/super_milking_tea": "超级挤奶茶",
        "/items/super_ranged_coffee": "超级远程咖啡",
        "/items/super_stamina_coffee": "超级耐力咖啡",
        "/items/super_tailoring_tea": "超级缝纫茶",
        "/items/super_woodcutting_tea": "超级伐木茶",
        "/items/swiftness_coffee": "迅捷咖啡",
        "/items/tailoring_tea": "缝纫茶",
        "/items/tailors_bottoms": "裁缝下装",
        "/items/tailors_top": "裁缝上衣",
        "/items/ultra_alchemy_tea": "究极炼金茶",
        "/items/ultra_attack_coffee": "究极攻击咖啡",
        "/items/ultra_brewing_tea": "究极冲泡茶",
        "/items/ultra_cheesesmithing_tea": "究极奶酪锻造茶",
        "/items/ultra_cooking_tea": "究极烹饪茶",
        "/items/ultra_crafting_tea": "究极制作茶",
        "/items/ultra_defense_coffee": "究极防御咖啡",
        "/items/ultra_enhancing_tea": "究极强化茶",
        "/items/ultra_foraging_tea": "究极采摘茶",
        "/items/ultra_intelligence_coffee": "究极智力咖啡",
        "/items/ultra_magic_coffee": "究极魔法咖啡",
        "/items/ultra_melee_coffee": "究极近战咖啡",
        "/items/ultra_milking_tea": "究极挤奶茶",
        "/items/ultra_ranged_coffee": "究极远程咖啡",
        "/items/ultra_stamina_coffee": "究极耐力咖啡",
        "/items/ultra_tailoring_tea": "究极缝纫茶",
        "/items/ultra_woodcutting_tea": "究极伐木茶",
        "/items/umbral_hide": "暗影皮",
        "/items/umbral_leather": "暗影皮革",
        "/items/verdant_cheese": "翠绿奶酪",
        "/items/verdant_milk": "翠绿牛奶",
        "/items/wheat": "小麦",
        "/items/wisdom_coffee": "经验咖啡",
        "/items/wisdom_tea": "经验茶",
        "/items/woodcutting_tea": "伐木茶",
        "/items/yogurt": "酸奶",
    };

    const state = {
        actionDetailMap: null,
        itemDetailMap: null,
        characterSkills: null,
        characterSkillMap: null,
        characterItems: null,
        characterItemMap: null,
        characterItemByLocationMap: null,
        characterHouseRoomMap: null,
        actionTypeDrinkSlotsMap: null,
        characterLoadoutDict: null,
        characterSetting: null,
        currentCharacterName: "",
        communityActionTypeBuffsDict: null,
        houseActionTypeBuffsDict: null,
        achievementActionTypeBuffsDict: null,
        personalActionTypeBuffsDict: null,
        consumableActionTypeBuffsDict: null,
        equipmentActionTypeBuffsDict: null,
        mooPassActionTypeBuffsDict: null,
        enhancementLevelTotalBonusMultiplierTable: null,
        shopItemDetailMap: null,
        itemNameToHrid: new Map(),
        itemTimeCache: new Map(),
        essencePlanCache: new Map(),
        fixedDecomposePlanCache: new Map(),
        fixedEnhancedEssencePlanCache: new Map(),
        fixedTransmutePlanCache: new Map(),
        fixedAttachedRareTooltipPlanCache: new Map(),
        attachedRareYieldCache: new Map(),
        itemTargetRelationCache: new Map(),
        skillingScrollTimeSavingsCache: new Map(),
        outputActionCache: null,
        lastRuntimeHydrationAt: 0,
        cachedInitClientData: null,
        cachedInitClientDataRaw: "",
        maxEnhancementByItem: null,
        localizedItemNameMap: new Map(),
        translationLoadStarted: false,
        isRefreshingTooltips: false,
        tooltipObserver: null,
        timeCalculatorUiObserver: null,
        tooltipRefreshTimer: 0,
        isShutDown: false,
        lastTooltipRender: null,
        enhancingRefreshQueued: false,
        alchemyInferenceRefreshQueued: false,
        alchemyInferenceObserver: null,
        alchemyObservedPanel: null,
        alchemyInferenceDelayTimers: [],
        eventAbortController: null,
        timeCalculatorRefreshQueued: false,
        timeCalculatorRefreshPending: false,
        timeCalculatorTabButton: null,
        timeCalculatorTabPanel: null,
        timeCalculatorContainer: null,
        timeCalculatorEntries: [],
        timeCalculatorLoadedCharacterId: null,
        timeCalculatorCompactMode: false,
        timeCalculatorSettingsOpen: false,
        timeCalculatorEssenceSourceItemHrids: { ...TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS },
        timeCalculatorDrafts: {
            addItemQuery: "",
            consumableQueryByEntryId: {},
        },
        lastHoveredItemHrid: "",
        lastHoveredItemAt: 0,
        enhancingPanelRef: null,
        itemTooltipDataCache: new Map(),
        cyclicSolveDepth: 0,
        activeItemSolveSet: new Set(),
        itemFailureReasonCache: new Map(),
    };

    const ENHANCING_ACTION_TYPE = "/action_types/enhancing";
    const ENHANCING_ACTION_HRID = "/actions/enhancing/enhance";
    const SKILLING_SCROLL_DEFAULT_DURATION_SECONDS = 1800;
    const SKILLING_SCROLL_VALUE_CONFIGS = {
        // Reuse the seal effect mapping already maintained in the labyrinth reference plugin.
        "/items/seal_of_gathering": {
            mode: "rate",
            baseItemHrid: "/items/holy_milk",
            baseActionTypeHrid: "/action_types/milking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_gathering",
                typeHrid: "/buff_types/gathering",
                flatBoost: 0.18,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
        "/items/seal_of_efficiency": {
            mode: "rate",
            baseItemHrid: "/items/holy_milk",
            baseActionTypeHrid: "/action_types/milking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_efficiency",
                typeHrid: "/buff_types/efficiency",
                flatBoost: 0.14,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
        "/items/seal_of_action_speed": {
            mode: "rate",
            baseItemHrid: "/items/holy_milk",
            baseActionTypeHrid: "/action_types/milking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_action_speed",
                typeHrid: "/buff_types/action_speed",
                flatBoost: 0.15,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
        "/items/seal_of_gourmet": {
            mode: "rate",
            baseItemHrid: "/items/dragon_fruit_yogurt",
            baseActionTypeHrid: "/action_types/cooking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_gourmet",
                typeHrid: "/buff_types/gourmet",
                flatBoost: 0.1,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
        "/items/seal_of_processing": {
            mode: "processing",
            baseItemHrid: "/items/holy_cheese",
            sourceItemHrid: "/items/holy_milk",
            sourceActionHrid: "/actions/milking/holy_cow",
            baseActionTypeHrid: "/action_types/milking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_processing",
                typeHrid: "/buff_types/processing",
                flatBoost: 0.2,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
        "/items/seal_of_rare_find": {
            mode: "rare_find",
            baseItemHrid: "/items/butter_of_proficiency",
            sourceItemHrid: "/items/holy_milk",
            sourceActionHrid: "/actions/milking/holy_cow",
            baseActionTypeHrid: "/action_types/milking",
            buff: {
                uniqueHrid: "/buff_uniques/ictime_seal_of_rare_find",
                typeHrid: "/buff_types/rare_find",
                flatBoost: 0.6,
                ratioBoost: 0,
                ratioBoostLevelBonus: 0,
                flatBoostLevelBonus: 0,
            },
        },
    };
    const ENHANCING_SUCCESS_RATES = [
        0.5, 0.45, 0.45, 0.4, 0.4, 0.4, 0.35, 0.35, 0.35, 0.35,
        0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3,
    ];
    const DUNGEON_CHEST_ITEM_HRIDS = [
        "/items/enchanted_chest",
        "/items/chimerical_chest",
        "/items/sinister_chest",
        "/items/pirate_chest",
    ];
    const KEY_FRAGMENT_ITEM_HRIDS = [
        "/items/blue_key_fragment",
        "/items/white_key_fragment",
        "/items/green_key_fragment",
        "/items/orange_key_fragment",
        "/items/brown_key_fragment",
        "/items/purple_key_fragment",
        "/items/burning_key_fragment",
        "/items/dark_key_fragment",
        "/items/stone_key_fragment",
    ];

    const MWITOOLS_ZH_ITEM_NAME_OVERRIDES = {
        "/items/chimerical_chest": "奇幻宝箱",
        "/items/sinister_chest": "阴森宝箱",
        "/items/enchanted_chest": "秘法宝箱",
        "/items/pirate_chest": "海盗宝箱",
        "/items/blue_key_fragment": "蓝色钥匙碎片",
        "/items/green_key_fragment": "绿色钥匙碎片",
        "/items/purple_key_fragment": "紫色钥匙碎片",
        "/items/white_key_fragment": "白色钥匙碎片",
        "/items/orange_key_fragment": "橙色钥匙碎片",
        "/items/brown_key_fragment": "棕色钥匙碎片",
        "/items/stone_key_fragment": "石头钥匙碎片",
        "/items/dark_key_fragment": "黑暗钥匙碎片",
        "/items/burning_key_fragment": "燃烧钥匙碎片",
    };

    const TIME_CALCULATOR_ITEM_NAME_OVERRIDES_ZH = {
        "/items/chimerical_chest": "\u5947\u5e7b\u5b9d\u7bb1",
        "/items/sinister_chest": "\u9634\u68ee\u5b9d\u7bb1",
        "/items/enchanted_chest": "\u79d8\u6cd5\u5b9d\u7bb1",
        "/items/pirate_chest": "\u6d77\u76d7\u5b9d\u7bb1",
        "/items/chimerical_refinement_chest": "\u5947\u5e7b\u7cbe\u70bc\u7bb1\u5b50",
        "/items/sinister_refinement_chest": "\u9634\u68ee\u7cbe\u70bc\u7bb1\u5b50",
        "/items/enchanted_refinement_chest": "\u79d8\u6cd5\u7cbe\u70bc\u7bb1\u5b50",
        "/items/pirate_refinement_chest": "\u6d77\u76d7\u7cbe\u70bc\u7bb1\u5b50",
        "/items/blue_key_fragment": "\u84dd\u8272\u94a5\u5319\u788e\u7247",
        "/items/white_key_fragment": "\u767d\u8272\u94a5\u5319\u788e\u7247",
        "/items/green_key_fragment": "\u7eff\u8272\u94a5\u5319\u788e\u7247",
        "/items/orange_key_fragment": "\u6a59\u8272\u94a5\u5319\u788e\u7247",
        "/items/brown_key_fragment": "\u68d5\u8272\u94a5\u5319\u788e\u7247",
        "/items/purple_key_fragment": "\u7d2b\u8272\u94a5\u5319\u788e\u7247",
        "/items/burning_key_fragment": "\u71c3\u70e7\u94a5\u5319\u788e\u7247",
        "/items/dark_key_fragment": "\u9ed1\u6697\u94a5\u5319\u788e\u7247",
        "/items/stone_key_fragment": "\u77f3\u5934\u94a5\u5319\u788e\u7247",
    };

    const SIMULATOR_ITEM_NAME_ALIASES = Object.fromEntries(
        Object.entries(MWITOOLS_ZH_ITEM_NAME_OVERRIDES).map(([hrid, name]) => [name, hrid])
    );

    const REFINEMENT_SHARD_EXPECTED_COUNT_PER_CHEST = 1.875;

    const DUNGEON_CHEST_CONFIG = {
        "/items/chimerical_chest": {
            entryKeyItemHrid: "/items/chimerical_entry_key",
            keyItemHrid: "/items/chimerical_chest_key",
            tokenItemHrid: "/items/chimerical_token",
            refinementChestItemHrid: "/items/chimerical_refinement_chest",
            refinementShardItemHrid: "/items/chimerical_refinement_shard",
            refinementShardCountPerChest: REFINEMENT_SHARD_EXPECTED_COUNT_PER_CHEST,
            drops: [
                { itemHrid: "/items/chimerical_essence", dropRate: 1, minCount: 400, maxCount: 800 },
                { itemHrid: "/items/chimerical_essence", dropRate: 0.05, minCount: 2000, maxCount: 4000 },
                { itemHrid: "/items/chimerical_token", dropRate: 1, minCount: 250, maxCount: 500 },
                { itemHrid: "/items/chimerical_token", dropRate: 0.05, minCount: 1500, maxCount: 3000 },
                { itemHrid: "/items/griffin_leather", dropRate: 0.1, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/manticore_sting", dropRate: 0.06, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/jackalope_antler", dropRate: 0.05, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/dodocamel_plume", dropRate: 0.02, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/griffin_talon", dropRate: 0.02, minCount: 1, maxCount: 1 },
            ],
        },
        "/items/sinister_chest": {
            entryKeyItemHrid: "/items/sinister_entry_key",
            keyItemHrid: "/items/sinister_chest_key",
            tokenItemHrid: "/items/sinister_token",
            refinementChestItemHrid: "/items/sinister_refinement_chest",
            refinementShardItemHrid: "/items/sinister_refinement_shard",
            refinementShardCountPerChest: REFINEMENT_SHARD_EXPECTED_COUNT_PER_CHEST,
            drops: [
                { itemHrid: "/items/sinister_essence", dropRate: 1, minCount: 400, maxCount: 800 },
                { itemHrid: "/items/sinister_essence", dropRate: 0.05, minCount: 2000, maxCount: 4000 },
                { itemHrid: "/items/sinister_token", dropRate: 1, minCount: 250, maxCount: 500 },
                { itemHrid: "/items/sinister_token", dropRate: 0.05, minCount: 1500, maxCount: 3000 },
                { itemHrid: "/items/acrobats_ribbon", dropRate: 0.04, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/magicians_cloth", dropRate: 0.04, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/chaotic_chain", dropRate: 0.02, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/cursed_ball", dropRate: 0.02, minCount: 1, maxCount: 1 },
            ],
        },
        "/items/enchanted_chest": {
            entryKeyItemHrid: "/items/enchanted_entry_key",
            keyItemHrid: "/items/enchanted_chest_key",
            tokenItemHrid: "/items/enchanted_token",
            refinementChestItemHrid: "/items/enchanted_refinement_chest",
            refinementShardItemHrid: "/items/enchanted_refinement_shard",
            refinementShardCountPerChest: REFINEMENT_SHARD_EXPECTED_COUNT_PER_CHEST,
            drops: [
                { itemHrid: "/items/enchanted_essence", dropRate: 1, minCount: 400, maxCount: 800 },
                { itemHrid: "/items/enchanted_essence", dropRate: 0.05, minCount: 2000, maxCount: 4000 },
                { itemHrid: "/items/enchanted_token", dropRate: 1, minCount: 250, maxCount: 500 },
                { itemHrid: "/items/enchanted_token", dropRate: 0.05, minCount: 1500, maxCount: 3000 },
                { itemHrid: "/items/knights_ingot", dropRate: 0.04, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/bishops_scroll", dropRate: 0.04, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/royal_cloth", dropRate: 0.04, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/regal_jewel", dropRate: 0.02, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/sundering_jewel", dropRate: 0.02, minCount: 1, maxCount: 1 },
            ],
        },
        "/items/pirate_chest": {
            entryKeyItemHrid: "/items/pirate_entry_key",
            keyItemHrid: "/items/pirate_chest_key",
            tokenItemHrid: "/items/pirate_token",
            refinementChestItemHrid: "/items/pirate_refinement_chest",
            refinementShardItemHrid: "/items/pirate_refinement_shard",
            refinementShardCountPerChest: REFINEMENT_SHARD_EXPECTED_COUNT_PER_CHEST,
            drops: [
                { itemHrid: "/items/pirate_essence", dropRate: 1, minCount: 400, maxCount: 800 },
                { itemHrid: "/items/pirate_essence", dropRate: 0.05, minCount: 2000, maxCount: 4000 },
                { itemHrid: "/items/pirate_token", dropRate: 1, minCount: 250, maxCount: 500 },
                { itemHrid: "/items/pirate_token", dropRate: 0.05, minCount: 1500, maxCount: 3000 },
                { itemHrid: "/items/marksman_brooch", dropRate: 0.03, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/corsair_crest", dropRate: 0.03, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/damaged_anchor", dropRate: 0.03, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/maelstrom_plating", dropRate: 0.03, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/kraken_leather", dropRate: 0.03, minCount: 1, maxCount: 1 },
                { itemHrid: "/items/kraken_fang", dropRate: 0.03, minCount: 1, maxCount: 1 },
            ],
        },
    };

    const DUNGEON_TOKEN_SHOP_COSTS = {
        "/items/chimerical_token": {
            "/items/chimerical_essence": 1,
            "/items/griffin_leather": 600,
            "/items/manticore_sting": 1000,
            "/items/jackalope_antler": 1200,
            "/items/dodocamel_plume": 3000,
            "/items/griffin_talon": 3000,
        },
        "/items/sinister_token": {
            "/items/sinister_essence": 1,
            "/items/acrobats_ribbon": 2000,
            "/items/magicians_cloth": 2000,
            "/items/chaotic_chain": 3000,
            "/items/cursed_ball": 3000,
        },
        "/items/enchanted_token": {
            "/items/enchanted_essence": 1,
            "/items/royal_cloth": 2000,
            "/items/knights_ingot": 2000,
            "/items/bishops_scroll": 2000,
            "/items/regal_jewel": 3000,
            "/items/sundering_jewel": 3000,
        },
        "/items/pirate_token": {
            "/items/pirate_essence": 1,
            "/items/marksman_brooch": 2000,
            "/items/corsair_crest": 2000,
            "/items/damaged_anchor": 2000,
            "/items/maelstrom_plating": 2000,
            "/items/kraken_leather": 2000,
            "/items/kraken_fang": 3000,
        },
    };
    const REFINEMENT_TIER_EXPECTED_COUNTS = {
        0: 0,
        1: 0.33,
        2: 1,
    };
    const REFINEMENT_CHEST_ITEM_HRIDS = Object.values(DUNGEON_CHEST_CONFIG)
        .map((config) => config.refinementChestItemHrid)
        .filter(Boolean);
    const REFINEMENT_SHARD_ITEM_HRIDS = Object.values(DUNGEON_CHEST_CONFIG)
        .map((config) => config.refinementShardItemHrid)
        .filter(Boolean);
    const REFINEMENT_CHEST_TO_BASE_CHEST_HRID = Object.fromEntries(
        Object.entries(DUNGEON_CHEST_CONFIG)
            .filter(([, config]) => config?.refinementChestItemHrid)
            .map(([chestItemHrid, config]) => [config.refinementChestItemHrid, chestItemHrid])
    );
    const REFINEMENT_SHARD_TO_BASE_CHEST_HRID = Object.fromEntries(
        Object.entries(DUNGEON_CHEST_CONFIG)
            .filter(([, config]) => config?.refinementShardItemHrid)
            .map(([chestItemHrid, config]) => [config.refinementShardItemHrid, chestItemHrid])
    );
    const TIME_CALCULATOR_ITEM_HRIDS = [
        ...DUNGEON_CHEST_ITEM_HRIDS,
        ...REFINEMENT_CHEST_ITEM_HRIDS,
        ...KEY_FRAGMENT_ITEM_HRIDS,
    ];

    const DUNGEON_MATERIAL_ITEM_HRIDS = new Set(
        [
            ...Object.values(DUNGEON_TOKEN_SHOP_COSTS).flatMap((shopMap) => Object.keys(shopMap)),
            ...REFINEMENT_SHARD_ITEM_HRIDS,
        ]
    );
    const DUNGEON_RELATED_ITEM_HRIDS = new Set([
        ...Object.values(DUNGEON_CHEST_CONFIG).flatMap((config) => [
            config.entryKeyItemHrid,
            config.keyItemHrid,
            config.tokenItemHrid,
            config.refinementChestItemHrid,
            config.refinementShardItemHrid,
            ...(config.drops || []).map((drop) => drop.itemHrid),
        ]),
        "/items/chimerical_quiver",
        "/items/sinister_cape",
        "/items/enchanted_cloak",
    ].filter(Boolean));

    const isZh = String(localStorage.getItem("i18nextLng") || "").toLowerCase().startsWith("zh");

    function clearCaches() {
        state.itemTimeCache.clear();
        state.itemTooltipDataCache.clear();
        state.essencePlanCache.clear();
        state.fixedDecomposePlanCache.clear();
        state.fixedEnhancedEssencePlanCache.clear();
        state.fixedTransmutePlanCache.clear();
        state.fixedAttachedRareTooltipPlanCache.clear();
        state.attachedRareYieldCache.clear();
        state.itemTargetRelationCache.clear();
        state.skillingScrollTimeSavingsCache.clear();
        state.itemFailureReasonCache.clear();
        state.activeItemSolveSet.clear();
        state.cyclicSolveDepth = 0;
        state.outputActionCache = null;
        state.maxEnhancementByItem = null;
    }

    function clearStructuralCaches() {
        state.outputActionCache = null;
    }

    function decodeEscapedJsonString(value) {
        if (typeof value !== "string") {
            return "";
        }
        try {
            return JSON.parse(`"${value.replace(/"/g, '\\"')}"`);
        } catch (_error) {
            return value;
        }
    }

    const LZ_BASE64_KEY_STRING = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    const LZ_BASE64_REVERSE_DICT = Object.create(null);
    for (let i = 0; i < LZ_BASE64_KEY_STRING.length; i += 1) {
        LZ_BASE64_REVERSE_DICT[LZ_BASE64_KEY_STRING.charAt(i)] = i;
    }

    function lzDecompress(length, resetValue, getNextValue) {
        const dictionary = [];
        let enlargeIn = 4;
        let dictSize = 4;
        let numBits = 3;
        let entry = "";
        const result = [];
        const data = {
            val: getNextValue(0),
            position: resetValue,
            index: 1,
        };
        for (let i = 0; i < 3; i += 1) {
            dictionary[i] = i;
        }

        let bits = 0;
        let maxPower = Math.pow(2, 2);
        let power = 1;
        while (power !== maxPower) {
            const resb = data.val & data.position;
            data.position >>= 1;
            if (data.position === 0) {
                data.position = resetValue;
                data.val = getNextValue(data.index++);
            }
            bits |= (resb > 0 ? 1 : 0) * power;
            power <<= 1;
        }

        let c = bits;
        if (c === 0) {
            bits = 0;
            maxPower = Math.pow(2, 8);
            power = 1;
            while (power !== maxPower) {
                const resb = data.val & data.position;
                data.position >>= 1;
                if (data.position === 0) {
                    data.position = resetValue;
                    data.val = getNextValue(data.index++);
                }
                bits |= (resb > 0 ? 1 : 0) * power;
                power <<= 1;
            }
            c = String.fromCharCode(bits);
        } else if (c === 1) {
            bits = 0;
            maxPower = Math.pow(2, 16);
            power = 1;
            while (power !== maxPower) {
                const resb = data.val & data.position;
                data.position >>= 1;
                if (data.position === 0) {
                    data.position = resetValue;
                    data.val = getNextValue(data.index++);
                }
                bits |= (resb > 0 ? 1 : 0) * power;
                power <<= 1;
            }
            c = String.fromCharCode(bits);
        } else if (c === 2) {
            return "";
        } else {
            c = "";
        }

        dictionary[3] = c;
        let w = c;
        result.push(c);

        while (true) {
            if (data.index > length) {
                return "";
            }

            bits = 0;
            maxPower = Math.pow(2, numBits);
            power = 1;
            while (power !== maxPower) {
                const resb = data.val & data.position;
                data.position >>= 1;
                if (data.position === 0) {
                    data.position = resetValue;
                    data.val = getNextValue(data.index++);
                }
                bits |= (resb > 0 ? 1 : 0) * power;
                power <<= 1;
            }

            c = bits;
            if (c === 0) {
                bits = 0;
                maxPower = Math.pow(2, 8);
                power = 1;
                while (power !== maxPower) {
                    const resb = data.val & data.position;
                    data.position >>= 1;
                    if (data.position === 0) {
                        data.position = resetValue;
                        data.val = getNextValue(data.index++);
                    }
                    bits |= (resb > 0 ? 1 : 0) * power;
                    power <<= 1;
                }
                dictionary[dictSize++] = String.fromCharCode(bits);
                c = dictSize - 1;
                enlargeIn -= 1;
            } else if (c === 1) {
                bits = 0;
                maxPower = Math.pow(2, 16);
                power = 1;
                while (power !== maxPower) {
                    const resb = data.val & data.position;
                    data.position >>= 1;
                    if (data.position === 0) {
                        data.position = resetValue;
                        data.val = getNextValue(data.index++);
                    }
                    bits |= (resb > 0 ? 1 : 0) * power;
                    power <<= 1;
                }
                dictionary[dictSize++] = String.fromCharCode(bits);
                c = dictSize - 1;
                enlargeIn -= 1;
            } else if (c === 2) {
                return result.join("");
            }

            if (enlargeIn === 0) {
                enlargeIn = Math.pow(2, numBits);
                numBits += 1;
            }

            if (dictionary[c]) {
                entry = dictionary[c];
            } else if (c === dictSize) {
                entry = w + w.charAt(0);
            } else {
                return null;
            }
            result.push(entry);

            dictionary[dictSize++] = w + entry.charAt(0);
            enlargeIn -= 1;
            w = entry;

            if (enlargeIn === 0) {
                enlargeIn = Math.pow(2, numBits);
                numBits += 1;
            }
        }
    }

    function getLzStringHelper() {
        const runtimeLz = typeof LZString !== "undefined" ? LZString : window.LZString;
        if (runtimeLz && typeof runtimeLz.decompressFromUTF16 === "function") {
            return runtimeLz;
        }
        return {
            decompressFromUTF16(compressed) {
                if (compressed == null) {
                    return "";
                }
                if (compressed === "") {
                    return null;
                }
                return lzDecompress(compressed.length, 16384, (index) => compressed.charCodeAt(index) - 32);
            },
            decompressFromBase64(compressed) {
                if (compressed == null) {
                    return "";
                }
                const normalized = String(compressed || "").replace(/[^A-Za-z0-9+/=]/g, "");
                if (!normalized) {
                    return null;
                }
                return lzDecompress(normalized.length, 32, (index) => LZ_BASE64_REVERSE_DICT[normalized.charAt(index)] || 0);
            },
        };
    }

    function parseLocalizedItemNamesFromScript(scriptText) {
        if (typeof scriptText !== "string" || !scriptText.includes('"/items/')) {
            return 0;
        }
        let added = 0;
        const regex = /"((?:\\\/|\/)items\/[^"]+)":\s*"((?:\\.|[^"\\])*)"/g;
        let match;
        while ((match = regex.exec(scriptText))) {
            const rawKey = match[1].replace(/\\\//g, "/");
            const decodedValue = decodeEscapedJsonString(match[2]);
            if (!rawKey.startsWith("/items/") || !decodedValue) {
                continue;
            }
            if (/^[\x00-\x7F]+$/.test(decodedValue)) {
                continue;
            }
            state.localizedItemNameMap.set(rawKey, decodedValue);
            added += 1;
        }
        return added;
    }

    async function loadLocalizedItemNames() {
        if (!isZh || state.translationLoadStarted || state.localizedItemNameMap.size > 0) {
            return;
        }
        state.translationLoadStarted = true;
        try {
            const scriptUrls = Array.from(document.querySelectorAll("script[src]"))
                .map((node) => node.src)
                .filter((src) => src && src.startsWith(location.origin) && src.endsWith(".js"));
            let added = 0;
            for (const src of scriptUrls) {
                if (state.localizedItemNameMap.size > 500) {
                    break;
                }
                try {
                    const response = await fetch(src, { credentials: "same-origin", cache: "force-cache" });
                    if (!response.ok) {
                        continue;
                    }
                    const text = await response.text();
                    added += parseLocalizedItemNamesFromScript(text);
                } catch (_error) {
                    continue;
                }
            }
            if (added > 0) {
                refreshOpenTooltips();
            }
        } finally {
            state.translationLoadStarted = false;
        }
    }

    function getLocalizedItemName(itemHrid, fallbackName = "") {
        if (!itemHrid) {
            return fallbackName || "";
        }
        if (isZh && MWITOOLS_ZH_ITEM_NAME_OVERRIDES[itemHrid]) {
            return MWITOOLS_ZH_ITEM_NAME_OVERRIDES[itemHrid];
        }
        if (isZh && ESSENCE_SOURCE_NAME_ZH[itemHrid]) {
            return ESSENCE_SOURCE_NAME_ZH[itemHrid];
        }
        if (isZh && itemHrid === "/items/catalyst_of_coinification") {
            return "点金催化剂";
        }
        if (isZh && itemHrid === "/items/catalyst_of_decomposition") {
            return "分解催化剂";
        }
        if (isZh && itemHrid === "/items/catalyst_of_transmutation") {
            return "转化催化剂";
        }
        if (isZh && itemHrid === "/items/prime_catalyst") {
            return "至高催化剂";
        }
        const localized = state.localizedItemNameMap.get(itemHrid);
        if (localized) {
            return localized;
        }
        const detailName = state.itemDetailMap?.[itemHrid]?.name || "";
        if (detailName && (!isZh || /[^\x00-\x7F]/.test(detailName))) {
            return detailName;
        }
        if (isZh) {
            loadLocalizedItemNames();
        }
        return fallbackName || detailName || itemHrid;
    }

    function isMissingDerivedRuntimeState() {
        return !state.characterItemByLocationMap ||
            !state.characterSkillMap ||
            !state.communityActionTypeBuffsDict ||
            !state.houseActionTypeBuffsDict ||
            !state.achievementActionTypeBuffsDict ||
            !state.personalActionTypeBuffsDict ||
            !state.consumableActionTypeBuffsDict ||
            !state.equipmentActionTypeBuffsDict;
    }

    function getContainerValues(container) {
        if (!container) {
            return [];
        }
        if (container instanceof Map) {
            return Array.from(container.values());
        }
        if (Array.isArray(container)) {
            return container.slice();
        }
        if (typeof container === "object") {
            return Object.values(container);
        }
        return [];
    }

    function getContainerValue(container, key) {
        if (!container || !key) {
            return null;
        }
        if (container instanceof Map) {
            return container.get(key) || null;
        }
        if (typeof container === "object") {
            return container[key] || null;
        }
        return null;
    }

    function isLikelyGameState(candidate) {
        return Boolean(
            candidate &&
            typeof candidate === "object" &&
            candidate.character &&
            (candidate.actionDetailMaps || candidate.itemDetailDict || candidate.characterItemMap) &&
            (Object.prototype.hasOwnProperty.call(candidate, "gameConn") ||
                Object.prototype.hasOwnProperty.call(candidate, "combatUnit") ||
                Object.prototype.hasOwnProperty.call(candidate, "characterActions"))
        );
    }

    function findGameStateFromFiber(rootFiber) {
        if (!rootFiber || typeof rootFiber !== "object") {
            return null;
        }
        const queue = [rootFiber];
        const visited = new Set();
        let steps = 0;
        while (queue.length > 0 && steps < 20000) {
            const fiber = queue.shift();
            if (!fiber || typeof fiber !== "object" || visited.has(fiber)) {
                continue;
            }
            visited.add(fiber);
            steps += 1;

            const candidate = fiber.stateNode?.state;
            if (isLikelyGameState(candidate)) {
                return candidate;
            }

            if (fiber.child) {
                queue.push(fiber.child);
            }
            if (fiber.sibling) {
                queue.push(fiber.sibling);
            }
        }
        return null;
    }

    function getGameState() {
        const gamePage = document.querySelector('[class^="GamePage"]');
        if (gamePage) {
            const reactKey = Object.keys(gamePage).find((key) => key.startsWith("__reactFiber$"));
            if (reactKey) {
                const fiberNode = gamePage[reactKey];
                const directState = fiberNode?.return?.stateNode?.state || null;
                if (isLikelyGameState(directState)) {
                    return directState;
                }
            }
        }

        const rootElement = document.getElementById("root");
        let rootContainer = rootElement?._reactRootContainer || null;
        if (!rootContainer) {
            const fallbackRoot = Array.from(document.querySelectorAll("div")).find((el) =>
                Object.prototype.hasOwnProperty.call(el, "_reactRootContainer")
            );
            rootContainer = fallbackRoot?._reactRootContainer || null;
        }
        return findGameStateFromFiber(rootContainer?.current || null);
    }

    function normalizeActionDetailMap(obj) {
        if (!obj || typeof obj !== "object") {
            return null;
        }
        if (obj.actionDetailMap && typeof obj.actionDetailMap === "object") {
            return obj.actionDetailMap;
        }
        if (!obj.actionDetailMaps || typeof obj.actionDetailMaps !== "object") {
            return null;
        }
        const flattened = {};
        for (const actionMap of Object.values(obj.actionDetailMaps)) {
            if (actionMap && typeof actionMap === "object") {
                Object.assign(flattened, actionMap);
            }
        }
        return Object.keys(flattened).length > 0 ? flattened : null;
    }

    function normalizeItemDetailMap(obj) {
        if (!obj || typeof obj !== "object") {
            return null;
        }
        if (obj.itemDetailMap && typeof obj.itemDetailMap === "object") {
            return obj.itemDetailMap;
        }
        if (obj.itemDetailDict && typeof obj.itemDetailDict === "object") {
            return obj.itemDetailDict;
        }
        return null;
    }

    function normalizeShopItemDetailMap(obj) {
        if (!obj || typeof obj !== "object") {
            return null;
        }
        if (obj.shopItemDetailMap && typeof obj.shopItemDetailMap === "object") {
            return obj.shopItemDetailMap;
        }
        return null;
    }

    function updateClientData(obj) {
        if (!obj || typeof obj !== "object") {
            return;
        }
        const actionDetailMap = normalizeActionDetailMap(obj);
        const itemDetailMap = normalizeItemDetailMap(obj);
        const shopItemDetailMap = normalizeShopItemDetailMap(obj);
        if (actionDetailMap) {
            state.actionDetailMap = actionDetailMap;
        }
        if (itemDetailMap) {
            state.itemDetailMap = itemDetailMap;
            state.itemNameToHrid.clear();
            for (const [itemHrid, item] of Object.entries(itemDetailMap)) {
                if (item?.name) {
                    state.itemNameToHrid.set(item.name, itemHrid);
                }
            }
        }
        if (shopItemDetailMap) {
            state.shopItemDetailMap = shopItemDetailMap;
        }
        if (obj.enhancementLevelTotalBonusMultiplierTable) {
            state.enhancementLevelTotalBonusMultiplierTable = obj.enhancementLevelTotalBonusMultiplierTable;
        }
        if (isZh) {
            loadLocalizedItemNames();
        }
    }

    function updateCharacterData(obj, options = {}) {
        const { refreshTooltips = true } = options;
        if (!obj || typeof obj !== "object") {
            return;
        }
        let changed = false;
        if (obj.characterSkills) {
            state.characterSkills = obj.characterSkills;
            changed = true;
        }
        if (obj.characterSkillMap) {
            state.characterSkillMap = obj.characterSkillMap;
            state.characterSkills = getContainerValues(obj.characterSkillMap);
            changed = true;
        }
        if (obj.characterItems) {
            state.characterItems = obj.characterItems;
            changed = true;
        }
        if (obj.characterItemMap) {
            state.characterItemMap = obj.characterItemMap;
            state.characterItems = getContainerValues(obj.characterItemMap);
            changed = true;
        }
        if (obj.characterItemByLocationMap) {
            state.characterItemByLocationMap = obj.characterItemByLocationMap;
            changed = true;
        }
        if (obj.characterHouseRoomMap) {
            state.characterHouseRoomMap = obj.characterHouseRoomMap;
            changed = true;
        }
        if (obj.characterHouseRoomDict) {
            state.characterHouseRoomMap = obj.characterHouseRoomDict;
            changed = true;
        }
        if (obj.actionTypeDrinkSlotsMap) {
            state.actionTypeDrinkSlotsMap = obj.actionTypeDrinkSlotsMap;
            changed = true;
        }
        if (obj.actionTypeDrinkSlotsDict) {
            state.actionTypeDrinkSlotsMap = obj.actionTypeDrinkSlotsDict;
            changed = true;
        }
        if (obj.characterLoadoutDict) {
            state.characterLoadoutDict = obj.characterLoadoutDict;
            changed = true;
        }
        if (obj.characterSetting) {
            state.characterSetting = obj.characterSetting;
            changed = true;
        }
        if (typeof obj.currentCharacterName === "string") {
            state.currentCharacterName = obj.currentCharacterName.trim();
            changed = true;
        }
        if (obj.communityActionTypeBuffsDict) {
            state.communityActionTypeBuffsDict = obj.communityActionTypeBuffsDict;
            changed = true;
        }
        if (obj.houseActionTypeBuffsDict) {
            state.houseActionTypeBuffsDict = obj.houseActionTypeBuffsDict;
            changed = true;
        }
        if (obj.achievementActionTypeBuffsDict) {
            state.achievementActionTypeBuffsDict = obj.achievementActionTypeBuffsDict;
            changed = true;
        }
        if (obj.personalActionTypeBuffsDict) {
            state.personalActionTypeBuffsDict = obj.personalActionTypeBuffsDict;
            changed = true;
        }
        if (obj.consumableActionTypeBuffsDict) {
            state.consumableActionTypeBuffsDict = obj.consumableActionTypeBuffsDict;
            changed = true;
        }
        if (obj.equipmentActionTypeBuffsDict) {
            state.equipmentActionTypeBuffsDict = obj.equipmentActionTypeBuffsDict;
            changed = true;
        }
        if (obj.mooPassActionTypeBuffsDict) {
            state.mooPassActionTypeBuffsDict = obj.mooPassActionTypeBuffsDict;
            changed = true;
        }
        if (changed) {
            if (isMissingDerivedRuntimeState()) {
                hydrateFromReactState({ refreshTooltips });
            }
            if (state.timeCalculatorContainer?.isConnected &&
                state.timeCalculatorLoadedCharacterId !== null &&
                state.timeCalculatorLoadedCharacterId !== getCurrentCharacterId()) {
                rerenderTimeCalculatorPanel();
            }
        }
    }

    function hydrateFromReactState(options = {}) {
        const { refreshTooltips = true } = options;
        try {
            const appState = getGameState();
            if (!appState) {
                return false;
            }
            updateClientData(appState);
            updateCharacterData({
                characterSkillMap: appState.characterSkillMap,
                characterItemMap: appState.characterItemMap,
                characterItemByLocationMap: appState.characterItemByLocationMap,
                characterHouseRoomDict: appState.characterHouseRoomDict,
                actionTypeDrinkSlotsDict: appState.actionTypeDrinkSlotsDict,
                characterLoadoutDict: appState.characterLoadoutDict,
                characterSetting: appState.characterSetting,
                currentCharacterName: appState.character?.name
                    || appState.characterDTO?.name
                    || appState.selectedCharacter?.name
                    || appState.characterSetting?.name
                    || appState.characterSetting?.characterName
                    || "",
                communityActionTypeBuffsDict: appState.communityActionTypeBuffsDict,
                houseActionTypeBuffsDict: appState.houseActionTypeBuffsDict,
                achievementActionTypeBuffsDict: appState.achievementActionTypeBuffsDict,
                personalActionTypeBuffsDict: appState.personalActionTypeBuffsDict,
                consumableActionTypeBuffsDict: appState.consumableActionTypeBuffsDict,
                equipmentActionTypeBuffsDict: appState.equipmentActionTypeBuffsDict,
                mooPassActionTypeBuffsDict: appState.mooPassActionTypeBuffsDict,
            }, { refreshTooltips });
            return true;
        } catch (error) {
            console.error("[ICTime] Failed to hydrate runtime state from React.", error);
            return false;
        }
    }

    function ensureRuntimeStateFresh(force = false, options = {}) {
        const now = Date.now();
        if (!force && now - state.lastRuntimeHydrationAt < 1000) {
            return false;
        }
        state.lastRuntimeHydrationAt = now;
        return hydrateFromReactState(options);
    }

    function loadCachedClientData() {
        const raw = localStorage.getItem("initClientData");
        if (!raw) {
            return false;
        }

        if (state.cachedInitClientData && state.cachedInitClientDataRaw === raw) {
            updateClientData(state.cachedInitClientData);
            return true;
        }

        const lz = getLzStringHelper();
        const parsers = [
            () => JSON.parse(raw),
            () => {
                if (!lz || typeof lz.decompressFromUTF16 !== "function") {
                    return null;
                }
                const decompressed = lz.decompressFromUTF16(raw);
                return decompressed ? JSON.parse(decompressed) : null;
            },
            () => {
                if (!lz || typeof lz.decompressFromBase64 !== "function") {
                    return null;
                }
                const decompressed = lz.decompressFromBase64(raw);
                return decompressed ? JSON.parse(decompressed) : null;
            },
        ];

        for (const parser of parsers) {
            try {
                const parsed = parser();
                if (parsed && typeof parsed === "object") {
                    state.cachedInitClientData = parsed;
                    state.cachedInitClientDataRaw = raw;
                    updateClientData(parsed);
                    return true;
                }
            } catch (_error) {
                // Try next parser.
            }
        }

        console.error("[ICTime] Failed to parse initClientData with all parsers.");
        return false;
    }

    function hookWebSocket() {
        const descriptor = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
        if (!descriptor?.get) {
            return;
        }
        const originalGet = descriptor.get;
        descriptor.get = function hookedData() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket)) {
                return originalGet.call(this);
            }
            const url = socket.url || "";
            if (!/api(-test)?\.milkywayidle(cn)?\.com\/ws/.test(url)) {
                return originalGet.call(this);
            }
            const message = originalGet.call(this);
            Object.defineProperty(this, "data", { value: message });
            handleSocketMessage(message);
            return message;
        };
        Object.defineProperty(MessageEvent.prototype, "data", descriptor);
    }

    function handleSocketMessage(message) {
        try {
            const obj = JSON.parse(message);
            if (!obj || typeof obj !== "object") {
                return;
            }
            if (obj.type === "init_client_data") {
                updateClientData(obj);
                return;
            }
            if (obj.type === "init_character_data") {
                updateCharacterData(obj);
                return;
            }
            updateCharacterData(obj);
        } catch {
            return;
        }
    }

    function getItemHridFromTooltip(tooltip) {
        const anchor = tooltip.querySelector('a[href*="#"]');
        if (anchor) {
            const href = anchor.getAttribute("href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex >= 0) {
                return `/items/${href.slice(hashIndex + 1)}`;
            }
        }

        const nameSpans = tooltip.querySelectorAll("div.ItemTooltipText_name__2JAHA span");
        for (const span of nameSpans) {
            const text = (span.textContent || "").trim();
            if (state.itemNameToHrid.has(text)) {
                return state.itemNameToHrid.get(text);
            }
        }

        const hoveredHrid = getItemHridFromHoveredSource(tooltip);
        if (hoveredHrid) {
            return hoveredHrid;
        }
        return null;
    }

    function getItemEnhancementLevelFromTooltip(tooltip) {
        if (!tooltip) {
            return 0;
        }
        const nameContainer = tooltip.querySelector("div.ItemTooltipText_name__2JAHA") || tooltip;
        const text = (nameContainer.textContent || "").replace(/\s+/g, " ").trim();
        const match = text.match(/\+(\d+)\b/);
        return match ? Math.max(0, Number(match[1] || 0)) : 0;
    }

    function extractItemHridFromElement(element) {
        if (!element) {
            return null;
        }
        const isSvgUseElement = typeof SVGUseElement !== "undefined" && element instanceof SVGUseElement;
        if (isSvgUseElement) {
            const href = element.getAttribute("href") || element.getAttribute("xlink:href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex >= 0 && href.includes("items_sprite")) {
                return `/items/${href.slice(hashIndex + 1).trim()}`;
            }
        }
        const uses = element.querySelectorAll("use");
        for (const use of uses) {
            const href = use.getAttribute("href") || use.getAttribute("xlink:href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex < 0) {
                continue;
            }
            const iconId = href.slice(hashIndex + 1).trim();
            if (!iconId) {
                continue;
            }
            if (href.includes("items_sprite")) {
                return `/items/${iconId}`;
            }
        }
        return null;
    }

    function runUiGuarded(label, fn) {
        try {
            return fn();
        } catch (error) {
            console.error(`[ICTime] ${label} failed.`, error);
            return null;
        }
    }

    function getItemHridFromHoveredSource(tooltip) {
        if (state.lastHoveredItemHrid && Date.now() - state.lastHoveredItemAt < 2000) {
            return state.lastHoveredItemHrid;
        }
        return null;
    }

    function trackHoveredItem(target) {
        if (!(target instanceof Element)) {
            return false;
        }
        let node = target;
        let depth = 0;
        while (node && depth < 6) {
            const itemHrid = extractItemHridFromElement(node);
            if (itemHrid) {
                state.lastHoveredItemHrid = itemHrid;
                state.lastHoveredItemAt = Date.now();
                return true;
            }
            node = node.parentElement;
            depth += 1;
        }
        return false;
    }

    function queueTooltipRefresh(delay = 180) {
        if (state.isShutDown) {
            return;
        }
        if (state.tooltipRefreshTimer) {
            clearTimeout(state.tooltipRefreshTimer);
        }
        state.tooltipRefreshTimer = setTimeout(() => {
            state.tooltipRefreshTimer = 0;
            refreshOpenTooltips();
        }, delay);
    }

    function getTooltipContentContainer(tooltip) {
        return (
            tooltip.querySelector(".ItemTooltipText_itemTooltipText__zFq3A") ||
            tooltip.querySelector('[class*="ItemTooltipText_itemTooltipText"]') ||
            tooltip.querySelector('[class*="ItemTooltipText_text"]')
        );
    }

    function buildOutputActionCache() {
        const cache = new Map();
        if (!state.actionDetailMap) {
            return cache;
        }

        const gatheringCandidates = new Map();

        for (const action of Object.values(state.actionDetailMap)) {
            if (!action || !SUPPORTED_ACTION_TYPES.has(action.type)) {
                continue;
            }
            const isProduction = Array.isArray(action.inputItems) && action.inputItems.length > 0;
            if (!isProduction) {
                continue;
            }
            for (const output of action.outputItems || []) {
                if (output?.itemHrid && !cache.has(output.itemHrid)) {
                    cache.set(output.itemHrid, action.hrid);
                }
            }
        }

        for (const action of Object.values(state.actionDetailMap)) {
            if (!action || !SUPPORTED_ACTION_TYPES.has(action.type)) {
                continue;
            }
            const isGathering = !action.inputItems || action.inputItems.length === 0;
            if (!isGathering) {
                continue;
            }
            for (const drop of action.dropTable || []) {
                if (drop?.itemHrid) {
                    const current = gatheringCandidates.get(drop.itemHrid);
                    if (isBetterGatheringSource(action, drop.itemHrid, current)) {
                        gatheringCandidates.set(drop.itemHrid, action.hrid);
                    }
                }
                const processed = PROCESSABLE_ITEM_MAP.get(drop?.itemHrid);
                if (processed) {
                    const currentProcessed = gatheringCandidates.get(processed);
                    if (isBetterGatheringSource(action, processed, currentProcessed)) {
                        gatheringCandidates.set(processed, action.hrid);
                    }
                }
            }
        }

        for (const [itemHrid, actionHrid] of gatheringCandidates.entries()) {
            if (!cache.has(itemHrid)) {
                cache.set(itemHrid, actionHrid);
            }
        }

        return cache;
    }

    function getBaseGatheringOutputCount(action, targetItemHrid) {
        let count = 0;
        for (const drop of action?.dropTable || []) {
            const average = (drop.dropRate || 0) * (((drop.minCount || 0) + (drop.maxCount || 0)) / 2);
            if (drop.itemHrid === targetItemHrid) {
                count += average;
            }
            if (PROCESSABLE_ITEM_MAP.get(drop.itemHrid) === targetItemHrid) {
                count += average / 2;
            }
        }
        return count;
    }

    function isDedicatedGatheringAction(action, targetItemHrid) {
        const drops = action?.dropTable || [];
        if (drops.length !== 1) {
            return false;
        }
        const onlyDrop = drops[0];
        return onlyDrop?.itemHrid === targetItemHrid && Number(onlyDrop.dropRate || 0) >= 1;
    }

    function isBetterGatheringSource(candidateAction, targetItemHrid, currentActionHrid) {
        if (!currentActionHrid) {
            return true;
        }
        const currentAction = state.actionDetailMap?.[currentActionHrid];
        if (!currentAction) {
            return true;
        }

        const candidateDedicated = isDedicatedGatheringAction(candidateAction, targetItemHrid);
        const currentDedicated = isDedicatedGatheringAction(currentAction, targetItemHrid);
        if (candidateDedicated !== currentDedicated) {
            return candidateDedicated;
        }

        const candidateBaseCount = getBaseGatheringOutputCount(candidateAction, targetItemHrid);
        const currentBaseCount = getBaseGatheringOutputCount(currentAction, targetItemHrid);
        if (candidateBaseCount !== currentBaseCount) {
            return candidateBaseCount > currentBaseCount;
        }

        const candidateBaseSeconds = Number(candidateAction.baseTimeCost || 0);
        const currentBaseSeconds = Number(currentAction.baseTimeCost || 0);
        if (candidateBaseSeconds !== currentBaseSeconds) {
            return candidateBaseSeconds < currentBaseSeconds;
        }

        return false;
    }

    function findActionForItem(itemHrid) {
        if (!state.outputActionCache) {
            state.outputActionCache = buildOutputActionCache();
        }
        const actionHrid = state.outputActionCache.get(itemHrid);
        return actionHrid ? state.actionDetailMap?.[actionHrid] || null : null;
    }

    function getEnhancementBonusMultiplier(level) {
        if (state.enhancementLevelTotalBonusMultiplierTable && state.enhancementLevelTotalBonusMultiplierTable[level] != null) {
            return state.enhancementLevelTotalBonusMultiplierTable[level];
        }
        return (ENHANCEMENT_BONUS[level] || 0) / 100;
    }

    function getBuffAmount(buff) {
        if (!buff) {
            return 0;
        }
        return (
            Number(buff.flatBoost || 0) +
            Number(buff.flatBoostLevelBonus || 0) +
            Number(buff.ratioBoost || 0) +
            Number(buff.ratioBoostLevelBonus || 0)
        );
    }

    function clamp01(value) {
        if (!Number.isFinite(value)) {
            return 0;
        }
        return Math.max(0, Math.min(1, value));
    }

    function getActionTypeBuffs(sourceKey, actionTypeHrid) {
        const dict = state[sourceKey];
        const buffs = dict?.[actionTypeHrid];
        return Array.isArray(buffs) ? buffs : [];
    }

    function sumBuffsByType(buffs, typeHrid) {
        let total = 0;
        for (const buff of buffs) {
            if (buff?.typeHrid !== typeHrid) {
                continue;
            }
            total += getBuffAmount(buff);
        }
        return total;
    }

    function getToolSlotForActionType(actionTypeHrid) {
        const skillId = actionTypeHrid?.split("/").pop() || "";
        return skillId ? `/item_locations/${skillId}_tool` : "";
    }

    function parseWearableReference(rawValue) {
        if (!rawValue) {
            return null;
        }
        const parts = String(rawValue).split("::");
        if (parts.length < 4) {
            return null;
        }
        return {
            itemHrid: parts[2] || "",
            enhancementLevel: Number(parts[3] || 0) || 0,
        };
    }

    function buildMaxEnhancementByItem() {
        if (state.maxEnhancementByItem) {
            return state.maxEnhancementByItem;
        }
        const maxByItem = new Map();
        for (const item of getContainerValues(state.characterItemMap)) {
            if (!item?.itemHrid) {
                continue;
            }
            if (!Number.isFinite(Number(item.count)) || Number(item.count) <= 0) {
                continue;
            }
            const enhancement = Math.max(0, Number(item.enhancementLevel || 0));
            const current = maxByItem.get(item.itemHrid);
            if (!Number.isFinite(current) || enhancement > current) {
                maxByItem.set(item.itemHrid, enhancement);
            }
        }
        state.maxEnhancementByItem = maxByItem;
        return maxByItem;
    }

    function listLoadouts() {
        return Object.values(state.characterLoadoutDict || {}).filter(Boolean);
    }

    function resolveSkillingLoadout(actionTypeHrid) {
        const loadouts = listLoadouts()
            .filter((loadout) => loadout?.isDefault)
            .sort((a, b) => Number(a?.id || 0) - Number(b?.id || 0));
        const direct = loadouts.find((loadout) => loadout.actionTypeHrid === actionTypeHrid);
        if (direct) {
            return {
                source: "action",
                loadout: direct,
            };
        }
        const fallback = loadouts.find((loadout) => !loadout.actionTypeHrid);
        if (fallback) {
            return {
                source: "global",
                loadout: fallback,
            };
        }
        return {
            source: "current",
            loadout: null,
        };
    }

    function resolveWearableEnhancement(entry, loadout) {
        if (!entry) {
            return 0;
        }
        if (loadout?.useExactEnhancement) {
            return Math.max(0, Number(entry.enhancementLevel || 0));
        }
        const highest = buildMaxEnhancementByItem().get(entry.itemHrid);
        if (Number.isFinite(highest)) {
            return Math.max(0, highest);
        }
        return Math.max(0, Number(entry.enhancementLevel || 0));
    }

    function getEquippedItems(actionTypeHrid = "") {
        const loadoutInfo = actionTypeHrid ? resolveSkillingLoadout(actionTypeHrid) : { loadout: null };
        const loadout = loadoutInfo.loadout;
        if (loadout?.wearableMap) {
            const items = [];
            for (const [slotKey, rawRef] of Object.entries(loadout.wearableMap || {})) {
                const entry = parseWearableReference(rawRef);
                if (!entry?.itemHrid) {
                    continue;
                }
                items.push({
                    itemHrid: entry.itemHrid,
                    enhancementLevel: resolveWearableEnhancement(entry, loadout),
                    itemLocationHrid: slotKey,
                    count: 1,
                });
            }
            return items;
        }
        const byLocation = state.characterItemByLocationMap;
        if (byLocation instanceof Map) {
            return Array.from(byLocation.values()).filter((item) => item && item.itemLocationHrid !== "/item_locations/inventory");
        }
        if (byLocation && typeof byLocation === "object") {
            return Object.values(byLocation).filter((item) => item && item.itemLocationHrid !== "/item_locations/inventory");
        }
        return (state.characterItems || []).filter((item) => item && item.itemLocationHrid !== "/item_locations/inventory");
    }

    function getSkillLevel(skillHrid) {
        const fromMap = getContainerValue(state.characterSkillMap, skillHrid);
        if (fromMap?.level != null) {
            return Number(fromMap.level) || 0;
        }
        const fromArray = (state.characterSkills || []).find((entry) => entry.skillHrid === skillHrid);
        return Number(fromArray?.level || 0);
    }

    function buildEquipmentNoncombatTotals(actionTypeHrid) {
        const totals = {};
        const toolSlot = getToolSlotForActionType(actionTypeHrid);
        for (const item of getEquippedItems(actionTypeHrid)) {
            const location = item.itemLocationHrid || "";
            if (location.endsWith("_tool") && location !== toolSlot) {
                continue;
            }
            const equipmentDetail = state.itemDetailMap?.[item.itemHrid]?.equipmentDetail;
            if (!equipmentDetail) {
                continue;
            }
            const enhancementMultiplier = getEnhancementBonusMultiplier(item.enhancementLevel || 0);
            const baseStats = equipmentDetail.noncombatStats || {};
            const enhancementStats = equipmentDetail.noncombatEnhancementBonuses || {};
            for (const [key, value] of Object.entries(baseStats)) {
                if (Number.isFinite(Number(value))) {
                    totals[key] = (totals[key] || 0) + Number(value);
                }
            }
            for (const [key, value] of Object.entries(enhancementStats)) {
                if (Number.isFinite(Number(value))) {
                    totals[key] = (totals[key] || 0) + Number(value) * enhancementMultiplier;
                }
            }
        }
        return totals;
    }

    function getDrinkConcentration(actionTypeHrid) {
        const pouch = getEquippedItems(actionTypeHrid).find((item) => item.itemHrid === "/items/guzzling_pouch");
        if (!pouch || !state.itemDetailMap?.["/items/guzzling_pouch"]?.equipmentDetail) {
            return 1;
        }
        const detail = state.itemDetailMap["/items/guzzling_pouch"].equipmentDetail;
        const base = detail.noncombatStats?.drinkConcentration || 0;
        const bonus = detail.noncombatEnhancementBonuses?.drinkConcentration || 0;
        return 1 + base + bonus * getEnhancementBonusMultiplier(pouch.enhancementLevel || 0);
    }

    function getTeaBuffs(actionTypeHrid) {
        const skillId = actionTypeHrid.replace("/action_types/", "");
        const loadoutInfo = resolveSkillingLoadout(actionTypeHrid);
        const concentration = getDrinkConcentration(actionTypeHrid);
        const buffs = {
            efficiencyFraction: 0,
            quantityFraction: 0,
            lessResourceFraction: 0,
            processingFraction: 0,
            rareFindFraction: 0,
            successRateFraction: 0,
            alchemySuccessFraction: 0,
            skillLevelBonus: 0,
            actionLevelPenalty: 0,
            activeTeas: [],
            concentrationMultiplier: concentration,
            durationSeconds: 300 / concentration,
            loadoutInfo,
        };

        const loadoutTeaList = Array.isArray(loadoutInfo.loadout?.drinkItemHrids)
            ? loadoutInfo.loadout.drinkItemHrids.filter(Boolean).map((itemHrid) => ({ itemHrid }))
            : [];
        const currentTeaList = state.actionTypeDrinkSlotsMap?.[actionTypeHrid] || [];
        const teaList = loadoutTeaList.length > 0 ? loadoutTeaList : currentTeaList;
        for (const tea of teaList) {
            if (!tea?.itemHrid) {
                continue;
            }
            buffs.activeTeas.push(tea.itemHrid);
            const teaDetail = state.itemDetailMap?.[tea.itemHrid];
            for (const buff of teaDetail?.consumableDetail?.buffs || []) {
                if (buff.typeHrid === "/buff_types/artisan") {
                    buffs.lessResourceFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/gathering" || buff.typeHrid === "/buff_types/gourmet") {
                    buffs.quantityFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/processing") {
                    buffs.processingFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/rare_find") {
                    buffs.rareFindFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === "/buff_types/efficiency") {
                    buffs.efficiencyFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/success_rate") {
                    buffs.successRateFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === "/buff_types/alchemy_success") {
                    buffs.alchemySuccessFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === `/buff_types/${skillId}_level`) {
                    buffs.skillLevelBonus += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/action_level") {
                    buffs.actionLevelPenalty += buff.flatBoost;
                }
            }
        }

        buffs.quantityFraction *= concentration;
        buffs.lessResourceFraction *= concentration;
        buffs.processingFraction *= concentration;
        buffs.rareFindFraction *= concentration;
        buffs.efficiencyFraction *= concentration;
        buffs.successRateFraction *= concentration;
        buffs.alchemySuccessFraction *= concentration;
        buffs.skillLevelBonus *= concentration;
        buffs.actionLevelPenalty *= concentration;
        return buffs;
    }

    function getEquippedItem(itemHrid) {
        let best = null;
        for (const item of getEquippedItems()) {
            if (item.itemHrid !== itemHrid) {
                continue;
            }
            if (!best || (item.enhancementLevel || 0) > (best.enhancementLevel || 0)) {
                best = item;
            }
        }
        return best;
    }

    function getGlobalActionBuffs(actionTypeHrid) {
        return [
            ...getActionTypeBuffs("communityActionTypeBuffsDict", actionTypeHrid),
            ...getActionTypeBuffs("houseActionTypeBuffsDict", actionTypeHrid),
            ...getActionTypeBuffs("achievementActionTypeBuffsDict", actionTypeHrid),
            ...getActionTypeBuffs("mooPassActionTypeBuffsDict", actionTypeHrid),
        ];
    }

    function getActionSummary(action) {
        const skillId = action.type.replace("/action_types/", "");
        const totals = buildEquipmentNoncombatTotals(action.type);
        const globalBuffs = getGlobalActionBuffs(action.type);
        const teaBuffs = getTeaBuffs(action.type);
        const actionSpeedFraction =
            Number(totals[`${skillId}Speed`] || 0) +
            Number(totals.skillingSpeed || 0) +
            sumBuffsByType(globalBuffs, "/buff_types/action_speed");
        const equipmentEfficiencyFraction =
            Number(totals[`${skillId}Efficiency`] || 0) +
            Number(totals.skillingEfficiency || 0);
        const equipmentRareFindFraction =
            Number(totals[`${skillId}RareFind`] || 0) +
            Number(totals.skillingRareFind || 0);
        const buffEfficiencyFraction =
            sumBuffsByType(globalBuffs, "/buff_types/efficiency") +
            teaBuffs.efficiencyFraction;
        const buffRareFindFraction =
            sumBuffsByType(globalBuffs, "/buff_types/rare_find") +
            teaBuffs.rareFindFraction;
        const processingFraction =
            sumBuffsByType(globalBuffs, "/buff_types/processing") +
            teaBuffs.processingFraction;
        const baseLevel = Math.max(getSkillLevel(action.levelRequirement?.skillHrid), Number(action.levelRequirement?.level || 0));
        const levelBonus =
            sumBuffsByType(globalBuffs, `/buff_types/${skillId}_level`) +
            teaBuffs.skillLevelBonus -
            sumBuffsByType(globalBuffs, "/buff_types/action_level") -
            teaBuffs.actionLevelPenalty;
        const effectiveLevel = baseLevel + levelBonus;
        const levelEfficiencyFraction = Math.max(effectiveLevel - Number(action.levelRequirement?.level || 0), 0) / 100;
        const efficiencyFraction = equipmentEfficiencyFraction + buffEfficiencyFraction + levelEfficiencyFraction;
        const rareFindFraction = equipmentRareFindFraction + buffRareFindFraction;
        const buffQuantityFraction =
            sumBuffsByType(globalBuffs, "/buff_types/gathering") +
            sumBuffsByType(globalBuffs, "/buff_types/gourmet");
        const gatheringQuantityFraction =
            (SUPPORTED_ACTION_TYPES.has(action.type) && (!action.inputItems || action.inputItems.length === 0))
                ? Number(totals.gatheringQuantity || 0) + buffQuantityFraction + teaBuffs.quantityFraction
                : buffQuantityFraction + teaBuffs.quantityFraction;
        const baseSeconds = (action.baseTimeCost || 0) / 1000000000;
        const speedSeconds = Math.max(baseSeconds / (1 + actionSpeedFraction), 3);
        const successRateFraction =
            sumBuffsByType(globalBuffs, "/buff_types/success_rate") +
            teaBuffs.successRateFraction;
        const alchemySuccessFraction =
            sumBuffsByType(globalBuffs, "/buff_types/alchemy_success") +
            teaBuffs.alchemySuccessFraction;
        return {
            seconds: speedSeconds,
            baseSeconds,
            actionSpeedFraction,
            equipmentEfficiencyFraction,
            buffEfficiencyFraction,
            efficiencyFraction,
            equipmentRareFindFraction,
            buffRareFindFraction,
            rareFindFraction,
            processingFraction,
            gatheringQuantityFraction,
            effectiveLevel,
            successRateFraction,
            alchemySuccessFraction,
            teaBuffs,
        };
    }

    function getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary) {
        const sourceItem = state.itemDetailMap?.[sourceItemHrid];
        if (!sourceItem || !actionSummary) {
            return 0;
        }
        const itemLevel = Math.max(1, Number(sourceItem.itemLevel || 0));
        const levelEfficiencyFraction = Math.max(Number(actionSummary.effectiveLevel || 0) - itemLevel, 0) / 100;
        return Math.max(
            0,
            Number(actionSummary.equipmentEfficiencyFraction || 0) +
                Number(actionSummary.buffEfficiencyFraction || 0) +
                levelEfficiencyFraction
        );
    }

    function getAlchemyDecomposeSuccessChance(sourceItemHrid, actionSummary) {
        const sourceItem = state.itemDetailMap?.[sourceItemHrid];
        if (!sourceItem || !actionSummary) {
            return 0;
        }
        const itemLevel = Math.max(1, Number(sourceItem.itemLevel || 0));
        const effectiveLevel = Math.max(0, Number(actionSummary.effectiveLevel || getSkillLevel("/skills/alchemy")));
        const levelMultiplier = effectiveLevel >= itemLevel
            ? 1
            : Math.max(0, 1 - 0.5 * (1 - effectiveLevel / itemLevel));
        const baseChance = 0.6 * levelMultiplier;
        const multipliedChance = baseChance * (1 + Number(actionSummary.alchemySuccessFraction || 0));
        return clamp01(multipliedChance + Number(actionSummary.successRateFraction || 0));
    }

    function getAlchemyTransmuteSuccessChance(sourceItemHrid, actionSummary) {
        const sourceItem = state.itemDetailMap?.[sourceItemHrid];
        if (!sourceItem || !actionSummary) {
            return 0;
        }
        const itemLevel = Math.max(1, Number(sourceItem.itemLevel || 0));
        const effectiveLevel = Math.max(0, Number(actionSummary.effectiveLevel || getSkillLevel("/skills/alchemy")));
        const levelMultiplier = effectiveLevel >= itemLevel
            ? 1
            : Math.max(0, 1 - 0.5 * (1 - effectiveLevel / itemLevel));
        const baseChance = Number(sourceItem.alchemyDetail?.transmuteSuccessRate || 0) * levelMultiplier;
        const multipliedChance = baseChance * (1 + Number(actionSummary.alchemySuccessFraction || 0));
        return clamp01(multipliedChance + Number(actionSummary.successRateFraction || 0));
    }

    function getAlchemyTransmuteCatalystSuccessBonus(catalystItemHrid, fallback = 0) {
        if (!catalystItemHrid) {
            return 0;
        }
        const configured = TRANSMUTE_CATALYST_SUCCESS_BONUSES[catalystItemHrid];
        if (Number.isFinite(configured)) {
            return Math.max(0, Number(configured || 0));
        }
        return Math.max(0, Number(fallback || 0));
    }

    function getAlchemyDecomposeEnhancingEssenceOutput(itemLevel, enhancementLevel) {
        const safeLevel = Math.max(0, Number(itemLevel || 0));
        const safeEnhancementLevel = Math.max(0, Number(enhancementLevel || 0));
        if (safeEnhancementLevel <= 0) {
            return 0;
        }
        return Math.round(2 * (0.5 + 0.1 * Math.pow(1.05, safeLevel)) * Math.pow(2, safeEnhancementLevel));
    }

    function getEnhancedEquipmentEssenceInfo(itemHrid, enhancementLevel, recommendation, catalystItemHrid = "") {
        const safeEnhancementLevel = Math.max(0, Number(enhancementLevel || 0));
        if (safeEnhancementLevel <= 0) {
            return null;
        }
        const itemDetail = state.itemDetailMap?.[itemHrid];
        const decomposeAction = state.actionDetailMap?.["/actions/alchemy/decompose"];
        if (!itemDetail || !decomposeAction || !recommendation) {
            return null;
        }

        const actionSummary = getActionSummary(decomposeAction);
        const catalystMultiplier = catalystItemHrid === "/items/catalyst_of_decomposition"
            ? 1.15
            : catalystItemHrid === "/items/prime_catalyst"
                ? 1.25
                : 1;
        const successChance = clamp01(getAlchemyDecomposeSuccessChance(itemHrid, actionSummary) * catalystMultiplier);
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(itemHrid, actionSummary);
        const efficiencyMultiplier = 1 + efficiencyFraction;
        const essenceOutputCount = getAlchemyDecomposeEnhancingEssenceOutput(itemDetail.itemLevel, safeEnhancementLevel);
        const expectedEssenceCount = essenceOutputCount * successChance * efficiencyMultiplier;
        if (!Number.isFinite(expectedEssenceCount) || expectedEssenceCount <= 0) {
            return null;
        }

        const teaPerAction = actionSummary.seconds / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of actionSummary.teaBuffs.activeTeas) {
            const teaSeconds = calculateItemSeconds(teaItemHrid, new Set([itemHrid]));
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds <= 0) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        let sideOutputSeconds = 0;
        for (const output of itemDetail.alchemyDetail?.decomposeItems || []) {
            if (!output?.itemHrid || output.itemHrid === "/items/enhancing_essence") {
                continue;
            }
            const outputDetail = state.itemDetailMap?.[output.itemHrid];
            if (outputDetail?.categoryHrid === "/item_categories/equipment") {
                continue;
            }
            const outputSeconds = calculateItemSeconds(output.itemHrid, new Set([itemHrid]));
            if (outputSeconds == null || !Number.isFinite(outputSeconds) || outputSeconds <= 0) {
                continue;
            }
            sideOutputSeconds += Number(output.count || 0) * outputSeconds * successChance * efficiencyMultiplier;
        }

        let catalystSecondsTotal = 0;
        if (catalystItemHrid) {
            const catalystSeconds = calculateItemSeconds(catalystItemHrid, new Set([itemHrid]));
            if (catalystSeconds != null && Number.isFinite(catalystSeconds) && catalystSeconds > 0) {
                catalystSecondsTotal = catalystSeconds * successChance;
            }
        }

        const sourceSeconds = Math.max(0, Number(recommendation.totalSeconds || 0)) * efficiencyMultiplier;
        const netSeconds = Math.max(0, actionSummary.seconds + teaSecondsTotal + catalystSecondsTotal + sourceSeconds - sideOutputSeconds);
        return {
            secondsPerEssence: netSeconds / expectedEssenceCount,
            expectedEssenceCount,
            essenceOutputCount,
            successChance,
            efficiencyFraction,
            teaSecondsTotal,
            catalystSecondsTotal,
            sourceSeconds,
            sideOutputSeconds,
            netSeconds,
            actionSeconds: actionSummary.seconds,
            catalystItemHrid,
        };
    }

    function getCountExpectationAtScaledValue(scaledCount, processingChance) {
        const safeScaledCount = Math.max(0, Number(scaledCount || 0));
        if (!safeScaledCount) {
            return {
                totalExpectedCount: 0,
                baseItemExpectedCount: 0,
                processedItemExpectedCount: 0,
            };
        }

        const lowerCount = Math.floor(safeScaledCount);
        const upperCount = Math.ceil(safeScaledCount);
        const upperProbability = safeScaledCount - lowerCount;
        const lowerProbability = 1 - upperProbability;
        const processedExpectedCount =
            processingChance *
            (lowerProbability * Math.floor(lowerCount / 2) + upperProbability * Math.floor(upperCount / 2));
        const baseExpectedCount =
            (1 - processingChance) * safeScaledCount +
            processingChance * (lowerProbability * (lowerCount % 2) + upperProbability * (upperCount % 2));

        return {
            totalExpectedCount: safeScaledCount,
            baseItemExpectedCount: baseExpectedCount,
            processedItemExpectedCount: processedExpectedCount,
        };
    }

    function getDropExpectedCounts(drop, quantityMultiplier, processingFraction) {
        const dropRate = clamp01(Number(drop?.dropRate || 0));
        const minCount = Math.max(0, Number(drop?.minCount || 0));
        const maxCount = Math.max(minCount, Number(drop?.maxCount || 0));
        const processingChance = clamp01(Number(processingFraction || 0));

        if (!dropRate || !maxCount || !quantityMultiplier) {
            return {
                totalExpectedCount: 0,
                baseItemExpectedCount: 0,
                processedItemExpectedCount: 0,
            };
        }

        const scaledMinCount = minCount * quantityMultiplier;
        const scaledMaxCount = maxCount * quantityMultiplier;
        if (scaledMaxCount <= scaledMinCount) {
            const pointExpectation = getCountExpectationAtScaledValue(scaledMinCount, processingChance);
            return {
                totalExpectedCount: pointExpectation.totalExpectedCount * dropRate,
                baseItemExpectedCount: pointExpectation.baseItemExpectedCount * dropRate,
                processedItemExpectedCount: pointExpectation.processedItemExpectedCount * dropRate,
            };
        }

        let totalExpectedCount = 0;
        let baseItemExpectedCount = 0;
        let processedItemExpectedCount = 0;
        const intervalWidth = scaledMaxCount - scaledMinCount;
        const startSegment = Math.floor(scaledMinCount);
        const endSegment = Math.ceil(scaledMaxCount);

        for (let segment = startSegment; segment < endSegment; segment += 1) {
            const segmentStart = Math.max(scaledMinCount, segment);
            const segmentEnd = Math.min(scaledMaxCount, segment + 1);
            const segmentWidth = segmentEnd - segmentStart;
            if (segmentWidth <= 0) {
                continue;
            }

            // Within a unit interval, expected outputs are linear in x, so the midpoint average is exact.
            const midpoint = segmentStart + segmentWidth / 2;
            const segmentExpectation = getCountExpectationAtScaledValue(midpoint, processingChance);
            const weight = (segmentWidth / intervalWidth) * dropRate;
            totalExpectedCount += segmentExpectation.totalExpectedCount * weight;
            baseItemExpectedCount += segmentExpectation.baseItemExpectedCount * weight;
            processedItemExpectedCount += segmentExpectation.processedItemExpectedCount * weight;
        }

        return {
            totalExpectedCount,
            baseItemExpectedCount,
            processedItemExpectedCount,
        };
    }

    function getDirectDisplayOutputCountPerAction(action, targetItemHrid, summary) {
        const isProduction = Array.isArray(action.inputItems) && action.inputItems.length > 0;

        if (isProduction) {
            const directOutput = (action.outputItems || []).find((output) => output.itemHrid === targetItemHrid);
            if (!directOutput) {
                return 0;
            }
            return (directOutput.count || 0) * (1 + summary.gatheringQuantityFraction);
        }

        let count = 0;
        for (const drop of action.dropTable || []) {
            const expectedCounts = getDropExpectedCounts(
                drop,
                1 + summary.gatheringQuantityFraction,
                summary.processingFraction
            );
            if (drop.itemHrid === targetItemHrid) {
                count += PROCESSABLE_ITEM_MAP.has(drop.itemHrid)
                    ? expectedCounts.baseItemExpectedCount
                    : expectedCounts.totalExpectedCount;
            }
            if (PROCESSABLE_ITEM_MAP.get(drop.itemHrid) === targetItemHrid) {
                count += expectedCounts.processedItemExpectedCount;
            }
        }
        return count;
    }

    function getAdditionalProcessedOutputFromInputs(action, targetItemHrid, summary) {
        const isProduction = Array.isArray(action?.inputItems) && action.inputItems.length > 0;
        if (!isProduction || !targetItemHrid) {
            return 0;
        }

        let additionalCount = 0;
        for (const input of getDisplayInputs(action, summary)) {
            const sourceAction = findActionForItem(input.itemHrid);
            if (!sourceAction || sourceAction.hrid === action.hrid) {
                continue;
            }
            const sourceSummary = getActionSummary(sourceAction);
            const sourceBaseOutput = getDirectDisplayOutputCountPerAction(sourceAction, input.itemHrid, sourceSummary);
            if (!Number.isFinite(sourceBaseOutput) || sourceBaseOutput <= 0) {
                continue;
            }
            const sourceProcessedOutput = getDirectDisplayOutputCountPerAction(sourceAction, targetItemHrid, sourceSummary);
            if (!Number.isFinite(sourceProcessedOutput) || sourceProcessedOutput <= 0) {
                continue;
            }
            additionalCount += input.count * (sourceProcessedOutput / sourceBaseOutput);
        }
        return additionalCount;
    }

    function getDisplayOutputCountPerAction(action, targetItemHrid, summary) {
        const directCount = getDirectDisplayOutputCountPerAction(action, targetItemHrid, summary);
        const additionalProcessedCount = getAdditionalProcessedOutputFromInputs(action, targetItemHrid, summary);
        return directCount + additionalProcessedCount;
    }

    function getEffectiveOutputCountPerAction(action, targetItemHrid, summary) {
        return getDisplayOutputCountPerAction(action, targetItemHrid, summary) * (1 + summary.efficiencyFraction);
    }

    function getProcessingProductDetail(action, targetItemHrid, summary) {
        if (!action || !targetItemHrid || !PROCESSABLE_ITEM_MAP.has(targetItemHrid)) {
            return null;
        }

        const processedItemHrid = PROCESSABLE_ITEM_MAP.get(targetItemHrid);
        let expectedCount = 0;
        for (const drop of action.dropTable || []) {
            if (drop.itemHrid !== targetItemHrid) {
                continue;
            }
            const expectedCounts = getDropExpectedCounts(
                drop,
                1 + summary.gatheringQuantityFraction,
                summary.processingFraction
            );
            expectedCount += expectedCounts.processedItemExpectedCount;
        }

        if (!Number.isFinite(expectedCount) || expectedCount <= 0) {
            return null;
        }

        return {
            itemHrid: processedItemHrid,
            itemName: getLocalizedItemName(processedItemHrid),
            expectedCount,
        };
    }

    function getAdjustedInputs(action, summary) {
        const teaBuffs = summary.teaBuffs;
        const isProduction = Array.isArray(action.inputItems) && action.inputItems.length > 0;
        const efficiencyMultiplier = isProduction ? 1 + summary.efficiencyFraction : 1;
        const inputs = [];
        for (const input of action.inputItems || []) {
            inputs.push({
                itemHrid: input.itemHrid,
                count: (input.count || 0) * (1 - teaBuffs.lessResourceFraction) * efficiencyMultiplier,
            });
        }
        if (action.upgradeItemHrid) {
            inputs.push({
                itemHrid: action.upgradeItemHrid,
                count: efficiencyMultiplier,
            });
        }
        return inputs;
    }

    function getDisplayInputs(action, summary) {
        const teaBuffs = summary.teaBuffs;
        const inputs = [];
        for (const input of action.inputItems || []) {
            inputs.push({
                itemHrid: input.itemHrid,
                count: (input.count || 0) * (1 - teaBuffs.lessResourceFraction),
            });
        }
        if (action.upgradeItemHrid) {
            inputs.push({
                itemHrid: action.upgradeItemHrid,
                count: 1,
            });
        }
        return inputs;
    }

    function itemDependsOnCurrentRecipe(itemHrid, targetItemHrid) {
        if (!itemHrid || !targetItemHrid) {
            return false;
        }
        const pending = [itemHrid];
        const visited = new Set();
        while (pending.length > 0) {
            const currentItemHrid = pending.pop();
            if (!currentItemHrid) {
                continue;
            }
            if (currentItemHrid === targetItemHrid) {
                return true;
            }
            if (visited.has(currentItemHrid)) {
                continue;
            }
            visited.add(currentItemHrid);
            if (visited.size > 256) {
                return false;
            }
            const fixedDecomposeSourceItemHrid = FIXED_DECOMPOSE_SOURCE_RULES[currentItemHrid];
            if (fixedDecomposeSourceItemHrid) {
                if (!visited.has(fixedDecomposeSourceItemHrid)) {
                    pending.push(fixedDecomposeSourceItemHrid);
                }
                const decomposeActionTypeHrid = state.actionDetailMap?.["/actions/alchemy/decompose"]?.type || "/action_types/alchemy";
                const decomposeTeaBuffs = getTeaBuffs(decomposeActionTypeHrid);
                for (const teaItemHrid of decomposeTeaBuffs.activeTeas || []) {
                    if (teaItemHrid && teaItemHrid !== currentItemHrid && !visited.has(teaItemHrid)) {
                        pending.push(teaItemHrid);
                    }
                }
            }
            const essenceRule = ESSENCE_DECOMPOSE_RULES[currentItemHrid];
            if (essenceRule) {
                for (const [sourceItemHrid, itemDetail] of Object.entries(state.itemDetailMap || {})) {
                    const match = (itemDetail?.alchemyDetail?.decomposeItems || []).find((entry) => entry.itemHrid === currentItemHrid);
                    if (!match || !isAllowedEssenceDecomposeSource(currentItemHrid, sourceItemHrid)) {
                        continue;
                    }
                    if (!visited.has(sourceItemHrid)) {
                        pending.push(sourceItemHrid);
                    }
                }
                const decomposeActionTypeHrid = state.actionDetailMap?.["/actions/alchemy/decompose"]?.type || "/action_types/alchemy";
                const decomposeTeaBuffs = getTeaBuffs(decomposeActionTypeHrid);
                for (const teaItemHrid of decomposeTeaBuffs.activeTeas || []) {
                    if (teaItemHrid && teaItemHrid !== currentItemHrid && !visited.has(teaItemHrid)) {
                        pending.push(teaItemHrid);
                    }
                }
            }
            const fixedEnhancedEssenceRule = FIXED_ENHANCING_ESSENCE_RULES[currentItemHrid];
            if (fixedEnhancedEssenceRule?.sourceItemHrid && !visited.has(fixedEnhancedEssenceRule.sourceItemHrid)) {
                pending.push(fixedEnhancedEssenceRule.sourceItemHrid);
            }
            const fixedTransmuteRule = FIXED_TRANSMUTE_SOURCE_RULES[currentItemHrid];
            if (fixedTransmuteRule?.sourceItemHrid) {
                if (!visited.has(fixedTransmuteRule.sourceItemHrid)) {
                    pending.push(fixedTransmuteRule.sourceItemHrid);
                }
                const transmuteActionTypeHrid = state.actionDetailMap?.[fixedTransmuteRule.actionHrid || "/actions/alchemy/transmute"]?.type || "/action_types/alchemy";
                const transmuteTeaBuffs = getTeaBuffs(transmuteActionTypeHrid);
                for (const teaItemHrid of transmuteTeaBuffs.activeTeas || []) {
                    if (teaItemHrid && teaItemHrid !== currentItemHrid && !visited.has(teaItemHrid)) {
                        pending.push(teaItemHrid);
                    }
                }
            }
            const action = findActionForItem(currentItemHrid);
            if (!action) {
                continue;
            }
            for (const input of action.inputItems || []) {
                if (input?.itemHrid && !visited.has(input.itemHrid)) {
                    pending.push(input.itemHrid);
                }
            }
            if (action.upgradeItemHrid && !visited.has(action.upgradeItemHrid)) {
                pending.push(action.upgradeItemHrid);
            }
            const teaBuffs = getTeaBuffs(action.type);
            for (const teaItemHrid of teaBuffs.activeTeas || []) {
                if (teaItemHrid && teaItemHrid !== currentItemHrid && !visited.has(teaItemHrid)) {
                    pending.push(teaItemHrid);
                }
            }
        }
        return false;
    }

    function getItemTargetRelationCacheKey(itemHrid, targetItemHrid) {
        return `${itemHrid}=>${targetItemHrid}`;
    }

    function getFixedSourceDecomposeRelationToTarget(itemHrid, targetItemHrid, sourceItemHrid, stack = new Set()) {
        if (!itemHrid || !targetItemHrid || !sourceItemHrid) {
            return null;
        }
        const decomposeAction = state.actionDetailMap?.["/actions/alchemy/decompose"];
        const sourceItemDetail = state.itemDetailMap?.[sourceItemHrid];
        const match = (sourceItemDetail?.alchemyDetail?.decomposeItems || []).find((entry) => entry.itemHrid === itemHrid);
        if (!decomposeAction || !sourceItemDetail || !match) {
            return null;
        }

        const actionSummary = getActionSummary(decomposeAction);
        const bulkMultiplier = Math.max(1, Number(sourceItemDetail?.alchemyDetail?.bulkMultiplier || 1));
        const outputCount = Number(match.count || 0) * bulkMultiplier;
        if (!Number.isFinite(outputCount) || outputCount <= 0) {
            return null;
        }

        const successChance = getAlchemyDecomposeSuccessChance(sourceItemHrid, actionSummary);
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
        const efficiencyMultiplier = 1 + efficiencyFraction;
        const expectedOutputCount = outputCount * successChance * efficiencyMultiplier;
        if (!Number.isFinite(expectedOutputCount) || expectedOutputCount <= 0) {
            return null;
        }

        const sourceStack = new Set(stack);
        if (itemDependsOnCurrentRecipe(sourceItemHrid, itemHrid)) {
            sourceStack.add(itemHrid);
        }
        const sourceRelation = getItemSecondsLinearRelationToTarget(sourceItemHrid, targetItemHrid, sourceStack);
        if (!sourceRelation ||
            !Number.isFinite(sourceRelation.baseSeconds) ||
            sourceRelation.baseSeconds < 0 ||
            !Number.isFinite(sourceRelation.targetSecondsCoefficient) ||
            sourceRelation.targetSecondsCoefficient < 0) {
            return null;
        }

        const teaPerAction = Number(actionSummary.seconds || 0) / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of actionSummary.teaBuffs.activeTeas || []) {
            if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid) ||
                itemDependsOnCurrentRecipe(teaItemHrid, targetItemHrid)) {
                continue;
            }
            const teaStack = new Set(stack);
            teaStack.add(itemHrid);
            const teaSeconds = calculateItemSeconds(teaItemHrid, teaStack);
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds < 0) {
                if (isRecursiveDependencyFailureReason(getDependencyFailureReason(teaItemHrid))) {
                    continue;
                }
                return null;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        return {
            baseSeconds: (
                Number(actionSummary.seconds || 0) +
                teaSecondsTotal +
                Number(sourceRelation.baseSeconds || 0) * bulkMultiplier * efficiencyMultiplier
            ) / expectedOutputCount,
            targetSecondsCoefficient: (
                Number(sourceRelation.targetSecondsCoefficient || 0) * bulkMultiplier * efficiencyMultiplier
            ) / expectedOutputCount,
        };
    }

    function getItemSecondsLinearRelationToTarget(itemHrid, targetItemHrid, stack = new Set()) {
        if (!itemHrid || !targetItemHrid) {
            return null;
        }
        if (itemHrid === targetItemHrid) {
            return {
                baseSeconds: 0,
                targetSecondsCoefficient: 1,
            };
        }
        const cacheKey = getItemTargetRelationCacheKey(itemHrid, targetItemHrid);
        if (state.itemTargetRelationCache.has(cacheKey)) {
            return state.itemTargetRelationCache.get(cacheKey);
        }
        if (!itemDependsOnCurrentRecipe(itemHrid, targetItemHrid)) {
            const directSeconds = calculateItemSeconds(itemHrid, stack);
            if (directSeconds == null || !Number.isFinite(directSeconds) || directSeconds < 0) {
                state.itemTargetRelationCache.set(cacheKey, null);
                return null;
            }
            const directRelation = {
                baseSeconds: directSeconds,
                targetSecondsCoefficient: 0,
            };
            state.itemTargetRelationCache.set(cacheKey, directRelation);
            return directRelation;
        }
        if (stack.has(itemHrid)) {
            state.itemTargetRelationCache.set(cacheKey, null);
            return null;
        }
        if (getGeneralShopPurchaseInfo(itemHrid)) {
            const shopRelation = {
                baseSeconds: 0,
                targetSecondsCoefficient: 0,
            };
            state.itemTargetRelationCache.set(cacheKey, shopRelation);
            return shopRelation;
        }
        const dungeonMaterialPlan = getDungeonMaterialPlan(itemHrid);
        if (dungeonMaterialPlan) {
            const dungeonRelation = {
                baseSeconds: dungeonMaterialPlan.secondsPerItem,
                targetSecondsCoefficient: 0,
            };
            state.itemTargetRelationCache.set(cacheKey, dungeonRelation);
            return dungeonRelation;
        }
        if (FIXED_DECOMPOSE_SOURCE_RULES[itemHrid]) {
            const fixedDecomposeRelation = getFixedSourceDecomposeRelationToTarget(
                itemHrid,
                targetItemHrid,
                FIXED_DECOMPOSE_SOURCE_RULES[itemHrid],
                stack
            );
            state.itemTargetRelationCache.set(cacheKey, fixedDecomposeRelation);
            return fixedDecomposeRelation;
        }
        const essenceRule = ESSENCE_DECOMPOSE_RULES[itemHrid];
        if (essenceRule?.type === "fixed_source") {
            const fixedEssenceRelation = getFixedSourceDecomposeRelationToTarget(
                itemHrid,
                targetItemHrid,
                getConfiguredEssenceDecomposeSourceItemHrid(itemHrid),
                stack
            );
            state.itemTargetRelationCache.set(cacheKey, fixedEssenceRelation);
            return fixedEssenceRelation;
        }
        if (isTimeCalculatorSupportedItem(itemHrid) ||
            FIXED_TRANSMUTE_SOURCE_RULES[itemHrid] ||
            FIXED_ENHANCING_ESSENCE_RULES[itemHrid] ||
            FIXED_ATTACHED_RARE_TOOLTIP_SOURCE_RULES[itemHrid] ||
            ESSENCE_DECOMPOSE_RULES[itemHrid]) {
            state.itemTargetRelationCache.set(cacheKey, null);
            return null;
        }

        const action = findActionForItem(itemHrid);
        if (!action) {
            const emptyRelation = {
                baseSeconds: 0,
                targetSecondsCoefficient: 0,
            };
            state.itemTargetRelationCache.set(cacheKey, emptyRelation);
            return emptyRelation;
        }

        const summary = getActionSummary(action);
        const outputCount = getEffectiveOutputCountPerAction(action, itemHrid, summary);
        if (!outputCount || !Number.isFinite(outputCount)) {
            state.itemTargetRelationCache.set(cacheKey, null);
            return null;
        }

        stack.add(itemHrid);
        try {
            let baseSecondsPerAction = Number(summary.seconds || 0);
            let targetSecondsCoefficientPerAction = 0;

            for (const input of getAdjustedInputs(action, summary)) {
                let inputRelation = null;
                if (input.itemHrid === targetItemHrid) {
                    inputRelation = {
                        baseSeconds: 0,
                        targetSecondsCoefficient: 1,
                    };
                } else if (itemDependsOnCurrentRecipe(input.itemHrid, targetItemHrid)) {
                    inputRelation = getItemSecondsLinearRelationToTarget(input.itemHrid, targetItemHrid, stack);
                } else {
                    const inputSeconds = calculateItemSeconds(input.itemHrid, stack);
                    if (inputSeconds != null && Number.isFinite(inputSeconds) && inputSeconds >= 0) {
                        inputRelation = {
                            baseSeconds: inputSeconds,
                            targetSecondsCoefficient: 0,
                        };
                    }
                }
                if (!inputRelation) {
                    state.itemTargetRelationCache.set(cacheKey, null);
                    return null;
                }
                baseSecondsPerAction += Number(input.count || 0) * Number(inputRelation.baseSeconds || 0);
                targetSecondsCoefficientPerAction +=
                    Number(input.count || 0) * Number(inputRelation.targetSecondsCoefficient || 0);
            }

            const teaPerAction = Number(summary.seconds || 0) / Math.max(summary.teaBuffs.durationSeconds || 300, 1);
            let selfTeaCoefficient = 0;
            for (const teaItemHrid of summary.teaBuffs.activeTeas || []) {
                if (teaItemHrid === itemHrid) {
                    selfTeaCoefficient += teaPerAction;
                    continue;
                }
                if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid) ||
                    itemDependsOnCurrentRecipe(teaItemHrid, targetItemHrid)) {
                    continue;
                }
                const teaSeconds = calculateItemSeconds(teaItemHrid, stack);
                if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds < 0) {
                    if (isRecursiveDependencyFailureReason(getDependencyFailureReason(teaItemHrid))) {
                        continue;
                    }
                    state.itemTargetRelationCache.set(cacheKey, null);
                    return null;
                }
                baseSecondsPerAction += teaPerAction * teaSeconds;
            }

            const denominator = outputCount - selfTeaCoefficient;
            if (!Number.isFinite(denominator) || denominator <= 0) {
                state.itemTargetRelationCache.set(cacheKey, null);
                return null;
            }

            const relation = {
                baseSeconds: baseSecondsPerAction / denominator,
                targetSecondsCoefficient: targetSecondsCoefficientPerAction / denominator,
            };
            state.itemTargetRelationCache.set(cacheKey, relation);
            return relation;
        } finally {
            stack.delete(itemHrid);
        }
    }

    function calculateItemSeconds(itemHrid, stack = new Set()) {
        if (!itemHrid) {
            return null;
        }
        if (state.itemTimeCache.has(itemHrid)) {
            return state.itemTimeCache.get(itemHrid);
        }
        if (stack.size === 0 && isMissingDerivedRuntimeState()) {
            ensureRuntimeStateFresh();
        }
        if (stack.size > 64) {
            state.itemFailureReasonCache.set(itemHrid, isZh ? "递归层级过深,已截断" : "Truncated: recursion depth exceeded");
            return null;
        }
        if (stack.has(itemHrid)) {
            if (state.itemTimeCache.has(itemHrid)) {
                return state.itemTimeCache.get(itemHrid);
            }
            state.itemFailureReasonCache.set(itemHrid, isZh ? "递归依赖环,已截断" : "Truncated: recursive dependency cycle");
            return null;
        }
        if (state.activeItemSolveSet.has(itemHrid)) {
            state.itemFailureReasonCache.set(itemHrid, isZh ? "递归依赖环,已截断" : "Truncated: recursive dependency cycle");
            return null;
        }

        state.activeItemSolveSet.add(itemHrid);
        state.cyclicSolveDepth += 1;
        try {
            const timeCalculatorEntry = getConfiguredTimeCalculatorEntry(itemHrid);
            if (timeCalculatorEntry) {
                const summary = getTimeCalculatorEntrySummary(timeCalculatorEntry);
                if (summary.failureReason) {
                    state.itemFailureReasonCache.set(itemHrid, summary.failureReason);
                    return null;
                }
                const result = Number.isFinite(summary.secondsPerChest) && summary.secondsPerChest > 0 ? summary.secondsPerChest : 0;
                state.itemFailureReasonCache.delete(itemHrid);
                state.itemTimeCache.set(itemHrid, result);
                return result;
            }

            if (isTimeCalculatorSupportedItem(itemHrid)) {
                const reason = getMissingConfiguredTimeReason(itemHrid);
                state.itemFailureReasonCache.set(itemHrid, reason);
                return null;
            }

            if (getGeneralShopPurchaseInfo(itemHrid)) {
                state.itemFailureReasonCache.delete(itemHrid);
                state.itemTimeCache.set(itemHrid, 0);
                return 0;
            }

            const dungeonMaterialPlan = getDungeonMaterialPlan(itemHrid);
            if (dungeonMaterialPlan) {
                state.itemFailureReasonCache.delete(itemHrid);
                state.itemTimeCache.set(itemHrid, dungeonMaterialPlan.secondsPerItem);
                return dungeonMaterialPlan.secondsPerItem;
            }
            if (DUNGEON_MATERIAL_ITEM_HRIDS.has(itemHrid) && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const fixedDecomposePlan = getFixedDecomposePlan(itemHrid, stack);
            if (fixedDecomposePlan) {
                state.itemTimeCache.set(itemHrid, fixedDecomposePlan.totalSeconds);
                return fixedDecomposePlan.totalSeconds;
            }
            if (FIXED_DECOMPOSE_SOURCE_RULES[itemHrid] && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const fixedTransmutePlan = getFixedTransmutePlan(itemHrid, stack);
            if (fixedTransmutePlan) {
                state.itemTimeCache.set(itemHrid, fixedTransmutePlan.totalSeconds);
                return fixedTransmutePlan.totalSeconds;
            }
            if (FIXED_TRANSMUTE_SOURCE_RULES[itemHrid] && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const fixedEnhancedEssencePlan = getFixedEnhancedEssencePlan(itemHrid);
            if (fixedEnhancedEssencePlan) {
                state.itemTimeCache.set(itemHrid, fixedEnhancedEssencePlan.totalSeconds);
                return fixedEnhancedEssencePlan.totalSeconds;
            }
            if (FIXED_ENHANCING_ESSENCE_RULES[itemHrid] && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const fixedAttachedRareTooltipPlan = getFixedAttachedRareTooltipPlan(itemHrid, stack);
            if (fixedAttachedRareTooltipPlan) {
                state.itemTimeCache.set(itemHrid, fixedAttachedRareTooltipPlan.totalSeconds);
                return fixedAttachedRareTooltipPlan.totalSeconds;
            }
            if (FIXED_ATTACHED_RARE_TOOLTIP_SOURCE_RULES[itemHrid] && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const essencePlan = getEssenceDecomposePlan(itemHrid, stack);
            if (essencePlan) {
                state.itemTimeCache.set(itemHrid, essencePlan.totalSeconds);
                return essencePlan.totalSeconds;
            }
            if (ESSENCE_DECOMPOSE_RULES[itemHrid] && state.itemFailureReasonCache.has(itemHrid)) {
                return null;
            }

            const action = findActionForItem(itemHrid);
            if (!action) {
                if (DUNGEON_RELATED_ITEM_HRIDS.has(itemHrid) && state.itemFailureReasonCache.has(itemHrid)) {
                    return null;
                }
                if (DUNGEON_RELATED_ITEM_HRIDS.has(itemHrid)) {
                    state.itemFailureReasonCache.set(
                        itemHrid,
                        isZh ? "地牢成品暂不参与时间计算" : "Dungeon equipment is not timed yet"
                    );
                    return null;
                }
                state.itemFailureReasonCache.delete(itemHrid);
                state.itemTimeCache.set(itemHrid, 0);
                return 0;
            }

            const summary = getActionSummary(action);
            stack.add(itemHrid);
            const actionSeconds = summary.seconds;
            const outputCount = getEffectiveOutputCountPerAction(action, itemHrid, summary);
            if (!outputCount || !Number.isFinite(outputCount)) {
                stack.delete(itemHrid);
                state.itemFailureReasonCache.set(itemHrid, isZh ? "产出无效,已截断" : "Truncated: invalid output");
                return null;
            }

            let totalSecondsPerAction = actionSeconds;
            for (const input of getAdjustedInputs(action, summary)) {
                const inputSeconds = calculateItemSeconds(input.itemHrid, stack);
                if (inputSeconds == null) {
                    stack.delete(itemHrid);
                    state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(input.itemHrid));
                    return null;
                }
                totalSecondsPerAction += input.count * inputSeconds;
            }

            const teaPerAction = actionSeconds / Math.max(summary.teaBuffs.durationSeconds || 300, 1);
            let selfTeaCoefficient = 0;
            for (const teaItemHrid of summary.teaBuffs.activeTeas) {
                if (teaItemHrid === itemHrid) {
                    selfTeaCoefficient += teaPerAction;
                    continue;
                }
                if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid)) {
                    continue;
                }
                const teaSeconds = calculateItemSeconds(teaItemHrid, stack);
                if (teaSeconds == null) {
                    if (isRecursiveDependencyFailureReason(getDependencyFailureReason(teaItemHrid))) {
                        continue;
                    }
                    stack.delete(itemHrid);
                    state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(teaItemHrid));
                    return null;
                }
                totalSecondsPerAction += teaPerAction * teaSeconds;
            }

            const denominator = outputCount - selfTeaCoefficient;
            const result = denominator > 0 ? totalSecondsPerAction / denominator : null;
            stack.delete(itemHrid);

            if (result != null) {
                state.itemFailureReasonCache.delete(itemHrid);
                state.itemTimeCache.set(itemHrid, result);
            } else {
                state.itemFailureReasonCache.set(itemHrid, isZh ? "分母无效,已截断" : "Truncated: invalid denominator");
            }
            return result;
        } finally {
            state.activeItemSolveSet.delete(itemHrid);
            state.cyclicSolveDepth = Math.max(0, state.cyclicSolveDepth - 1);
            if (state.cyclicSolveDepth === 0) {
                state.activeItemSolveSet.clear();
            }
        }
    }

    function formatSignedPercent(value, digits = 1) {
        const prefix = value > 0 ? "+" : "";
        return `${prefix}${value.toFixed(digits)}%`;
    }

    function formatPercent(value, digits = 1) {
        return `${Number(value).toFixed(digits)}%`;
    }

    function formatNumber(value) {
        return Number(value).toFixed(2);
    }

    function formatPreciseNumber(value) {
        const numeric = Number(value || 0);
        if (!Number.isFinite(numeric)) {
            return "0";
        }
        const abs = Math.abs(numeric);
        let text = "0";
        if (abs === 0) {
            text = "0";
        } else if (abs >= 100) {
            text = numeric.toFixed(2);
        } else if (abs >= 1) {
            text = numeric.toFixed(3);
        } else if (abs >= 0.01) {
            text = numeric.toFixed(4);
        } else if (abs >= 0.0001) {
            text = numeric.toFixed(6);
        } else {
            text = numeric.toExponential(3);
        }
        return text
            .replace(/(\.\d*?[1-9])0+$/u, "$1")
            .replace(/\.0+$/u, "");
    }

    function formatAttachedRareNumber(value) {
        const numeric = Number(value || 0);
        if (!Number.isFinite(numeric) || numeric === 0) {
            return "0";
        }
        if (Math.abs(numeric) < 0.001) {
            return numeric
                .toFixed(9)
                .replace(/(\.\d*?[1-9])0+$/u, "$1")
                .replace(/\.0+$/u, "");
        }
        return formatPreciseNumber(numeric);
    }

    function parseNonNegativeDecimal(value) {
        const raw = String(value ?? "").trim();
        let normalized = raw;
        if (raw.includes(",") && raw.includes(".")) {
            normalized = raw.replace(/,/g, "");
        } else if (raw.includes(",")) {
            normalized = raw.replace(/,/g, ".");
        }
        const parsed = Number(normalized || 0);
        return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
    }

    function getExpectedDropCount(drop) {
        if (!drop) {
            return 0;
        }
        const minCount = Number(drop.minCount || 0);
        const maxCount = Number(drop.maxCount || 0);
        const dropRate = Number(drop.dropRate || 0);
        return Math.max(0, ((minCount + maxCount) / 2) * dropRate);
    }

    function getAttachedRareYieldCacheKey(itemHrid, targetRareHrid) {
        return `${itemHrid}::${targetRareHrid}`;
    }

    function getAttachedRareLabel(targetRareHrid) {
        return isZh
            ? (ATTACHED_RARE_LABEL_ZH[targetRareHrid] || getLocalizedItemName(targetRareHrid, targetRareHrid))
            : (ATTACHED_RARE_LABEL_EN[targetRareHrid] || getLocalizedItemName(targetRareHrid, targetRareHrid));
    }

    function getDirectRareOutputCountPerAction(action, targetRareHrid, summary) {
        if (!action || !targetRareHrid || !ATTACHED_RARE_TARGET_ITEM_HRID_SET.has(targetRareHrid)) {
            return 0;
        }
        const efficiencyMultiplier = 1 + Math.max(0, Number(summary?.efficiencyFraction || 0));
        const rareFindMultiplier = Math.max(0, 1 + Number(summary?.rareFindFraction || 0));
        let total = 0;
        for (const drop of action.rareDropTable || []) {
            if (drop?.itemHrid !== targetRareHrid) {
                continue;
            }
            total += getExpectedDropCount(drop);
        }
        return total * efficiencyMultiplier * rareFindMultiplier;
    }

    function getAttachedRareYieldPerItem(itemHrid, targetRareHrid, stack = new Set()) {
        if (!itemHrid || !targetRareHrid || !ATTACHED_RARE_TARGET_ITEM_HRID_SET.has(targetRareHrid)) {
            return 0;
        }
        const cacheKey = getAttachedRareYieldCacheKey(itemHrid, targetRareHrid);
        if (state.attachedRareYieldCache.has(cacheKey)) {
            return state.attachedRareYieldCache.get(cacheKey);
        }
        if (stack.has(cacheKey)) {
            return 0;
        }

        const action = findActionForItem(itemHrid);
        if (!action) {
            state.attachedRareYieldCache.set(cacheKey, 0);
            return 0;
        }

        const summary = getActionSummary(action);
        const outputCount = getEffectiveOutputCountPerAction(action, itemHrid, summary);
        if (!Number.isFinite(outputCount) || outputCount <= 0) {
            state.attachedRareYieldCache.set(cacheKey, 0);
            return 0;
        }

        stack.add(cacheKey);
        let propagatedInputRarePerAction = 0;
        for (const input of getAdjustedInputs(action, summary)) {
            const attachedRare = getAttachedRareYieldPerItem(input.itemHrid, targetRareHrid, stack);
            if (!Number.isFinite(attachedRare) || attachedRare <= 0) {
                continue;
            }
            propagatedInputRarePerAction += Number(input.count || 0) * attachedRare;
        }
        stack.delete(cacheKey);

        const directRarePerAction = getDirectRareOutputCountPerAction(action, targetRareHrid, summary);
        const result = (directRarePerAction + propagatedInputRarePerAction) / outputCount;
        const safeResult = Number.isFinite(result) && result > 0 ? result : 0;
        state.attachedRareYieldCache.set(cacheKey, safeResult);
        return safeResult;
    }

    function getAttachedRareTooltipLines(itemHrid) {
        const lines = [];
        for (const targetRareHrid of ATTACHED_RARE_TARGET_ITEM_HRIDS) {
            const amount = getAttachedRareYieldPerItem(itemHrid, targetRareHrid);
            if (!Number.isFinite(amount) || amount <= 0) {
                continue;
            }
            const countPerRare = 1 / amount;
            if (!Number.isFinite(countPerRare) || countPerRare <= 0) {
                continue;
            }
            lines.push(
                isZh
                    ? `生产${formatAttachedRareNumber(countPerRare)}个此物品附带1个${getAttachedRareLabel(targetRareHrid)}`
                    : `Produce ${formatAttachedRareNumber(countPerRare)} of this item for 1 extra ${getAttachedRareLabel(targetRareHrid)}`
            );
        }
        return lines;
    }

    function getConsumableAttachedRareTimeSavings(itemHrid) {
        if (!itemHrid) {
            return {
                totalSeconds: 0,
                breakdown: [],
            };
        }

        let totalSeconds = 0;
        const breakdown = [];
        for (const targetRareHrid of CONSUMABLE_VALUE_ATTACHED_RARE_ITEM_HRIDS) {
            const attachedCount = Number(getAttachedRareYieldPerItem(itemHrid, targetRareHrid) || 0);
            if (!Number.isFinite(attachedCount) || attachedCount <= 0) {
                continue;
            }

            const targetSeconds = calculateItemSeconds(targetRareHrid, new Set([itemHrid]));
            if (!Number.isFinite(targetSeconds) || targetSeconds == null || targetSeconds <= 0) {
                continue;
            }

            const savedSeconds = attachedCount * targetSeconds;
            if (!Number.isFinite(savedSeconds) || savedSeconds <= 0) {
                continue;
            }

            totalSeconds += savedSeconds;
            breakdown.push({
                itemHrid: targetRareHrid,
                attachedCount,
                targetSeconds,
                savedSeconds,
            });
        }

        return {
            totalSeconds,
            breakdown,
        };
    }

    function getMissingConfiguredTimeReason(itemHrid) {
        const itemName = getLocalizedItemName(itemHrid, state.itemDetailMap?.[itemHrid]?.name || itemHrid);
        return isZh
            ? `缺少${itemName}数据,已截断`
            : `Truncated: missing ${itemName} data`;
    }

    function isRecursiveDependencyFailureReason(reason) {
        return typeof reason === "string" && (
            reason.includes("递归依赖环") ||
            reason.includes("recursive dependency cycle")
        );
    }

    function getGeneralShopPurchaseInfo(itemHrid) {
        if (!itemHrid || itemHrid.includes("_charm")) {
            return null;
        }
        for (const detail of Object.values(state.shopItemDetailMap || {})) {
            if (!detail || detail.itemHrid !== itemHrid) {
                continue;
            }
            const costs = Array.isArray(detail.costs) ? detail.costs : [];
            if (
                detail.category === "/shop_categories/general" &&
                costs.length === 1 &&
                costs[0]?.itemHrid === "/items/coin"
            ) {
                return detail;
            }
        }
        return null;
    }

    function getDependencyFailureReason(itemHrid) {
        return state.itemFailureReasonCache.get(itemHrid) || getMissingConfiguredTimeReason(itemHrid);
    }

    function parseUiNumber(value) {
        const raw = String(value ?? "").trim().replace(/\s+/g, "");
        if (!raw) {
            return 0;
        }
        let normalized = raw;
        if (normalized.includes(",") && normalized.includes(".")) {
            normalized = normalized.replace(/,/g, "");
        } else if (normalized.includes(",")) {
            normalized = normalized.replace(/,/g, ".");
        }
        const match = normalized.match(/-?\d+(?:\.\d+)?/);
        const parsed = Number(match ? match[0] : normalized);
        return Number.isFinite(parsed) ? parsed : 0;
    }

    function parseUiPercent(value) {
        return clamp01(parseUiNumber(value) / 100);
    }

    function parseUiDurationSeconds(value) {
        const raw = String(value ?? "").trim().toLowerCase();
        if (!raw) {
            return 0;
        }
        let total = 0;
        const units = [
            { pattern: /(-?\d+(?:[.,]\d+)?)d/g, scale: 86400 },
            { pattern: /(-?\d+(?:[.,]\d+)?)h/g, scale: 3600 },
            { pattern: /(-?\d+(?:[.,]\d+)?)min/g, scale: 60 },
            { pattern: /(-?\d+(?:[.,]\d+)?)m(?![a-z])/g, scale: 60 },
            { pattern: /(-?\d+(?:[.,]\d+)?)s/g, scale: 1 },
        ];
        for (const unit of units) {
            let match;
            while ((match = unit.pattern.exec(raw))) {
                total += parseUiNumber(match[1]) * unit.scale;
            }
        }
        if (total > 0) {
            return total;
        }
        return Math.max(0, parseUiNumber(raw));
    }

    function isResolvableItemSeconds(itemHrid, seconds) {
        if (!itemHrid) {
            return false;
        }
        if (itemHrid === "/items/coin") {
            return true;
        }
        if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
            return false;
        }
        if (seconds > 0) {
            return true;
        }
        return Boolean(
            getGeneralShopPurchaseInfo(itemHrid) ||
            getConfiguredTimeCalculatorEntry(itemHrid) ||
            getDungeonMaterialPlan(itemHrid) ||
            FIXED_DECOMPOSE_SOURCE_RULES[itemHrid] ||
            FIXED_TRANSMUTE_SOURCE_RULES[itemHrid] ||
            FIXED_ENHANCING_ESSENCE_RULES[itemHrid] ||
            ESSENCE_DECOMPOSE_RULES[itemHrid] ||
            findActionForItem(itemHrid)
        );
    }

    function isAlchemyInferenceResolvableItemSeconds(itemHrid, seconds) {
        if (ATTACHED_RARE_TARGET_ITEM_HRID_SET.has(itemHrid)) {
            return false;
        }
        return isResolvableItemSeconds(itemHrid, seconds);
    }

    function getDungeonMaterialPlan(itemHrid) {
        if (!DUNGEON_MATERIAL_ITEM_HRIDS.has(itemHrid)) {
            return null;
        }
        const refinementBaseChestItemHrid = REFINEMENT_SHARD_TO_BASE_CHEST_HRID[itemHrid] || "";
        if (refinementBaseChestItemHrid) {
            const config = DUNGEON_CHEST_CONFIG[refinementBaseChestItemHrid];
            const entry = config?.refinementChestItemHrid ? getConfiguredTimeCalculatorEntry(config.refinementChestItemHrid) : null;
            if (!entry) {
                /*
                state.itemFailureReasonCache.set(itemHrid, isZh ? "鏈厤缃簿鐐煎疂绠辨椂闂达紝宸叉埅鏂? : "Truncated: missing refinement chest time");
                */
                state.itemFailureReasonCache.set(itemHrid, "Truncated: missing refinement chest time");
                return null;
            }
            const summary = getTimeCalculatorEntrySummary(entry);
            if (summary.failureReason) {
                state.itemFailureReasonCache.set(itemHrid, summary.failureReason);
                return null;
            }
            if (!Number.isFinite(summary.secondsPerChest) || summary.secondsPerChest <= 0) {
                /*
                state.itemFailureReasonCache.set(itemHrid, isZh ? "绮剧偧瀹濈鏃堕棿鏃犳晥锛屽凡鎴柇" : "Truncated: invalid refinement chest time");
                */
                state.itemFailureReasonCache.set(itemHrid, "Truncated: invalid refinement chest time");
                return null;
            }
            const keyItemHrid = getRefinementChestOpenKeyHrid(config.refinementChestItemHrid);
            const keySeconds = keyItemHrid ? calculateItemSeconds(keyItemHrid) : 0;
            if (keyItemHrid && (!Number.isFinite(keySeconds) || keySeconds <= 0)) {
                state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(keyItemHrid));
                return null;
            }
            const directExpected = Math.max(0.0001, Number(config.refinementShardCountPerChest || 1));
            const totalSecondsPerOpen = summary.secondsPerChest + (Number.isFinite(keySeconds) ? keySeconds : 0);
            const secondsPerItem = totalSecondsPerOpen / directExpected;
            if (!Number.isFinite(secondsPerItem) || secondsPerItem <= 0) {
                /*
                state.itemFailureReasonCache.set(itemHrid, isZh ? "绮剧偧纰庣墖鍒嗘瘝鏃犳晥锛屽凡鎴柇" : "Truncated: invalid refinement denominator");
                */
                state.itemFailureReasonCache.set(itemHrid, "Truncated: invalid refinement denominator");
                return null;
            }
            const chestName = getLocalizedItemName(
                config.refinementChestItemHrid,
                state.itemDetailMap?.[config.refinementChestItemHrid]?.name || config.refinementChestItemHrid
            );
            const keyName = keyItemHrid
                ? getLocalizedItemName(keyItemHrid, state.itemDetailMap?.[keyItemHrid]?.name || keyItemHrid)
                : "";
            return {
                itemHrid,
                chestItemHrid: config.refinementChestItemHrid,
                chestName,
                keyItemHrid,
                keyName,
                tokenItemHrid: "",
                chestSeconds: summary.secondsPerChest,
                keySeconds: Number.isFinite(keySeconds) ? keySeconds : 0,
                directExpected,
                tokenExpected: 0,
                tokenCost: 0,
                shopExpected: 0,
                totalExpected: directExpected,
                totalSecondsPerOpen,
                secondsPerItem,
            };
        }
        let bestPlan = null;
        let failureReason = "";
        for (const [chestItemHrid, config] of Object.entries(DUNGEON_CHEST_CONFIG)) {
            const entry = getConfiguredTimeCalculatorEntry(chestItemHrid);
            if (!entry) {
                failureReason = isZh ? "未配置对应宝箱时间,已截断" : "Truncated: missing chest time";
                continue;
            }
            const summary = getTimeCalculatorEntrySummary(entry);
            if (summary.failureReason) {
                failureReason = summary.failureReason;
                continue;
            }
            if (!Number.isFinite(summary.secondsPerChest) || summary.secondsPerChest <= 0) {
                failureReason = isZh ? "宝箱时间无效,已截断" : "Truncated: invalid chest time";
                continue;
            }
            const keySeconds = calculateItemSeconds(config.keyItemHrid);
            if (!Number.isFinite(keySeconds) || keySeconds <= 0) {
                failureReason = getDependencyFailureReason(config.keyItemHrid);
                continue;
            }
            const directExpected = (config.drops || [])
                .filter((drop) => drop.itemHrid === itemHrid)
                .reduce((total, drop) => total + getExpectedDropCount(drop), 0);
            const tokenExpected = (config.drops || [])
                .filter((drop) => drop.itemHrid === config.tokenItemHrid)
                .reduce((total, drop) => total + getExpectedDropCount(drop), 0);
            const tokenCost = Number(DUNGEON_TOKEN_SHOP_COSTS?.[config.tokenItemHrid]?.[itemHrid] || 0);
            const shopExpected = tokenCost > 0 ? tokenExpected / tokenCost : 0;
            const totalExpected = directExpected + shopExpected;
            if (!Number.isFinite(totalExpected) || totalExpected <= 0) {
                continue;
            }
            const totalSecondsPerOpen = summary.secondsPerChest + keySeconds;
            const secondsPerItem = totalSecondsPerOpen / totalExpected;
            if (!Number.isFinite(secondsPerItem) || secondsPerItem <= 0) {
                failureReason = isZh ? "地牢材料分母无效,已截断" : "Truncated: invalid dungeon denominator";
                continue;
            }
            const chestName = getLocalizedItemName(chestItemHrid, state.itemDetailMap?.[chestItemHrid]?.name || chestItemHrid);
            const keyName = getLocalizedItemName(config.keyItemHrid, state.itemDetailMap?.[config.keyItemHrid]?.name || config.keyItemHrid);
            const plan = {
                itemHrid,
                chestItemHrid,
                chestName,
                keyItemHrid: config.keyItemHrid,
                keyName,
                tokenItemHrid: config.tokenItemHrid,
                chestSeconds: summary.secondsPerChest,
                keySeconds,
                directExpected,
                tokenExpected,
                tokenCost,
                shopExpected,
                totalExpected,
                totalSecondsPerOpen,
                secondsPerItem,
            };
            if (!bestPlan || plan.secondsPerItem < bestPlan.secondsPerItem) {
                bestPlan = plan;
            }
        }
        if (!bestPlan && failureReason) {
            state.itemFailureReasonCache.set(itemHrid, failureReason);
        }
        return bestPlan;
    }

    function formatAutoDuration(seconds) {
        const safeSeconds = Math.max(0, Number(seconds || 0));
        if (safeSeconds >= 24 * 3600) {
            return `${formatNumber(safeSeconds / 86400)} d`;
        }
        if (safeSeconds >= 600 * 60) {
            return `${formatNumber(safeSeconds / 3600)} h`;
        }
        if (safeSeconds >= 600) {
            return `${formatNumber(safeSeconds / 60)} min`;
        }
        return `${formatNumber(safeSeconds)} s`;
    }

    function getPerActionCostBreakdown(itemHrid, action, summary) {
        const stack = new Set([itemHrid]);
        let inputSecondsTotal = 0;
        for (const input of getAdjustedInputs(action, summary)) {
            const inputSeconds = calculateItemSeconds(input.itemHrid, stack);
            if (inputSeconds == null) {
                continue;
            }
            inputSecondsTotal += input.count * inputSeconds;
        }

        const teaPerAction = action.baseTimeCost
            ? summary.seconds / Math.max(summary.teaBuffs.durationSeconds || 300, 1)
            : 0;
        let teaSecondsTotal = 0;
        for (const teaItemHrid of summary.teaBuffs.activeTeas) {
            if (teaItemHrid === itemHrid) {
                continue;
            }
            const teaSeconds = calculateItemSeconds(teaItemHrid, stack);
            if (teaSeconds == null) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        return {
            inputSecondsTotal,
            teaSecondsTotal,
        };
    }

    function getLoadoutDisplayText(action) {
        const loadoutInfo = resolveSkillingLoadout(action?.type);
        if (!loadoutInfo.loadout) {
            return isZh ? "配装: 当前装备" : "Loadout: Current";
        }
        const mode = loadoutInfo.loadout.useExactEnhancement
            ? (isZh ? "精确强化" : "Exact")
            : (isZh ? "最高强化" : "Highest");
        return isZh
            ? `配装: ${loadoutInfo.loadout.name || loadoutInfo.loadout.id} (${mode})`
            : `Loadout: ${loadoutInfo.loadout.name || loadoutInfo.loadout.id} (${mode})`;
    }

    function isAllowedEssenceDecomposeSource(essenceHrid, sourceItemHrid) {
        const rule = ESSENCE_DECOMPOSE_RULES[essenceHrid];
        if (!rule || !sourceItemHrid) {
            return false;
        }
        const itemDetail = state.itemDetailMap?.[sourceItemHrid];
        const action = findActionForItem(sourceItemHrid);
        if (rule.type === "fixed_source") {
            return sourceItemHrid === getConfiguredEssenceDecomposeSourceItemHrid(essenceHrid);
        }
        if (rule.type === "raw_skill") {
            return Boolean(action && action.type === rule.actionTypeHrid && (!action.inputItems || action.inputItems.length === 0));
        }
        if (rule.type === "resource_skill") {
            return Boolean(action && action.type === rule.actionTypeHrid && itemDetail?.categoryHrid === "/item_categories/resource");
        }
        if (rule.type === "food_skill") {
            return Boolean(action && action.type === rule.actionTypeHrid && itemDetail?.categoryHrid === "/item_categories/food");
        }
        if (rule.type === "brewing_base") {
            return Boolean(
                sourceItemHrid.endsWith("_tea_leaf") ||
                sourceItemHrid.endsWith("_coffee_bean") ||
                (action && action.type === "/action_types/brewing")
            );
        }
        return false;
    }

    function getFixedDecomposePlan(itemHrid, stack = new Set()) {
        const sourceItemHrid = FIXED_DECOMPOSE_SOURCE_RULES[itemHrid];
        if (!sourceItemHrid) {
            return null;
        }
        if (state.fixedDecomposePlanCache.has(itemHrid)) {
            return state.fixedDecomposePlanCache.get(itemHrid);
        }
        const decomposeAction = state.actionDetailMap?.["/actions/alchemy/decompose"];
        const sourceItemDetail = state.itemDetailMap?.[sourceItemHrid];
        const match = (sourceItemDetail?.alchemyDetail?.decomposeItems || []).find((entry) => entry.itemHrid === itemHrid);
        if (!decomposeAction || !sourceItemDetail || !match) {
            const failureReason = !match
                ? (isZh ? "分解产出无效,已截断" : "Truncated: invalid decompose output")
                : getDependencyFailureReason(sourceItemHrid);
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedDecomposePlanCache.set(itemHrid, null);
            return null;
        }

        const actionSummary = getActionSummary(decomposeAction);
        const bulkMultiplier = Math.max(1, Number(sourceItemDetail?.alchemyDetail?.bulkMultiplier || 1));
        const outputCount = Number(match.count || 0) * bulkMultiplier;
        if (!outputCount) {
            const failureReason = isZh ? "分解产出无效,已截断" : "Truncated: invalid decompose output";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedDecomposePlanCache.set(itemHrid, null);
            return null;
        }

        const successChance = getAlchemyDecomposeSuccessChance(sourceItemHrid, actionSummary);
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
        const efficiencyMultiplier = 1 + efficiencyFraction;
        const expectedOutputCount = outputCount * successChance * efficiencyMultiplier;
        if (!expectedOutputCount) {
            const failureReason = isZh ? "分解期望无效,已截断" : "Truncated: invalid decompose expectation";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedDecomposePlanCache.set(itemHrid, null);
            return null;
        }

        const sourceStack = new Set(stack);
        sourceStack.add(itemHrid);
        const sourceItemSeconds = calculateItemSeconds(sourceItemHrid, sourceStack);
        if (sourceItemSeconds == null || !Number.isFinite(sourceItemSeconds) || sourceItemSeconds < 0) {
            state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(sourceItemHrid));
            state.fixedDecomposePlanCache.set(itemHrid, null);
            return null;
        }

        const sourceBaseSeconds = sourceItemSeconds * bulkMultiplier;
        const sourceSeconds = sourceBaseSeconds * efficiencyMultiplier;
        const teaPerAction = actionSummary.seconds / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of actionSummary.teaBuffs.activeTeas) {
            if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid)) {
                continue;
            }
            const teaStack = new Set(stack);
            teaStack.add(itemHrid);
            const teaSeconds = calculateItemSeconds(teaItemHrid, teaStack);
            if (teaSeconds == null) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }
        const totalSeconds = (actionSummary.seconds + teaSecondsTotal + sourceSeconds) / expectedOutputCount;
        if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {
            const failureReason = isZh ? "分解总耗时无效,已截断" : "Truncated: invalid decompose total";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedDecomposePlanCache.set(itemHrid, null);
            return null;
        }
        const plan = {
            itemHrid: sourceItemHrid,
            itemName: getLocalizedItemName(sourceItemHrid, sourceItemDetail?.name || sourceItemHrid),
            outputCount,
            bulkMultiplier,
            efficiencyFraction,
            successChance,
            expectedOutputCount,
            sourceItemSeconds,
            sourceBaseSeconds,
            sourceSeconds,
            teaSecondsTotal,
            actionSeconds: actionSummary.seconds,
            totalSeconds,
            action: decomposeAction,
        };
        state.itemFailureReasonCache.delete(itemHrid);
        state.fixedDecomposePlanCache.set(itemHrid, plan);
        state.itemTooltipDataCache.delete(itemHrid);
        return plan;
    }

    function getFixedEnhancedEssencePlan(itemHrid) {
        const rule = FIXED_ENHANCING_ESSENCE_RULES[itemHrid];
        if (!rule) {
            return null;
        }
        if (state.fixedEnhancedEssencePlanCache.has(itemHrid)) {
            return state.fixedEnhancedEssencePlanCache.get(itemHrid);
        }
        const sourceItemDetail = state.itemDetailMap?.[rule.sourceItemHrid];
        if (!sourceItemDetail) {
            const failureReason = getDependencyFailureReason(rule.sourceItemHrid);
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedEnhancedEssencePlanCache.set(itemHrid, null);
            return null;
        }

        const recommendation = getEnhancingRecommendationForItem(rule.sourceItemHrid, rule.enhancementLevel);
        if (!recommendation) {
            const failureReason = getDependencyFailureReason(rule.sourceItemHrid) || (isZh ? "强化来源无法计算" : "Enhancing source unavailable");
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedEnhancedEssencePlanCache.set(itemHrid, null);
            return null;
        }

        const essenceInfo = getEnhancedEquipmentEssenceInfo(
            rule.sourceItemHrid,
            rule.enhancementLevel,
            recommendation,
            rule.catalystItemHrid || ""
        );
        if (!Number.isFinite(essenceInfo?.secondsPerEssence) || essenceInfo.secondsPerEssence <= 0) {
            const failureReason = isZh ? "强化精华期望无效,已截断" : "Truncated: invalid enhancing essence expectation";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedEnhancedEssencePlanCache.set(itemHrid, null);
            return null;
        }

        const plan = {
            itemHrid: rule.sourceItemHrid,
            itemName: getLocalizedItemName(rule.sourceItemHrid, sourceItemDetail?.name || rule.sourceItemHrid),
            enhancementLevel: rule.enhancementLevel,
            recommendation,
            essenceInfo,
            catalystItemHrid: rule.catalystItemHrid || "",
            catalystItemName: rule.catalystItemHrid
                ? getLocalizedItemName(rule.catalystItemHrid, state.itemDetailMap?.[rule.catalystItemHrid]?.name || rule.catalystItemHrid)
                : "",
            action: state.actionDetailMap?.["/actions/alchemy/decompose"] || null,
            totalSeconds: essenceInfo.secondsPerEssence,
        };
        state.itemFailureReasonCache.delete(itemHrid);
        state.fixedEnhancedEssencePlanCache.set(itemHrid, plan);
        state.itemTooltipDataCache.delete(itemHrid);
        return plan;
    }

    function getFixedTransmutePlan(itemHrid, stack = new Set()) {
        const rule = FIXED_TRANSMUTE_SOURCE_RULES[itemHrid];
        if (!rule) {
            return null;
        }
        if (state.fixedTransmutePlanCache.has(itemHrid)) {
            return state.fixedTransmutePlanCache.get(itemHrid);
        }

        const transmuteAction = state.actionDetailMap?.[rule.actionHrid || "/actions/alchemy/transmute"];
        const sourceItemHrid = rule.sourceItemHrid;
        const sourceItemDetail = state.itemDetailMap?.[sourceItemHrid];
        const transmuteDrop = (sourceItemDetail?.alchemyDetail?.transmuteDropTable || []).find((drop) => drop?.itemHrid === itemHrid);
        if (!transmuteAction || !sourceItemDetail || !transmuteDrop) {
            const failureReason = !transmuteDrop
                ? (isZh ? "转化产出无效,已截断" : "Truncated: invalid transmute output")
                : getDependencyFailureReason(sourceItemHrid);
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedTransmutePlanCache.set(itemHrid, null);
            return null;
        }

        const actionSummary = getActionSummary(transmuteAction);
        const successChance = getAlchemyTransmuteSuccessChance(sourceItemHrid, actionSummary);
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
        const efficiencyMultiplier = Math.max(1 + efficiencyFraction, 1);
        const averageTargetCount = ((Number(transmuteDrop.minCount || 0) + Number(transmuteDrop.maxCount || 0)) / 2) * Number(transmuteDrop.dropRate || 0);
        const expectedOutputCount = successChance * averageTargetCount * efficiencyMultiplier;
        if (!Number.isFinite(expectedOutputCount) || expectedOutputCount <= 0) {
            const failureReason = isZh ? "转化期望无效,已截断" : "Truncated: invalid transmute expectation";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedTransmutePlanCache.set(itemHrid, null);
            return null;
        }

        const sourceStack = new Set(stack);
        sourceStack.add(itemHrid);
        const sourceItemSeconds = calculateItemSeconds(sourceItemHrid, sourceStack);
        if (sourceItemSeconds == null || !Number.isFinite(sourceItemSeconds) || sourceItemSeconds < 0) {
            state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(sourceItemHrid));
            state.fixedTransmutePlanCache.set(itemHrid, null);
            return null;
        }

        const teaPerAction = Number(actionSummary.seconds || 0) / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of actionSummary.teaBuffs.activeTeas) {
            if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid)) {
                continue;
            }
            const teaStack = new Set(stack);
            teaStack.add(itemHrid);
            const teaSeconds = calculateItemSeconds(teaItemHrid, teaStack);
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds < 0) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        const sourceSeconds = sourceItemSeconds * efficiencyMultiplier;
        let sideOutputSeconds = 0;
        const sideOutputs = [];
        for (const drop of sourceItemDetail.alchemyDetail?.transmuteDropTable || []) {
            if (!drop?.itemHrid || drop.itemHrid === itemHrid) {
                continue;
            }
            const averageCount = (Number(drop.minCount || 0) + Number(drop.maxCount || 0)) / 2;
            const expectedCount = successChance * Number(drop.dropRate || 0) * averageCount * efficiencyMultiplier;
            if (!Number.isFinite(expectedCount) || expectedCount <= 0) {
                continue;
            }
            const outputStack = new Set(stack);
            outputStack.add(itemHrid);
            const outputSeconds = calculateItemSeconds(drop.itemHrid, outputStack);
            if (outputSeconds == null || !Number.isFinite(outputSeconds) || outputSeconds <= 0) {
                continue;
            }
            const expectedSeconds = expectedCount * outputSeconds;
            sideOutputSeconds += expectedSeconds;
            sideOutputs.push({
                itemHrid: drop.itemHrid,
                itemName: getLocalizedItemName(drop.itemHrid, state.itemDetailMap?.[drop.itemHrid]?.name || drop.itemHrid),
                expectedCount,
                outputSeconds,
                expectedSeconds,
            });
        }

        const netSeconds = Math.max(0, Number(actionSummary.seconds || 0) + teaSecondsTotal + sourceSeconds - sideOutputSeconds);
        const totalSeconds = netSeconds / expectedOutputCount;
        if (!Number.isFinite(totalSeconds) || totalSeconds < 0) {
            const failureReason = isZh ? "转化总耗时无效,已截断" : "Truncated: invalid transmute total";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedTransmutePlanCache.set(itemHrid, null);
            return null;
        }

        const plan = {
            itemHrid: sourceItemHrid,
            itemName: getLocalizedItemName(sourceItemHrid, sourceItemDetail?.name || sourceItemHrid),
            successChance,
            sourceItemSeconds,
            sourceSeconds,
            teaSecondsTotal,
            sideOutputSeconds,
            netSeconds,
            actionSeconds: Number(actionSummary.seconds || 0),
            rawActionSeconds: actionSummary.seconds,
            efficiencyFraction,
            efficiencyMultiplier,
            expectedOutputCount,
            averageTargetCount,
            transmuteDropRate: Number(transmuteDrop.dropRate || 0),
            totalSeconds,
            sideOutputs,
            action: transmuteAction,
        };
        state.itemFailureReasonCache.delete(itemHrid);
        state.fixedTransmutePlanCache.set(itemHrid, plan);
        state.itemTooltipDataCache.delete(itemHrid);
        return plan;
    }

    function getFixedAttachedRareTooltipPlan(itemHrid, stack = new Set()) {
        const rule = FIXED_ATTACHED_RARE_TOOLTIP_SOURCE_RULES[itemHrid];
        if (!rule) {
            return null;
        }
        if (state.fixedAttachedRareTooltipPlanCache.has(itemHrid)) {
            return state.fixedAttachedRareTooltipPlanCache.get(itemHrid);
        }

        const transmuteAction = state.actionDetailMap?.["/actions/alchemy/transmute"];
        const sourceItemCandidates = [
            rule.sourceItemHrid,
            ...((Array.isArray(rule.fallbackSourceItemHrids) ? rule.fallbackSourceItemHrids : []).filter(Boolean)),
        ].filter(Boolean);
        let sourceItemHrid = rule.sourceItemHrid;
        let sourceItemDetail = null;
        let targetDrop = null;
        for (const candidateItemHrid of sourceItemCandidates) {
            const candidateItemDetail = state.itemDetailMap?.[candidateItemHrid];
            const candidateTargetDrop = (candidateItemDetail?.alchemyDetail?.transmuteDropTable || [])
                .find((drop) => drop?.itemHrid === itemHrid);
            if (!candidateItemDetail || !candidateTargetDrop) {
                continue;
            }
            sourceItemHrid = candidateItemHrid;
            sourceItemDetail = candidateItemDetail;
            targetDrop = candidateTargetDrop;
            break;
        }
        if (!transmuteAction || !sourceItemDetail || !targetDrop) {
            const failureReason = !targetDrop
                ? (isZh ? "转化产出无效,已截断" : "Truncated: invalid transmute output")
                : getDependencyFailureReason(sourceItemHrid);
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
            return null;
        }

        const actionSummary = getActionSummary(transmuteAction);
        const baseSuccessChance = getAlchemyTransmuteSuccessChance(sourceItemHrid, actionSummary);
        const catalystSuccessBonus = getAlchemyTransmuteCatalystSuccessBonus(
            rule.catalystItemHrid || "",
            rule.catalystSuccessBonus || 0
        );
        const successChance = clamp01(baseSuccessChance + catalystSuccessBonus);
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
        const efficiencyMultiplier = Math.max(1 + efficiencyFraction, 1);
        const averageTargetCount =
            ((Number(targetDrop.minCount || 0) + Number(targetDrop.maxCount || 0)) / 2) *
            Number(targetDrop.dropRate || 0);
        const directTargetExpectedCount = successChance * averageTargetCount * efficiencyMultiplier;
        if (!Number.isFinite(directTargetExpectedCount) || directTargetExpectedCount <= 0) {
            const failureReason = isZh ? "转化期望无效,已截断" : "Truncated: invalid transmute expectation";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
            return null;
        }

        const sourceStack = new Set(stack);
        sourceStack.add(itemHrid);
        const sourceItemRelation = getItemSecondsLinearRelationToTarget(sourceItemHrid, itemHrid, sourceStack);
        if (!sourceItemRelation ||
            !Number.isFinite(sourceItemRelation.baseSeconds) ||
            sourceItemRelation.baseSeconds < 0 ||
            !Number.isFinite(sourceItemRelation.targetSecondsCoefficient) ||
            sourceItemRelation.targetSecondsCoefficient < 0) {
            state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(sourceItemHrid));
            state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
            return null;
        }
        const inputBaseSecondsTotal = Number(sourceItemRelation.baseSeconds || 0) * efficiencyMultiplier;
        const inputTargetSecondsCoefficient = Number(sourceItemRelation.targetSecondsCoefficient || 0) * efficiencyMultiplier;

        const teaPerAction = Number(actionSummary.seconds || 0) / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of actionSummary.teaBuffs.activeTeas || []) {
            if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid)) {
                continue;
            }
            const teaStack = new Set(stack);
            teaStack.add(itemHrid);
            const teaSeconds = calculateItemSeconds(teaItemHrid, teaStack);
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds < 0) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        let catalystBaseSecondsTotal = 0;
        let catalystTargetSecondsCoefficient = 0;
        let catalystSecondsTotal = 0;
        if (rule.catalystItemHrid) {
            const catalystStack = new Set(stack);
            catalystStack.add(itemHrid);
            const catalystRelation = getItemSecondsLinearRelationToTarget(rule.catalystItemHrid, itemHrid, catalystStack);
            if (!catalystRelation ||
                !Number.isFinite(catalystRelation.baseSeconds) ||
                catalystRelation.baseSeconds < 0 ||
                !Number.isFinite(catalystRelation.targetSecondsCoefficient) ||
                catalystRelation.targetSecondsCoefficient < 0) {
                state.itemFailureReasonCache.set(itemHrid, getDependencyFailureReason(rule.catalystItemHrid));
                state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
                return null;
            }
            const catalystConsumptionPerAction = successChance * efficiencyMultiplier;
            catalystBaseSecondsTotal = Math.max(0, Number(catalystRelation.baseSeconds || 0)) * catalystConsumptionPerAction;
            catalystTargetSecondsCoefficient = Math.max(0, Number(catalystRelation.targetSecondsCoefficient || 0)) * catalystConsumptionPerAction;
        }

        const inputAttachedTargetExpectedCount =
            efficiencyMultiplier * Math.max(0, Number(getAttachedRareYieldPerItem(sourceItemHrid, itemHrid) || 0));

        let knownOutputSeconds = 0;
        let knownOutputAttachedTargetExpectedCount = 0;
        const sideOutputs = [];
        for (const drop of sourceItemDetail.alchemyDetail?.transmuteDropTable || []) {
            if (!drop?.itemHrid || drop.itemHrid === itemHrid) {
                continue;
            }
            const averageCount = (Number(drop.minCount || 0) + Number(drop.maxCount || 0)) / 2;
            const expectedCount = successChance * Number(drop.dropRate || 0) * averageCount * efficiencyMultiplier;
            if (!Number.isFinite(expectedCount) || expectedCount <= 0) {
                continue;
            }
            const outputStack = new Set(stack);
            outputStack.add(itemHrid);
            const outputSeconds = calculateItemSeconds(drop.itemHrid, outputStack);
            const attachedRare = Math.max(0, Number(getAttachedRareYieldPerItem(drop.itemHrid, itemHrid) || 0));
            const attachedExpectedCount = expectedCount * attachedRare;
            const expectedSeconds = Number.isFinite(outputSeconds) && outputSeconds >= 0
                ? expectedCount * Math.max(0, Number(outputSeconds || 0))
                : 0;
            knownOutputSeconds += expectedSeconds;
            knownOutputAttachedTargetExpectedCount += attachedExpectedCount;
            sideOutputs.push({
                itemHrid: drop.itemHrid,
                itemName: getLocalizedItemName(drop.itemHrid, state.itemDetailMap?.[drop.itemHrid]?.name || drop.itemHrid),
                expectedCount,
                outputSeconds: Number.isFinite(outputSeconds) ? Math.max(0, Number(outputSeconds || 0)) : 0,
                expectedSeconds,
                attachedExpectedCount,
            });
        }

        const effectiveTargetExpectedCount =
            directTargetExpectedCount +
            inputAttachedTargetExpectedCount -
            knownOutputAttachedTargetExpectedCount -
            inputTargetSecondsCoefficient -
            catalystTargetSecondsCoefficient;
        if (!Number.isFinite(effectiveTargetExpectedCount) || effectiveTargetExpectedCount <= 0) {
            const failureReason = isZh ? "转化总期望无效,已截断" : "Truncated: invalid transmute denominator";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
            return null;
        }

        const totalSeconds =
            (inputBaseSecondsTotal + Number(actionSummary.seconds || 0) + teaSecondsTotal + catalystSecondsTotal - knownOutputSeconds) /
            effectiveTargetExpectedCount;
        if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {
            const failureReason = isZh ? "转化总耗时无效,已截断" : "Truncated: invalid transmute total";
            state.itemFailureReasonCache.set(itemHrid, failureReason);
            state.fixedAttachedRareTooltipPlanCache.set(itemHrid, null);
            return null;
        }

        const sourceItemSeconds =
            Number(sourceItemRelation.baseSeconds || 0) +
            Number(sourceItemRelation.targetSecondsCoefficient || 0) * totalSeconds;
        const inputSecondsTotal = inputBaseSecondsTotal + inputTargetSecondsCoefficient * totalSeconds;
        catalystSecondsTotal = catalystBaseSecondsTotal + catalystTargetSecondsCoefficient * totalSeconds;

        const plan = {
            targetItemHrid: itemHrid,
            targetItemName: getLocalizedItemName(itemHrid, state.itemDetailMap?.[itemHrid]?.name || itemHrid),
            itemHrid: sourceItemHrid,
            itemName: getLocalizedItemName(sourceItemHrid, sourceItemDetail?.name || sourceItemHrid),
            catalystItemHrid: rule.catalystItemHrid || "",
            catalystItemName: rule.catalystItemHrid
                ? getLocalizedItemName(rule.catalystItemHrid, state.itemDetailMap?.[rule.catalystItemHrid]?.name || rule.catalystItemHrid)
                : "",
            successChance,
            baseSuccessChance,
            catalystSuccessBonus,
            efficiencyFraction,
            efficiencyMultiplier,
            directTargetExpectedCount,
            inputAttachedTargetExpectedCount,
            knownOutputAttachedTargetExpectedCount,
            effectiveTargetExpectedCount,
            sourceItemSeconds,
            sourceItemBaseSeconds: Number(sourceItemRelation.baseSeconds || 0),
            sourceItemTargetSecondsCoefficient: Number(sourceItemRelation.targetSecondsCoefficient || 0),
            inputBaseSecondsTotal,
            inputTargetSecondsCoefficient,
            inputSecondsTotal,
            catalystBaseSecondsTotal,
            catalystTargetSecondsCoefficient,
            catalystSecondsTotal,
            knownOutputSeconds,
            teaSecondsTotal,
            actionSeconds: Number(actionSummary.seconds || 0),
            totalSeconds,
            targetDropRate: Number(targetDrop.dropRate || 0),
            targetAverageCount: (Number(targetDrop.minCount || 0) + Number(targetDrop.maxCount || 0)) / 2,
            sideOutputs,
            action: transmuteAction,
        };
        state.itemFailureReasonCache.delete(itemHrid);
        state.fixedAttachedRareTooltipPlanCache.set(itemHrid, plan);
        state.itemTooltipDataCache.delete(itemHrid);
        return plan;
    }

    function getEssenceDecomposePlan(itemHrid, stack = new Set()) {
        const rule = ESSENCE_DECOMPOSE_RULES[itemHrid];
        if (!rule) {
            return null;
        }
        if (state.essencePlanCache.has(itemHrid)) {
            return state.essencePlanCache.get(itemHrid);
        }
        const decomposeAction = state.actionDetailMap?.["/actions/alchemy/decompose"];
        if (!decomposeAction) {
            return null;
        }
        const actionSummary = getActionSummary(decomposeAction);
        let best = null;
        let failureReason = "";
        for (const [sourceItemHrid, itemDetail] of Object.entries(state.itemDetailMap || {})) {
            const match = (itemDetail?.alchemyDetail?.decomposeItems || []).find((entry) => entry.itemHrid === itemHrid);
            if (!match || !isAllowedEssenceDecomposeSource(itemHrid, sourceItemHrid)) {
                continue;
            }
            const bulkMultiplier = Math.max(1, Number(itemDetail?.alchemyDetail?.bulkMultiplier || 1));
            const outputCount = Number(match.count || 0) * bulkMultiplier;
            if (!outputCount) {
                failureReason = isZh ? "分解产出无效,已截断" : "Truncated: invalid decompose output";
                continue;
            }
            const successChance = getAlchemyDecomposeSuccessChance(sourceItemHrid, actionSummary);
            const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
            const efficiencyMultiplier = 1 + efficiencyFraction;
            const expectedOutputCount = outputCount * successChance * efficiencyMultiplier;
            if (!expectedOutputCount) {
                failureReason = isZh ? "分解期望无效,已截断" : "Truncated: invalid decompose expectation";
                continue;
            }

            const sourceStack = new Set(stack);
            sourceStack.add(itemHrid);
            const sourceItemSeconds = calculateItemSeconds(sourceItemHrid, sourceStack);
            if (sourceItemSeconds == null || !Number.isFinite(sourceItemSeconds) || sourceItemSeconds < 0) {
                failureReason = getDependencyFailureReason(sourceItemHrid);
                continue;
            }

            const sourceBaseSeconds = sourceItemSeconds * bulkMultiplier;
            const sourceSeconds = sourceBaseSeconds * efficiencyMultiplier;
            const teaPerAction = actionSummary.seconds / Math.max(actionSummary.teaBuffs.durationSeconds || 300, 1);
            let teaSecondsTotal = 0;
            for (const teaItemHrid of actionSummary.teaBuffs.activeTeas) {
                if (itemDependsOnCurrentRecipe(teaItemHrid, itemHrid)) {
                    continue;
                }
                const teaStack = new Set(stack);
                teaStack.add(itemHrid);
                const teaSeconds = calculateItemSeconds(teaItemHrid, teaStack);
                if (teaSeconds == null) {
                    continue;
                }
                teaSecondsTotal += teaPerAction * teaSeconds;
            }
            const totalSeconds = (actionSummary.seconds + teaSecondsTotal + sourceSeconds) / expectedOutputCount;
            if (!Number.isFinite(totalSeconds) || totalSeconds <= 0) {
                failureReason = isZh ? "分解总耗时无效,已截断" : "Truncated: invalid decompose total";
                continue;
            }
            const sourceName = getLocalizedItemName(sourceItemHrid, itemDetail?.name || sourceItemHrid);
            if (!best || totalSeconds < best.totalSeconds) {
                best = {
                    itemHrid: sourceItemHrid,
                    itemName: sourceName,
                    outputCount,
                    bulkMultiplier,
                    efficiencyFraction,
                    successChance,
                    expectedOutputCount,
                    sourceItemSeconds,
                    sourceBaseSeconds,
                    sourceSeconds,
                    teaSecondsTotal,
                    actionSeconds: actionSummary.seconds,
                    totalSeconds,
                    action: decomposeAction,
                };
            }
        }
        if (best) {
            state.itemFailureReasonCache.delete(itemHrid);
        } else if (rule.type === "fixed_source") {
            const configuredSourceItemHrid = getConfiguredEssenceDecomposeSourceItemHrid(itemHrid);
            state.itemFailureReasonCache.set(itemHrid, failureReason || getDependencyFailureReason(configuredSourceItemHrid));
        } else if (failureReason) {
            state.itemFailureReasonCache.set(itemHrid, failureReason);
        }
        state.essencePlanCache.set(itemHrid, best);
        state.itemTooltipDataCache.delete(itemHrid);
        return best;
    }

    function getItemCalculationDetail(itemHrid) {
        if (isMissingDerivedRuntimeState()) {
            ensureRuntimeStateFresh();
        }
        const timeCalculatorEntry = getConfiguredTimeCalculatorEntry(itemHrid);
        if (timeCalculatorEntry) {
            const summary = getTimeCalculatorEntrySummary(timeCalculatorEntry);
            if (summary.itemType === "fragment") {
                return [
                    isZh ? `24小时碎片${formatNumber(summary.quantityPer24h)}` : `24h qty ${formatNumber(summary.quantityPer24h)}`,
                    isZh ? `食物${formatAutoDuration(summary.foodSeconds)}` : `food ${formatAutoDuration(summary.foodSeconds)}`,
                    isZh ? `饮料${formatAutoDuration(summary.drinkSeconds)}` : `drink ${formatAutoDuration(summary.drinkSeconds)}`,
                ].join(" | ");
                }
                return [
                    isZh ? `\u5355\u6b21\u5730\u7262${formatAutoDuration(summary.runMinutes * 60)}` : `run ${formatAutoDuration(summary.runMinutes * 60)}`,
                    ...(summary.dungeonEntryKeySeconds > 0
                        ? [isZh ? `\u5730\u7262\u94a5\u5319${formatAutoDuration(summary.dungeonEntryKeySeconds)}` : `entry key ${formatAutoDuration(summary.dungeonEntryKeySeconds)}`]
                        : []),
                    isZh
                        ? `${summary.itemType === "refinement_chest" ? "\u7cbe\u70bc\u7bb1\u5b50\u671f\u671b" : "\u5b9d\u7bb1\u671f\u671b"}${formatNumber(summary.expectedChestCount)}`
                        : `exp ${formatNumber(summary.expectedChestCount)}`,
                    isZh ? `\u98df\u7269${formatAutoDuration(summary.foodSeconds)}` : `food ${formatAutoDuration(summary.foodSeconds)}`,
                    isZh ? `\u996e\u6599${formatAutoDuration(summary.drinkSeconds)}` : `drink ${formatAutoDuration(summary.drinkSeconds)}`,
                ].join(" | ");
                return [
                isZh ? `单次地牢${formatAutoDuration(summary.runMinutes * 60)}` : `run ${formatAutoDuration(summary.runMinutes * 60)}`,
                ...(summary.dungeonEntryKeySeconds > 0
                    ? [isZh ? `地牢钥匙${formatAutoDuration(summary.dungeonEntryKeySeconds)}` : `entry key ${formatAutoDuration(summary.dungeonEntryKeySeconds)}`]
                    : []),
                isZh ? `宝箱期望${formatNumber(summary.expectedChestCount)}` : `exp ${formatNumber(summary.expectedChestCount)}`,
                isZh ? `食物${formatAutoDuration(summary.foodSeconds)}` : `food ${formatAutoDuration(summary.foodSeconds)}`,
                isZh ? `饮料${formatAutoDuration(summary.drinkSeconds)}` : `drink ${formatAutoDuration(summary.drinkSeconds)}`,
            ].join(" | ");
        }
        const dungeonMaterialPlan = getDungeonMaterialPlan(itemHrid);
        if (dungeonMaterialPlan) {
            return [
                isZh ? `宝箱${formatAutoDuration(dungeonMaterialPlan.chestSeconds)}` : `chest ${formatAutoDuration(dungeonMaterialPlan.chestSeconds)}`,
                isZh ? `钥匙${formatAutoDuration(dungeonMaterialPlan.keySeconds)}` : `key ${formatAutoDuration(dungeonMaterialPlan.keySeconds)}`,
                isZh ? `直掉${formatNumber(dungeonMaterialPlan.directExpected)}` : `drop ${formatNumber(dungeonMaterialPlan.directExpected)}`,
                isZh ? `代币换算${formatNumber(dungeonMaterialPlan.shopExpected)}` : `shop ${formatNumber(dungeonMaterialPlan.shopExpected)}`,
                isZh ? `总期望${formatNumber(dungeonMaterialPlan.totalExpected)}` : `exp ${formatNumber(dungeonMaterialPlan.totalExpected)}`,
            ].join(" | ");
        }
        if (getGeneralShopPurchaseInfo(itemHrid)) {
            return isZh ? "商店购买" : "Shop purchase";
        }
        const fixedAttachedRareTooltipPlan = getFixedAttachedRareTooltipPlan(itemHrid);
        if (fixedAttachedRareTooltipPlan) {
            const parts = [
                isZh ? `单次转化${formatAutoDuration(fixedAttachedRareTooltipPlan.actionSeconds)}` : `transmute ${formatAutoDuration(fixedAttachedRareTooltipPlan.actionSeconds)}`,
                isZh ? `效率${formatSignedPercent(fixedAttachedRareTooltipPlan.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(fixedAttachedRareTooltipPlan.efficiencyFraction * 100, 2)}`,
                isZh ? `成功${formatPercent(fixedAttachedRareTooltipPlan.successChance * 100, 2)}` : `succ ${formatPercent(fixedAttachedRareTooltipPlan.successChance * 100, 2)}`,
                isZh ? `总期望${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.effectiveTargetExpectedCount)}` : `exp ${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.effectiveTargetExpectedCount)}`,
            ];
            if (Number.isFinite(fixedAttachedRareTooltipPlan.inputAttachedTargetExpectedCount) && fixedAttachedRareTooltipPlan.inputAttachedTargetExpectedCount > 0) {
                parts.push(
                    isZh
                        ? `输入附带${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.inputAttachedTargetExpectedCount)}`
                        : `input extra ${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.inputAttachedTargetExpectedCount)}`
                );
            }
            if (Number.isFinite(fixedAttachedRareTooltipPlan.knownOutputAttachedTargetExpectedCount) && fixedAttachedRareTooltipPlan.knownOutputAttachedTargetExpectedCount > 0) {
                parts.push(
                    isZh
                        ? `产出附带抵扣${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.knownOutputAttachedTargetExpectedCount)}`
                        : `output extra ${formatAttachedRareNumber(fixedAttachedRareTooltipPlan.knownOutputAttachedTargetExpectedCount)}`
                );
            }
            if (Number.isFinite(fixedAttachedRareTooltipPlan.teaSecondsTotal) && fixedAttachedRareTooltipPlan.teaSecondsTotal > 0) {
                parts.push(isZh ? `单次茶${formatAutoDuration(fixedAttachedRareTooltipPlan.teaSecondsTotal)}` : `tea ${formatAutoDuration(fixedAttachedRareTooltipPlan.teaSecondsTotal)}`);
            }
            if (Number.isFinite(fixedAttachedRareTooltipPlan.catalystSecondsTotal) && fixedAttachedRareTooltipPlan.catalystSecondsTotal > 0) {
                parts.push(isZh ? `单次催化剂${formatAutoDuration(fixedAttachedRareTooltipPlan.catalystSecondsTotal)}` : `cat ${formatAutoDuration(fixedAttachedRareTooltipPlan.catalystSecondsTotal)}`);
            }
            if (Number.isFinite(fixedAttachedRareTooltipPlan.sourceItemSeconds) && fixedAttachedRareTooltipPlan.sourceItemSeconds > 0) {
                parts.push(isZh ? `原料Time${formatAutoDuration(fixedAttachedRareTooltipPlan.sourceItemSeconds)}` : `src ${formatAutoDuration(fixedAttachedRareTooltipPlan.sourceItemSeconds)}`);
            }
            if (Number.isFinite(fixedAttachedRareTooltipPlan.knownOutputSeconds) && fixedAttachedRareTooltipPlan.knownOutputSeconds > 0) {
                parts.push(
                    isZh
                        ? `副产物抵扣${formatAutoDuration(fixedAttachedRareTooltipPlan.knownOutputSeconds)}`
                        : `side ${formatAutoDuration(fixedAttachedRareTooltipPlan.knownOutputSeconds)}`
                );
            }
            return parts.join(" | ");
        }
        const fixedDecomposePlan = getFixedDecomposePlan(itemHrid);
        if (fixedDecomposePlan) {
            const parts = [
                isZh ? `单次分解${formatAutoDuration(fixedDecomposePlan.actionSeconds)}` : `decomp ${formatAutoDuration(fixedDecomposePlan.actionSeconds)}`,
                isZh ? `效率${formatSignedPercent(fixedDecomposePlan.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(fixedDecomposePlan.efficiencyFraction * 100, 2)}`,
                isZh ? `成功${formatPercent(fixedDecomposePlan.successChance * 100, 2)}` : `succ ${formatPercent(fixedDecomposePlan.successChance * 100, 2)}`,
            ];
            if (Number.isFinite(fixedDecomposePlan.teaSecondsTotal) && fixedDecomposePlan.teaSecondsTotal > 0) {
                parts.push(isZh ? `单次茶${formatAutoDuration(fixedDecomposePlan.teaSecondsTotal)}` : `tea ${formatAutoDuration(fixedDecomposePlan.teaSecondsTotal)}`);
            }
            if (Number.isFinite(fixedDecomposePlan.sourceItemSeconds) && fixedDecomposePlan.sourceItemSeconds > 0) {
                parts.push(
                    isZh
                        ? `原料Time${formatAutoDuration(fixedDecomposePlan.sourceItemSeconds)}`
                        : `src ${formatAutoDuration(fixedDecomposePlan.sourceItemSeconds)}`
                );
            }
            if (Number.isFinite(fixedDecomposePlan.sourceSeconds) && fixedDecomposePlan.sourceSeconds > 0) {
                parts.push(
                    isZh
                        ? `本次原料${formatAutoDuration(fixedDecomposePlan.sourceSeconds)}`
                        : `mat ${formatAutoDuration(fixedDecomposePlan.sourceSeconds)}`
                );
            }
            return parts.join(" | ");
        }
        const fixedTransmutePlan = getFixedTransmutePlan(itemHrid);
        if (fixedTransmutePlan) {
            const parts = [
                isZh ? `单次转化${formatAutoDuration(fixedTransmutePlan.actionSeconds)}` : `transmute ${formatAutoDuration(fixedTransmutePlan.actionSeconds)}`,
                isZh ? `效率${formatSignedPercent(fixedTransmutePlan.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(fixedTransmutePlan.efficiencyFraction * 100, 2)}`,
                isZh ? `成功${formatPercent(fixedTransmutePlan.successChance * 100, 2)}` : `succ ${formatPercent(fixedTransmutePlan.successChance * 100, 2)}`,
                isZh ? `期望产出${formatNumber(fixedTransmutePlan.expectedOutputCount)}` : `exp ${formatNumber(fixedTransmutePlan.expectedOutputCount)}`,
            ];
            if (Number.isFinite(fixedTransmutePlan.teaSecondsTotal) && fixedTransmutePlan.teaSecondsTotal > 0) {
                parts.push(isZh ? `单次茶${formatAutoDuration(fixedTransmutePlan.teaSecondsTotal)}` : `tea ${formatAutoDuration(fixedTransmutePlan.teaSecondsTotal)}`);
            }
            if (Number.isFinite(fixedTransmutePlan.sourceItemSeconds) && fixedTransmutePlan.sourceItemSeconds > 0) {
                parts.push(isZh ? `原料Time${formatAutoDuration(fixedTransmutePlan.sourceItemSeconds)}` : `src ${formatAutoDuration(fixedTransmutePlan.sourceItemSeconds)}`);
            }
            if (Number.isFinite(fixedTransmutePlan.sideOutputSeconds) && fixedTransmutePlan.sideOutputSeconds > 0) {
                parts.push(isZh ? `副产物抵扣${formatAutoDuration(fixedTransmutePlan.sideOutputSeconds)}` : `side ${formatAutoDuration(fixedTransmutePlan.sideOutputSeconds)}`);
            }
            return parts.join(" | ");
        }
        const fixedEnhancedEssencePlan = getFixedEnhancedEssencePlan(itemHrid);
        if (fixedEnhancedEssencePlan) {
            const parts = [
                isZh ? `单次分解${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.actionSeconds)}` : `decomp ${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.actionSeconds)}`,
                isZh ? `效率${formatSignedPercent(fixedEnhancedEssencePlan.essenceInfo.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(fixedEnhancedEssencePlan.essenceInfo.efficiencyFraction * 100, 2)}`,
                isZh ? `成功${formatPercent(fixedEnhancedEssencePlan.essenceInfo.successChance * 100, 2)}` : `succ ${formatPercent(fixedEnhancedEssencePlan.essenceInfo.successChance * 100, 2)}`,
                isZh
                    ? `+${fixedEnhancedEssencePlan.enhancementLevel}总时间${formatAutoDuration(fixedEnhancedEssencePlan.recommendation.totalSeconds || 0)}`
                    : `+${fixedEnhancedEssencePlan.enhancementLevel} total ${formatAutoDuration(fixedEnhancedEssencePlan.recommendation.totalSeconds || 0)}`,
            ];
            if (Number.isFinite(fixedEnhancedEssencePlan.essenceInfo.expectedEssenceCount) && fixedEnhancedEssencePlan.essenceInfo.expectedEssenceCount > 0) {
                parts.push(
                    isZh
                        ? `期望精华${formatNumber(fixedEnhancedEssencePlan.essenceInfo.expectedEssenceCount)}`
                        : `exp ${formatNumber(fixedEnhancedEssencePlan.essenceInfo.expectedEssenceCount)}`
                );
            }
            if (Number.isFinite(fixedEnhancedEssencePlan.essenceInfo.teaSecondsTotal) && fixedEnhancedEssencePlan.essenceInfo.teaSecondsTotal > 0) {
                parts.push(isZh ? `单次茶${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.teaSecondsTotal)}` : `tea ${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.teaSecondsTotal)}`);
            }
            if (Number.isFinite(fixedEnhancedEssencePlan.essenceInfo.catalystSecondsTotal) && fixedEnhancedEssencePlan.essenceInfo.catalystSecondsTotal > 0) {
                parts.push(isZh ? `单次催化剂${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.catalystSecondsTotal)}` : `cat ${formatAutoDuration(fixedEnhancedEssencePlan.essenceInfo.catalystSecondsTotal)}`);
            }
            return parts.join(" | ");
        }
        const essencePlan = getEssenceDecomposePlan(itemHrid);
        if (essencePlan) {
            const parts = [
                isZh ? `单次分解${formatAutoDuration(essencePlan.actionSeconds)}` : `decomp ${formatAutoDuration(essencePlan.actionSeconds)}`,
                isZh ? `效率${formatSignedPercent(essencePlan.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(essencePlan.efficiencyFraction * 100, 2)}`,
                isZh ? `成功${formatPercent(essencePlan.successChance * 100, 2)}` : `succ ${formatPercent(essencePlan.successChance * 100, 2)}`,
            ];
            if (Number.isFinite(essencePlan.teaSecondsTotal) && essencePlan.teaSecondsTotal > 0) {
                parts.push(isZh ? `单次茶${formatAutoDuration(essencePlan.teaSecondsTotal)}` : `tea ${formatAutoDuration(essencePlan.teaSecondsTotal)}`);
            }
            if (Number.isFinite(essencePlan.sourceItemSeconds) && essencePlan.sourceItemSeconds > 0) {
                parts.push(
                    isZh
                        ? `原料Time${formatAutoDuration(essencePlan.sourceItemSeconds)}`
                        : `src ${formatAutoDuration(essencePlan.sourceItemSeconds)}`
                );
            }
            if (Number.isFinite(essencePlan.sourceSeconds) && essencePlan.sourceSeconds > 0) {
                parts.push(
                    isZh
                        ? `本次原料${formatAutoDuration(essencePlan.sourceSeconds)}`
                        : `mat ${formatAutoDuration(essencePlan.sourceSeconds)}`
                );
            }
            return parts.join(" | ");
        }
        const action = findActionForItem(itemHrid);
        if (!action) {
            return null;
        }

        const actionInfo = getActionSummary(action);
        const outputCount = getDisplayOutputCountPerAction(action, itemHrid, actionInfo);
        const breakdown = getPerActionCostBreakdown(itemHrid, action, actionInfo);
        const displayInputs = getDisplayInputs(action, actionInfo);
        const parts = [
            isZh ? `单次耗时${formatAutoDuration(actionInfo.seconds)}` : `act ${formatAutoDuration(actionInfo.seconds)}`,
            isZh ? `效率${formatSignedPercent(actionInfo.efficiencyFraction * 100, 2)}` : `eff ${formatSignedPercent(actionInfo.efficiencyFraction * 100, 2)}`,
        ];

        if (Number.isFinite(outputCount) && outputCount > 0) {
            parts.push(isZh ? `产出${formatNumber(outputCount)}` : `out ${formatNumber(outputCount)}`);
        }
        const processingProductDetail = getProcessingProductDetail(action, itemHrid, actionInfo);
        if (processingProductDetail) {
            parts.push(
                isZh
                    ? `加工${processingProductDetail.itemName}${formatNumber(processingProductDetail.expectedCount)}`
                    : `proc ${processingProductDetail.itemName} ${formatNumber(processingProductDetail.expectedCount)}`
            );
        }
        if (Number.isFinite(breakdown.teaSecondsTotal) && breakdown.teaSecondsTotal > 0) {
            parts.push(isZh ? `单次茶${formatAutoDuration(breakdown.teaSecondsTotal)}` : `tea ${formatAutoDuration(breakdown.teaSecondsTotal)}`);
        }
        if (displayInputs.length === 1) {
            const sourceSeconds = calculateItemSeconds(displayInputs[0].itemHrid);
            if (Number.isFinite(sourceSeconds) && sourceSeconds > 0) {
                parts.push(isZh ? `原料Time${formatAutoDuration(sourceSeconds)}` : `src ${formatAutoDuration(sourceSeconds)}`);
            }
        }
        if (Number.isFinite(breakdown.inputSecondsTotal) && breakdown.inputSecondsTotal > 0) {
            parts.push(isZh ? `本次原料${formatAutoDuration(breakdown.inputSecondsTotal)}` : `mat ${formatAutoDuration(breakdown.inputSecondsTotal)}`);
        }

        return parts.join(" | ");
    }

    function getItemLoadoutDetail(itemHrid) {
        if (isMissingDerivedRuntimeState()) {
            ensureRuntimeStateFresh();
        }
        if (getConfiguredTimeCalculatorEntry(itemHrid)) {
            return isZh ? "来源: 时间计算面板" : "Source: Time calculator";
        }
        if (getGeneralShopPurchaseInfo(itemHrid)) {
            return isZh ? "来源: 商店购买" : "Source: Shop purchase";
        }
        const fixedAttachedRareTooltipPlan = getFixedAttachedRareTooltipPlan(itemHrid);
        if (fixedAttachedRareTooltipPlan) {
            const catalystText = fixedAttachedRareTooltipPlan.catalystItemName
                ? `${isZh ? `催化剂: ${fixedAttachedRareTooltipPlan.catalystItemName}` : `Catalyst: ${fixedAttachedRareTooltipPlan.catalystItemName}`} | `
                : "";
            return `${isZh ? `转化: ${fixedAttachedRareTooltipPlan.itemName}` : `Transmute: ${fixedAttachedRareTooltipPlan.itemName}`} | ${catalystText}${getLoadoutDisplayText(fixedAttachedRareTooltipPlan.action)}`;
        }
        const fixedDecomposePlan = getFixedDecomposePlan(itemHrid);
        if (fixedDecomposePlan) {
            return `${isZh ? `分解: ${fixedDecomposePlan.itemName}` : `Decompose: ${fixedDecomposePlan.itemName}`} | ${getLoadoutDisplayText(fixedDecomposePlan.action)}`;
        }
        const fixedTransmutePlan = getFixedTransmutePlan(itemHrid);
        if (fixedTransmutePlan) {
            return `${isZh ? `转化: ${fixedTransmutePlan.itemName}` : `Transmute: ${fixedTransmutePlan.itemName}`} | ${getLoadoutDisplayText(fixedTransmutePlan.action)}`;
        }
        const fixedEnhancedEssencePlan = getFixedEnhancedEssencePlan(itemHrid);
        if (fixedEnhancedEssencePlan) {
            const sourceLabel = `${fixedEnhancedEssencePlan.itemName} +${fixedEnhancedEssencePlan.enhancementLevel}`;
            return `${isZh ? `分解: ${sourceLabel}` : `Decompose: ${sourceLabel}`} | ${getLoadoutDisplayText(fixedEnhancedEssencePlan.action)}`;
        }
        const dungeonMaterialPlan = getDungeonMaterialPlan(itemHrid);
        if (dungeonMaterialPlan) {
            return isZh
                ? `来源: ${dungeonMaterialPlan.chestName} + ${dungeonMaterialPlan.keyName}`
                : `Source: ${dungeonMaterialPlan.chestName} + ${dungeonMaterialPlan.keyName}`;
        }
        const essencePlan = getEssenceDecomposePlan(itemHrid);
        if (essencePlan) {
            return `${isZh ? `分解: ${essencePlan.itemName}` : `Decompose: ${essencePlan.itemName}`} | ${getLoadoutDisplayText(essencePlan.action)}`;
        }
        const action = findActionForItem(itemHrid);
        if (!action) {
            return null;
        }
        return getLoadoutDisplayText(action);
    }

    function getLoadoutById(loadoutId) {
        if (!Number.isFinite(Number(loadoutId))) {
            return null;
        }
        return state.characterLoadoutDict?.[Number(loadoutId)] || null;
    }

    function getEquippedItemsForLoadout(actionTypeHrid, explicitLoadout) {
        const loadout = explicitLoadout || resolveSkillingLoadout(actionTypeHrid).loadout;
        if (loadout?.wearableMap) {
            const items = [];
            for (const [slotKey, rawRef] of Object.entries(loadout.wearableMap || {})) {
                const entry = parseWearableReference(rawRef);
                if (!entry?.itemHrid) {
                    continue;
                }
                items.push({
                    itemHrid: entry.itemHrid,
                    enhancementLevel: resolveWearableEnhancement(entry, loadout),
                    itemLocationHrid: slotKey,
                    count: 1,
                });
            }
            return items;
        }
        return getEquippedItems(actionTypeHrid);
    }

    function buildEquipmentNoncombatTotalsForLoadout(actionTypeHrid, explicitLoadout) {
        const totals = {};
        const toolSlot = getToolSlotForActionType(actionTypeHrid);
        for (const item of getEquippedItemsForLoadout(actionTypeHrid, explicitLoadout)) {
            const location = item.itemLocationHrid || "";
            if (location.endsWith("_tool") && location !== toolSlot) {
                continue;
            }
            const equipmentDetail = state.itemDetailMap?.[item.itemHrid]?.equipmentDetail;
            if (!equipmentDetail) {
                continue;
            }
            const enhancementMultiplier = getEnhancementBonusMultiplier(item.enhancementLevel || 0);
            const baseStats = equipmentDetail.noncombatStats || {};
            const enhancementStats = equipmentDetail.noncombatEnhancementBonuses || {};
            for (const [key, value] of Object.entries(baseStats)) {
                if (Number.isFinite(Number(value))) {
                    totals[key] = (totals[key] || 0) + Number(value);
                }
            }
            for (const [key, value] of Object.entries(enhancementStats)) {
                if (Number.isFinite(Number(value))) {
                    totals[key] = (totals[key] || 0) + Number(value) * enhancementMultiplier;
                }
            }
        }
        return totals;
    }

    function getDrinkConcentrationForLoadout(actionTypeHrid, explicitLoadout) {
        const pouch = getEquippedItemsForLoadout(actionTypeHrid, explicitLoadout).find((item) => item.itemHrid === "/items/guzzling_pouch");
        if (!pouch || !state.itemDetailMap?.["/items/guzzling_pouch"]?.equipmentDetail) {
            return 1;
        }
        const detail = state.itemDetailMap["/items/guzzling_pouch"].equipmentDetail;
        const base = detail.noncombatStats?.drinkConcentration || 0;
        const bonus = detail.noncombatEnhancementBonuses?.drinkConcentration || 0;
        return 1 + base + bonus * getEnhancementBonusMultiplier(pouch.enhancementLevel || 0);
    }

    function getTeaBuffsForLoadout(actionTypeHrid, explicitLoadout) {
        const skillId = actionTypeHrid.replace("/action_types/", "");
        const concentration = getDrinkConcentrationForLoadout(actionTypeHrid, explicitLoadout);
        const buffs = {
            blessedFraction: 0,
            efficiencyFraction: 0,
            quantityFraction: 0,
            lessResourceFraction: 0,
            processingFraction: 0,
            successRateFraction: 0,
            alchemySuccessFraction: 0,
            skillLevelBonus: 0,
            actionLevelPenalty: 0,
            wisdomFraction: 0,
            activeTeas: [],
            concentrationMultiplier: concentration,
            durationSeconds: 300 / concentration,
        };

        const loadoutTeaList = Array.isArray(explicitLoadout?.drinkItemHrids)
            ? explicitLoadout.drinkItemHrids.filter(Boolean).map((itemHrid) => ({ itemHrid }))
            : [];
        const currentTeaList = state.actionTypeDrinkSlotsMap?.[actionTypeHrid] || [];
        const teaList = loadoutTeaList.length > 0 ? loadoutTeaList : currentTeaList;
        for (const tea of teaList) {
            if (!tea?.itemHrid) {
                continue;
            }
            buffs.activeTeas.push(tea.itemHrid);
            const teaDetail = state.itemDetailMap?.[tea.itemHrid];
            for (const buff of teaDetail?.consumableDetail?.buffs || []) {
                if (buff.typeHrid === "/buff_types/artisan") {
                    buffs.lessResourceFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/gathering" || buff.typeHrid === "/buff_types/gourmet") {
                    buffs.quantityFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/processing") {
                    buffs.processingFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/efficiency") {
                    buffs.efficiencyFraction += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/success_rate") {
                    buffs.successRateFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === "/buff_types/alchemy_success") {
                    buffs.alchemySuccessFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === "/buff_types/blessed") {
                    buffs.blessedFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === "/buff_types/wisdom") {
                    buffs.wisdomFraction += getBuffAmount(buff);
                } else if (buff.typeHrid === `/buff_types/${skillId}_level`) {
                    buffs.skillLevelBonus += buff.flatBoost;
                } else if (buff.typeHrid === "/buff_types/action_level") {
                    buffs.actionLevelPenalty += buff.flatBoost;
                }
            }
        }

        buffs.blessedFraction *= concentration;
        buffs.efficiencyFraction *= concentration;
        buffs.quantityFraction *= concentration;
        buffs.lessResourceFraction *= concentration;
        buffs.processingFraction *= concentration;
        buffs.successRateFraction *= concentration;
        buffs.alchemySuccessFraction *= concentration;
        buffs.skillLevelBonus *= concentration;
        buffs.actionLevelPenalty *= concentration;
        buffs.wisdomFraction *= concentration;
        return buffs;
    }

    function getHouseRoomLevel(roomHrid) {
        const room = getContainerValue(state.characterHouseRoomMap, roomHrid);
        return Math.max(0, Number(room?.level || 0));
    }

    function getEnhancingPanel() {
        if (state.enhancingPanelRef?.isConnected) {
            return state.enhancingPanelRef;
        }
        const candidates = Array.from(document.querySelectorAll("div")).filter((element) => {
            const text = element.innerText || "";
            return text.includes("推荐等级") && text.includes("目标等级") && text.includes("保护") && text.includes("成功率");
        });
        if (!candidates.length) {
            state.enhancingPanelRef = null;
            return null;
        }
        candidates.sort((left, right) => (left.innerText || "").length - (right.innerText || "").length);
        state.enhancingPanelRef = candidates[0] || null;
        return state.enhancingPanelRef;
    }

    function shouldRefreshEnhancingFromTarget(target) {
        if (!(target instanceof Element)) {
            return false;
        }
        const panel = getEnhancingPanel();
        if (!panel || !panel.isConnected) {
            return false;
        }
        if (panel.contains(target)) {
            return true;
        }
        const clickable = target.closest("button, [role='button'], input, select, textarea, label");
        const text = ((clickable && clickable.textContent) || target.textContent || "").trim();
        return text === "强化" || text === "当前行动";
    }

    function getItemsSpriteBaseHref() {
        const itemUse = Array.from(document.querySelectorAll("use")).find((node) => {
            const value = node.getAttribute("href") || node.getAttribute("xlink:href") || "";
            return value.includes("items_sprite") && value.includes("#");
        });
        if (!itemUse) {
            return `${location.origin}/static/media/items_sprite.svg`;
        }
        const value = itemUse.getAttribute("href") || itemUse.getAttribute("xlink:href") || "";
        return value.split("#")[0];
    }

    function getIconHrefByItemHrid(itemHrid) {
        return `${getItemsSpriteBaseHref()}#${(itemHrid || "").split("/").pop() || ""}`;
    }

    function createIconSvg(iconHref, sizePx = 18) {
        const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        svg.setAttribute("width", `${sizePx}px`);
        svg.setAttribute("height", `${sizePx}px`);
        svg.style.display = "block";
        const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
        use.setAttributeNS("http://www.w3.org/1999/xlink", "href", iconHref);
        svg.appendChild(use);
        return svg;
    }

    function getVisibleEnhancingProtectionNames(panel) {
        const names = new Map();
        if (!panel) {
            return names;
        }
        for (const icon of panel.querySelectorAll('svg[role="img"][aria-label]')) {
            const label = (icon.getAttribute("aria-label") || "").trim();
            if (!label || label === "Guide" || label === "Skill" || label === "Unlimited") {
                continue;
            }
            const use = icon.querySelector("use");
            const href = use?.getAttribute("href") || use?.getAttribute("xlink:href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex < 0 || !href.includes("items_sprite")) {
                continue;
            }
            names.set(`/items/${href.slice(hashIndex + 1)}`, label);
        }
        return names;
    }

    function findDescendantWithText(root, text) {
        if (!root) {
            return null;
        }
        const candidates = root.querySelectorAll("div, span");
        for (const candidate of candidates) {
            if ((candidate.textContent || "").trim().startsWith(text)) {
                return candidate;
            }
        }
        return null;
    }

    function getEnhancingNotesContainer(panel) {
        if (!panel) {
            return null;
        }
        const candidates = Array.from(panel.querySelectorAll("div")).filter((element) => {
            const text = (element.textContent || "").trim().replace(/\s+/g, " ");
            return text.startsWith("推荐等级") && text.length < 40;
        });
        const noteText = candidates.sort((left, right) => {
            return (left.textContent || "").length - (right.textContent || "").length;
        })[0] || null;
        return noteText?.parentElement || null;
    }

    function getEnhancingPanelSelection() {
        const panel = getEnhancingPanel();
        if (!panel) {
            return null;
        }

        const itemUse = Array.from(panel.querySelectorAll("use")).find((node) => {
            const value = node.getAttribute("href") || node.getAttribute("xlink:href") || "";
            return value.includes("items_sprite");
        });
        const href = itemUse ? (itemUse.getAttribute("href") || itemUse.getAttribute("xlink:href") || "") : "";
        const hashIndex = href.indexOf("#");
        const itemHrid = hashIndex >= 0 ? `/items/${href.slice(hashIndex + 1)}` : "";
        if (!itemHrid || !state.itemDetailMap?.[itemHrid]) {
            return null;
        }

        const targetInput = panel.querySelector('input[role="spinbutton"], input[type="number"]');
        const targetLevel = Math.max(1, Math.min(20, Number(targetInput?.value || 1) || 1));

        const loadoutContainer = findDescendantWithText(panel, "配装")?.parentElement || null;
        const loadoutInput = loadoutContainer
            ? Array.from(loadoutContainer.querySelectorAll("input")).find((input) => /^\d+$/.test(String(input.value || "")))
            : null;
        const loadout = getLoadoutById(Number(loadoutInput?.value || 0)) || resolveSkillingLoadout(ENHANCING_ACTION_TYPE).loadout;

        return {
            panel,
            itemHrid,
            targetLevel,
            loadout,
            notesContainer: getEnhancingNotesContainer(panel),
        };
    }

    function solveLinearSystem(matrix, constants) {
        const size = constants.length;
        const rows = matrix.map((row, index) => row.slice().concat([constants[index]]));
        for (let col = 0; col < size; col += 1) {
            let pivot = col;
            for (let row = col + 1; row < size; row += 1) {
                if (Math.abs(rows[row][col]) > Math.abs(rows[pivot][col])) {
                    pivot = row;
                }
            }
            if (Math.abs(rows[pivot][col]) < 1e-12) {
                return null;
            }
            if (pivot !== col) {
                const temp = rows[col];
                rows[col] = rows[pivot];
                rows[pivot] = temp;
            }
            const pivotValue = rows[col][col];
            for (let currentCol = col; currentCol <= size; currentCol += 1) {
                rows[col][currentCol] /= pivotValue;
            }
            for (let row = 0; row < size; row += 1) {
                if (row === col) {
                    continue;
                }
                const factor = rows[row][col];
                if (!factor) {
                    continue;
                }
                for (let currentCol = col; currentCol <= size; currentCol += 1) {
                    rows[row][currentCol] -= factor * rows[col][currentCol];
                }
            }
        }
        return rows.map((row) => row[size]);
    }

    function getEnhancingProtectionOptions(itemHrid) {
        const itemDetail = state.itemDetailMap?.[itemHrid];
        if (!itemDetail) {
            return [];
        }
        const seen = new Set();
        const options = [];
        for (const candidateHrid of [itemHrid].concat(itemDetail.protectionItemHrids || [])) {
            if (!candidateHrid || candidateHrid === "/items/mirror_of_protection" || candidateHrid.includes("_refined") || seen.has(candidateHrid)) {
                continue;
            }
            seen.add(candidateHrid);
            options.push(candidateHrid);
        }
        return options;
    }

    function getEnhancingAttemptMetrics(itemHrid, explicitLoadout) {
        const itemDetail = state.itemDetailMap?.[itemHrid];
        const actionDetail = state.actionDetailMap?.[ENHANCING_ACTION_HRID];
        if (!itemDetail || !actionDetail) {
            return null;
        }
        const totals = buildEquipmentNoncombatTotalsForLoadout(ENHANCING_ACTION_TYPE, explicitLoadout);
        const teaBuffs = getTeaBuffsForLoadout(ENHANCING_ACTION_TYPE, explicitLoadout);
        const globalBuffs = getGlobalActionBuffs(ENHANCING_ACTION_TYPE);
        const observatoryLevel = getHouseRoomLevel("/house_rooms/observatory");
        const enhancingLevel = getSkillLevel("/skills/enhancing");
        const effectiveLevel = enhancingLevel + teaBuffs.skillLevelBonus;
        const itemLevel = Math.max(1, Number(itemDetail.itemLevel || 1));
        const enhancingSuccessBonus =
            Number(totals.enhancingSuccess || 0) * 100 +
            sumBuffsByType(globalBuffs, "/buff_types/enhancing_success") * 100;
        const actionSpeedBonus =
            Number(totals.enhancingSpeed || 0) * 100 +
            Number(totals.skillingSpeed || 0) * 100 +
            sumBuffsByType(globalBuffs, "/buff_types/action_speed") * 100 +
            teaBuffs.actionLevelPenalty * 0;
        let totalBonus = 1;
        if (effectiveLevel >= itemLevel) {
            totalBonus = 1 + (0.05 * (effectiveLevel + observatoryLevel - itemLevel) + enhancingSuccessBonus) / 100;
        } else {
            totalBonus = (1 - (0.5 * (1 - effectiveLevel / itemLevel))) + ((0.05 * observatoryLevel) + enhancingSuccessBonus) / 100;
        }
        const speedBonus = (enhancingLevel > itemLevel
            ? (effectiveLevel + observatoryLevel - itemLevel)
            : observatoryLevel) + actionSpeedBonus;
        const attemptSeconds = Math.max((actionDetail.baseTimeCost / 1000000000) / (1 + speedBonus / 100), 3);
        return {
            attemptSeconds,
            totalBonus,
            itemLevel,
            effectiveLevel,
            observatoryLevel,
            teaBuffs,
        };
    }

    function getEnhancingMaterialSeconds(itemHrid) {
        if (itemHrid === "/items/coin") {
            return 0;
        }
        const seconds = calculateItemSeconds(itemHrid);
        return Number.isFinite(seconds) && seconds > 0 ? seconds : 0;
    }

    function getEnhancingTeaSeconds(teaBuffs, attemptSeconds) {
        const teaPerAction = Number(attemptSeconds || 0) / Math.max(teaBuffs?.durationSeconds || 300, 1);
        let total = 0;
        for (const teaItemHrid of teaBuffs?.activeTeas || []) {
            const teaSeconds = calculateItemSeconds(teaItemHrid);
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds <= 0) {
                continue;
            }
            total += teaPerAction * teaSeconds;
        }
        return total;
    }

    function solveEnhancingAttempts(stopAt, protectAt, successMultiplier, blessedExtraChance) {
        const size = stopAt + 1;
        const matrix = Array.from({ length: size }, () => Array(size).fill(0));
        const attemptsConstants = Array(size).fill(0);
        const protectsConstants = Array(size).fill(0);

        matrix[stopAt][stopAt] = 1;

        for (let level = 0; level < stopAt; level += 1) {
            const baseSuccessChance = clamp01((ENHANCING_SUCCESS_RATES[level] || ENHANCING_SUCCESS_RATES[ENHANCING_SUCCESS_RATES.length - 1]) * successMultiplier);
            const doubleSuccessChance = clamp01(baseSuccessChance * blessedExtraChance);
            const normalSuccessChance = Math.max(0, baseSuccessChance - doubleSuccessChance);
            const failureChance = Math.max(0, 1 - baseSuccessChance);
            const failureUsesProtection = level >= protectAt;
            const failureDestination = failureUsesProtection ? Math.max(0, level - 1) : 0;
            const normalSuccessDestination = Math.min(stopAt, level + 1);
            const doubleSuccessDestination = Math.min(stopAt, level + 2);

            matrix[level][level] = 1;
            matrix[level][normalSuccessDestination] -= normalSuccessChance;
            matrix[level][doubleSuccessDestination] -= doubleSuccessChance;
            matrix[level][failureDestination] -= failureChance;
            attemptsConstants[level] = 1;
            protectsConstants[level] = failureChance * (failureUsesProtection ? 1 : 0);
        }

        const attempts = solveLinearSystem(matrix, attemptsConstants);
        const protects = solveLinearSystem(matrix, protectsConstants);
        if (!attempts || !protects) {
            return null;
        }
        return {
            attempts: attempts[0],
            protects: protects[0],
        };
    }

    function getEnhancingRecommendationForItem(itemHrid, targetLevel, explicitLoadout = null) {
        if (isMissingDerivedRuntimeState()) {
            ensureRuntimeStateFresh();
        }
        const itemDetail = state.itemDetailMap?.[itemHrid];
        if (!itemDetail) {
            return null;
        }
        if (targetLevel < 2) {
            return {
                itemHrid,
                targetLevel,
                loadout: explicitLoadout || resolveSkillingLoadout(ENHANCING_ACTION_TYPE).loadout,
                recommendProtectAt: 0,
                recommendMaterialHrid: "",
                recommendMaterialName: isZh ? "无需" : "None",
                totalSeconds: calculateItemSeconds(itemHrid) || 0,
                attempts: 0,
                protects: 0,
                enhancementMaterialCounts: [],
                perAttemptSeconds: 0,
                attemptSeconds: 0,
            };
        }

        const loadout = explicitLoadout || resolveSkillingLoadout(ENHANCING_ACTION_TYPE).loadout;
        const metrics = getEnhancingAttemptMetrics(itemHrid, loadout);
        if (!metrics) {
            return null;
        }

        const activeProtectionOptions = getEnhancingProtectionOptions(itemHrid);
        const protectionCandidates = activeProtectionOptions
            .map((candidateHrid) => ({
                itemHrid: candidateHrid,
                itemName: getLocalizedItemName(candidateHrid, state.itemDetailMap?.[candidateHrid]?.name || candidateHrid),
                seconds: getEnhancingMaterialSeconds(candidateHrid),
            }))
            .sort((left, right) => left.seconds - right.seconds);
        const bestProtectionMaterial = protectionCandidates[0] || null;
        const hasValidProtectionTime = Boolean(
            bestProtectionMaterial &&
            Number.isFinite(bestProtectionMaterial.seconds) &&
            bestProtectionMaterial.seconds > 0
        );

        const baseItemSeconds = Math.max(0, Number(calculateItemSeconds(itemHrid) || 0));

        const enhancementMaterialSeconds = (itemDetail.enhancementCosts || []).reduce((total, cost) => {
            if (!cost?.itemHrid || cost.itemHrid === "/items/coin") {
                return total;
            }
            return total + Number(cost.count || 0) * getEnhancingMaterialSeconds(cost.itemHrid);
        }, 0);
        const enhancementMaterialCounts = (itemDetail.enhancementCosts || [])
            .filter((cost) => cost?.itemHrid && cost.itemHrid !== "/items/coin")
            .map((cost) => ({
                itemHrid: cost.itemHrid,
                itemName: getLocalizedItemName(cost.itemHrid, state.itemDetailMap?.[cost.itemHrid]?.name || cost.itemHrid),
                countPerAttempt: Number(cost.count || 0),
            }));
        const teaSeconds = getEnhancingTeaSeconds(metrics.teaBuffs, metrics.attemptSeconds);
        const perAttemptSeconds = metrics.attemptSeconds + enhancementMaterialSeconds + teaSeconds;
        const blessedExtraChance = Math.max(0, Number(metrics.teaBuffs.blessedFraction || 0));

        let bestPlan = null;
        if (!hasValidProtectionTime) {
            const fallbackProtectAt = targetLevel >= 7 ? 7 : (targetLevel >= 2 ? targetLevel : 0);
            const fallbackSolved = fallbackProtectAt > 0
                ? solveEnhancingAttempts(targetLevel, fallbackProtectAt, metrics.totalBonus, blessedExtraChance)
                : null;
            const fallbackMaterial = protectionCandidates[0] || {
                itemHrid: activeProtectionOptions[0] || "",
                itemName: "",
                seconds: 0,
            };
            return {
                itemHrid,
                targetLevel,
                loadout,
                recommendProtectAt: fallbackProtectAt,
                recommendMaterialHrid: fallbackMaterial.itemHrid || "",
                recommendMaterialName: fallbackMaterial.itemName || (isZh ? "无法计算" : "Unavailable"),
                totalSeconds: baseItemSeconds +
                    perAttemptSeconds * Number(fallbackSolved?.attempts || 0) +
                    Number(fallbackMaterial.seconds || 0) * Number(fallbackSolved?.protects || 0),
                attempts: Number(fallbackSolved?.attempts || 0),
                protects: Number(fallbackSolved?.protects || 0),
                enhancementMaterialCounts,
                perAttemptSeconds,
                attemptSeconds: metrics.attemptSeconds,
                baseItemSeconds,
            };
        }
        const protectionCandidatesToTry = [targetLevel + 1];
        for (let protectAt = 2; protectAt <= targetLevel; protectAt += 1) {
            protectionCandidatesToTry.push(protectAt);
        }
        for (const protectAt of protectionCandidatesToTry) {
            const solved = solveEnhancingAttempts(targetLevel, protectAt, metrics.totalBonus, blessedExtraChance);
            if (!solved) {
                continue;
            }
            const totalSeconds = baseItemSeconds +
                perAttemptSeconds * solved.attempts +
                (bestProtectionMaterial ? bestProtectionMaterial.seconds * solved.protects : 0);
            if (!bestPlan || totalSeconds < bestPlan.totalSeconds) {
                bestPlan = {
                    itemHrid,
                    targetLevel,
                    loadout,
                    recommendProtectAt: protectAt > targetLevel ? 0 : protectAt,
                    recommendMaterialHrid: bestProtectionMaterial?.itemHrid || "",
                    recommendMaterialName: bestProtectionMaterial?.itemName || (isZh ? "无" : "None"),
                    totalSeconds,
                    attempts: solved.attempts,
                    protects: solved.protects,
                    enhancementMaterialCounts,
                    perAttemptSeconds,
                    attemptSeconds: metrics.attemptSeconds,
                    baseItemSeconds,
                };
            }
        }
        return bestPlan;
    }

    function getEnhancingRecommendation() {
        const selection = getEnhancingPanelSelection();
        if (!selection) {
            return null;
        }
        const recommendation = getEnhancingRecommendationForItem(selection.itemHrid, selection.targetLevel, selection.loadout);
        return recommendation ? { ...selection, ...recommendation } : null;
    }

    function renderEnhancingRecommendation() {
        document.querySelectorAll(".ictime-enhancing-recommend").forEach((node) => node.remove());
    }

    function queueEnhancingRefresh() {
        if (state.enhancingRefreshQueued || state.isShutDown) {
            return;
        }
        state.enhancingRefreshQueued = true;
        requestAnimationFrame(() => {
            state.enhancingRefreshQueued = false;
            renderEnhancingRecommendation();
        });
    }

    function getAlchemyTransmutePanel() {
        const panel = document.querySelector('[class*="SkillActionDetail_alchemyComponent"]');
        if (!(panel instanceof HTMLElement) || !panel.isConnected) {
            return null;
        }
        const selectedTab = Array.from(document.querySelectorAll('[class*="AlchemyPanel_tabsComponentContainer"] button, [class*="AlchemyPanel_tabsComponentContainer"] [role="tab"]'))
            .find((button) => button.classList.contains("Mui-selected") || button.getAttribute("aria-selected") === "true");
        const selectedText = (selectedTab?.textContent || "").trim().toLowerCase();
        if (selectedText && !["转化", "transmute", "当前行动", "current action"].includes(selectedText)) {
            return null;
        }
        return panel;
    }

    function shouldRefreshAlchemyInferenceFromTarget(target) {
        if (!(target instanceof Element)) {
            return false;
        }
        const panel = getAlchemyTransmutePanel();
        if (panel?.contains(target)) {
            return true;
        }
        const clickable = target.closest("button, [role='button'], input, select, textarea, label");
        const text = ((clickable && clickable.textContent) || target.textContent || "").trim();
        return ["转化", "当前行动", "炼金", "Transmute", "Current Action", "Alchemy"].includes(text);
    }

    function isAlchemyInferenceOwnedNode(node) {
        if (node instanceof Element) {
            return node.matches(".ictime-alchemy-inference, .ictime-alchemy-inference-row") ||
                Boolean(node.closest(".ictime-alchemy-inference, .ictime-alchemy-inference-row"));
        }
        return node instanceof Text
            ? Boolean(node.parentElement?.closest(".ictime-alchemy-inference, .ictime-alchemy-inference-row"))
            : false;
    }

    function ensureAlchemyInferenceObserver() {
        const panel = getAlchemyTransmutePanel();
        if (state.alchemyObservedPanel === panel && state.alchemyInferenceObserver) {
            return;
        }
        state.alchemyInferenceObserver?.disconnect();
        state.alchemyInferenceObserver = null;
        state.alchemyObservedPanel = null;
        if (!panel) {
            return;
        }
        const observer = new MutationObserver((mutations) => {
            if (state.isShutDown) {
                return;
            }
            let shouldRefresh = false;
            for (const mutation of mutations) {
                if (mutation.type === "characterData") {
                    if (!isAlchemyInferenceOwnedNode(mutation.target)) {
                        shouldRefresh = true;
                        break;
                    }
                    continue;
                }
                if (mutation.type !== "childList") {
                    continue;
                }
                const changedNodes = [...mutation.addedNodes, ...mutation.removedNodes];
                if (!changedNodes.length) {
                    continue;
                }
                if (changedNodes.every((node) => isAlchemyInferenceOwnedNode(node))) {
                    continue;
                }
                shouldRefresh = true;
                break;
            }
            if (shouldRefresh) {
                queueAlchemyInferenceRefresh();
            }
        });
        observer.observe(panel, { childList: true, subtree: true, characterData: true });
        state.alchemyInferenceObserver = observer;
        state.alchemyObservedPanel = panel;
    }

    function getAlchemyNotesContainer(panel) {
        if (!panel) {
            return null;
        }
        return panel.querySelector('[class*="SkillActionDetail_notes"]') || panel;
    }

    function extractItemHridFromElement(element) {
        if (!(element instanceof Element)) {
            return "";
        }
        const anchor = element.querySelector('a[href*="#"]');
        if (anchor) {
            const href = anchor.getAttribute("href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex >= 0) {
                return `/items/${href.slice(hashIndex + 1)}`;
            }
        }
        const use = element.querySelector("use");
        if (use) {
            const href = use.getAttribute("href") || use.getAttribute("xlink:href") || "";
            const hashIndex = href.indexOf("#");
            if (hashIndex >= 0) {
                return `/items/${href.slice(hashIndex + 1)}`;
            }
        }
        const labelled = element.querySelector("[aria-label]") || element.closest("[aria-label]");
        const label = labelled?.getAttribute("aria-label") || "";
        return findItemHridByDisplayName(label);
    }

    function parseAlchemyDropRows(panel, selector, section = "output") {
        return Array.from(panel.querySelectorAll(selector)).map((row) => {
            const children = Array.from(row.children || []);
            const countText = children[0]?.textContent || "";
            const nameText = (children[1]?.textContent || row.querySelector('[class*="Item_name"]')?.textContent || "").trim();
            const rateText = children[2]?.textContent || "";
            const itemHrid = extractItemHridFromElement(row) || findItemHridByDisplayName(nameText);
            const count = Math.max(0, parseUiNumber(countText));
            const rate = rateText ? parseUiPercent(rateText) : 1;
            return {
                row,
                itemHrid,
                itemName: nameText || getLocalizedItemName(itemHrid, itemHrid),
                count,
                rate,
                expectedCount: count * rate,
                section,
            };
        }).filter((entry) => entry.itemHrid && Number.isFinite(entry.expectedCount) && entry.expectedCount > 0);
    }

    function getAlchemyTransmutePanelSelection() {
        const panel = getAlchemyTransmutePanel();
        if (!panel) {
            return null;
        }
        const requirementsRoot = panel.querySelector('[class*="SkillActionDetail_itemRequirements"]');
        const inputItems = [];
        for (const container of requirementsRoot?.querySelectorAll('[class*="Item_itemContainer"]') || []) {
            let countNode = container.previousElementSibling;
            while (countNode && !String(countNode.className || "").includes("SkillActionDetail_inputCount")) {
                countNode = countNode.previousElementSibling;
            }
            const count = Math.max(0, parseUiNumber(countNode?.textContent || 0));
            const rawName = (container.querySelector('[class*="Item_name"]')?.textContent || "").trim();
            const baseName = rawName.split("+")[0].trim();
            const itemHrid = extractItemHridFromElement(container) || findItemHridByDisplayName(baseName);
            if (!itemHrid) {
                continue;
            }
            inputItems.push({
                itemHrid,
                itemName: baseName || getLocalizedItemName(itemHrid, itemHrid),
                count,
            });
        }
        if (!inputItems.length) {
            return null;
        }
        const sourceItemHrid = inputItems.find((item) => item.itemHrid !== "/items/coin")?.itemHrid || inputItems[0].itemHrid;
        const sourceItemDetail = state.itemDetailMap?.[sourceItemHrid];
        if (!(sourceItemDetail?.alchemyDetail?.transmuteDropTable || []).length) {
            return null;
        }

        const successNode = panel.querySelector('[class*="SkillActionDetail_successRate"] [class*="SkillActionDetail_value"]');
        const timeNode = panel.querySelector('[class*="SkillActionDetail_timeCost"] [class*="SkillActionDetail_value"]');
        const successChance = parseUiPercent(successNode?.textContent || 0);
        const actionSeconds = parseUiDurationSeconds(timeNode?.textContent || 0);
        if (!Number.isFinite(successChance) || successChance <= 0 || !Number.isFinite(actionSeconds) || actionSeconds <= 0) {
            return null;
        }
        const transmuteAction = state.actionDetailMap?.["/actions/alchemy/transmute"];
        const actionSummary = transmuteAction ? getActionSummary(transmuteAction) : null;
        const efficiencyFraction = getAlchemyDecomposeEfficiencyFraction(sourceItemHrid, actionSummary);
        const efficiencyMultiplier = Math.max(1 + efficiencyFraction, 1);

        const catalystElement = panel.querySelector(
            '[class*="SkillActionDetail_catalystItemInput"] [class*="Item_item"] [aria-label], ' +
            '[class*="SkillActionDetail_catalystItemInputContainer"] [class*="Item_item"] [aria-label]'
        );
        const catalystItemHrid = catalystElement
            ? (extractItemHridFromElement(catalystElement) || findItemHridByDisplayName(catalystElement.getAttribute("aria-label") || ""))
            : "";

        const outputItems = parseAlchemyDropRows(panel, '[class*="SkillActionDetail_alchemyOutput"] [class*="SkillActionDetail_drop__"]', "output");
        const essenceDrops = parseAlchemyDropRows(panel, '[class*="SkillActionDetail_essenceDrops"] [class*="SkillActionDetail_drop__"]', "essence");
        const rareDrops = parseAlchemyDropRows(panel, '[class*="SkillActionDetail_rareDrops"] [class*="SkillActionDetail_drop__"]', "rare");

        return {
            panel,
            notesContainer: getAlchemyNotesContainer(panel),
            sourceItemHrid,
            sourceItemName: getLocalizedItemName(sourceItemHrid, sourceItemDetail?.name || sourceItemHrid),
            inputItems,
            catalystItemHrid,
            successChance,
            actionSeconds,
            rawActionSeconds: actionSeconds,
            efficiencyFraction,
            efficiencyMultiplier,
            outputs: outputItems.concat(essenceDrops, rareDrops),
        };
    }

    function getCurrentAlchemyTransmuteInference() {
        const selection = getAlchemyTransmutePanelSelection();
        if (!selection) {
            return null;
        }

        const efficiencyMultiplier = Math.max(1, Number(selection.efficiencyMultiplier || (1 + Number(selection.efficiencyFraction || 0)) || 1));
        let inputBaseSecondsTotal = 0;
        for (const input of selection.inputItems) {
            if (input.itemHrid === "/items/coin") {
                continue;
            }
            const seconds = calculateItemSeconds(input.itemHrid);
            if (!isAlchemyInferenceResolvableItemSeconds(input.itemHrid, seconds)) {
                return null;
            }
            inputBaseSecondsTotal += Number(input.count || 0) * Math.max(0, Number(seconds || 0));
        }
        const inputSecondsTotal = inputBaseSecondsTotal * efficiencyMultiplier;

        let catalystBaseSecondsTotal = 0;
        let catalystSecondsTotal = 0;
        if (selection.catalystItemHrid) {
            const catalystSeconds = calculateItemSeconds(selection.catalystItemHrid);
            if (!isAlchemyInferenceResolvableItemSeconds(selection.catalystItemHrid, catalystSeconds)) {
                return null;
            }
            catalystBaseSecondsTotal = Math.max(0, Number(catalystSeconds || 0));
            catalystSecondsTotal = catalystBaseSecondsTotal * selection.successChance * efficiencyMultiplier;
        }

        const teaBuffs = getTeaBuffs("/action_types/alchemy");
        const actionSeconds = Number(selection.actionSeconds || 0);
        const teaPerAction = actionSeconds / Math.max(teaBuffs.durationSeconds || 300, 1);
        let teaSecondsTotal = 0;
        for (const teaItemHrid of teaBuffs.activeTeas || []) {
            const teaSeconds = calculateItemSeconds(teaItemHrid);
            if (teaSeconds == null || !Number.isFinite(teaSeconds) || teaSeconds < 0) {
                continue;
            }
            teaSecondsTotal += teaPerAction * teaSeconds;
        }

        const consideredOutputs = selection.outputs.filter((output) => output.section === "output");
        if (!consideredOutputs.length) {
            return null;
        }

        let knownOutputBaseSeconds = 0;
        let knownOutputSeconds = 0;
        const knownOutputs = [];
        const unknownOutputs = [];
        for (const output of consideredOutputs) {
            const baseExpectedCount = output.expectedCount;
            const weightedExpectedCount = baseExpectedCount * selection.successChance * efficiencyMultiplier;
            if (!Number.isFinite(weightedExpectedCount) || weightedExpectedCount <= 0) {
                continue;
            }
            const seconds = calculateItemSeconds(output.itemHrid);
            if (!isAlchemyInferenceResolvableItemSeconds(output.itemHrid, seconds)) {
                unknownOutputs.push({
                    ...output,
                    baseExpectedCount,
                    weightedExpectedCount,
                });
                continue;
            }
            const outputSeconds = Math.max(0, Number(seconds || 0));
            knownOutputBaseSeconds += baseExpectedCount * outputSeconds;
            knownOutputSeconds += weightedExpectedCount * outputSeconds;
            knownOutputs.push({
                ...output,
                baseExpectedCount,
                weightedExpectedCount,
            });
        }
        if (unknownOutputs.length !== 1) {
            return null;
        }

        const unknownOutput = unknownOutputs[0];
        if (!Number.isFinite(unknownOutput.weightedExpectedCount) || unknownOutput.weightedExpectedCount <= 0) {
            return null;
        }

        const isAttachedRareTarget = ATTACHED_RARE_TARGET_ITEM_HRID_SET.has(unknownOutput.itemHrid);
        const directTargetExpectedCount = unknownOutput.weightedExpectedCount;
        let inputAttachedTargetExpectedCount = 0;
        let knownOutputAttachedTargetExpectedCount = 0;
        if (isAttachedRareTarget) {
            for (const input of selection.inputItems) {
                if (!input?.itemHrid || input.itemHrid === "/items/coin") {
                    continue;
                }
                const attachedRare = getAttachedRareYieldPerItem(input.itemHrid, unknownOutput.itemHrid);
                if (!Number.isFinite(attachedRare) || attachedRare <= 0) {
                    continue;
                }
                inputAttachedTargetExpectedCount += Number(input.count || 0) * efficiencyMultiplier * attachedRare;
            }
            for (const output of knownOutputs) {
                const attachedRare = getAttachedRareYieldPerItem(output.itemHrid, unknownOutput.itemHrid);
                if (!Number.isFinite(attachedRare) || attachedRare <= 0) {
                    continue;
                }
                knownOutputAttachedTargetExpectedCount += output.weightedExpectedCount * attachedRare;
            }
        }

        const effectiveTargetExpectedCount = directTargetExpectedCount + inputAttachedTargetExpectedCount - knownOutputAttachedTargetExpectedCount;
        if (!Number.isFinite(effectiveTargetExpectedCount) || effectiveTargetExpectedCount <= 0) {
            return null;
        }

        const inferredSeconds = (inputSecondsTotal + catalystSecondsTotal + actionSeconds + teaSecondsTotal - knownOutputSeconds) / effectiveTargetExpectedCount;
        if (!Number.isFinite(inferredSeconds) || inferredSeconds <= 0) {
            return null;
        }

        return {
            ...selection,
            targetItemHrid: unknownOutput.itemHrid,
            targetItemName: unknownOutput.itemName || getLocalizedItemName(unknownOutput.itemHrid, unknownOutput.itemHrid),
            targetConditionalCount: unknownOutput.baseExpectedCount,
            targetExpectedCount: effectiveTargetExpectedCount,
            directTargetExpectedCount,
            inputAttachedTargetExpectedCount,
            knownOutputAttachedTargetExpectedCount,
            effectiveTargetExpectedCount,
            isAttachedRareTarget,
            targetRow: unknownOutput.row,
            inferredSeconds,
            inputBaseSecondsTotal,
            inputSecondsTotal,
            catalystBaseSecondsTotal,
            catalystSecondsTotal,
            rawActionSeconds: Number(selection.rawActionSeconds || selection.actionSeconds || 0),
            actionSeconds,
            efficiencyFraction: Number(selection.efficiencyFraction || 0),
            efficiencyMultiplier,
            teaSecondsTotal,
            knownOutputBaseSeconds,
            knownOutputSeconds,
        };
    }

    function renderAlchemyTransmuteInference() {
        document.querySelectorAll(".ictime-alchemy-inference").forEach((node) => node.remove());
        document.querySelectorAll(".ictime-alchemy-inference-row").forEach((node) => node.remove());

        const inference = getCurrentAlchemyTransmuteInference();
        if (!inference) {
            return;
        }

        if (inference.targetRow instanceof HTMLElement) {
            const inline = document.createElement("span");
            inline.className = "ictime-alchemy-inference-row";
            inline.dataset.ictimeOwner = instanceId;
            inline.style.marginLeft = "6px";
            inline.style.color = "#7dd3fc";
            inline.style.fontSize = "0.85em";
            inline.textContent = isZh
                ? `ICTime推导 ${formatAutoDuration(inference.inferredSeconds)}`
                : `ICTime ${formatAutoDuration(inference.inferredSeconds)}`;
            inference.targetRow.appendChild(inline);
        }

        if (isTimeCalculatorCompactModeEnabled()) {
            return;
        }

        const host = inference.notesContainer || inference.panel;
        if (!(host instanceof HTMLElement)) {
            return;
        }
        const efficiencyMultiplier = Math.max(1, Number(inference.efficiencyMultiplier || (1 + Number(inference.efficiencyFraction || 0)) || 1));
        const efficiencyText = formatPreciseNumber(efficiencyMultiplier);
        const successRateTextCurrent = `${formatPreciseNumber(inference.successChance * 100)}%`;
        const actionTermTextCurrent = isZh
            ? `行动(${formatAutoDuration(inference.actionSeconds)})`
            : `action(${formatAutoDuration(inference.actionSeconds)})`;
        const inputTermTextCurrent = efficiencyMultiplier > 1
            ? (isZh
                ? `输入(${formatAutoDuration(inference.inputBaseSecondsTotal)} * ${efficiencyText} = ${formatAutoDuration(inference.inputSecondsTotal)})`
                : `input(${formatAutoDuration(inference.inputBaseSecondsTotal)} * ${efficiencyText} = ${formatAutoDuration(inference.inputSecondsTotal)})`)
            : (isZh
                ? `输入(${formatAutoDuration(inference.inputSecondsTotal)})`
                : `input(${formatAutoDuration(inference.inputSecondsTotal)})`);
        const catalystTermTextCurrent = Number(inference.catalystBaseSecondsTotal || 0) > 0
            ? (efficiencyMultiplier > 1 || Number(inference.successChance || 0) < 1
                ? (isZh
                    ? `催化剂(${formatAutoDuration(inference.catalystBaseSecondsTotal)} * ${successRateTextCurrent} * ${efficiencyText} = ${formatAutoDuration(inference.catalystSecondsTotal)})`
                    : `catalyst(${formatAutoDuration(inference.catalystBaseSecondsTotal)} * ${successRateTextCurrent} * ${efficiencyText} = ${formatAutoDuration(inference.catalystSecondsTotal)})`)
                : (isZh
                    ? `催化剂(${formatAutoDuration(inference.catalystSecondsTotal)})`
                    : `catalyst(${formatAutoDuration(inference.catalystSecondsTotal)})`))
            : (isZh ? "催化剂(0 s)" : "catalyst(0 s)");
        const knownOutputTermTextCurrent = (efficiencyMultiplier > 1 || Number(inference.successChance || 0) < 1) && Number(inference.knownOutputBaseSeconds || 0) > 0
            ? (isZh
                ? `其余产出(${formatAutoDuration(inference.knownOutputBaseSeconds)} * ${successRateTextCurrent} * ${efficiencyText} = ${formatAutoDuration(inference.knownOutputSeconds)})`
                : `other outputs(${formatAutoDuration(inference.knownOutputBaseSeconds)} * ${successRateTextCurrent} * ${efficiencyText} = ${formatAutoDuration(inference.knownOutputSeconds)})`)
            : (isZh
                ? `其余产出(${formatAutoDuration(inference.knownOutputSeconds)})`
                : `other outputs(${formatAutoDuration(inference.knownOutputSeconds)})`);
        const numeratorTextCurrent = isZh
            ? `${inputTermTextCurrent} + ${actionTermTextCurrent} + 茶(${formatAutoDuration(inference.teaSecondsTotal)}) + ${catalystTermTextCurrent} - ${knownOutputTermTextCurrent}`
            : `${inputTermTextCurrent} + ${actionTermTextCurrent} + tea(${formatAutoDuration(inference.teaSecondsTotal)}) + ${catalystTermTextCurrent} - ${knownOutputTermTextCurrent}`;
        const directDenominatorTextCurrent = efficiencyMultiplier > 1 || Number(inference.successChance || 0) < 1
            ? `${formatPreciseNumber(inference.targetConditionalCount)} * ${successRateTextCurrent} * ${efficiencyText} = ${formatPreciseNumber(inference.directTargetExpectedCount || inference.targetExpectedCount)}`
            : formatPreciseNumber(inference.directTargetExpectedCount || inference.targetExpectedCount);
        const denominatorTextCurrent = formatPreciseNumber(inference.effectiveTargetExpectedCount || inference.targetExpectedCount);
        const targetRateTextCurrent = `${formatPreciseNumber(inference.targetConditionalCount * 100)}%`;
        const expectedRateTextCurrent = `${formatPreciseNumber((inference.directTargetExpectedCount || inference.targetExpectedCount) * 100)}%`;
        const formulaBlockCurrent = document.createElement("div");
        formulaBlockCurrent.className = "ictime-alchemy-inference";
        formulaBlockCurrent.dataset.ictimeOwner = instanceId;
        formulaBlockCurrent.style.marginTop = "8px";
        formulaBlockCurrent.style.color = "#7dd3fc";
        formulaBlockCurrent.style.fontSize = "0.85rem";
        formulaBlockCurrent.style.lineHeight = "1.35";
        const formulaTitleCurrent = document.createElement("div");
        formulaTitleCurrent.textContent = isZh
            ? `ICTime推导公式: (${numeratorTextCurrent}) / ${denominatorTextCurrent} = ${formatAutoDuration(inference.inferredSeconds)}`
            : `ICTime formula: (${numeratorTextCurrent}) / ${denominatorTextCurrent} = ${formatAutoDuration(inference.inferredSeconds)}`;
        const formulaDetailCurrent = document.createElement("div");
        formulaDetailCurrent.style.opacity = "0.85";
        if (inference.isAttachedRareTarget) {
            const attachedLabel = getAttachedRareLabel(inference.targetItemHrid);
            formulaDetailCurrent.textContent = isZh
                ? `目标期望产出: 直接转化(${directDenominatorTextCurrent}) + 输入附带${attachedLabel}(${formatPreciseNumber(inference.inputAttachedTargetExpectedCount)}) - 其余产出附带${attachedLabel}(${formatPreciseNumber(inference.knownOutputAttachedTargetExpectedCount)}) = ${formatPreciseNumber(inference.effectiveTargetExpectedCount)}`
                : `expected target output: direct(${directDenominatorTextCurrent}) + input extra ${attachedLabel}(${formatPreciseNumber(inference.inputAttachedTargetExpectedCount)}) - other outputs extra ${attachedLabel}(${formatPreciseNumber(inference.knownOutputAttachedTargetExpectedCount)}) = ${formatPreciseNumber(inference.effectiveTargetExpectedCount)}`;
        } else {
            formulaDetailCurrent.textContent = isZh
                ? `目标期望产出: ${targetRateTextCurrent} * ${successRateTextCurrent}${efficiencyMultiplier > 1 ? ` * ${efficiencyText}` : ""} = ${expectedRateTextCurrent}`
                : `expected target output: ${targetRateTextCurrent} * ${successRateTextCurrent}${efficiencyMultiplier > 1 ? ` * ${efficiencyText}` : ""} = ${expectedRateTextCurrent}`;
        }
        formulaBlockCurrent.appendChild(formulaTitleCurrent);
        formulaBlockCurrent.appendChild(formulaDetailCurrent);
        host.appendChild(formulaBlockCurrent);
        return;
        const actionTermText = Number(inference.efficiencyFraction || 0) > 0
            ? (isZh
                ? `行动(${formatAutoDuration(inference.rawActionSeconds || inference.effectiveActionSeconds)} / ${formatPreciseNumber(1 + Number(inference.efficiencyFraction || 0))} = ${formatAutoDuration(inference.effectiveActionSeconds || inference.actionSeconds)})`
                : `action(${formatAutoDuration(inference.rawActionSeconds || inference.effectiveActionSeconds)} / ${formatPreciseNumber(1 + Number(inference.efficiencyFraction || 0))} = ${formatAutoDuration(inference.effectiveActionSeconds || inference.actionSeconds)})`)
            : (isZh
                ? `行动(${formatAutoDuration(inference.effectiveActionSeconds || inference.actionSeconds)})`
                : `action(${formatAutoDuration(inference.effectiveActionSeconds || inference.actionSeconds)})`);
        const numeratorText = isZh
            ? `输入(${formatAutoDuration(inference.inputSecondsTotal)}) + ${actionTermText} + 茶(${formatAutoDuration(inference.teaSecondsTotal)}) + 催化剂(${formatAutoDuration(inference.catalystSecondsTotal)}) - 其余产出(${formatAutoDuration(inference.knownOutputSeconds)})`
            : `input(${formatAutoDuration(inference.inputSecondsTotal)}) + ${actionTermText} + tea(${formatAutoDuration(inference.teaSecondsTotal)}) + catalyst(${formatAutoDuration(inference.catalystSecondsTotal)}) - other outputs(${formatAutoDuration(inference.knownOutputSeconds)})`;
        const denominatorText = formatPreciseNumber(inference.targetExpectedCount);
        const targetRateText = `${formatPreciseNumber(inference.targetConditionalCount * 100)}%`;
        const successRateText = `${formatPreciseNumber(inference.successChance * 100)}%`;
        const expectedRateText = `${formatPreciseNumber(inference.targetExpectedCount * 100)}%`;
        const formulaBlock = document.createElement("div");
        formulaBlock.className = "ictime-alchemy-inference";
        formulaBlock.dataset.ictimeOwner = instanceId;
        formulaBlock.style.marginTop = "8px";
        formulaBlock.style.color = "#7dd3fc";
        formulaBlock.style.fontSize = "0.85rem";
        formulaBlock.style.lineHeight = "1.35";
        const formulaTitle = document.createElement("div");
        formulaTitle.textContent = isZh
            ? `ICTime推导公式: (${numeratorText}) / ${denominatorText} = ${formatAutoDuration(inference.inferredSeconds)}`
            : `ICTime formula: (${numeratorText}) / ${denominatorText} = ${formatAutoDuration(inference.inferredSeconds)}`;
        const formulaDetail = document.createElement("div");
        formulaDetail.style.opacity = "0.85";
        formulaDetail.textContent = isZh
            ? `目标期望产出: ${targetRateText} × ${successRateText} = ${expectedRateText}`
            : `expected target output: ${targetRateText} * ${successRateText} = ${expectedRateText}`;
        formulaBlock.appendChild(formulaTitle);
        formulaBlock.appendChild(formulaDetail);
        host.appendChild(formulaBlock);
        return;
        const block = document.createElement("div");
        block.className = "ictime-alchemy-inference";
        block.dataset.ictimeOwner = instanceId;
        block.style.marginTop = "8px";
        block.style.color = "#7dd3fc";
        block.style.fontSize = "0.85rem";
        block.style.lineHeight = "1.35";
        const title = document.createElement("div");
        title.textContent = isZh
            ? `ICTime推导: ${inference.targetItemName} ≈ ${formatAutoDuration(inference.inferredSeconds)}`
            : `ICTime derive: ${inference.targetItemName} ≈ ${formatAutoDuration(inference.inferredSeconds)}`;
        const detail = document.createElement("div");
        detail.style.opacity = "0.85";
        detail.textContent = isZh
            ? `输入${formatAutoDuration(inference.inputSecondsTotal)} + 行动${formatAutoDuration(inference.actionSeconds)} + 茶${formatAutoDuration(inference.teaSecondsTotal)} - 其余产出${formatAutoDuration(inference.knownOutputSeconds)}`
            : `input ${formatAutoDuration(inference.inputSecondsTotal)} + action ${formatAutoDuration(inference.actionSeconds)} + tea ${formatAutoDuration(inference.teaSecondsTotal)} - other outputs ${formatAutoDuration(inference.knownOutputSeconds)}`;
        block.appendChild(title);
        block.appendChild(detail);
        host.appendChild(block);
    }

    function queueAlchemyInferenceRefresh() {
        if (state.alchemyInferenceRefreshQueued || state.isShutDown) {
            return;
        }
        state.alchemyInferenceRefreshQueued = true;
        requestAnimationFrame(() => {
            state.alchemyInferenceRefreshQueued = false;
            ensureAlchemyInferenceObserver();
            renderAlchemyTransmuteInference();
        });
    }

    function scheduleAlchemyInferenceRefreshBurst() {
        for (const timerId of state.alchemyInferenceDelayTimers) {
            clearTimeout(timerId);
        }
        state.alchemyInferenceDelayTimers = [120, 400, 900].map((delayMs) => setTimeout(() => {
            queueAlchemyInferenceRefresh();
        }, delayMs));
    }

    function getCurrentCharacterId() {
        const fromUrl = Number(new URLSearchParams(location.search).get("characterId") || 0);
        return Number.isFinite(fromUrl) && fromUrl > 0 ? fromUrl : 0;
    }

    function getTimeCalculatorStorageKey(characterId = getCurrentCharacterId()) {
        return `ICTime_TimeCalculator_${characterId || "default"}`;
    }

    function normalizeDungeonTier(value) {
        const numeric = Math.floor(Number(value || 0));
        if (numeric >= 2) {
            return 2;
        }
        if (numeric >= 1) {
            return 1;
        }
        return 0;
    }

    function normalizeDungeonPartyCount(value) {
        const numeric = Math.floor(Number(value || 5));
        if (numeric >= 5) {
            return 5;
        }
        if (numeric >= 1) {
            return numeric;
        }
        return 5;
    }

    function getRefinementExpectedCountByTier(tier) {
        return Number(REFINEMENT_TIER_EXPECTED_COUNTS[normalizeDungeonTier(tier)] || 0);
    }

    function getBaseDungeonChestHrid(itemHrid) {
        if (DUNGEON_CHEST_CONFIG[itemHrid]) {
            return itemHrid;
        }
        return REFINEMENT_CHEST_TO_BASE_CHEST_HRID[itemHrid] || REFINEMENT_SHARD_TO_BASE_CHEST_HRID[itemHrid] || "";
    }

    function getDungeonChestConfigByAnyItem(itemHrid) {
        const baseChestItemHrid = getBaseDungeonChestHrid(itemHrid);
        return baseChestItemHrid ? DUNGEON_CHEST_CONFIG[baseChestItemHrid] || null : null;
    }

    function getRefinementChestOpenKeyHrid(itemHrid) {
        const config = getDungeonChestConfigByAnyItem(itemHrid);
        if (!config?.refinementChestItemHrid) {
            return "";
        }
        const runtimeOpenKeyHrid = state.itemDetailMap?.[config.refinementChestItemHrid]?.openKeyItemHrid;
        return runtimeOpenKeyHrid || config.keyItemHrid || "";
    }

    function getCombatChestQuantityMultiplier() {
        return Math.max(0.0001, 1 + getCombatChestQuantityFraction());
    }

    function getDungeonPartyChestQuantityMultiplier(partyCount) {
        return Math.max(0.0001, 5 / normalizeDungeonPartyCount(partyCount));
    }

    function isTimeCalculatorSupportedItem(itemHrid) {
        return TIME_CALCULATOR_ITEM_HRIDS.includes(itemHrid);
    }

    function getTimeCalculatorEntryType(itemHrid) {
        if (DUNGEON_CHEST_ITEM_HRIDS.includes(itemHrid)) {
            return "chest";
        }
        if (REFINEMENT_CHEST_ITEM_HRIDS.includes(itemHrid)) {
            return "refinement_chest";
        }
        if (KEY_FRAGMENT_ITEM_HRIDS.includes(itemHrid)) {
            return "fragment";
        }
        return "";
    }

    function getTimeCalculatorItemDisplayName(itemHrid) {
        if (!itemHrid) {
            return "";
        }
        if (isZh && TIME_CALCULATOR_ITEM_NAME_OVERRIDES_ZH[itemHrid]) {
            return TIME_CALCULATOR_ITEM_NAME_OVERRIDES_ZH[itemHrid];
        }
        return getLocalizedItemName(itemHrid, state.itemDetailMap?.[itemHrid]?.name || itemHrid);
    }

    function getConfiguredEssenceDecomposeSourceItemHrid(essenceHrid) {
        loadTimeCalculatorData();
        const essenceRule = ESSENCE_DECOMPOSE_RULES[essenceHrid];
        const fixedRuleSourceItemHrid = essenceRule?.type === "fixed_source"
            ? (essenceRule.sourceItemHrid || "")
            : "";
        const defaultSourceItemHrid = TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS[essenceHrid] || fixedRuleSourceItemHrid || "";
        const configuredSourceItemHrid = state.timeCalculatorEssenceSourceItemHrids?.[essenceHrid] || defaultSourceItemHrid;
        if (!state.itemDetailMap) {
            return configuredSourceItemHrid || defaultSourceItemHrid;
        }
        if (isValidTimeCalculatorEssenceSourceItemHrid(essenceHrid, configuredSourceItemHrid)) {
            return configuredSourceItemHrid;
        }
        if (isValidTimeCalculatorEssenceSourceItemHrid(essenceHrid, defaultSourceItemHrid)) {
            return defaultSourceItemHrid;
        }
        const firstOption = getTimeCalculatorEssenceSourceOptions(essenceHrid)[0];
        return firstOption?.itemHrid || configuredSourceItemHrid || defaultSourceItemHrid;
    }

    function isValidTimeCalculatorEssenceSourceItemHrid(essenceHrid, sourceItemHrid) {
        if (!essenceHrid || !sourceItemHrid) {
            return false;
        }
        const itemDetail = state.itemDetailMap?.[sourceItemHrid];
        const decomposeItems = itemDetail?.alchemyDetail?.decomposeItems || [];
        if (!decomposeItems.some((entry) => entry?.itemHrid === essenceHrid)) {
            return false;
        }
        if (essenceHrid === "/items/brewing_essence") {
            return sourceItemHrid.endsWith("_tea_leaf");
        }
        if (essenceHrid === "/items/tailoring_essence") {
            return sourceItemHrid.endsWith("_hide");
        }
        return true;
    }

    function getTimeCalculatorEssenceSourceOptions(essenceHrid) {
        const options = [];
        const seen = new Set();
        for (const [itemHrid, itemDetail] of Object.entries(state.itemDetailMap || {})) {
            if (!isValidTimeCalculatorEssenceSourceItemHrid(essenceHrid, itemHrid)) {
                continue;
            }
            if (seen.has(itemHrid)) {
                continue;
            }
            seen.add(itemHrid);
            options.push({
                itemHrid,
                itemName: getLocalizedItemName(itemHrid, itemDetail?.name || itemHrid),
            });
        }
        options.sort((left, right) => left.itemName.localeCompare(right.itemName, isZh ? "zh-CN" : "en"));
        return options;
    }

    function setTimeCalculatorEssenceSourceItemHrid(essenceHrid, sourceItemHrid) {
        loadTimeCalculatorData();
        const nextSourceItemHrid = getConfiguredEssenceDecomposeSourceItemHrid(essenceHrid) === sourceItemHrid
            ? sourceItemHrid
            : (isValidTimeCalculatorEssenceSourceItemHrid(essenceHrid, sourceItemHrid)
                ? sourceItemHrid
                : getConfiguredEssenceDecomposeSourceItemHrid(essenceHrid));
        const currentSourceItemHrid = getConfiguredEssenceDecomposeSourceItemHrid(essenceHrid);
        if (currentSourceItemHrid === nextSourceItemHrid) {
            return;
        }
        state.timeCalculatorEssenceSourceItemHrids = {
            ...TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS,
            ...(state.timeCalculatorEssenceSourceItemHrids || {}),
            [essenceHrid]: nextSourceItemHrid,
        };
        saveTimeCalculatorData();
        rerenderTimeCalculatorPanel();
        refreshOpenTooltips();
        renderAlchemyTransmuteInference();
        renderEnhancingRecommendation();
    }

    function loadTimeCalculatorData() {
        const characterId = getCurrentCharacterId();
        if (state.timeCalculatorLoadedCharacterId === characterId) {
            return;
        }
        state.timeCalculatorLoadedCharacterId = characterId;
        state.timeCalculatorEntries = [];
        state.timeCalculatorCompactMode = false;
        state.timeCalculatorEssenceSourceItemHrids = { ...TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS };
        const raw = localStorage.getItem(getTimeCalculatorStorageKey(characterId));
        if (!raw) {
            return;
        }
        try {
            const parsed = JSON.parse(raw);
            state.timeCalculatorCompactMode = Boolean(parsed?.compactMode);
            const parsedEssenceSources = parsed?.essenceSources && typeof parsed.essenceSources === "object"
                ? parsed.essenceSources
                : {};
            state.timeCalculatorEssenceSourceItemHrids = {
                ...TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS,
                ...parsedEssenceSources,
            };
            const entries = Array.isArray(parsed?.entries) ? parsed.entries : [];
            state.timeCalculatorEntries = entries
                .filter((entry) => entry && isTimeCalculatorSupportedItem(entry.itemHrid || entry.chestItemHrid))
                .map((entry, index) => ({
                    id: String(entry.id || `chest-${Date.now()}-${index}`),
                    itemHrid: entry.itemHrid || entry.chestItemHrid,
                    collapsed: Boolean(entry.collapsed),
                    dungeonTier: normalizeDungeonTier(entry.dungeonTier),
                    partyCount: normalizeDungeonPartyCount(entry.partyCount),
                    runMinutes: parseNonNegativeDecimal(entry.runMinutes),
                    quantityPer24h: parseNonNegativeDecimal(entry.quantityPer24h),
                    foods: Array.isArray(entry.foods) ? entry.foods.map((item, itemIndex) => ({
                        id: String(item.id || `food-${index}-${itemIndex}`),
                        itemHrid: item.itemHrid,
                        perHour: parseNonNegativeDecimal(item.perHour),
                    })).filter((item) => item.itemHrid) : [],
                    drinks: Array.isArray(entry.drinks) ? entry.drinks.map((item, itemIndex) => ({
                        id: String(item.id || `drink-${index}-${itemIndex}`),
                        itemHrid: item.itemHrid,
                        perHour: parseNonNegativeDecimal(item.perHour),
                    })).filter((item) => item.itemHrid) : [],
                }));
        } catch (error) {
            console.error("[ICTime] Failed to load time calculator data.", error);
        }
    }

    function saveTimeCalculatorData(shouldClearDerivedCaches = true) {
        const characterId = getCurrentCharacterId();
        state.timeCalculatorLoadedCharacterId = characterId;
        localStorage.setItem(getTimeCalculatorStorageKey(characterId), JSON.stringify({
            compactMode: Boolean(state.timeCalculatorCompactMode),
            essenceSources: {
                ...TIME_CALCULATOR_DEFAULT_ESSENCE_SOURCE_ITEM_HRIDS,
                ...(state.timeCalculatorEssenceSourceItemHrids || {}),
            },
            entries: state.timeCalculatorEntries.map((entry) => ({
                id: entry.id,
                itemHrid: entry.itemHrid,
                collapsed: Boolean(entry.collapsed),
                dungeonTier: normalizeDungeonTier(entry.dungeonTier),
                partyCount: normalizeDungeonPartyCount(entry.partyCount),
                runMinutes: parseNonNegativeDecimal(entry.runMinutes),
                quantityPer24h: parseNonNegativeDecimal(entry.quantityPer24h),
                foods: (entry.foods || []).map((item) => ({
                    id: item.id,
                    itemHrid: item.itemHrid,
                    perHour: parseNonNegativeDecimal(item.perHour),
                })),
                drinks: (entry.drinks || []).map((item) => ({
                    id: item.id,
                    itemHrid: item.itemHrid,
                    perHour: parseNonNegativeDecimal(item.perHour),
                })),
            })),
        }));
        if (shouldClearDerivedCaches) {
            clearCaches();
        }
    }

    function isTimeCalculatorCompactModeEnabled() {
        loadTimeCalculatorData();
        return Boolean(state.timeCalculatorCompactMode);
    }

    function isTimeCalculatorSettingsOpen() {
        return Boolean(state.timeCalculatorSettingsOpen);
    }

    function setTimeCalculatorSettingsOpen(open) {
        const nextValue = Boolean(open);
        if (state.timeCalculatorSettingsOpen === nextValue) {
            return;
        }
        state.timeCalculatorSettingsOpen = nextValue;
        rerenderTimeCalculatorPanel();
    }

    function setTimeCalculatorCompactMode(enabled) {
        loadTimeCalculatorData();
        const nextValue = Boolean(enabled);
        if (state.timeCalculatorCompactMode === nextValue) {
            return;
        }
        state.timeCalculatorCompactMode = nextValue;
        saveTimeCalculatorData();
        rerenderTimeCalculatorPanel();
        refreshOpenTooltips();
        renderAlchemyTransmuteInference();
        renderEnhancingRecommendation();
    }

    function rerenderTimeCalculatorPanel() {
        if (state.timeCalculatorContainer?.isConnected) {
            renderTimeCalculatorPanel();
            return;
        }
        queueTimeCalculatorRefresh();
    }

    function shouldDeferTimeCalculatorRefresh() {
        if (state.timeCalculatorSettingsOpen) {
            return true;
        }
        const container = state.timeCalculatorContainer;
        const activeElement = document.activeElement;
        if (!container?.isConnected || !(activeElement instanceof HTMLElement) || !container.contains(activeElement)) {
            return false;
        }
        return activeElement.isContentEditable || ["INPUT", "SELECT", "TEXTAREA"].includes(activeElement.tagName);
    }

    function flushPendingTimeCalculatorRefresh() {
        if (!state.timeCalculatorRefreshPending || state.timeCalculatorRefreshQueued || state.isShutDown) {
            return;
        }
        if (shouldDeferTimeCalculatorRefresh()) {
            return;
        }
        state.timeCalculatorRefreshPending = false;
        queueTimeCalculatorRefresh();
    }

    function moveTimeCalculatorEntry(entryId, offset) {
        const index = state.timeCalculatorEntries.findIndex((entry) => entry.id === entryId);
        if (index < 0) {
            return;
        }
        const nextIndex = Math.max(0, Math.min(state.timeCalculatorEntries.length - 1, index + offset));
        if (nextIndex === index) {
            return;
        }
        const [entry] = state.timeCalculatorEntries.splice(index, 1);
        state.timeCalculatorEntries.splice(nextIndex, 0, entry);
        saveTimeCalculatorData(false);
        rerenderTimeCalculatorPanel();
    }

    function syncTimeCalculatorEntriesFromCardOrder(container) {
        if (!(container instanceof HTMLElement)) {
            return;
        }
        const orderedIds = Array.from(container.querySelectorAll(".ictime-timecalc-entry-card"))
            .map((node) => node.dataset.entryId || "")
            .filter(Boolean);
        if (!orderedIds.length) {
            return;
        }
        const entryMap = new Map(state.timeCalculatorEntries.map((entry) => [entry.id, entry]));
        const reorderedEntries = [];
        const seen = new Set();
        for (const entryId of orderedIds) {
            const entry = entryMap.get(entryId);
            if (!entry || seen.has(entryId)) {
                continue;
            }
            reorderedEntries.push(entry);
            seen.add(entryId);
        }
        for (const entry of state.timeCalculatorEntries) {
            if (entry?.id && !seen.has(entry.id)) {
                reorderedEntries.push(entry);
            }
        }
        state.timeCalculatorEntries = reorderedEntries;
        saveTimeCalculatorData(false);
    }

    function animateTimeCalculatorCardReorder(container, draggingCard, targetCard = null) {
        if (!(container instanceof HTMLElement) || !(draggingCard instanceof HTMLElement)) {
            return;
        }
        const cards = Array.from(container.querySelectorAll(".ictime-timecalc-entry-card"));
        const firstRects = new Map();
        cards.forEach((card) => {
            firstRects.set(card, card.getBoundingClientRect());
        });
        if (targetCard instanceof HTMLElement && targetCard !== draggingCard) {
            container.insertBefore(draggingCard, targetCard);
        } else if (container.lastElementChild !== draggingCard) {
            container.appendChild(draggingCard);
        }
        cards.forEach((card) => {
            const first = firstRects.get(card);
            const last = card.getBoundingClientRect();
            if (!first) {
                return;
            }
            const dx = first.left - last.left;
            const dy = first.top - last.top;
            if (!dx && !dy) {
                return;
            }
            card.style.transform = `translate(${dx}px, ${dy}px)`;
            card.style.transition = "transform 0s";
            card.style.willChange = "transform";
            requestAnimationFrame(() => {
                card.style.transform = "";
                card.style.transition = "transform 150ms cubic-bezier(.2,.8,.2,1)";
            });
        });
    }

    function enableTimeCalculatorPointerSort(container) {
        if (!(container instanceof HTMLElement) || container.dataset.ictimePointerSort === "true") {
            return;
        }
        container.dataset.ictimePointerSort = "true";

        let draggingCard = null;
        let captureHandle = null;
        let pointerY = 0;
        let rafPending = false;

        const processMove = () => {
            rafPending = false;
            if (!(draggingCard instanceof HTMLElement)) {
                return;
            }
            const cards = Array.from(container.querySelectorAll(".ictime-timecalc-entry-card"));
            const draggingIndex = cards.indexOf(draggingCard);
            if (draggingIndex < 0) {
                return;
            }
            for (const card of cards) {
                if (card === draggingCard) {
                    continue;
                }
                const box = card.getBoundingClientRect();
                const middle = box.top + (box.height / 2);
                if (pointerY < middle) {
                    if (cards[draggingIndex] !== card) {
                        animateTimeCalculatorCardReorder(container, draggingCard, card);
                    }
                    return;
                }
            }
            animateTimeCalculatorCardReorder(container, draggingCard, null);
        };

        const onMove = (event) => {
            if (!(draggingCard instanceof HTMLElement)) {
                return;
            }
            pointerY = event.clientY;
            if (!rafPending) {
                rafPending = true;
                requestAnimationFrame(processMove);
            }
        };

        const finishDrag = (pointerId = null) => {
            if (!(draggingCard instanceof HTMLElement)) {
                return;
            }
            draggingCard.style.opacity = "";
            draggingCard.style.zIndex = "";
            if (captureHandle instanceof HTMLElement) {
                captureHandle.style.cursor = "grab";
                if (pointerId != null && typeof captureHandle.releasePointerCapture === "function") {
                    try {
                        captureHandle.releasePointerCapture(pointerId);
                    } catch (_error) {
                        // Ignore stale pointer capture cleanup.
                    }
                }
            }
            document.body.style.userSelect = "";
            syncTimeCalculatorEntriesFromCardOrder(container);
            draggingCard = null;
            captureHandle = null;
            document.removeEventListener("pointermove", onMove);
            document.removeEventListener("pointerup", onUp);
            document.removeEventListener("pointercancel", onCancel);
        };

        const onUp = (event) => {
            finishDrag(event.pointerId);
        };

        const onCancel = () => {
            finishDrag(null);
        };

        container.addEventListener("pointerdown", (event) => {
            const handle = event.target instanceof Element
                ? event.target.closest(".ictime-timecalc-drag-handle")
                : null;
            if (!(handle instanceof HTMLElement)) {
                return;
            }
            const card = handle.closest(".ictime-timecalc-entry-card");
            if (!(card instanceof HTMLElement)) {
                return;
            }
            event.preventDefault();
            draggingCard = card;
            captureHandle = handle;
            pointerY = event.clientY;
            draggingCard.style.opacity = "0.55";
            draggingCard.style.zIndex = "1";
            document.body.style.userSelect = "none";
            handle.style.cursor = "grabbing";
            if (typeof handle.setPointerCapture === "function") {
                try {
                    handle.setPointerCapture(event.pointerId);
                } catch (_error) {
                    // Ignore pointer capture failures on detached nodes.
                }
            }
            document.addEventListener("pointermove", onMove);
            document.addEventListener("pointerup", onUp);
            document.addEventListener("pointercancel", onCancel);
        });
    }

    function getTimeCalculatorItemOptions() {
        return TIME_CALCULATOR_ITEM_HRIDS
            .filter((itemHrid) => state.itemDetailMap?.[itemHrid])
            .map((itemHrid) => ({
                itemHrid,
                itemName: getTimeCalculatorItemDisplayName(itemHrid),
            }));
    }

    function getTimeCalculatorConsumableOptions(kind = "") {
        const results = [];
        for (const item of Object.values(state.itemDetailMap || {})) {
            if (!item?.hrid || !item.consumableDetail) {
                continue;
            }
            const consumable = item.consumableDetail;
            const isFood = Number(consumable.hitpointRestore || 0) > 0 || Number(consumable.manapointRestore || 0) > 0;
            const isDrink = Array.isArray(consumable.buffs) && consumable.buffs.length > 0;
            if ((kind === "food" && !isFood) || (kind === "drink" && !isDrink) || (!isFood && !isDrink)) {
                continue;
            }
            results.push({
                itemHrid: item.hrid,
                itemName: getLocalizedItemName(item.hrid, item.name || item.hrid),
                kind: isFood ? "food" : "drink",
            });
        }
        results.sort((left, right) => left.itemName.localeCompare(right.itemName, isZh ? "zh-CN" : "en"));
        return results;
    }

    function getCombatChestQuantityFraction() {
        const combatBuffs = getActionTypeBuffs("communityActionTypeBuffsDict", "/action_types/combat");
        return Math.max(0, sumBuffsByType(combatBuffs, "/buff_types/combat_drop_quantity"));
    }

    function parseSimulatorDungeonTierValue(...sources) {
        for (const source of sources) {
            if (Number.isFinite(Number(source))) {
                return normalizeDungeonTier(source);
            }
        }
        for (const source of sources) {
            const text = String(source || "");
            const match = text.match(/T\s*([012])/i);
            if (match) {
                return normalizeDungeonTier(match[1]);
            }
        }
        return 0;
    }

    function getCurrentCharacterName() {
        if (state.currentCharacterName) {
            return state.currentCharacterName;
        }
        const appState = getGameState?.() || null;
        const candidates = [
            appState?.character?.name,
            appState?.characterDTO?.name,
            appState?.selectedCharacter?.name,
            appState?.characterName,
            appState?.characterSetting?.name,
            appState?.characterSetting?.characterName,
        ].filter((value) => typeof value === "string" && value.trim());
        if (candidates.length > 0) {
            return candidates[0].trim();
        }
        const activeCharacterLabel = Array.from(document.querySelectorAll("button, div, span"))
            .map((node) => (node.textContent || "").trim())
            .find((text) => text && text.length <= 24 && /活跃角色|当前角色|切换角色/.test(text) === false && /Lv\\.|等级|推荐|时间计算/.test(text) === false);
        return activeCharacterLabel || "";
    }

    function mapDungeonNameToChestHrid(dungeonName) {
        const text = String(dungeonName || "");
        if (text.includes("秘法要塞")) {
            return "/items/enchanted_chest";
        }
        if (text.includes("奇幻洞穴")) {
            return "/items/chimerical_chest";
        }
        if (text.includes("阴森马戏团")) {
            return "/items/sinister_chest";
        }
        if (text.includes("海盗基地")) {
            return "/items/pirate_chest";
        }
        return "";
    }

    function findItemHridByDisplayName(itemName) {
        const target = String(itemName || "").trim();
        if (!target) {
            return "";
        }
        if (SIMULATOR_ITEM_NAME_ALIASES[target]) {
            return SIMULATOR_ITEM_NAME_ALIASES[target];
        }
        for (const [hrid, localized] of state.localizedItemNameMap.entries()) {
            if (localized === target) {
                return hrid;
            }
        }
        for (const [hrid, localized] of Object.entries(MWITOOLS_ZH_ITEM_NAME_OVERRIDES)) {
            if (localized === target) {
                return hrid;
            }
        }
        for (const [hrid, item] of Object.entries(state.itemDetailMap || {})) {
            if ((item?.name || "").trim() === target) {
                return hrid;
            }
            if (getLocalizedItemName(hrid, item?.name || hrid) === target) {
                return hrid;
            }
        }
        const normalized = target
            .replace(/钥匙碎片/g, "")
            .replace(/颜色/g, "")
            .replace(/黑暗/g, "暗")
            .replace(/石头/g, "石")
            .replace(/蓝色/g, "蓝")
            .replace(/绿色/g, "绿")
            .replace(/紫色/g, "紫")
            .replace(/白色/g, "白")
            .replace(/橙色/g, "橙")
            .replace(/棕色/g, "棕")
            .replace(/\s+/g, "");
        const fragmentFallbacks = {
            "蓝": "/items/blue_key_fragment",
            "绿": "/items/green_key_fragment",
            "紫": "/items/purple_key_fragment",
            "白": "/items/white_key_fragment",
            "橙": "/items/orange_key_fragment",
            "棕": "/items/brown_key_fragment",
            "石": "/items/stone_key_fragment",
            "暗": "/items/dark_key_fragment",
            "燃烧": "/items/burning_key_fragment",
        };
        if (fragmentFallbacks[normalized]) {
            return fragmentFallbacks[normalized];
        }
        return "";
    }

    function buildSimulatorConsumables(consumables) {
        const foods = [];
        const drinks = [];
        for (const consumable of consumables || []) {
            const itemHrid = findItemHridByDisplayName(consumable.name);
            if (!itemHrid) {
                continue;
            }
            const option = getTimeCalculatorConsumableOptions().find((candidate) => candidate.itemHrid === itemHrid);
            if (!option) {
                continue;
            }
            const target = option.kind === "food" ? foods : drinks;
            target.push({
                id: `${option.kind}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                itemHrid,
                perHour: parseNonNegativeDecimal(consumable.perHour),
            });
        }
        return { foods, drinks };
    }

    function normalizeCharacterName(value) {
        return String(value || "")
            .trim()
            .replace(/[\s\u3000]+/g, "")
            .replace(/[()()\[\]【】\-_.]/g, "")
            .toLowerCase();
    }

    function upsertTimeCalculatorEntry(payload) {
        const existing = state.timeCalculatorEntries.find((entry) => entry.itemHrid === payload.itemHrid);
        if (existing) {
            existing.collapsed = typeof payload.collapsed === "boolean" ? payload.collapsed : Boolean(existing.collapsed);
            existing.dungeonTier = payload.itemHrid && REFINEMENT_CHEST_ITEM_HRIDS.includes(payload.itemHrid)
                ? normalizeDungeonTier(payload.dungeonTier || existing.dungeonTier || 1)
                : 0;
            existing.partyCount = payload.itemHrid && (DUNGEON_CHEST_ITEM_HRIDS.includes(payload.itemHrid) || REFINEMENT_CHEST_ITEM_HRIDS.includes(payload.itemHrid))
                ? normalizeDungeonPartyCount(payload.partyCount || existing.partyCount || 5)
                : 5;
            existing.runMinutes = parseNonNegativeDecimal(payload.runMinutes);
            existing.quantityPer24h = parseNonNegativeDecimal(payload.quantityPer24h);
            existing.foods = Array.isArray(payload.foods) ? payload.foods : [];
            existing.drinks = Array.isArray(payload.drinks) ? payload.drinks : [];
            return existing;
        }
        state.timeCalculatorEntries.push({
            id: payload.id || `entry-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
            itemHrid: payload.itemHrid,
            collapsed: typeof payload.collapsed === "boolean" ? payload.collapsed : false,
            dungeonTier: payload.itemHrid && REFINEMENT_CHEST_ITEM_HRIDS.includes(payload.itemHrid)
                ? normalizeDungeonTier(payload.dungeonTier || 1)
                : 0,
            partyCount: payload.itemHrid && (DUNGEON_CHEST_ITEM_HRIDS.includes(payload.itemHrid) || REFINEMENT_CHEST_ITEM_HRIDS.includes(payload.itemHrid))
                ? normalizeDungeonPartyCount(payload.partyCount || 5)
                : 5,
            runMinutes: parseNonNegativeDecimal(payload.runMinutes),
            quantityPer24h: parseNonNegativeDecimal(payload.quantityPer24h),
            foods: Array.isArray(payload.foods) ? payload.foods : [],
            drinks: Array.isArray(payload.drinks) ? payload.drinks : [],
        });
        return state.timeCalculatorEntries[state.timeCalculatorEntries.length - 1];
    }

    function getConfiguredTimeCalculatorEntry(itemHrid) {
        loadTimeCalculatorData();
        return (state.timeCalculatorEntries || []).find((entry) => entry?.itemHrid === itemHrid) || null;
    }

    function getDungeonEntryKeyHridByChest(itemHrid) {
        return getDungeonChestConfigByAnyItem(itemHrid)?.entryKeyItemHrid || "";
    }

    async function importFromSimulatorSnapshot() {
        ensureRuntimeStateFresh(true, { refreshTooltips: false });
        const previousSnapshot = await sharedGetValue(SIMULATOR_IMPORT_STORAGE_KEY, null);
        const requestAt = Date.now();
        await sharedSetValue(SIMULATOR_IMPORT_REQUEST_KEY, { requestedAt: requestAt });
        let snapshot = null;
        const startedAt = Date.now();
        while (Date.now() - startedAt < 12000) {
            const candidate = await sharedGetValue(SIMULATOR_IMPORT_STORAGE_KEY, null);
            if (candidate?.characters?.length) {
                snapshot = candidate;
            }
            if (candidate?.capturedAt && candidate.capturedAt >= requestAt - 500) {
                snapshot = candidate;
                break;
            }
            await new Promise((resolve) => setTimeout(resolve, 250));
        }
        if ((!snapshot?.characters?.length) && previousSnapshot?.characters?.length) {
            snapshot = previousSnapshot;
        }
        state.lastSimulatorImportSnapshot = snapshot;
        if (!snapshot?.characters?.length) {
            return false;
        }
        const mappedChestItemHrid = mapDungeonNameToChestHrid(snapshot.dungeonName);
        const currentCharacterName = getCurrentCharacterName();
        const normalizedCurrentCharacterName = normalizeCharacterName(currentCharacterName);
        const matched = snapshot.characters.find((entry) => normalizeCharacterName(entry.name) === normalizedCurrentCharacterName);
        if (!matched) {
            state.lastSimulatorImportResult = {
                failed: true,
                reason: "character_mismatch",
                currentCharacterName,
                normalizedCurrentCharacterName,
                snapshotSelectedCharacterName: snapshot.selectedCharacterName || "",
                snapshotCharacterNames: snapshot.characters.map((entry) => entry.name || ""),
            };
            return false;
        }
        const { foods, drinks } = buildSimulatorConsumables(matched.consumables);
        let importedCount = 0;
        const chestItemHrid = mappedChestItemHrid || "";
        const chestConfig = chestItemHrid ? DUNGEON_CHEST_CONFIG[chestItemHrid] : null;
        const dungeonTier = parseSimulatorDungeonTierValue(snapshot?.dungeonTier, snapshot?.dungeonName);
        if (chestItemHrid && parseNonNegativeDecimal(matched.averageMinutes) > 0) {
            if (dungeonTier <= 0) {
                upsertTimeCalculatorEntry({
                    id: `chest-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                    itemHrid: chestItemHrid,
                    runMinutes: parseNonNegativeDecimal(matched.averageMinutes),
                    quantityPer24h: 0,
                    foods,
                    drinks,
                });
                importedCount += 1;
            } else if (chestConfig?.refinementChestItemHrid) {
                upsertTimeCalculatorEntry({
                    id: `refinement-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                    itemHrid: chestConfig.refinementChestItemHrid,
                    dungeonTier,
                    runMinutes: parseNonNegativeDecimal(matched.averageMinutes),
                    quantityPer24h: 0,
                    foods,
                    drinks,
                });
                importedCount += 1;
            }
        }

        const durationHours = Math.max(0.0001, parseNonNegativeDecimal(matched.durationHours || 24));
        state.lastSimulatorImportResult = {
            dungeonName: snapshot.dungeonName || "",
            dungeonTier,
            chestItemHrid,
            matchedCharacterName: matched.name || "",
            durationHours,
            drops: (matched.nonRandomDrops || []).map((drop) => ({
                name: drop.name,
                itemHrid: findItemHridByDisplayName(drop.name),
                count: parseNonNegativeDecimal(drop.count),
            })),
        };
        for (const drop of matched.nonRandomDrops || []) {
            const fragmentHrid = findItemHridByDisplayName(drop.name);
            if (!KEY_FRAGMENT_ITEM_HRIDS.includes(fragmentHrid)) {
                continue;
            }
            upsertTimeCalculatorEntry({
                id: `fragment-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                itemHrid: fragmentHrid,
                runMinutes: 0,
                quantityPer24h: parseNonNegativeDecimal(drop.count) * (24 / durationHours),
                foods: foods.map((item) => ({ ...item, id: `food-${Date.now()}-${Math.random().toString(36).slice(2, 6)}` })),
                drinks: drinks.map((item) => ({ ...item, id: `drink-${Date.now()}-${Math.random().toString(36).slice(2, 6)}` })),
            });
            importedCount += 1;
        }
        if (!importedCount) {
            return false;
        }
        saveTimeCalculatorData();
        rerenderTimeCalculatorPanel();
        return true;
    }

    function getTimeCalculatorEntrySummary(entry) {
        const itemType = getTimeCalculatorEntryType(entry?.itemHrid);
        const runMinutes = parseNonNegativeDecimal(entry?.runMinutes);
        const runSeconds = runMinutes * 60;
        const quantityPer24h = parseNonNegativeDecimal(entry?.quantityPer24h);
        const partyCount = itemType === "chest" || itemType === "refinement_chest"
            ? normalizeDungeonPartyCount(entry?.partyCount || 5)
            : 5;
        const dungeonEntryKeyHrid = itemType === "chest" || itemType === "refinement_chest"
            ? getDungeonEntryKeyHridByChest(entry?.itemHrid)
            : "";
        const rawDungeonEntryKeySeconds = dungeonEntryKeyHrid ? calculateItemSeconds(dungeonEntryKeyHrid) : 0;
        const dungeonEntryKeyFailureReason =
            dungeonEntryKeyHrid && (!Number.isFinite(rawDungeonEntryKeySeconds) || rawDungeonEntryKeySeconds <= 0)
                ? getDependencyFailureReason(dungeonEntryKeyHrid)
                : "";
        const dungeonEntryKeySeconds = Number.isFinite(rawDungeonEntryKeySeconds) && rawDungeonEntryKeySeconds > 0
            ? rawDungeonEntryKeySeconds
            : 0;
        const foodSeconds = (entry?.foods || []).reduce((total, item) => {
            const itemSeconds = calculateItemSeconds(item.itemHrid);
            if (!Number.isFinite(itemSeconds) || itemSeconds <= 0) {
                return total;
            }
            const hours = itemType === "fragment" ? 24 : (runMinutes / 60);
            return total + itemSeconds * parseNonNegativeDecimal(item.perHour) * hours;
        }, 0);
        const drinkSeconds = (entry?.drinks || []).reduce((total, item) => {
            const itemSeconds = calculateItemSeconds(item.itemHrid);
            if (!Number.isFinite(itemSeconds) || itemSeconds <= 0) {
                return total;
            }
            const hours = itemType === "fragment" ? 24 : (runMinutes / 60);
            return total + itemSeconds * parseNonNegativeDecimal(item.perHour) * hours;
        }, 0);
        if (itemType === "fragment") {
            const expectedChestCount = Math.max(0.0001, quantityPer24h);
            const baseSeconds = 24 * 60 * 60;
            const totalSeconds = baseSeconds + foodSeconds + drinkSeconds;
            return {
                itemType,
                runMinutes,
                runSeconds,
                quantityPer24h,
                failureReason: dungeonEntryKeyFailureReason,
                dungeonEntryKeyHrid,
                dungeonEntryKeySeconds: Number.isFinite(dungeonEntryKeySeconds) ? dungeonEntryKeySeconds : 0,
                dungeonEntryKeyContributionSeconds: 0,
                foodSeconds,
                foodContributionSeconds: totalSeconds > 0 ? (foodSeconds / expectedChestCount) : 0,
                drinkSeconds,
                drinkContributionSeconds: totalSeconds > 0 ? (drinkSeconds / expectedChestCount) : 0,
                totalSeconds,
                expectedChestCount,
                secondsPerChest: totalSeconds / expectedChestCount,
                dungeonTier: 0,
                partyCount,
                partyMultiplier: 1,
                chestQuantityMultiplier: 1,
            };
        }

        const partyMultiplier = getDungeonPartyChestQuantityMultiplier(partyCount);
        const chestQuantityMultiplier = getCombatChestQuantityMultiplier() * partyMultiplier;
        const adjustedDungeonEntryKeySeconds = dungeonEntryKeySeconds * chestQuantityMultiplier;
        const sharedRunSeconds = runSeconds + foodSeconds + drinkSeconds;
        const adjustedTotalSeconds = sharedRunSeconds + adjustedDungeonEntryKeySeconds;
        const baseSeconds = runSeconds + foodSeconds + drinkSeconds + dungeonEntryKeySeconds;
        if (itemType === "refinement_chest") {
            const dungeonTier = normalizeDungeonTier(entry?.dungeonTier || 1);
            const refinementBaseCount = getRefinementExpectedCountByTier(dungeonTier);
            if (!refinementBaseCount) {
                return {
                    itemType,
                    runMinutes,
                    runSeconds,
                    quantityPer24h,
                    /*
                    failureReason: isZh ? "绮剧偧瀹濈闅炬害鏃犳晥锛屽凡鎴柇" : "Truncated: invalid refinement tier",
                    */
                    failureReason: "Truncated: invalid refinement tier",
                    dungeonEntryKeyHrid,
                    dungeonEntryKeySeconds: adjustedDungeonEntryKeySeconds,
                    dungeonEntryKeyContributionSeconds: 0,
                    foodSeconds,
                    foodContributionSeconds: 0,
                    drinkSeconds,
                    drinkContributionSeconds: 0,
                    totalSeconds: adjustedTotalSeconds,
                    expectedChestCount: 0,
                    refinementExpectedCount: 0,
                    normalExpectedCount: 0,
                    secondsPerChest: 0,
                    dungeonTier,
                    partyCount,
                    partyMultiplier,
                    chestQuantityMultiplier,
                };
            }
            const baseChestItemHrid = getBaseDungeonChestHrid(entry?.itemHrid);
            const baseChestEntry = baseChestItemHrid ? getConfiguredTimeCalculatorEntry(baseChestItemHrid) : null;
            if (!baseChestEntry) {
                return {
                    itemType,
                    runMinutes,
                    runSeconds,
                    quantityPer24h,
                    /*
                    failureReason: isZh ? "鏈厤缃甌0瀹濈鏃堕棿锛屽凡鎴柇" : "Truncated: missing T0 chest time",
                    */
                    failureReason: "Truncated: missing T0 chest time",
                    dungeonEntryKeyHrid,
                    dungeonEntryKeySeconds: adjustedDungeonEntryKeySeconds,
                    dungeonEntryKeyContributionSeconds: 0,
                    foodSeconds,
                    foodContributionSeconds: 0,
                    drinkSeconds,
                    drinkContributionSeconds: 0,
                    totalSeconds: adjustedTotalSeconds,
                    expectedChestCount: 0,
                    refinementExpectedCount: 0,
                    normalExpectedCount: 0,
                    secondsPerChest: 0,
                    dungeonTier,
                    partyCount,
                    partyMultiplier,
                    chestQuantityMultiplier,
                };
            }
            const baseChestSummary = getTimeCalculatorEntrySummary(baseChestEntry);
            if (baseChestSummary.failureReason) {
                return {
                    itemType,
                    runMinutes,
                    runSeconds,
                    quantityPer24h,
                    failureReason: baseChestSummary.failureReason,
                    dungeonEntryKeyHrid,
                    dungeonEntryKeySeconds: adjustedDungeonEntryKeySeconds,
                    dungeonEntryKeyContributionSeconds: 0,
                    foodSeconds,
                    foodContributionSeconds: 0,
                    drinkSeconds,
                    drinkContributionSeconds: 0,
                    totalSeconds: adjustedTotalSeconds,
                    expectedChestCount: 0,
                    refinementExpectedCount: 0,
                    normalExpectedCount: 0,
                    secondsPerChest: 0,
                    dungeonTier,
                    partyCount,
                    partyMultiplier,
                    chestQuantityMultiplier,
                };
            }
            const baseChestEntryKeyHrid = getDungeonEntryKeyHridByChest(baseChestItemHrid);
            const rawBaseChestEntryKeySeconds = baseChestEntryKeyHrid ? calculateItemSeconds(baseChestEntryKeyHrid) : 0;
            const baseChestEntryKeyFailureReason =
                baseChestEntryKeyHrid && (!Number.isFinite(rawBaseChestEntryKeySeconds) || rawBaseChestEntryKeySeconds <= 0)
                    ? getDependencyFailureReason(baseChestEntryKeyHrid)
                    : "";
            if (baseChestEntryKeyFailureReason) {
                return {
                    itemType,
                    runMinutes,
                    runSeconds,
                    quantityPer24h,
                    failureReason: baseChestEntryKeyFailureReason,
                    dungeonEntryKeyHrid,
                    dungeonEntryKeySeconds: adjustedDungeonEntryKeySeconds,
                    dungeonEntryKeyContributionSeconds: 0,
                    foodSeconds,
                    foodContributionSeconds: 0,
                    drinkSeconds,
                    drinkContributionSeconds: 0,
                    totalSeconds: adjustedTotalSeconds,
                    expectedChestCount: 0,
                    refinementExpectedCount: 0,
                    normalExpectedCount: 0,
                    secondsPerChest: 0,
                    dungeonTier,
                    partyCount,
                    partyMultiplier,
                    chestQuantityMultiplier,
                };
            }
            const normalExpectedCount = chestQuantityMultiplier;
            const refinementExpectedCount = Math.max(0.0001, refinementBaseCount * chestQuantityMultiplier);
            const baseChestRunSeconds = parseNonNegativeDecimal(baseChestEntry?.runMinutes) * 60;
            const baseChestFoodSeconds = Math.max(0, Number(baseChestSummary.foodSeconds || 0));
            const baseChestDrinkSeconds = Math.max(0, Number(baseChestSummary.drinkSeconds || 0));
            const baseChestEntryKeySeconds = Number.isFinite(rawBaseChestEntryKeySeconds) && rawBaseChestEntryKeySeconds > 0
                ? rawBaseChestEntryKeySeconds
                : 0;
            const normalChestBaselineSeconds =
                baseChestRunSeconds +
                baseChestFoodSeconds +
                baseChestDrinkSeconds +
                baseChestEntryKeySeconds * chestQuantityMultiplier;
            const remainingSeconds = adjustedTotalSeconds - normalChestBaselineSeconds;
            if (!Number.isFinite(remainingSeconds) || remainingSeconds <= 0) {
                return {
                    itemType,
                    runMinutes,
                    runSeconds,
                    quantityPer24h,
                    /*
                    failureReason: isZh ? "绮剧偧瀹濈鍒嗗瓙鏃犳晥锛屽凡鎴柇" : "Truncated: invalid refinement numerator",
                    */
                    failureReason: "Truncated: invalid refinement numerator",
                    dungeonEntryKeyHrid,
                    dungeonEntryKeySeconds: adjustedDungeonEntryKeySeconds,
                    dungeonEntryKeyContributionSeconds: 0,
                    foodSeconds,
                    foodContributionSeconds: 0,
                    drinkSeconds,
                    drinkContributionSeconds: 0,
                    totalSeconds: adjustedTotalSeconds,
                    expectedChestCount: refinementExpectedCount,
                    refinementExpectedCount,
                    normalExpectedCount,
                    baseChestRunSeconds,
                    baseChestFoodSeconds,
                    baseChestDrinkSeconds,
                    baseChestEntryKeySeconds,
                    normalChestBaselineSeconds,
                    secondsPerChest: 0,
                    dungeonTier,
                    partyCount,
                    partyMultiplier,
                    chestQuantityMultiplier,
                };
            }
            return {
                itemType,
                runMinutes,
                runSeconds,
                quantityPer24h,
                failureReason: dungeonEntryKeyFailureReason,
                dungeonEntryKeyHrid,
                dungeonEntryKeySeconds: Number.isFinite(adjustedDungeonEntryKeySeconds) ? adjustedDungeonEntryKeySeconds : 0,
                dungeonEntryKeyContributionSeconds: adjustedDungeonEntryKeySeconds,
                foodSeconds,
                foodContributionSeconds: foodSeconds,
                drinkSeconds,
                drinkContributionSeconds: drinkSeconds,
                totalSeconds: adjustedTotalSeconds,
                expectedChestCount: refinementExpectedCount,
                refinementExpectedCount,
                refinementBaseCount,
                normalExpectedCount,
                baseChestRunSeconds,
                baseChestFoodSeconds,
                baseChestDrinkSeconds,
                baseChestEntryKeySeconds,
                normalChestBaselineSeconds,
                secondsPerChest: remainingSeconds / refinementExpectedCount,
                dungeonTier,
                partyCount,
                partyMultiplier,
                chestQuantityMultiplier,
            };
        }

        const expectedChestCount = chestQuantityMultiplier;
        return {
            itemType,
            runMinutes,
            runSeconds,
            quantityPer24h,
            failureReason: dungeonEntryKeyFailureReason,
            dungeonEntryKeyHrid,
            dungeonEntryKeySeconds: Number.isFinite(adjustedDungeonEntryKeySeconds) ? adjustedDungeonEntryKeySeconds : 0,
            dungeonEntryKeyContributionSeconds: adjustedDungeonEntryKeySeconds,
            foodSeconds,
            foodContributionSeconds: foodSeconds,
            drinkSeconds,
            drinkContributionSeconds: drinkSeconds,
            totalSeconds: adjustedTotalSeconds,
            expectedChestCount,
            secondsPerChest: adjustedTotalSeconds / expectedChestCount,
            dungeonTier: 0,
            partyCount,
            partyMultiplier,
            chestQuantityMultiplier,
        };
    }

    function getTimeCalculatorPanelRoots() {
        const tabsContainer = document.querySelector('[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabsContainer"]');
        const tabPanelsContainer = document.querySelector('[class^="CharacterManagement_tabsComponentContainer"] [class*="TabsComponent_tabPanelsContainer"]');
        return {
            tabsContainer,
            tabPanelsContainer,
        };
    }

    function syncTimeCalculatorPanelHiddenState(tabPanelsContainer = getTimeCalculatorPanelRoots().tabPanelsContainer) {
        if (!(tabPanelsContainer instanceof HTMLElement)) {
            return;
        }
        for (const panelNode of tabPanelsContainer.querySelectorAll('[class*="TabPanel_tabPanel"]')) {
            if (!(panelNode instanceof HTMLElement)) {
                continue;
            }
            panelNode.hidden = panelNode.classList.contains("TabPanel_hidden__26UM3");
        }
    }

    function createTimeCalculatorSearchControl(options, placeholderText, addButtonText, onAdd, config = {}) {
        const {
            getDraftValue = () => "",
            setDraftValue = () => {},
        } = config;
        const wrapper = document.createElement("div");
        wrapper.style.display = "flex";
        wrapper.style.alignItems = "center";
        wrapper.style.gap = "6px";
        wrapper.style.position = "relative";

        const input = document.createElement("input");
        input.type = "text";
        input.placeholder = placeholderText;
        input.style.background = "#dde2f8";
        input.style.color = "#000000";
        input.style.border = "none";
        input.style.borderRadius = "4px";
        input.style.padding = "4px";
        input.style.margin = "2px";
        input.style.minWidth = "180px";
        input.style.flex = "1";
        input.autocomplete = "off";
        input.value = String(getDraftValue() || "");

        const addButton = document.createElement("button");
        addButton.textContent = addButtonText;
        addButton.style.background = "#4caf50";
        addButton.style.color = "#ffffff";
        addButton.style.border = "none";
        addButton.style.borderRadius = "4px";
        addButton.style.padding = "4px 8px";
        addButton.style.cursor = "pointer";

        const searchResults = document.createElement("div");
        searchResults.style.background = "#2c2e45";
        searchResults.style.border = "none";
        searchResults.style.borderRadius = "4px";
        searchResults.style.padding = "4px";
        searchResults.style.margin = "2px";
        searchResults.style.width = "240px";
        searchResults.style.maxHeight = "260px";
        searchResults.style.overflowY = "auto";
        searchResults.style.zIndex = "1000";
        searchResults.style.display = "none";
        searchResults.style.position = "absolute";
        searchResults.style.left = "4px";
        searchResults.style.top = "36px";

        const optionMap = new Map(options.map((option) => [option.itemName, option]));
        const hideResults = () => {
            searchResults.style.display = "none";
        };
        const populateResults = (filteredOptions) => {
            searchResults.innerHTML = "";
            filteredOptions.forEach((option, index) => {
                const resultItem = document.createElement("div");
                resultItem.style.borderBottom = "1px solid #98a7e9";
                resultItem.style.borderRadius = "4px";
                resultItem.style.padding = "4px";
                resultItem.style.alignItems = "center";
                resultItem.style.display = "flex";
                resultItem.style.cursor = "pointer";
                if (index === 0) {
                    resultItem.style.background = "#4a4c6a";
                }
                const itemIcon = document.createElement("div");
                itemIcon.appendChild(createIconSvg(getIconHrefByItemHrid(option.itemHrid), 18));
                const itemName = document.createElement("span");
                itemName.textContent = option.itemName;
                itemName.style.marginLeft = "2px";
                resultItem.appendChild(itemIcon);
                resultItem.appendChild(itemName);
                resultItem.addEventListener("mouseenter", () => {
                    resultItem.style.background = "#4a4c6a";
                });
                resultItem.addEventListener("mouseleave", () => {
                    resultItem.style.background = "transparent";
                });
                resultItem.addEventListener("click", () => {
                    input.value = option.itemName;
                    setDraftValue(option.itemName);
                    hideResults();
                });
                searchResults.appendChild(resultItem);
            });
            searchResults.style.display = filteredOptions.length ? "block" : "none";
        };
        const triggerAdd = () => {
            const option = optionMap.get(input.value.trim()) || null;
            if (!option?.itemHrid) {
                return;
            }
            onAdd(option.itemHrid);
            input.value = "";
            setDraftValue("");
            hideResults();
        };
        addButton.addEventListener("click", triggerAdd);
        input.addEventListener("focus", () => {
            setTimeout(() => {
                input.select();
            }, 0);
            const searchTerm = input.value.toLowerCase().trim();
            if (searchTerm.length >= 1) {
                const filtered = options.filter((option) => option.itemName.toLowerCase().includes(searchTerm));
                populateResults(filtered);
            }
        });
        input.addEventListener("input", () => {
            setDraftValue(input.value);
            const searchTerm = input.value.toLowerCase().trim();
            if (searchTerm.length < 1) {
                hideResults();
                return;
            }
            const filtered = options.filter((option) => option.itemName.toLowerCase().includes(searchTerm));
            populateResults(filtered);
        });
        input.addEventListener("keydown", (event) => {
            if (event.key === "Enter") {
                event.preventDefault();
                triggerAdd();
            } else if (event.key === "Escape") {
                hideResults();
            }
        });
        document.addEventListener("click", (event) => {
            if (!wrapper.contains(event.target)) {
                hideResults();
            }
        });

        wrapper.appendChild(input);
        wrapper.appendChild(addButton);
        wrapper.appendChild(searchResults);
        return wrapper;
    }

    function createTimeCalculatorItemBadge(itemHrid, itemName) {
        const badge = document.createElement("div");
        badge.style.minWidth = "40px";
        badge.style.alignItems = "center";
        badge.style.display = "flex";

        const iconContainer = document.createElement("div");
        iconContainer.style.marginLeft = "2px";
        const svg = createIconSvg(getIconHrefByItemHrid(itemHrid), 18);
        iconContainer.appendChild(svg);

        const name = document.createElement("span");
        name.textContent = itemName;
        name.style.padding = "4px 1px";
        name.style.marginLeft = "2px";
        name.style.whiteSpace = "nowrap";
        name.style.overflow = "hidden";

        badge.appendChild(iconContainer);
        badge.appendChild(name);
        return badge;
    }

    function makeTimeCalculatorItemRow(entry, kind, item) {
        const row = document.createElement("div");
        row.style.display = "flex";
        row.style.alignItems = "center";
        row.style.gap = "6px";
        row.style.marginTop = "4px";

        const itemName = getLocalizedItemName(item.itemHrid, state.itemDetailMap?.[item.itemHrid]?.name || item.itemHrid);
        const name = createTimeCalculatorItemBadge(item.itemHrid, itemName);
        name.style.flex = "1";
        row.appendChild(name);

        const rateInput = document.createElement("input");
        rateInput.type = "number";
        rateInput.min = "0";
        rateInput.step = "0.01";
        rateInput.value = String(Number(item.perHour || 0));
        rateInput.style.background = "#dde2f8";
        rateInput.style.color = "#000000";
        rateInput.style.border = "none";
        rateInput.style.borderRadius = "4px";
        rateInput.style.padding = "4px";
        rateInput.style.width = "88px";
        rateInput.title = isZh ? "每小时消耗数量" : "Per-hour consumption";
        const commitRateInput = () => {
            item.perHour = Math.max(0, Number(rateInput.value || 0));
            saveTimeCalculatorData();
            rerenderTimeCalculatorPanel();
        };
        rateInput.addEventListener("change", commitRateInput);
        rateInput.addEventListener("blur", commitRateInput);
        rateInput.addEventListener("keydown", (event) => {
            if (event.key === "Enter") {
                event.preventDefault();
                rateInput.blur();
            }
        });
        row.appendChild(rateInput);

        const removeButton = document.createElement("button");
        removeButton.textContent = isZh ? "删除" : "Remove";
        removeButton.style.background = "#b33939";
        removeButton.style.color = "#ffffff";
        removeButton.style.border = "none";
        removeButton.style.borderRadius = "4px";
        removeButton.style.padding = "4px 8px";
        removeButton.style.cursor = "pointer";
        removeButton.textContent = isZh ? "\u5220\u9664" : "Remove";
        removeButton.addEventListener("click", () => {
            entry[kind] = (entry[kind] || []).filter((candidate) => candidate.id !== item.id);
            saveTimeCalculatorData();
            rerenderTimeCalculatorPanel();
        });
        row.appendChild(removeButton);
        return row;
    }

    function createTimeCalculatorEntryCard(entry) {
        const summary = getTimeCalculatorEntrySummary(entry);
        const card = document.createElement("div");
        card.className = "ictime-timecalc-entry-card";
        card.dataset.entryId = entry.id;
        card.style.background = "#2c2e45";
        card.style.borderRadius = "6px";
        card.style.padding = "8px";
        card.style.margin = "6px 0";
        card.style.transition = "transform 150ms cubic-bezier(.2,.8,.2,1)";

        const header = document.createElement("div");
        header.style.display = "flex";
        header.style.alignItems = "center";
        header.style.justifyContent = "space-between";
        header.style.textAlign = "left";
        card.appendChild(header);

        const title = createTimeCalculatorItemBadge(
            entry.itemHrid,
            getTimeCalculatorItemDisplayName(entry.itemHrid)
        );
        title.style.flex = "1";
        title.style.fontWeight = "bold";
        header.appendChild(title);

        const headerButtons = document.createElement("div");
        headerButtons.style.display = "flex";
        headerButtons.style.alignItems = "center";
        headerButtons.style.gap = "6px";
        header.appendChild(headerButtons);

        const dragHandle = document.createElement("span");
        dragHandle.className = "ictime-timecalc-drag-handle";
        dragHandle.textContent = isZh ? "拖动" : "Drag";
        dragHandle.style.cursor = "grab";
        dragHandle.style.padding = "0 6px";
        dragHandle.style.opacity = "0.68";
        dragHandle.style.fontSize = "0.78rem";
        dragHandle.style.lineHeight = "1.4";
        dragHandle.style.borderRadius = "4px";
        dragHandle.style.background = "#4a4c6a";
        dragHandle.style.color = "#ffffff";
        dragHandle.style.touchAction = "none";
        dragHandle.textContent = isZh ? "\u62d6\u52a8" : "Drag";
        headerButtons.appendChild(dragHandle);

        const toggleButton = document.createElement("button");
        toggleButton.textContent = entry.collapsed
            ? (isZh ? "展开" : "Expand")
            : (isZh ? "折叠" : "Collapse");
        toggleButton.style.background = "#4a4c6a";
        toggleButton.style.color = "#ffffff";
        toggleButton.style.border = "none";
        toggleButton.style.borderRadius = "4px";
        toggleButton.style.padding = "4px 8px";
        toggleButton.style.cursor = "pointer";
        toggleButton.textContent = entry.collapsed
            ? (isZh ? "\u5c55\u5f00" : "Expand")
            : (isZh ? "\u6298\u53e0" : "Collapse");
        toggleButton.addEventListener("click", () => {
            entry.collapsed = !entry.collapsed;
            saveTimeCalculatorData(false);
            if (card.isConnected) {
                card.replaceWith(createTimeCalculatorEntryCard(entry));
                return;
            }
            rerenderTimeCalculatorPanel();
        });
        headerButtons.appendChild(toggleButton);

        const removeButton = document.createElement("button");
        removeButton.textContent = isZh ? "删除" : "Remove";
        removeButton.style.background = "#b33939";
        removeButton.style.color = "#ffffff";
        removeButton.style.border = "none";
        removeButton.style.borderRadius = "4px";
        removeButton.style.padding = "4px 8px";
        removeButton.style.cursor = "pointer";
        removeButton.textContent = isZh ? "\u5220\u9664" : "Remove";
        removeButton.addEventListener("click", () => {
            state.timeCalculatorEntries = state.timeCalculatorEntries.filter((candidate) => candidate.id !== entry.id);
            saveTimeCalculatorData();
            rerenderTimeCalculatorPanel();
        });
        headerButtons.appendChild(removeButton);

        const collapsedSummary = document.createElement("div");
        collapsedSummary.style.marginTop = "8px";
        collapsedSummary.style.fontSize = "0.82rem";
        collapsedSummary.style.lineHeight = "1.35";
        collapsedSummary.style.textAlign = "left";
        collapsedSummary.textContent = summary.itemType === "fragment"
            ? `${isZh ? "获得一个耗时" : "Time per fragment"}:${formatAutoDuration(summary.secondsPerChest)}`
            : `${isZh ? "获得一个耗时" : "Time per chest"}:${formatAutoDuration(summary.secondsPerChest)}`;
        card.appendChild(collapsedSummary);
        /*
        collapsedSummary.textContent = summary.itemType === "fragment"
            ? `${isZh ? "鑾峰緱涓€涓€楁椂" : "Time per fragment"}锛?{formatAutoDuration(summary.secondsPerChest)}`
            : summary.itemType === "refinement_chest"
                ? `${isZh ? "鑾峰緱涓€涓簿鐐煎疂绠辨椂闂? : "Time per refinement chest"}锛?{formatAutoDuration(summary.secondsPerChest)}`
                : `${isZh ? "鑾峰緱涓€涓€楁椂" : "Time per chest"}锛?{formatAutoDuration(summary.secondsPerChest)}`;

        */
        collapsedSummary.textContent = summary.itemType === "fragment"
            ? `Time per fragment: ${formatAutoDuration(summary.secondsPerChest)}`
            : summary.itemType === "refinement_chest"
                ? `Time per refinement chest: ${formatAutoDuration(summary.secondsPerChest)}`
                : `Time per chest: ${formatAutoDuration(summary.secondsPerChest)}`;
        collapsedSummary.textContent = summary.itemType === "fragment"
            ? `${isZh ? "\u83b7\u5f97\u4e00\u4e2a\u788e\u7247\u8017\u65f6" : "Time per fragment"}: ${formatAutoDuration(summary.secondsPerChest)}`
            : summary.itemType === "refinement_chest"
                ? `${isZh ? "\u83b7\u5f97\u4e00\u4e2a\u7cbe\u70bc\u7bb1\u5b50\u8017\u65f6" : "Time per refinement chest"}: ${formatAutoDuration(summary.secondsPerChest)}`
                : `${isZh ? "\u83b7\u5f97\u4e00\u4e2a\u5b9d\u7bb1\u8017\u65f6" : "Time per chest"}: ${formatAutoDuration(summary.secondsPerChest)}`;

        if (entry.collapsed) {
            return card;
        }

        const timeRow = document.createElement("div");
        timeRow.style.display = "flex";
        timeRow.style.alignItems = "center";
        timeRow.style.gap = "6px";
        timeRow.style.marginTop = "8px";
        timeRow.style.textAlign = "left";
        card.appendChild(timeRow);

        const timeLabel = document.createElement("div");
        timeLabel.textContent = summary.itemType === "fragment"
            ? (isZh ? "24小时碎片数量" : "24h fragment qty")
            : (isZh ? "单次地牢时间(分钟)" : "Dungeon time (min)");
        timeLabel.textContent = summary.itemType === "fragment"
            ? (isZh ? "24\u5c0f\u65f6\u788e\u7247\u6570\u91cf" : "24h fragment qty")
            : (isZh ? "\u5355\u6b21\u5730\u7262\u65f6\u95f4(\u5206\u949f)" : "Dungeon time (min)");
        timeLabel.style.flex = "1";
        timeRow.appendChild(timeLabel);

        const timeInput = document.createElement("input");
        timeInput.type = "number";
        timeInput.min = "0";
        timeInput.step = "any";
        timeInput.inputMode = "decimal";
        timeInput.value = String(parseNonNegativeDecimal(summary.itemType === "fragment" ? entry.quantityPer24h || 0 : entry.runMinutes || 0));
        timeInput.style.background = "#dde2f8";
        timeInput.style.color = "#000000";
        timeInput.style.border = "none";
        timeInput.style.borderRadius = "4px";
        timeInput.style.padding = "4px";
        timeInput.style.width = "96px";
        const commitTimeInput = () => {
            if (summary.itemType === "fragment") {
                entry.quantityPer24h = parseNonNegativeDecimal(timeInput.value);
            } else {
                entry.runMinutes = parseNonNegativeDecimal(timeInput.value);
            }
            timeInput.value = String(summary.itemType === "fragment" ? entry.quantityPer24h : entry.runMinutes);
            saveTimeCalculatorData();
            rerenderTimeCalculatorPanel();
        };
        timeInput.addEventListener("change", commitTimeInput);
        timeInput.addEventListener("blur", commitTimeInput);
        timeInput.addEventListener("keydown", (event) => {
            if (event.key === "Enter") {
                event.preventDefault();
                timeInput.blur();
            }
        });
        timeRow.appendChild(timeInput);

        if (summary.itemType === "chest" || summary.itemType === "refinement_chest") {
            const partyRow = document.createElement("div");
            partyRow.style.display = "flex";
            partyRow.style.alignItems = "center";
            partyRow.style.gap = "6px";
            partyRow.style.marginTop = "8px";
            partyRow.style.textAlign = "left";
            card.appendChild(partyRow);

            const partyLabel = document.createElement("div");
            partyLabel.textContent = isZh ? "\u4eba\u6570" : "Party size";
            partyLabel.style.flex = "1";
            partyRow.appendChild(partyLabel);

            const partySelect = document.createElement("select");
            partySelect.style.background = "#dde2f8";
            partySelect.style.color = "#000000";
            partySelect.style.border = "none";
            partySelect.style.borderRadius = "4px";
            partySelect.style.padding = "4px";
            for (let count = 1; count <= 5; count += 1) {
                const option = document.createElement("option");
                option.value = String(count);
                option.textContent = String(count);
                partySelect.appendChild(option);
            }
            partySelect.value = String(normalizeDungeonPartyCount(entry.partyCount || 5));
            partySelect.addEventListener("change", () => {
                entry.partyCount = normalizeDungeonPartyCount(partySelect.value || 5);
                saveTimeCalculatorData();
                rerenderTimeCalculatorPanel();
            });
            partyRow.appendChild(partySelect);
        }

        if (summary.itemType === "refinement_chest") {
            const tierRow = document.createElement("div");
            tierRow.style.display = "flex";
            tierRow.style.alignItems = "center";
            tierRow.style.gap = "6px";
            tierRow.style.marginTop = "8px";
            tierRow.style.textAlign = "left";
            card.appendChild(tierRow);

            const tierLabel = document.createElement("div");
            tierLabel.textContent = isZh ? "\u5730\u7262T\u7b49\u7ea7" : "Dungeon tier";
            tierLabel.style.flex = "1";
            tierRow.appendChild(tierLabel);

            const tierSelect = document.createElement("select");
            tierSelect.style.background = "#dde2f8";
            tierSelect.style.color = "#000000";
            tierSelect.style.border = "none";
            tierSelect.style.borderRadius = "4px";
            tierSelect.style.padding = "4px";
            [
                { value: "1", label: "T1" },
                { value: "2", label: "T2" },
            ].forEach((optionConfig) => {
                const option = document.createElement("option");
                option.value = optionConfig.value;
                option.textContent = optionConfig.label;
                tierSelect.appendChild(option);
            });
            tierSelect.value = String(normalizeDungeonTier(entry.dungeonTier || 1) || 1);
            tierSelect.addEventListener("change", () => {
                entry.dungeonTier = normalizeDungeonTier(tierSelect.value || 1);
                saveTimeCalculatorData();
                rerenderTimeCalculatorPanel();
            });
            tierRow.appendChild(tierSelect);
        }

        const summaryBlock = document.createElement("div");
        summaryBlock.style.marginTop = "8px";
        summaryBlock.style.fontSize = "0.78rem";
        summaryBlock.style.lineHeight = "1.35";
        summaryBlock.style.textAlign = "left";
        summaryBlock.innerHTML = summary.itemType === "fragment"
            ? [
                `${isZh ? "单碎片时间" : "Time/fragment"}:${formatAutoDuration(summary.secondsPerChest)}`,
                `${isZh ? "食物时间" : "Food time"}:${formatAutoDuration(summary.foodSeconds)}`,
                `${isZh ? "饮料时间" : "Drink time"}:${formatAutoDuration(summary.drinkSeconds)}`,
            ].join("<br>")
            : [
                `${isZh ? "单次总时间" : "Total/run"}:${formatAutoDuration(summary.totalSeconds)}`,
                `${isZh ? "单箱时间" : "Time/chest"}:${formatAutoDuration(summary.secondsPerChest)}`,
                `${isZh ? "地牢钥匙时间" : "Entry key time"}:${formatAutoDuration(summary.dungeonEntryKeySeconds)}`,
                `${isZh ? "食物时间" : "Food time"}:${formatAutoDuration(summary.foodSeconds)}`,
                `${isZh ? "饮料时间" : "Drink time"}:${formatAutoDuration(summary.drinkSeconds)}`,
            ].join("<br>");
        card.appendChild(summaryBlock);
        summaryBlock.innerHTML = summary.itemType === "fragment"
            ? [
                `${isZh ? "\u5355\u788e\u7247\u65f6\u95f4" : "Time/fragment"}: ${formatAutoDuration(summary.secondsPerChest)}`,
                `${isZh ? "\u98df\u7269\u65f6\u95f4" : "Food time"}: ${formatAutoDuration(summary.foodSeconds)}`,
                `${isZh ? "\u996e\u6599\u65f6\u95f4" : "Drink time"}: ${formatAutoDuration(summary.drinkSeconds)}`,
            ].join("<br>")
            : [
                `${isZh ? "\u5730\u7262\u94a5\u5319\u65f6\u95f4" : "Entry key time"}: ${formatAutoDuration(summary.dungeonEntryKeyContributionSeconds || 0)}`,
                `${isZh ? "\u98df\u7269\u65f6\u95f4" : "Food time"}: ${formatAutoDuration(summary.foodContributionSeconds || 0)}`,
                `${isZh ? "\u996e\u6599\u65f6\u95f4" : "Drink time"}: ${formatAutoDuration(summary.drinkContributionSeconds || 0)}`,
            ].join("<br>");
        if (summary.itemType !== "fragment") {
            summaryBlock.innerHTML = [
                `${isZh ? "鍦扮墷閽ュ寵鏃堕棿" : "Entry key time"}锛?{formatAutoDuration(summary.dungeonEntryKeyContributionSeconds || 0)}`,
                `${isZh ? "椋熺墿鏃堕棿" : "Food time"}锛?{formatAutoDuration(summary.foodContributionSeconds || 0)}`,
                `${isZh ? "楗枡鏃堕棿" : "Drink time"}锛?{formatAutoDuration(summary.drinkContributionSeconds || 0)}`,
            ].join("<br>");
        }

        summaryBlock.innerHTML = summary.itemType === "fragment"
            ? [
                `${isZh ? "\u5355\u788e\u7247\u65f6\u95f4" : "Time/fragment"}: ${formatAutoDuration(summary.secondsPerChest)}`,
                `${isZh ? "\u98df\u7269\u65f6\u95f4" : "Food time"}: ${formatAutoDuration(summary.foodSeconds)}`,
                `${isZh ? "\u996e\u6599\u65f6\u95f4" : "Drink time"}: ${formatAutoDuration(summary.drinkSeconds)}`,
            ].join("<br>")
            : [
                `${isZh ? "\u5730\u7262\u94a5\u5319\u65f6\u95f4" : "Entry key time"}: ${formatAutoDuration(summary.dungeonEntryKeyContributionSeconds || 0)}`,
                `${isZh ? "\u98df\u7269\u65f6\u95f4" : "Food time"}: ${formatAutoDuration(summary.foodContributionSeconds || 0)}`,
                `${isZh ? "\u996e\u6599\u65f6\u95f4" : "Drink time"}: ${formatAutoDuration(summary.drinkContributionSeconds || 0)}`,
            ].join("<br>");

        const consumableSection = document.createElement("div");
        consumableSection.style.marginTop = "10px";
        consumableSection.style.textAlign = "left";
        card.appendChild(consumableSection);

        const consumableHeader = document.createElement("div");
        consumableHeader.textContent = isZh ? "消耗品(每小时消耗)" : "Consumables (per hour)";
        consumableHeader.style.fontWeight = "bold";
        consumableHeader.textContent = isZh ? "\u6d88\u8017\u54c1\uff08\u6bcf\u5c0f\u65f6\u6d88\u8017\uff09" : "Consumables (per hour)";
        consumableSection.appendChild(consumableHeader);

        const consumableControls = document.createElement("div");
        consumableControls.style.marginTop = "4px";
        consumableSection.appendChild(consumableControls);

        consumableControls.appendChild(createTimeCalculatorSearchControl(
            getTimeCalculatorConsumableOptions(),
            isZh ? "搜索食物或饮料..." : "Search food or drink...",
            isZh ? "加入消耗品" : "Add consumable",
            (itemHrid) => {
                const option = getTimeCalculatorConsumableOptions().find((candidate) => candidate.itemHrid === itemHrid);
                if (!option) {
                    return;
                }
                const targetKey = option.kind === "food" ? "foods" : "drinks";
                const existing = [...(entry.foods || []), ...(entry.drinks || [])].find((item) => item.itemHrid === itemHrid);
                if (!existing) {
                    entry[targetKey].push({
                        id: `${targetKey.slice(0, -1)}-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                        itemHrid,
                        perHour: 0,
                    });
                }
                saveTimeCalculatorData();
                rerenderTimeCalculatorPanel();
            },
            {
                getDraftValue: () => state.timeCalculatorDrafts?.consumableQueryByEntryId?.[entry.id] || "",
                setDraftValue: (value) => {
                    if (!state.timeCalculatorDrafts) {
                        state.timeCalculatorDrafts = { addItemQuery: "", consumableQueryByEntryId: {} };
                    }
                    if (!state.timeCalculatorDrafts.consumableQueryByEntryId) {
                        state.timeCalculatorDrafts.consumableQueryByEntryId = {};
                    }
                    state.timeCalculatorDrafts.consumableQueryByEntryId[entry.id] = String(value || "");
                },
            }
        ));

        const consumableSearchInput = consumableControls.querySelector("input");
        if (consumableSearchInput) {
            consumableSearchInput.placeholder = isZh ? "\u641c\u7d22\u98df\u7269\u6216\u996e\u6599..." : "Search food or drink...";
        }
        const consumableSearchButton = consumableControls.querySelector("button");
        if (consumableSearchButton) {
            consumableSearchButton.textContent = isZh ? "\u52a0\u5165\u6d88\u8017\u54c1" : "Add consumable";
        }

        for (const item of entry.foods || []) {
            consumableSection.appendChild(makeTimeCalculatorItemRow(entry, "foods", item));
        }
        for (const item of entry.drinks || []) {
            consumableSection.appendChild(makeTimeCalculatorItemRow(entry, "drinks", item));
        }

        return card;
    }

    function renderTimeCalculatorPanel() {
        if (isMissingDerivedRuntimeState()) {
            ensureRuntimeStateFresh();
        }
        if (!state.itemDetailMap) {
            loadCachedClientData();
        }
        const container = state.timeCalculatorContainer;
        if (!container) {
            return;
        }
        loadTimeCalculatorData();
        state.timeCalculatorRefreshPending = false;
        container.innerHTML = "";

        const addSection = document.createElement("div");
        addSection.style.background = "#2c2e45";
        addSection.style.borderRadius = "6px";
        addSection.style.padding = "8px";
        addSection.style.marginBottom = "8px";
        addSection.style.position = "relative";
        container.appendChild(addSection);

        const headerRow = document.createElement("div");
        headerRow.style.display = "flex";
        headerRow.style.alignItems = "center";
        headerRow.style.justifyContent = "space-between";
        headerRow.style.gap = "8px";
        addSection.appendChild(headerRow);

        const addTitle = document.createElement("div");
        addTitle.textContent = isZh ? "添加物品" : "Add item";
        addTitle.style.fontWeight = "bold";
        addTitle.textContent = isZh ? "\u6dfb\u52a0\u7269\u54c1" : "Add item";
        headerRow.appendChild(addTitle);

        const headerActions = document.createElement("div");
        headerActions.style.display = "flex";
        headerActions.style.alignItems = "center";
        headerActions.style.gap = "6px";
        headerRow.appendChild(headerActions);

        const refreshButton = document.createElement("button");
        refreshButton.type = "button";
        refreshButton.dataset.ictimeTimeCalcRefreshButton = "true";
        refreshButton.textContent = "\u21bb";
        refreshButton.title = isZh ? "\u5237\u65b0\u65f6\u95f4\u8ba1\u7b97" : "Refresh time calculator";
        refreshButton.setAttribute("aria-label", isZh ? "\u5237\u65b0\u65f6\u95f4\u8ba1\u7b97" : "Refresh time calculator");
        refreshButton.style.width = "22px";
        refreshButton.style.height = "22px";
        refreshButton.style.padding = "0";
        refreshButton.style.border = "none";
        refreshButton.style.borderRadius = "999px";
        refreshButton.style.cursor = "pointer";
        refreshButton.style.fontSize = "13px";
        refreshButton.style.lineHeight = "1";
        refreshButton.style.color = "#ffffff";
        refreshButton.style.boxShadow = "0 0 1px rgba(0, 0, 0, 0.8)";
        refreshButton.style.background = "#56628a";
        refreshButton.addEventListener("click", () => {
            clearCaches();
            rerenderTimeCalculatorPanel();
        });
        headerActions.appendChild(refreshButton);

        const settingsButton = document.createElement("button");
        settingsButton.type = "button";
        settingsButton.dataset.ictimeTimeCalcSettingsButton = "true";
        settingsButton.textContent = "\u2699";
        settingsButton.title = isZh ? "\u8bbe\u7f6e" : "Settings";
        settingsButton.setAttribute("aria-label", isZh ? "\u8bbe\u7f6e" : "Settings");
        settingsButton.style.width = "22px";
        settingsButton.style.height = "22px";
        settingsButton.style.padding = "0";
        settingsButton.style.border = "none";
        settingsButton.style.borderRadius = "999px";
        settingsButton.style.cursor = "pointer";
        settingsButton.style.fontSize = "13px";
        settingsButton.style.lineHeight = "1";
        settingsButton.style.color = "#ffffff";
        settingsButton.style.boxShadow = "0 0 1px rgba(0, 0, 0, 0.8)";
        settingsButton.style.background = isTimeCalculatorSettingsOpen() ? "#7682b6" : "#56628a";
        settingsButton.addEventListener("click", () => {
            setTimeCalculatorSettingsOpen(!isTimeCalculatorSettingsOpen());
        });
        headerActions.appendChild(settingsButton);

        const settingsPanel = document.createElement("div");
        settingsPanel.dataset.ictimeTimeCalcSettingsPanel = "true";
        settingsPanel.style.position = "absolute";
        settingsPanel.style.top = "36px";
        settingsPanel.style.right = "8px";
        settingsPanel.style.width = "min(420px, calc(100% - 16px))";
        settingsPanel.style.maxWidth = "calc(100% - 16px)";
        settingsPanel.style.padding = "10px";
        settingsPanel.style.borderRadius = "8px";
        settingsPanel.style.background = "#1c202f";
        settingsPanel.style.border = "1.5px solid rgba(214, 222, 255, 0.24)";
        settingsPanel.style.boxShadow = "0 0 5px 1px rgba(0, 0, 0, 0.65)";
        settingsPanel.style.zIndex = "3";
        settingsPanel.style.display = isTimeCalculatorSettingsOpen() ? "flex" : "none";
        settingsPanel.style.flexDirection = "column";
        settingsPanel.style.gap = "10px";
        addSection.appendChild(settingsPanel);

        const settingsPanelHeader = document.createElement("div");
        settingsPanelHeader.style.display = "flex";
        settingsPanelHeader.style.alignItems = "center";
        settingsPanelHeader.style.justifyContent = "space-between";
        settingsPanelHeader.style.gap = "8px";
        settingsPanel.appendChild(settingsPanelHeader);

        const settingsPanelTitle = document.createElement("div");
        settingsPanelTitle.textContent = isZh ? "\u8bbe\u7f6e" : "Settings";
        settingsPanelTitle.style.fontWeight = "bold";
        settingsPanelTitle.style.fontSize = "0.95rem";
        settingsPanelHeader.appendChild(settingsPanelTitle);

        const settingsCloseButton = document.createElement("button");
        settingsCloseButton.type = "button";
        settingsCloseButton.textContent = "\u00d7";
        settingsCloseButton.title = isZh ? "\u5173\u95ed" : "Close";
        settingsCloseButton.setAttribute("aria-label", isZh ? "\u5173\u95ed" : "Close");
        settingsCloseButton.style.width = "20px";
        settingsCloseButton.style.height = "20px";
        settingsCloseButton.style.padding = "0";
        settingsCloseButton.style.border = "none";
        settingsCloseButton.style.borderRadius = "999px";
        settingsCloseButton.style.cursor = "pointer";
        settingsCloseButton.style.fontSize = "14px";
        settingsCloseButton.style.lineHeight = "1";
        settingsCloseButton.style.color = "#ffffff";
        settingsCloseButton.style.background = "#bb5e5e";
        settingsCloseButton.onclick = (event) => {
            event.preventDefault();
            event.stopPropagation();
            if (!state.timeCalculatorSettingsOpen) {
                return false;
            }
            state.timeCalculatorSettingsOpen = false;
            rerenderTimeCalculatorPanel();
            return false;
        };
        settingsPanelHeader.appendChild(settingsCloseButton);

        const settingsPanelContent = document.createElement("div");
        settingsPanelContent.style.display = "flex";
        settingsPanelContent.style.flexDirection = "column";
        settingsPanelContent.style.gap = "10px";
        settingsPanel.appendChild(settingsPanelContent);

        const compactModeRow = document.createElement("label");
        compactModeRow.style.display = "flex";
        compactModeRow.style.alignItems = "center";
        compactModeRow.style.gap = "6px";
        compactModeRow.style.cursor = "pointer";
        compactModeRow.style.flexWrap = "wrap";
        const compactModeInput = document.createElement("input");
        compactModeInput.type = "checkbox";
        compactModeInput.dataset.ictimeTimeCalcCompactMode = "true";
        compactModeInput.checked = isTimeCalculatorCompactModeEnabled();
        compactModeInput.addEventListener("change", () => {
            setTimeCalculatorCompactMode(compactModeInput.checked);
        });
        const compactModeText = document.createElement("span");
        compactModeText.textContent = isZh
            ? "简洁模式(隐藏悬浮窗细节 / 炼金公式 / 强化细节)"
            : "Compact mode (hide tooltip detail / alchemy formula / enhancing detail)";
        compactModeText.style.fontSize = "0.82rem";
        compactModeText.textContent = isZh
            ? "\u7b80\u6d01\u6a21\u5f0f\uff08\u9690\u85cf\u60ac\u6d6e\u7a97\u7ec6\u8282 / \u70bc\u91d1\u516c\u5f0f / \u5f3a\u5316\u7ec6\u8282\uff09"
            : "Compact mode (hide tooltip detail / alchemy formula / enhancing detail)";
        compactModeRow.appendChild(compactModeInput);
        compactModeRow.appendChild(compactModeText);
        settingsPanelContent.appendChild(compactModeRow);

        const essenceSourceConfigs = [
            {
                essenceHrid: "/items/brewing_essence",
                label: isZh ? "\u51b2\u6ce1\u7cbe\u534e\u5206\u89e3\u8336\u53f6" : "Brewing essence leaf",
            },
            {
                essenceHrid: "/items/tailoring_essence",
                label: isZh ? "\u88c1\u7f1d\u7cbe\u534e\u5206\u89e3\u76ae" : "Tailoring essence hide",
            },
        ];
        for (const config of essenceSourceConfigs) {
            const sourceRow = document.createElement("label");
            sourceRow.style.display = "flex";
            sourceRow.style.alignItems = "center";
            sourceRow.style.gap = "8px";
            sourceRow.style.flexWrap = "wrap";

            const sourceLabel = document.createElement("span");
            sourceLabel.textContent = config.label;
            sourceLabel.style.fontSize = "0.82rem";
            sourceLabel.style.minWidth = "120px";
            sourceRow.appendChild(sourceLabel);

            const sourceSelect = document.createElement("select");
            sourceSelect.dataset.ictimeTimeCalcEssenceSource = config.essenceHrid;
            sourceSelect.style.flex = "1";
            sourceSelect.style.minWidth = "180px";
            sourceSelect.style.padding = "4px 6px";
            sourceSelect.style.borderRadius = "4px";
            sourceSelect.style.border = "1px solid rgba(255, 255, 255, 0.18)";
            sourceSelect.style.background = "#1e2032";
            sourceSelect.style.color = "#ffffff";

            const options = getTimeCalculatorEssenceSourceOptions(config.essenceHrid);
            const selectedSourceItemHrid = getConfiguredEssenceDecomposeSourceItemHrid(config.essenceHrid);
            const optionMap = new Map(options.map((option) => [option.itemHrid, option]));
            if (selectedSourceItemHrid && !optionMap.has(selectedSourceItemHrid)) {
                optionMap.set(selectedSourceItemHrid, {
                    itemHrid: selectedSourceItemHrid,
                    itemName: getLocalizedItemName(
                        selectedSourceItemHrid,
                        state.itemDetailMap?.[selectedSourceItemHrid]?.name || selectedSourceItemHrid
                    ),
                });
            }
            for (const option of Array.from(optionMap.values())) {
                const optionNode = document.createElement("option");
                optionNode.value = option.itemHrid;
                optionNode.textContent = option.itemName;
                sourceSelect.appendChild(optionNode);
            }
            sourceSelect.value = selectedSourceItemHrid;
            sourceSelect.addEventListener("change", () => {
                setTimeCalculatorEssenceSourceItemHrid(config.essenceHrid, sourceSelect.value);
            });
            sourceRow.appendChild(sourceSelect);
            settingsPanelContent.appendChild(sourceRow);
        }

        const importButton = document.createElement("button");
        importButton.textContent = isZh ? "模拟器导入" : "Import Simulator";
        importButton.style.background = "#1770b3";
        importButton.style.color = "#ffffff";
        importButton.style.border = "none";
        importButton.style.borderRadius = "4px";
        importButton.style.padding = "4px 10px";
        importButton.style.cursor = "pointer";
        importButton.style.marginTop = "6px";
        importButton.textContent = isZh ? "\u6a21\u62df\u5668\u5bfc\u5165" : "Import Simulator";
        importButton.addEventListener("click", async () => {
            const ok = await importFromSimulatorSnapshot();
            if (!ok) {
                const debugCharacterName = state.lastSimulatorImportResult?.currentCharacterName || getCurrentCharacterName() || "";
                const simulatorCharacterNames = (state.lastSimulatorImportResult?.snapshotCharacterNames || []).join(", ");
                alert(isZh
                    ? `没有可导入的模拟器结果,或当前地下城/角色无法匹配。\n当前读取角色名:${debugCharacterName || "(空)"}\n模拟器角色列表:${simulatorCharacterNames || "(空)"}`
                    : `No simulator result available or current dungeon/character could not be matched.\nCurrent character name: ${debugCharacterName || "(empty)"}\nSimulator characters: ${simulatorCharacterNames || "(empty)"}`);
                return;
            }
            alert(isZh ? "模拟器数据已导入完成。" : "Simulator data imported.");
        });
        addSection.appendChild(importButton);

        const addControls = document.createElement("div");
        addControls.style.marginTop = "6px";
        addSection.appendChild(addControls);

        addControls.appendChild(createTimeCalculatorSearchControl(
            getTimeCalculatorItemOptions(),
            isZh ? "搜索宝箱或钥匙碎片..." : "Search chest or key fragment...",
            isZh ? "加入" : "Add",
            (itemHrid) => {
                if (state.timeCalculatorEntries.some((entry) => entry.itemHrid === itemHrid)) {
                    return;
                }
                state.timeCalculatorEntries.push({
                    id: `chest-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
                    itemHrid,
                    collapsed: false,
                    dungeonTier: REFINEMENT_CHEST_ITEM_HRIDS.includes(itemHrid) ? 1 : 0,
                    partyCount: 5,
                    runMinutes: 0,
                    quantityPer24h: 0,
                    foods: [],
                    drinks: [],
                });
                saveTimeCalculatorData();
                rerenderTimeCalculatorPanel();
            },
            {
                getDraftValue: () => state.timeCalculatorDrafts?.addItemQuery || "",
                setDraftValue: (value) => {
                    if (!state.timeCalculatorDrafts) {
                        state.timeCalculatorDrafts = { addItemQuery: "", consumableQueryByEntryId: {} };
                    }
                    state.timeCalculatorDrafts.addItemQuery = String(value || "");
                },
            }
        ));

        const addSearchInput = addControls.querySelector("input");
        if (addSearchInput) {
            addSearchInput.placeholder = isZh ? "\u641c\u7d22\u5b9d\u7bb1/\u7cbe\u70bc\u7bb1\u5b50/\u94a5\u5319\u788e\u7247..." : "Search chest or key fragment...";
        }
        const addSearchButton = addControls.querySelector("button");
        if (addSearchButton) {
            addSearchButton.textContent = isZh ? "\u52a0\u5165" : "Add";
        }

        const entriesHost = document.createElement("div");
        entriesHost.className = "ictime-timecalc-entries";
        entriesHost.style.display = "flex";
        entriesHost.style.flexDirection = "column";
        container.appendChild(entriesHost);
        for (const entry of state.timeCalculatorEntries) {
            entriesHost.appendChild(createTimeCalculatorEntryCard(entry));
        }
        enableTimeCalculatorPointerSort(entriesHost);
    }

    function ensureTimeCalculatorUI() {
        if (state.isShutDown || !state.itemDetailMap) {
            return;
        }
        const { tabsContainer, tabPanelsContainer } = getTimeCalculatorPanelRoots();
        if (!tabsContainer || !tabPanelsContainer) {
            return;
        }
        syncTimeCalculatorPanelHiddenState(tabPanelsContainer);

        if (!state.timeCalculatorTabButton || !state.timeCalculatorTabButton.isConnected) {
            const oldTabButtons = tabsContainer.querySelectorAll("button");
            if (oldTabButtons.length < 2) {
                return;
            }
            const tabButton = oldTabButtons[1].cloneNode(true);
            if (tabButton.children[0]) {
                tabButton.children[0].textContent = isZh ? "时间计算" : "Time Calc";
            } else {
                tabButton.textContent = isZh ? "时间计算" : "Time Calc";
            }
            if (tabButton.children[0]) {
                tabButton.children[0].textContent = isZh ? "\u65f6\u95f4\u8ba1\u7b97" : "Time Calc";
            } else {
                tabButton.textContent = isZh ? "\u65f6\u95f4\u8ba1\u7b97" : "Time Calc";
            }
            tabButton.dataset.ictimeTimeCalc = "button";
            oldTabButtons[0].parentElement.appendChild(tabButton);
            state.timeCalculatorTabButton = tabButton;
        }

        if (!state.timeCalculatorTabPanel || !state.timeCalculatorTabPanel.isConnected) {
            const oldTabPanels = tabPanelsContainer.querySelectorAll('[class*="TabPanel_tabPanel"]');
            if (oldTabPanels.length < 2) {
                return;
            }
            const tabPanel = oldTabPanels[1].cloneNode(false);
            tabPanel.dataset.ictimeTimeCalc = "panel";
            oldTabPanels[0].parentElement.appendChild(tabPanel);
            state.timeCalculatorTabPanel = tabPanel;

            const panel = document.createElement("div");
            panel.className = "ictime-timecalc-container";
            panel.style.padding = "6px";
            panel.style.color = "#ffffff";
            panel.addEventListener("focusout", () => {
                setTimeout(() => {
                    flushPendingTimeCalculatorRefresh();
                }, 0);
            }, true);
            tabPanel.appendChild(panel);
            state.timeCalculatorContainer = panel;

            const sourceButtons = Array.from(tabsContainer.querySelectorAll("button")).filter((button) => button !== state.timeCalculatorTabButton);
            const sourcePanels = Array.from(tabPanelsContainer.querySelectorAll('[class*="TabPanel_tabPanel"]')).filter((panelNode) => panelNode !== state.timeCalculatorTabPanel);
            for (const button of sourceButtons) {
                button.addEventListener("click", () => {
                    if (!state.timeCalculatorTabPanel || !state.timeCalculatorTabButton) {
                        return;
                    }
                    state.timeCalculatorTabPanel.hidden = true;
                    state.timeCalculatorTabPanel.classList.add("TabPanel_hidden__26UM3");
                    state.timeCalculatorTabButton.classList.remove("Mui-selected");
                    state.timeCalculatorTabButton.setAttribute("aria-selected", "false");
                    state.timeCalculatorTabButton.tabIndex = -1;
                    requestAnimationFrame(() => syncTimeCalculatorPanelHiddenState(tabPanelsContainer));
                }, true);
            }
            state.timeCalculatorTabButton.addEventListener("click", () => {
                sourceButtons.forEach((button) => {
                    button.classList.remove("Mui-selected");
                    button.setAttribute("aria-selected", "false");
                    button.tabIndex = -1;
                });
                sourcePanels.forEach((panelNode) => {
                    panelNode.hidden = true;
                    panelNode.classList.add("TabPanel_hidden__26UM3");
                });
                state.timeCalculatorTabButton.classList.add("Mui-selected");
                state.timeCalculatorTabButton.setAttribute("aria-selected", "true");
                state.timeCalculatorTabButton.tabIndex = 0;
                state.timeCalculatorTabPanel.classList.remove("TabPanel_hidden__26UM3");
                state.timeCalculatorTabPanel.hidden = false;
                syncTimeCalculatorPanelHiddenState(tabPanelsContainer);
            }, true);
        }

        renderTimeCalculatorPanel();
    }

    function queueTimeCalculatorRefresh() {
        if (state.isShutDown) {
            return;
        }
        if (shouldDeferTimeCalculatorRefresh()) {
            state.timeCalculatorRefreshPending = true;
            return;
        }
        if (state.timeCalculatorRefreshQueued) {
            return;
        }
        state.timeCalculatorRefreshQueued = true;
        requestAnimationFrame(() => {
            state.timeCalculatorRefreshQueued = false;
            if (shouldDeferTimeCalculatorRefresh()) {
                state.timeCalculatorRefreshPending = true;
                return;
            }
            state.timeCalculatorRefreshPending = false;
            ensureTimeCalculatorUI();
        });
    }

    function getConsumableValueDetail(itemHrid, itemSeconds) {
        const itemDetail = state.itemDetailMap?.[itemHrid];
        const consumable = itemDetail?.consumableDetail;
        if (!consumable) {
            return null;
        }
        const hp = Math.max(0, Number(consumable.hitpointRestore || 0));
        const mp = Math.max(0, Number(consumable.manapointRestore || 0));
        if (!hp && !mp) {
            return null;
        }
        const divisorSeconds = Number(itemSeconds || 0);
        if (!Number.isFinite(divisorSeconds) || divisorSeconds <= 0) {
            return null;
        }

        const buildValueParts = (seconds) => {
            if (!Number.isFinite(seconds) || seconds <= 0) {
                return [];
            }
            const parts = [];
            if (hp > 0) {
                parts.push(isZh ? `回血性价比${formatNumber(hp / seconds)}` : `hp/value ${formatNumber(hp / seconds)}`);
            }
            if (mp > 0) {
                parts.push(isZh ? `回蓝性价比${formatNumber(mp / seconds)}` : `mp/value ${formatNumber(mp / seconds)}`);
            }
            return parts;
        };

        const baseParts = buildValueParts(divisorSeconds);
        if (!baseParts.length) {
            return null;
        }

        const savings = getConsumableAttachedRareTimeSavings(itemHrid);
        let adjustedText = "";
        const adjustedSeconds = divisorSeconds - Number(savings.totalSeconds || 0);
        if (Number.isFinite(adjustedSeconds) && adjustedSeconds > 0 && Number(savings.totalSeconds || 0) > 0) {
            const adjustedParts = buildValueParts(adjustedSeconds);
            if (adjustedParts.length) {
                adjustedText = isZh
                    ? `扣附带油线时间后:${adjustedParts.join(" | ")}`
                    : `After oil/thread deduction: ${adjustedParts.join(" | ")}`;
            }
        }

        return {
            baseText: baseParts.join(" | "),
            adjustedText,
        };
    }

    function getConsumableValueDetailNormalized(itemHrid, itemSeconds) {
        const itemDetail = state.itemDetailMap?.[itemHrid];
        const consumable = itemDetail?.consumableDetail;
        if (!consumable) {
            return null;
        }
        const hp = Math.max(0, Number(consumable.hitpointRestore || 0));
        const mp = Math.max(0, Number(consumable.manapointRestore || 0));
        if (!hp && !mp) {
            return null;
        }
        const divisorSeconds = Number(itemSeconds || 0);
        if (!Number.isFinite(divisorSeconds) || divisorSeconds <= 0) {
            return null;
        }

        const buildValueParts = (seconds) => {
            if (!Number.isFinite(seconds) || seconds <= 0) {
                return [];
            }
            const parts = [];
            if (hp > 0) {
                parts.push(isZh ? `\u56de\u8840\u6027\u4ef7\u6bd4${formatNumber(hp / seconds)}` : `hp/value ${formatNumber(hp / seconds)}`);
            }
            if (mp > 0) {
                parts.push(isZh ? `\u56de\u84dd\u6027\u4ef7\u6bd4${formatNumber(mp / seconds)}` : `mp/value ${formatNumber(mp / seconds)}`);
            }
            return parts;
        };

        const baseParts = buildValueParts(divisorSeconds);
        if (!baseParts.length) {
            return null;
        }

        const savings = getConsumableAttachedRareTimeSavings(itemHrid);
        let adjustedText = "";
        const adjustedSeconds = divisorSeconds - Number(savings.totalSeconds || 0);
        if (Number.isFinite(adjustedSeconds) && adjustedSeconds > 0 && Number(savings.totalSeconds || 0) > 0) {
            const adjustedParts = buildValueParts(adjustedSeconds);
            if (adjustedParts.length) {
                adjustedText = isZh
                    ? `\u6263\u9644\u5e26\u6cb9\u7ebf\u65f6\u95f4\u540e\uff1a${adjustedParts.join(" | ")}`
                    : `After oil/thread deduction: ${adjustedParts.join(" | ")}`;
            }
        }

        return {
            baseText: baseParts.join(" | "),
            adjustedText,
        };
    }

    function normalizeScrollDurationSeconds(value) {
        const numericValue = Number(value || 0);
        if (!Number.isFinite(numericValue) || numericValue <= 0) {
            return 0;
        }
        if (numericValue >= 1e10) {
            return numericValue / 1e9;
        }
        if (numericValue >= 1e6) {
            return numericValue / 1000;
        }
        return numericValue;
    }

    function tryExtractDurationSecondsFromText(text) {
        const rawText = String(text || "").trim();
        if (!rawText) {
            return 0;
        }
        let match = rawText.match(/(\d+(?:\.\d+)?)\s*(?:h|hr|hrs|hour|hours|\u5c0f\u65f6)/i);
        if (match) {
            return Number(match[1]) * 3600;
        }
        match = rawText.match(/(\d+(?:\.\d+)?)\s*(?:m|min|mins|minute|minutes|\u5206\u949f)/i);
        if (match) {
            return Number(match[1]) * 60;
        }
        return 0;
    }

    function isSkillingScrollItem(itemDetailOrHrid) {
        const itemDetail = itemDetailOrHrid && typeof itemDetailOrHrid === "object"
            ? itemDetailOrHrid
            : state.itemDetailMap?.[itemDetailOrHrid];
        const hrid = String(itemDetail?.hrid || itemDetailOrHrid || "");
        if (!hrid) {
            return false;
        }
        return hrid.includes("_scroll") ||
            hrid.startsWith("/items/seal_of_") ||
            itemDetail?.categoryHrid === "/item_categories/scroll" ||
            Boolean(itemDetail?.scrollDetail?.personalBuffTypeHrid);
    }

    function getSkillingScrollValueConfig(itemDetailOrHrid) {
        const itemDetail = itemDetailOrHrid && typeof itemDetailOrHrid === "object"
            ? itemDetailOrHrid
            : state.itemDetailMap?.[itemDetailOrHrid];
        const hrid = String(itemDetail?.hrid || itemDetailOrHrid || "");
        if (!hrid) {
            return null;
        }
        const config = SKILLING_SCROLL_VALUE_CONFIGS[hrid];
        return config ? { ...config, itemHrid: hrid } : null;
    }

    function getSkillingScrollDurationSeconds(itemDetail) {
        if (!itemDetail) {
            return 0;
        }
        if (!itemDetail?.consumableDetail) {
            return isSkillingScrollItem(itemDetail) ? SKILLING_SCROLL_DEFAULT_DURATION_SECONDS : 0;
        }
        const consumable = itemDetail.consumableDetail;
        const durationCandidates = [
            consumable.duration,
            consumable.durationSeconds,
            consumable.effectDuration,
            consumable.effectDurationSeconds,
            consumable.buffDuration,
            consumable.buffDurationSeconds,
            consumable.activeDuration,
            consumable.activeDurationSeconds,
        ];
        for (const buff of consumable.buffs || []) {
            durationCandidates.push(
                buff?.duration,
                buff?.durationSeconds,
                buff?.effectDuration,
                buff?.effectDurationSeconds,
                buff?.buffDuration,
                buff?.buffDurationSeconds
            );
        }
        for (const candidate of durationCandidates) {
            const seconds = normalizeScrollDurationSeconds(candidate);
            if (seconds > 0) {
                return seconds;
            }
        }

        const textCandidates = [
            itemDetail.name,
            itemDetail.itemName,
            itemDetail.description,
            itemDetail.itemDescription,
            itemDetail.consumableDetail?.description,
        ];
        for (const candidate of textCandidates) {
            const seconds = tryExtractDurationSecondsFromText(candidate);
            if (seconds > 0) {
                return seconds;
            }
        }

        const cooldownSeconds = normalizeScrollDurationSeconds(consumable.cooldownDuration);
        if (cooldownSeconds > 0) {
            return cooldownSeconds;
        }
        return isSkillingScrollItem(itemDetail) ? SKILLING_SCROLL_DEFAULT_DURATION_SECONDS : 0;
    }

    function isSkillingScrollBuffRelevantToHolyMilk(buffTypeHrid) {
        if (!buffTypeHrid || typeof buffTypeHrid !== "string") {
            return false;
        }
        return buffTypeHrid === "/buff_types/gathering" ||
            buffTypeHrid === "/buff_types/efficiency" ||
            buffTypeHrid === "/buff_types/action_speed" ||
            buffTypeHrid === "/buff_types/action_level" ||
            buffTypeHrid === "/buff_types/milking_level";
    }

    function getSkillingScrollBuffs(itemDetail) {
        if (!isSkillingScrollItem(itemDetail)) {
            return [];
        }
        const config = getSkillingScrollValueConfig(itemDetail);
        if (config?.buff?.typeHrid) {
            return [{
                ...config.buff,
                flatBoost: Number(config.buff.flatBoost || 0),
            }];
        }
        if (Array.isArray(itemDetail?.consumableDetail?.buffs) && itemDetail.consumableDetail.buffs.length) {
            return itemDetail.consumableDetail.buffs
                .filter((buff) => isSkillingScrollBuffRelevantToHolyMilk(buff?.typeHrid))
                .map((buff) => ({
                    ...buff,
                    flatBoost: Number(buff?.flatBoost || 0),
                }));
        }
        return [];
    }

    function withTemporaryActionBuffs(actionTypeHrid, extraBuffs, computeFn) {
        if (!Array.isArray(extraBuffs) || !extraBuffs.length || typeof computeFn !== "function") {
            return computeFn();
        }
        if (!state.communityActionTypeBuffsDict) {
            state.communityActionTypeBuffsDict = {};
        }
        const originalBuffs = Array.isArray(state.communityActionTypeBuffsDict[actionTypeHrid])
            ? state.communityActionTypeBuffsDict[actionTypeHrid]
            : [];
        state.communityActionTypeBuffsDict[actionTypeHrid] = [
            ...originalBuffs,
            ...extraBuffs.map((buff) => ({ ...buff })),
        ];
        clearCaches();
        try {
            return computeFn();
        } finally {
            if (originalBuffs.length > 0) {
                state.communityActionTypeBuffsDict[actionTypeHrid] = originalBuffs;
            } else {
                delete state.communityActionTypeBuffsDict[actionTypeHrid];
            }
            clearCaches();
        }
    }

    function buildSkillingScrollSavingsResult(config, durationSeconds, baseRate, buffedRate) {
        const baseSecondsPerItem = calculateItemSeconds(config.baseItemHrid);
        if (!Number.isFinite(baseSecondsPerItem) || baseSecondsPerItem <= 0 || !Number.isFinite(durationSeconds) || durationSeconds <= 0) {
            return null;
        }
        const baseItemName = ATTACHED_RARE_TARGET_ITEM_HRID_SET.has(config.baseItemHrid)
            ? getAttachedRareLabel(config.baseItemHrid)
            : getLocalizedItemName(
                config.baseItemHrid,
                state.itemDetailMap?.[config.baseItemHrid]?.name || config.baseItemHrid
            );
        const extraItemCount = Math.max(0, durationSeconds * (Math.max(0, Number(buffedRate || 0)) - Math.max(0, Number(baseRate || 0))));
        const savedSeconds = Math.max(0, extraItemCount * baseSecondsPerItem);
        return {
            itemHrid: config.itemHrid,
            durationSeconds,
            baseItemHrid: config.baseItemHrid,
            baseItemName,
            baseSecondsPerItem,
            buffedSecondsPerItem: Number(buffedRate || 0) > 0 ? (1 / Number(buffedRate || 0)) : 0,
            extraItemCount,
            savedSeconds,
        };
    }

    function getRateBasedSkillingScrollTimeSavings(config, durationSeconds, extraBuffs) {
        const baseSecondsPerItem = calculateItemSeconds(config.baseItemHrid);
        if (!Number.isFinite(baseSecondsPerItem) || baseSecondsPerItem <= 0) {
            return null;
        }
        const buffedSecondsPerItem = withTemporaryActionBuffs(config.baseActionTypeHrid, extraBuffs, () =>
            calculateItemSeconds(config.baseItemHrid)
        );
        if (!Number.isFinite(buffedSecondsPerItem) || buffedSecondsPerItem <= 0) {
            return null;
        }
        const result = buildSkillingScrollSavingsResult(config, durationSeconds, 1 / baseSecondsPerItem, 1 / buffedSecondsPerItem);
        if (!result) {
            return null;
        }
        result.buffedSecondsPerItem = buffedSecondsPerItem;
        return result;
    }

    function getProcessingScrollTimeSavings(config, durationSeconds, extraBuffs) {
        const sourceAction = state.actionDetailMap?.[config.sourceActionHrid];
        if (!sourceAction) {
            return null;
        }
        const baseSecondsPerProcessedItem = calculateItemSeconds(config.baseItemHrid);
        const baseSecondsPerSourceItem = calculateItemSeconds(config.sourceItemHrid);
        if (!Number.isFinite(baseSecondsPerProcessedItem) || baseSecondsPerProcessedItem <= 0) {
            return null;
        }
        if (!Number.isFinite(baseSecondsPerSourceItem) || baseSecondsPerSourceItem <= 0) {
            return null;
        }
        const getRates = () => {
            const summary = getActionSummary(sourceAction);
            if (!Number.isFinite(summary?.seconds) || summary.seconds <= 0) {
                return null;
            }
            const processedCount = getEffectiveOutputCountPerAction(sourceAction, config.baseItemHrid, summary);
            const sourceCount = getEffectiveOutputCountPerAction(sourceAction, config.sourceItemHrid, summary);
            return {
                processedRate: Number.isFinite(processedCount) && processedCount > 0 ? processedCount / summary.seconds : 0,
                sourceRate: Number.isFinite(sourceCount) && sourceCount > 0 ? sourceCount / summary.seconds : 0,
            };
        };
        const baseRates = getRates();
        const buffedRates = withTemporaryActionBuffs(config.baseActionTypeHrid, extraBuffs, getRates);
        if (!baseRates || !buffedRates) {
            return null;
        }

        const extraProcessedRate = Math.max(0, Number(buffedRates.processedRate || 0) - Number(baseRates.processedRate || 0));
        const lostSourceRate = Math.max(0, Number(baseRates.sourceRate || 0) - Number(buffedRates.sourceRate || 0));
        const savedSeconds = Math.max(
            0,
            durationSeconds * (
                extraProcessedRate * baseSecondsPerProcessedItem -
                lostSourceRate * baseSecondsPerSourceItem
            )
        );
        const baseItemName = getLocalizedItemName(
            config.baseItemHrid,
            state.itemDetailMap?.[config.baseItemHrid]?.name || config.baseItemHrid
        );
        return {
            itemHrid: config.itemHrid,
            durationSeconds,
            baseItemHrid: config.baseItemHrid,
            baseItemName,
            baseSecondsPerItem: baseSecondsPerProcessedItem,
            buffedSecondsPerItem: 0,
            extraItemCount: savedSeconds / baseSecondsPerProcessedItem,
            savedSeconds,
        };
    }

    function getRareFindScrollTimeSavings(config, durationSeconds, extraBuffs) {
        const sourceAction = state.actionDetailMap?.[config.sourceActionHrid];
        if (!sourceAction) {
            return null;
        }
        const getRate = () => {
            const summary = getActionSummary(sourceAction);
            if (!Number.isFinite(summary?.seconds) || summary.seconds <= 0) {
                return 0;
            }
            const sourceItemsPerSecond = getEffectiveOutputCountPerAction(sourceAction, config.sourceItemHrid, summary) / summary.seconds;
            const attachedRarePerItem = getAttachedRareYieldPerItem(config.sourceItemHrid, config.baseItemHrid);
            return Math.max(0, Number(sourceItemsPerSecond || 0)) * Math.max(0, Number(attachedRarePerItem || 0));
        };
        const baseRate = getRate();
        const buffedRate = withTemporaryActionBuffs(config.baseActionTypeHrid, extraBuffs, getRate);
        return buildSkillingScrollSavingsResult(config, durationSeconds, baseRate, buffedRate);
    }

    function getSkillingScrollTimeSavings(itemHrid) {
        if (!itemHrid) {
            return null;
        }
        if (state.skillingScrollTimeSavingsCache.has(itemHrid)) {
            return state.skillingScrollTimeSavingsCache.get(itemHrid);
        }
        const itemDetail = state.itemDetailMap?.[itemHrid];
        const config = getSkillingScrollValueConfig(itemDetail || itemHrid);
        const extraBuffs = getSkillingScrollBuffs(itemDetail);
        if (!isSkillingScrollItem(itemDetail) || !config || !extraBuffs.length) {
            state.skillingScrollTimeSavingsCache.set(itemHrid, null);
            return null;
        }

        const durationSeconds = getSkillingScrollDurationSeconds(itemDetail);
        if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
            state.skillingScrollTimeSavingsCache.set(itemHrid, null);
            return null;
        }
        let result = null;
        if (config.mode === "processing") {
            result = getProcessingScrollTimeSavings(config, durationSeconds, extraBuffs);
        } else if (config.mode === "rare_find") {
            result = getRareFindScrollTimeSavings(config, durationSeconds, extraBuffs);
        } else {
            result = getRateBasedSkillingScrollTimeSavings(config, durationSeconds, extraBuffs);
        }
        state.skillingScrollTimeSavingsCache.set(itemHrid, result);
        return result;
    }

    function getSkillingScrollTooltipText(itemHrid) {
        const savings = getSkillingScrollTimeSavings(itemHrid);
        if (!savings) {
            return "";
        }
        const prefix = isZh ? `\u4ee5${savings.baseItemName}\u4e3a\u57fa\u51c6` : `Based on ${savings.baseItemName}`;
        const durationText = formatAutoDuration(savings.durationSeconds);
        const savedText = formatAutoDuration(savings.savedSeconds);
        const extraItemText = formatNumber(savings.extraItemCount);
        const line1 = isZh
            ? `${prefix}\uff1a${durationText}\u5377\u8f74\u7701\u65f6${savedText}`
            : `${prefix}: save ${savedText} over ${durationText}`;
        const line2 = isZh
            ? `\u7b49\u6548\u591a\u4ea7${extraItemText}\u4e2a${savings.baseItemName}`
            : `Equivalent extra ${extraItemText} ${savings.baseItemName}`;
        return `${line1}\n${line2}`;
    }

    function getTooltipRenderData(itemHrid, enhancementLevel = 0) {
        if (!itemHrid) {
            return null;
        }
        const cacheKey = `${itemHrid}#${Math.max(0, Number(enhancementLevel || 0))}`;
        if (state.itemTooltipDataCache.has(cacheKey)) {
            return state.itemTooltipDataCache.get(cacheKey);
        }
        if (enhancementLevel > 0) {
            const recommendation = getEnhancingRecommendationForItem(itemHrid, enhancementLevel);
            if (!recommendation) {
                const failureReason = state.itemFailureReasonCache.get(itemHrid) || (isZh ? "强化信息无法计算" : "Enhancing data unavailable");
                const data = {
                    itemHrid,
                    enhancementLevel,
                    unavailable: true,
                    failureReason,
                    isEnhancedEquipment: true,
                };
                state.itemTooltipDataCache.set(cacheKey, data);
                return data;
            }
            const protectText = recommendation.recommendProtectAt > 0
                ? `${isZh ? "推荐保护等级" : "Recommended protect"}:${recommendation.recommendProtectAt}${isZh ? "级" : ""}`
                : `${isZh ? "推荐保护等级" : "Recommended protect"}:${isZh ? "无需" : "None"}`;
            const totalText = `${isZh ? "总时间消耗" : "Total time"}:${formatAutoDuration(recommendation.totalSeconds || 0)}`;
            const essenceInfo = getEnhancedEquipmentEssenceInfo(itemHrid, enhancementLevel, recommendation);
            const decompositionCatalystInfo = getEnhancedEquipmentEssenceInfo(
                itemHrid,
                enhancementLevel,
                recommendation,
                "/items/catalyst_of_decomposition"
            );
            const primeCatalystInfo = getEnhancedEquipmentEssenceInfo(
                itemHrid,
                enhancementLevel,
                recommendation,
                "/items/prime_catalyst"
            );
            const essenceText = Number.isFinite(essenceInfo?.secondsPerEssence) && essenceInfo.secondsPerEssence > 0
                ? `${isZh ? "强化精华时间" : "Enhancing essence time"}:${formatAutoDuration(essenceInfo.secondsPerEssence)}`
                : "";
            const extraParts = [];
            if (Number.isFinite(essenceInfo?.essenceOutputCount) && essenceInfo.essenceOutputCount > 0) {
                extraParts.push(
                    `${isZh ? "分解精华数量" : "Essence count"}:${formatNumber(essenceInfo.essenceOutputCount)}`
                );
            }
            if (Number.isFinite(decompositionCatalystInfo?.secondsPerEssence) && decompositionCatalystInfo.secondsPerEssence > 0) {
                extraParts.push(
                    `${isZh ? "分解催化剂强化精华时间" : "Decomp catalyst essence time"}:${formatAutoDuration(decompositionCatalystInfo.secondsPerEssence)}`
                );
            }
            if (Number.isFinite(primeCatalystInfo?.secondsPerEssence) && primeCatalystInfo.secondsPerEssence > 0) {
                extraParts.push(
                    `${isZh ? "至高催化剂强化精华时间" : "Prime catalyst essence time"}:${formatAutoDuration(primeCatalystInfo.secondsPerEssence)}`
                );
            }
            const data = {
                itemHrid,
                enhancementLevel,
                isEnhancedEquipment: true,
                seconds: recommendation.totalSeconds || 0,
                detailText: protectText,
                attachedRareText: "",
                loadoutText: totalText,
                consumableText: essenceText,
                consumableAdjustedText: "",
                scrollText: "",
                extraText: extraParts.join(" | "),
                isEssence: false,
            };
            state.itemTooltipDataCache.set(cacheKey, data);
            return data;
        }
        const fixedAttachedRareTooltipPlan = getFixedAttachedRareTooltipPlan(itemHrid);
        if (fixedAttachedRareTooltipPlan) {
            const consumableDetail = getConsumableValueDetailNormalized(itemHrid, fixedAttachedRareTooltipPlan.totalSeconds);
            const data = {
                itemHrid,
                seconds: fixedAttachedRareTooltipPlan.totalSeconds,
                detailText: getItemCalculationDetail(itemHrid),
                attachedRareText: "",
                loadoutText: getItemLoadoutDetail(itemHrid),
                consumableText: consumableDetail?.baseText || "",
                consumableAdjustedText: consumableDetail?.adjustedText || "",
                scrollText: "",
                isEssence: false,
            };
            state.itemTooltipDataCache.set(cacheKey, data);
            return data;
        }
        const skillingScrollText = getSkillingScrollTooltipText(itemHrid);
        const seconds = calculateItemSeconds(itemHrid);
        if (seconds == null || !Number.isFinite(seconds) || seconds <= 0) {
            let data = null;
            if (skillingScrollText) {
                data = {
                    itemHrid,
                    isSkillingScroll: true,
                    detailText: "",
                    attachedRareText: "",
                    loadoutText: "",
                    consumableText: "",
                    consumableAdjustedText: "",
                    scrollText: skillingScrollText,
                    isEssence: false,
                };
            } else {
                const failureReason = state.itemFailureReasonCache.get(itemHrid) || "";
                data = failureReason ? {
                    itemHrid,
                    unavailable: true,
                    failureReason,
                } : null;
            }
            state.itemTooltipDataCache.set(cacheKey, data);
            return data;
        }
        const consumableDetail = getConsumableValueDetailNormalized(itemHrid, seconds);
        const data = {
            itemHrid,
            seconds,
            detailText: getItemCalculationDetail(itemHrid),
            attachedRareText: getAttachedRareTooltipLines(itemHrid).join("\n"),
            loadoutText: getItemLoadoutDetail(itemHrid),
            consumableText: consumableDetail?.baseText || "",
            consumableAdjustedText: consumableDetail?.adjustedText || "",
            scrollText: skillingScrollText,
            isSkillingScroll: Boolean(skillingScrollText),
            isEssence: Boolean(getFixedEnhancedEssencePlan(itemHrid) || getEssenceDecomposePlan(itemHrid)),
        };
        state.itemTooltipDataCache.set(cacheKey, data);
        return data;
    }

    function ensureTooltipLabel(contentContainer, className, fontSize) {
        let label = contentContainer.querySelector(`.${className}`);
        if (!label) {
            label = document.createElement("div");
            label.className = className;
            label.dataset.ictimeOwner = instanceId;
            label.style.color = "#000000";
            label.style.fontSize = fontSize;
            label.style.lineHeight = "1.2";
            label.style.marginTop = "2px";
            contentContainer.appendChild(label);
        }
        label.dataset.ictimeOwner = instanceId;
        return label;
    }

    function decorateTooltip(tooltip) {
        runUiGuarded("decorateTooltip", () => {
            if (state.isShutDown || !tooltip?.isConnected) {
                return;
            }
            const contentContainer = getTooltipContentContainer(tooltip);
            const anchor = tooltip.querySelector('a[href*="#"]');
            const hasItemName = tooltip.querySelectorAll("div.ItemTooltipText_name__2JAHA span").length > 0;
            if (!contentContainer && !anchor && !hasItemName) {
                return;
            }
            if (isMissingDerivedRuntimeState()) {
                ensureRuntimeStateFresh();
            }
            if (!state.actionDetailMap || !state.itemDetailMap) {
                return;
            }

            const itemHrid = getItemHridFromTooltip(tooltip);
            if (!itemHrid) {
                return;
            }
            const enhancementLevel = getItemEnhancementLevelFromTooltip(tooltip);
            tooltip.dataset.ictimeItemHrid = itemHrid;
            tooltip.dataset.ictimeVersion = window.__ICTIME_VERSION__ || "";
            if (!contentContainer) {
                return;
            }
            const renderData = getTooltipRenderData(itemHrid, enhancementLevel);
            if (!renderData) {
                return;
            }
            if (renderData.unavailable) {
                const label = ensureTooltipLabel(contentContainer, "ictime-label", "0.75rem");
                label.textContent = renderData.isEnhancedEquipment
                    ? (isZh ? "ICTime: 强化信息不可用" : "ICTime: Enhancing unavailable")
                    : (isZh ? "ICTime: 已截断" : "ICTime: Truncated");
                const detailLabel = ensureTooltipLabel(contentContainer, "ictime-detail", "0.72rem");
                detailLabel.textContent = renderData.failureReason;
                const attachedRareLabel = ensureTooltipLabel(contentContainer, "ictime-attached-rare", "0.72rem");
                attachedRareLabel.textContent = "";
                attachedRareLabel.style.display = "none";
                const loadoutLabel = ensureTooltipLabel(contentContainer, "ictime-loadout", "0.72rem");
                loadoutLabel.textContent = "";
                loadoutLabel.style.display = "none";
                const consumableLabel = ensureTooltipLabel(contentContainer, "ictime-consumable", "0.72rem");
                consumableLabel.textContent = "";
                consumableLabel.style.display = "none";
                const consumableAdjustedLabel = ensureTooltipLabel(contentContainer, "ictime-consumable-adjusted", "0.72rem");
                consumableAdjustedLabel.textContent = "";
                consumableAdjustedLabel.style.display = "none";
                const scrollLabel = ensureTooltipLabel(contentContainer, "ictime-scroll", "0.72rem");
                scrollLabel.textContent = "";
                scrollLabel.style.display = "none";
                const extraLabel = ensureTooltipLabel(contentContainer, "ictime-extra", "0.72rem");
                extraLabel.textContent = "";
                extraLabel.style.display = "none";
                return;
            }
            state.lastTooltipRender = {
                itemHrid,
                enhancementLevel,
                seconds: renderData.seconds,
                detailText: renderData.detailText,
                attachedRareText: renderData.attachedRareText,
                loadoutText: renderData.loadoutText,
                consumableText: renderData.consumableText,
                consumableAdjustedText: renderData.consumableAdjustedText,
                scrollText: renderData.scrollText,
                extraText: renderData.extraText,
                renderedAt: Date.now(),
                tooltipTextBefore: tooltip.innerText || "",
            };
            const compactMode = isTimeCalculatorCompactModeEnabled();

            const label = ensureTooltipLabel(contentContainer, "ictime-label", "0.75rem");
            label.textContent = renderData.isEnhancedEquipment
                ? (isZh ? `ICTime: 强化+${enhancementLevel}` : `ICTime: Enhance +${enhancementLevel}`)
                : renderData.isEssence
                    ? `Time: ${formatAutoDuration(renderData.seconds)} | Time500: ${formatAutoDuration(renderData.seconds * 500)}`
                    : `Time: ${formatAutoDuration(renderData.seconds)}`;

            if (renderData.isSkillingScroll) {
                label.textContent = isZh ? "ICTime: \u5377\u8f74\u4ef7\u503c" : "ICTime: Scroll value";
            }

            const detailLabel = ensureTooltipLabel(contentContainer, "ictime-detail", "0.72rem");
            detailLabel.textContent = renderData.isEnhancedEquipment
                ? (renderData.detailText || "")
                : (renderData.detailText || (isZh ? "战斗/其他来源暂未纳入计算" : "Combat/other sources not included"));

            if (renderData.isSkillingScroll) {
                detailLabel.textContent = "";
            }
            if (compactMode) {
                detailLabel.textContent = "";
            }
            detailLabel.style.display = detailLabel.textContent ? "" : "none";

            const attachedRareLabel = ensureTooltipLabel(contentContainer, "ictime-attached-rare", "0.72rem");
            attachedRareLabel.style.whiteSpace = "pre-line";
            attachedRareLabel.textContent = renderData.attachedRareText || "";
            attachedRareLabel.style.display = renderData.attachedRareText ? "" : "none";

            const loadoutLabel = ensureTooltipLabel(contentContainer, "ictime-loadout", "0.72rem");
            loadoutLabel.style.display = renderData.loadoutText ? "" : "none";
            loadoutLabel.textContent = renderData.loadoutText || "";

            const consumableLabel = ensureTooltipLabel(contentContainer, "ictime-consumable", "0.72rem");
            consumableLabel.textContent = renderData.consumableText || "";
            consumableLabel.style.display = renderData.consumableText ? "" : "none";

            if (compactMode && renderData.isEnhancedEquipment) {
                consumableLabel.textContent = "";
                consumableLabel.style.display = "none";
            }

            const consumableAdjustedLabel = ensureTooltipLabel(contentContainer, "ictime-consumable-adjusted", "0.72rem");
            consumableAdjustedLabel.textContent = renderData.consumableAdjustedText || "";
            consumableAdjustedLabel.style.display = renderData.consumableAdjustedText ? "" : "none";

            if (compactMode && renderData.isEnhancedEquipment) {
                consumableAdjustedLabel.textContent = "";
                consumableAdjustedLabel.style.display = "none";
            }

            const scrollLabel = ensureTooltipLabel(contentContainer, "ictime-scroll", "0.72rem");
            scrollLabel.style.whiteSpace = "pre-line";
            scrollLabel.textContent = renderData.scrollText || "";
            scrollLabel.style.display = renderData.scrollText ? "" : "none";

            const extraLabel = ensureTooltipLabel(contentContainer, "ictime-extra", "0.72rem");
            extraLabel.textContent = renderData.extraText || "";
            extraLabel.style.display = renderData.extraText ? "" : "none";
            if (compactMode) {
                extraLabel.textContent = "";
                extraLabel.style.display = "none";
            }
        });
    }

    function refreshOpenTooltips() {
        if (state.isShutDown || state.isRefreshingTooltips) {
            return;
        }
        state.isRefreshingTooltips = true;
        try {
            document.querySelectorAll(".MuiTooltip-popper").forEach((tooltip) => {
                runUiGuarded("refreshOpenTooltips", () => decorateTooltip(tooltip));
            });
        } finally {
            state.isRefreshingTooltips = false;
        }
    }

    function observeTooltips() {
        if (state.tooltipObserver) {
            state.tooltipObserver.disconnect();
        }
        const observer = new MutationObserver((mutations) => {
            if (state.isShutDown) {
                return;
            }
            for (const mutation of mutations) {
                for (const addedNode of mutation.addedNodes) {
                    if (!(addedNode instanceof HTMLElement)) {
                        continue;
                    }
                    if (addedNode.classList.contains("MuiTooltip-popper")) {
                        runUiGuarded("observeTooltips", () => decorateTooltip(addedNode));
                    }
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
        state.tooltipObserver = observer;
    }

    function shouldObserveTimeCalculatorRootNode(node) {
        if (!(node instanceof HTMLElement)) {
            return false;
        }
        const selector = '[class^="CharacterManagement_tabsComponentContainer"], [class*="TabsComponent_tabsContainer"], [class*="TabsComponent_tabPanelsContainer"]';
        if (node.matches?.(selector)) {
            return true;
        }
        return Boolean(node.querySelector?.(selector));
    }

    function observeTimeCalculatorUI() {
        if (state.timeCalculatorUiObserver) {
            state.timeCalculatorUiObserver.disconnect();
        }
        const observer = new MutationObserver((mutations) => {
            if (state.isShutDown || state.timeCalculatorTabButton?.isConnected) {
                return;
            }
            for (const mutation of mutations) {
                for (const addedNode of mutation.addedNodes) {
                    if (shouldObserveTimeCalculatorRootNode(addedNode)) {
                        queueTimeCalculatorRefresh();
                        return;
                    }
                }
            }
        });
        observer.observe(document.body, { childList: true, subtree: true });
        state.timeCalculatorUiObserver = observer;
    }

    function shutdownInstance() {
        state.isShutDown = true;
        state.tooltipObserver?.disconnect();
        state.tooltipObserver = null;
        state.timeCalculatorUiObserver?.disconnect();
        state.timeCalculatorUiObserver = null;
        if (state.tooltipRefreshTimer) {
            clearTimeout(state.tooltipRefreshTimer);
            state.tooltipRefreshTimer = 0;
        }
        state.alchemyInferenceObserver?.disconnect();
        state.alchemyInferenceObserver = null;
        state.alchemyObservedPanel = null;
        for (const timerId of state.alchemyInferenceDelayTimers) {
            clearTimeout(timerId);
        }
        state.alchemyInferenceDelayTimers = [];
        state.eventAbortController?.abort();
        state.eventAbortController = null;
        state.timeCalculatorTabButton?.remove();
        state.timeCalculatorTabButton = null;
        state.timeCalculatorTabPanel?.remove();
        state.timeCalculatorTabPanel = null;
        state.timeCalculatorContainer = null;
        document.querySelectorAll(".ictime-alchemy-inference, .ictime-alchemy-inference-row").forEach((node) => node.remove());
    }

    function startWhenReady() {
        if (!document.body) {
            requestAnimationFrame(startWhenReady);
            return;
        }
        if (!state.actionDetailMap || !state.itemDetailMap) {
            loadCachedClientData();
        }
        if (isMissingDerivedRuntimeState()) {
            hydrateFromReactState();
        }
        if (!state.eventAbortController) {
            state.eventAbortController = new AbortController();
            const enhancingEventHandler = (event) => {
                runUiGuarded("enhancingEventHandler", () => {
                    if (shouldRefreshEnhancingFromTarget(event.target)) {
                        queueEnhancingRefresh();
                    }
                    if (shouldRefreshAlchemyInferenceFromTarget(event.target)) {
                        queueAlchemyInferenceRefresh();
                        scheduleAlchemyInferenceRefreshBurst();
                    }
                });
            };
            document.addEventListener("mouseover", (event) => {
                runUiGuarded("trackHoveredItem", () => {
                    if (trackHoveredItem(event.target)) {
                        queueTooltipRefresh();
                    }
                });
            }, { capture: true, passive: true, signal: state.eventAbortController.signal });
            document.addEventListener("input", enhancingEventHandler, { capture: true, signal: state.eventAbortController.signal });
            document.addEventListener("change", enhancingEventHandler, { capture: true, signal: state.eventAbortController.signal });
            document.addEventListener("click", enhancingEventHandler, { capture: true, signal: state.eventAbortController.signal });
        }
        observeTooltips();
        observeTimeCalculatorUI();
        refreshOpenTooltips();
        queueEnhancingRefresh();
        queueAlchemyInferenceRefresh();
        queueTimeCalculatorRefresh();
    }

    window.__ICTIME_DEBUG__ = {
        state,
        instanceId,
        findActionForItem,
        getActionSummary,
        getDisplayOutputCountPerAction,
        getEffectiveOutputCountPerAction,
        calculateItemSeconds,
        hydrateFromReactState,
        resolveSkillingLoadout,
        clearCaches,
        shutdownInstance,
        getEssenceDecomposePlan,
        getFixedTransmutePlan,
        getFixedAttachedRareTooltipPlan,
        getFixedEnhancedEssencePlan,
        getAttachedRareYieldPerItem,
        getItemSecondsLinearRelationToTarget,
        getSkillingScrollTimeSavings,
        getCurrentAlchemyTransmuteInference,
        renderAlchemyTransmuteInference,
        getEnhancingRecommendationForItem,
        getItemCalculationDetail,
        getItemLoadoutDetail,
        getEnhancingRecommendation,
        renderEnhancingRecommendation,
        renderTimeCalculatorPanel,
        getTimeCalculatorEntrySummary,
    };

    window.__ICTIME_CONTROLLER__ = {
        instanceId,
        shutdown: shutdownInstance,
    };

    hookWebSocket();
    loadCachedClientData();
    startWhenReady();
})();