importPackage(java.io);
importPackage(java.nio.charset);
importClass(Packages.mtr.mappings.Utilities);
importPackage(java.awt.image);
importClass(Packages.net.minecraft.server.MinecraftServer);
importPackage(Packages.net.minecraft.commands);
importPackage(java.net);
importClass(java.lang.System);
importClass(java.lang.Runtime);
importClass(java.net.URI);
importClass(java.net.URL);
importClass(java.io.BufferedReader);
importClass(java.io.InputStreamReader);
importClass(java.util.Locale);

// importClass(net.minecraft.class_156);
include(Resources.id("fangsu:scripts/gif_helper.js"));
include(Resources.id("fangsu:scripts/online_res_helper.js"));
include(Resources.id("fangsu:scripts/text_util.js"));
include(Resources.id("fangsu:scripts/route_helper.js"));

showMsgBox(null, [{ text: "确定 Confirm", function: () => MinecraftClient.setScreen(null) }], "安全警告 Security Warn", [
    "方速方块包安全提示",
    "本资源包使用以下Java库, 如您担心会产生安全隐患, 请您卸载方速方块包",
    "Security warn by FangSu Blocks",
    "Following Java libs are used, if you think there will be",
    "any security issues, please uninstall FangSu Blocks",
    "",
    "java.io java.nio java.lang java.net java.util java.awt org.lwjgl"
]);

/**
 * 在用“||”分割的字符串中，返回“||”之前的所有字符。
 * 如果源字符串不使用“||”分割，则返回该字符串。
 * @param {String} src 源字符串。
 * @returns {String}
 */
function getNonExtraParts(src) {
    if (src) return src.includes("||") ? TextUtil.getNonExtraParts(src) : src;
    else return "";
}

function getExtraParts(src) {
    return src.includes("||") ? TextUtil.getExtraParts(src) : src;
}

/**
 * 在用“|”分割的字符串中，获取其中的 CJK / 非 CJK 部分。
 * 如果源字符串不使用“|”分割，但使用“||”分割，则返回“||”之前的所有字符。
 * 如果源字符串既不使用“|”分割，也不使用“||”分割，则返回该字符串。
 * 如果源字符串多次使用“|”分割，则判断每个部分是否为 CJK 字符，并返回符合条件的所有部分。每个部分间用空格分割。
 * @param {String} src 源字符串。
 * @param {Boolean} isCjk 指定获取字符串中的 CJK 还是非 CJK 部分。
 * @returns {String}
 */
function getMatching(src, isCjk) {
    if (src) {
        src = String(src);
        if (!src.includes("|")) {
            return isCjk ? (TextUtil.isCjk(src) ? getNonExtraParts(src) : "") : TextUtil.isCjk(src) ? "" : getNonExtraParts(src);
        }
        return isCjk ? TextUtil.getCjkParts(src) : TextUtil.getNonCjkParts(src);
    }
    return "";
}

/**
 * 提取源字符串“||”之前的所有字符，然后将所有“|”替换成空格。
 * @param {String} name 源字符串。
 * @returns {String}
 */
function formatName(name) {
    return getNonExtraParts(name).replace("|", " ");
}

/**
 * 通过键名从列表中获取对应值
 * @param {Object} map - 类似Java的Map结构对象(需实现entrySet方法)
 * @param {*} key - 要查找的键值(使用宽松相等判断 == )
 * @returns {*|undefined} 返回第一个匹配键对应的值，未找到返回undefined
 */
function getMapValueByKey(map, key) {
    for (let entry of map.entrySet()) {
        if (entry.getKey() == key) {
            return entry.getValue();
        }
    }
}

/**
 * 通过索引位置从列表中获取对应值
 * @param {Object} map - 类似Java的Map结构对象(需实现entrySet方法)
 * @param {number} index - 要获取的索引位置(从0开始)
 * @returns {*} 返回对应索引位置的值
 * @throws {Error} 当索引超出范围时抛出异常
 */
function getMapValueByIndex(map, index) {
    let iterator = map.entrySet().iterator();

    for (let i = 0; i < index && iterator.hasNext(); i++) {
        iterator.next();
    }

    if (iterator.hasNext()) {
        return iterator.next().getValue();
    }

    throw new Error("Map does not contain " + index + " elements");
}

/**
 * 检查某个属性是否存在于对象中，并且属性的 JSON 字符串表示形式等于给定对象的 JSON 字符串表示形式。
 * @param {*} obj 要检查的属性所在的对象。
 * @param {*} propName 要检查的属性名称字符串。
 * @param {*} propValue 要检查的属性值。
 * @returns
 */
function checkJsonProperty(obj, propName, propValue) {
    return obj != null && propName in obj ? JSON.stringify(obj[propName]) == JSON.stringify(propValue) : false;
}

/**
 * 检查某个属性是否存在于对象中，并且属性值等于给定的值。
 * @param {*} obj 要检查的属性所在的对象。
 * @param {*} propName 要检查的属性名称字符串。
 * @param {*} propValue 要检查的属性值。
 * @returns
 */
