<%
    local fs = require "luci.openclash"
    local plugin_version = fs.oc_version()
    local version_tab = (self and self.version_tab) or false
%>
<script src="/luci-static/resources/openclash/js/common.js?v=<%=plugin_version%>"></script>
<script src="/luci-static/resources/openclash/js/oc-icons.js?v=<%=plugin_version%>"></script>
    <div class="oc">
        <div id="selectPopup" class="select-popup<%= version_tab and ' select-popup-tab' or ' hidden' %>">
            <div class="select-popup-header">
                <div class="config-editor-title">
                    <%:Check Update%>
                </div>
                <div class="config-editor-actions">
                    <button type="button" class="icon-btn" onclick="refreshAllData()" title="<%:Refresh%>">
                        <svg width="14" height="14"><use href="#oc-icon-refresh"/></svg>
                    </button>
                    <button type="button" class="icon-btn<%= version_tab and ' oc-hidden' or '' %>" onclick="closeSelectPopup()" title="<%:Close%>">
                        <svg width="14" height="14"><use href="#oc-icon-close"/></svg>
                    </button>
                </div>
            </div>
            <div class="select-config-section">
                <div class="config-grid">
                    <div class="config-item">
                        <div class="oc-option-title">
                            <span class="oc-option-text"><%:CPU Arch%></span>
                        </div>
                        <span class="ver-value" id="CPU_ARCH_VALUE"><%:Collecting data...%></span>
                    </div>
                    <div class="config-item">
                        <div class="oc-option-title"><span class="oc-option-text"><%:Compiled Version%><b> *</b></span></div>
                        <div class="form-select-wrapper">
                            <select class="select-class oc-arch-select" id="CORE_VERSION" onchange="onArchChange()">
                                <option value="linux-386"><%:linux-386%></option>
                                <option value="linux-amd64-v1"><%:linux-amd64-v1(x86-64)%></option>
                                <option value="linux-amd64-v2"><%:linux-amd64-v2(x86-64)%></option>
                                <option value="linux-amd64-v3"><%:linux-amd64-v3(x86-64)%></option>
                                <option value="linux-armv5"><%:linux-armv5%></option>
                                <option value="linux-armv6"><%:linux-armv6%></option>
                                <option value="linux-armv7"><%:linux-armv7%></option>
                                <option value="linux-arm64"><%:linux-arm64(armv8)%></option>
                                <option value="linux-loong64-abi1"><%:linux-loong64-abi1%></option>
                                <option value="linux-loong64-abi2"><%:linux-loong64-abi2%></option>
                                <option value="linux-riscv64"><%:linux-riscv64%></option>
                                <option value="linux-s390x"><%:linux-s390x%></option>
                                <option value="linux-mips-hardfloat"><%:linux-mips-hardfloat%></option>
                                <option value="linux-mips-softfloat"><%:linux-mips-softfloat%></option>
                                <option value="linux-mips64"><%:linux-mips64%></option>
                                <option value="linux-mips64le"><%:linux-mips64le%></option>
                                <option value="linux-mipsle-softfloat"><%:linux-mipsle-softfloat%></option>
                                <option value="linux-mipsle-hardfloat"><%:linux-mipsle-hardfloat%></option>
                                <option value="0"><%:Not Set%></option>
                            </select>
                        </div>
                    </div>
                    <div class="config-item">
                        <div class="oc-option-title"><span class="oc-option-text"><%:Release Branch%></span></div>
                        <div class="form-select-wrapper">
                            <select class="select-class" id="RELEASE_BRANCH" onchange="onBranchChange()">
                                <option value="master">Master</option>
                                <option value="dev">Developer</option>
                            </select>
                        </div>
                    </div>
                    <div class="config-item">
                        <div class="oc-option-title">
                            <span class="oc-option-text"><%:Smart Core%></span>
                            <a href="javascript:void(0);" onclick="window.open('https://github.com/vernesong/mihomo/releases', '_blank');" class="help-link" title="<%:View core infos that support smart group%>">
                                <svg width="12" height="12"><use href="#oc-icon-help"/></svg>
                            </a>
                        </div>
                        <div class="form-select-wrapper">
                            <select class="select-class" id="SMART_ENABLE" onchange="onSmartChange()">
                                <option value="0"><%:Disabled%></option>
                                <option value="1"><%:Enabled%></option>
                            </select>
                        </div>
                    </div>
                </div>
                <div class="version-card">
                    <div class="version-row">
                        <div class="config-item">
                            <span class="oc-option-title"><span class="oc-option-text"><%:Plugin Version%></span></span>
                            <span class="ver-value" id="installed-plugin">--</span>
                        </div>
                        <div class="config-item">
                            <span class="oc-option-title"><span class="oc-option-text"><%:Select Version%></span></span>
                            <div class="form-select-wrapper">
                                <select class="select-class" id="select-plugin-ver" onchange="forceRefresh=true;refreshAddrInfo({cancel:true,user:true})">
                                    <option value="__latest__"><%:Latest%></option>
                                </select>
                            </div>
                        </div>
                        <div class="config-item">
                            <span class="oc-option-title"><span class="oc-option-text"><%:Core Version%></span></span>
                            <span class="ver-value" id="installed-core">--</span>
                        </div>
                        <div class="config-item">
                            <span class="oc-option-title"><span class="oc-option-text"><%:Select Version%></span></span>
                            <div class="form-select-wrapper">
                                <select class="select-class" id="select-core-ver" onchange="forceRefresh=true;refreshAddrInfo({cancel:true,user:true})">
                                    <option value="__latest__"><%:Latest%></option>
                                </select>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <div class="ver-hint" id="version-hint"></div>

            <div class="select-popup-body" id="addr-list-body">
                <div class="addr-list-scroll">
                <div class="select-option addr-header">
                    <span class="addr-header-label"><span class="addr-header-index">#</span></span>
                    <span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-link-tabler"/></svg></span><span><%:Address%></span></span>
                    <span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-speedtest"/></svg></span><span><%:Latency%></span></span>
                    <span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-package"/></svg></span><span><%:Plugin%></span></span>
                    <span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-cpu"/></svg></span><span><%:Core%></span>
                        <div class="help-link" title="<%:Core path:%>/etc/openclash/core/clash_meta">
                            <svg width="12" height="12"><use href="#oc-icon-help"/></svg>
                        </div>
                    </span>
                    <span class="addr-header-label"><span class="addr-header-icon"><svg><use href="#oc-icon-zap"/></svg></span><span><%:Proxy%></span>
                        <div class="help-link" title="<%:Proxy The GitHub Address In The Config And OpenClash With Proxy(CDN) To Prevent File Download Failed.%>">
                            <svg width="12" height="12"><use href="#oc-icon-help"/></svg>
                        </div>
                    </span>
                </div>
                    <div class="select-option addr-row" data-value="https://raw.githubusercontent.com/">
                        <span class="addr-row-index"></span>
                        <span class="addr-row-url">https://raw.githubusercontent.com/</span>
                        <span class="addr-status">--</span>
                        <span class="ver-text ver-plugin">--</span>
                        <span class="ver-text ver-core">--</span>
                        <span class="addr-proxy-static"><%:RAW%></span>
                    </div>
                    <div class="select-option addr-row" data-value="https://fastly.jsdelivr.net/">
                        <span class="addr-row-index"></span>
                        <span class="addr-row-url">https://fastly.jsdelivr.net/</span>
                        <span class="addr-status">--</span>
                        <span class="ver-text ver-plugin">--</span>
                        <span class="ver-text ver-core">--</span>
                    </div>
                    <div class="select-option addr-row" data-value="https://testingcf.jsdelivr.net/">
                        <span class="addr-row-index"></span>
                        <span class="addr-row-url">https://testingcf.jsdelivr.net/</span>
                        <span class="addr-status">--</span>
                        <span class="ver-text ver-plugin">--</span>
                        <span class="ver-text ver-core">--</span>
                    </div>
                    <div class="select-option addr-row" data-value="https://cdn.jsdelivr.net/">
                        <span class="addr-row-index"></span>
                        <span class="addr-row-url">https://cdn.jsdelivr.net/</span>
                        <span class="addr-status">--</span>
                        <span class="ver-text ver-plugin">--</span>
                        <span class="ver-text ver-core">--</span>
                    </div>
                    <div class="select-option" data-value="custom" id="custom-addr-option">
                        <span><%:Custom Your CDN URL%></span>
                        <span class="oc-flex-center" style="height:100%;">
                            <svg width="18" height="18"><use href="#oc-icon-chevron-down"/></svg>
                        </span>
                    </div>
                    <input type="text" id="customOptionInput" class="custom-option-input oc-hidden" value="" placeholder="<%:Type CDN URL, Format Like%> https://ghfast.top/">
                    <div class="select-option oc-hidden" id="addCustomOption"><%:Add%></div>
                </div>
                <div class="addr-list-loading oc-hidden" id="addr-list-loading">
                    <div class="addr-list-spinner"></div>
                </div>
            </div>
            <div class="select-popup-footer" id="select-popup-footer">
                <div class="form-select-wrapper">
                    <select class="select-class" id="BACKUP_SELECT">
                        <option value="backup"><%:Backup File%></option>
                        <option value="backup_ex_core"><%:Backup Exclude Cores%></option>
                        <option value="backup_only_core"><%:Backup Core%></option>
                        <option value="backup_only_config"><%:Backup Config%></option>
                        <option value="backup_only_rule"><%:Backup Rule Provider%></option>
                        <option value="backup_only_proxy"><%:Backup Proxy Provider%></option>
                    </select>
                </div>
                <button type="button" class="footer-btn" onclick="return backup_file(this)">
                    <svg width="14" height="14"><use href="#oc-icon-download"/></svg>
                    <span><%:Backup File%></span>
                </button>
                <button type="button" class="footer-btn" onclick="return restore_config(this)">
                    <svg width="14" height="14"><use href="#oc-icon-history"/></svg>
                    <span><%:Restore Default%></span>
                </button>
                <button type="button" class="footer-btn" onclick="return remove_all_core(this)">
                    <svg width="14" height="14"><use href="#oc-icon-trash"/></svg>
                    <span><%:Remove Core%></span>
                </button>
            </div>
            <div class="select-popup-status-bar">
                <div class="select-popup-status" id="addr-list-status"><%:Fetching data...%></div>
                <div class="select-popup-tip"><%:Click%> <b><%:link%></b> <%:to update all, click%> <b><%:version%></b> <%:to update individually%></div>
            </div>
        </div>
    </div>

