let org_console = $.extend({}, console || {}, true);
const convertArgsToString = (args) => {
    let txt = '';
    for (let item of args) {
        txt += `${(typeof item == 'object' ? JSON.stringify(item) : item)} `;
    }
    return txt;
}
console = $.extend(console, {
    log: (...args) => {
        try {
            if (window.device != undefined) {
                window.plugins.gntel.pushGLog("log", convertArgsToString(args));
            }
        } catch (ex) { }
        org_console.log(convertArgsToString(args));
    },
    info: (...args) => {
        try {
            if (window.device != undefined) {
                window.plugins.gntel.pushGLog("info", convertArgsToString(args));
            }
        } catch (ex) { }
        org_console.log(convertArgsToString(args));
    },
    warn: (...args) => {
        try {
            if (window.device != undefined) {
                window.plugins.gntel.pushGLog("warn", convertArgsToString(args));
            }
        } catch (ex) { }
        org_console.log(convertArgsToString(args));
    },
    error: (...args) => {
        try {
            if (window.device != undefined) {
                window.plugins.gntel.pushGLog("error", convertArgsToString(args));
            }
        } catch (ex) { }
        org_console.error(convertArgsToString(args));
    }
}, true);


$(() => {
    $(".tab_btn_setting > li").click(function () {
        var li = $(this).index();
        $(".tab_btn_setting > li").removeClass("on");
        $(this).addClass("on");

        $(".tab_wrap_setting > li").removeClass("show");
        $(".tab_wrap_setting > li").eq(li).addClass("show");
    });

    $('.toggle').click(function () {
        $('.mcaller_menu,.main_cont,.sub_cont,.call_cont,.view_cont,.dial_cont,.foot_btn').toggleClass('active');
    });

    // 레이어팝업 닫기
    $(document).on('click', '.popup_close:not(.fade_action), .pop_btn_close', (e) => {
        $(e.currentTarget).closest('.dim_bg').addClass('none');
    });
});

Storage.prototype.setArray = function (key, obj) {
    return this.setItem(key, JSON.stringify(obj))
}
Storage.prototype.getArray = function (key, type = 'object') {
    return JSON.parse(this.getItem(key) || (type == 'object' ? '{}' : '[]'));
}

let menu_info = localStorage.getArray('menu_info'),
    serverData = localStorage.getArray('serverData'),
    readed_contents = localStorage.getArray('readed_contents'),
    userData = window.localStorage.getArray("userData", 'object'),
    user_map = [],
    group_map = [],
    call_history_list = {},
    board_seqs = {
        "article": "new"
        , "personnel": "9"
        , "press": "43"
        , "trend": "52"
        , "occasion": "10"
    };
;

//mcid_tel_no_format, mcid_tel_name_format, mcid_mobile_no_format 이 define.js에 값이 설정되어있으면 그값을 사용함.
var mcid_tel_no_format = typeof mcid_tel_no_format == 'undefined'?'`82${parseInt(serverData.svcPrefixNo)}${u.cid_no}`': mcid_tel_no_format;
var mcid_tel_name_format = typeof mcid_tel_name_format == 'undefined'?'`[${u.dept_name}] ${u.name}`':mcid_tel_name_format;
var mcid_mobile_no_format = typeof mcid_mobile_no_format == 'undefined'?'`82${u.mobile_no.replace(/0/,"")}`':mcid_mobile_no_format;

const popAlert = (msg, callback) => {
    $('.dim_bg.alert_pop p.txt').html(msg);
    $('.dim_bg.alert_pop .btn_ok').off('click').on('click', function () {
        if (callback) callback();
        $('.dim_bg.alert_pop').addClass('none');
    });

    $('.dim_bg.alert_pop').removeClass('none');
}

