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,.address_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"
    };

;

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 {
            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') {
                alert('인증정보가 유효하지 않습니다.');

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

/**
 * 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(
                `${serverData.server_host}/astroapi/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,
                        //     prefixNumber: "1000"
                        // };

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

                        syncUserData(
                            {
                                mobileNumber: mobile_number
                            }
                        );

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

                        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: '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' },
        { key: 'prefixNumber', jsonkey: 'prefixNumber' },

    ];

    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' },

    ];

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

function createAddress() {
    // 로컬 읽기
    try { newContact = window.localStorage.getArray("contact") || []; } catch (e) { newContact = []; }
    try { favoriteContact = window.localStorage.getArray("favorite") || []; } catch (e) { favoriteContact = []; }

    // 단말 연락처 수집
    if (!navigator.contactsPhoneNumbers || !navigator.contactsPhoneNumbers.list) {
        // 플러그인 미탑재 시 저장된 로컬 기준으로 그리기
        printFavorites();
        printAddress();
        bindSimpleEvents();
        return;
    }

    if (window.device != undefined) {
        navigator.contactsPhoneNumbers.list(function (contacts) {
            // 전처리
            for (var i = 0; i < contacts.length; i++) {
                var c = contacts[i];

                // 이름 없으면 제거
                if (!c || !c.displayName) { contacts.splice(i, 1); i--; continue; }

                // iOS 이름 합치기
                try {
                    if ((device.platform || '').toLowerCase() === 'ios') {
                        c.displayName = (c.lastName || '') + (c.firstName || '') || c.displayName;
                    }
                } catch (e) { }

                // 초성
                c.nameCho = getCho(c.displayName);

                // 썸네일 기본
                if (!c.thumbnail) c.thumbnail = setThumbImage();

                // 번호 정리
                if (Array.isArray(c.phoneNumbers)) {
                    for (var j = 0; j < c.phoneNumbers.length; j++) {
                        var p = c.phoneNumbers[j] || {};
                        var raw = p.normalizedNumber || p.number || "";
                        var clean = cleanPhoneNumber(raw);
                        c.phoneNumbers[j].normalizedNumber = clean;
                        c.phoneNumbers[j].number = getKoreanNumber(clean);
                        if (!c.phoneNumbers[j].type) c.phoneNumbers[j].type = "기타";
                    }
                } else {
                    c.phoneNumbers = [];
                }
            }

            // 정렬
            contacts.sort(function (a, b) {
                var A = $.trim(a.displayName), B = $.trim(b.displayName);
                if (isKorean(A)) return isKorean(B) ? (A < B ? -1 : 1) : -1;
                if (isAlphabet(A)) return isKorean(B) ? 1 : (isAlphabet(B) ? (A < B ? -1 : 1) : -1);
                if (isNumber(A)) return (isKorean(B) || isAlphabet(B)) ? 1 : (isNumber(B) ? (A < B ? -1 : 1) : -1);
                return (!isNumber(B) && !isAlphabet(B) && !isKorean(B)) ? (A < B ? -1 : 1) : 1;
            });

            // 저장
            newContact = contacts;
            window.localStorage.setArray("contact", newContact);

            if (!Array.isArray(favoriteContact)) {
                favoriteContact = [];
                window.localStorage.setArray("favorite", favoriteContact);
            }

            // 렌더
            printFavorites();
            printAddress();

            // 이벤트 바인딩(체크박스/통화)
            bindSimpleEvents();

        }, function (err) {
            console.error(err);
            // 실패해도 로컬 기준으로 그리기
            printFavorites();
            printAddress();
            bindSimpleEvents();
        });
    } else {
        newContact = window.localStorage.getArray("contact");
        // 렌더
        printFavorites();
        printAddress();

        // 이벤트 바인딩(체크박스/통화)
        bindSimpleEvents();
    }


    // 이벤트(즐겨찾기/통화)
    function bindSimpleEvents() {
        // 즐겨찾기 토글
        $("#address_list_favorites, #address_list_all")
            .off("change.addrFav")
            .on("change.addrFav", ".favchk", function (e) {
                e.stopPropagation();
                var id = String($(this).closest("li").data("id") || "");
                if (!id) return;
                var idx = favoriteContact.indexOf(id);
                if (idx === -1) favoriteContact.push(id);
                else favoriteContact.splice(idx, 1);
                window.localStorage.setArray("favorite", favoriteContact);
                printFavorites();
                // 전체 목록에도 체크 반영
                $("#address_list_all li[data-id='" + id + "'] .favchk").prop("checked", favoriteContact.indexOf(id) > -1);
            });

        // 통화 버튼: openCallPopup(contact)가 있으면 사용
        // $("#address_list_favorites, #address_list_all")
        //     .off("click.addrCall")
        //     .on("click.addrCall", "a.call", function (e) {
        //         e.preventDefault();
        //         var $li = $(this).closest("li");
        //         var contact = $li.data("contactObj");
        //         if (!contact) {
        //             try { contact = JSON.parse($li.attr("data-contact")); } catch (e) { }
        //         }
        //         if (!contact) return;

        //         if (typeof openCallPopup === "function") {
        //             openCallPopup(contact);
        //         } else {
        //             // 대안: 기본 번호를 콘솔로 확인만
        //             var primary = (Array.isArray(contact.phoneNumbers) && contact.phoneNumbers[0]) ? contact.phoneNumbers[0] : null;
        //             console.log("call:", contact.displayName, primary ? (primary.normalizedNumber || primary.number) : "(번호없음)");
        //         }
        //     });
    }
}

function printFavorites() {
    if (!Array.isArray(favoriteContact)) {
        try { favoriteContact = window.localStorage.getArray("favorite") || []; } catch (e) { favoriteContact = []; }
    }
    var $wrap = $("#address_list_favorites");
    $wrap.empty();
    if (!Array.isArray(newContact) || newContact.length === 0) return;

    // id → contact 맵
    var map = {};
    for (var i = 0; i < newContact.length; i++) {
        var c = newContact[i];
        if (c && c.id != null) map[String(c.id)] = c;
    }

    for (var k = 0; k < favoriteContact.length; k++) {
        var favId = String(favoriteContact[k] || "");
        var c = map[favId];
        if (!c || !c.displayName || !Array.isArray(c.phoneNumbers) || c.phoneNumbers.length === 0) continue;

        var p = c.phoneNumbers[0] || {};
        var numDisp = p.number || p.normalizedNumber || "";

        var favInputId = "fav_" + c.id; // ★ label for 연결
        var $li = $(`
      <li data-id="${c.id}">
        <div class="accordion_list">
          <div class="fav">
            <input type="checkbox" id="${favInputId}" class="favchk" checked>
            <label for="${favInputId}" class="favbtn" aria-label="즐겨찾기"></label>
          </div>
          <div class="thumb" style="background-image:url('${getThumbImage(c.thumbnail)}')"></div>
          <div class="who">
            <div class="name"></div>
            <div class="num"></div>
          </div>
          <a href="#" class="call" aria-label="통화"></a>
        </div>
      </li>
    `);
        $li.find(".name").text(c.displayName || "");
        $li.find(".num").text(numDisp || "");
        $li.attr("data-contact", JSON.stringify(c));
        $li.data("contactObj", c);

        $wrap.append($li);
    }
}

// 전체 렌더 대신 “첫 페이지만” 붙이는 래퍼로 유지
function printAddress() {
  const $wrap = $('#address_list_all');
  if ($wrap.children().length === 0) {
    drawAddressList(0);
  }
}

// 전체 렌더
// function printAddress() {
//     var $wrap = $("#address_list_all");
//     $wrap.empty();
//     if (!Array.isArray(newContact) || newContact.length === 0) return;

//     // 최신 즐겨찾기 반영
//     if (!Array.isArray(favoriteContact)) {
//         try { favoriteContact = window.localStorage.getArray("favorite") || []; } catch (e) { favoriteContact = []; }
//     }
//     var favSet = {};
//     for (var i = 0; i < favoriteContact.length; i++) favSet[String(favoriteContact[i])] = true;

//     for (var j = 0; j < newContact.length; j++) {
//         var c = newContact[j];
//         if (!c || !c.displayName || !Array.isArray(c.phoneNumbers) || c.phoneNumbers.length === 0) continue;

//         var p = c.phoneNumbers[0] || {};
//         var numDisp = p.number || p.normalizedNumber || "";
//         var checked = !!favSet[String(c.id || "")];

//         var favInputId = "fav_" + c.id; // ★ label for 연결
//         var $li = $(`
//       <li data-id="${c.id}">
//         <div class="accordion_list">
//           <div class="fav">
//             <input type="checkbox" id="${favInputId}" class="favchk"${checked ? " checked" : ""}>
//             <label for="${favInputId}" class="favbtn" aria-label="즐겨찾기"></label>
//           </div>
//           <div class="thumb" style="background-image:url('${getThumbImage(c.thumbnail)}')"></div>
//           <div class="who">
//             <div class="name"></div>
//             <div class="num"></div>
//           </div>
//           <a href="#" class="call" aria-label="통화"></a>
//         </div>
//       </li>
//     `);
//         $li.find(".name").text(c.displayName || "");
//         $li.find(".num").text(numDisp || "");
//         $li.attr("data-contact", JSON.stringify(c));
//         $li.data("contactObj", c);

//         $wrap.append($li);
//     }
// }


// 로컬에 저장된 주소록 로드
function callAddress() {
    newContact = window.localStorage.getArray("contact");
}

// 문자열 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;
};

let itcdelay = (function () {
    // Function
    let itcTimer = 0;
    return function (callback, ms) {
        clearTimeout(itcTimer);
        itcTimer = setTimeout(callback, ms);
    };
})();

// 즐겨찾기 토글
function toggleFavorite(id) {
    favoriteContact = window.localStorage.getArray("favorite") || [];
    var idx = favoriteContact.indexOf($.trim(id));
    if (idx >= 0) favoriteContact.splice(idx, 1);
    else favoriteContact.push($.trim(id));
    window.localStorage.setArray("favorite", favoriteContact);
}


// 번호로 연락처 검색
function searchPad() {
    const rawText =
        (typeof window.callNum === "string" && window.callNum) ||
        ($("#display_number").text() || "");
    const query = String(rawText).replace(/\D/g, ""); // 숫자만

    const $preview = $("#apx_preview");
    const $more = $("#apx_more_button");

    if (!query) {
        window.searchContactByNumber = [];
        $preview.addClass("is-empty").find(".apx_unit").remove();
        $more.attr("data-total", "0").text("0").hide();
        if (typeof closeSearchList === "function") closeSearchList();
        return [];
    }

    const contacts = Array.isArray(window.newContact)
        ? window.newContact
        : (typeof window.localStorage?.getArray === "function"
            ? (window.localStorage.getArray("contact") || [])
            : []);

    function toNormalizedContact(contact) {
        const phoneList = Array.isArray(contact?.phoneNumbers) ? contact.phoneNumbers : [];
        const normalizedPhones = phoneList.map((phoneEntry) => {
            const normalizedNumber = String(phoneEntry?.normalizedNumber || phoneEntry || "").replace(/\D/g, "");
            const displayNumber = phoneEntry?.display ||
                (typeof formatPhoneDisplay === "function" ? formatPhoneDisplay(normalizedNumber) : normalizedNumber);
            return { normalizedNumber, display: displayNumber };
        });

        const thumbUrl =
            (typeof window.getThumbImage === "function" && contact?.thumbnail)
                ? window.getThumbImage(contact.thumbnail)
                : (contact?.thumbUrl || "");

        return {
            userId: contact?.userId || contact?.id || "",
            name: contact?.displayName || contact?.name || "",
            thumbUrl,
            phoneNumbers: normalizedPhones
        };
    }

    const matched = [];
    for (const c of contacts) {
        if (!c) continue;
        const nc = toNormalizedContact(c);
        const has = nc.phoneNumbers.some(ph => String(ph.normalizedNumber || "").includes(query));
        if (has) matched.push(nc);
    }

    window.searchContactByNumber = matched;

    if (matched.length === 0) {
        $preview.addClass("is-empty").find(".apx_unit").remove();
        $more.attr("data-total", "0").text("0").hide();
    } else {
        $preview.removeClass("is-empty");
        $more
            .attr("data-total", String(matched.length))
            .text(String(matched.length))
            .toggle(matched.length >= 2); // 핵심: 1개 이하면 숨김
    }

    return matched;
}


// 다이얼 결과 리스트 팝업용 선택 목록
function selectDialNumber(contacts, keyword) {
    var phNumArrHtml = "";
    for (var i = 0; i < contacts.length; i++) {
        var tel = contacts[i].phoneNumbers.find(item => new RegExp(keyword).test(item.normalizedNumber));
        var num = tel.normalizedNumber;

        var idx = num.indexOf(keyword);
        var numHtml = num;
        if (idx >= 0) {
            numHtml = num.substring(0, idx) + "<span>" + num.substring(idx, idx + keyword.length) + "</span>" + num.substring(idx + keyword.length);
        }

        phNumArrHtml += "<li class='dial_pad_list' data-id='" + contacts[i].id + "' data-telephonenumber='" + num + "'>";
        phNumArrHtml += "<span class='portrait'><img src='" + getThumbImage(contacts[i].thumbnail) + "'></span>";
        phNumArrHtml += "<h4>" + contacts[i].displayName + "</h4>";
        phNumArrHtml += "<p>" + tel.type + " " + numHtml + "</p>";
        phNumArrHtml += "</li>";
    }

    var alertEl = "<div class='bg_calling on'><div class='pop_calling'>";
    alertEl += "<h3>검색결과<button type='button' class='close_calling'><img src='images/btn_close_wh.png'></button></h3>";
    alertEl += "<div class='phone_nums'><ul class='list'>" + phNumArrHtml + "</ul></div>";
    alertEl += "</div></div>";
    $(".ui-mobile-viewport").append(alertEl);
    $("#sel_num_1").prop("checked", true);
}

// 연락처 선택 시 통화 방식 선택 팝업
function selectCall(contact, num, type) {
    var phNumArr = contact.phoneNumbers;
    var phNumArrHtml = "";

    if ($.isEmptyObject(num)) {
        for (var j = 0; j < phNumArr.length; j++) {
            var k = j + 1;
            phNumArrHtml += "<input type='radio' name='sel_num' id='sel_num_" + k + "' value='" + JSON.stringify(phNumArr[j]) + "'>";
            phNumArrHtml += "<label for='sel_num_" + k + "'>" + phNumArr[j].type + " " + phNumArr[j].number + "</label>";
        }
    } else {
        var anonymousPhoneNumber = { number: num, normalizedNumber: num, type: "기타" };
        phNumArrHtml = "<input type='radio' name='sel_num' id='sel_num_1' value='" + JSON.stringify(anonymousPhoneNumber) + "'>";
        phNumArrHtml += "<label for='sel_num_1'>" + type + " " + num + "</label>";
    }

    var buttonHtml = "";
    if (serverData.cust_func_code == "1") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_01'><img src='images/biz_call_btn.png'>콜백</button>";
    } else if (serverData.cust_func_code == "2") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/biz_call_btn.png'>안심통화</button>";
    } else if (serverData.cust_func_code == "3") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/icon_telephone.png'>안심통화</button>";
    } else if (serverData.cust_func_code == "4") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/icon_telephone.png'>안심통화</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_02'><img src='images/icon_mobile.png'>휴대폰통화</button>";
    } else if (serverData.cust_func_code == "5") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/icon_telephone.png'>안심통화</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_02'><img src='images/icon_mobile.png'>5G통화</button>";
    } else if (serverData.cust_func_code == "6") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_01'><img src='images/biz_call_btn.png'>콜백</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/biz_call_btn.png'>안심통화</button>";
    } else if (serverData.cust_func_code == "7") {
        buttonHtml = "<div class='calling_btn'>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_01'><img src='images/biz_call_btn.png'>콜백</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/biz_call_btn.png'>안심통화</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_02'><img src='images/icon_mobile.png'>휴대폰통화</button>";
    } else if (serverData.cust_func_code == "8") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_03'><img src='images/icon_telephone.png'>내선통화</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_02'><img src='images/icon_mobile.png'>휴대폰통화</button>";
    } else if (serverData.cust_func_code == "9") {
        buttonHtml = "<div class='calling_btn btn_01'><button type='button' data-id='" + contact.id + "' class='btn_calling_01'><img src='images/biz_call_btn.png'>콜백</button>";
        buttonHtml += "<button type='button' data-id='" + contact.id + "' class='btn_calling_02'><img src='images/icon_mobile.png'>휴대폰통화</button>";
    }

    var alertEl = "<div class='bg_calling on'><div class='pop_calling'>";
    alertEl += "<h3>" + contact.displayName + "<button type='button' class='close_calling'><img src='images/btn_close_wh.png'></button></h3>";
    alertEl += "<div class='phone_nums'>" + phNumArrHtml + "</div>";
    alertEl += buttonHtml + "</div></div>";
    $(".ui-mobile-viewport").append(alertEl);
    $("#sel_num_1").prop("checked", true);

    if ($('.bg_calling div.calling_btn>button').length == 1 &&
        $('.bg_calling div.phone_nums>input[type=radio]').length == 1) {
        $('.bg_calling div.calling_btn>button:eq(0)').trigger('click');
    }
}

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

function addCallHistory(number, type) {
    console.log('addCallHistory ::', number, '::', type);

    call_history_list.unshift({
        number: nomalizeNumber($.trim(number)),
        date_time: gformatter.date(new Date(), 'y-M-d h:m:s')
    });
    writeFile('call_history.json', JSON.stringify({ "callHistory": call_history_list }), (r) => {
        console.log('success write call_history.json');
    });
}

// 콜쓰루
function callthrough(phoneNumber, type) {
    const toNumber = getTelephoneNumber(phoneNumber);
    // const fromNumber = getUserNumber();
    // if ($.isEmptyObject(fromNumber)) return;
    // if ($.isEmptyObject(userData.prefixEx)) {
    //     console.log("설정에서 내선지역번호를 저장 후 다시 시도해주세요");
    //     return;
    // }
    // // console.log("toNumber : " + toNumber + ", fromNumber : " + fromNumber);
    const params = {
        url: `https://${serverData.cust_con_pbx_ip}:${serverData.cust_con_port}/ipxapi/v1/users/${userData.id}/callthrough?from=${serverData.call_thru_prefix}${userData.mobileNumber}&to=${toNumber}`,
        token: window.localStorage.getArray("userData").token
    };
    console.log(params);

    requestToCm(params, function (response) {
        if (response.data.error.message === "OK") {
            addCallHistory(phoneNumber, type);
            if ((device.platform || '').toLowerCase() === 'android') {
                window.plugins.gntel.setCallToNumber(phoneNumber);
                cordova.plugins.phonedialer.call(
                    userData.prefixEx,
                    function (success) {
                        console.log('callthrough', 'Dialing succee  ded');
                    },
                    function (err) {
                        if (err === "empty") console.log("Unknown phone number");
                        else console.error("Dialer Error: " + err);
                    }
                );
            } else {
                cordova.InAppBrowser.open(`tel:${userData.prefixEx}`, "_system");
            }
        } else {
            console.error("Callthrough Error: " + response.data.error.message);
        }
    });
}

// 클릭투콜
function clickToCall(phoneNumber, type) {
    if (phoneNumber.length === serverData.cidLength) {
        let etcNo = null;
        if (serverData.pbxType === 'CM') {
            for (var key in user_map) {
                var u = user_map[key];
                if (u.cid_no === phoneNumber) {
                    if (u.etc_no) etcNo = u.etc_no;
                    break;
                }
            }
        }
        phoneNumber = etcNo ? etcNo : serverData.svcPrefixNo + phoneNumber;
    }

    if ((device.platform || '').toLowerCase() === 'android') {
        cordova.plugins.phonedialer.call(
            phoneNumber,
            function () { addCallHistory(phoneNumber, type); },
            function (err) {
                if (err === "empty") console.log("Unknown phone number");
                else console.error("Dialer Error: " + err);
            }
        );
    } else {
        cordova.InAppBrowser.open(`tel:${phoneNumber}`, "_system");
        try { addCallHistory(phoneNumber, type); } catch (e) { console.warn(e); }
    }
}

// 콜백
function callBack(phoneNumber, type) {
    const toNumber = phoneNumber;
    const fromNumber = userData.mobileNumber;
    if ($.isEmptyObject(fromNumber)) return;

    const params = {
        url: `https://${serverData.cust_con_pbx_ip}:${serverData.cust_con_port}/ipxapi/v1/users/${userData.id}/callback?from=${fromNumber}&to=${toNumber}`,
        token: window.localStorage.getArray("userData").token
    };

    requestToCm(params, function (response) {
        if (response.data.error.message === "OK") {
            console.log("내 휴대폰으로 발신 요청 중입니다. 잠시만 기다려주세요");
            addCallHistory(phoneNumber, type);
        } else {
            console.error("Callback Error: " + response.data.error.message);
        }
    });
}

// 전화 끊기
function callDisconnect(fromNumber, toNumber, cancelCallback) {
    const params = {
        url: `https://${serverData.cust_con_pbx_ip}:${serverData.cust_con_port}/ipxapi/v1/users/${userData.id}/disconnect?from=${fromNumber}&to=${toNumber}`,
        token: window.localStorage.getArray("userData").token
    };

    requestToCm(params, function (response) {
        if (response.data.error.message === "OK") {
            if (typeof cancelCallback === "function") cancelCallback();
        } else {
            console.error("Disconnect Error: " + response.data.error.message);
        }
    });
}

function getTelephoneNumber(el) {
    var telephoneNumber = el;
    telephoneNumber = telephoneNumber.replace('+82', '0');
    //var serverData = window.localStorage.getArray("serverData");
    var accCodeUsed = serverData.accCodeUsed;
    var accCode = serverData.accCode;
    var digitCount = serverData.digitCount;
    digitCount = parseInt(digitCount);
    console.log("digitCount:" + digitCount);
    console.log("accCode:" + accCode);
    console.log("accCodeUsed:" + accCodeUsed);
    if (telephoneNumber.length > digitCount) {
        console.log("digitCount1:" + telephoneNumber.length);
        if (accCodeUsed == "Y") {
            telephoneNumber = accCode + telephoneNumber;
        }
    }
    console.log("telephoneNumber" + telephoneNumber);

    telephoneNumber = encodeURIComponent(telephoneNumber);
    return telephoneNumber;
}

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 {
        // 브라우저: 조용히 LocalStorage로 폴백 (콘솔 에러 없음)
        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);
    }
};
