import { UITransform, Vec2, Vec3,Node, _decorator, find, director, Color, UIOpacity, tween, Label, Camera, view, Quat, math } from "cc";
const { ccclass, property } = _decorator;
import sensitiveArray from '../extend/SensitiveWords';

@ccclass("Utils")
export class Utils {

    /**
     * 验证字符串是否是空的
     * @s 字符串
     */
    public static isNull(s: string) {
        if(s == "" 
            || s == null 
            || s == undefined){
            return true;
        }
        if (typeof s === 'string'){
            let re = new RegExp("^[ ]+$");
            return re.test(s);
        }else{
            return false;
        }
    }

    /**
     * 合并多个字典
     * @param args 
     */
    public static merge(...args) {
        let mergeFn = <T, U> (arg1: T, arg2: U) : (T & U) =>{
            let res = {} as (T & U);
            res = Object.assign(arg1, arg2);
            return res;
        };
        let nDict = {};
        args.forEach(obj => {
           nDict = mergeFn(nDict,obj);
        });
        return nDict;
    }
 
    /**
     * 排除合并对象，但排除'reward'和'dec'属性（如果e中已存在）
     * mergeWithExceptions(e, pData, ['reward', 'dec']);
     * @param target 
     * @param source 
     * @param exclude 
     * @returns 
     */
    public static mergeWithExceptions(target: any, source: any, exclude: string[]) {
        Object.keys(source).forEach(key => {
            if (!exclude.includes(key) || !(key in target)) {
                target[key] = source[key];
            }
        });
        return target;
    }
    /**
     * 深度拷贝
     * @param {any} sObj 拷贝的对象
     * @returns 
     */
    public static clone(sObj: any) {
        if (sObj === null || typeof sObj !== "object") {
            return sObj;
        }
        let s: { [key: string]: any } = {};
        if (sObj.constructor === Array) {
            s = [];
        }
        for (let i in sObj) {
            if (sObj.hasOwnProperty(i)) {
                s[i] = this.clone(sObj[i]);
            }
        }
        return s;
    }

    /**
     * 转换数字
     */
    public static formatUnits(num: number): string {
        if (num >= 1e8) {
            return (num / 1e8).toFixed(1) + '亿';
        } else if (num >= 1e7) {
            return (num / 1e7).toFixed(1) + '千万';
        } else if (num >= 1e6) {
            return (num / 1e6).toFixed(1) + '百万';
        } else if (num >= 1e4) {
            return (num / 1e4).toFixed(1) + '万';
        } else if (num >= 1000) {
            return (num / 1000).toFixed(1) + 'k';
        } else {
            return num.toString();
        }
    }

    /**
     * 将object转化为数组
     * @param { any} srcObj  
     * @returns 
     */
    public static objectToArray(srcObj: { [key: string]: any }) {
        let resultArr: any[] = [];
        // to array
        for (let key in srcObj) {
            if (!srcObj.hasOwnProperty(key)) {
                continue;
            }
            resultArr.push(srcObj[key]);
        }
        return resultArr;
    }

    /**
     * !#zh 将数组转化为object。
     */
    /**
     * 将数组转化为object。
     * @param { any} srcObj 
     * @param { string} objectKey 
     * @returns 
     */
    public static arrayToObject(srcObj: any, objectKey: string) {
        let resultObj: { [key: string]: any } = {};
        // to object
        for (var key in srcObj) {
            if (!srcObj.hasOwnProperty(key) || !srcObj[key][objectKey]) {
                continue;
            }
            resultObj[srcObj[key][objectKey]] = srcObj[key];
        }
        return resultObj;
    }

    /**
     * 根据权重,计算随机内容
     * @param {arrany} weightArr 
     * @param {number} totalWeight 权重
     * @returns 
     */
    public static getWeightRandIndex(weightArr: [], totalWeight: number) {
        let randWeight: number = Math.floor(Math.random() * totalWeight);
        let sum: number = 0;
        for (var weightIndex: number = 0; weightIndex < weightArr.length; weightIndex++) {
            sum += weightArr[weightIndex];
            if (randWeight < sum) {
                break;
            }
        }
        return weightIndex;
    }

    /**
     * 从n个数中获取m个随机数
     * @param {Number} n   总数
     * @param {Number} m    获取数
     * @returns {Array} array   获取数列
     */
    public static getRandomNFromM(n: number, m: number) {
        let array: any[] = [];
        let intRd: number = 0;
        let count: number = 0;
        while (count < m) {
            if (count >= n + 1) {
                break;
            }
            intRd = this.getRandomInt(0, n);
            var flag = 0;
            for (var i = 0; i < count; i++) {
                if (array[i] === intRd) {
                    flag = 1;
                    break;
                }
            }
            if (flag === 0) {
                array[count] = intRd;
                count++;
            }
        }
        return array;
    }

    /**
     * 获取随机整数
     * @param {Number} min 最小值
     * @param {Number} max 最大值
     * @returns 
     */
    public static getRandomInt(min: number, max: number) {
        let r: number = Math.random();
        let rr: number = r * (max - min + 1) + min;
        return Math.floor(rr);
    }