const popConfirm = (msg, ok_callback, cancel_callback) => {
    $('.dim_bg.confirm_pop p.txt').html(msg);
    $('.dim_bg.confirm_pop .btn_ok').off('click').on('click', function () {
        if (ok_callback) ok_callback();
        $('.dim_bg.confirm_pop').addClass('none');
    });

    $('.dim_bg.confirm_pop .btn_cancel').off('click').on('click', function () {
        if (cancel_callback) cancel_callback();
        $('.dim_bg.confirm_pop').addClass('none');
    });

    $('.dim_bg.confirm_pop').removeClass('none');
}

const requestAstro = async (url, params, method, excloude_token = false) => {
    if (!url.startsWith('http://') && !url.startsWith('https://')) {
        url = `${serverData.server_host}/astroapi/${url}`
    }
    userData = localStorage.getArray('userData');

    let fetchoptions = {
        method: method
        , headers: {
            "Content-Type": "application/json"
            , 'Authorization': `Bearer ${userData.astroToken}`
        }
    }

    
    if (excloude_token) {
        delete fetchoptions.headers.Authorization;
    }
    
    if (method.toLowerCase() != 'get') {
        try {
            if(params instanceof FormData){
                delete fetchoptions.headers["Content-Type"];
                fetchoptions.body = params;
            }else{
                fetchoptions.body = JSON.stringify(params);
            }
        } catch (error) {
            console.warn('params is not JSON', params);
        }
    }

    try {
        let response = await fetch(
            url
            , fetchoptions
        );

        if (!response.ok) {
            throw new Error(`Response status: ${response.status}`);
        }

        let response_json = await response.json();

        try {
            if (response_json.code == 'E0001' || response_json.code == '100') {
                popAlert('인증정보가 유효하지 않습니다.', function(){
                    if(location.href.indexOf('server.html') || location.href.indexOf('login.html')){
                        return;
                    }
                    logout(true);
                });

                
            }
        } catch (e) {
            console.warn(e, url);
        }

        return response_json;

    } catch (error) {
        console.error(error);
        throw error;
    }

}

const logout = (force = false) => {
    let process = () =>{
        let data = {
            mobileNumber: userData.mobileNumber
        };

        if (userData.device_uuid) {
            data.device_uuid = userData.device_uuid;
        }

        if (userData.fcmToken) {
            data.fcmToken = userData.fcmToken;
        }

        syncUserData(data, true)

        location.href = 'login.html';
    }
    if(force){
        process()
    }else{
        popConfirm('로그아웃 하시겠습니까?', () => {
            process();
        })
    }
}

let substituteVariables = (content, variables) => {
    // 정규 표현식을 사용하여 ${key} 패턴을 찾아서 해당 변수 값으로 치환합니다
    if (variables == null || variables == undefined) {
        return content;
    }
    return content.replace(/{{(.*?)}}/g, (match, key) => {
        return variables[key.trim()] || match;
    });
}

/**
    progress : 0% ~ 100% string
*/
const onDownloadProgress = (progress) => {
    $('.loading .progressbar').css('width', progress);
    $('.loading #progress_value').text(progress);
}

const fileDownloader = {
    downloadAndOpenFile(fileUrl, fileName) {
        let loader_interval = null, seconds = 0;
        onDownloadProgress('0%');

        $('.loading').removeClass('none');

        loader_interval = setInterval(() => {
            if (seconds <= 98) {
                onDownloadProgress(`${++seconds}%`);
            }
        }, 1000);

        const fileTransfer = new FileTransfer();
        const targetPath = cordova.file.dataDirectory + fileName;

        fileTransfer.download(
            `${serverData.server_host}/astroapi/${fileUrl}`,
            targetPath,
            function (entry) {
                seconds = 99;
                onDownloadProgress(`100%`);
                setTimeout(function () {
                    clearInterval(loader_interval);
                    $('.loading').addClass('none');
                }, 500);
                console.log("Download complete: " + entry.toURL());

                cordova.plugins.fileOpener2.open(
                    entry.toURL(),
                    fileDownloader.getMimeType(fileName),
                    {
                        error: function (e) {
                            console.error("Error opening file: ", e);
                        },
                        success: function () {
                            console.log("File opened successfully");
                        }
                    }
                );
            },
            function (error) {
                $('.loading').addClass('none');
                clearInterval(loader_interval);
                console.error("Download error: ", error);
            },
            true,
            {
                headers: {
                    "accept": "*/*",
                    "Authorization": `Bearer ${userData.astroToken}`
                }
            }
        );
    },
    getMimeType: function (fileName) {
        const ext = fileName.split('.').pop().toLowerCase();
        switch (ext) {
            case 'pdf': return 'application/pdf';
            case 'doc': case 'docx': return 'application/msword';
            case 'ppt': case 'pptx': return 'application/vnd.ms-powerpoint';
            case 'xls': case 'xlsx': return 'application/vnd.ms-excel';
            case 'apk': return 'application/vnd.android.package-archive';
            case 'zip': return 'application/zip';
            case 'hwp': return 'application/hwp';
            default: return 'application/octet-stream';
        }
    }
}