function checkProperty(obj, propName, propValue) {
    return obj != null && propName in obj ? obj[propName] == propValue : false;
}

function clamp(number, min, max) {
    return Math.min(Math.max(number, min), max);
}

function warn(message) {
    MinecraftClient.displayMessage("§e§l" + message, false);
}

/**
 * 动态加载资源, 避免重复加载导致卡顿
 * @param {Object} res 需在函数外创建res对象
 * @param {String} type 资源类别("font" "image" "str")
 * @param {String} path 资源路径
 * @returns
 */
function loadRes(res, type, path) {
    return loadResource(type, path);
}
/**
 * 动态加载资源并缓存结果，避免重复加载导致的性能问题
 *
 * @param {string} type - 资源类型，支持以下值：
 *   - "font"：字体资源
 *   - "image" 或 "img"：静态图片
 *   - "model"：原始模型
 *   - "partedModel"：分块模型
 *   - "str"：文本内容
 *   - "json"：JSON 对象（自动合并多个资源文件）
 *   - "gif"：GIF 动画
 *   - "webimg" 或 "webImg"：网络图片
 *   - "webgif" 或 "webGif"：网络 GIF
 * @param {string} path - 资源路径标识符（如 "mtrsteamloco:imgnotfound.png"）
 *
 * @returns {*} 加载的资源对象，类型取决于资源类别：
 *   - "font"：{Font} 字体对象
 *   - "image" | "img" | "webimg" | "webImg"：{BufferedImage} 图片对象
 *   - "model"：{RawModel} 原始模型对象
 *   - "partedModel"：{Object} 分块模型容器（含 get 方法）
 *   - "str"：{string} 文本内容
 *   - "json"：{Object} 合并后的 JSON 对象
 *   - "gif" | "webgif" | "webGif"：{GifPlayer} GIF 播放器对象
 *
 * @throws {Error} 当遇到以下情况时抛出错误：
 *   - 传入未知的资源类型
 *   - 加载非 JSON 资源失败且无默认回退时
 *
 * @description
 * 1. 优先检查全局缓存(GlobalRegister)，存在则直接返回缓存结果
 * 2. 对网络资源("webimg", "webgif")使用 WebImageLoader 加载
 * 3. JSON 资源会合并所有同名资源文件属性（数组会连接，对象会合并）
 * 4. 加载失败时返回默认资源（如图片加载失败返回 404 图片）
 * 5. 所有成功加载的资源会自动存入 GlobalRegister 缓存
 */
function loadResource(type, path) {
    if (type == "webimg" || type == "webImg") {
        return WebImageLoader.load(path);
    }
    if (type == "webgif" || type == "webGif") {
        return WebImageLoader.load(path);
    }
    if (GlobalRegister.containsKey(path)) return GlobalRegister.get(path);
    let resL = null;
    try {
        if (type == "font") resL = Resources.readFont(Resources.id(path));
        else if (type == "systemFont") resL = Resources.getSystemFont(path);
        else if (type == "image" || type == "img") resL = Resources.readBufferedImage(Resources.id(path));
        else if (type == "model") resL = ModelManager.loadRawModel(Resources.manager(), Resources.id(path), null);
        else if (type == "partedModel") resL = ModelManager.loadPartedRawModel(Resources.manager(), Resources.id(path), null);
        else if (type == "str") resL = Resources.readString(Resources.id(path));
        else if (type == "json" || type == "JSON") {
            let resList = UtilitiesClient.getResources(Resources.manager(), Resources.id(path));
            let jsonList = [];
            resL = {};
            // print(resList);
            for (let res of resList) {
                let inputStream = Packages.mtr.mappings.Utilities.getInputStream(res);
                let inputStreamStr = inputStreamToString(inputStream);
                // print("[DEBUG] input: ", inputStreamStr);
                jsonList.push(JSON.parse(inputStreamStr));
                // print("[DEBUG] resL: ", JSON.stringify(resL));
                for (let prop in JSON.parse(inputStreamStr)) {
                    var propItem = JSON.parse(inputStreamStr)[prop];
                    // print("[DEBUG] type of propItem ", typeof (propItem));
                    // print("[DEBUG] type of resL ", Object.prototype.toString.call(resL[prop]));
                    if (resL[prop]) {
                        if (typeof resL[prop] !== typeof propItem) {
                            resL[prop] = propItem;
                        } else {
                            if (typeof resL[prop] == "object") {
                                if (Object.prototype.toString.call(resL[prop]) == "[object Array]") {
                                    resL[prop] = resL[prop].concat(propItem);
                                } else if (Object.prototype.toString.call(resL[prop]) === "[object Object]") {
                                    resL[prop] = Object.assign(resL[prop], propItem);
                                }
                            } else {
                                resL[prop] = propItem;
                            }
                        }
                    } else resL[prop] = propItem;
                }
            }
            // resL = deepMergeArray(jsonList);
            // print(JSON.stringify(resL))
        } else if (type == "gif") {
            resL = new GifPlayer(path);
        } else throw new Error("Unknown res type: " + String(type));
    } catch (e) {
        if (type == "font" || type == "systemFont") resL = Resources.getSystemFont("SansSerif");
        else if (type == "image" || type == "img") resL = Resources.readBufferedImage(Resources.id("mtrsteamloco:imgnotfound.png"));
        else if (type == "model") resL = loadResource("model", "fangsu:err/not_found.obj");
        else if (type == "partedModel") {
            resL = {};
            resL.get = (item_name) => {
                loadResource("model", "fangsu:err/unknown_model.obj");
            };
        } else if (type == "str") resL = "{}";
        else if (type == "json" || type == "JSON") resL = {};
        else if (type == "gif") {
            resL = new GifPlayer("mtrsteamloco:imgnotfound.gif");
        } else setErrorInfo("Error occured when loading " + String(path) + " " + String(type) + "\n" + e);
    }
    GlobalRegister.put(path, resL);
    return resL;
}

