include(Resources.idr("fs_gif_helper.js"));

function setDebugInfo(str) {
    // 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] ", str);
}

// 辅助函数：在数组中查找子序列的起始位置
function findSubsequenceIndex(mainArray, subArray) {
    for (let i = 0; i <= mainArray.length - subArray.length; i++) {
        let match = true;
        for (let j = 0; j < subArray.length; j++) {
            if (mainArray[i + j] !== subArray[j]) {
                match = false;
                break;
            }
        }
        if (match) return i;
    }
    return -1;
}

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

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);
}

/**
 * 动态加载资源并缓存结果，避免重复加载导致的性能问题
 *
 * @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 hasCjkPart(str) {
    return TextUtil.getCjkParts(str).length > 0;
}

function hasNonCjkPart(str) {
    return TextUtil.getNonCjkParts(str).length > 0;
}