const gformatter = {
    /**
     * @param {*} date 
     *  Type Date 
     *  or String(yyyy-mm-dd hh:mm:ss ** hh:mm:ss부분은 optional)
     * @param pattern String 'y-M-d h:m:s'
     */
    date: function (date, pattern) {
        let y, M, d, h, m, s;

        if (date instanceof String) {
            try {
                date = date
                    .replace(/[^\d]/g, '');
                if (date.length == 8) {
                    date = date.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
                } else if (date.length == 12) {
                    date = date.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1-$2-$3 $4:$5');
                } else if (date.length == 14) {
                    date = date.replace(/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/, '$1-$2-$3 $4:$5:$6');
                } else {
                    console.log('INVALID DATE STRING');
                    return null;
                }

                date = new Date(date);

            } catch (error) {
                console.log('INVALID DATE STRING');
            }
        }

        y = date.getFullYear().toString();
        M = (date.getMonth() + 1).toString();
        if (M.length == 1) { M = '0' + M; }
        d = date.getDate().toString();
        if (d.length == 1) { d = '0' + d; }
        h = date.getHours().toString();
        if (h.length == 1) { h = '0' + h; }
        mm = date.getMinutes().toString();
        if (mm.length == 1) { mm = '0' + mm; }
        ss = date.getSeconds().toString();
        if (ss.length == 1) { ss = '0' + ss; }

        if (pattern != undefined) {
            return pattern
                .replace(/y/, y)
                .replace(/M/, M)
                .replace(/d/, d)
                .replace(/h/, h)
                .replace(/m/, mm)
                .replace(/s/, ss);
        }
        return y + '.' + M + '.' + d + ' ' + h + ':' + mm + ':' + ss;
    }
    , tel: function (telno) {
        let r_telno = telno.replace(/[^\d]/g, '');
        switch (r_telno.length) {
            case 7:
            case 8:
                return r_telno.replace(/(\d{3,4})(\d{4})/, '$1-$2');
            case 9:
                return r_telno.replace(/(\d{2})(\d{3})(\d{4})/, '$1-$2-$3');
            case 10:
                return r_telno.startsWith('02') ? r_telno.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3') : r_telno.replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
            case 11:
                return r_telno.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
            default:
                return telno;
        }
    }
    , number: function (s) {
        return (s || '').toString().replace(/[^0-9]/g, '');
    }
    , string : function (v) {
        return v == null ? "" : String(v);
    }
}

/**
 * product_name : mcaller or callbanner or mbanner
 */