function getByPath(obj, path) {
    return path.split(".").reduce((o, key) => o[key], obj);
}

function parseObj(o) {
    if (!o) return { error: "o is empty" };
    let o1 = {};
    for (let ot of o) {
        o1[ot.id] = ot;
    }
    return o1;
}

function getPlatformById(id) {
    for (let plat of MTRClientData.PLATFORMS) {
        if (String(new java.lang.Long(plat.id)) === String(new java.lang.Long(id))) {
            return plat;
        }
    }
    return null;
}

function getRouteByPlatform(plat) {
    return MTRClientData.DATA_CACHE.requestPlatformIdToRoutes(new java.lang.Long(plat.id));
}

function getRouteWithCondition(con) {
    let routes = [];
    let routeList = MTRClientData.ROUTES;
    for (let c of con) {
        if (c.type == "platform" || c.type == "platformId") {
            let platform = c.val;
            for (let route of routeList) {
                // print(route.platformIds)
                for (let routePlatform of route.platformIds) {
                    // print(String(new java.lang.Long(routePlatform.platformId)))
                    if (new java.lang.Long(routePlatform.platformId) == new java.lang.Long(c.type == "platformId" ? c.val : platform.id)) {
                        routes.push(route);
                    }
                }
            }
        } else if (c.type == "name") {
            let targetName = c.name;
            for (let route of routeList) {
                // print(route.name)
                if (route.name == targetName) {
                    routes.push(route);
                }
            }
        }
        routeList = routes;
    }
    return routes;
}

function getRouteById(id) {
    for (let route of MTRClientData.ROUTES) {
        if (String(new java.lang.Long(route.id)) === String(new java.lang.Long(id))) {
            return route;
        }
    }
    return null;
}

function getStationById(id) {
    try {
        for (let stn of MTRClientData.STATIONS) {
            if (String(new java.lang.Long(stn.id)) === String(new java.lang.Long(id))) {
                return stn;
            }
        }
        return null;
    } catch (e) {
        return null;
    }
}

/**
 * 获取某个站台所在的车站
 * @param {Platform} plat 站台
 * @returns Station
 */
function getStationByPlatform(plat) {
    var posCentral = new Vector3f(plat.getMidPos());
    return MinecraftClient.getStationAt(posCentral);
}

function getNearestStation(posX, posZ) {
    let drawStations = [];
    let stations = MTRClientData.STATIONS;
    try {
        for (let station of stations) {
            // print("[DEBUG] platform:", plat);
            let stationPos = new Vector3f(station.getCenter());
            drawStations.push({
                text: station.name || "?",
                id: String(new java.lang.Long(station.id)),
                distance: Math.sqrt((Math.abs(posX - stationPos.x()) ^ 2) + (Math.abs(posZ - stationPos.z()) ^ 2))
            });
        }
        drawStations.sort(function (a, b) {
            return a.distance - b.distance;
        });
        return drawStations[0];
    } catch (e) {
        return null;
    }
}

function getPlatformByStation(station) {
    let platforms = MTRClientData.DATA_CACHE.requestStationIdToPlatforms(new java.lang.Long(station.id));
    return Array.from(platforms.values());
}

function getDestinationByRouteId(id) {
    let route = getRouteById(String(id));
    // print(String(id), "  ", (route))
    if (route == null) return "?";
    let destinationRoutePlatform = route.platformIds[route.platformIds.size() - 1];
    let destinationPlatform = getPlatformById(String(new java.lang.Long(destinationRoutePlatform.platformId)));
    let station = getStationByPlatform(destinationPlatform);
    // print(destinationPlatform);
    // print(destinationPlatform.getMidPos());
    // print(station);
    if (station) return station.name;
    else return "未命名|Undefinded";
}

function getDestinationByPlatform(plat) {
    if (!plat) return [];
    var destSet = new Set();
    getRouteWithCondition([{ type: "platform", val: plat }]).forEach((route) => {
        var platforms = route.platformIds;
        if (platforms.size() > 0) {
            var lastPlat = platforms.get(platforms.size() - 1);
            var station = getStationByPlatform(getPlatformById(String(new java.lang.Long(lastPlat.platformId))));
            if (station) {
                station.name && destSet.add("" + station.name); // 显式转为JS字符串
            } else destSet.add("未命名|Undefinded");
        }
    });

    return Array.from(destSet);
}