    /**
     * 获取字符串长度
     * @param {string} render 
     * @returns 
     */
    public static getStringLength(render: string) {
        let strArr: string = render;
        let len: number = 0;
        for (let i: number = 0, n = strArr.length; i < n; i++) {
            let val: number = strArr.charCodeAt(i);
            if (val <= 255) {
                len = len + 1;
            } else {
                len = len + 2;
            }
        }
        return Math.ceil(len / 2);
    }
    /**
     * 要从一个数组模型中随机取出 n 个元素
     * @param arr 
     * @param n 
     * @returns 返回一个新的数组
     */
    public static getRandomElements<T>(arr: T[], n: number): T[] {
        if (n <= 0) return []; // 如果 n 小于等于 0，返回空数组
        //复制数组以避免修改原数组
        const copy = [...arr];
        //Fisher-Yates 洗牌算法
        for (let i = copy.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            [copy[i], copy[j]] = [copy[j], copy[i]];
        }
        //如果 n 超过数组长度，返回乱序后的整个数组
        if (n >= copy.length) {
            return copy;
        }
        //返回前 n 个元素
        return copy.slice(0, n);
    }

    /**
     * 判断传入的参数是否为空的Object。数组或undefined会返回false
     * @param obj
     */
    public static isEmptyObject(obj: any) {
        let result: boolean = true;
        if (obj && obj.constructor === Object) {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    result = false;
                    break;
                }
            }
        } else {
            result = false;
        }
        return result;
    }

    /**
     * 判断是否是新的一天
     * @param {Object|Number} dateValue 时间对象 todo MessageCenter 与 pve 相关的时间存储建议改为 Date 类型
     * @returns {boolean}
     */
    public static isNewDay(dateValue: any) {
        // todo：是否需要判断时区？
        var oldDate: any = new Date(dateValue);
        var curDate: any = new Date();
        var oldYear = oldDate.getYear();
        var oldMonth = oldDate.getMonth();
        var oldDay = oldDate.getDate();
        var curYear = curDate.getYear();
        var curMonth = curDate.getMonth();
        var curDay = curDate.getDate();
        if (curYear > oldYear) {
            return true;
        } else {
            if (curMonth > oldMonth) {
                return true;
            } else {
                if (curDay > oldDay) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 获取对象属性数量
     * @param {object}o 对象
     * @returns 
     */
    public static getPropertyCount(o: Object) {
        var n, count = 0;
        for (n in o) {
            if (o.hasOwnProperty(n)) {
                count++;
            }
        }
        return count;
    }

    /**
     * 返回一个差异化数组（将array中diff里的值去掉）
     * @param array
     * @param diff
     */
    public static difference(array: [], diff: any) {
        let result: any[] = [];
        if (array.constructor !== Array || diff.constructor !== Array) {
            return result;
        }
        let length = array.length;
        for (let i: number = 0; i < length; i++) {
            if (diff.indexOf(array[i]) === -1) {
                result.push(array[i]);
            }
        }
        return result;
    }


    public static _stringToArray(string: string) {
        // 用于判断emoji的正则们
        var rsAstralRange = '\\ud800-\\udfff';
        var rsZWJ = '\\u200d';
        var rsVarRange = '\\ufe0e\\ufe0f';
        var rsComboMarksRange = '\\u0300-\\u036f';
        var reComboHalfMarksRange = '\\ufe20-\\ufe2f';
        var rsComboSymbolsRange = '\\u20d0-\\u20ff';
        var rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange;
        var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + ']');
        var rsFitz = '\\ud83c[\\udffb-\\udfff]';
        var rsOptVar = '[' + rsVarRange + ']?';
        var rsCombo = '[' + rsComboRange + ']';
        var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')';
        var reOptMod = rsModifier + '?';
        var rsAstral = '[' + rsAstralRange + ']';
        var rsNonAstral = '[^' + rsAstralRange + ']';
        var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}';
        var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]';
        var rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*';
        var rsSeq = rsOptVar + reOptMod + rsOptJoin;
        var rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
        var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
        var hasUnicode = function (val: any) {
            return reHasUnicode.test(val);
        };
        var unicodeToArray = function (val: any) {
            return val.match(reUnicode) || [];
        };
        var asciiToArray = function (val: any) {
            return val.split('');
        };
        return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string);
    }

    // 模拟传msg的uuid
    public static simulationUUID() {
        function s4() {
            return Math.floor((1 + Math.random()) * 0x10000)
                .toString(16)
                .substring(1);
        }
        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
            s4() + '-' + s4() + s4() + s4();
    }

    public static trim(str: string) {
        return str.replace(/(^\s*)|(\s*$)/g, "");
    }

    /**
     * 判断当前时间是否在有效时间内
     * @param {String|Number} start 起始时间。带有时区信息
     * @param {String|Number} end 结束时间。带有时区信息
     */
    public static isNowValid(start: any, end: any) {
        var startTime = new Date(start);
        var endTime = new Date(end);
        var result = false;
        if (startTime.getDate() + '' !== 'NaN' && endTime.getDate() + '' !== 'NaN') {
            var curDate = new Date();
            result = curDate < endTime && curDate > startTime;
        }
        return result;
    }

    /**
     * 返回相隔天数
     * @param start 
     * @param end 
     * @returns 
     */
    public static getDeltaDays(start: any, end: any) {
        start = new Date(start);
        end = new Date(end);
        let startYear: number = start.getFullYear();
        let startMonth: number = start.getMonth() + 1;
        let startDate: number = start.getDate();
        let endYear: number = end.getFullYear();
        let endMonth: number = end.getMonth() + 1;
        let endDate: number = end.getDate();
        start = new Date(startYear + '/' + startMonth + '/' + startDate + ' GMT+0800').getTime();
        end = new Date(endYear + '/' + endMonth + '/' + endDate + ' GMT+0800').getTime();
        let deltaTime = end - start;
        return Math.floor(deltaTime / (24 * 60 * 60 * 1000));
    }

    /**
     * 获取数组最小值
     * @param array 数组
     * @returns 
     */
    public static getMin(array: number[]) {
        let result: number = null!;
        if (array.constructor === Array) {
            let length = array.length;
            for (let i = 0; i < length; i++) {
                if (i === 0) {
                    result = Number(array[0]);
                } else {
                    result = result > Number(array[i]) ? Number(array[i]) : result;
                }
            }
        }
        return result;
    }

    /**
     * 格式化两位小数点
     * @param time 
     * @returns 
     */
    public static formatTwoDigits(time: number) {
        //@ts-ignore
        return (Array(2).join(0) + time).slice(-2);
    }

    /**
     * 根据格式返回时间
     * @param date  时间
     * @param fmt 格式
     * @returns 
     */
    public static formatDate(date: Date, fmt: string) {
        let o: any = {
            "M+": date.getMonth() + 1, //月份
            "d+": date.getDate(), //日
            "h+": date.getHours(), //小时
            "m+": date.getMinutes(), //分
            "s+": date.getSeconds(), //秒
            "q+": Math.floor((date.getMonth() + 3) / 3), //季度
            "S": date.getMilliseconds() //毫秒
        };
        if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
        for (let k in o)
            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
        return fmt;
    }

    /**
     * 获取格式化后的日期（不含小时分秒）
     */
    public static getDay() {
        let date: Date = new Date();
        return date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
    }

    /**
     * 格式化名字，XXX...
     * @param {string} name 需要格式化的字符串 
     * @param {number}limit 
     * @returns {string} 返回格式化后的字符串XXX...
     */
    public static formatName(name: string, limit: number) {
        limit = limit || 6;
        var nameArray = this._stringToArray(name);
        var str = '';
        var length = nameArray.length;
        if (length > limit) {
            for (var i = 0; i < limit; i++) {
                str += nameArray[i];
            }
            str += '...';
        } else {
            str = name;
        }
        return str;
    }

    /**
     * 格式化钱数，超过10000 转换位 10K   10000K 转换为 10M
     * @param {number}money 需要被格式化的数值
     * @returns {string}返回 被格式化的数值
     */
    public static formatMoney(money: number) {
        let arrUnit: string[] = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'B', 'N', 'D'];

        let strValue: string = '';
        for (let idx: number = 0; idx < arrUnit.length; idx++) {
            if (money >= 10000) {
                money /= 1000;
            } else {
                strValue = Math.floor(money) + arrUnit[idx];
                break;
            }
        }
        if (strValue === '') {
            strValue = Math.floor(money) + 'U'; //超过最大值就加个U
        }
        return strValue;
    }

    /**
     * 开始展示文字
     * @param lable 文本
     * @param words 播放的文字
     * @param cb 播放完成回调
     * @param cbbS 回调延迟
     * @param delay 延迟逐字播放
     * @param s 一个字的播放速度/秒
     */
    public static verbatim(lable: Label,words: string, cb?: Function,cbbS:number = 0,delay: number = 0,s: number = 0.1,){
        if (!words.hasOwnProperty('length'))return;
        lable.node.active = true;
        lable.unscheduleAllCallbacks();
        let f = function(){
            let arr = words.replace(/ /g,"").split('');
            var step = 0;
            var allWords: string = "";
            let fun = ()=> {
                allWords += arr[step];
                lable.string = allWords;
                if(++step >= arr.length) {
                    lable.unschedule(fun);
                    let cbFun = ()=>{cb?.()};
                    cbbS > 0 ? (lable.scheduleOnce(cbFun, cbbS)) : cbFun();
                }
            };
            lable.schedule(fun,s,Number.MAX_SAFE_INTEGER);
        };
        delay > 0 ? (lable.scheduleOnce(f.bind(this),delay)) : f();
    }

    /**
     * 格式化数值
     * @param {number}value 需要被格式化的数值
     * @returns {string}返回 被格式化的数值
     */
    public static formatValue(value: number) {
        let arrUnit: string[] = [];
        let strValue: string = '';
        for (let i = 0; i < 26; i++) {
            arrUnit.push(String.fromCharCode(97 + i));
        }
        for (let idx: number = 0; idx < arrUnit.length; idx++) {
            if (value >= 10000) {
                value /= 1000;
            } else {
                strValue = Math.floor(value) + arrUnit[idx];
                break;
            }
        }
        return strValue;
    }

    /**
     * 根据剩余秒数格式化剩余时间 返回 HH:MM:SS
     * @param {Number} leftSec 
     */
    public static formatTimeForSecond(leftSec: number, withoutSeconds: boolean = false) {
        let timeStr: string = '';
        let sec: number = leftSec % 60;
        let leftMin: number = Math.floor(leftSec / 60);
        leftMin = leftMin < 0 ? 0 : leftMin;
        let hour: number = Math.floor(leftMin / 60);
        let min: number = leftMin % 60;
        if (hour > 0) {
            timeStr += hour > 9 ? hour.toString() : '0' + hour;
            timeStr += ':';
        } else {
            timeStr += '00:';
        }
        timeStr += min > 9 ? min.toString() : '0' + min;
        if (!withoutSeconds) {
            timeStr += ':';
            timeStr += sec > 9 ? sec.toString() : '0' + sec;
        }
        return timeStr;
    }

    /**
     * 计算 3D 空间中两点之间的欧几里得距离
     * @param a 第一个点
     * @param b 第二个点
     * @returns 两点之间的距离
     */
    public static distance(a: Vec3, b: Vec3): number {
        const dx = b.x - a.x;
        const dy = b.y - a.y;
        const dz = b.z - a.z;
        return Math.sqrt(dx * dx + dy * dy + dz * dz);
    }

    /**
     *  根据剩余毫秒数格式化剩余时间 返回 HH:MM:SS
     *
     * @param {Number} ms
     */
    public static formatTimeForMillisecond(ms: number): Object {
        let second: number = Math.floor(ms / 1000 % 60);
        let minute: number = Math.floor(ms / 1000 / 60 % 60);
        let hour: number = Math.floor(ms / 1000 / 60 / 60);
        return { 'hour': hour, 'minute': minute, 'second': second };
    }

    /**
     * 格式化时间戳字符串
     * @param timestamp 1740006560000
     * @returns 输出2025-02-20 05:09:20
     */
    public static formatTimestamp(timestamp: number): string {
        const date = new Date(timestamp);
        //获取年月日时分秒
        const year = date.getFullYear();
        //月份从 0 开始，需要 +1
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hours = String(date.getHours()).padStart(2, '0');
        const minutes = String(date.getMinutes()).padStart(2, '0');
        const seconds = String(date.getSeconds()).padStart(2, '0');
        //拼接成 YYYY-MM-DD HH:MM:SS 格式
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
    }

    /**
     * 随机乱序数组
     * @param array 
     * @returns 
     */
    public static randomArray(array) {
        // 使用 Fisher-Yates Shuffle 算法
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            // 交换元素
            [array[i], array[j]] = [array[j], array[i]];
        }
        return array;
    }

    /**
     * 获得开始和结束两者之间相隔分钟数
     *
     * @static
     * @param {number} start
     * @param {number} end
     * @memberof utils
     */
    public static getOffsetMimutes(start: number, end: number) {
        let offSetTime: number = end - start;
        let minute: number = Math.floor((offSetTime % (1000 * 60 * 60)) / (1000 * 60));
        return minute;
    }

    /**
     * 获取随机小数
     * @param {Number} min 最小值
     * @param {Number} max 最大值
     * @returns 
     */
    public static getRandomFloat(min: number, max: number) {
        return Math.random() * (max - min) + min;
    }
    
    /**
     * 返回指定小数位的数值
     * @param {number} num 
     * @param {number} idx 
     */
    public static formatNumToFixed(num: number, idx: number = 0) {
        return Number(num.toFixed(idx));
    }

    /**
     * 用于数值到达另外一个目标数值之间进行平滑过渡运动效果
     * @param {number} targetValue 目标数值 
     * @param {number} curValue 当前数值
     * @param {number} ratio    过渡比率
     * @returns 
     */
    public static lerp(targetValue: number, curValue: number, ratio: number = 0.25) {
        let v: number = curValue;
        if (targetValue > curValue) {
            v = curValue + (targetValue - curValue) * ratio;
        } else if (targetValue < curValue) {
            v = curValue - (curValue - targetValue) * ratio;
        }
        return v;
    }

    /**
     * 数据解密
     * @param {String} str 
     */
    public static decrypt(b64Data: string) {
        if(b64Data == null || b64Data == undefined){
           return "";
        }
        let n: number = 6;
        if (b64Data.length % 2 === 0) {
            n = 7;
        }
        let decodeData = '';
        for (var idx = 0; idx < b64Data.length - n; idx += 2) {
            decodeData += b64Data[idx + 1];
            decodeData += b64Data[idx];
        }
        decodeData += b64Data.slice(b64Data.length - n + 1);
        decodeData = this._base64Decode(decodeData);
        return decodeData;
    }

    /**
     * 数据加密
     * @param {String} str 
     */
    public static encrypt(str: string) {
        if(str == null || str == undefined){
          return "";
        }
        let b64Data = this._base64encode(str);
        let n: number = 6;
        if (b64Data.length % 2 === 0) {
            n = 7;
        }
        let encodeData: string = '';
        for (let idx = 0; idx < (b64Data.length - n + 1) / 2; idx++) {
            encodeData += b64Data[2 * idx + 1];
            encodeData += b64Data[2 * idx];
        }
        encodeData += b64Data.slice(b64Data.length - n + 1);
        return encodeData;
    }

    //public method for encoding
    /**
     * base64加密
     * @param {string}input 
     * @returns 
     */
    private static _base64encode(input: string) {
        let keyStr: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        let output: string = "", chr1: number, chr2: number, chr3: number, enc1: number, enc2: number, enc3: number, enc4: number, i: number = 0;
        input = this._utf8Encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
                keyStr.charAt(enc1) + keyStr.charAt(enc2) +
                keyStr.charAt(enc3) + keyStr.charAt(enc4);
        }
        return output;
    }

    /**
     * utf-8 加密
     * @param string 
     * @returns 
     */
    private static _utf8Encode(string: string) {
        string = string.replace(/\r\n/g, "\n");
        let utftext: string = "";
        for (let n: number = 0; n < string.length; n++) {
            let c: number = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if ((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }
        }
        return utftext;
    }

    /**
     * utf-8解密
     * @param utftext 
     * @returns 
     */
    private static _utf8Decode(utftext: string) {
        let string = "";
        let i: number = 0;
        let c: number = 0;
        let c1: number = 0;
        let c2: number = 0;
        let c3: number = 0;
        while (i < utftext.length) {
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if ((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i + 1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = utftext.charCodeAt(i + 1);
                c3 = utftext.charCodeAt(i + 2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }

    /**
     * base64解密
     * @param {string}input 解密字符串
     * @returns 
     */
    private static _base64Decode(input: string) {
        let keyStr: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
        let output: string = "";
        let chr1: number;
        let chr2: number;
        let chr3: number;
        let enc1: number;
        let enc2: number;
        let enc3: number;
        let enc4: number;
        let i: number = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length) {
            enc1 = keyStr.indexOf(input.charAt(i++));
            enc2 = keyStr.indexOf(input.charAt(i++));
            enc3 = keyStr.indexOf(input.charAt(i++));
            enc4 = keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = this._utf8Decode(output);
        return output;
    }
    
    /**
     * 页面渐隐渐显动画
     * @param n 节点
     * @param isApper 是否是出现
     * @param restore 是否恢复255显示的状态
     * @param cb 执行完回调 
     */
    public static pageAnim(n: Node,isApper: boolean = true,cb?:Function){
        let uiop:UIOpacity = n.getComponent(UIOpacity);
        if(!n || !uiop){
            cb?.();
        }else{
            uiop.opacity = isApper ? 25 : 255;
            let toOpacity: number = isApper ? 255 : 25;
            tween(uiop)
            .to(0.4,{opacity:toOpacity})
            .call(function(){
                cb?.();
            }.bind(this))
            .start();
        }
    }
    
    /**
     * 将数组（array）拆分成多个 size 长度的区块，并将这些区块组成一个新数组
     * @param {Array}array 
     * @param {number}size 
     * @returns 
     */
    public static chunk(array: any[], size: number) {
        var length = array === null ? 0 : array.length;
        if (!length || size < 1) {
            return [];
        }
        let result = [];
        while (array.length > size) {
            result.push(array.slice(0, size));
            array = array.slice(size);
        }
        result.push(array);
        return result;
    }
    
    /**
     * 一个字符串首字母大写
     */
    public static upperCase(str){
        if(this.isNull(str))return "";
        return str.replace(/^\w/, (c) => c.toUpperCase());
    }

    /**
     * 查询一个节点下的子节点
     */
    public static findName(root: Node, name: string) : Node{
        let child: Node;
        if(name.indexOf("/") != -1) {
            child = find(name, root);
        }else {
            if(!root) {root = director.getScene();}
            if (root.name === name) {
                child = root;
            } else {
                child = this.findChild(name, root);
            }
        }
        if(child) {
            return child;
        } else {
            console.log("没有找到指定的Node, node name ==", name);
            return null;
        }
    }

    private static findChild(name: string, parent: Node): Node {
        let child: Node = parent.getChildByName(name);
        if (child) {
            return child;
        } else {
            let children: Node[] = parent.children;
            for (let i = 0; i < children.length; i++) {
                child = this.findChild(name, children[i]);
                if (child) {
                    return child;
                }
            }
        }
        return null;
    }

    /**
     * 处理万为单位
     * @param num 数值
     * @param point 保留小数点
     * @param s 是否去掉无用的0
     * @returns 
     */
    public static numUnit(num: number,point: number = 0,f: boolean = true): string {
        let n: number = num;
        let unit: number = 10000;
        if (n > unit) {
            if(point == 0){
               n = Math.ceil(n / unit);
               return n.toString() + "万";
            }else{
                let s: string = (n / unit).toFixed(point);
                if(f){
                   return this.removeZeros(s) + "万";
                }else{
                   return s + "万";
                }
            }
        }else{
            return Math.ceil(num).toString();
        }
    }

    /**
     * 格式化数字：
     * * 如果小数部分全是 0（如 38.0 或 38.00），去掉小数部分，返回整数。
     * 如果是小数（如 38.1 或 38.01），保留两位小数。
     * @param num 输入的数字
     * @returns 格式化后的数字
     */
    public static formatNumber(num: number): number | string {
        // 如果 num 是 undefined 或 null，返回 0 或空字符串
        if (num == undefined || num == null) return 0;
        //将数字转换为字符串
        const numStr = num.toString();
        //判断小数部分是否全是 0 如果小数部分全是 0，去掉小数部分并返回整数
        if (numStr.includes('.') && /\.0+$/.test(numStr)) {
            return parseInt(numStr, 10);
        } else {
            //否则保留两位小数
            const fixedNum = num.toFixed(2);
            //如果小数部分全是 0，去掉小数部分
            if(fixedNum.endsWith(".00")) {
                return parseInt(fixedNum, 10);
            }
            return parseFloat(fixedNum);
        }
    }

    /**
     * 去掉小数点后无用的0
     * @param numberString 字符串呢
     * @returns 
     */
    public static removeZeros(numberString: string): string {
        const trimmedString = numberString.trim(); // 去除首尾空格
        const decimalIndex = trimmedString.indexOf('.');
        if (decimalIndex !== -1) {
          let endIndex = trimmedString.length - 1;
          while (trimmedString[endIndex] === '0') {
            endIndex--;
          }
          if (trimmedString[endIndex] === '.') {
            endIndex--; // 如果小数点后面全是零，也去掉小数点
          }
          return trimmedString.slice(0, endIndex + 1);
        }
        return trimmedString;
    }

    /**
     * 数组移除某一个元素
     */
    public static remove(arr,param){
        let index = arr.indexOf(param)
        if (index > -1) {
            arr.splice(index, 1)
        }
    }

    /**
     * 16进制的颜色
     * @param hexColor 
     * @returns 
     */
    public static hexColor(hexColor) {
        const hex = hexColor.replace(/^#?/, "0x");
        const c = parseInt(hex);
        const r = c >> 16;
        const g = (65280 & c) >> 8;
        const b = 255 & c;
        return new Color(r, g, b, 255);
    };

    /**
     * 检查是否有铭感词
     * @param str 文字
     * @returns 返回是否有铭感词
     */
    public static filtion(str): boolean{
        return sensitiveArray.some(word => {
            //忽略大小写匹配
            const lowerCaseStr = str.toLowerCase();
            const lowerCaseWord = word.toLowerCase();
            return lowerCaseStr.includes(lowerCaseWord);
        });
    }

    /**
     * 计算两点间的距离
     */
    public static pDistance(localPos: Vec3,tarPos: Vec3): number {
        let dx = localPos.x - tarPos.x;
        let dy = localPos.y - tarPos.y;
        let dis = Math.sqrt(dx * dx + dy * dy);
        return dis;
    }
    
    /**
     * 计算两点之间的绝对距离
     */
    public static pAbsDistance(a: Vec3,b: Vec3): number {
        let p: number = Math.abs(a.x - b.x);
        let k: number = Math.abs(a.y - b.y);
        return p + k;
    }

    /**
     * 角度转向量 
     * @param angle 
     * @returns 
     */  
    public static angle_to_vector (angle: number): Vec2 {
        // tan = sin / cos 将传入的角度转为弧度
        let radian = this.angle_to_radian(angle);
        // 算出cos,sin和tan
        let cos = Math.cos(radian);// 邻边 / 斜边
        let sin = Math.sin(radian);// 对边 / 斜边
        let tan = sin / cos;// 对边 / 邻边
        //结合在一起并归一化
        let vec = new Vec2(cos, sin).normalize();
        //返回向量
        return(vec);
    }

    /**
     * 向量转角度
     * @param vector 
     * @returns 
     */
    public static vector_to_angle (vector: Vec2): number {
        //将传入的向量归一化
        let dir = vector.normalize();
        //计算出目标角度的弧度
        let radian = dir.signAngle(new Vec2(1, 0));
        //把弧度计算成角度
        let angle = -this.radian_to_angle(radian);
        //返回角度
        return(angle);
    }   

    /**
     * 角度转弧度
     * @param angle 
     * @returns 
     */
    public static angle_to_radian (angle: number): number {
        //角度转弧度公式 π / 180 * 角度 计算出弧度
        let radian = Math.PI / 180 * angle;
        //返回弧度
        return(radian);
    }

    /**
     * 弧度转角度
     * @param radian 
     * @returns 
     */
    public static radian_to_angle (radian: number): number {
        //弧度转角度公式 180 / π * 弧度 计算出角度
        let angle = 180 / Math.PI * radian;
        //返回角度
        return(angle);
    }


    /**
     * 计算弧度
     * @param start 
     * @param end 
     */
    public static getAngle(start: Vec3, end: Vec3) {
        //两点的x、y值
        var x = end.x - start.x;
        var y = end.y - start.y;
        var hypotenuse = Math.sqrt(x * x + y * y);
        //斜边长度
        var cos = x / hypotenuse;
        var radian = Math.acos(cos);
        //求出弧度
        var angle = 180 / (Math.PI / radian);
        //用弧度算出角度
        if (y < 0) {
           angle = 0 - angle;
        }else if (y == 0 && x < 0) {
           angle = 180;
        }
        return angle;
    }

    /**
     * 扣血转化成字符串
     * @param number 
     * @returns 
     */
    public static numberToString(value: number | string) {
        const num = Number(value);
        if (isNaN(num)) return "0";
        //特殊处理：小于1百万显示完整数字
        if(num < 100000) {
           return Math.floor(num).toString();
        }
        //使用标准格式化中文单位
        return this.formatBigNumber(num, ["", "K", "M", "B"]);
    }

    /**
     * 基本用法（自动中文单位）
       console.log(formatChineseBigNumber(998989));  // "998989"
       console.log(formatChineseBigNumber(24326755)); // "2432.67万"
       console.log(formatChineseBigNumber(958753111)); // "9.58亿"
       console.log(formatChineseBigNumber(9587531115987));// "9.58万亿"
       //高级用法（自定义单位）
       console.log(formatBigNumber(1500, ["", "K", "M"], 1000)); // "1.5K"
       console.log(formatBigNumber(2500000, ["", "K", "M"], 1000)); // "2.5M"
     * 格式化大数字带单位
     * @param value 要格式化的数字或字符串
     * @param customUnits 可选的自定义单位数组（从大到小排序）
     * @param customK 可选的进制基数（默认10000）
     * @returns 格式化后的字符串
     */
    public static formatBigNumber(
        value: number | string,
        customUnits?: string[],
        customK: number = 10000
    ): string {
        const num = Number(value);
        if (isNaN(num)) return "0";
        const defaultUnits = ["", "万", "亿", "万亿"];
        const units = customUnits || defaultUnits;
        const k = customK;
        //小于1万的直接返回原数字
        if(num < k) {
           return num % 1 === 0 ? num.toString() : num.toFixed(2);
        }
        //计算单位
        const i = Math.floor(Math.log(num) / Math.log(k));
        const unitIndex = Math.min(i, units.length - 1);
        const divided = num / Math.pow(k, unitIndex);
        // 智能确定小数位数
        let decimalPlaces = 2;
        if (divided >= 1000) decimalPlaces = 0;
        else if (divided >= 100) decimalPlaces = 1;
        // 格式化并去除无效的.00
        let formatted = divided.toFixed(decimalPlaces);
        if(formatted.endsWith(".00")) {
            formatted = formatted.slice(0, -3);
        }else if (formatted.endsWith("0") && decimalPlaces > 0) {
            //处理类似1032.50万 -> 1032.5万的情况
            formatted = formatted.replace(/0+$/, "").replace(/\.$/, "");
        }
        return formatted + units[unitIndex];
    }

    /**
     * 将某个节点下的坐标转移到另外一个节点
     * @param fromNode 坐标所在的节点
     * @param toNode 目标节点
     * @returns 转换后的坐标值
     */
    public static convertPosition(fromNode: Node, toNode: Node): Vec3 {
        let pos: Vec3 = fromNode.position.clone();
        // 将 pos 转为世界坐标系下的坐标
        const worldPos: Vec3 = fromNode.parent.getComponent(UITransform).convertToWorldSpaceAR(pos);
        // 将世界坐标系下的坐标转为目标节点的局部坐标系下的坐标
        const localPos: Vec3 = toNode.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
        return localPos;
    }

    /**
     * 以敌人的中心点 攻击范围为半径 产生随机坐标
     * @param center 中心点
     * @param radius 半径
     * @returns 
     */
    public static randomPointGenerator(center: Vec3,radius: number){
        //随机角度
        let angle = Math.random() * Math.PI * 2;
        //随机距离
        let distance = Math.sqrt(Math.random()) * radius;
        //根据极坐标转换成笛卡尔坐标
        const x = center.x + distance * Math.cos(angle);
        const y = center.y + distance * Math.sin(angle);
        return new Vec3(x,y,1);
    }

    /**
     * 将某个节点上的坐标转移到另外一个节点
     * @param fromNode 坐标所在的节点
     * @param toNode 目标节点
     * @returns 转换后的坐标值
     */
    public static convertPositionPos(fromNode: Node,pos: Vec3, toNode: Node): Vec3 {
        let nPos: Vec3 = pos.clone();
        // 将 pos 转为世界坐标系下的坐标
        const worldPos: Vec3 = fromNode.parent.getComponent(UITransform).convertToWorldSpaceAR(nPos);
        // 将世界坐标系下的坐标转为目标节点的局部坐标系下的坐标
        const localPos: Vec3 = toNode.getComponent(UITransform).convertToNodeSpaceAR(worldPos);
        return localPos;
    }

    /**
     * 获取屏幕中心对应的3D世界坐标
     * @param camera 摄像机
     * @param groundY 地面高度
     * @returns 
     */
    public static getScreenCenterWorldPos(camera: Camera,groundY:number = 0): Vec3 {
        if (!camera) return Vec3.ZERO;
        //获取屏幕中心坐标
        const screenSize = view.getVisibleSize();
        const centerX = screenSize.width / 2;
        const centerY = screenSize.height / 2;
        //生成从屏幕中心发出的射线
        const ray = camera.screenPointToRay(centerX, centerY);
        //计算射线与特定平面的交点（假设地面Y=0）
        const distance = (groundY - ray.o.y) / ray.d.y;
        // 4. 返回交点坐标
        return new Vec3(
            ray.o.x + ray.d.x * distance,
            ray.o.y + ray.d.y * distance,
            ray.o.z + ray.d.z * distance
        );
    }

    /**
     * 让相机平滑看向目标节点
     * @param selfNode 设置方向的节点
     * @param targetNode 目标节点
     * @param duration 平滑过渡时间（秒），默认0.3秒
     */
    public static lookAtNode(selfNode: Node, targetNode: Node, duration: number = 0.3): void {
        if(!selfNode || !targetNode) return;
        //计算水平方向（忽略Y轴差异）
        const targetPos = targetNode.worldPosition;
        const selfPos = selfNode.worldPosition;
        const direction = new Vec3();
        Vec3.subtract(direction, targetPos, selfPos);
        //关键修复：将Y轴归零计算水平方向
        const horizontalDir = new Vec3(direction.x, 0, direction.z).normalize();
        //计算垂直角度（仅Y轴差异）
        const distance = Vec3.distance(targetPos, selfPos);
        const heightDiff = targetPos.y - selfPos.y;
        const verticalAngle = math.toRadian(math.clamp(
            Math.atan2(heightDiff, Math.sqrt(direction.x * direction.x + direction.z * direction.z)) * 180 / Math.PI,
            -89, 89 // 限制在±89度内防止万向节锁
        ));
        //构建最终旋转（先水平后垂直）
        const targetRotation = new Quat();
        Quat.fromViewUp(targetRotation, horizontalDir, Vec3.UP);
        //添加垂直旋转
        const verticalRot = new Quat();
        Quat.fromEuler(verticalRot, verticalAngle, 0, 0);
        Quat.multiply(targetRotation, targetRotation, verticalRot);
        //应用旋转
        if(duration <= 0) {
            selfNode.setRotation(targetRotation);
        }else{
            const startRotation = selfNode.rotation.clone();
            tween(selfNode)
                .to(duration, { rotation: targetRotation }, {
                    onUpdate: (_, ratio: number) => {
                        const currentRot = new Quat();
                        Quat.slerp(currentRot, startRotation, targetRotation, ratio);
                        selfNode.setRotation(currentRot);
                    }
                })
                .start();
        }
    }
    
    /**
     * 根据A，B两个坐标点 和抛物线的弧度 来计算中心点坐标
     */
    public static calculateParabolaCenter(start: Vec3, end: Vec3){
        // 计算两点之间的水平距离
        const deltaX = end.x - start.x;
        // 将控制点的 x 坐标设置为两点的中点
        const controlX = (start.x + end.x) / 2;
        // 计算抛物线的最高点，使其位于两点之间的中间位置 可以根据需要调整最高点的位置
        const highestY = Math.max(start.y, end.y) + Math.abs(deltaX) / 4; 
        // 计算控制点的 y 坐标
        const controlY = highestY;
       //返回抛物线的中心坐标点
       return new Vec3(controlX, controlY);
    }

    /**
     * 在UI坐标系内生成安全的随机点
     * @param uiParent 父节点(UITransform)
     * @param center 中心点(Vec3) - 基于父节点的本地坐标
     * @param radius 半径(Number) - 单位为像素
     */
    public static randomUIPointGenerator(uiParent: Node, center: Vec3, radius: number): Vec3 {
        // 将像素半径转换为世界坐标比例
        const uiSize = uiParent.getComponent(UITransform).contentSize;
        const maxRadius = Math.min(
            uiSize.width / 2 - Math.abs(center.x),
            uiSize.height / 2 - Math.abs(center.y)
        );
        const safeRadius = Math.min(radius, maxRadius);
        //生成随机角度和距离（距离使用平方根使分布均匀）
        const angle = Math.random() * Math.PI * 2;
        const distance = Math.sqrt(Math.random()) * safeRadius;
        //转换为本地坐标
        return new Vec3(
            center.x + distance * Math.cos(angle),
            center.y + distance * Math.sin(angle),
            0 // UI节点z轴设为0
        );
    }
    
    /**
     * 生成一个随机颜色值的函数
     */
    public static getRandomColor() {
        let r = Math.floor(Math.random() * 256);
        let g = Math.floor(Math.random() * 256);
        let b = Math.floor(Math.random() * 256);
        return new Color(r, g, b);
    }
}


/**
 *  //摄像机正前方是跟随枪
    const targetPos = Game.I.player.node.worldPosition;
    const playerRotation = Game.I.player.node.worldRotation;
    const offset = new Vec3(0, 2, -8); // 上方2单位，后方5单位
    // 计算世界空间偏移
    const worldOffset = Vec3.transformQuat(new Vec3(), 
        offset, 
        playerRotation
    );
    Game.I.camera.node.worldPosition = Vec3.add(new Vec3(), targetPos, worldOffset);
    Game.I.camera.node.lookAt(targetPos);

    let crossPos: Vec3 = Utils.getScreenCenterWorldPos(Game.I.camera);
    const direction = Vec3.subtract(new Vec3(), crossPos, gun.node.worldPosition);
    direction.normalize();
    Game.I.camera.node.forward = direction;
 */