const authorizeCust = (product_name, cust_name, cust_pw, mobile_number) => {
    if (product_name == undefined) {
        try {
            product_name = (title != '' ? title.toLocaleLowerCase() : 'mcaller');
        } catch (error) {
            product_name = 'mcaller';
        }
    }

    requestAstro(`https://ent-update.lgntel.com/astroapi/verctrl/v1/appinfo/check/${product_name}/${cust_name}`, {}, 'get', true)
        .then(response => {
            let json = {};
            try {
                json = JSON.parse(response.data.info);
            } catch (error) {
                console.log('appinfo/check parse response error', response);
                json = {};
            }

            syncServerData({
                server_host: json.server_host
            });

            requestAstro(
                `mdial/v1/searchcust?cust_name=${cust_name}&cust_pw=${cust_pw}`
                , {}
                , 'post'
            )
            .then(response => {
                let code = response.code || response.errcode
                switch (code) {
                    case "0000":
                    case "0":
                        // let data = {
                        //     companyId: cust_name,
                        //     companyPw: cust_pw,
                        //     autoLogin: "",
                        //     checkIdSave: "",
                        //     //mobileNumber: mobile_number
                        // };

                        // userData = {...userData, mobileNumber: mobile_number}
                        // localStorage.setArray('userData',userData);

                        syncUserData(
                            {
                                mobileNumber: mobile_number
                            }
                        );

                        syncServerData(
                            {
                                ...response,
                                companyId: cust_name,
                                companyPw: cust_pw
                            }
                        );

                        if (typeof device != 'undefined') {
                            let OS = device.platform === "Android" ? "ANDROID" : "IOS";

                            window.plugins.gntel.setUpdateContentsUrl(`https://ent-update.lgntel.com/astroapi/verctrl/v1/check/${json.update_name}/${OS}`, function () {
                                $("#loading-overlay").fadeIn();
                                window.plugins.gntel.updateContents(function () {
                                    $("#loading-overlay").fadeOut();
                                    window.plugins.gntel.loadLocalFile("index.html");
                                }, function () {
                                    $("#loading-overlay").fadeOut();
                                    window.plugins.gntel.loadLocalFile("index.html");
                                });
                            });
                        } else {
                            location.href = 'index.html';
                        }

                        break;

                    default:
                        break;
                }

            })
        })
        ;
}

const parsePortString = (str) => {
    if (str == undefined || str.trim() == '') return '';
    return str.replace(/:?(.*)/, ':$1');
}

let syncServerData = (data, force = false) => {

    serverData = localStorage.getArray('serverData');

    let server_data_keys = [
        //회사관련
        { key: 'corp_code', jsonkey: 'corp_code' },
        { key: 'pbxType', jsonkey: 'cust_pbx_type' },
        { key: 'svcPrefixNo', jsonkey: 'cust_svc_prefix_no' },
        { key: 'cidLength', jsonkey: 'cust_cid_length' },
        { key: 'fsSvcNo', jsonkey: 'cust_fs_svc_no' },
        { key: 'cust_con_port', jsonkey: 'cust_con_port' },
        { key: 'cust_con_pbx_ip', jsonkey: 'cust_con_pbx_ip' },
        { key: 'cust_name', jsonkey: 'cust_name' },
        { key: 'cust_logo1', jsonkey: 'cust_logo1' },
        { key: 'cust_logo2', jsonkey: 'cust_logo2' },
        { key: 'cust_logo3', jsonkey: 'cust_logo3' },
        { key: 'accCodeUsed', jsonkey: 'cust_acc_code_used' },
        { key: 'accCode', jsonkey: 'cust_acc_code' },
        { key: 'digitCount', jsonkey: 'cust_digit_cnt' },
        { key: 'cust_func_code', jsonkey: 'cust_func_code' },
        { key: 'cust_func_code', jsonkey: 'funcCode' },
        { key: 'rootGroupcode', jsonkey: 'rootGroupcode' },
        { key: 'notice_used', jsonkey: 'notice_used', default: 'Y' },
        { key: 'notice_def_remains', jsonkey: 'notice_def_remains', default: '0' },
        { key: 'notice_manage_groups', jsonkey: 'notice_manage_groups'},
        { key: 'forward_call_mention', jsonkey: 'forward_call_mention' },
        { key: 'call_thru_prefix', jsonkey: 'call_thru_prefix' },
        { key: 'otp_used', jsonkey: 'otp_used' },
        { key: 'otp_type', jsonkey: 'otp_type' },
        { key: 'profile_used', jsonkey: 'profile_used' },
        { key: 'default_profile', jsonkey: 'default_profile' },
        { key: 'server_host', jsonkey: 'server_host' },
        { key: 'companyId', jsonkey: 'companyId' },
        { key: 'companyPw', jsonkey: 'companyPw' }

    ];

    if (force) {
        serverData = data;
    } else {
        server_data_keys.map(item => {
            if (data[item.jsonkey] !== undefined) {
                serverData[item.key] = data[item.jsonkey];
            } else if (item.default !== undefined) {
                serverData[item.key] = item.default;
            }
        });
    }

    localStorage.setArray('serverData', serverData);

}