function getDestinationByPlatId(id) {
    var plat = getPlatformById(String(id));
    return getDestinationByPlatform(plat);
}

function findStation(station, stationList) {
    // print(String(station.name))
    for (let i = 0; i < stationList.length; i++) {
        let currentStation = stationList[i];
        // print(String(currentStation.stationName));
        if (station.name === currentStation.stationName) return i;
    }
    return -1;
}

/**
 * 将Java输入数据流转换为字符串
 * @param {java.io.InputStream|ByteArray} inputStream - 输入流或字节数组
 * @returns {string} 转换后的字符串
 */
function inputStreamToString(input) {
    var is = new java.io.DataInputStream(input); // 尝试将输入对象转换为 DataInputStream
    var baos = new java.io.ByteArrayOutputStream();
    var buffer = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 1024);
    try {
        var bytesRead;
        while ((bytesRead = is.read(buffer)) !== -1) {
            baos.write(buffer, 0, bytesRead);
        }
        return baos.toString("UTF-8");
    } finally {
        is.close(); // 确保流关闭
    }
}

/**
 * 深度合并对象数组为单个对象。
 * - 使用 JSON.parse/stringify 解析和克隆每个对象
 * - 如果存在时间戳字符串，则将其转换为 Date 对象
 * - 跳过没有 'id' 属性的对象
 * - 递归合并对象，连接数组
 *
 * @param {Array<Object>} arr - 要合并的对象数组
 * @returns {Object} 合并后的结果对象
 */
function deepMergeArray(arr) {
    let result = {};

    for (let item of arr) {
        let parsedItem;

        try {
            // 克隆对象以避免修改原始对象
            parsedItem = JSON.parse(JSON.stringify(item));

            // 如果存在时间戳字符串，则将其转换为 Date 对象
            if (parsedItem.timestamp) {
                parsedItem.timestamp = new Date(parsedItem.timestamp);
            }

            // 跳过没有 id 的项
            if (!parsedItem.id) {
                continue;
            }
        } catch (error) {
            continue;
        }

        /**
         * 将源对象递归合并到目标对象中
         * @param {Object} target - 要合并到的目标对象
         * @param {Object} source - 要从中合并的源对象
         * @returns {Object} 合并后的目标对象
         */
        function merge(target, source) {
            for (var key in source) {
                if (!Object.prototype.hasOwnProperty.call(source, key)) {
                    continue;
                }

                var sourceValue = source[key];
                var targetValue = target[key];

                if (sourceValue && typeof sourceValue === "object") {
                    target[key] = Array.isArray(sourceValue) ? (targetValue || []).concat(sourceValue) : merge(targetValue || {}, sourceValue);
                } else {
                    target[key] = sourceValue;
                }
            }
            return target;
        }

        result = merge(result, parsedItem);
    }

    return result;
}

function addPrefix(str, prefix, addSpace) {
    let orinCjk = getMatching(str, true);
    let orinNonCjk = getMatching(str, false);
    let finalCjk = orinCjk;
    let finalNonCjk = orinNonCjk;
    if (orinCjk != "") finalCjk = getMatching(prefix, true) + (addSpace ? " " : "") + orinCjk;
    if (orinNonCjk != "") finalNonCjk = getMatching(prefix, false) + (addSpace ? " " : " ") + orinNonCjk;
    if (finalCjk != "" && finalNonCjk != "") return finalCjk + "|" + finalNonCjk;
    if (finalCjk == "" && finalNonCjk != "") return finalNonCjk;
    if (finalCjk != "" && finalNonCjk == "") return finalCjk;
}

function addSuffix(target, suffix) {
    var targetHasCjk = hasCjkPart(target);
    var targetHasNonCjk = hasNonCjkPart(target);
    var suffixHasCjk = hasCjkPart(suffix);
    var suffixHasNonCjk = hasNonCjkPart(suffix);

    // 处理中文部分
    var cjkPart = getMatching(target, true);
    if (targetHasCjk && suffixHasCjk) {
        var cjkSuffix = getMatching(suffix, true);
        if (cjkPart.indexOf(cjkSuffix) === -1) {
            cjkPart += cjkSuffix;
        }
    }

    // 处理英文部分
    var nonCjkPart = getMatching(target, false);
    if (targetHasNonCjk && suffixHasNonCjk) {
        var nonCjkSuffix = getMatching(suffix, false);
        // 使用Java兼容的方式移除空格
        var cleanedTarget = String(nonCjkPart).replace(/\\s+/g, "");
        var cleanedSuffix = String(nonCjkSuffix).replace(/\\s+/g, "");

        // 检查英文部分是否已包含后缀（忽略大小写）
        if (cleanedTarget.toLowerCase().indexOf(cleanedSuffix.toLowerCase()) === -1) {
            nonCjkPart = nonCjkPart + " " + nonCjkSuffix;
        }
    }

    // 根据原始目标字符串格式组合结果
    if (targetHasCjk && targetHasNonCjk) {
        return cjkPart + "|" + nonCjkPart;
    } else if (targetHasCjk) {
        return cjkPart;
    } else if (targetHasNonCjk) {
        return nonCjkPart;
    }
    // 如果目标字符串为空，直接返回后缀
    return suffix;
}

