(function (global, $) {
    'use strict';

    global.G = global.G || {};

    // 로컬스토리지
    var L = {
        getJSON: function (key, defVal) {
            try {
                if (window.localStorage && typeof window.localStorage.getArray === 'function') {
                    var v = window.localStorage.getArray(key);
                    if (v == null) return defVal;
                    if (typeof v === 'object') return v;
                    if (typeof v === 'string') { try { return JSON.parse(v); } catch (e) { return defVal; } }
                    return defVal;
                } else {
                    var s = window.localStorage ? window.localStorage.getItem(key) : null;
                    return s ? JSON.parse(s) : defVal;
                }
            } catch (e) { return defVal; }
        },
        setJSON: function (key, val) {
            try {
                if (window.localStorage && typeof window.localStorage.setArray === 'function') {
                    window.localStorage.setArray(key, val);
                } else if (window.localStorage) {
                    window.localStorage.setItem(key, JSON.stringify(val));
                }
            } catch (e) { }
        },
        getArray: function (key) { var v = this.getJSON(key, []); return Array.isArray(v) ? v : []; },
        getObject: function (key) { var v = this.getJSON(key, {}); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : {}; }
    };

    // 상태
    var groupSaveDate = (window.localStorage && window.localStorage.getItem("setGroupLastDate")) || "";
    var lastGroupDateTime = "";
    var organization_user_map = {};
    var organization_group_map = L.getObject("group_map");
    var dept1_group = L.getArray("dept1_group");
    var favo_map = { favo_user: {}, favo_group: {} };
    var favo_template = "", group_template = "", person_template = "";
    var groupParents = {};

    // 유틸
    function emptyToSpace(v) { var s = gformatter.string(v).trim(); return s ? s : ""; }
    function fmtPhone(s) { try { return global.gformatter.tel(s); } catch (e) { return s; } }
    function getThumbUrl(u) {
        if (u && u.photo_path_url) {
            let host = serverData.server_host;
            return `${host}${u.photo_path_url}`;
        }
        try { return setThumbImage(); } catch (e) { return ""; }
    }
    function formatDisplayGroup(group_names, sep) {
        if (!group_names) return "";
        var exp = '${g-2}/${g-1}';
        var arr = String(group_names).split(sep || '@@');
        if (!arr.length) return "";
        while (exp.indexOf("${") > -1) {
            var s = exp.indexOf("${");
            var e = exp.indexOf("}", s);
            if (e < 0) break;
            var t = exp.substring(s, e + 1);
            var n = parseInt(t.replace(/[$ { } g]/g, ''), 10);
            var gname = "";
            if (n > 0) gname = arr[n] || "";
            else if (n < 0) gname = arr[arr.length + n] || "";
            exp = exp.replace(t, gname);
        }
        if (exp.indexOf("/") === 0) exp = exp.substring(1);
        return exp;
    }

    // 플랫폼
    // function isAndroid() { return gformatter.string(global.device && global.device.platform).toLowerCase() === 'android'; }
    function isIOS() { return gformatter.string(global.device && global.device.platform).toLowerCase() === 'ios'; }

    // 안전 다이얼
    // function safeDial(number) {
    //     var num = gformatter.number(number);
    //     if (!num) return;
    //     if (isAndroid() && global.cordova && cordova.plugins && cordova.plugins.phonedialer) {
    //         cordova.plugins.phonedialer.call(num, function () { }, function (err) {
    //             if (err === 'empty') console.log('Unknown phone number');
    //             else if (typeof popAlert === 'function') popAlert('Dialer Error: ' + err);
    //             else alert('Dialer Error: ' + err);
    //         });
    //         return;
    //     }
    //     var uri = 'tel:' + num;
    //     if (global.cordova && cordova.InAppBrowser) cordova.InAppBrowser.open(uri, '_system');
    //     else global.location.href = uri;
    // }

    // 문자
    function safeSMS(number) {
        var num = gformatter.number(number);
        if (!num) return;
        var uri = 'sms:' + num;
        if (global.cordova && cordova.InAppBrowser) cordova.InAppBrowser.open(uri, '_system');
        else global.location.href = uri;
    }

    // 스크롤 락
    function lockScroll() {
        var y = global.scrollY || document.documentElement.scrollTop || 0;
        $('body').attr('data-scrollY', y);
        $('html, body').addClass('no-scroll');
        $('body').css({ position: 'fixed', top: '-' + y + 'px', width: '100%', overflow: 'hidden', touchAction: 'none' });
    }
    function unlockScroll() {
        var y = parseInt($('body').attr('data-scrollY') || '0', 10);
        $('body').removeAttr('data-scrollY');
        $('html, body').removeClass('no-scroll');
        $('body').css({ position: '', top: '', width: '', overflow: '', touchAction: '' });
        global.scrollTo(0, isNaN(y) ? 0 : y);
    }

    // ===== 통화 선택 팝업 =====
    // function ensureCallPopup() {
    //     var $layer = $(".dim_bg.call_pop");
    //     if ($layer.length) return $layer;

    //     $layer = $(`
    //         <div class="dim_bg call_pop" style="display:none;">
    //           <div class="popup" role="dialog" aria-modal="true">
    //             <h3 class="call_title"></h3>
    //             <span class="popup_close" aria-label="닫기"></span>
    //             <div class="popup_container">
    //               <form class="call_form" method="post" action="">
    //                 <div class="cont_box">
    //                   <ul class="call_num" id="call_num_list"></ul>
    //                 </div>
    //                 <p class="popup_btn">
    //                   <button type="button" class="btn blue" id="btn_callthrough">안심통화</button>
    //                   <button type="button" class="btn blue" id="btn_clicktocall">휴대폰 통화</button>
    //                 </p>
    //               </form>
    //             </div>
    //           </div>
    //         </div>
    //     `);
    //     $("body").append($layer);
    //     bindCallPopupEvents($layer);
    //     return $layer;
    // }

    // function bindCallPopupEvents($layer) {
    //     $layer.off("click.org_callpop_close").on("click.org_callpop_close", ".popup_close", function (e) {
    //         e.preventDefault();
    //         closeCallPopup();
    //     });
    //     $layer.off("click.org_callpop_bg").on("click.org_callpop_bg", function (e) {
    //         if (e.target !== this) return;
    //         closeCallPopup();
    //     });

    //     $layer.off("click.org_callthrough").on("click.org_callthrough", "#btn_callthrough", function (e) {
    //         e.preventDefault();
    //         var ctx = $layer.data("ctx") || {};
    //         var sel = getSelectedNumber($layer);
    //         if (!sel) return;

    //         var prefixedNumber = (ctx.dialPrefix ? String(ctx.dialPrefix) : "") + sel.number;
    //         console.log(prefixedNumber);
    //         try { callthrough(String(ctx.callId || sel.id || ""), prefixedNumber, sel.type || "기타"); } catch (err) { }
    //         closeCallPopup();
    //     });

    //     $layer.off("click.org_clicktocall").on("click.org_clicktocall", "#btn_clicktocall", function (e) {
    //         e.preventDefault();
    //         var ctx = $layer.data("ctx") || {};
    //         var sel = getSelectedNumber($layer);
    //         if (!sel) return;

    //         var prefixedNumber = (ctx.dialPrefix ? String(ctx.dialPrefix) : "") + sel.number;
    //         try { clickToCall(String(ctx.callId || sel.id || ""), prefixedNumber, sel.type || "기타"); } catch (err) { }
    //         closeCallPopup();
    //     });

    //     function getSelectedNumber($root) {
    //         var $checked = $root.find('input[name="num"]:checked');
    //         if (!$checked.length) return null;
    //         return {
    //             id: $checked.data("id") || "",
    //             number: $checked.val() || "",
    //             type: $checked.data("type") || "기타"
    //         };
    //     }
    // }

    // 트리 컨테이너
    function ensureOrgTree() {
        var $root = $('.call_cont.active');
        if (!$root.length) return null;
        var $tree = $root.children('ul.tree.org_tree');
        if (!$tree.length) $tree = $('<ul class="tree org_tree"></ul>').appendTo($root);
        else $tree.empty();
        return $tree;
    }

    // detail 팝업 HTML
    function createDetailPopupHTML() {
        return '' +
            '<div class="dim_bg detail_info" style="display:none;">' +
            '  <div class="popup" role="dialog" aria-modal="true" aria-labelledby="detail-name">' +
            '    <button type="button" class="popup_close" aria-label="닫기"></button>' +
            '    <div class="popup_head">' +
            '      <span class="photo" aria-hidden="true"></span>' +
            '      <div class="txt">' +
            '        <span class="name" id="detail-name"></span>' +
            '        <span class="role"></span>' +
            '      </div>' +
            '    </div>' +
            '    <div class="popup_container">' +
            '      <div class="cont_box">' +
            '        <dl><dt>부서</dt><dd></dd></dl>' +
            '        <dl><dt>직급</dt><dd></dd></dl>' +
            '        <dl><dt>직위</dt><dd></dd></dl>' +
            '        <dl><dt>대표번호</dt><dd></dd></dl>' +
            '        <dl><dt>이메일</dt><dd></dd></dl>' +
            '        <dl><dt>휴대폰</dt><dd></dd></dl>' +
            '        <dl><dt>담당업무</dt><dd></dd></dl>' +
            '      </div>' +
            '    </div>' +
            '  </div>' +
            '</div>';
    }

    // detail 팝업
    function ensureDetailPopup() {
        var $overlay = $('.dim_bg.detail_info');
        if (!$overlay.length) {
            $overlay = $(createDetailPopupHTML());
            $('body').append($overlay);
        } else if ($overlay.parent()[0] !== document.body) {
            $overlay.detach().appendTo('body');
        }
        $overlay.css({ position: 'fixed', zIndex: 10000, top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.5)' });
        $overlay.find('.popup').css({
            position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%,-50%)',
            maxHeight: 'calc(100vh - 40px)', width: 'min(92vw, 520px)', overflow: 'hidden',
            background: '#fff', borderRadius: '14px'
        });
        $overlay.find('.popup_container').css({ maxHeight: 'calc(100vh - 120px)', overflow: 'auto', WebkitOverflowScrolling: 'touch' });
        return $overlay;
    }

    // 초기 바인딩
    $(function () {
        favo_template = $('#tree_favo_template').text() || '';
        group_template = $('#tree_group_template').text() || '';
        person_template = $('#tree_group_person_template').text() || '';

        // 조직도 열기/닫기
        $(".btn_organ_pop").off("click").on("click", function (e) { e.preventDefault(); getTreeData(); $(".organ_pop").show(); });
        $(".organ_pop_close").off("click").on("click", function (e) { e.preventDefault(); $(".organ_pop").hide(); });

        // detail 열기
        $(document).off("click.org", ".detail_info_open").on("click.org", ".detail_info_open", function (e) {
            e.preventDefault();
            var $li = $(this).closest('li');
            var userId = $li.data('userid') || $li.data('user-id');
            var u = organization_user_map[userId];
            if (!u) return;

            var $ov = ensureDetailPopup().show().removeClass('none');
            var $popup = $ov.find('.popup');
            var dds = $popup.find('dl dd');

            var region = (global.serverData && global.serverData.region_number) || '';
            var tel = u.cid_no ? fmtPhone(region + gformatter.number(u.cid_no)) : '';
            var mobile = u.mobile_no ? fmtPhone(u.mobile_no) : '';
            var email = u.email || '';
            var desc = gformatter.string(u.description).trim();
            var group = formatDisplayGroup(u.group_names, '@@') || '-';
            var position = emptyToSpace(u.position || u.clss) || '-';
            var title = emptyToSpace(u.title || u.duty || u.position) || '-';

            $popup.find('.photo').css('background-image', 'url("' + (getThumbUrl(u) || '') + '")');
            $popup.find('.name').text(u.name || '-');
            $popup.find('.role').text(position);

            dds.eq(0).text(group);
            dds.eq(1).text(position);
            dds.eq(2).text(title);

            dds.eq(3).empty()
                .append(tel || '-')
                .append(tel ? '<div class="menu"><a class="tel' + (tel ? '' : ' disabled') + '" href="#" data-number="' + gformatter.number(tel) + '" data-user-id="' + (u.user_id || '') + '"></a></div>' : '');

            var $mailBtn = $('<a>')
                .addClass('mail' + (email ? '' : ' disabled'))
                .attr('href', '#')
                .attr('data-email', email)
                .on('click', function (ev) {
                    ev.preventDefault();
                    if (!email) return;
                    var link = 'mailto:' + email;
                    if (isIOS() && global.cordova && cordova.InAppBrowser) cordova.InAppBrowser.open(link, '_system');
                    else global.location.href = link;
                });
            dds.eq(4).empty().append(email || '-').append(email ? $('<div class="menu">').append($mailBtn) : '');

            dds.eq(5).empty()
                .append(mobile || '-')
                .append(mobile ?
                    '<div class="menu">' +
                    '<a class="cell' + (mobile ? '' : ' disabled') + '" href="#" data-number="' + gformatter.number(mobile) + '" data-user-id="' + (u.user_id || '') + '"></a>' +
                    '<a class="msg' + (mobile ? '' : ' disabled') + '" href="#" data-number="' + gformatter.number(mobile) + '"></a>' +
                    '</div>' : ''
                );

            var $duties = dds.eq(6).empty();
            if (desc) {
                var lines = desc.split(/\r?\n|;|•/);
                for (var i = 0; i < lines.length; i++) {
                    var t = gformatter.string(lines[i]).trim();
                    if (t) $('<p>').text(t.charAt(0) === '-' ? t : '- ' + t).appendTo($duties);
                }
            } else {
                $duties.html('<p>-</p>');
            }

            $('.popup_container').scrollTop(0);
            lockScroll();
            $popup.attr('tabindex', -1).focus();
        });

        // detail 닫기
        $(document).off("click.org_closebtn", ".dim_bg.detail_info .popup_close")
            .on("click.org_closebtn", ".dim_bg.detail_info .popup_close", function (e) {
                e.preventDefault();
                $(this).closest('.dim_bg.detail_info').hide().addClass('none');
                unlockScroll();
            });
        $(document).off("click.org_closebg", ".dim_bg.detail_info")
            .on("click.org_closebg", ".dim_bg.detail_info", function (e) {
                if ($(e.target).closest('.popup').length === 0) { $(this).hide().addClass('none'); unlockScroll(); }
            });
        $(document).off("keydown.org_detail").on("keydown.org_detail", function (e) {
            if (e.key === "Escape") {
                var $ov = $('.dim_bg.detail_info:visible');
                if ($ov.length) { $ov.hide().addClass('none'); unlockScroll(); }
            }
        });

        // detail 통화/문자 → 통화선택 팝업
        $(document).off('click.org_call', '.dim_bg.detail_info a.tel, .dim_bg.detail_info a.cell')
            .on('click.org_call', '.dim_bg.detail_info a.tel, .dim_bg.detail_info a.cell', function (e) {
                e.preventDefault();
                if ($(this).hasClass('disabled')) return;

                var number = gformatter.number($(this).data('number') || "");
                if (!number) return;

                var $ov = $(this).closest('.dim_bg.detail_info');
                var name = $ov.find('.popup_head .txt .name').text() || "";

                var isCid = $(this).hasClass('tel');
                var dialPrefix = isCid ? (serverData.svcPrefixNo || "") : "";

                openCallPopupByNumbers(name, [{
                    id: $(this).data('user-id') || "",
                    number: number,
                    display: fmtPhone(number),
                    type: isCid ? '대표번호' : '휴대폰'
                }], {
                    callId: $(this).data('user-id') || "",
                    dialPrefix: dialPrefix
                });
            });


        // detail 문자
        $(document).off('click.org_sms', '.dim_bg.detail_info a.msg')
            .on('click.org_sms', '.dim_bg.detail_info a.msg', function (e) {
                e.preventDefault();
                if ($(this).hasClass('disabled')) return;
                safeSMS($(this).data('number'));
            });


        /* 연락처, 이름 검색 */
        $(document)
            .off('input.orgSearch keyup.orgSearch', '#inp_board_search_organization.organization_search')
            .on('input.orgSearch keyup.orgSearch', '#inp_board_search_organization.organization_search', function () {
                itcdelay(function () {
                    var query = $.trim($('#inp_board_search_organization').val() || '');

                    // 1글자(바이트 기준) 이하면 초기화
                    if (query === '' || getByteLengthOfString(query) <= 1) {
                        var $resultList = $('#organization .list_org_srch');
                        $resultList.empty().removeClass('tree search_tree').addClass('list_org');
                        $('.article_srch').stop().hide();
                        $('.org_tree').stop().show();
                        $(document).off('click.org_usercheck_search', '#organization .list_org_srch .checkbox.user-checkbox');
                        return;
                    }

                    // 즐겨찾기 사용자
                    var favoriteUserSet = {};
                    try {
                        var favInfos = JSON.parse(localStorage.getItem('fav_infos') || '[]');
                        for (var i = 0; i < favInfos.length; i++) {
                            var fav = favInfos[i];
                            if (fav && fav.type === 'user' && fav.favorate_code != null) {
                                favoriteUserSet[String(fav.favorate_code)] = true;
                            }
                        }
                    } catch (e) { }

                    var queryLower = query.toLowerCase();
                    var queryDigits = query.replace(/\D+/g, '');

                    var $resultList = $('#organization .list_org_srch');
                    $resultList.empty().removeClass('list_org').addClass('tree search_tree');

                    // 검색 결과 1명 추가 (트리 마크업 + 즐겨찾기 체크박스)
                    function appendResultUser(user) {
                        var userId = String(user.user_id || '');
                        var isFavorite = !!favoriteUserSet[userId];

                        var $li = $('<li>').attr('data-user-id', userId);
                        $('<div class="checktree checkbox user-checkbox">')
                            .toggleClass('checked', isFavorite)
                            .css('top', '16px')
                            .appendTo($li);

                        var $label = $('<label class="user-label">').appendTo($li);
                        $('<span class="thumb">')
                            .css('background-image', "url('" + (typeof getThumbUrl === 'function' ? getThumbUrl(user) : '') + "')")
                            .appendTo($label);
                        $('<span class="detail_info_open">')
                            .text((gformatter.string(user.position || user.clss) ? gformatter.string(user.position || user.clss) + ' / ' : '') + gformatter.string(user.name))
                            .appendTo($label);

                        $resultList.append($li);
                    }

                    // 매칭
                    var matchCount = 0;
                    for (var userKey in organization_user_map) {
                        if (!organization_user_map.hasOwnProperty(userKey)) continue;
                        var user = organization_user_map[userKey];

                        var nameLower = gformatter.string(user.name).toLowerCase();
                        var choLower = gformatter.string(user.cho_name).toLowerCase();
                        var deptLower = gformatter.string(user.dept_name).toLowerCase();
                        var descLower = gformatter.string(user.description).toLowerCase();

                        var mobileDigits = gformatter.number(user.mobile_no);
                        var etcDigits = gformatter.number(user.etc_no);
                        var cidDigits = gformatter.number(user.cid_no);

                        var isHit =
                            (nameLower.indexOf(queryLower) > -1) ||
                            (choLower.indexOf(queryLower) > -1) ||
                            (query.length >= 2 && deptLower.indexOf(queryLower) > -1) ||
                            (query.length >= 2 && descLower.indexOf(queryLower) > -1) ||
                            (queryDigits !== '' && (
                                mobileDigits.indexOf(queryDigits) > -1 ||
                                etcDigits.indexOf(queryDigits) > -1 ||
                                cidDigits.indexOf(queryDigits) > -1
                            ));

                        if (isHit) { appendResultUser(user); matchCount++; }
                    }

                    if (matchCount > 0) {
                        $('.org_tree').stop().hide();
                        $('.article_srch').stop().show();
                    } else {
                        $resultList.empty().removeClass('tree search_tree').addClass('list_org');
                        $('.article_srch').stop().hide();
                        $('.org_tree').stop().show();
                    }

                    // 검색 리스트 내 즐겨찾기 토글 (공통 로직 사용)
                    $(document)
                        .off('click.org_usercheck_search', '#organization .list_org_srch .checkbox.user-checkbox')
                        .on('click.org_usercheck_search', '#organization .list_org_srch .checkbox.user-checkbox', function (e) {
                            e.stopPropagation();

                            var $checkbox = $(this);
                            var userId = String($checkbox.closest('li').data('userId') || '');
                            if (!userId) return;

                            var willCheck = !$checkbox.hasClass('checked');
                            if (willCheck) insertFavorate(userId, 'user', this);
                            else deleteFavorate(userId, 'user', this);

                            $checkbox.toggleClass('checked', willCheck).removeClass('half_checked');

                            if (typeof updateFavoritesUI_New === 'function') updateFavoritesUI_New();
                            if (typeof syncUserCheckboxVisual_New === 'function') syncUserCheckboxVisual_New(userId);

                            // 현재 검색 결과의 참조 집합도 같이 갱신
                            if (willCheck) favoriteUserSet[userId] = true;
                            else delete favoriteUserSet[userId];
                        });
                }, 300);
            });
    });

    // 최신 동기화
    function checkOrganization() {
        requestAstro('base/v1/corp/updatetime/' + serverData.corp_code, {}, 'get')
            .then(function (res) {
                var data = res.data || {};
                lastGroupDateTime = data.update_time || "";

                function afterLoad(umap) {
                    drawOrganization();
                    if (isIOS()) generateCallExtension(umap);
                    if (window.localStorage) window.localStorage.setItem("setGroupLastDate", lastGroupDateTime);
                }

                if (groupSaveDate === lastGroupDateTime) {
                    if ($.isEmptyObject(organization_user_map)) {
                        readFile('user_map.json', function (txt) {
                            try { organization_user_map = JSON.parse(txt || '{}'); } catch (e) { organization_user_map = {}; }
                            user_map = organization_user_map;
                            if ($.isEmptyObject(organization_user_map)) getOrganization(afterLoad);
                            else drawOrganization();
                        });
                    } else {
                        drawOrganization();
                    }
                } else {
                    getOrganization(afterLoad);
                }
            })
            .catch(function (err) {
                console.error('checkOrganization', err);
                if (!$.isEmptyObject(organization_group_map) && (dept1_group || []).length > 0) drawOrganization();
            });
    }

    // 화면 그리기
    function drawOrganization() {
        var manageGroups = String(serverData.noticeManageGroups || '').split(',').filter(function (x) { return !!x; });

        $('ul.list_org_favo, ul.favo_tree, ul.all_tree_root').empty();

        favo_map = { favo_user: {}, favo_group: {} };
        var fav_infos = L.getArray('fav_infos');
        for (var i = 0; i < fav_infos.length; i++) {
            var it = fav_infos[i];
            if (it.type === 'user') favo_map.favo_user[it.favorate_code] = it;
            else favo_map.favo_group[it.favorate_code] = it;
        }

        renderLegacyFavorites(manageGroups);
        renderLegacyTree(manageGroups);
        renderNewTree();

        bindLegacyEvents();
        bindNewTreeEvents();
    }

    // (기존) 즐겨찾기 렌더
    function renderLegacyFavorites(manageGroups) {
        var fav_infos = L.getArray('fav_infos');
        for (var i = 0; i < fav_infos.length; i++) {
            var item = fav_infos[i];
            if (item.type === 'user') {
                var u = organization_user_map[item.favorate_code]; if (!u) continue;
                $(".list_org_favo").append(
                    person_template
                        .replace(/{{user_name}}/g, "[" + (u.position || u.clss || '') + "] " + u.name)
                        .replace(/{{comment}}/g, formatDisplayGroup(u.group_names, '@@'))
                        .replace(/{{user_class}}/g, u.clss)
                        .replace(/{{phone_number}}/g, u.cid_no)
                        .replace(/{{cell_phone_number}}/g, u.mobile_no)
                        .replace(/{{email}}/g, u.email)
                        .replace(/{{user_position}}/g, u.position)
                        .replace(/{{group_name}}/g, u.group_name)
                        .replace(/{{user_id}}/g, u.user_id)
                        .replace(/{{fav_flag}}/g, "btn_favo_org")
                        .replace(/{{user_id_type}}/g, "favo_" + u.user_id)
                        .replace(/{{user_photo_uri}}/g, getThumbUrl(u))
                        .replace(/{{view_type}}/g, "favo")
                );
            } else {
                var g = (organization_group_map[item.favorate_code] || {}).info;
                if (!g) continue;
                if (manageGroups.indexOf(g.code) > -1) continue;
                $(".favo_tree").append(
                    favo_template
                        .replace(/{{group_name}}/g, g.name)
                        .replace(/{{group_code}}/g, g.code)
                        .replace(/{{group_count}}/g, "(" + (g.user_cnt || 0) + ")")
                );
            }
        }
    }

    // 트리 렌더
    function renderLegacyTree(manageGroups) {
        (dept1_group || []).forEach(function (group) {
            var favOff = favo_map.favo_group.hasOwnProperty(group.code) ? "" : "off";
            if (manageGroups.indexOf(group.code) > -1) return;

            $(".all_tree_root").append(
                group_template
                    .replace(/{{group_name}}/g, group.name)
                    .replace(/{{group_count}}/g, "(" + group.user_cnt + ")")
                    .replace(/{{group_code}}/g, group.code)
                    .replace(/{{haschild}}/g, group.haschild)
                    .replace(/{{dept_class}}/g, "01")
                    .replace(/{{fav_flag}}/g, favOff)
            );

            var parent_code = group.code;

            var users = (organization_group_map[parent_code] || {}).user_id_list || [];
            if (users.length > 0) {
                $("#" + parent_code).append("<ul class='list_org ul_person_" + parent_code + "'></ul>");
                for (var i = 0; i < users.length; i++) {
                    var uid = users[i];
                    var u = organization_user_map[uid]; if (!u) continue;
                    var flag = favo_map.favo_user.hasOwnProperty(uid) ? "btn_favo_org" : "btn_favo_no_org";
                    $(".ul_person_" + parent_code).append(
                        person_template
                            .replace(/{{user_name}}/g, "[" + (u.clss || u.position || '') + "] " + u.name)
                            .replace(/{{comment}}/g, "")
                            .replace(/{{user_class}}/g, u.clss)
                            .replace(/{{phone_number}}/g, u.cid_no)
                            .replace(/{{cell_phone_number}}/g, u.mobile_no)
                            .replace(/{{email}}/g, u.email)
                            .replace(/{{user_position}}/g, u.position)
                            .replace(/{{group_name}}/g, u.group_name)
                            .replace(/{{user_id}}/g, u.user_id)
                            .replace(/{{fav_flag}}/g, flag)
                            .replace(/{{user_id_type}}/g, u.user_id)
                            .replace(/{{user_photo_uri}}/g, getThumbUrl(u))
                            .replace(/{{view_type}}/g, "")
                    );
                }
            }

            var childs = (organization_group_map[parent_code] || {}).child_groups || [];
            $("#" + parent_code).append("<ul class='tree ul_" + parent_code + "'></ul>");
            for (var j = 0; j < childs.length; j++) {
                var gc = childs[j];
                var gi = (organization_group_map[gc] || {}).info; if (!gi) continue;
                var depth = parseInt(gi.relation_depth, 10) + 1;
                var fav = favo_map.favo_group.hasOwnProperty(gc) ? "" : "off";
                if (manageGroups.indexOf(gi.code) > -1) continue;
                $(".ul_" + parent_code).append(
                    group_template
                        .replace(/{{group_name}}/g, gi.name)
                        .replace(/{{group_count}}/g, "(" + gi.user_cnt + ")")
                        .replace(/{{group_code}}/g, gi.code)
                        .replace(/{{dept_class}}/g, "0" + depth)
                        .replace(/{{fav_flag}}/g, fav)
                );
            }
        });

        $(".dpt_01:last").trigger('click');
    }

    // 새 트리
    function renderNewTree() {
        var $tree = ensureOrgTree(); if (!$tree) return;

        var $fav = $('<li class="fav"></li>');
        $fav.append('<div class="checktree arrow expanded"></div>');
        $fav.append('<div class="checktree checkbox checked"></div>');
        $fav.append('<label class="fav-label" ><span>즐겨찾기</span></label>');
        $fav.append('<ul class="tree-open fav_groups_ul"></ul>');
        $fav.append('<ul class="tree-open fav_users_ul"></ul>');
        $tree.append($fav);

        (dept1_group || []).forEach(function (g) {
            $tree.append(renderNewGroupLi(g.code));
        });

        updateFavoritesUI_New();
        syncAllChecksFromFavs_New($tree);
    }

    // 새 트리: 그룹 li
    function renderNewGroupLi(code, opts, appendUser = true) {
        opts = opts || {};
        var gi = (organization_group_map[code] || {}).info; if (!gi) return $('<li></li>');
        var hasUsers = ((organization_group_map[code] || {}).user_id_list || []).length > 0;
        var hasChild = ((organization_group_map[code] || {}).child_groups || []).length > 0;
        var hasAny = hasUsers || hasChild;
        var users = (organization_group_map[code] || {}).user_id_list || [];

        var $li = $('<li></li>').attr('data-group-code', code);
        if (hasChild || (appendUser && hasUsers)) {
            $li.append('<div class="checktree arrow ' + (hasAny ? 'collapsed' : '') + '" ></div>');
        }
        $li.append('<div class="checktree checkbox group-checkbox"></div>');
        $li.append('<label class="group-label"><span>' + emptyToSpace(gi.name) + '</span></label>');

        var $ul = $('<ul></ul>').hide().removeClass('tree-open');

        if (appendUser) {
            for (var i = 0; i < users.length; i++) $ul.append(renderNewUserLi(users[i], { hideFav: !!opts.hideUserFav }));
        }

        var childs = (organization_group_map[code] || {}).child_groups || [];
        for (var j = 0; j < childs.length; j++) $ul.append(renderNewGroupLi(childs[j], opts, appendUser));

        $li.append($ul);
        return $li;
    }

    // 새 트리: 사용자 li
    function renderNewUserLi(uid, opts) {
        opts = opts || {};
        var u = organization_user_map[uid]; if (!u) return $('<li></li>');
        var hideFav = !!opts.hideFav;
        var checked = !!favo_map.favo_user[uid];

        var $li = $('<li></li>').attr('data-user-id', uid);
        if (!hideFav) {
            $li.append('<div class="checktree checkbox user-checkbox' + (checked ? ' checked' : '') + '" style="top: 16px;"></div>');
            $li.append(
                '<label class="user-label" >' +
                '<span class="thumb" style="background-image:url(\'' + getThumbUrl(u) + '\');"></span>' +
                '<span class="detail_info_open">' + emptyToSpace(u.position || u.clss || '') + ' / ' + emptyToSpace(u.name) + '</span>' +
                '</label>'
            );
        } else {
            $li.append(
                '<label class="user-label no-fav" >' +
                '<span class="thumb" style="background-image:url(\'' + getThumbUrl(u) + '\');"></span>' +
                '<span class="detail_info_open">' + emptyToSpace(u.position || u.clss || '') + ' / ' + emptyToSpace(u.name) + '</span>' +
                '</label>'
            );
        }
        return $li;
    }

    // 즐겨찾기 섹션 갱신
    function updateFavoritesUI_New() {
        var $g = $('.call_cont.active .org_tree .fav > ul.fav_groups_ul');
        var $u = $('.call_cont.active .org_tree .fav > ul.fav_users_ul');
        $g.empty(); $u.empty();

        var gKeys = Object.keys(favo_map.favo_group);
        for (var i = 0; i < gKeys.length; i++) $g.append(renderNewGroupLi(gKeys[i], { hideUserFav: true }));

        var uKeys = Object.keys(favo_map.favo_user);
        for (var j = 0; j < uKeys.length; j++) $u.append(renderNewUserLi(uKeys[j]));

        var $tree = $('.call_cont.active .org_tree');
        syncAllChecksFromFavs_New($tree);
    }

    // 체크 동기화
    function syncAllChecksFromFavs_New($tree) {
        $tree.find('li[data-user-id]').each(function () {
            var uid = $(this).data('userId');
            $(this).children('.checkbox.user-checkbox').toggleClass('checked', !!favo_map.favo_user[uid]).removeClass('half_checked');
        });
        $tree.find('li[data-group-code]').each(function () {
            var code = $(this).data('groupCode');
            var on = !!favo_map.favo_group[code];
            $(this).children('.checkbox.group-checkbox').toggleClass('checked', on).removeClass('half_checked');
        });
    }
    function updateAncestorsVisualFrom_New() { /* 사용 안함 */ }
    function syncUserCheckboxVisual_New(uid) {
        var on = !!favo_map.favo_user[uid];
        $('.org_tree li[data-user-id="' + uid + '"] > .checkbox.user-checkbox').toggleClass('checked', on).removeClass('half_checked');
    }

    // 새 트리 이벤트
    function bindNewTreeEvents($pTree) {
        var $tree = $('.call_cont.active .org_tree');
        if ($pTree) {
            $tree = $pTree;
        }
        if (!$tree.length) return;

        // 화살표
        $tree.off("click.org_arrow", ".arrow").on("click.org_arrow", ".arrow", function (e) {
            e.stopPropagation();
            var $li = $(this).closest('li');
            var $ul = $li.children('ul').first();
            if (!$ul.length) return;
            var open = $ul.hasClass('tree-open');
            if (open) { $ul.removeClass('tree-open').slideUp(120); $(this).removeClass('expanded').addClass('collapsed'); }
            else { $ul.addClass('tree-open').slideDown(120); $(this).removeClass('collapsed').addClass('expanded'); }
        });

        // 라벨 = 화살표
        $tree.off("click.org_favlabel", ".fav > label.fav-label").on("click.org_favlabel", ".fav > label.fav-label", function (e) {
            e.stopPropagation(); $(this).closest('li.fav').children('.arrow').trigger('click');
        });
        $tree.off("click.org_grouplabel", "label.group-label").on("click.org_grouplabel", "label.group-label", function (e) {
            e.stopPropagation(); $(this).closest('li').children('.arrow').trigger('click');
        });

        // 그룹 즐겨찾기
        $tree.off("click.org_groupcheck", ".checkbox.group-checkbox").on("click.org_groupcheck", ".checkbox.group-checkbox", function (e) {
            e.stopPropagation();
            var code = $(this).closest('li[data-group-code]').data('groupCode');
            var willCheck = !$(this).hasClass('checked');
            if (willCheck) insertFavorate(code, "group", this); else deleteFavorate(code, "group", this);
            updateFavoritesUI_New();
            $(this).toggleClass('checked', willCheck).removeClass('half_checked');
        });

        // 사용자 즐겨찾기
        $tree.off("click.org_usercheck", ".checkbox.user-checkbox").on("click.org_usercheck", ".checkbox.user-checkbox", function (e) {
            e.stopPropagation();
            var uid = $(this).closest('li[data-user-id]').data('userId');
            var willCheck = !$(this).hasClass('checked');
            if (willCheck) insertFavorate(uid, "user", this); else deleteFavorate(uid, "user", this);
            updateFavoritesUI_New();
            syncUserCheckboxVisual_New(uid);
        });
    }

    // 템플릿 이벤트
    function bindLegacyEvents() {
        $("#organization .wt_src").off("click.legacy_all", ".all_view_tree").on("click.legacy_all", ".all_view_tree", function (e) {
            e.stopPropagation(); $(this).toggleClass("on").siblings("ul").slideToggle('fast');
        });

        $("#organization .wt_src").off("click.legacy_favgrp", ".favo_group").on("click.legacy_favgrp", ".favo_group", function (e) {
            e.stopPropagation(); $(this).toggleClass("on"); $(this).siblings("ul").slideToggle("fast");
        });

        $("#organization .wt_src").off("click.legacy_favuser", ".favo").on("click.legacy_favuser", ".favo", function (e) {
            e.stopPropagation();
            var uid = $(this).attr("user_id");
            if ($(this).hasClass("btn_favo_org")) {
                if ($(this).attr("view_type") == "favo") {
                    popAlert("그룹 내 사용자는 삭제할 수 없습니다.");
                } else {
                    $(this).removeClass("btn_favo_org").addClass("btn_favo_no_org");
                    deleteFavorate(uid, "user", this);
                    updateFavoritesUI_New(); syncUserCheckboxVisual_New(uid);
                }
            } else {
                $(this).removeClass("btn_favo_no_org").addClass("btn_favo_org");
                insertFavorate(uid, "user", this);
                updateFavoritesUI_New(); syncUserCheckboxVisual_New(uid);
            }
        });

        $("#organization .wt_src").off("click.legacy_favtree", ".btn_favo_tree:not(.none)")
            .on("click.legacy_favtree", ".btn_favo_tree:not(.none)", function (e) {
                e.stopPropagation();
                var code = $(this).attr("groupcode");
                var user_cnt = (organization_group_map[code] || {}).user_cnt || 0;
                if ($(this).hasClass("off")) { if (user_cnt > 0) insertFavorate(code, "group", this); }
                else { deleteFavorate(code, "group", this); }
                updateFavoritesUI_New();
                $('.org_tree li[data-group-code="' + code + '"] > .checkbox.group-checkbox').toggleClass('checked', !!favo_map.favo_group[code]).removeClass('half_checked');
            });
    }

    // 즐겨찾기 추가
    function insertFavorate(fav_info, type, obj) {
        var info = L.getArray('fav_infos');
        var exist = false;
        for (var i = 0; i < info.length; i++) {
            if (info[i] && info[i].favorate_code == fav_info && info[i].type == type) { exist = true; break; }
        }
        if (!exist) {
            info.push({ favorate_code: fav_info, type: type });
            L.setJSON('fav_infos', info);
        }
        if (type === "user") favo_map.favo_user[fav_info] = { favorate_code: fav_info, type: type };
        else favo_map.favo_group[fav_info] = { favorate_code: fav_info, type: type };
        if (obj) $(obj).removeClass('off');
        refreshFavoList(type, fav_info);
    }

    // 즐겨찾기 리스트 반영
    function refreshFavoList(type, fav_info) {
        if (type === "user") {
            var u = organization_user_map[fav_info]; if (!u) return;
            if ($('.list_org_favo li#favo_' + u.user_id).length === 0) {
                $(".list_org_favo").append(
                    person_template
                        .replace(/{{user_name}}/g, "[" + (u.position || u.clss || '') + "] " + u.name)
                        .replace(/{{comment}}/g, formatDisplayGroup(u.group_names, '@@'))
                        .replace(/{{user_class}}/g, u.clss)
                        .replace(/{{phone_number}}/g, u.cid_no)
                        .replace(/{{cell_phone_number}}/g, u.mobile_no)
                        .replace(/{{email}}/g, u.email)
                        .replace(/{{user_position}}/g, u.position)
                        .replace(/{{group_name}}/g, u.group_name)
                        .replace(/{{user_id}}/g, u.user_id)
                        .replace(/{{fav_flag}}/g, "btn_favo_org")
                        .replace(/{{user_id_type}}/g, "favo_" + u.user_id)
                        .replace(/{{user_photo_uri}}/g, getThumbUrl(u))
                        .replace(/{{view_type}}/g, "favo")
                );
            }
        } else {
            var g = (organization_group_map[fav_info] || {}).info; if (!g) return;
            if ($('.favo_tree li#favo_' + g.code).length === 0) {
                $(".favo_tree").append(
                    favo_template
                        .replace(/{{group_name}}/g, g.name)
                        .replace(/{{group_code}}/g, g.code)
                        .replace(/{{group_count}}/g, "(" + (g.user_cnt || 0) + ")")
                );
            }
        }
    }

    // 즐겨찾기 삭제
    function deleteFavorate(fav_info, type, obj) {
        var info = L.getArray('fav_infos');
        var next = [];
        for (var i = 0; i < info.length; i++) {
            var it = info[i];
            if (!(it && it.favorate_code == fav_info && it.type == type)) next.push(it);
        }
        L.setJSON('fav_infos', next);

        if (obj && $(obj).parents().find(".fav_tree_root").length > 0) {
            if (type === "user") {
                $("#" + fav_info).find(".favo").removeClass("btn_favo_org").addClass("btn_favo_no_org");
            } else {
                var rootBtn = $("#" + fav_info).find(".btn_favo_tree")[0];
                if (rootBtn) $(rootBtn).addClass("off");
            }
        }
        if (type === "user") delete favo_map.favo_user[fav_info];
        else delete favo_map.favo_group[fav_info];
        if (obj) $(obj).addClass("off");
        $("#favo_" + fav_info).remove();
    }

    // 데이터 로드
    function getOrganization(done) {
        if (!serverData.rootGroupcode) { popAlert("조직최상위 코드 설정이 필요합니다 관리자에게 문의하세요."); return; }

        Promise.all([
            requestAstro('base/v1/users/' + serverData.rootGroupcode, {}, 'get'),
            requestAstro('base/v1/corps/withdept/' + serverData.rootGroupcode, {}, 'get')
        ])
            .then(function (arr) {
                var uRes = arr[0], gRes = arr[1];
                var users = (uRes && uRes.data) || [];
                var groups = (gRes && gRes.data) || [];

                organization_group_map = {}; organization_user_map = {}; groupParents = {};
                var mindepth = 999;

                for (var i = 0; i < groups.length; i++) {
                    var g = groups[i];
                    organization_group_map[g.code] = { info: g, child_groups: [], user_id_list: [], user_cnt: 0 };
                    if (g.prnt_code) groupParents[g.code] = g.prnt_code;
                    if (g.relation_depth < mindepth) mindepth = g.relation_depth;
                }

                for (var j = 0; j < users.length; j++) {
                    var u = users[j];
                    var gm = organization_group_map[u.dept_code];
                    if (!gm) continue;
                    gm.user_id_list.push(u.user_id);
                    gm.user_cnt++;
                    organization_user_map[u.user_id] = u;

                    try {
                        if (typeof mcid_tel_no_format !== 'undefined' && u.cid_no) { u.mcid_tel_no = eval(mcid_tel_no_format); }
                        if (typeof mcid_tel_name_format !== 'undefined') { u.mcid_tel_name = eval(mcid_tel_name_format); }
                        if (typeof mcid_mobile_no_format !== 'undefined' && u.mobile_no) { u.mcid_mobile_no = eval(mcid_mobile_no_format); }
                    } catch (e) { }
                }

                for (var k = 0; k < groups.length; k++) {
                    var gg = groups[k];
                    var cnt = 0;
                    var chain = groups.filter(function (x) { return x.relation_code && (x.relation_code + '.').indexOf(gg.relation_code + '.') === 0; });
                    for (var c = 0; c < chain.length; c++) cnt += (organization_group_map[chain[c].code] || {}).user_cnt || 0;
                    gg.user_cnt = cnt;
                    if (gg.prnt_code && organization_group_map[gg.prnt_code]) organization_group_map[gg.prnt_code].child_groups.push(gg.code);
                    gg.haschild = (!chain.length && gg.user_cnt <= 0) ? '0' : '1';
                }

                dept1_group = groups.filter(function (x) { return x.relation_depth == mindepth; });

                L.setJSON('group_map', organization_group_map);
                L.setJSON('dept1_group', dept1_group);

                // if (global.device) {
                    writeFile('user_map.json', JSON.stringify(organization_user_map), function () {
                        if (window.localStorage) window.localStorage.setItem('groupok', '1');
                        if (done) done(organization_user_map);
                    });
                // } else {
                //     user_map = organization_user_map;
                //     if (done) done(organization_user_map);
                // }
            })
            .catch(function (err) {
                console.log('getOrganization fail', err);
                if (done) done({ error: true });
            });
    }

    // iOS 확장 DB
    function generateCallExtension(userMapJson) {
        var saved = (window.localStorage && window.localStorage.getItem("setGroupLastDate")) || "";
        if (!(window.plugins && window.plugins.gntel)) return;
        window.plugins.gntel.checkMIddleDBFileExist(function (exists) {
            if (!exists) {
                window.plugins.gntel.middleDBGenerator(function () { }, function () { }, 'user_map.json');
            } else {
                var params = [];
                var listC = Object.values(userMapJson).filter(function (x) { return x.create_time > saved; });
                for (var i = 0; i < listC.length; i++) {
                    var itC = listC[i];
                    if (itC.mcid_tel_no) params.push({ name: (itC.mcid_tel_name || ''), number: itC.mcid_tel_no, status: 'c' });
                }
                var listU = Object.values(userMapJson).filter(function (x) { return x.update_time > saved; });
                for (var j = 0; j < listU.length; j++) {
                    var itU = listU[j];
                    if (itU.mcid_tel_no) params.push({ name: (itU.mcid_tel_name || ''), number: itU.mcid_tel_no, status: 'u' });
                }
                if (params.length) window.plugins.gntel.updateMiddleDB(function () { }, function () { }, params);
            }
        }, function (r) { console.log('checkMIddleDBFileExist fail', r); });
    }

    // 트리 데이터
    function getTreeData() {
        requestAstro('base/v1/corps/withdept/tree/' + serverData.corp_code, {}, 'get')
            .then(function (json) {
                try {
                    var data = json.data || [];
                    var editor = new DinampTreeEditor('.organization');
                    editor.setData(transformData(data)).set({ extended: false, editable: true });
                    bindJsTreeClickEvent();
                } catch (e) { console.error('getTreeData transform', e); }
            })
            .catch(function (err) {
                console.log('getTreeData error', err);
                popAlert("오류가 발생했습니다.<br/>잠시 후 다시 시도해 주세요");
            });

        function transformData(arr) {
            var out = [];
            for (var i = 0; i < arr.length; i++) {
                var it = arr[i];
                var node = { title: it.name, code: it.code, corp_code: it.corp_code, prnt_code: it.prnt_code, group_type: it.group_type, children: [] };
                if (it.childs && it.childs.length) node.children = transformData(it.childs);
                out.push(node);
            }
            return out;
        }
    }

    // 에디터 클릭
    function bindJsTreeClickEvent() {
        $(".jsTree").off("click.org_jstree", "p").on("click.org_jstree", "p", function (e) {
            e.preventDefault();
            var groupType = $(this).attr('group-type');
            var code = $(this).attr('data-code');
            var corp = $(this).attr('corp-code');
            if (!code || groupType === "C") return;

            $('.jsTree .itemParent').removeClass('selected');
            $(this).closest('.itemParent').addClass('selected');

            requestAstro('base/v1/dept/detail/' + code + '/' + corp, {}, 'get')
                .then(function (json) {
                    var data = json.data || {};
                    L.setJSON("selected_dept_code", data.code);
                    L.setJSON("selected_dept_name", data.name);
                })
                .catch(function (err) {
                    console.error("[조직도 클릭] 부서 상세 조회 실패", err);
                    popAlert("오류가 발생했습니다.<br/>잠시 후 다시 시도해 주세요");
                });
        });
    }

    global.G = {
        ...(global.G || {}),
        drawOrganization: drawOrganization,
        checkOrganization: checkOrganization,
        renderNewGroupLi: renderNewGroupLi,
        bindNewTreeEvents: bindNewTreeEvents
    };

})(window, window.jQuery);