let syncUserData = (data, force = false) => {
    userData = localStorage.getArray('userData');

    let user_data_keys = [
        //교환기인증 관련
        { key: 'dn', jsonkey: 'etc_no' },
        { key: 'id', jsonkey: 'id' },
        { key: 'node', jsonkey: 'node' },
        { key: 'systemType', jsonkey: 'systemType' },
        { key: 'token', jsonkey: 'token' },

        //astro checkstatus
        { key: 'etcNo', jsonkey: 'etc_no' },
        { key: 'etcNo', jsonkey: 'etcNo' },
        { key: 'userId', jsonkey: 'userId' },
        { key: 'userName', jsonkey: 'userName' },
        { key: 'noticeRegPermitted', jsonkey: 'noticeRegPermitted' },
        { key: 'deptCode', jsonkey: 'deptCode' },
        { key: 'astroToken', jsonkey: 'astroToken' },
        { key: 'allow_push', jsonkey: 'allow_push' },
        { key: 'push_start_time', jsonkey: 'push_start_time' },
        { key: 'push_end_time', jsonkey: 'push_end_time' },
        { key: 'mobileNumber', jsonkey: 'mobileNumber' },
        { key: 'prefixEx', jsonkey: 'prefixEx' },

        //firebase token
        { key: 'fcmToken', jsonkey: 'fcmToken' },

        { key: 'device_uuid', jsonkey: 'device_uuid' },

        { key: 'autoLogin', jsonkey: 'autoLogin' },

    ];

    if (force) {
        userData = data;
    } else {
        user_data_keys.map(item => {
            if (data[item.jsonkey] !== undefined) {
                userData[item.key] = data[item.jsonkey];
            } else if (item.default !== undefined) {
                userData[item.key] = item.default;
            }
        });
    }

    localStorage.setArray('userData', userData);
}

const phonenumberValidation = (inputtxt) => {
    console.log(inputtxt);
    if ($.isEmptyObject(inputtxt)) {
        return false;
    } else if (inputtxt.length > 3) {
        return true;
    } else {
        return false;
    }
}

let emptyToSpace = (v) => { return (v == null || (v + '').trim() === '') ? '' : (v + ''); }

let isAndroid = () => { return (window.device?.platform || '').toLowerCase() === 'android'; }

let isIOS = () => { return (window.device?.platform || '').toLowerCase() === 'ios'; }

let newContact = [];
let favoriteContact;
let historyContact;
let imsitime = "";
let searchImsitime = "";

// 날짜 포맷 (연-월-일 그룹 헤더용)
function getYear(el) {
    var getYear = el.split("일");
    return getYear[0] + "일";
}

// 썸네일
function getThumbImage(el) {
    return !$.isEmptyObject(el) ? el : "images/thumb.png";
}
function setThumbImage() {
    return "images/thumb.png";
}

// 문자 분류
function isAlphabet(ch) {
    var c = ch.charCodeAt(0);
    return (65 <= c && c <= 90) || (97 <= c && c <= 122);
}
function isKorean(ch) {
    var c = ch.charCodeAt(0);
    return (44032 <= c && c <= 55203);
}

function isNumber(ch) {
    var c = ch.charCodeAt(0);
    return (48 <= c && c <= 57);
}