importPackage(java.awt);
importPackage(java.awt.image);

function convertToGrayscaleDarkened(bufferedImage) {
    // 1. 创建灰度转换器
    let colorConvertOp = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);

    // 2. 应用灰度转换
    let grayImage = new BufferedImage(bufferedImage.getWidth(), bufferedImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
    colorConvertOp.filter(bufferedImage, grayImage);

    // 3. 进一步变暗（通过调整亮度）
    let darkenedImage = new BufferedImage(grayImage.getWidth(), grayImage.getHeight(), BufferedImage.TYPE_BYTE_GRAY);
    let g = darkenedImage.createGraphics();
    g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.7)); // 透明度降低 = 变暗
    g.drawImage(grayImage, 0, 0, null);
    g.dispose();

    return darkenedImage;
}

function drawWaterPrint(g, w, h) {
    g.setColor(rgbaToColor(166, 166, 188, 50));
    // g.setColor(Color.BLACK);
    let font = loadResource("font", "mtrsteamloco:fonts/ae.ttf");
    let versionJSON = loadResource("str", "fangsu:version.json");
    let versionObj = JSON.parse(versionJSON);
    let dispName = ComponentUtil.getString(ComponentUtil.translatable("cfg.version." + versionObj.ver)) + " " + versionObj.name;
    let currentY = h * 0.8;
    drawStrUnified(g, font, ComponentUtil.getString(ComponentUtil.translatable("cfg.version")), w * 0.97, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
    drawStrUnified(g, font, dispName, w * 0.97, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
    drawStrUnified(g, font, "方速方块包 FangSu Blocks", w * 0.97, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
    drawStrUnified(g, font, "代码 Code By 596wjr", w * 0.97, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
    drawStrUnified(g, font, "ANTE 版本 : " + Resources.getNTEVersionInt(), w * 0.97, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
    if (Resources.getNTEVersionInt() > versionObj.max_nte) {
        g.setColor(Color.RED);
        drawStrUnified(
            g,
            font,

            ComponentUtil.getString(ComponentUtil.translatable("cfg.version.ante_too_high", String(Resources.getNTEVersionInt()), String(versionObj.max_nte))),
            w * 0.97,
            currentY,
            h * 0.02,
            2,
            2
        );
        g.setColor(rgbaToColor(166, 166, 188, 50));
        currentY += h * 0.02;
    }
    if (Resources.getNTEVersionInt() < versionObj.min_nte) {
        g.setColor(Color.RED);
        drawStrUnified(
            g,
            font,

            ComponentUtil.getString(ComponentUtil.translatable("cfg.version.ante_too_low", String(Resources.getNTEVersionInt()), String(versionObj.min_nte))),
            w * 0.97,
            currentY,
            h * 0.02,
            2,
            2
        );
        g.setColor(rgbaToColor(166, 166, 188, 50));
        currentY += h * 0.02;
    }
    if (versionObj.ver == "release") {
        drawStrUnified(g, font, "前往 https://afdian.com/a/596wjr 支持我", w * 0.97, currentY, h * 0.02, 2, 2);
        currentY += h * 0.02;
        drawStrUnified(g, font, "Support me at https://afdian.com/a/596wjr", w * 0.97, currentY, h * 0.02, 2, 2);
        currentY += h * 0.02;
    }
    return;
}

function drawScreenInfoPrint(g, w, h, text) {
    g.setColor(rgbaToColor(166, 166, 188, 50));
    let font = loadResource("font", "mtrsteamloco:fonts/ae.ttf");
    let versionJSON = loadResource("str", "fangsu:version.json");
    let versionObj = JSON.parse(versionJSON);
    let currentY = h * 0.9;
    if (text) {
        let currentX = w * 0.1;

        if (typeof text == "object") {
            if (text.type == "info") {
                currentX += drawStrDL(g, font, font, "[", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(181, 206, 168, 50));
                currentX += drawStrDL(g, font, font, "INFO", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(166, 166, 188, 50));
                currentX += drawStrDL(g, font, font, "] ", currentX, currentY, h * 0.02, 2, 2);
            }
            if (text.type == "debug") {
                currentX += drawStrDL(g, font, font, "[", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(86, 156, 214, 50));
                currentX += drawStrDL(g, font, font, "DEBUG", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(166, 166, 188, 50));
                currentX += drawStrDL(g, font, font, "] ", currentX, currentY, h * 0.02, 2, 2);
            }
            if (text.type == "warn") {
                currentX += drawStrDL(g, font, font, "[", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(255, 170, 0, 50));
                currentX += drawStrDL(g, font, font, "WARN", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(166, 166, 188, 50));
                currentX += drawStrDL(g, font, font, "] ", currentX, currentY, h * 0.02, 2, 2);
            }
            if (text.type == "error") {
                currentX += drawStrDL(g, font, font, "[", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(170, 0, 0, 50));
                currentX += drawStrDL(g, font, font, "WARN", currentX, currentY, h * 0.02, 2, 2);
                g.setColor(rgbaToColor(166, 166, 188, 50));
                currentX += drawStrDL(g, font, font, "] ", currentX, currentY, h * 0.02, 2, 2);
            }
            drawStrDL(g, font, font, text.text, currentX, currentY, h * 0.02, 2, 2);
        } else drawStrDL(g, font, font, text, currentX, currentY, h * 0.02, 2, 2);
    } else drawStrDL(g, font, font, "方速方块包 By 596wjr", currentX, currentY, h * 0.02, 2, 2);
    currentY += h * 0.02;
}

function isRelease() {
    let versionJSON = loadResource("str", "fangsu:version.json");
    let versionObj = JSON.parse(versionJSON);
    return versionObj.ver == "release";
}

function setDebugInfo(str) {
    // if (getClientConfig("bool", "show_debug", false)) {
    // MinecraftClient.displayMessage("[§l" + Date.now() + "§r] " + "[§3§lDEBUG§r] " + str, false);
    // print("[DEBUG] ", str);
    // }
}
function setWarnInfo(str) {
    MinecraftClient.displayMessage("[§l" + Date.now() + "§r] " + "[§6§lWARN§r] " + str, false);
    // print("[WARN] ", str);
}
function setErrorInfo(str) {
    MinecraftClient.displayMessage("[§l" + Date.now() + "§r] " + "[§4§lERROR§r] " + str, false);
    print("[ERROR] ", String(str.stack));
}

function runCommand(command) {
    try {
        let player = MinecraftClient.getPlayer();
        let level = MinecraftClient.getLevel(); //class_638
        let serverScoreboard = level.method_8428(); //class_269
        setDebugInfo(serverScoreboard.getClass());
        setDebugInfo(serverScoreboard.method_1170("mtr_balance"));
        // level.method_8525()
        // let mcServer = playerEntity.m_20194_();
        // let commandSourceStack = mcServer.createCommandSourceStack();

        // playerEntity.method_3142("/say Hello World")
        // 3. 调用 MTR 的 Utilities.sendCommand
        // Packages.mtr.mappings.Utilities.sendCommand(mcServer, commandSourceStack, command);
    } catch (e) {
        setWarnInfo(e); // 你的错误处理函数
    }
}

// 返回当前本地日期的 {年, 月, 日} 对象（月份为1-12）
function getCurrentDate() {
    var now = new Date();
    return {
        y: now.getFullYear(),
        m: now.getMonth() + 1, // 月份从0开始，需+1转为1-12
        d: now.getDate()
    };
}

// 返回当前星期几的数字（0=星期日，1=星期一...6=星期六）
function getCurrentWeekday() {
    return new Date().getDay();
}

// 函数1：根据布尔值返回不同格式的日期字符串
function formatDate(isCjk) {
    var date = new Date();
    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    var day = date.getDate();

    if (isCjk) {
        // 返回 "YYYY年MM月DD日" 格式
        return year + "年" + padZero(month) + "月" + padZero(day) + "日";
    } else {
        // 返回 "MM, DDth YYYY" 格式
        return padZero(month) + ", " + getOrdinalSuffix(day) + " " + year;
    }
}

// 函数2：根据布尔值返回中文星期或英文星期
function formatWeekday(isCjk) {
    var date = new Date();
    var day = date.getDay();
    var weekdaysCN = ["日", "一", "二", "三", "四", "五", "六"];
    var weekdaysEN = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

    return isCjk ? "星期" + weekdaysCN[day] : weekdaysEN[day];
}

// 辅助函数：补零
function padZero(num) {
    return num < 10 ? "0" + num : num;
}

// 辅助函数：获取英文日期后缀
function getOrdinalSuffix(day) {
    if (day > 3 && day < 21) return day + "th";
    switch (day % 10) {
        case 1:
            return day + "st";
        case 2:
            return day + "nd";
        case 3:
            return day + "rd";
        default:
            return day + "th";
    }
}

function isWindows() {
    var osName = System.getProperty("os.name").toLowerCase(java.util.Locale.ROOT);
    return osName.indexOf("win") >= 0;
}
// 打开URI的实现
function openUri(uriString) {
    if (!isWindows()) {
        setErrorInfo("只支持Windows平台");

        return;
    }
    try {
        // 创建URI和URL对象
        var uri = new URI(uriString);
        var url = uri.toURL();

        // Windows特定的命令
        var command = ["rundll32", "url.dll,FileProtocolHandler", url.toString()];

        // 执行命令
        var process = Runtime.getRuntime().exec(command);

        // 读取错误流
        var errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        var errorLine;
        while ((errorLine = errorReader.readLine()) !== null) {
            setWarnInfo("Error: " + errorLine);
        }

        // 关闭所有流
        process.getInputStream().close();
        process.getErrorStream().close();
        process.getOutputStream().close();
    } catch (e) {
        setWarnInfo("打开URI失败: " + e);
    }
}

function getShortId(obj) {
    var str = "";
    if (Array.isArray(obj)) {
        // 创建数组副本（避免修改原数组）
        var arrCopy = [];
        for (let i = 0; i < obj.length; i++) {
            arrCopy[i] = obj[i];
        }

        // 统一元素类型并排序
        arrCopy.sort(function (a, b) {
            a = String(a);
            b = String(b);
            if (a < b) return -1;
            if (a > b) return 1;
            return 0;
        });

        str = JSON.stringify(arrCopy);
    } else if (typeof obj == "string") {
        str = obj;
    } else {
        str = JSON.stringify(obj);
    }

    // FNV-1a哈希算法
    var hash = 0x811c9dc5;
    for (let i = 0; i < str.length; i++) {
        hash ^= str.charCodeAt(i);
        hash *= 0x01000193;
    }

    return hash.toString(16);
}

function isGraalJS() {
    return false;
    return Resources.getNTEVersionInt() >= 10101;
}

importClass(java.awt.image.BufferedImage);
importClass(java.awt.Color);
importClass(java.awt.Graphics2D);
importClass(java.awt.RenderingHints);
importClass(java.awt.Transparency);

/**
 * 使用像素操作更改图像颜色 (ES5 兼容版本)
 * @param {java.awt.Image} originalImage 原始图像
 * @param {java.awt.Color} newColor 新颜色
 * @returns {java.awt.Image} 更改颜色后的图像
 */
function changeImageColor(originalImage, newColor) {
    // 将原始图像转换为 BufferedImage
    var width = originalImage.getWidth(null);
    var height = originalImage.getHeight(null);

    var bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);

    var g2d = bufferedImage.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.drawImage(originalImage, 0, 0, null);
    g2d.dispose();

    // 提取新颜色的RGB分量
    var newRed = newColor.getRed();
    var newGreen = newColor.getGreen();
    var newBlue = newColor.getBlue();

    // 遍历并修改所有像素
    for (var y = 0; y < height; y++) {
        for (var x = 0; x < width; x++) {
            var rgb = bufferedImage.getRGB(x, y);

            // 获取原始alpha通道
            var alpha = (rgb >> 24) & 0xff;

            // 如果像素完全透明，则跳过
            if (alpha === 0) {
                continue;
            }

            // 组合新颜色(保持原始alpha)
            var newRGB = (alpha << 24) | (newRed << 16) | (newGreen << 8) | newBlue;
            bufferedImage.setRGB(x, y, newRGB);
        }
    }

    return bufferedImage;
}

// function getClientConfig(type, key, default_val) {
//     let grKey = `clientConfig_${key}`;
//     if (GlobalRegister.containsKey(grKey)) return ClientConfig.get(key);
//     else {
//         let textField;
//         if (type == "bool") {
//             textField = newBooleanToggleResponder(key, ComponentUtil.translatable(`fangsu.ante.clientconfig.${key}`), default_val);
//         } else {
//             textField = new TextField(key, ComponentUtil.translatable(`fangsu.ante.clientconfig.${key}`), default_val);
//         }
//         ClientConfig.register(textField);
//         ClientConfig.save();
//         return default_val;
//     }
// }

// function newBooleanToggleResponder(key, name, defaultValue) {
//     return new ConfigResponder({
//         key: () => key,
//         init: (configMap) => {
//             if (!configMap.containsKey(key)) configMap.put(key, defaultValue + "");
//         },
//         getListEntries: (configMap, builder, screenSupplier) => {
//             let value = configMap.get(key);
//             let flag = false;
//             if (value + "" == "true") flag = true;
//             return [
//                 builder
//                     .startBooleanToggle(name, flag)
//                     .setDefaultValue(defaultValue)
//                     .setSaveConsumer((value) => {
//                         configMap.put(key, value + "");
//                     })
//                     .build()
//             ];
//         }
//     });
// }

function dispErrScreen(Err) {
    print(`[ERROR] in FangSu scripts
            ${Err.stack}`);
    let errScreen = new IScreen.WithTextrue(ComponentUtil.literal("errScreen"));
    errScreen.initFunction = (screen, w, h) => {
        let state = screen.state;
        state.w = w;
        state.h = h;
    };
    errScreen.renderFunction = (screen, mx, my, d) => {
        let tex = screen.texture;
        let g = tex.graphics;
        let state = screen.state;

        let { width: w, height: h } = tex;

        let errorFont = Resources.getSystemFont("Serif");
        g.setColor(Color.BLUE);
        g.fillRect(0, 0, w, h);
        g.setColor(Color.WHITE);
        g.setFont(errorFont.deriveFont(Font.PLAIN, h * 0.2));
        g.drawString(":(", w * 0.15, h * 0.3);
        g.setFont(errorFont.deriveFont(Font.PLAIN, h * 0.05));
        g.drawString("你的游戏出现了问题, 可能是自定义资源包的问题", w * 0.15, h * 0.4);
        g.drawString("请先恢复自定义配置, 如果仍旧错误可以寻求他人帮助", w * 0.15, h * 0.5);

        let currentLine = "";
        let txL = [];
        let ft = errorFont.deriveFont(Font.PLAIN, h * 0.03);
        for (let char of String(Err)) {
            let testLine = currentLine + char;
            let testWidth = g.getFontMetrics(ft).stringWidth(testLine);
            if (testWidth <= w * 0.55) {
                currentLine = testLine;
            } else {
                txL.push(currentLine);
                currentLine = char;
            }
        }
        if (currentLine) txL.push(currentLine);
        let drawY = h * 0.7;
        g.setFont(ft);
        for (let tx of txL) {
            g.drawString(tx, w * 0.35, drawY);
            drawY += h * 0.035;
        }
        g.drawString("At: " + Err.fileName + ":" + Err.lineNumber, w * 0.35, drawY);
        drawY += h * 0.035;
        g.drawString("请求帮助时, 请不要只发送这个窗口的截图", w * 0.35, drawY);

        drawWaterPrint(g, w, h);
        tex.upload();
    };
    MinecraftClient.setScreen(errScreen);
}

function showMsgBox(basicTexture, buttons, title, lines) {
    let sc = new IScreen.WithTextrue(ComponentUtil.literal("msgbox"));
    let font = loadResource("font", "mtrsteamloco:fonts/misans-bold.otf");
    sc.initFunction = (screen, w, h) => {
        let state = screen.state;
        state.w = w;
        state.h = h;
        // let widths = [getUnifiedStringWidth(screen.texture.graphics, font, title, h * 0.05)];
        // lines.forEach((line) => {
        //     widths.puth(getUnifiedStringWidth(screen.texture.graphics, font, typeof line == "string" ? line : line.text, h * 0.05));
        // });
        // state.maxWidth = Math.max(widths);
        // state.maxWidth = w * 0.5;
    };
    sc.renderFunction = (screen, x, y, d) => {
        let tex = screen.texture;
        let g = tex.graphics;
        let state = screen.state;

        let { width: w, height: h } = tex;
        let posRateX = w / state.w;
        let posRateY = h / state.h;
        let mx = x * posRateX;
        let my = y * posRateY;

        state.maxWidth = w * 0.5;

        let currentY = h * 0.5 - lines.length * h * 0.05 * 0.5;

        if (basicTexture) g.drawImage(basicTexture, 0, 0, w, h, null);

        g.setColor(Color.BLUE);
        g.fillRect(w * 0.5 - state.maxWidth * 0.5 - w * 0.05, currentY, w * 0.1 + state.maxWidth, h * 0.07);
        g.setColor(Color.WHITE);
        g.fillRect(w * 0.5 - state.maxWidth * 0.5 - w * 0.05, currentY + h * 0.07, w * 0.1 + state.maxWidth, (lines.length + 2) * h * 0.04);

        g.setColor(Color.WHITE);
        drawStrUnified(g, font, title, w * 0.5, currentY + h * 0.06, h * 0.04, 1);
        currentY += h * 0.06;

        for (let line of lines) {
            g.setColor(Color.BLACK);
            drawStrUnified(g, font, typeof line == "string" ? line : line.text, w * 0.5, currentY + h * 0.05, h * 0.03, 1);
            currentY += h * 0.04;
        }

        if (buttons)
            for (let i = 0; i < buttons.length; i++) {
                let buttonWidth = state.maxWidth / buttons.length;
                let currentX = w * 0.5 - buttons.length * buttonWidth * 0.5 + buttonWidth * i;
                let selected = mx > currentX + w * 0.01 && mx < currentX + buttonWidth - w * 0.01 && my > currentY + h * 0.025 && my < currentY + h * 0.08;
                // g.setColor(selected ? rgbToColor(127, 127, 127) : Color.GRAY);
                // g.fillRect(currentX + w * 0.01, currentX + buttonWidth - w * 0.01, currentY + h * 0.025, currentY + h * 0.08);
                g.setColor(selected ? Color.BLACK : Color.GRAY);
                drawStrUnified(g, font, buttons[i].text, currentX + buttonWidth * 0.5, currentY + h * 0.07, h * 0.035, 1);

                if (state.clickInfo && selected) buttons[i].function();
            }

        state.clickInfo = null;
        tex.upload();
    };
    sc.mouseReleasedFunction = (screen, x, y, i) => {
        if (screen.state.skipFirstClick) {
            screen.state.skipFirstClick = false;
            return true;
        }
        let tex = screen.texture;
        let { width: w, height: h } = tex;
        let state = screen.state;
        let mx = (w / state.w) * x;
        let my = (h / state.h) * y;
        screen.state.clickInfo = { x: mx, y: my };
        return true;
    };
    MinecraftClient.setScreen(sc);
}