<script type="text/javascript">
//<![CDATA[
    var core_version = document.getElementById('CORE_VERSION');
    var release_branch = document.getElementById('RELEASE_BRANCH');
    var smart_enable = document.getElementById('SMART_ENABLE');
    var versionTabMode = document.getElementById('selectPopup').classList.contains('select-popup-tab');
    var currentDownloadType = 'one_key';
    var refreshTimer = null;
    var versionRetryTimer = null;
    var retryTimer = null;
    var addrInfoBusy = false;
    var addrInfoXHR = null;
    var addrInfoError = false;
    var addrDataReceived = false;
    var reqUpdateDone = false;
    var reqVersionHistoryDone = false;
    var reqAddrInfoDone = false;
    var versionHistoryBusy = false;
    var versionHistoryXHR = null;
    var hintTimer = null;
    var defaultTipTimer = null;
    var logStreamXHR = null;
    var logStreamReconnect = null;
    var logStreamActive = false;
    var logRetryCount = 0;
    var lastLogLineCount = null;
    var logMaxWaitTimer = null;
    var fetchedBranch = null;
    var _isOix = false;
    var _pkgType = 'opkg';
    var forceRefresh = false;
    var infoCache = {};
    var currentProxyAddr = '';

    window.levelTranslations = {
        'info': '<%:Info%>',
        'warning': '<%:Warning%>',
        'error': '<%:Error%>',
        'debug': '<%:Debug%>',
        'tip': '<%:Tip%>',
        'watchdog': '<%:Watchdog%>',
        'fatal': '<%:Fatal%>'
    };

    function ocRandomInterval(min, max) {
        return Math.floor(Math.random() * (max - min + 1) + min);
    }

    function isValidURL(str) {
        var pattern = new RegExp('^(https?:\\/\\/)?'+ // protocol
            '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'+ // domain name
            '((\\d{1,3}\\.){3}\\d{1,3}))'+ // OR ip (v4) address
            '(\\:\\d+)?(\\/[-a-z\\d%_.&#126;+]*)*'+ // port and path
            '(\\?[;&a-z\\d%_.&#126;+=-]*)?'+ // query string
            '(\\#[-a-z\\d_]*)?'+'\/$','i');
        return !!pattern.test(str);
    }

    function showHint(message, duration) {
        var hint = document.getElementById('version-hint');
        if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
        if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
        hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + message + '</span>';
        hint.classList.add('show');
        hint.classList.remove('note');
        if (duration === 0) return;
        hintTimer = setTimeout(function() {
            showDefaultNote();
            hintTimer = null;
        }, duration || 5000);
    }

    function showLogLine(html) {
        var hint = document.getElementById('version-hint');
        if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
        if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
        hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + html + '</span>';
        hint.classList.add('show');
        hint.classList.add('note');
    }

    function showDefaultNote() {
        var hint = document.getElementById('version-hint');
        if (hintTimer) { clearTimeout(hintTimer); hintTimer = null; }
        if (defaultTipTimer) { clearTimeout(defaultTipTimer); defaultTipTimer = null; }
        var tips = [
            '<%:Note: if the update fails, you can manually download and upload%>',
            '<%:Note: the client may not support update, because the firmware with squashfs format will not release flash space after updating%>'
        ];
        var tip = tips[Math.floor(Math.random() * tips.length)];
        hint.innerHTML = '<svg><use href="#oc-icon-alert-circle"/></svg><span>' + tip + '</span>';
        hint.classList.add('show');
        hint.classList.add('note');
        defaultTipTimer = setTimeout(function() {
            defaultTipTimer = null;
            showDefaultNote();
        }, 5000);
    }

    function classifyAddr(url) {
        if (!url || url === '') return 'raw';
        if (/raw\.githubusercontent\.com/i.test(url)) return 'raw';
        if (/jsdelivr|fastly|testingcf/i.test(url)) return 'jsdelivr';
        if (/dl\.dler\.io/i.test(url)) return 'dler';
        return 'proxy';
    }

    function buildDownloadURL(addr, type, branch, version, sha, arch) {
        var isJsDelivr = classifyAddr(addr) === 'jsdelivr';
        var filename, pathPrefix;

        if (type === 'core' && _isOix) {
            if (!version) return '';
            filename = 'mihomo-' + arch + '-' + version + '.gz';
            var oixURL = 'https://github.com/vernesong/mihomo-oix/releases/download/Pre-Alpha/' + filename;
            var proxyURL = 'https://dl.dler.io/mihomo-oix/' + filename + '?tag=Pre-Alpha';
            var ctype = classifyAddr(addr);
            if (ctype === 'dler') {
                return proxyURL;
            }
            if (addr && addr !== '' && ctype !== 'raw' && ctype !== 'jsdelivr') {
                return addr + oixURL;
            }
            return proxyURL;
        }

        if (type === 'plugin') {
            var verNoV = version.replace(/^v/, '');
            if (_pkgType === 'apk') {
                filename = 'luci-app-openclash-' + verNoV + '.apk';
            } else {
                filename = 'luci-app-openclash_' + verNoV + '_all.ipk';
            }
            pathPrefix = 'package/' + branch;
        } else {
            filename = 'clash-' + arch + '.tar.gz';
            var coreSubPath = (smart_enable.value === '1') ? 'smart' : 'meta';
            pathPrefix = 'core/' + branch + '/' + coreSubPath;
        }

        if (sha) {
            var refPath = (type === 'plugin') ? branch : branch + '/' + coreSubPath;
            if (isJsDelivr)
                return addr + 'gh/vernesong/OpenClash@' + sha + '/' + refPath + '/' + filename;
            else if (classifyAddr(addr) === 'raw')
                return 'https://raw.githubusercontent.com/vernesong/OpenClash/' + sha + '/' + refPath + '/' + filename;
            else
                return addr + 'https://raw.githubusercontent.com/vernesong/OpenClash/' + sha + '/' + refPath + '/' + filename;
        } else {
            if (isJsDelivr)
                return addr + 'gh/vernesong/OpenClash@' + pathPrefix + '/' + filename;
            else if (classifyAddr(addr) === 'raw')
                return 'https://raw.githubusercontent.com/vernesong/OpenClash/' + pathPrefix + '/' + filename;
            else
                return addr + 'https://raw.githubusercontent.com/vernesong/OpenClash/' + pathPrefix + '/' + filename;
        }
    }

    function findSHAbyVersion(selectEl, targetVer) {
        if (!targetVer || !selectEl) return '';
        for (var i = 0; i < selectEl.options.length; i++) {
            if (selectEl.options[i].getAttribute('data-ver') === targetVer) {
                var v = selectEl.options[i].value;
                return (v && v !== '__latest__') ? v : '';
            }
        }
        return '';
    }

    function escAttr(s) {
        return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
    }

    function makeVerCell(addrUrl, type, version) {
        var branch = fetchedBranch || release_branch.value || 'dev';
        var arch = core_version.value;
        var selectEl = type === 'plugin'
            ? document.getElementById('select-plugin-ver')
            : document.getElementById('select-core-ver');
        var verText = version;
        var isLatest = false;

        if (!verText) {
            if (type === 'plugin') {
                return null;
            }

            var selectedOption = selectEl.options[selectEl.selectedIndex];
            isLatest = !selectedOption || selectedOption.value === '__latest__';

            if (!isLatest) {
                verText = selectedOption.getAttribute('data-ver');
            }

            if (!verText) return null;
        }

        var useVersion = (verText === 'Latest') ? '' : verText;
        var sha = useVersion ? findSHAbyVersion(selectEl, useVersion) : '';
        var dlUrl = buildDownloadURL(addrUrl, type, branch, useVersion, sha, arch);

        var verLink = '<a href="javascript:void(0)" class="ver-link" data-type="' + type + '" data-ver="' + escAttr(useVersion) + '" data-dl="' + escAttr(dlUrl) + '" title="<%:Click to Install%>">' + verText + '</a>';
        if (!dlUrl) {
            return verLink;
        }
        return verLink +
            '<a href="' + escAttr(dlUrl) + '" target="_blank" rel="noopener" class="dl-btn" title="<%:Download%>"><svg width="11" height="11"><use href="#oc-icon-download"/></svg></a>';
    }

    function updateAddrRow(row, addrUrl, info) {
        var smart = smart_enable.value || '0';
        var latencyEl = row.querySelector('.addr-status');
        var verSpans = row.querySelectorAll('.ver-text');
        var pluginEl = verSpans[0];
        var coreEl = verSpans[1];

        if (info.latency != null && info.latency > 0) {
            var ms = parseInt(info.latency);
            latencyEl.textContent = ms + ' ms';
            latencyEl.className = 'addr-status ' + (ms <= 500 ? 'fast' : ms <= 1000 ? 'medium' : 'slow');
            latencyEl.title = '';
        } else if (info.latency === -1) {
            latencyEl.textContent = '<%:Access Timed Out%>';
            latencyEl.className = 'addr-status err';
            latencyEl.title = '';
        } else if (info.latency === -2) {
            latencyEl.textContent = '<%:Access Denied%>';
            latencyEl.className = 'addr-status err';
            latencyEl.title = '';
        } else {
            latencyEl.textContent = '--';
            latencyEl.className = 'addr-status';
            latencyEl.title = '';
        }

        if (pluginEl) {
            if (info.plugin_ver) {
                pluginEl.innerHTML = makeVerCell(addrUrl, 'plugin', info.plugin_ver);
                pluginEl.classList.add('has-data');
                pluginEl.classList.remove('err');
            } else {
                var selLink = makeVerCell(addrUrl, 'plugin');
                if (selLink) {
                    pluginEl.innerHTML = selLink;
                    pluginEl.classList.add('has-data');
                    pluginEl.classList.remove('err');
                } else if (info.latency === -1 || info.latency === -2) {
                    pluginEl.textContent = info.latency === -1 ? '<%:Access Timed Out%>' : '<%:Access Denied%>';
                    pluginEl.classList.add('has-data', 'err');
                } else {
                    pluginEl.textContent = '--';
                    pluginEl.classList.remove('has-data', 'err');
                }
            }
        }
        if (coreEl) {
            var coreVer = (smart === '1' && info.core_smart_ver) ? info.core_smart_ver : info.core_meta_ver;
            if (coreVer) {
                coreEl.innerHTML = makeVerCell(addrUrl, 'core', coreVer);
                coreEl.classList.add('has-data');
                coreEl.classList.remove('err');
            } else if (info.latency === -1 || info.latency === -2 || info.latency === -3 || info.latency == null) {
                if (info.latency === -1) {
                    coreEl.textContent = '<%:Access Timed Out%>';
                    coreEl.classList.add('has-data', 'err');
                } else if (info.latency === -2) {
                    coreEl.textContent = '<%:Access Denied%>';
                    coreEl.classList.add('has-data', 'err');
                } else {
                    coreEl.textContent = '--';
                    coreEl.classList.remove('has-data', 'err');
                }
            } else {
                var selLink = makeVerCell(addrUrl, 'core');
                if (selLink) {
                    coreEl.innerHTML = selLink;
                    coreEl.classList.add('has-data');
                    coreEl.classList.remove('err');
                } else if (info.core_error) {
                    coreEl.textContent = info.core_error === 'timeout' ? '<%:Access Timed Out%>' : '<%:Access Denied%>';
                    coreEl.classList.add('has-data', 'err');
                } else {
                    coreEl.textContent = '<%:Access Timed Out%>';
                    coreEl.classList.add('has-data', 'err');
                }
            }
        }

        infoCache[addrUrl] = {
            plugin_ver: info.plugin_ver || null,
            core_meta_ver: info.core_meta_ver || null,
            core_smart_ver: info.core_smart_ver || null,
            latency: info.latency,
            core_error: info.core_error || null
        };
    }

    function buildAddrRows(st) {
        var listBody = document.querySelector('#addr-list-body .addr-list-scroll') || document.getElementById('addr-list-body');
        var customOpt = document.getElementById('custom-addr-option');
        var existing = {};
        listBody.querySelectorAll('.addr-row').forEach(function(row) {
            var v = row.dataset.value;
            if (v) existing[v] = true;
        });
        function addRow(val) {
            if (!val || existing[val]) return;
            var div = document.createElement('div');
            div.className = 'select-option addr-row';
            div.dataset.value = val;
            div.innerHTML = '<span class="addr-row-index"></span><span class="addr-row-url">' + val + '</span><span class="addr-status">--</span><span class="ver-text ver-plugin">--</span><span class="ver-text ver-core">--</span>';
            listBody.insertBefore(div, customOpt);
            existing[val] = true;
        }
        var mod = st.github_address_mod;
        if (mod && mod !== '0') addRow(mod);
        (st.cdn_list || []).forEach(addRow);
    }

    function renderProxyStates() {
        document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
            if (classifyAddr(row.dataset.value) === 'raw') return;
            var el = row.querySelector('.addr-proxy');
            if (!el) {
                el = document.createElement('span');
                el.className = 'addr-proxy';
                row.appendChild(el);
            }
            var on = (row.dataset.value === currentProxyAddr);
            el.textContent = on ? '<%:Enabled%>' : '<%:Disabled%>';
            el.className = 'addr-proxy ' + (on ? 'on' : 'off');
        });
    }

    function toggleProxy(row) {
        var val = row.dataset.value;
        if (!val) return;
        var newVal = (val === currentProxyAddr) ? '0' : val;
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_github_address_mod")%>', {value: newVal}, function(x) {
            if (x && x.status == 200) {
                currentProxyAddr = newVal;
                renderProxyStates();
            } else {
                showHint('<%:Failed to save%>');
            }
        });
    }

    function fetchUpdateInfo(opts) {
        core_version.disabled = true;
        release_branch.disabled = true;
        smart_enable.disabled = true;
        document.getElementById('select-plugin-ver').disabled = true;
        document.getElementById('select-core-ver').disabled = true;
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update")%>', null, function(x, st) {
            if (x && x.status == 200) {
                if (st.corever && st.corever != '0') core_version.value = st.corever; else core_version.value = '0';
                if (st.release_branch) release_branch.value = st.release_branch; else release_branch.value = 'master';
                if (st.smart_enable) smart_enable.value = st.smart_enable; else smart_enable.value = '0';
                _isOix = !!(st.oix_core);
                _pkgType = st.pkg_type || 'opkg';
                document.getElementById('installed-plugin').textContent = (st.opcv && st.opcv != '0') ? st.opcv : '--';
                document.getElementById('installed-core').textContent = (st.coremetacv && st.coremetacv != '0') ? st.coremetacv : '--';
                var cpuArchEl = document.getElementById('CPU_ARCH_VALUE');
                if (cpuArchEl) {
                    cpuArchEl.textContent = (st.coremodel && st.coremodel != '') ? st.coremodel : '--';
                }
                currentProxyAddr = (st.github_address_mod || '0');
                buildAddrRows(st);
                renderProxyStates();
            }
            core_version.disabled = false;
            release_branch.disabled = false;
            smart_enable.disabled = false;
            fetchedBranch = release_branch.value;
            updateDataStatus({ update: true });
            fetchVersionHistory(!!(opts && opts.force), {cancel: true});
            refreshAddrInfo({cancel: true, user: true});
        });
    }

    function initAllData() {
        fetchUpdateInfo();
        if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
        refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
    }

    function fetchVersionHistory(force, opts) {
        if (document.getElementById('selectPopup').classList.contains('hidden')) return;
        if (versionHistoryBusy) {
            if (!(opts && opts.cancel)) return;
            if (versionHistoryXHR) { try { versionHistoryXHR.abort(); } catch(e) {} versionHistoryXHR = null; }
            versionHistoryBusy = false;
        }
        versionHistoryBusy = true;
        updateDataStatus({ version: false });
        var branch = fetchedBranch || release_branch.value || 'dev';
        var pluginSelect = document.getElementById('select-plugin-ver');
        var coreSelect = document.getElementById('select-core-ver');
        var hint = document.getElementById('version-hint');
        var params = { branch: branch };
        var smart = smart_enable.value || '0';

        var appendHistoryEntry = function(entry) {
            if (entry.type === 'plugin' && entry.version) {
                pluginSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
            } else if (!_isOix && entry.type === 'core_meta' && smart !== '1' && entry.version) {
                coreSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
            } else if (!_isOix && entry.type === 'core_smart' && smart === '1' && entry.version) {
                coreSelect.innerHTML += '<option value="' + entry.sha + '" data-ver="' + entry.version + '">' + entry.version + ' (<%:Date%>: ' + (entry.date||'').substring(0,10) + ')</option>';
            }
        };

        if (force) params.force = '1';

        pluginSelect.disabled = true;
        coreSelect.disabled = true;

        pluginSelect.innerHTML = '<option value="__latest__"><%:Latest%></option>';
        coreSelect.innerHTML = '<option value="__latest__"><%:Latest%></option>';

        var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "version_history")%>?' +
            Object.keys(params).map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
        var xhr = new XMLHttpRequest();
        xhr.timeout = 0;
        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        var lastLen = 0;
        var pendingBuf = '';
        xhr.onprogress = function() {
            if (xhr.status != 200) return;
            var text = xhr.responseText;
            if (text.length <= lastLen) return;
            var newText = text.substring(lastLen);
            lastLen = text.length;
            pendingBuf += newText;
            var lines = pendingBuf.split('\n');
            pendingBuf = lines.pop();
            for (var i = 0; i < lines.length; i++) {
                var line = lines[i].trim();
                if (!line) continue;
                try {
                    var entry = JSON.parse(line);
                    if (entry.complete) {
                        if (entry.error) {
                            showHint('<%:Failed to get version history%>');
                            pluginSelect.disabled = true;
                            coreSelect.disabled = true;
                            if (!versionRetryTimer) {
                                versionRetryTimer = setInterval(fetchVersionHistory, 10000);
                            }
                        } else {
                            pluginSelect.disabled = false;
                            coreSelect.disabled = false;
                            if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
                        }
                        continue;
                    }
                    appendHistoryEntry(entry);
                } catch(e) {}
            }
        };
        xhr.onload = function() {
            if (xhr.status != 200 || !xhr.responseText) {
                showHint('<%:Failed to get version history%>');
                if (!versionRetryTimer) {
                    versionRetryTimer = setInterval(fetchVersionHistory, 10000);
                }
                return;
            }
            var text = xhr.responseText;
            if (text.length > lastLen) {
                var newText = text.substring(lastLen);
                lastLen = text.length;
                pendingBuf += newText;
                var lines = pendingBuf.split('\n');
                pendingBuf = lines.pop();
                for (var i = 0; i < lines.length; i++) {
                    var line = lines[i].trim();
                    if (!line) continue;
                    try {
                        var entry = JSON.parse(line);
                        if (entry.complete) {
                            if (entry.error) {
                                showHint('<%:Failed to get version history%>');
                                pluginSelect.disabled = true;
                                coreSelect.disabled = true;
                                if (!versionRetryTimer) {
                                    versionRetryTimer = setInterval(fetchVersionHistory, 10000);
                                }
                            } else {
                                pluginSelect.disabled = false;
                                coreSelect.disabled = false;
                                if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
                            }
                            continue;
                        }
                        appendHistoryEntry(entry);
                    } catch(e) {}
                }
            }
            if (pendingBuf && pendingBuf.trim()) {
                var line = pendingBuf.trim();
                pendingBuf = '';
                try {
                    var entry = JSON.parse(line);
                    if (entry.complete) {
                        if (entry.error) {
                            showHint('<%:Failed to get version history%>');
                            pluginSelect.disabled = true;
                            coreSelect.disabled = true;
                            if (!versionRetryTimer) {
                                versionRetryTimer = setInterval(fetchVersionHistory, 10000);
                            }
                        } else {
                            pluginSelect.disabled = false;
                            coreSelect.disabled = false;
                            if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
                        }
                    } else {
                        appendHistoryEntry(entry);
                    }
                } catch(e) {}
            }

        };
        xhr.onerror = function() {
            showHint('<%:Failed to get version history%>');
            if (!versionRetryTimer) {
                versionRetryTimer = setInterval(fetchVersionHistory, 10000);
            }
        };
        xhr.onloadend = function() {
            if (versionHistoryXHR === xhr) {
                versionHistoryXHR = null;
                versionHistoryBusy = false;
            }
            updateDataStatus({ version: true });
        };
        xhr.send();
        versionHistoryXHR = xhr;
    }

    function saveOptions() {
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "save_corever_branch")%>', {
            core_ver: core_version.value,
            release_branch: release_branch.value,
            smart_enable: smart_enable.value
        },function() {});
    }

    function onArchChange() {
        saveOptions();
        document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
            var addrUrl = row.dataset.value;
            if (!addrUrl || addrUrl === 'custom') return;
            var info = infoCache[addrUrl];
            if (info) updateAddrRow(row, addrUrl, info);
        });
    }

    function onBranchChange() {
        fetchedBranch = release_branch.value;
        if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
        if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
        saveOptions();
        resetDataStatus(true);
        fetchVersionHistory(false, {cancel: true});
        forceRefresh = true;
        refreshAddrInfo({cancel: true, user: true});
    }

    function onSmartChange() {
        saveOptions();
        resetDataStatus(true);
        fetchVersionHistory(false, {cancel: true});
        forceRefresh = true;
        refreshAddrInfo({cancel: true, user: true});
    }

    function setAddrListLoading(loading) {
        var overlay = document.getElementById('addr-list-loading');
        if (overlay) overlay.classList.toggle('oc-hidden', !loading);
    }

    function clearAddrRows() {
        document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
            var latencyEl = row.querySelector('.addr-status');
            var verSpans = row.querySelectorAll('.ver-text');
            if (latencyEl) {
                latencyEl.textContent = '--';
                latencyEl.className = 'addr-status';
            }
            verSpans.forEach(function(el) {
                el.textContent = '--';
                el.classList.remove('has-data', 'err');
            });
        });
    }

    function updateDataStatus(flags) {
        if (flags) {
            if ('update' in flags) reqUpdateDone = flags.update;
            if ('version' in flags) reqVersionHistoryDone = flags.version;
            if ('addr' in flags) reqAddrInfoDone = flags.addr;
        }
        var status = document.getElementById('addr-list-status');
        if (!status) return;
        if (addrInfoError) {
            status.textContent = '<%:Failed to get data, will retry%>';
            return;
        }
        status.textContent = (reqUpdateDone && reqVersionHistoryDone && reqAddrInfoDone) ? '<%:Ready%>' : '<%:Fetching data...%>';
    }

    function resetDataStatus(keepUpdate) {
        if (!keepUpdate) reqUpdateDone = false;
        reqVersionHistoryDone = false;
        reqAddrInfoDone = false;
        addrInfoError = false;
        updateDataStatus();
    }

    function refreshAddrInfo(opts) {
        if (document.getElementById('selectPopup').classList.contains('hidden')) return;
        var addrs = [];
        document.querySelectorAll('#addr-list-body .addr-row').forEach(function(row) {
            var v = row.dataset.value;
            if (v && v !== 'custom' && isValidURL(v)) addrs.push(v);
        });
        if (addrs.length === 0) return;
        if (addrInfoBusy) {
            if (!(opts && opts.cancel)) return;
            if (addrInfoXHR) { try { addrInfoXHR.abort(); } catch(e) {} addrInfoXHR = null; }
            addrInfoBusy = false;
        }
        addrInfoBusy = true;
        if (opts && opts.user) updateDataStatus({ addr: false });

        var branch = fetchedBranch || release_branch.value || 'dev';
        var smart = smart_enable.value || '0';
        var pluginVer = document.getElementById('select-plugin-ver').value;
        var coreVer = document.getElementById('select-core-ver').value;
        var params = {
            addrs: addrs.join(','),
            branch: branch,
            smart: smart,
            plugin_ver: pluginVer || '',
            core_ver: coreVer || ''
        };
        if (opts && opts.merge) {
            params.merge = '1';
        }
        if (forceRefresh) {
            params.force = '1';
            forceRefresh = false;
            infoCache = {};
            addrDataReceived = false;
            showDefaultNote();
            clearAddrRows();
        }
        if (opts && opts.user && !addrDataReceived) setAddrListLoading(true);
        var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "addr_info")%>?' +
            Object.keys(params).map(function(k) { return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); }).join('&');
        var xhr = new XMLHttpRequest();
        xhr.timeout = 0;
        xhr.open('GET', url, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        var lastLen = 0;
        var pendingBuf = '';
        xhr.onprogress = function() {
            if (xhr.status != 200) return;
            var text = xhr.responseText;
            if (text.length <= lastLen) return;
            var newText = text.substring(lastLen);
            lastLen = text.length;
            pendingBuf += newText;
            var lines = pendingBuf.split('\n');
            pendingBuf = lines.pop();
            for (var i = 0; i < lines.length; i++) {
                var line = lines[i].trim();
                if (!line) continue;
                try {
                    var info = JSON.parse(line);
                    if (info.complete) {
                        if (info.error === 'version_stale') {
                            fetchVersionHistory(true);
                        }
                        continue;
                    }
                    var addrUrl = info.addr;
                    if (!addrUrl) continue;
                    if (!addrDataReceived) { addrDataReceived = true; setAddrListLoading(false); }
                    var rows = document.querySelectorAll('#addr-list-body .addr-row[data-value="' + CSS.escape(addrUrl) + '"]');
                    rows.forEach(function(row) { updateAddrRow(row, addrUrl, info); });
                } catch(e) {}
            }
        };
        xhr.onload = function() {
            if (xhr.status != 200 || !xhr.responseText) {
                if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
                if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
                addrInfoError = true;
                if (!retryTimer) {
                    retryTimer = setInterval(refreshAddrInfo, 10000);
                }
                return;
            }
            addrInfoError = false;
            var text = xhr.responseText;
            if (text.length > lastLen) {
                var newText = text.substring(lastLen);
                lastLen = text.length;
                pendingBuf += newText;
                var lines = pendingBuf.split('\n');
                pendingBuf = lines.pop();
                for (var i = 0; i < lines.length; i++) {
                    var line = lines[i].trim();
                    if (!line) continue;
                    try {
                        var info = JSON.parse(line);
                        if (info.complete) {
                            if (info.error === 'version_stale') {
                                fetchVersionHistory(true);
                            }
                            continue;
                        }
                        var addrUrl = info.addr;
                        if (!addrUrl) continue;
                        if (!addrDataReceived) { addrDataReceived = true; setAddrListLoading(false); }
                        var rows = document.querySelectorAll('#addr-list-body .addr-row[data-value="' + CSS.escape(addrUrl) + '"]');
                        rows.forEach(function(row) { updateAddrRow(row, addrUrl, info); });
                    } catch(e) {}
                }
            }

            if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
            if (!refreshTimer) {
                refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
            }
        };
        xhr.onerror = function() {
            if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
            if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
            addrInfoError = true;
            retryTimer = setInterval(refreshAddrInfo, 10000);
        };
        xhr.onloadend = function() {
            if (addrInfoXHR === xhr) {
                addrInfoXHR = null;
                addrInfoBusy = false;
            }
            if (opts && opts.user) updateDataStatus({ addr: true });
        };
        xhr.send();
        addrInfoXHR = xhr;
    }

    function initHeaderDrag() {
        var popup = document.getElementById('selectPopup');
        var header = popup ? popup.querySelector('.select-popup-header') : null;
        if (!popup || !header || popup.classList.contains('select-popup-tab')) return;
        if (header.dataset.ocDragInit) return;
        header.dataset.ocDragInit = '1';
        header.classList.add('oc-draggable');

        var dragging = false;
        var startX = 0, startY = 0, startLeft = 0, startTop = 0;

        header.addEventListener('pointerdown', function(e) {
            if (e.target.closest && e.target.closest('button, a, select, input')) return;
            dragging = true;
            var rect = popup.getBoundingClientRect();
            startX = e.clientX;
            startY = e.clientY;
            startLeft = rect.left;
            startTop = rect.top;
            popup.style.transform = 'none';
            popup.style.left = rect.left + 'px';
            popup.style.top = rect.top + 'px';
            popup.style.transition = 'none';
            if (header.setPointerCapture) {
                try { header.setPointerCapture(e.pointerId); } catch(err) {}
            }
            e.preventDefault();
        });

        header.addEventListener('pointermove', function(e) {
            if (!dragging) return;
            popup.style.left = (startLeft + (e.clientX - startX)) + 'px';
            popup.style.top = (startTop + (e.clientY - startY)) + 'px';
        });

        function stopDrag(e) {
            if (!dragging) return;
            dragging = false;
            popup.style.transition = '';
            if (header.releasePointerCapture) {
                try { header.releasePointerCapture(e.pointerId); } catch(err) {}
            }
        }
        header.addEventListener('pointerup', stopDrag);
        header.addEventListener('pointercancel', stopDrag);
    }

    function update(btn, type) {
        var selectPopup = document.getElementById('selectPopup');
        var customOptionInput = document.getElementById('customOptionInput');
        var addCustomOption = document.getElementById('addCustomOption');

        currentDownloadType = type || 'one_key';
        customOptionInput.classList.add('oc-hidden');
        addCustomOption.classList.add('oc-hidden');
        customOptionInput.value = 'https://ghfast.top/';
        fetchedBranch = null;
        addrDataReceived = false;
        resetDataStatus();

        selectPopup.classList.remove('hidden');
        selectPopup.onclick = function(event) { click_handler(event); };
        setAddrListLoading(true);
        initAllData();
        initHeaderDrag();
    }

    function handleVersionUpdate(verLink) {
        var row = verLink.closest ? verLink.closest('.addr-row') : null;
        var addrUrl = row ? row.dataset.value : '';
        var type = verLink.getAttribute('data-type');
        var ver = verLink.getAttribute('data-ver') || '';
        var dlUrl = verLink.getAttribute('data-dl') || '';

        var branch = fetchedBranch || release_branch.value || 'dev';
        var arch = core_version.value;
        var selectEl = type === 'plugin'
            ? document.getElementById('select-plugin-ver')
            : document.getElementById('select-core-ver');

        if (type === 'core' && (!arch || arch === '0')) {
            showHint('<%:No Compiled Version Selected, Please Select In Update Page And Try Again%>');
            return;
        }
        var sha = ver ? findSHAbyVersion(selectEl, ver) : '';
        var selOpt = selectEl.options[selectEl.selectedIndex];
        var isLatest = !selOpt || selOpt.value === '__latest__';
        if (ver && !sha && !isLatest && !(type === 'core' && _isOix)) {
            showHint('<%:The selected version is not in the version history, please refresh and try again%>');
            return;
        }

        if (ver) {
            dlUrl = buildDownloadURL(addrUrl, type, branch, ver, sha, arch);
        }

        showDefaultNote();

        var addrInfo = infoCache[addrUrl];
        if (!addrInfo || addrInfo.latency == null) {
            showHint('<%:Version info not ready, please wait and try again%>');
            return;
        }
        if (addrInfo.latency === -1 || addrInfo.latency === -2 || addrInfo.latency === -3) {
            var errMsg = addrInfo.latency === -1 ? '<%:Link access timed out, cannot download%>' : '<%:Link access failed, cannot download%>';
            showHint(errMsg);
            return;
        }

        if (!dlUrl) {
            showHint('<%:Failed to get download link%>');
            return;
        }

        if (type === 'plugin') {
            var installedVer = (document.getElementById('installed-plugin').textContent || '').trim();
            if (ver && installedVer && installedVer !== '--' && installedVer === ver) {
                showHint('<%:The selected version is the same as the installed version, no need to update%>');
                return;
            }
        } else {
            var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
            if (ver && installedCoreVer && installedCoreVer !== '--' && installedCoreVer === ver) {
                showHint('<%:The selected version is the same as the installed version, no need to update%>');
                return;
            }
        }

        startLog('<%:Checking...%>', false, type === 'core' ? 'openclash_core.sh' : 'openclash_update.sh');

        var addr = addrUrl;
        if (addr === 'https://raw.githubusercontent.com/') addr = '';

        if (type === 'core') {
            var params = {download_url: dlUrl};
            if (addr && addr !== '') params.url = addr;
            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "core_download")%>', params, function() {});
        } else {
            var pParams = {url: addr, download_url: dlUrl, update_type: 'plugin'};
            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "one_key_update")%>', pParams, function() {});
        }
    }

    function initPopupResizeObserver() {
        var popup = document.getElementById('selectPopup');
        if (!popup || typeof ResizeObserver === 'undefined') return;
        if (popup.dataset.ocResizeInit) return;
        popup.dataset.ocResizeInit = '1';
        new ResizeObserver(function() {
            if (popup.offsetWidth > 0 && popup.offsetHeight > 0) {
                if (popup.classList.contains('select-popup-tab')) return;
                if (popup.style.left || popup.style.top) return;
                popup.style.transform = 'translate(' + Math.round(-popup.offsetWidth / 2) + 'px, ' + Math.round(-popup.offsetHeight / 2) + 'px)';
            }
        }).observe(popup);
    }

    function click_handler(event) {
        var target = event.target;
        var customInput = document.getElementById('customOptionInput');
        var addBtn = document.getElementById('addCustomOption');
        var optionElem = target.closest ? target.closest('.select-option') : null;

        var staticEl = target.closest ? target.closest('.addr-proxy-static') : null;
        if (staticEl) return;

        var verLink = target.closest ? target.closest('.ver-link') : null;
        if (verLink) {
            handleVersionUpdate(verLink);
            return;
        }
        var dlBtn = target.closest ? target.closest('.dl-btn') : null;
        if (dlBtn) {
            return;
        }
        var proxyEl = target.closest ? target.closest('.addr-proxy') : null;
        if (proxyEl) {
            var prow = proxyEl.closest('.addr-row');
            if (prow) toggleProxy(prow);
            return;
        }

        if (optionElem && optionElem.dataset.value === 'custom' && !customInput.classList.contains('oc-hidden')) {
            customInput.classList.add('oc-hidden');
            addBtn.classList.add('oc-hidden');
        } else if (optionElem && optionElem.dataset.value === 'custom') {
            customInput.classList.remove('oc-hidden');
            addBtn.classList.remove('oc-hidden');
            customInput.focus();
        } else if (target === customInput) {
            customInput.classList.remove('oc-hidden');
            addBtn.classList.remove('oc-hidden');
        } else if (target === addBtn) {
            var val = customInput.value.trim();
            if (val !== '' && isValidURL(val)) {
                var listBody = document.querySelector('#addr-list-body .addr-list-scroll') || document.getElementById('addr-list-body');
                var div = document.createElement('div');
                div.className = 'select-option addr-row';
                div.dataset.value = val;
                div.innerHTML = '<span class="addr-row-index"></span><span class="addr-row-url">' + val + '</span><span class="addr-status">--</span><span class="ver-text ver-plugin">--</span><span class="ver-text ver-core">--</span>';
                listBody.insertBefore(div, document.getElementById('custom-addr-option'));
                customInput.classList.add('oc-hidden');
                addBtn.classList.add('oc-hidden');
                renderProxyStates();
                refreshAddrInfo({merge: true, cancel: true, user: true});
            } else {
                alert("<%:Please enter a valid URL!%>");
            }
        } else if (optionElem && optionElem.classList.contains('addr-row') && target.closest('.addr-row-url')) {
            var addr = optionElem.dataset.value;

            showDefaultNote();

            var addrInfo = infoCache[addr];
            if (!addrInfo || addrInfo.latency == null) {
                showHint('<%:Version info not ready, please wait and try again%>');
                return;
            }
            if (addrInfo.latency === -1 || addrInfo.latency === -2 || addrInfo.latency === -3) {
                var errMsg = addrInfo.latency === -1 ? '<%:Link access timed out, cannot download%>' : '<%:Link access failed, cannot download%>';
                showHint(errMsg);
                return;
            }

            var branch = fetchedBranch || release_branch.value || 'dev';
            var arch = core_version.value;
            var v = core_version.value;
            var r = release_branch.value;
            var s = smart_enable.value;

            if (!arch || arch === '0') {
                showHint('<%:No Compiled Version Selected, Please Select In Update Page And Try Again%>');
                return;
            }

            var pluginSel = document.getElementById('select-plugin-ver');
            var coreSel = document.getElementById('select-core-ver');
            var pluginOpt = pluginSel.options[pluginSel.selectedIndex];
            var coreOpt = coreSel.options[coreSel.selectedIndex];
            var isPluginLatest = !pluginOpt || pluginOpt.value === '__latest__';
            var isCoreLatest = !coreOpt || coreOpt.value === '__latest__';

            var pluginVer = addrInfo.plugin_ver || '';
            var coreVer = (s === '1' && addrInfo.core_smart_ver)
                ? addrInfo.core_smart_ver
                : (addrInfo.core_meta_ver || '');

            var pluginSHA = (!isPluginLatest && addrInfo.plugin_ver) ? findSHAbyVersion(pluginSel, addrInfo.plugin_ver) : '';
            var useCoreVer = (s === '1' && addrInfo.core_smart_ver) ? addrInfo.core_smart_ver : addrInfo.core_meta_ver;
            var coreSHA = (!isCoreLatest && useCoreVer) ? findSHAbyVersion(coreSel, useCoreVer) : '';

            if (currentDownloadType !== 'core_download' && !isPluginLatest && addrInfo.plugin_ver && !pluginSHA) {
                showHint('<%:The selected version is not in the version history, please refresh and try again%>');
                return;
            }
            if (!isCoreLatest && useCoreVer && !coreSHA) {
                showHint('<%:The selected version is not in the version history, please refresh and try again%>');
                return;
            }

            if (currentDownloadType !== 'core_download') {
                // One-key update: both plugin and core will be updated
                if (!pluginVer) {
                    showHint('<%:Failed to get plugin version info from this link, please check again later%>');
                    return;
                }
                if (!coreVer) {
                    showHint('<%:Failed to get core version info from this link, please check again later%>');
                    return;
                }

                var installedVer = (document.getElementById('installed-plugin').textContent || '').trim();
                var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
                var pluginSame = installedVer && installedVer !== '--' && installedVer === pluginVer;
                var coreSame = installedCoreVer && installedCoreVer !== '--' && installedCoreVer === coreVer;

                if (pluginSame && coreSame) {
                    showHint('<%:The selected version is the same as the installed version, no need to update%>');
                    return;
                }
            } else {
                // Core download only: check core only
                if (!coreVer) {
                    showHint('<%:Failed to get core version info from this link, please check again later%>');
                    return;
                }

                var installedCoreVer = (document.getElementById('installed-core').textContent || '').trim();
                if (installedCoreVer && installedCoreVer !== '--' && installedCoreVer === coreVer) {
                    showHint('<%:The selected version is the same as the installed version, no need to update%>');
                    return;
                }
            }

            startLog('<%:Checking...%>', false, currentDownloadType === 'core_download' ? 'openclash_core.sh' : 'openclash_update.sh');

            if (addr === 'https://raw.githubusercontent.com/') addr = '';

            if (currentDownloadType === 'core_download') {
                var coreURL = buildDownloadURL(addr, 'core', r, coreVer, coreSHA, arch);
                var params = {download_url: coreURL};
                if (addr && addr !== '') params.url = addr;
                XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "core_download")%>', params, function() {});
            } else {
                var pluginURL = buildDownloadURL(addr, 'plugin', r, pluginVer, pluginSHA, arch);
                var oneKeyParams = {url: addr};
                if (!pluginSame) {
                    oneKeyParams.download_url = pluginURL;
                }
                XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "one_key_update")%>', oneKeyParams, function() {});
            }
        }
    }

    function refreshAllData() {
        showDefaultNote();
        infoCache = {};
        addrDataReceived = false;
        resetDataStatus();
        setAddrListLoading(true);
        forceRefresh = true;
        clearAddrRows();
        if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
        if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
        if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
        fetchedBranch = null;
        fetchUpdateInfo({force: true});
        refreshTimer = setInterval(refreshAddrInfo, ocRandomInterval(10000, 20000));
    }

    function closeSelectPopup() {
        var popup = document.getElementById('selectPopup');
        var customInput = document.getElementById('customOptionInput');
        var addBtn = document.getElementById('addCustomOption');
        if (!versionTabMode) {
            popup.classList.add('hidden');
            popup.style.transform = '';
            popup.style.left = '';
            popup.style.top = '';
            popup.style.transition = '';
        }
        if (addrInfoXHR) { try { addrInfoXHR.abort(); } catch(e) {} addrInfoXHR = null; }
        if (versionHistoryXHR) { try { versionHistoryXHR.abort(); } catch(e) {} versionHistoryXHR = null; }
        addrInfoBusy = false;
        versionHistoryBusy = false;
        if (!logStreamActive) { showDefaultNote(); }
        if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; }
        if (versionRetryTimer) { clearInterval(versionRetryTimer); versionRetryTimer = null; }
        if (retryTimer) { clearInterval(retryTimer); retryTimer = null; }
        customInput.classList.add('oc-hidden');
        addBtn.classList.add('oc-hidden');
    }

    function setBtnBusy(btn, busy, busyText) {
        if (!btn) return;
        var btnText = btn.querySelector('span:not(.loading-spinner)');
        if (busy) {
            if (btnText && !btn.dataset.ocBtnOriginalText) {
                btn.dataset.ocBtnOriginalText = btnText.textContent;
            }
            if (btnText && busyText) { btnText.textContent = busyText; }
            ocSetBtnLoading(btn, true);
        } else {
            ocSetBtnLoading(btn, false);
            if (btnText && btn.dataset.ocBtnOriginalText) {
                btnText.textContent = btn.dataset.ocBtnOriginalText;
                delete btn.dataset.ocBtnOriginalText;
            }
        }
    }

    function remove_all_core(btn) {
        if (confirm('<%:Are you sure want to remove all core files?%>')) {
            setBtnBusy(btn, true, '<%:Removing...%>');
            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "remove_all_core")%>', null, function(x) {
                alert((x && x.status == 200) ? '<%:Remove succeeded!%>' : '<%:Remove failed!%>');
                setBtnBusy(btn, false);
            });
        }
        return false;
    }

    function backup_file(btn) {
        var endpoint = document.getElementById('BACKUP_SELECT').value;
        var url = '<%=luci.dispatcher.build_url("admin", "services", "openclash")%>' + '/' + endpoint;
        setBtnBusy(btn, true, '<%:Backing up...%>');
        var xhr = new XMLHttpRequest();
        xhr.open('GET', url, true);
        xhr.responseType = 'blob';
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.onload = function() {
            setBtnBusy(btn, false);
            if (xhr.status == 200 && xhr.response) {
                var disposition = xhr.getResponseHeader('Content-Disposition') || '';
                var match = disposition.match(/filename="?([^";]+)"?/);
                var filename = match ? match[1] : endpoint + '.tar.gz';
                var blobUrl = URL.createObjectURL(xhr.response);
                var a = document.createElement('a');
                a.href = blobUrl;
                a.download = filename;
                document.body.appendChild(a);
                a.click();
                document.body.removeChild(a);
                setTimeout(function() { URL.revokeObjectURL(blobUrl); }, 1000);
            } else {
                alert('<%:Backup failed!%>');
            }
        };
        xhr.onerror = function() {
            setBtnBusy(btn, false);
            alert('<%:Backup failed!%>');
        };
        xhr.send();
        return false;
    }

    function restore_config(btn) {
        if (confirm('<%:Are you sure want to restore the default config?%>')) {
            setBtnBusy(btn, true, '<%:Restoring...%>');
            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "restore")%>', null, function(x) {
                setBtnBusy(btn, false);
                if (x && x.status == 200) {
                    alert('<%:Restore succeeded!%>');
                } else {
                    alert('<%:Restore failed!%>');
                }
                window.location.href = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "settings")%>';
            });
        }
        return false;
    }

    function startLog(initialMessage, skipClearLog, scriptName) {
        if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
        if (logStreamReconnect) { clearTimeout(logStreamReconnect); logStreamReconnect = null; }
        if (initialMessage) {
            logRetryCount = 0;
            lastLogLineCount = null;
        }
        logStreamActive = true;

        var watchMode = !!scriptName && scriptName !== 'view';
        var streamUrl = '<%=luci.dispatcher.build_url("admin", "services", "openclash", "startlog")%>';
        if (watchMode) {
            streamUrl += '?script=' + encodeURIComponent(scriptName);
        }

        if (initialMessage) {
            if (logMaxWaitTimer) { clearTimeout(logMaxWaitTimer); }
            logMaxWaitTimer = setTimeout(function() {
                if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
                logStreamActive = false;
                var hint = document.getElementById('version-hint');
                if (hint) { hint.classList.remove('logging'); }
            }, 600000);
        }

        if (initialMessage) {
            showLogLine('<b style="color:var(--info-color)">' + initialMessage + '</b>');
        }

        function openStream() {
            if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
            var xhr = new XMLHttpRequest();
            xhr.timeout = 0;
            xhr.open('GET', streamUrl, true);
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            var streamEnded = false;
            var gotData = false;
            var processedLines = 0;
            var finishTimer = null;

            function finalCleanup(showDefault) {
                if (logStreamXHR === xhr) { logStreamXHR = null; }
                try { xhr.abort(); } catch(e) {}
                logStreamActive = false;
                if (showDefault) {
                    var hint = document.getElementById('version-hint');
                    if (hint) { hint.classList.remove('logging'); }
                    showDefaultNote();
                }
            }

            xhr.onprogress = function() {
                if (logStreamXHR !== xhr) return;
                if (streamEnded) return;
                var text = xhr.responseText || '';
                var startLine = (lastLogLineCount != null) ? lastLogLineCount : 0;
                if (processedLines > startLine) startLine = processedLines;
                var lines = text.split('\n');
                var hasPartial = text.length > 0 && text.charAt(text.length - 1) !== '\n';
                var endIdx = hasPartial ? lines.length - 1 : lines.length;
                var newChunk = lines.slice(startLine, endIdx).join('\n');
                processedLines = endIdx;
                var raw = newChunk.replace(/##FINISHED##|##CONTINUE##|##FINISH##/g, '');
                if (raw.trim()) {
                    gotData = true;
                    displayLog(raw);
                }
                if (/##FINISHED##\s*$/.test(text) && !finishTimer) {
                    finishTimer = setTimeout(function() {
                        streamEnded = true;
                        finalCleanup(true);
                    }, 10000);
                }
            };

            xhr.onload = function() {
                if (logStreamXHR !== xhr) return;
                if (streamEnded) return;
                var text = xhr.responseText || '';
                if (!watchMode || /##FINISHED##\s*$/.test(text)) {
                    if (!finishTimer) {
                        finishTimer = setTimeout(function() {
                            streamEnded = true;
                            finalCleanup(true);
                        }, 10000);
                    }
                    return;
                }
                if (finishTimer) { clearTimeout(finishTimer); finishTimer = null; }
                streamEnded = true;
                lastLogLineCount = text.replace(/##FINISHED##\s*$|##CONTINUE##\s*$/, '').replace(/\n+$/, '').split('\n').length;
                finalCleanup(false);
                if (logStreamReconnect) clearTimeout(logStreamReconnect);
                logStreamReconnect = setTimeout(function() {
                    if (!logStreamXHR) startLog(null, true, scriptName);
                }, 1000);
            };

            xhr.onerror = function() {
                if (logStreamXHR !== xhr) return;
                if (finishTimer) { clearTimeout(finishTimer); finishTimer = null; }
                streamEnded = true;
                lastLogLineCount = (xhr.responseText || '').replace(/##FINISHED##\s*$|##CONTINUE##\s*$/, '').replace(/\n+$/, '').split('\n').length;
                finalCleanup(false);
                if (watchMode) {
                    if (logStreamReconnect) clearTimeout(logStreamReconnect);
                    logStreamReconnect = setTimeout(function() {
                        if (!logStreamXHR) startLog(null, true, scriptName);
                    }, 1000);
                }
            };

            xhr.send();
            logStreamXHR = xhr;
        }

        if (skipClearLog) {
            openStream();
        } else {
            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "del_start_log")%>', null, function() {
                openStream();
            });
        }
    }

    function displayLog(logContent) {
        var hint = document.getElementById('version-hint');
        if (!hint) return;

        var lines = logContent.split('\n');
        var allLines = [];
        for (var i = 0; i < lines.length; i++) {
            var t = lines[i].replace(/##FINISHED##|##CONTINUE##|##FINISH##/g, '').trim().replace(/^\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}\s*/, '');
            if (t) allLines.push(t);
        }
        if (allLines.length === 0) return;

        var maxLines = 3;

        if (!hint.classList.contains('logging')) {
            hint.classList.add('logging');
            hint.innerHTML = '';
            hint.scrollTop = 0;
        }

        var existing = [];
        for (var i = 0; i < hint.children.length; i++) {
            var child = hint.children[i];
            if (child.tagName === 'DIV' && child.textContent !== '\u200B' && child.firstChild && child.firstChild.textContent) {
                existing.push(child.firstChild.textContent);
            }
        }

        var merged = existing.concat(allLines);
        if (merged.length > maxLines) {
            merged = merged.slice(merged.length - maxLines);
        }

        hint.innerHTML = '';
        hint.scrollTop = 0;
        for (var j = 0; j < merged.length; j++) {
            var color = ocGetLogColor(merged[j]);
            var div = document.createElement('div');
            div.style.whiteSpace = 'nowrap';
            div.innerHTML = '<b style="color:' + color + '">' + merged[j] + '</b>';
            hint.appendChild(div);
        }

        while (hint.children.length < maxLines) {
            var spacer = document.createElement('div');
            spacer.style.visibility = 'hidden';
            spacer.style.whiteSpace = 'nowrap';
            spacer.textContent = '\u200B';
            hint.insertBefore(spacer, hint.firstChild);
        }

        hint.style.willChange = 'scroll-position';
        ocAnimateScroll(hint);
    }

    showDefaultNote();
    if (versionTabMode) {
        var tabPopup = document.getElementById('selectPopup');
        if (tabPopup) {
            tabPopup.onclick = function(event) { click_handler(event); };
        }
        setAddrListLoading(true);
        resetDataStatus();
        initAllData();
    }

    function stopLogStream() {
        if (logStreamReconnect) { clearTimeout(logStreamReconnect); logStreamReconnect = null; }
        if (logMaxWaitTimer) { clearTimeout(logMaxWaitTimer); logMaxWaitTimer = null; }
        if (logStreamXHR) { logStreamXHR.abort(); logStreamXHR = null; }
        logStreamActive = false;
        var hint = document.getElementById('version-hint');
        if (hint) { hint.classList.remove('logging'); }
    }
    window.addEventListener('beforeunload', stopLogStream);
    window.addEventListener('pagehide', stopLogStream);
    initPopupResizeObserver();
//]]>
</script>