// 초성 추출
function getCho(str) {
    var rCho = ["ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ", "ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"];
    var result = [];
    for (var i in str) {
        var ch = str.substr(i, 1);
        var n = ch.charCodeAt(0) - 0xAC00;
        var jong = n % 28;
        var jung = ((n - jong) / 28) % 21;
        var cho = ((((n - jong) / 28) - jung) / 21);
        result.push(rCho[cho]);
    }
    return result.join('');
}

// 번호 정규화
function cleanPhoneNumber(num) {
    if (!num) return "";
    // 특수문자 제거 (숫자만 남김)
    let cleaned = String(num).replace(/[^\d+]/g, "");
    // +82 → 0으로 변환
    cleaned = cleaned.replace(/^\+82/, "0");
    return cleaned;
}

// 한국형 번호 display 포맷 (cleanPhoneNumber 이후 사용)
function getKoreanNumber(phoneNumber) {
    if (!phoneNumber) return "";
    if (phoneNumber.startsWith('+')) return phoneNumber;
    if (phoneNumber.length <= 4) return phoneNumber;
    if (phoneNumber.length <= 8) return phoneNumber.replace(/([0-9]+)([0-9]{4})/, "$1-$2");
    return phoneNumber.replace(/(^02.{0}|^01.{1}|[0-9]{3})([0-9]+)([0-9]{4})/, "$1-$2-$3");
}

// 문자열 Bytes로 몇자인지 확인
const getByteLengthOfString = function (s, b, i, c) {
    for (b = i = 0; (c = s.charCodeAt(i++)); b += c >> 11 ? 3 : c >> 7 ? 2 : 1);
    return b;
};

function nomalizeNumber(number) {
    return $.trim(number).replace('+82', '0').replace(/[^0-9]/g, '');
}

function getUserNumber() {
    //var serverData = window.localStorage.getArray("serverData");
    //alert("serverData"+JSON.stringify(serverData));
    var accCodeUsed = serverData.accCodeUsed;
    var accCode = serverData.accCode;
    var digitCount = serverData.digitCount;
    var mobileNumber = userData.mobileNumber;
    digitCount = parseInt(digitCount);
    if (!$.isEmptyObject(mobileNumber)) {
        if (mobileNumber.length > digitCount) {
            if (accCodeUsed == "Y") {
                mobileNumber = accCode + mobileNumber;
            }
        }

        mobileNumber = encodeURIComponent(mobileNumber);
        return mobileNumber;
    } else {
        popAlert("HP번호가 없습니다.", function () {
            goToSetting()
        });
        return "";
    }
}

/* ===== File API with silent LocalStorage fallback (browser-friendly) ===== */


let FS_AVAILABLE = typeof window.requestFileSystem !== "undefined";
const LS_KEY = (name) => `__fs__:${name}`;

// 내부 공통: LS 쓰기(문자열/Blob 모두 지원)
function writeToLocalStorage(filename, data, callback) {
    const key = LS_KEY(filename);

    // 문자열이면 바로 저장
    if (typeof data === "string") {
        try {
            localStorage.setItem(key, data);
            if (callback) callback();
        } catch (e) {
            if (callback) callback(e);
        }
        return;
    }

    // Blob이면 텍스트로 변환해 저장
    if (data instanceof Blob) {
        try {
            const reader = new FileReader();
            reader.onload = function () {
                try {
                    localStorage.setItem(key, reader.result || "");
                    if (callback) callback();
                } catch (e) {
                    if (callback) callback(e);
                }
            };
            reader.onerror = function () {
                if (callback) callback(reader.error || new Error("Blob read failed"));
            };
            reader.readAsText(data);
        } catch (e) {
            if (callback) callback(e);
        }
        return;
    }

    // 그 외 타입은 문자열로 강제 캐스팅
    try {
        localStorage.setItem(key, String(data ?? ""));
        if (callback) callback();
    } catch (e) {
        if (callback) callback(e);
    }
}

// 내부 공통: LS 읽기
function readFromLocalStorage(filename, callback) {
    try {
        const key = LS_KEY(filename);
        const val = localStorage.getItem(key);
        if (callback) callback(val != null ? val : "{}");
    } catch {
        if (callback) callback("{}");
    }
}

const toast = (msg) => {
    $("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h3>" + msg + "</h3></div>")
        .css({
            "display": "block",
            "position": "fixed",
            "padding": "12px",
            "text-align": "center",
            "color": "#FFF",
            "width": "290px",
            "height": "40px",
            "background": "#00000080",
            "font-weight": "normal",
            "font-size": "15px",
            "border-radius": "25px",
            "left": ($(window).width() - 290) / 2,
            "top": $(window).height() - 140,
            "z-index": 999
        })
        .appendTo(".wrap").delay(1800)
        .fadeOut(100, function () {
            $(this).remove();
        });
}


window.writeFile = function writeFile(filename, data, callback) {
    FS_AVAILABLE = typeof window.requestFileSystem !== "undefined";
    if (FS_AVAILABLE) {
        // Cordova/지원 브라우저: 기존 FileSystem 로직
        let type = window.PERSISTENT;
        let size = 5 * 1024 * 1024;

        function write() {
            window.requestFileSystem(type, size, function (fs) {
                fs.root.getFile(filename, { create: true, exclusive: false }, function (fileEntry) {
                    fileEntry.createWriter(function (fileWriter) {
                        fileWriter.onwriteend = function () { if (callback) callback(); };
                        fileWriter.onerror = function (e) { if (callback) callback(e); };
                        fileWriter.write(data);
                    }, function (error) { if (callback) callback(error); });
                }, function (error) { if (callback) callback(error); });
            }, function (error) { if (callback) callback(error); });
        }

        // 기존 파일이 있으면 삭제 후 쓰기
        window.requestFileSystem(type, size, function (fs) {
            fs.root.getFile(filename, { create: false }, function (fileEntry) {
                fileEntry.remove(function () { write(); }, function () { write(); });
            }, function () { write(); });
        }, function () { write(); });
    } else {
        writeToLocalStorage(filename, data, callback);
    }
};

// ---------- writeFileX ----------
window.writeFileX = function writeFileX(filename, data, callback) {
    FS_AVAILABLE = typeof window.requestFileSystem !== "undefined";
    if (FS_AVAILABLE) {
        let type = window.PERSISTENT;
        let size = 10 * 1024 * 1024;
        window.requestFileSystem(type, size, function (fs) {
            fs.root.getFile(filename, { create: true, exclusive: false }, function (fileEntry) {
                fileEntry.createWriter(function (fileWriter) {
                    fileWriter.onwriteend = function () { if (callback) callback(); };
                    fileWriter.onerror = function (e) { if (callback) callback(e); };
                    fileWriter.write(data);
                }, function (error) { if (callback) callback(error); });
            }, function (error) { if (callback) callback(error); });
        }, function (error) { if (callback) callback(error); });
    } else {
        writeToLocalStorage(filename, data, callback);
    }
};

// ---------- readFile ----------
window.readFile = function readFile(filename, callback) {
    FS_AVAILABLE = typeof window.requestFileSystem !== "undefined";
    if (FS_AVAILABLE) {
        let type = window.PERSISTENT;
        let size = 50 * 1024 * 1024;
        window.requestFileSystem(type, size, function (fs) {
            fs.root.getFile(filename, { create: false, exclusive: false }, function (fileEntry) {
                fileEntry.file(function (file) {
                    const reader = new FileReader();
                    reader.onloadend = function (e) {
                        try {
                            const arrayBuffer = new Uint8Array(e.target.result || new ArrayBuffer(0));
                            const enc = new TextDecoder("utf-8");
                            if (callback) callback(enc.decode(arrayBuffer));
                        } catch {
                            if (callback) callback("{}");
                        }
                    };
                    reader.onerror = function () { if (callback) callback("{}"); };
                    reader.readAsArrayBuffer(file);
                }, function () { if (callback) callback("{}"); });
            }, function () { if (callback) callback("{}"); });
        }, function () { if (callback) callback("{}"); });
    } else {
        readFromLocalStorage(filename, callback);
    }
};
