<%
    local fs = require "luci.openclash"
    local plugin_version = fs.oc_version()
    local uci = require("luci.model.uci").cursor()
    local RELEASE_BRANCH = fs.uci_get_config("config", "release_branch")
%>
<html>
<head>
<script src="/luci-static/resources/openclash/js/common.js?v=<%=plugin_version%>"></script>
<link rel="stylesheet" href="/luci-static/resources/openclash/css/oc.css?v=<%=plugin_version%>">
<script src="/luci-static/resources/openclash/js/cm6.min.js?v=<%=plugin_version%>"></script>
</head>
<fieldset class="cbi-section">
    <table width="100%">
    <tr>
        <td colspan="4">
            <div class="oc">
                <div class="main-cards-container">
                    <div class="announcement-card">
                        <div id="announcement-banner" class="announcement-banner">
                            <div class="megaphone-container">
                                <svg id="megaphone" width="15" height="15"><use href="#oc-icon-megaphone"/></svg>
                            </div>
                            <div id="announcement-content" class="announcement-content"></div>
                        </div>
                    </div>
                    <div class="main-card">
                        <!-- Row 1: Running Status -->
                        <div class="card-row full-width core-status-row">
                            <div class="sub-card">
                                <div class="card-title"><%:Running Status%></div>
                                <div class="version-display-container">
                                    <div class="version-display oc-hidden" id="plugin-version-display">
                                        <svg width="12" height="12"><use href="#oc-icon-package"/></svg>
                                        <span class="version-text" id="plugin-version-text">-</span>
                                    </div>
                                    <div class="version-display oc-hidden" id="core-version-display">
                                        <svg width="12" height="12"><use href="#oc-icon-cpu"/></svg>
                                        <span class="version-text" id="core-version-text">-</span>
                                    </div>
                                </div>
                                <div class="core-main-controls">
                                    <div class="core-status-toggle">
                                        <span id="_clash" class="value-indicator"><%:Collecting data...%></span>
                                        <span id="_oclog" class="value-indicator oc-hidden"><b><%:Collecting data...%></b></span>
                                        <div class="plugin-toggle-container">
                                            <label class="plugin-toggle-switch">
                                                <input type="checkbox" id="plugin_toggle" onchange="togglePlugin(this)">
                                                <span class="plugin-toggle-slider"></span>
                                            </label>
                                        </div>
                                    </div>
                                    <div class="core-control-buttons">
                                        <button type="button" class="icon-btn action-btn" id="restart_core" title="<%:Restart%>" onclick="return restartCore()">
                                            <svg width="14" height="14"><use href="#oc-icon-refresh"/></svg>
                                        </button>
                                        <button type="button" class="icon-btn action-btn" id="edit_overwrite" title="<%:Overwrite Module%>" onclick="return editOverwrite()">
                                            <svg width="32" height="32"><use href="#oc-icon-overwrite"/></svg>
                                            <span class="btn-text"><%:Overwrite Module%></span>
                                        </button>
                                    </div>
                                </div>
                                <div class="card-actions">
                                    <button type="button" class="icon-btn theme-toggle-btn" id="theme-toggle" onclick="toggleThemeMode()">
                                        <svg id="sun-icon" width="14" height="14"><use href="#oc-icon-sun"/></svg>
                                        <svg id="moon-icon" width="14" height="14" class="oc-hidden"><use href="#oc-icon-moon"/></svg>
                                        <svg id="auto-icon" width="14" height="14" class="oc-hidden"><use href="#oc-icon-auto-theme"/></svg>
                                    </button>
                                    <button type="button" class="icon-btn action-btn" id="go_wiki" title="<%:Wiki%>" onclick="return wikipage()"><svg width="14" height="14"><use href="#oc-icon-wiki"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="go_tutorials" title="<%:Tutorials%>" onclick="return gitbookpage()"><svg width="14" height="14"><use href="#oc-icon-book"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="go_star" title="<%:Star%>" onclick="return homepage()"><svg width="14" height="14"><use href="#oc-icon-star"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="go_telegram" title="<%:Telegram%>" onclick="return telegrampage()"><svg width="14" height="14"><use href="#oc-icon-telegram"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="go_sponsor" title="<%:Sponsor%>" onclick="return sponsorpage()"><svg width="14" height="14"><use href="#oc-icon-heart"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="logo_btn" title="Mihomo" onclick="return winOpen('https://www.github.com/metacubex/mihomo');">
                                        <img id="_logo" src="/luci-static/resources/openclash/img/logo.png?v=<%=plugin_version%>" loading="lazy" width="14px" height="14px" alt="Mihomo"/>
                                        <span class="logo-text">Powered by <span class="mihomo-link">Mihomo</span></span>
                                    </button>
                                </div>
                            </div>
                        </div>

                        <!-- Row 2: Running Mode & Proxy Mode -->
                        <div class="card-row dual-cards mode-row">
                            <div class="sub-card">
                                <div class="card-title"><%:Running Mode%></div>
                                <div class="card-content">
                                    <div class="card-value"><span id="_mode" class="value-indicator"><%:Collecting data...%></span></div>
                                    <div class="card-controls">
                                        <div id="radio-ru-mode" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="normal" name="radios-ru" value="" onclick="return switch_run_mode(this.value)"/><label for="normal" id="run_normal" class="cbi-button-option"><%:Compat%></label>
                                            <input type="radio" id="tun" name="radios-ru" value="-tun" onclick="return switch_run_mode(this.value)"/><label for="tun" class="cbi-button-option"><%:TUN%></label>
                                            <input type="radio" id="mix" name="radios-ru" value="-mix" onclick="return switch_run_mode(this.value)"/><label for="mix" class="cbi-button-option"><%:Mix%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="sub-card">
                                <div class="card-title"><%:Proxy Mode%></div>
                                <div class="card-content">
                                    <div class="card-controls">
                                        <div id="radio-mode" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="rule" name="radios" value="rule" onclick="return switch_rule_mode(this.value)"/><label for="rule" class="cbi-button-option"><%:Rule%></label>
                                            <input type="radio" id="global" name="radios" value="global" onclick="return switch_rule_mode(this.value)"/><label for="global" class="cbi-button-option"><%:Global%></label>
                                            <input type="radio" id="direct" name="radios" value="direct" onclick="return switch_rule_mode(this.value)"/><label for="direct" class="cbi-button-option"><%:Direct%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <!-- Row 3: Settings -->
                        <div class="card-row stats-grid">
                            <div class="sub-card">
                                <div class="card-title"><%:Area Bypass%><div class="help-link" title="<%:Bypass Specified Regions Network Flows, Improve Performance, If Inaccessibility on Bypass Gateway, Try to Enable Bypass Gateway Compatible Option%>"><svg width="12" height="12"><use href="#oc-icon-help"/></svg></div></div>
                                <div class="card-content">
                                    <div class="card-controls">
                                        <div id="oc-setting-oversea" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="oc_setting_oversea_1" name="oc-setting-oversea" value="1" onclick="return switch_oc_setting_oversea('1')"/><label for="oc_setting_oversea_1" class="cbi-button-option"><%:Mainland%></label>
                                            <input type="radio" id="oc_setting_oversea_2" name="oc-setting-oversea" value="2" onclick="return switch_oc_setting_oversea('2')"/><label for="oc_setting_oversea_2" class="cbi-button-option"><%:Oversea%></label>
                                            <input type="radio" id="oc_setting_oversea_0" name="oc-setting-oversea" value="0" onclick="return switch_oc_setting_oversea('0')"/><label for="oc_setting_oversea_0" class="cbi-button-option"><%:Off%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="sub-card">
                                <div class="card-title"><%:Sniffer%><a href="javascript:void(0);" onclick="window.open('https://wiki.metacubex.one/config/sniff/?h=sniff#_1', '_blank');" class="help-link" title="<%:Sniff the domain name of the traffics to avoid rule-based proxy failure%>"><svg width="12" height="12"><use href="#oc-icon-help"/></svg></a></div>
                                <div class="card-content">
                                    <div class="card-controls">
                                        <div id="dns-setting-sniffer" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="meta_sniffer_on" name="meta-sniffer-radios" value="1" onclick="return switch_meta_sniffer('1')"/><label for="meta_sniffer_on" class="cbi-button-option"><%:On%></label>
                                            <input type="radio" id="meta_sniffer_off" name="meta-sniffer-radios" value="0" onclick="return switch_meta_sniffer('0')"/><label for="meta_sniffer_off" class="cbi-button-option"><%:Off%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="sub-card">
                                <div class="card-title"><%:DNS Proxy%><a href="javascript:void(0);" onclick="window.open('https://wiki.metacubex.one/config/dns/?h=res#respect-rules', '_blank');" class="help-link" title="<%:DNS querys respect-rules to preventing access failure%>"><svg width="12" height="12"><use href="#oc-icon-help"/></svg></a></div>
                                <div class="card-content">
                                    <div class="card-controls">
                                        <div id="dns-setting-respect" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="respect_rules_on" name="respect-rules-radios" value="1" onclick="return switch_respect_rules('1')"/><label for="respect_rules_on" class="cbi-button-option"><%:On%></label>
                                            <input type="radio" id="respect_rules_off" name="respect-rules-radios" value="0" onclick="return switch_respect_rules('0')"/><label for="respect_rules_off" class="cbi-button-option"><%:Off%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                            <div class="sub-card">
                                <div class="card-title"><%:Stream Unlock%><div class="help-link" title="<%:Auto Select Proxy For Streaming Unlock, Support Netflix, Disney Plus, HBO And YouTube Premium, etc%>"><svg width="12" height="12"><use href="#oc-icon-help"/></svg></div></div>
                                <div class="card-content">
                                    <div class="card-controls">
                                        <div id="stream-unlock-setting" class="cbi-button-group oc-inline-flex">
                                            <input type="radio" id="stream_unlock_on" name="stream-unlock-radios" value="1" onclick="return switch_stream_unlock('1')"/><label for="stream_unlock_on" class="cbi-button-option"><%:On%></label>
                                            <input type="radio" id="stream_unlock_off" name="stream-unlock-radios" value="0" onclick="return switch_stream_unlock('0')"/><label for="stream_unlock_off" class="cbi-button-option"><%:Off%></label>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>

                        <!-- Row 4: Config File -->
                        <div class="card-row full-width">
                            <div class="sub-card">
                                <div class="card-title"><%:Config File%></div>
                                <div class="config-file-content">
                                    <div id="subscription-info-display" class="subscription-info-container oc-hidden">
                                        <div class="subscription-nav-arrow left" id="subscription-prev-arrow" onclick="switchToPreviousConfig()" title="<%:Previous%>"></div>
                                        <div class="subscription-nav-arrow right" id="subscription-next-arrow" onclick="switchToNextConfig()" title="<%:Next%>"></div>
                                        <div class="subscription-info-main">
                                            <div class="subscription-info-header">
                                                <div class="config-file-name-wrapper">
                                                    <div class="config-file-name" id="current-config-name"><%:No Config Selected%></div>
                                                    <span id="age-file-icon" class="age-file-icon oc-hidden"></span>
                                                </div>
                                                <div class="file-info" id="file-info-section">
                                                    <span id="file-modify-time" class="file-info-item" title="<%:Modified%>"><svg class="file-modify-icon" width="14" height="14"><use href="#oc-icon-clock-edit"/></svg><span id="file-modify-time-value">--</span></span>
                                                </div>
                                            </div>
                                            <div class="subscription-info-details">
                                                <div class="subscription-progress oc-hidden" id="subscription-progress-section">
                                                    <div class="subscription-progress-bar">
                                                        <div id="subscription-progress-fill" class="subscription-progress-fill high" style="width: 0%"></div>
                                                    </div>
                                                    <div id="subscription-info-text" class="subscription-info-text"><%:Collecting data...%></div>
                                                </div>
                                                <div class="card-actions subscription-actions-row">
                                                    <div id="subscription-overwrite-tags" class="subscription-overwrite-tags oc-hidden"></div>
                                                    <div class="subscription-action-buttons">
                                                        <button type="button" class="icon-btn action-btn" id="refresh-subscription" title="<%:Refresh%>" onclick="return refreshSubscriptionInfo()">
                                                            <svg width="14" height="14"><use href="#oc-icon-refresh"/></svg>
                                                        </button>
                                                        <button type="button" class="icon-btn action-btn" id="config-subscription-url" title="<%:Specify URL%>" onclick="return setSubscriptionUrl()">
                                                            <svg width="14" height="14"><use href="#oc-icon-chat"/></svg>
                                                        </button>
                                                    </div>
                                                </div>
                                            </div>
                                        </div>
                                    </div>

                                    <div id="config-file-empty-state" class="config-file-empty-state oc-hidden">
                                        <div class="config-file-empty-message"><%:No config files found, Please upload a config file to get started%></div>
                                        <button type="button" class="config-upload-btn-large" id="upload_config_large" onclick="return uploadConfig()">
                                            <svg width="20" height="20"><use href="#oc-icon-upload"/></svg>
                                            <%:Add Config File%>
                                        </button>
                                    </div>

                                    <div class="config-file-bottom">
                                        <div class="config-selector">
                                            <select id="config_file_select" class="config-select"><option value=""><%:Collecting data...%></option></select>
                                        </div>
                                        <div class="card-actions">
                                            <button type="button" class="icon-btn action-btn" id="switch_config" title="<%:SwiTch%>" onclick="return switchConfig()"><svg width="14" height="14"><use href="#oc-icon-switch"/></svg></button>
                                            <button type="button" class="icon-btn action-btn" id="update_config" title="<%:Update%>" onclick="return updateConfig()"><svg width="14" height="14"><use href="#oc-icon-update"/></svg></button>
                                            <button type="button" class="icon-btn action-btn" id="edit_config" title="<%:Edit%>" onclick="return editConfig()"><svg width="14" height="14"><use href="#oc-icon-edit"/></svg></button>
                                            <button type="button" class="icon-btn action-btn" id="edit_subscribe" title="<%:Edit Subscription%>" onclick="return editSubscribe()"><svg width="14" height="14"><use href="#oc-icon-settings"/></svg></button>
                                            <button type="button" class="icon-btn action-btn" id="upload_config" title="<%:Add%>" onclick="return uploadConfig()"><svg width="14" height="14"><use href="#oc-icon-add"/></svg></button>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="main-card">
                        <!-- Row 1: Control Panel & Mix Proxy -->
                        <div class="card-row dual-cards">
                            <div class="sub-card">
                                <div class="card-title"><%:Control Panel%></div>
                                <div class="card-content">
                                    <div class="card-value">
                                        <span id="_daip" class="value-indicator"><%:Collecting data...%></span>
                                    </div>
                                </div>
                                <div class="card-actions">
                                    <button type="button" class="icon-btn copy-btn" id="copy_address" title="<%:Copy Address%>" onclick="return copyAddress()"><svg width="14" height="14"><use href="#oc-icon-copy"/></svg></button>
                                    <button type="button" class="icon-btn copy-btn" id="copy_secret" title="<%:Copy Secret%>" onclick="return copySecret()"><svg width="14" height="14"><use href="#oc-icon-lock"/></svg></button>
                                </div>
                            </div>
                            <div class="sub-card">
                                <div class="card-title"><%:Mix Proxy%></div>
                                <div class="card-content">
                                    <div class="card-value">
                                        <span id="_mix_proxy" class="value-indicator"><%:Collecting data...%></span>
                                    </div>
                                </div>
                                <div class="card-actions">
                                    <button type="button" class="icon-btn copy-btn" id="copy_mix_address" title="<%:Copy Address%>" onclick="return copyMixAddress()"><svg width="14" height="14"><use href="#oc-icon-copy"/></svg></button>
                                    <button type="button" class="icon-btn action-btn" id="copy_pac_config" title="<%:Get PAC Config%>" onclick="return generatePacConfig()"><svg width="14" height="14"><use href="#oc-icon-globe"/></svg></button>
                                    <button type="button" class="icon-btn copy-btn" id="copy_mix_secret" title="<%:Copy Auth Info%>" onclick="return copyMixAuth()"><svg width="14" height="14"><use href="#oc-icon-lock"/></svg></button>
                                </div>
                            </div>
                        </div>

                        <!-- Row 2: Dashboards -->
                        <div class="card-row full-width">
                            <div class="sub-card dashboard-container">
                                <div class="card-title"><%:Control Panel%><div class="help-link" title="<%:If the control panel does not open properly, please try going to Plugin Settings - Dashboard Settings to reset the panel%>"><svg width="12" height="12"><use href="#oc-icon-help"/></svg></div></div>
                                <div class="dashboard-buttons">
                                    <button class="dashboard-btn" id="_web"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_webm"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_webz"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_webo"><%:Collecting data...%></button>
                                </div>
                            </div>
                        </div>

                        <!-- Row 3: Quick Actions -->
                        <div class="card-row full-width">
                            <div class="sub-card quick-actions-container">
                                <div class="card-title"><%:Quick Action%></div>
                                <div class="quick-actions-buttons">
                                    <button class="dashboard-btn" id="_close_all_connection_btn"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_reload_firewall_btn"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_flush_dns_cache_btn"><%:Collecting data...%></button>
                                    <button class="dashboard-btn" id="_one_key_update_btn"><%:Collecting data...%></button>
                                </div>
                            </div>
                        </div>

                        <!-- Row 4: Statistics -->
                        <div class="card-row stats-grid">
                            <div class="sub-card stat-item"><span class="stat-label"><%:Up%></span><span id="upload_" class="stat-value">0 KB/S</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Down%></span><span id="download_" class="stat-value">0 KB/S</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Up Total%></span><span id="uploadtotal_" class="stat-value">0 KB</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Down Total%></span><span id="downloadtotal_" class="stat-value">0 KB</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Connect%></span><span id="connect_t" class="stat-value">0</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Ram%></span><span id="mem_t" class="stat-value">0 KB</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:CPU%></span><span id="cpu_t" class="stat-value">0 %</span></div>
                            <div class="sub-card stat-item"><span class="stat-label"><%:Load Avg%></span><span id="load_a" class="stat-value">0 %</span></div>
                        </div>
                    </div>
                </div>

            <!-- Usage Help Model -->
            <div class="config-upload-model-overlay" id="usage-help-overlay">
                <div class="config-upload-model usage-help-model" id="usage-help-model">
                    <div class="config-upload-header">
                        <div class="config-upload-title"><%:Usage Help%></div>
                        <button type="button" class="icon-btn" onclick="UsageHelpManager.hide()">
                            <svg width="14" height="14"><use href="#oc-icon-close"/></svg>
                        </button>
                    </div>
                    <div class="config-upload-content">
                        <div class="form-group">
                            <label class="highlight-label"><%:Copy the following text to any AI (ChatGPT, DeepSeek, etc.) to get usage help for OpenClash:%></label>
                            <textarea id="usage-help-textarea" class="form-textarea oc-hidden"></textarea>
                            <div id="usage-help-editor"></div>
                        </div>
                    </div>
                    <div class="config-upload-footer">
                        <div class="config-upload-status" id="usage-help-status"></div>
                        <div class="config-upload-buttons">
                            <button type="button" class="btn cancel-btn" onclick="UsageHelpManager.hide()"><%:Cancel%></button>
                            <button type="button" class="btn upload-btn" onclick="UsageHelpManager.copyAndHide()"><%:Copy%></button>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Usage Help Button -->
            <div class="oc-usage-help-float" id="usage-help-float" title="<%:Usage Help%>" onclick="UsageHelpManager.show()">
                <svg width="24" height="24"><use href="#oc-icon-usage-help"/></svg>
                <span class="oc-usage-help-bubble" id="usage-help-bubble"><%:Click to get usage help%></span>
            </div>
            </div>
        </td>
    </tr>
</table>
</fieldset>

<script src="/luci-static/resources/openclash/js/oc-icons.js?v=<%=plugin_version%>"></script>
<script type="text/javascript">//<![CDATA[
    window.levelTranslations = {
        'info': '<%:Info%>',
        'warning': '<%:Warning%>',
        'error': '<%:Error%>',
        'debug': '<%:Debug%>',
        'tip': '<%:Tip%>',
        'watchdog': '<%:Watchdog%>',
        'fatal': '<%:Fatal%>'
    };
    var DOMCache = {
        clash: document.getElementById('_clash'),
        mode: document.getElementById('_mode'),
        web: document.getElementById('_web'),
        webo: document.getElementById('_webo'),
        webm: document.getElementById('_webm'),
        webz: document.getElementById('_webz'),
        daip: document.getElementById('_daip'),
        oclog: document.getElementById('_oclog'),
        close_all_connection: document.getElementById('_close_all_connection_btn'),
        reload_firewall: document.getElementById('_reload_firewall_btn'),
        one_key_update: document.getElementById('_one_key_update_btn'),
        flush_dns_cache: document.getElementById('_flush_dns_cache_btn'),
        radio_mode: document.getElementById('radio-mode'),
        radio: document.getElementsByName("radios"),
        radio_ru: document.getElementsByName("radios-ru"),
        radio_run_normal: document.getElementById("run_normal"),
        copy_secret: document.getElementById('copy_secret'),
        copy_address: document.getElementById('copy_address'),
        copy_mix_address: document.getElementById('copy_mix_address'),
        copy_mix_secret: document.getElementById('copy_mix_secret'),
        copy_pac_config: document.getElementById('copy_pac_config'),
        mix_proxy: document.getElementById('_mix_proxy'),
        dns_setting_sniffer: document.getElementById('dns-setting-sniffer'),
        dns_setting_respect: document.getElementById('dns-setting-respect'),
        meta_sniffer_on: document.getElementById('meta_sniffer_on'),
        meta_sniffer_off: document.getElementById('meta_sniffer_off'),
        respect_rules_on: document.getElementById('respect_rules_on'),
        respect_rules_off: document.getElementById('respect_rules_off'),
        oc_setting_oversea: document.getElementById('oc-setting-oversea'),
        oc_setting_oversea_0: document.getElementById('oc_setting_oversea_0'),
        oc_setting_oversea_1: document.getElementById('oc_setting_oversea_1'),
        oc_setting_oversea_2: document.getElementById('oc_setting_oversea_2'),
        stream_unlock_setting: document.getElementById('stream-unlock-setting'),
        stream_unlock_on: document.getElementById('stream_unlock_on'),
        stream_unlock_off: document.getElementById('stream_unlock_off'),
        core_version_display: document.getElementById('core-version-display'),
        core_version_text: document.getElementById('core-version-text'),
        plugin_version_display: document.getElementById('plugin-version-display'),
        plugin_version_text: document.getElementById('plugin-version-text')
    };

    var getDashboardBaseURL = ocGetDashboardBaseURL;
    var buildDashboardURL = ocBuildDashboardURL;
    var bytesToSize = ocFormatBytes;
    var debounceButton = ocDebounce;

    var DarkModeDetector = {
        init: function() {
            this.applyDarkMode();
        },

        applyDarkMode: function() {
            var sunIcon = document.getElementById('sun-icon');
            var moonIcon = document.getElementById('moon-icon');
            var autoIcon = document.getElementById('auto-icon');
            if (sunIcon && moonIcon && autoIcon) {
                var theme = localStorage.getItem('oc-theme') || 'auto';
                sunIcon.classList.add('oc-hidden');
                moonIcon.classList.add('oc-hidden');
                autoIcon.classList.add('oc-hidden');

                if (theme === 'light') {
                    sunIcon.classList.remove('oc-hidden');
                } else if (theme === 'dark') {
                    moonIcon.classList.remove('oc-hidden');
                } else {
                    autoIcon.classList.remove('oc-hidden');
                }
            }
        }
    };

    var StateManager = {
        current_status: {},
        cached_proxy_info: null,
        last_request_time: {},
        request_cache: {},
        cache_ttl: 5000,
        pending_requests: new Map(),

        _cachedXHR: function(url, params, callback, force) {
            var cacheKey = params ? url + JSON.stringify(params) : url;
            var now = Date.now();

            if (this.pending_requests.has(cacheKey)) {
                this.pending_requests.get(cacheKey).push(callback);
                return;
            }

            var cacheExists = this.request_cache.hasOwnProperty(cacheKey);
            var isCacheStale = cacheExists && (now - this.last_request_time[cacheKey] >= this.cache_ttl);

            if (!force && cacheExists && !isCacheStale) {
                setTimeout(function() {
                    callback({ status: 200, fromCache: true }, StateManager.request_cache[cacheKey]);
                }, 0);
                return;
            }

            if (!force && cacheExists && isCacheStale) {
                setTimeout(function() {
                    callback({ status: 200, fromCache: true, stale: true }, StateManager.request_cache[cacheKey]);
                }, 0);
                this._refreshCacheSilent(url, params, cacheKey);
                return;
            }

            this.pending_requests.set(cacheKey, [callback]);
            var self = this;

            XHR.get(url, params, function(x, data) {
                var callbacks = self.pending_requests.get(cacheKey) || [];
                self.pending_requests.delete(cacheKey);

                if (x && x.status == 200 && data) {
                    self.request_cache[cacheKey] = data;
                    self.last_request_time[cacheKey] = Date.now();
                }

                callbacks.forEach(function(cb) {
                    try {
                        cb(x, data);
                    } catch (e) {
                        console.warn('StateManager: XHR callback error for ' + cacheKey, e);
                    }
                });
            });
        },

        cachedXHRGet: function(url, callback, force) {
            this._cachedXHR(url, null, callback, force);
        },

        cachedXHRGetWithParams: function(url, params, callback, force) {
            this._cachedXHR(url, params, callback, force);
        },

        clearCache: function(url, params) {
            var cacheKey = params ? url + JSON.stringify(params) : url;
            delete this.request_cache[cacheKey];
            delete this.last_request_time[cacheKey];
        },

        clearAllCache: function() {
            this.request_cache = {};
            this.last_request_time = {};
        },

        hasCache: function(url, params) {
            var cacheKey = params ? url + JSON.stringify(params) : url;
            return !!this.request_cache[cacheKey];
        },

        getCacheAge: function(url, params) {
            var cacheKey = params ? url + JSON.stringify(params) : url;
            if (!this.last_request_time[cacheKey]) {
                return null;
            }
            return Date.now() - this.last_request_time[cacheKey];
        },

        setCacheTTL: function(ttl) {
            this.cache_ttl = ttl;
        },

        batchUpdateDOM: function(updates) {
            for (var i = 0; i < updates.length; i++) {
                var update = updates[i];
                if (update.element && update.content !== undefined) {
                    update.element.innerHTML = update.content;
                }
            }
        },

        _refreshCacheSilent: function(url, params, cacheKey) {
            var self = this;
            XHR.get(url, params, function(x, data) {
                if (x && x.status == 200 && data) {
                    self.request_cache[cacheKey] = data;
                    self.last_request_time[cacheKey] = Date.now();
                }
            });
        },

        cachedXHRGetWithRetry: function(url, params, callback, force, maxRetries) {
            maxRetries = maxRetries || 3;
            var retryCount = 0;
            var self = this;

            function attemptRequest() {
                self.cachedXHRGetWithParams(url, params, function(x, data) {
                    if (x && x.status == 200) {
                        callback(x, data);
                    } else if (retryCount < maxRetries) {
                        retryCount++;
                        setTimeout(attemptRequest, 1000 * retryCount);
                    } else {
                        callback(x, data);
                    }
                }, force && retryCount === 0);
            }

            attemptRequest();
        }
    };

    var WSManager = {
        connections: {},
        connectionStates: {
            CONNECTING: 0,
            CONNECTED: 1,
            DISCONNECTED: 2,
            ERROR: 3
        },
        reconnectDelay: 5000,
        heartbeatInterval: 30000,
        heartbeatTimers: {},
        _ws_connect: false,
        _allowedCloseCodes: [1000, 1001, 1005, 1006],

        isSupported: function() {
            return typeof window.WebSocket !== "undefined";
        },

        createConnection: function(type, url, messageHandler, options) {
            options = options || {};

            this.closeConnection(type);

            if (!this.isSupported()) {
                this.enableFallbackMode();
                return null;
            }

            try {
                var ws = new window.WebSocket(url);

                this.connections[type] = {
                    socket: ws,
                    url: url,
                    messageHandler: messageHandler,
                    state: this.connectionStates.CONNECTING,
                    lastActivity: Date.now(),
                    options: options
                };

                if (ws.addEventListener) {
                    ws.addEventListener('open', this.handleOpen.bind(this, type));
                    ws.addEventListener('message', this.handleMessage.bind(this, type));
                    ws.addEventListener('error', this.handleError.bind(this, type));
                    ws.addEventListener('close', this.handleClose.bind(this, type));
                } else {
                    ws.onopen = this.handleOpen.bind(this, type);
                    ws.onmessage = this.handleMessage.bind(this, type);
                    ws.onerror = this.handleError.bind(this, type);
                    ws.onclose = this.handleClose.bind(this, type);
                }

                return ws;
            } catch (error) {
                this.handleConnectionError(type, error);
                return null;
            }
        },

        startHeartbeat: function(type) {
            var self = this;
            this.stopHeartbeat(type);

            this.heartbeatTimers[type] = setInterval(function() {
                var connection = self.connections[type];
                if (connection && connection.socket && connection.state === self.connectionStates.CONNECTED) {
                    var timeSinceLastActivity = Date.now() - connection.lastActivity;
                    if (timeSinceLastActivity > self.heartbeatInterval * 3) {
                        self.reconnectConnection(type);
                    }
                }
            }, this.heartbeatInterval);

            if (!this._visibilityHandlerAdded) {
                document.addEventListener('visibilitychange', function() {
                    if (document.hidden) {
                        for (var t in self.heartbeatTimers) {
                            self.stopHeartbeat(t);
                        }
                    } else {
                        for (var t in self.connections) {
                            if (self.connections[t] && self.connections[t].state === self.connectionStates.CONNECTED) {
                                self.startHeartbeat(t);
                            }
                        }
                    }
                });
                this._visibilityHandlerAdded = true;
            }
        },

        stopHeartbeat: function(type) {
            if (this.heartbeatTimers[type]) {
                clearInterval(this.heartbeatTimers[type]);
                delete this.heartbeatTimers[type];
            }
        },

        reconnectConnection: function(type) {
            var connection = this.connections[type];
            if (connection) {
                this.createConnection(type, connection.url, connection.messageHandler, connection.options);
            }
        },

        closeConnection: function(type) {
            var connection = this.connections[type];
            if (connection && connection.socket) {
                this.stopHeartbeat(type);
                try {
                    connection.socket.close(1000, 'Normal closure');
                } catch (error) {}
                delete this.connections[type];
            }
        },

        closeAll: function() {
            var types = Object.keys(this.connections);
            for (var i = 0; i < types.length; i++) {
                this.closeConnection(types[i]);
            }
            this._ws_connect = false;
        },

        handleMessage: function(type, event) {
            var connection = this.connections[type];
            if (connection && connection.messageHandler) {
                connection.lastActivity = Date.now();
                try {
                    var data;
                    try {
                        data = JSON.parse(event.data);
                    } catch (e) {
                        data = event.data;
                    }
                    connection.messageHandler({ data: data, raw: event.data, event: event });
                } catch (error) {
                    console.warn('WSManager: message handler error for type=' + type, error);
                }
            }
        },

        handleOpen: function(type, event) {
            var connection = this.connections[type];
            if (connection) {
                connection.state = this.connectionStates.CONNECTED;
                connection.lastActivity = Date.now();
            }
            this.startHeartbeat(type);

            if (!this._ws_connect) {
                var connectedCount = 0;
                for (var t in this.connections) {
                    if (this.connections[t].state === this.connectionStates.CONNECTED) {
                        connectedCount++;
                    }
                }
                if (connectedCount >= 1) {
                    this._ws_connect = true;
                    if (NetworkStatsManager.isEnabled) {
                        NetworkStatsManager.stop();
                    }
                }
            }
        },

        handleError: function(type, error) {
            var connection = this.connections[type];
            if (connection) {
                connection.state = this.connectionStates.ERROR;
            }
            this.stopHeartbeat(type);
            this.scheduleReconnect(type);
            this.enableFallbackMode();
        },

        handleClose: function(type, event) {
            var connection = this.connections[type];
            if (connection) {
                connection.state = this.connectionStates.DISCONNECTED;
            }
            this.stopHeartbeat(type);

            if (this._allowedCloseCodes.indexOf(event.code) === -1) {
                this.scheduleReconnect(type);
            }
        },

        handleConnectionError: function(type, error) {
            var connection = this.connections[type];
            if (connection) {
                connection.state = this.connectionStates.ERROR;
            }
            this.scheduleReconnect(type);
            this.enableFallbackMode();
        },

        scheduleReconnect: function(type) {
            var self = this;
            var connection = this.connections[type];

            if (!connection) {
                this.enableFallbackMode();
                return;
            }

            var url = connection.url;
            var handler = connection.messageHandler;
            var options = connection.options;

            setTimeout(function() {
                self.createConnection(type, url, handler, options);
            }, this.reconnectDelay);
        },

        enableFallbackMode: function() {
            this._ws_connect = false;
            this.closeAll();
            NetworkStatsManager.resetStats();
            if (!NetworkStatsManager.isEnabled) {
                NetworkStatsManager.start();
            }
        },

        getConnectionState: function(type) {
            var connection = this.connections[type];
            return connection ? connection.state : this.connectionStates.DISCONNECTED;
        },

        getAllConnectionStates: function() {
            var states = {};
            for (var type in this.connections) {
                states[type] = this.getConnectionState(type);
            }
            return states;
        },

        hasActiveConnections: function() {
            for (var type in this.connections) {
                if (this.getConnectionState(type) === this.connectionStates.CONNECTED) {
                    return true;
                }
            }
            return false;
        }
    };

    var ConfigFileManager = {
        configList: [],
        currentConfig: '',
        currentConfigIndex: -1,
        selectElement: null,
        rawCurrentConfig: '',

        init: function() {
            this.selectElement = document.getElementById('config_file_select');
            if (this.selectElement) {
                this.loadConfigFileList(true);
                this.setupEventListeners();
            }
        },

        setupEventListeners: function() {
            if (this.selectElement) {
                this.selectElement.addEventListener('change', this.onConfigChange.bind(this));
            }
        },

        loadConfigFileList: function(force) {
            this.updateSelectOptions([{value: '', text: '<%:Collecting data...%>', disabled: true}]);

            var params = {};
            var cached = localStorage.getItem('oc_config_fp');
            if (cached) {
                try {
                    var c = JSON.parse(cached);
                    if (c && c.fp) {
                        var ageCache = localStorage.getItem('oc_config_age');
                        if (ageCache) {
                            try {
                                var a = JSON.parse(ageCache);
                                if (a && a.fp === c.fp) params.fingerprint = c.fp;
                            } catch (e) {}
                        }
                    }
                } catch (e) {}
            }

            StateManager.cachedXHRGetWithParams('<%=luci.dispatcher.build_url("admin", "services", "openclash", "config_file_list")%>', params, function(x, data) {
                if (x && x.status == 200) {
                    ConfigFileManager.handleConfigListResponse(data, params.fingerprint);
                } else {
                    ConfigFileManager.handleConfigListError();
                }
            }, !!force);
        },

        handleConfigListResponse: function(data, sentFp) {
            try {
                var configFiles = [];
                var currentConfigFile = '';

                if (data.config_files && Array.isArray(data.config_files)) {
                    configFiles = data.config_files;
                }

                if (data.current_config) {
                    currentConfigFile = data.current_config;
                }

                var fp = data.fingerprint;
                var ageFromCache = false;
                if (fp && sentFp && fp === sentFp) {
                    var ageCache = localStorage.getItem('oc_config_age');
                    if (ageCache) {
                        try {
                            var ac = JSON.parse(ageCache);
                            if (ac && ac.fp === fp && ac.ages) {
                                for (var j = 0; j < configFiles.length; j++) {
                                    var fname = configFiles[j].name.replace(/\.(yaml|yml)$/i, '');
                                    if (ac.ages.hasOwnProperty(fname)) {
                                        configFiles[j].age = ac.ages[fname];
                                    }
                                }
                                ageFromCache = true;
                            }
                        } catch (e) {}
                    }
                }

                if (fp && !ageFromCache) {
                    var ages = {};
                    for (var k = 0; k < configFiles.length; k++) {
                        var fn = configFiles[k].name.replace(/\.(yaml|yml)$/i, '');
                        ages[fn] = configFiles[k].age || false;
                    }
                    (window.requestIdleCallback || function(cb) { setTimeout(cb, 1); })(function() {
                        localStorage.setItem('oc_config_fp', JSON.stringify({fp: fp}));
                        localStorage.setItem('oc_config_age', JSON.stringify({fp: fp, ages: ages}));
                    });
                }

                var currentFileInfo = null;

                this.rawCurrentConfig = currentConfigFile;
                this.configList = configFiles;
                window.configFiles = configFiles;
                this.currentConfig = currentConfigFile;
                this.currentConfigIndex = -1;

                this.toggleEmptyState(configFiles.length === 0);

                this.updateConfigSelect(configFiles, currentConfigFile);

                if (configFiles.length > 0) {
                    if (currentConfigFile) {
                        for (var i = 0; i < configFiles.length; i++) {
                            var file = configFiles[i];
                            var filePath = typeof file === 'string' ? file : (file.path || file.filepath || file);

                            if (filePath === currentConfigFile) {
                                this.currentConfigIndex = i;
                                if (typeof file === 'object' && file.mtime && file.size) {
                                    currentFileInfo = {
                                        mtime: file.mtime,
                                        size: file.size,
                                        age: file.age
                                    };
                                }
                                break;
                            }
                        }
                    }

                    this.updateSubscriptionDisplay(currentConfigFile, currentFileInfo);

                    if (SubscriptionManager.currentConfigFile !== currentConfigFile) {
                        SubscriptionManager.currentConfigFile = currentConfigFile;
                        SubscriptionManager.getSubscriptionInfo();
                    }
                } else {
                    this.hideSubscriptionDisplay();
                    SubscriptionManager.currentConfigFile = '';
                }

                this.updateNavigationArrows();

                if (this._retryGetConfigList) {
                    clearInterval(this._retryGetConfigList);
                    this._retryGetConfigList = null;
                }

            } catch (e) {
                this.handleConfigListError();
            }
        },

        handleConfigListError: function() {
            this.updateSelectOptions([
                {value: '', text: '<%:Failed to load config files%>', disabled: true}
            ]);

            this.toggleEmptyState(true);
            this.hideSubscriptionDisplay();

            if (!this._retryGetConfigList) {
                var self = this;
                this._retryGetConfigList = setInterval(function() {
                    self.loadConfigFileList(true);
                }, 5000);
            }
        },

        toggleEmptyState: function(isEmpty) {
            var emptyStateElement = document.getElementById('config-file-empty-state');
            var configFileBottom = document.querySelector('.config-file-bottom');
            var configFileContent = document.querySelector('.config-file-content');

            if (isEmpty) {
                if (emptyStateElement) {
                    emptyStateElement.classList.remove('oc-hidden');
                }
                if (configFileBottom) {
                    configFileBottom.classList.add('oc-hidden');
                }
                if (configFileContent) {
                    configFileContent.classList.add('empty-state');
                }
                this.hideSubscriptionDisplay();
            } else {
                if (emptyStateElement) {
                    emptyStateElement.classList.add('oc-hidden');
                }
                if (configFileBottom) {
                    configFileBottom.classList.remove('oc-hidden');
                }
                if (configFileContent) {
                    configFileContent.classList.remove('empty-state');
                }
            }
        },

        hideSubscriptionDisplay: function() {
            var subscriptionDisplay = document.getElementById('subscription-info-display');
            if (subscriptionDisplay) {
                subscriptionDisplay.classList.add('oc-hidden');
            }
        },

        updateConfigSelect: function(configFiles, currentConfig) {
            var options = [];

            if (!configFiles || configFiles.length === 0) {
                options.push({
                    value: '',
                    text: '<%:No config files found%>',
                    disabled: true
                });
            } else {
                options.push({
                    value: '',
                    text: '<%:Please select a config file%>',
                    disabled: false
                });

                configFiles.forEach(function(file) {
                    var fileName, filePath;

                    if (typeof file === 'string') {
                        fileName = file;
                        filePath = file;
                    } else {
                        fileName = file.name || file.filename || file.path || file;
                        filePath = file.path || file.filepath || file.name || file;
                    }

                    var displayName = fileName;

                    options.push({
                        value: filePath,
                        text: displayName,
                        disabled: false,
                        selected: filePath === currentConfig || fileName === currentConfig
                    });
                });
            }

            this.updateSelectOptions(options);
        },

        updateSelectOptions: function(options) {
            if (!this.selectElement) return;

            this.selectElement.innerHTML = '';

            options.forEach(function(option) {
                var optionElement = document.createElement('option');
                optionElement.value = option.value;
                optionElement.textContent = option.text;
                optionElement.disabled = option.disabled || false;
                optionElement.selected = option.selected || false;

                this.selectElement.appendChild(optionElement);
            }, this);
        },

        updateSubscriptionDisplay: function(configFile, fileInfo) {
            var container = document.getElementById('subscription-info-display');
            var configNameElement = document.getElementById('current-config-name');
            var fileModifyTimeElement = document.getElementById('file-modify-time');
            var detailsSection = document.getElementById('subscription-info-details');
            var configFileContent = document.querySelector('.config-file-content');
            var ageFileIconElement = document.getElementById('age-file-icon');

            if (!container) return;

            if (this.configList.length === 0) {
                container.classList.add('oc-hidden');
                return;
            }

            if (!configFile) {
                container.classList.add('oc-hidden');
                return;
            }

            if (configFileContent && configFileContent.classList.contains('empty-state')) {
                container.classList.add('oc-hidden');
                return;
            }

            container.classList.remove('oc-hidden');

            if (configNameElement) {
                var displayName = this.formatDisplayName(configFile);
                if (configFile === this.rawCurrentConfig) {
                    configNameElement.innerHTML =
                        '<span class="selected-file-dot"></span>' + displayName;
                } else {
                    configNameElement.textContent = displayName;
                }
                if (configNameElement.scrollWidth > configNameElement.clientWidth) {
                    configNameElement.title = displayName;
                } else {
                    configNameElement.title = '';
                }
            }

            if (fileInfo) {
                if (fileModifyTimeElement) {
                    var modifyTime = this.formatUnixTime(fileInfo.mtime);
                    var fileModifyTimeValue = document.getElementById('file-modify-time-value');
                    fileModifyTimeValue.textContent = modifyTime;
                }

                if (detailsSection) {
                    detailsSection.style.display = 'flex';
                }

                if (ageFileIconElement) {
                    if (fileInfo.age) {
                        ageFileIconElement.innerHTML = '<svg width="16" height="16"><use href="#oc-icon-shield-check"/></svg>';
                        ageFileIconElement.title = '<%:Age Encryption File%>';
                        ageFileIconElement.classList.remove('oc-hidden');
                    } else {
                        ageFileIconElement.innerHTML = '';
                        ageFileIconElement.classList.add('oc-hidden');
                    }
                }
            } else {
                if (fileModifyTimeElement) {
                    var fileModifyTimeValue = document.getElementById('file-modify-time-value');
                    var modifyTimeText = '--';
                    fileModifyTimeValue.textContent = modifyTimeText;
                }

                if (detailsSection) {
                    detailsSection.style.display = 'none';
                }

                if (ageFileIconElement) {
                    ageFileIconElement.innerHTML = '';
                    ageFileIconElement.classList.add('oc-hidden');
                }
            }
        },

        formatUnixTime: ocFormatUnixTime,

        formatFileSize: ocFormatFileSize,

        formatDisplayName: function(fileName) {
            if (!fileName) return '<%:Unknown%>';

            var name = fileName.split('/').pop().split('\\').pop();

            //name = name.replace(/\.(yaml|yml)$/i, '');

            if (name.length > 30) {
                name = name.substring(0, 27) + '...';
            }

            return name;
        },

        onConfigChange: function(event) {
            var selectedValue = event.target.value;

            if (selectedValue) {
                this.currentConfig = selectedValue;

                for (var i = 0; i < this.configList.length; i++) {
                    var file = this.configList[i];
                    var filePath = typeof file === 'string' ? file : (file.path || file.filepath || file);

                    if (filePath === selectedValue) {
                        this.currentConfigIndex = i;
                        break;
                    }
                }

                var selectedFileInfo = this.getConfigFileInfo(selectedValue);
                this.updateSubscriptionDisplay(selectedValue, selectedFileInfo);
                this.updateNavigationArrows();

                if (SubscriptionManager.currentConfigFile !== selectedValue) {
                    SubscriptionManager.currentConfigFile = selectedValue;
                    var detailsSection = document.getElementById('subscription-info-details');
                    if (detailsSection) {
                        detailsSection.style.display = 'none';
                    }
                    SubscriptionManager.getSubscriptionInfo();
                }
                OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
            } else {
                this.currentConfig = '';
                this.currentConfigIndex = -1;
                SubscriptionManager.currentConfigFile = '';
                this.hideSubscriptionDisplay();
                this.updateNavigationArrows();
                OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);
            }
        },

        getConfigFileInfo: function(selectedValue) {
            var selectedFileInfo = null;
            for (var i = 0; i < this.configList.length; i++) {
                var file = this.configList[i];
                var filePath = typeof file === 'string' ? file : (file.path || file.filepath || file);
                if (filePath === selectedValue) {
                    if (typeof file === 'object' && file.mtime && file.size) {
                        selectedFileInfo = {
                            mtime: file.mtime,
                            size: file.size,
                            age: file.age
                        };
                    }
                    break;
                }
            }
            return selectedFileInfo;
        },

        updateNavigationArrows: function() {
            var prevArrow = document.getElementById('subscription-prev-arrow');
            var nextArrow = document.getElementById('subscription-next-arrow');

            if (!prevArrow || !nextArrow) return;

            var hasMultipleConfigs = this.configList.length > 1;
            var currentIndex = this.currentConfigIndex;

            if (!hasMultipleConfigs || currentIndex === -1) {
                prevArrow.style.display = 'none';
                nextArrow.style.display = 'none';
                return;
            }

            prevArrow.style.display = 'block';
            nextArrow.style.display = 'block';

            prevArrow.classList.remove('disabled');
            nextArrow.classList.remove('disabled');
        },

        switchToConfigByIndex: function(index) {
            if (this.configList.length === 0) {
                return false;
            }

            if (index < 0) {
                index = this.configList.length - 1; 
            } else if (index >= this.configList.length) {
                index = 0;
            }

            var file = this.configList[index];
            var filePath = typeof file === 'string' ? file : (file.path || file.filepath || file);
            if (this.selectElement) {
                this.selectElement.value = filePath;
            }

            this.currentConfig = filePath;
            this.currentConfigIndex = index;

            var fileInfo = this.getConfigFileInfo(filePath);
            this.updateSubscriptionDisplay(filePath, fileInfo);
            this.updateNavigationArrows();

            if (SubscriptionManager.currentConfigFile !== filePath) {
                SubscriptionManager.currentConfigFile = filePath;
                var detailsSection = document.getElementById('subscription-info-details');
                if (detailsSection) {
                    detailsSection.style.display = 'none';
                }
                SubscriptionManager.getSubscriptionInfo();
            }

            OverwriteSubscribeManager.render(OverwriteSubscribeManager.data);

            return true;
        },

        refreshConfigList: function() {
            this.loadConfigFileList(false);
        },

        getCurrentConfig: function() {
            return this.currentConfig;
        },

        getSelectedConfig: function() {
            return this.selectElement ? this.selectElement.value : '';
        }
    };

    var SubscriptionManager = {
        currentConfigFile: '',
        retryCount: 0,
        maxRetries: 5,
        updateTimer: null,
        isInitialized: false,

        enableOverflowDrag: function(element) {
            if (!element) return;

            function updateDragState() {
                var isOverflowing = element.scrollWidth > element.clientWidth + 1;
                if (isOverflowing) {
                    element.classList.add('is-draggable');
                } else {
                    element.classList.remove('is-draggable');
                    element.classList.remove('dragging');
                    element.scrollLeft = 0;
                }
            }

            if (!element._dragBound) {
                var isDragging = false;
                var startX = 0;
                var startScrollLeft = 0;
                var touchIdentifier = null;

                function endDrag() {
                    if (!isDragging) return;
                    isDragging = false;
                    touchIdentifier = null;
                    element.classList.remove('dragging');
                }

                element.addEventListener('mousedown', function(e) {
                    if (!element.classList.contains('is-draggable')) return;
                    isDragging = true;
                    startX = e.pageX - element.getBoundingClientRect().left;
                    startScrollLeft = element.scrollLeft;
                    element.classList.add('dragging');
                    e.preventDefault();
                });

                element.addEventListener('mousemove', function(e) {
                    if (!isDragging) return;
                    var x = e.pageX - element.getBoundingClientRect().left;
                    var walk = (x - startX) * 1.2;
                    element.scrollLeft = startScrollLeft - walk;
                    e.preventDefault();
                });

                element.addEventListener('mouseleave', function() {
                    endDrag();
                });

                element.addEventListener('mouseup', function() {
                    endDrag();
                });

                element.addEventListener('touchstart', function(e) {
                    if (!element.classList.contains('is-draggable')) return;
                    if (!e.touches || e.touches.length !== 1) return;

                    var touch = e.touches[0];
                    isDragging = true;
                    touchIdentifier = touch.identifier;
                    startX = touch.pageX - element.getBoundingClientRect().left;
                    startScrollLeft = element.scrollLeft;
                    element.classList.add('dragging');
                }, { passive: true });

                element.addEventListener('touchmove', function(e) {
                    if (!isDragging || !e.touches || e.touches.length === 0) return;

                    var touch = null;
                    for (var i = 0; i < e.touches.length; i++) {
                        if (e.touches[i].identifier === touchIdentifier) {
                            touch = e.touches[i];
                            break;
                        }
                    }

                    if (!touch) return;

                    var x = touch.pageX - element.getBoundingClientRect().left;
                    var walk = (x - startX) * 1.2;
                    element.scrollLeft = startScrollLeft - walk;
                    e.preventDefault();
                }, { passive: false });

                element.addEventListener('touchend', function() {
                    endDrag();
                });

                element.addEventListener('touchcancel', function() {
                    endDrag();
                });

                element.addEventListener('dragstart', function(e) {
                    e.preventDefault();
                });

                element._dragBound = true;
            }

            updateDragState();
        },

        init: function() {
            if (this.isInitialized) return;
            this.isInitialized = true;
            SubscriptionManager.loadSubscriptionInfo();
            SubscriptionManager.startAutoUpdate();
        },

        loadSubscriptionInfo: function() {
            var currentConfig = ConfigFileManager.getCurrentConfig() || ConfigFileManager.getSelectedConfig();
            if (currentConfig !== this.currentConfigFile) {
                this.currentConfigFile = currentConfig;
                this.getSubscriptionInfo();
            }
        },

        getSubscriptionInfo: function() {
            if (ConfigFileManager.configList.length === 0) {
                return;
            }

            if (!this.currentConfigFile) return;

            var requestConfigFile = this.currentConfigFile;
            var filename = this.extractFilename(this.currentConfigFile);
            if (!filename) return;

            var cachedData = localStorage.getItem('sub_info_' + filename);
            var shouldFetchNew = true;

            if (cachedData) {
                try {
                    var parsedData = JSON.parse(cachedData);

                    if (parsedData.providers) {
                        if (parsedData.providers.length > 0) {
                            if (this.currentConfigFile === requestConfigFile) {
                                this.displaySubscriptionInfo(parsedData);
                            }
                        } else {
                            if (this.currentConfigFile === requestConfigFile) {
                                this.showNoInfo();
                            }
                        }
                    }

                    if (parsedData.get_time) {
                        var currentTime = Math.floor(Date.now() / 1000);
                        var cacheTime = parseInt(parsedData.get_time);
                        var timeDiff = currentTime - cacheTime;
                        var halfHourInSeconds = 30 * 60;

                        if (timeDiff <= halfHourInSeconds) {
                            shouldFetchNew = false;
                        }
                    }
                } catch (e) {
                    shouldFetchNew = true;
                }
            }

            if (shouldFetchNew) {
                if (!cachedData) {
                    this.showLoading();
                }

                StateManager.cachedXHRGetWithParams('<%=luci.dispatcher.build_url("admin", "services", "openclash", "sub_info_get")%>', {filename: filename}, function(x, status) {
                    if (SubscriptionManager.currentConfigFile !== requestConfigFile) {
                        return;
                    }

                    var needsErrorHandling = false;
                    if (x && x.status == 200 && status.providers.length > 0) {
                        var newProvidersLength = status.providers.length;
                        var cachedProvidersLength = (parsedData && parsedData.providers) ? parsedData.providers.length : 0;

                        if (newProvidersLength !== cachedProvidersLength && cachedProvidersLength > newProvidersLength) {
                            needsErrorHandling = true;
                        }

                        if (!needsErrorHandling) {
                            SubscriptionManager.retryCount = 0;
                            (window.requestIdleCallback || function(cb) { setTimeout(cb, 1); })(function() { localStorage.setItem('sub_info_' + filename, JSON.stringify(status)); });
                            if (status.providers && status.providers.length === 0) {
                                SubscriptionManager.showNoInfo();
                            }
                            SubscriptionManager.displaySubscriptionInfo(status);
                        }
                    } else {
                        needsErrorHandling = true;
                        if (!cachedData) {
                            SubscriptionManager.showNoInfo();
                        }
                    }

                    if (needsErrorHandling) {
                        SubscriptionManager.handleError(status);
                    }
                }, true);
            }
        },

        displaySubscriptionInfo: function(data) {
            if (ConfigFileManager.configList.length === 0) {
                return;
            }

            var container = document.getElementById('subscription-info-display');
            var progressSection = document.getElementById('subscription-progress-section');
            var detailsSection = document.getElementById('subscription-info-details');

            if (!container) return;

            var configFileContent = document.querySelector('.config-file-content');
            if (configFileContent && configFileContent.classList.contains('empty-state')) {
                return;
            }

            container.classList.remove('oc-hidden');

            if (data && data.providers && data.providers.length > 0) {
                if (data.providers.length > 1 || data.providers[0].provider_name) {
                    this.displayMultipleProviders(data.providers, progressSection);
                } else {
                    this.displaySingleProvider(data.providers[0], progressSection);
                }
            } else {
                progressSection.classList.add('oc-hidden');
            }

            if (detailsSection) {
                detailsSection.style.display = 'flex';
            }
        },

        displaySingleProvider: function(provider, progressSection) {
            if (!progressSection) return;

            progressSection.innerHTML = '';
            progressSection.classList.remove('oc-hidden');
            progressSection.className = 'subscription-progress';

            var progressBar = document.createElement('div');
            progressBar.className = 'subscription-progress-bar';

            var progressFill = document.createElement('div');
            progressFill.className = 'subscription-progress-fill';
            progressBar.appendChild(progressFill);

            var infoText = document.createElement('div');
            infoText.className = 'subscription-info-text';

            progressSection.appendChild(progressBar);
            progressSection.appendChild(infoText);

            var percent = parseFloat(provider.percent) || 0;
            var used = String(provider.surplus || provider.used || '0 B');
            var total = String(provider.total || '0 B');
            var expire = String(provider.expire || '');
            var daysLeft = parseInt(provider.day_left, 10);
            if (isNaN(daysLeft)) daysLeft = 0;

            progressFill.style.width = percent + '%';
            progressFill.className = 'subscription-progress-fill ' +
                (percent >= 50 ? 'high' : (percent >= 20 ? 'medium' : 'low'));

            var infoString = used + ' / ' + total + ' (' + percent + '%)';
            var tooltipString = infoString;
            if (expire && expire !== 'null' && daysLeft > 0) {
                infoString += ' • ' + expire + ' (<%:Remaining%> ' + daysLeft + ' <%:days%>)';
                tooltipString += '\n' + expire + ' (<%:Remaining%> ' + daysLeft + ' <%:days%>)';
            }

            infoText.textContent = infoString;
            infoText.title = tooltipString;
            this.enableOverflowDrag(infoText);
        },

        displayMultipleProviders: function(providers, progressSection) {
            if (!progressSection) return;

            var displayProviders = providers;
            var providerCount = displayProviders.length;

            progressSection.innerHTML = '';
            progressSection.classList.remove('oc-hidden');
            progressSection.className = 'subscription-providers-grid';
            progressSection.setAttribute('data-count', providerCount);

            displayProviders.forEach(function(provider) {
                var card = document.createElement('div');
                card.className = 'provider-card';

                var cardName = document.createElement('div');
                var providerName = String(provider.provider_name || 'Unknown');
                cardName.className = 'subscription-info-title';
                cardName.textContent = providerName;
                cardName.title = providerName;
                card.appendChild(cardName);

                var progressBar = document.createElement('div');
                progressBar.className = 'subscription-progress-bar';

                var progressFill = document.createElement('div');
                var percent = parseFloat(provider.percent) || 0;
                progressFill.style.width = percent + '%';
                progressFill.className = 'subscription-progress-fill ' +
                    (percent >= 50 ? 'high' : (percent >= 20 ? 'medium' : 'low'));

                progressBar.appendChild(progressFill);
                card.appendChild(progressBar);

                var infoText = document.createElement('div');
                infoText.className = 'subscription-info-text';

                var used = String(provider.used || '0 B');
                var total = String(provider.total || 'N/A');
                var expire = String(provider.expire || '');
                var daysLeft = parseInt(provider.day_left, 10);
                if (isNaN(daysLeft)) daysLeft = 0;

                var infoString = used + ' / ' + total + ' (' + percent + '%)';
                if (expire && expire !== 'null' && daysLeft > 0) {
                    infoString += ' • ' + expire + ' (<%:Remaining%> ' + daysLeft + ' <%:days%>)';
                }

                var tooltipString = used + ' / ' + total + ' (' + percent + '%)';
                if (expire && expire !== 'null' && daysLeft > 0) {
                    tooltipString += '\n' + expire + ' (<%:Remaining%> ' + daysLeft + ' <%:days%>)';
                }

                infoText.textContent = infoString;
                infoText.title = tooltipString;
                SubscriptionManager.enableOverflowDrag(infoText);

                card.appendChild(infoText);
                progressSection.appendChild(card);
            });

            if (providerCount > 3) {
                this.initDragScroll(progressSection);
            }
        },

        initDragScroll: function(element) {
            var isDragging = false;
            var startX;
            var scrollLeft;
            var hasMoved = false;

            var onMouseDown = function(e) {
                if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON') {
                    return;
                }

                isDragging = true;
                hasMoved = false;
                startX = e.pageX - element.offsetLeft;
                scrollLeft = element.scrollLeft;
                element.classList.add('dragging');
            };

            var onMouseMove = function(e) {
                if (!isDragging) return;

                var x = e.pageX - element.offsetLeft;
                var walk = (x - startX) * 1.5;

                if (Math.abs(walk) > 5) {
                    hasMoved = true;
                    e.preventDefault();
                    element.scrollLeft = scrollLeft - walk;
                }
            };

            var onMouseUp = function() {
                isDragging = false;
                element.classList.remove('dragging');
            };

            var onMouseLeave = function() {
                if (isDragging) {
                    isDragging = false;
                    element.classList.remove('dragging');
                }
            };

            element.addEventListener('mousedown', onMouseDown);
            element.addEventListener('mousemove', onMouseMove);
            element.addEventListener('mouseup', onMouseUp);
            element.addEventListener('mouseleave', onMouseLeave);

            element.addEventListener('dragstart', function(e) {
                e.preventDefault();
            });
        },

        showNoInfo: function() {
            if (ConfigFileManager.configList.length === 0) {
                return;
            }

            var container = document.getElementById('subscription-info-display');
            var progressSection = document.getElementById('subscription-progress-section');
            var detailsSection = document.getElementById('subscription-info-details');

            if (!container) return;

            var configFileContent = document.querySelector('.config-file-content');
            if (configFileContent && configFileContent.classList.contains('empty-state')) {
                return;
            }

            container.classList.remove('oc-hidden');

            if (progressSection) {
                progressSection.innerHTML = '<div class="subscription-no-info"><%:No Sub Info Found%></div>';
                progressSection.classList.remove('oc-hidden');
                progressSection.className = 'subscription-progress';
            }

            if (detailsSection) {
                var fileModifyTimeElement = document.getElementById('file-modify-time');

                var hasFileInfo = false;
                if (fileModifyTimeElement) {
                    var modifyTimeText = fileModifyTimeElement.textContent || '';
                    hasFileInfo = !modifyTimeText.includes('--');
                }

                detailsSection.style.display = hasFileInfo ? 'flex' : 'none';
            }
        },

        showLoading: function() {
            if (ConfigFileManager.configList.length === 0) {
                return;
            }

            var container = document.getElementById('subscription-info-display');
            var progressSection = document.getElementById('subscription-progress-section');
            var detailsSection = document.getElementById('subscription-info-details');

            if (!container) return;

            var configFileContent = document.querySelector('.config-file-content');
            if (configFileContent && configFileContent.classList.contains('empty-state')) {
                return;
            }

            container.classList.remove('oc-hidden');

            if (progressSection) {
                progressSection.innerHTML = '<div class="subscription-loading"><span class="loading-spinner"></span><%:Collecting data...%></div>';
                progressSection.classList.remove('oc-hidden');
                progressSection.className = 'subscription-progress';
            }

            if (detailsSection) {
                detailsSection.style.display = 'none';
            }
        },

        handleError: function(status) {
            if (this.retryCount >= this.maxRetries) {
                this.retryCount = 0;
                if (this.currentConfigFile && status && status.providers) {
                    var errFilename = this.extractFilename(this.currentConfigFile);
                    if (errFilename) {
                        (window.requestIdleCallback || function(cb) { setTimeout(cb, 1); })(function() { localStorage.setItem('sub_info_' + errFilename, JSON.stringify(status)); });
                    }
                }
                if (!status.providers || status.providers.length === 0) {
                    this.showNoInfo();
                }
                if (status.providers && status.providers.length > 0) {
                    SubscriptionManager.displaySubscriptionInfo(status);
                }
            } else {
                this.retryCount++;
                setTimeout(function() {
                    SubscriptionManager.getSubscriptionInfo();
                }, 5000);
            }
        },

        extractFilename: function(path) {
            if (!path) return '';
            var parts = path.split('/');
            var filename = parts[parts.length - 1];

            if (filename.endsWith('.yaml')) {
                filename = filename.slice(0, -5);
            } else if (filename.endsWith('.yml')) {
                filename = filename.slice(0, -4);
            }

            return filename;
        },

        startAutoUpdate: function() {
            if (this.updateTimer) {
                clearTimeout(this.updateTimer);
            }

            this.updateTimer = setTimeout(function() {
                SubscriptionManager.getSubscriptionInfo();
                SubscriptionManager.startAutoUpdate();
            }, 60000 * 15);
        },

        stopAutoUpdate: function() {
            if (this.updateTimer) {
                clearTimeout(this.updateTimer);
                this.updateTimer = null;
            }
        }
    };

    var OverwriteSubscribeManager = {
        container: null,
        data: null,
        isLoading: false,
        _dragBound: false,
        _isDragging: false,
        _dragStartX: 0,
        _dragStartScrollLeft: 0,
        _touchIdentifier: null,

        init: function() {
            this.container = document.getElementById('subscription-overwrite-tags');
            if (!this.container) return;
            this.bindDragScroll();
            this.load(false);
        },

        hasOverflow: function() {
            if (!this.container) return false;
            return this.container.scrollWidth > this.container.clientWidth + 1;
        },

        updateDragScrollState: function() {
            if (!this.container) return;
            if (this.hasOverflow()) {
                this.container.classList.add('drag-scroll-enabled');
            } else {
                this.container.classList.remove('drag-scroll-enabled');
                this.container.classList.remove('dragging');
            }
        },

        bindDragScroll: function() {
            if (!this.container || this._dragBound) {
                return;
            }

            var self = this;

            function endDrag() {
                if (!self._isDragging) return;
                self._isDragging = false;
                self._touchIdentifier = null;
                self.container.classList.remove('dragging');
            }

            this.container.addEventListener('mousedown', function(e) {
                if (!self.hasOverflow()) {
                    return;
                }
                self._isDragging = true;
                self._dragStartX = e.pageX - self.container.offsetLeft;
                self._dragStartScrollLeft = self.container.scrollLeft;
                self.container.classList.add('dragging');
                e.preventDefault();
            });

            this.container.addEventListener('mousemove', function(e) {
                if (!self._isDragging) {
                    return;
                }
                var x = e.pageX - self.container.offsetLeft;
                var walk = (x - self._dragStartX) * 1.5;
                self.container.scrollLeft = self._dragStartScrollLeft - walk;
                e.preventDefault();
            });

            this.container.addEventListener('mouseleave', function() {
                endDrag();
            });

            this.container.addEventListener('mouseup', function() {
                endDrag();
            });

            this.container.addEventListener('touchstart', function(e) {
                if (!self.hasOverflow() || !e.touches || e.touches.length !== 1) {
                    return;
                }
                var touch = e.touches[0];
                self._isDragging = true;
                self._touchIdentifier = touch.identifier;
                self._dragStartX = touch.pageX - self.container.offsetLeft;
                self._dragStartScrollLeft = self.container.scrollLeft;
                self.container.classList.add('dragging');
            }, { passive: true });

            this.container.addEventListener('touchmove', function(e) {
                if (!self._isDragging || !e.touches || e.touches.length === 0) {
                    return;
                }

                var touch = null;
                for (var i = 0; i < e.touches.length; i++) {
                    if (e.touches[i].identifier === self._touchIdentifier) {
                        touch = e.touches[i];
                        break;
                    }
                }
                if (!touch) {
                    return;
                }

                var x = touch.pageX - self.container.offsetLeft;
                var walk = (x - self._dragStartX) * 1.5;
                self.container.scrollLeft = self._dragStartScrollLeft - walk;
                e.preventDefault();
            }, { passive: false });

            this.container.addEventListener('touchend', function() {
                endDrag();
            });

            this.container.addEventListener('touchcancel', function() {
                endDrag();
            });

            this.container.addEventListener('dragstart', function(e) {
                e.preventDefault();
            });

            this._dragBound = true;
        },

        load: function(force) {
            if (!this.container || this.isLoading) {
                return;
            }

            this.isLoading = true;
            var self = this;

            StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "overwrite_subscribe_info")%>', function(x, status) {
                self.isLoading = false;
                if (x && x.status == 200 && status && status.status === 'success' && status.data) {
                    self.data = status.data;
                    self.render(status.data);
                } else {
                    self.render(null);
                }
            }, !!force);
        },

        getTypeClass: function(type) {
            var normalizedType = String(type || '').toLowerCase();
            if (normalizedType === 'http') return 'type-http';
            if (normalizedType === 'file') return 'type-file';
            return 'type-unknown';
        },

        parseModuleConfigList: function(configValue) {
            if (!Array.isArray(configValue)) {
                return [];
            }
            return configValue.map(function(item) {
                return String(item || '').trim();
            }).filter(function(item) {
                return !!item;
            });
        },

        isModuleForCurrentConfig: function(moduleInfo, currentConfigPath) {
            var configList = this.parseModuleConfigList(moduleInfo ? moduleInfo.config : null);
            if (!configList.length) {
                return true;
            }

            if (configList.indexOf('all') !== -1) {
                return true;
            }

            var fullPath = String(currentConfigPath || '').trim();
            if (!fullPath) {
                return false;
            }

            return configList.indexOf(fullPath) !== -1;
        },

        getEnabledModules: function(data) {
            if (!data || typeof data !== 'object') {
                return [];
            }

            var currentConfigPath = ConfigFileManager.getCurrentConfig() || ConfigFileManager.getSelectedConfig();

            var modules = [];
            for (var moduleName in data) {
                if (!Object.prototype.hasOwnProperty.call(data, moduleName)) {
                    continue;
                }

                var moduleInfo = data[moduleName] || {};
                if (parseInt(moduleInfo.enable, 10) === 1 && this.isModuleForCurrentConfig(moduleInfo, currentConfigPath)) {
                    modules.push({
                        name: moduleName,
                        type: moduleInfo.type || 'unknown',
                        order: parseInt(moduleInfo.order, 10) || 0
                    });
                }
            }

            modules.sort(function(a, b) {
                if (a.order === b.order) {
                    return String(a.name).localeCompare(String(b.name));
                }
                return a.order - b.order;
            });

            return modules;
        },

        render: function(data) {
            if (!this.container) return;

            var enabledModules = this.getEnabledModules(data);
            this.container.innerHTML = '';

            if (enabledModules.length === 0) {
                this.container.classList.add('oc-hidden');
                this.updateDragScrollState();
                return;
            }

            for (var i = 0; i < enabledModules.length; i++) {
                var moduleItem = enabledModules[i];
                var tag = document.createElement('span');
                tag.className = 'subscription-overwrite-tag ' + this.getTypeClass(moduleItem.type);
                tag.title = moduleItem.name;
                tag.textContent = moduleItem.name;
                if (moduleItem.type == 'http') {
                    tag.title += ' [<%:HTTP Module%>]';
                } else if (moduleItem.type == 'file') {
                    tag.title += ' [<%:File Module%>]';
                } else {
                    tag.title += ' [<%:Unknown%>]';
                }
                this.container.appendChild(tag);
            }

            this.container.classList.remove('oc-hidden');
            this.updateDragScrollState();
        }
    };

    var LogManager = {
        streamingXHR: null,
        retryCount: 0,
        maxRetries: 3,
        retryDelay: 1000,
        lastLogLineCount: null,
        reconnectTimer: null,

        // Clear the log before streaming so the operation API is served first and the stream does not block it
        startLogDisplay: function(initialMessage, skipClearLog, scriptName) {
            if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
            if (this.streamingXHR) {
                if (initialMessage) {
                    this.streamingXHR.abort();
                    this.streamingXHR = null;
                } else {
                    return;
                }
            }
            if (initialMessage) {
                this.retryCount = 0;
                this.lastLogLineCount = null;
            }

            StateManager.clearCache('<%=luci.dispatcher.build_url("admin", "services", "openclash", "startlog")%>');

            if (DOMCache.oclog) {
                if (initialMessage) {
                    DOMCache.oclog.classList.remove('oc-hidden');
                    DOMCache.oclog.innerHTML = '<b style="color:var(--tip-color)">' + initialMessage + '</b>';
                }
            }

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

            if (hasInitialMessage) {
                if (this.maxWaitTimer) { clearTimeout(this.maxWaitTimer); }
                this.maxWaitTimer = setTimeout(function() {
                    if (self.streamingXHR) {
                        self.streamingXHR.abort();
                        self.streamingXHR = null;
                    }
                }, 600000);
            }

            function openStream() {
                if (self.streamingXHR) {
                    self.streamingXHR.abort();
                    self.streamingXHR = 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(hidePanel) {
                    if (self.streamingXHR === xhr) {
                        self.streamingXHR = null;
                    }
                    if (hidePanel) {
                        var capturedXhr = xhr;
                        setTimeout(function() {
                            if (self.streamingXHR !== capturedXhr && !self.streamingXHR) {
                                if (DOMCache.oclog) {
                                    DOMCache.oclog.classList.add('oc-hidden');
                                    DOMCache.oclog.innerHTML = '';
                                }
                            }
                        }, 1000);
                    }
                }

                xhr.onprogress = function() {
                    if (self.streamingXHR !== xhr) return;
                    if (streamEnded) return;
                    var text = xhr.responseText || '';
                    var startLine = (self.lastLogLineCount != null) ? self.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;
                        self.displayLog(raw);
                    }
                    if (/##FINISHED##\s*$/.test(text) && !finishTimer) {
                        finishTimer = setTimeout(function() {
                            streamEnded = true;
                            finalCleanup(true);
                        }, 10000);
                    }
                };

                xhr.onload = function() {
                    if (self.streamingXHR !== 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;
                    self.lastLogLineCount = text.replace(/##FINISHED##\s*$|##CONTINUE##\s*$/, '').replace(/\n+$/, '').split('\n').length;
                    finalCleanup(false);
                    self.reconnectTimer = setTimeout(function() {
                        self.reconnectTimer = null;
                        if (!self.streamingXHR) self.startLogDisplay(null, true, scriptName);
                    }, 1000);
                };

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

                xhr.send();
                self.streamingXHR = xhr;
            }

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

        stopLogDisplay: function() {
            if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
            if (this.maxWaitTimer) { clearTimeout(this.maxWaitTimer); this.maxWaitTimer = null; }
            if (DOMCache.oclog && DOMCache.oclog._ocScrollAnimId) {
                cancelAnimationFrame(DOMCache.oclog._ocScrollAnimId);
                DOMCache.oclog._ocScrollAnimId = null;
            }
            var xhr = this.streamingXHR;
            this.streamingXHR = null;
            if (xhr) {
                xhr.abort();
            }
            if (DOMCache.oclog) {
                setTimeout(function() {
                    if (!LogManager.streamingXHR) {
                        DOMCache.oclog.classList.add('oc-hidden');
                        DOMCache.oclog.innerHTML = '';
                    }
                }, 1000);
            }
        },

        displayLog: function(logContent) {
            if (!this.streamingXHR || !DOMCache.oclog) 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 (!DOMCache.oclog.wheelBlocked) {
                DOMCache.oclog.addEventListener('wheel', function(e) { e.preventDefault(); }, { passive: false });
                DOMCache.oclog.wheelBlocked = true;
            }

            var existing = [];
            for (var i = 0; i < DOMCache.oclog.children.length; i++) {
                var child = DOMCache.oclog.children[i];
                if (child.tagName === 'DIV' && child.className !== 'oc-log-spacer' && 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);
            }

            DOMCache.oclog.innerHTML = '';
            DOMCache.oclog.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>';
                DOMCache.oclog.appendChild(div);
            }

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

            DOMCache.oclog.classList.remove('oc-hidden');
            DOMCache.oclog.style.willChange = 'scroll-position';

            ocAnimateScroll(DOMCache.oclog);
        }
    };

    var SystemStatusManager = {
        pollTimer: null,
        pollInterval: 5000,
        retryCount: 0,
        maxRetries: 3,
        isEnabled: false,

        _ZERO_SYS_STATS: [
            {id: "cpu_t", html: "<font style=\"color:var(--success-color)\">0 %</font>"},
            {id: "load_a", html: "<font style=\"color:var(--success-color)\">0 %</font>"}
        ],

        resetSysStats: function() {
            for (var i = 0; i < this._ZERO_SYS_STATS.length; i++) {
                var item = this._ZERO_SYS_STATS[i];
                var el = document.getElementById(item.id);
                if (el) el.innerHTML = item.html;
            }
        },

        start: function() {
            if (this.isEnabled) return;
            this.isEnabled = true;
            this.retryCount = 0;
            this.poll();
        },

        stop: function() {
            this.isEnabled = false;
            if (this.pollTimer) {
                clearTimeout(this.pollTimer);
                this.pollTimer = null;
            }
        },

        poll: function() {
            if (!this.isEnabled) return;

            var self = this;
            StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "toolbar_show_sys")%>', function(x, status) {
                if (x && x.status == 200 && x.responseText != "" && status) {
                    self.retryCount = 0;
                    var cpuColor = status.cpu <= 50 ? "var(--success-color)" : (status.cpu <= 80 ? "var(--tip-color)" : "var(--error-color)");
                    var cpuValue = status.cpu <= 100 ? status.cpu + " %" : "0 %";
                    document.getElementById("cpu_t").innerHTML = "<font style=\"color:"+cpuColor+"\">"+cpuValue+"</font>";

                    var loadValue = parseFloat(status.load_avg) || 0;
                    var loadColor = loadValue <= 50 ? "var(--success-color)" : (loadValue <= 80 ? "var(--tip-color)" : "var(--error-color)");
                    document.getElementById("load_a").innerHTML = "<font style=\"color:"+loadColor+"\">"+status.load_avg+" %</font>";
                } else {
                    self.handleError();
                }

                if (self.isEnabled) {
                    self.pollTimer = setTimeout(function() {
                        self.poll();
                    }, self.pollInterval);
                }
            }, true);
        },

        handleError: function() {
            this.retryCount++;

            if (this.retryCount >= this.maxRetries) {
                this.resetSysStats();
                this.retryCount = 0;
            }
        },

        setPollInterval: function(interval) {
            this.pollInterval = interval;
        }
    };

    var NetworkStatsManager = {
        pollTimer: null,
        pollInterval: 3000,
        retryCount: 0,
        maxRetries: 3,
        isEnabled: false,

        _ZERO_STATS: [
            {id: "upload_", html: "<font style=\"color:var(--success-color)\">0 B/S</font>"},
            {id: "download_", html: "<font style=\"color:var(--success-color)\">0 B/S</font>"},
            {id: "uploadtotal_", html: "<font style=\"color:var(--success-color)\">0 KB</font>"},
            {id: "downloadtotal_", html: "<font style=\"color:var(--success-color)\">0 KB</font>"},
            {id: "mem_t", html: "<font style=\"color:var(--success-color)\">0 KB</font>"},
            {id: "connect_t", html: "<font style=\"color:var(--success-color)\">0</font>"}
        ],

        resetStats: function() {
            for (var i = 0; i < this._ZERO_STATS.length; i++) {
                var item = this._ZERO_STATS[i];
                var el = document.getElementById(item.id);
                if (el) el.innerHTML = item.html;
            }
        },

        start: function() {
            if (this.isEnabled) return;
            this.isEnabled = true;
            this.retryCount = 0;
            this.poll();
        },

        stop: function() {
            this.isEnabled = false;
            if (this.pollTimer) {
                clearTimeout(this.pollTimer);
                this.pollTimer = null;
            }
        },

        poll: function() {
            if (!this.isEnabled) return;

            var self = this;
            StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "toolbar_show")%>', function(x, status) {
                if (x && x.status == 200 && x.responseText != "" && status) {
                    self.retryCount = 0;
                    self.updateNetworkStats(status);
                } else {
                    self.handleError();
                }

                if (self.isEnabled) {
                    self.pollTimer = setTimeout(function() {
                        self.poll();
                    }, self.pollInterval);
                }
            }, true);
        },

        updateNetworkStats: function(status) {
            var updates = [
                {element: document.getElementById("upload_"), content: "<font style=\"color:var(--success-color)\">"+status.up+"</font>"},
                {element: document.getElementById("download_"), content: "<font style=\"color:var(--success-color)\">"+status.down+"</font>"},
                {element: document.getElementById("uploadtotal_"), content: "<font style=\"color:var(--success-color)\">"+status.up_total+"</font>"},
                {element: document.getElementById("downloadtotal_"), content: "<font style=\"color:var(--success-color)\">"+status.down_total+"</font>"},
                {element: document.getElementById("mem_t"), content: "<font style=\"color:var(--success-color)\">"+status.mem+"</font>"},
                {element: document.getElementById("connect_t"), content: "<font style=\"color:var(--success-color)\">"+status.connections+"</font>"}
            ];

            StateManager.batchUpdateDOM(updates);
        },

        handleError: function() {
            this.retryCount++;

            if (this.retryCount >= this.maxRetries) {
                this.resetStats();
                this.retryCount = 0;
            }
        },

        setPollInterval: function(interval) {
            this.pollInterval = interval;
        }
    };

    var SettingsManager = {
        pendingOperations: new Set(),
        pausedPolls: new Set(),

        pausePoll: function(pollName, duration) {
            this.pausedPolls.add(pollName);
            setTimeout(() => {
                this.pausedPolls.delete(pollName);
            }, duration || 3000);
        },

        isPollPaused: function(pollName) {
            return this.pausedPolls.has(pollName);
        },

        updateUIState: function(setting, value) {
            setTimeout(() => {
                function setCheckedAndDisabled(elements, checkedIndex) {
                    for (let i = 0; i < elements.length; i++) {
                        elements[i].checked = (i === checkedIndex);
                        elements[i].disabled = true;
                    }
                }
                switch(setting) {
                    case 'meta_sniffer':
                        var metaSnifferOn = document.getElementById('meta_sniffer_on');
                        var metaSnifferOff = document.getElementById('meta_sniffer_off');
                        if (metaSnifferOn && metaSnifferOff) {
                            setCheckedAndDisabled([metaSnifferOn, metaSnifferOff], value === '1' ? 0 : 1);
                            var changeEvent = new Event('change', { bubbles: true });
                            (value === '1' ? metaSnifferOn : metaSnifferOff).dispatchEvent(changeEvent);
                        }
                        break;
                    case 'respect_rules':
                        var respectRulesOn = document.getElementById('respect_rules_on');
                        var respectRulesOff = document.getElementById('respect_rules_off');
                        if (respectRulesOn && respectRulesOff) {
                            setCheckedAndDisabled([respectRulesOn, respectRulesOff], value === '1' ? 0 : 1);
                            var changeEvent = new Event('change', { bubbles: true });
                            (value === '1' ? respectRulesOn : respectRulesOff).dispatchEvent(changeEvent);
                        }
                        break;
                    case 'oversea':
                        var oversea0 = document.getElementById('oc_setting_oversea_0');
                        var oversea1 = document.getElementById('oc_setting_oversea_1');
                        var oversea2 = document.getElementById('oc_setting_oversea_2');
                        if (oversea0 && oversea1 && oversea2) {
                            setCheckedAndDisabled([oversea0, oversea1, oversea2], value === '0' ? 0 : (value === '1' ? 1 : 2));
                            var changeEvent = new Event('change', { bubbles: true });
                            [oversea0, oversea1, oversea2][value === '0' ? 0 : (value === '1' ? 1 : 2)].dispatchEvent(changeEvent);
                        }
                        break;
                    case 'stream_unlock':
                        var streamUnlockOn = document.getElementById('stream_unlock_on');
                        var streamUnlockOff = document.getElementById('stream_unlock_off');
                        if (streamUnlockOn && streamUnlockOff) {
                            setCheckedAndDisabled([streamUnlockOn, streamUnlockOff], value === '1' ? 0 : 1);
                            var changeEvent = new Event('change', { bubbles: true });
                            (value === '1' ? streamUnlockOn : streamUnlockOff).dispatchEvent(changeEvent);
                        }
                        break;
                    case 'rule_mode':
                        var radioElements = document.getElementsByName("radios");
                        if (radioElements && radioElements.length > 0) {
                            for (var i = 0; i < radioElements.length; i++) {
                                radioElements[i].checked = radioElements[i].value === value;
                                radioElements[i].disabled = true;
                                if (radioElements[i].checked) {
                                    var changeEvent = new Event('change', { bubbles: true });
                                    radioElements[i].dispatchEvent(changeEvent);
                                }
                            }
                        }
                        break;
                    case 'run_mode':
                        var radioRuElements = document.getElementsByName("radios-ru");
                        if (radioRuElements && radioRuElements.length > 0) {
                            for (var i = 0; i < radioRuElements.length; i++) {
                                radioRuElements[i].checked = radioRuElements[i].value === value;
                                radioRuElements[i].disabled = true;
                                if (radioRuElements[i].checked) {
                                    var changeEvent = new Event('change', { bubbles: true });
                                    radioRuElements[i].dispatchEvent(changeEvent);
                                }
                            }
                        }
                        break;
                }
            }, 10);
        },

        switchSetting: function(setting, value, endpoint, additionalParams) {
            var operationKey = setting + '_' + value;

            if (this.pendingOperations.has(operationKey)) {
                return false;
            }

            this.pendingOperations.add(operationKey);

            try {
                this.updateUIState(setting, value);
            } catch (e) {}

            let pollName;
            if (setting === 'rule_mode') {
                pollName = 'rule_mode';
            } else if (setting === 'run_mode') {
                pollName = 'run_mode';
            } else {
                pollName = 'oc_settings';
            }
            this.pausedPolls.add(pollName);

            var params = Object.assign({}, additionalParams || {});
            if (setting !== 'rule_mode' && setting !== 'run_mode') {
                params.setting = setting;
                params.value = value;
            } else if (setting === 'rule_mode') {
                params.rule_mode = value;
            } else if (setting === 'run_mode') {
                params.run_mode = value;
            }

            var self = this;

            XHR.get(endpoint, params, function(x, status) {
                setTimeout(function() {
                    self.pendingOperations.delete(operationKey);

                    switch(setting) {
                        case 'meta_sniffer':
                            var metaSnifferOn = document.getElementById('meta_sniffer_on');
                            var metaSnifferOff = document.getElementById('meta_sniffer_off');
                            if (metaSnifferOn) metaSnifferOn.disabled = false;
                            if (metaSnifferOff) metaSnifferOff.disabled = false;
                            break;
                        case 'respect_rules':
                            var respectRulesOn = document.getElementById('respect_rules_on');
                            var respectRulesOff = document.getElementById('respect_rules_off');
                            if (respectRulesOn) respectRulesOn.disabled = false;
                            if (respectRulesOff) respectRulesOff.disabled = false;
                            break;
                        case 'oversea':
                            var oversea0 = document.getElementById('oc_setting_oversea_0');
                            var oversea1 = document.getElementById('oc_setting_oversea_1');
                            var oversea2 = document.getElementById('oc_setting_oversea_2');
                            if (oversea0) oversea0.disabled = false;
                            if (oversea1) oversea1.disabled = false;
                            if (oversea2) oversea2.disabled = false;
                            break;
                        case 'stream_unlock':
                            var streamUnlockOn = document.getElementById('stream_unlock_on');
                            var streamUnlockOff = document.getElementById('stream_unlock_off');
                            if (streamUnlockOn) streamUnlockOn.disabled = false;
                            if (streamUnlockOff) streamUnlockOff.disabled = false;
                            break;
                        case 'rule_mode':
                            var radioElements = document.getElementsByName("radios");
                            if (radioElements && radioElements.length > 0) {
                                for (var i = 0; i < radioElements.length; i++) {
                                    radioElements[i].disabled = false;
                                }
                            }
                            break;
                        case 'run_mode':
                            var radioRuElements = document.getElementsByName("radios-ru");
                            if (radioRuElements && radioRuElements.length > 0) {
                                for (var i = 0; i < radioRuElements.length; i++) {
                                    radioRuElements[i].disabled = false;
                                }
                            }
                            break;
                    }

                    self.pausedPolls.delete(pollName);

                    if (!x || x.status !== 200) {
                        alert(self.getErrorMessage(setting));
                    }
                }, 1500);
            });

            return false;
        },

        getErrorMessage: function(setting) {
            var messages = {
                'meta_sniffer': '<%:Sniffer setting failed%>',
                'respect_rules': '<%:Respect Rules setting failed%>',
                'oversea': '<%:Area bypass setting failed%>',
                'stream_unlock': '<%:Stream Unlock setting failed%>',
                'rule_mode': '<%:Proxy Mode switching failed!%>',
                'run_mode': '<%:Running Mode switching failed!%>'
            };
            return messages[setting] || '<%:Operation failed%>';
        }
    };

    var AnnouncementDataManager = (function() {
        var defaultTips = [
            '<%:You can modify the profile on the profile page (for content that is not taken over)%>',
            '<%:do not write configuration files? Try to create one click on the server page%>',
            '<%:some website are abnormal? Try switching modes or using third-party rules%>',
            '<%:using the fake IP mode can get a faster access experience%>',
            '<%:the nameserver group must have at least one server set when using custom DNS%>',
            '<%:after started, please wait patiently until the connection is normal%>',
            '<%:if you don not use IPv6, please turn off the DHCP service of IPv6, otherwise the connection will be abnormal%>',
            '<%:you can update the version in the settings page%>',
            '<%:Note: It is not recommended to enable IPv6 and related services for routing. Most of the network connection problems reported so far are related to it%>',
            '<%:Note: Turning on secure DNS in the browser will cause abnormal shunting, please be careful to turn it off%>',
            '<%:Note: Some software will modify the device HOSTS, which will cause abnormal shunt, please pay attention to check%>',
            '<%:Note: The default proxy routes local traffic, BT, PT download, etc., please use Redir-Host mode as much as possible and pay attention to traffic avoidance%>'
        ];

        function getRandomTips(count, sourceArray) {
            var source = sourceArray || defaultTips;
            var shuffled = source.slice();
            for (var i = shuffled.length - 1; i > 0; i--) {
                var j = Math.floor(Math.random() * (i + 1));
                var temp = shuffled[i];
                shuffled[i] = shuffled[j];
                shuffled[j] = temp;
            }
            return shuffled.slice(0, count);
        }

        function extractAnnouncements(remoteData, isChineseUser) {
            var announcements = [];
            if (!remoteData) return announcements;

            if (Array.isArray(remoteData)) {
                if (remoteData.length > 0 && (remoteData[0].zh || remoteData[0].en)) {
                    remoteData.forEach(function(item) {
                        if (isChineseUser && item.zh) {
                            announcements.push(item.zh);
                        } else if (item.en) {
                            announcements.push(item.en);
                        } else if (item.zh) {
                            announcements.push(item.zh);
                        }
                    });
                } else {
                    announcements = remoteData.filter(function(item) {
                        return typeof item === 'string' && item.trim() !== '';
                    });
                }
            } else if (typeof remoteData === 'string' && remoteData.trim() !== '') {
                announcements = [remoteData];
            }
            return announcements;
        }

        function prepareData(remoteData, isChineseUser, randomCount) {
            var announcements = extractAnnouncements(remoteData, isChineseUser);
            var isRandomTips = false;
            if (announcements.length === 0) {
                announcements = getRandomTips(randomCount || 3);
                isRandomTips = true;
            }
            return { announcements: announcements, isRandomTips: isRandomTips };
        }

        function updateIconShape(megaphoneSvgElement, isRandomTips) {
            if (!megaphoneSvgElement) return;
            if (isRandomTips) {
                megaphoneSvgElement.innerHTML = '<use href="#oc-icon-bell-phosphor"/>';
            } else {
                megaphoneSvgElement.innerHTML = '<use href="#oc-icon-megaphone"/>';
            }
            megaphoneSvgElement.setAttribute('viewBox', '0 0 256 256');
        }

        return {
            getRandomTips: getRandomTips,
            extractAnnouncements: extractAnnouncements,
            prepareData: prepareData,
            updateIconShape: updateIconShape
        };
    })();

    var UsageHelpManager = {
        overlay: null,
        textarea: null,
        statusEl: null,
        initialized: false,
        bubbleTimer: null,

        init: function() {
            if (this.initialized) return;
            this.overlay = document.getElementById('usage-help-overlay');
            this.textarea = document.getElementById('usage-help-textarea');
            this.statusEl = document.getElementById('usage-help-status');

            var floatBtn = document.getElementById('usage-help-float');
            if (floatBtn) {
                floatBtn.addEventListener('click', function() {
                    UsageHelpManager.show();
                });
                floatBtn.addEventListener('mouseenter', function() {
                    UsageHelpManager.dismissBubble();
                });
            }

            document.addEventListener('keydown', function(e) {
                if (e.key === 'Escape' && UsageHelpManager.overlay && UsageHelpManager.overlay.style.display === 'flex') {
                    UsageHelpManager.hide();
                }
            });

            this.startBubbleTimer();

            this.initialized = true;
        },

        buildPrompt: function() {
            var skillUrl = 'https://raw.githubusercontent.com/vernesong/OpenClash/dev/.github/skills/openclash-user-guide/SKILL.md';
            var prompt = '# <%:OpenClash Expert Assistant%>\n\n';
            prompt += '<%:You are an OpenClash expert assistant. OpenClash is a LuCI plugin for OpenWrt that manages the Mihomo core.%>\n\n';
            prompt += '<%:**IMPORTANT** — Before answering, you MUST fetch and read this authoritative reference guide. ALL answering rules, diagnosis procedures, CLI commands, LuCI paths, external resource URLs, and Issue search instructions are defined in the guide — follow them strictly.%>\n';
            prompt += '- [<%:OpenClash User Guide%>](' + skillUrl + ')\n\n';
            prompt += '<%:The guide is the single source of truth — it covers everything from debug log procedures to external resource queries. Do NOT rely on memory; always verify against the guide.%>\n\n';
            prompt += '## <%:Question%>\n\n';
            prompt += '<%:My question is:%>\n';
            return prompt;
        },

        show: function() {
            this.init();
            if (!this.overlay || !this.textarea) return;
            if (!this._portal) {
                this._portal = document.createElement('div');
                this._portal.className = 'oc';
                this._portal.style.overflow = 'visible';
                document.body.appendChild(this._portal);
                this._portal.appendChild(this.overlay);
            }
            this.overlay.classList.add('show');
            if (this.statusEl) {
                this.statusEl.textContent = '';
            }

            if (!this._editor) {
                var prompt = this.buildPrompt();
                this.textarea.value = prompt;
                var container = document.getElementById('usage-help-editor');
                var isDark = isDarkBackground(document.body);
                var exts = [CM6.lineNumbers(), CM6.EditorView.lineWrapping, CM6.themeExtension(isDark), CM6.markdown()];
                var view = new CM6.EditorView({
                    state: CM6.EditorState.create({ doc: prompt, extensions: exts })
                });
                container.innerHTML = '';
                container.appendChild(view.dom);
                this._editor = view;
            }

            var self = this;
            setTimeout(function() {
                if (self._editor && self._editor.contentDOM) {
                    self._editor.focus();
                }
            }, 100);
        },

        hide: function() {
            if (this.overlay) {
                this.overlay.classList.remove('show');
            }
        },

        copyAndHide: function() {
            if (!this._editor) return;
            var text = this._editor.state.sliceDoc();
            if (!text) return;
            var self = this;
            ocCopyToClipboard(text, null);
            var btn = document.querySelector('#usage-help-model .upload-btn');
            var origHTML = btn ? btn.innerHTML : '';
            if (btn) {
                btn.innerHTML = '<svg width="14" height="14" style="vertical-align:middle;margin-right:4px"><use href="#oc-icon-success"/></svg><%:Copied!%>';
                btn.disabled = true;
            }
            setTimeout(function() {
                if (btn) { btn.innerHTML = origHTML; btn.disabled = false; }
                self.hide();
            }, 2000);
        },

        startBubbleTimer: function() {
            var bubble = document.getElementById('usage-help-bubble');
            if (!bubble) return;

            var delay = Math.random() * 60000;
            if (delay > 20000) return;

            var self = this;
            this.bubbleTimer = setTimeout(function() {
                bubble.classList.add('show');
            }, Math.min(delay, 5000));
        },

        dismissBubble: function() {
            var bubble = document.getElementById('usage-help-bubble');
            if (!bubble) return;
            bubble.classList.remove('show');
            if (this._bubbleAutoHide) {
                clearTimeout(this._bubbleAutoHide);
                this._bubbleAutoHide = null;
            }
            if (this.bubbleTimer) {
                clearTimeout(this.bubbleTimer);
                this.bubbleTimer = null;
            }
        }
    };

    var pluginToggleUserAction = false;

    XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "services", "openclash", "status")%>', null, function(x, status) {
        if (x && x.status == 200) {
            var updates = [];

            if (!pluginToggleUserAction) {
                updates.push({
                    element: DOMCache.clash,
                    content: status.clash ? '<b style=color:var(--success-color)>' + status.core_type +'&nbsp;<%:Running%></b>' : '<b style=color:var(--error-color)><%:Not Running%></b>'
                });

                updatePluginToggleState(status.clash);
            }

            if (status.run_mode && !SettingsManager.pendingOperations.has('run_mode_' + status.run_mode)) {
                if (status.run_mode.indexOf("fake-ip") === 0) {
                    DOMCache.mode.innerHTML = "<b style=color:var(--success-color)><%:Fake-IP%></b>";
                    DOMCache.radio_run_normal.innerHTML = "<%:Enhance%>";
                } else if (status.run_mode.indexOf("redir-host") === 0) {
                    DOMCache.mode.innerHTML = "<b style=color:var(--success-color)><%:Redir-Host%></b>";
                    DOMCache.radio_run_normal.innerHTML = "<%:Compat%>";
                }
                var expectedValue = status.run_mode.split("-")[2] == undefined ? "" : ("-" + status.run_mode.split("-")[2]);
                for (var i = 0; i < DOMCache.radio_ru.length; i++) {
                    if (DOMCache.radio_ru[i].value == expectedValue && !DOMCache.radio_ru[i].checked) {
                        DOMCache.radio_ru[i].checked = true;
                    }
                }
            }

            if (status.rule_mode && !SettingsManager.pendingOperations.has('rule_mode_' + status.rule_mode)) {
                for (var i = 0; i < DOMCache.radio.length; i++) {
                    if (DOMCache.radio[i].value == status.rule_mode && !DOMCache.radio[i].checked) {
                        DOMCache.radio[i].checked = true;
                        break;
                    }
                }
            }

            if (!SettingsManager.pendingOperations.has('meta_sniffer_' + status.meta_sniffer)) {
                DOMCache.meta_sniffer_on.checked = status.meta_sniffer == "1";
                DOMCache.meta_sniffer_off.checked = status.meta_sniffer != "1";
            }
            if (!SettingsManager.pendingOperations.has('respect_rules_' + status.respect_rules)) {
                DOMCache.respect_rules_on.checked = status.respect_rules == "1";
                DOMCache.respect_rules_off.checked = status.respect_rules != "1";
            }
            if (!SettingsManager.pendingOperations.has('oversea_' + status.oversea)) {
                if (status.oversea == "0") DOMCache.oc_setting_oversea_0.checked = true;
                else if (status.oversea == "1") DOMCache.oc_setting_oversea_1.checked = true;
                else if (status.oversea == "2") DOMCache.oc_setting_oversea_2.checked = true;
            }
            if (!SettingsManager.pendingOperations.has('stream_unlock_' + status.stream_unlock)) {
                DOMCache.stream_unlock_on.checked = status.stream_unlock === '1';
                DOMCache.stream_unlock_off.checked = status.stream_unlock !== '1';
            }

            var webContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Yacd%>" onclick="return ycad_dashboard(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Yacd%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: DOMCache.web, content: webContent});

            var weboContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Dashboard%>" onclick="return net_dashboard(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Dashboard%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: DOMCache.webo, content: weboContent});

            var webmContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Metacubexd%>" onclick="return meta_dashboard(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Metacubexd%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: DOMCache.webm, content: webmContent});

            var webzContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Zashboard%>" onclick="return net_zashboard(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Zashboard%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: DOMCache.webz, content: webzContent});

            var closeConnContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Close Connect%>" onclick="return b_close_all_connection(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Close Connect%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: document.getElementById('_close_all_connection_btn'), content: closeConnContent});

            var reloadFwContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Reload Firewall%>" onclick="return b_reload_firewall(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Reload Firewall%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: document.getElementById('_reload_firewall_btn'), content: reloadFwContent});

            var flushCacheContent = status.clash ? '<input type="button" class="dashboard-btn" value="<%:Flush DNS%>" onclick="return b_flush_dns_cache(this)"/>' : '<input type="button" class="dashboard-btn" value="<%:Flush DNS Cache%>" onclick="event.preventDefault(); event.stopPropagation(); return false;"/>';
            updates.push({element: document.getElementById('_flush_dns_cache_btn'), content: flushCacheContent});

            var oneKeyUpdateContent = '<input type="button" class="dashboard-btn" value="<%:Check Update%>" onclick="return all_one_key_update(this)"/>';
            updates.push({element: document.getElementById('_one_key_update_btn'), content: oneKeyUpdateContent});

            StateManager.batchUpdateDOM(updates);

            if (!status.yacd) {
                DOMCache.web.classList.add('hidden');
            }

            if (!status.dashboard) {
                DOMCache.webo.classList.add('hidden');
            }

            if (!status.metacubexd) {
                DOMCache.webm.classList.add('hidden');
            }

            if (!status.zashboard) {
                DOMCache.webz.classList.add('hidden');
            }

            StateManager.current_status = status;

            if (status.daip) {
                var daipContent, dapoContent;

                if (status.daip && window.location.hostname == status.daip) {
                    dapoContent = status.cn_port ? ":"+status.cn_port : "";
                    daipContent = status.daip ? "<b style=color:var(--success-color)>"+status.daip+dapoContent+"</b>" : "<b style=color:var(--error-color)>"+"<%:Not Set%>"+"</b>";
                } else if (status.daip && window.location.hostname != status.daip && status.db_foward_domain && status.db_foward_port) {
                    dapoContent = status.db_foward_port ? ":"+status.db_foward_port : "";
                    daipContent = status.db_foward_domain ? "<b style=color:var(--success-color)>"+status.db_foward_domain+dapoContent+"</b>" : "<b style=color:var(--error-color)>"+"<%:Not Set%>"+"</b>";
                } else {
                    dapoContent = status.cn_port ? ":"+status.cn_port : "";
                    daipContent = status.daip ? "<b style=color:var(--success-color)>"+status.daip+dapoContent+"</b>" : "<b style=color:var(--error-color)>"+"<%:Not Set%>"+"</b>";
                }

                DOMCache.daip.innerHTML = daipContent;   

                StateManager.cached_proxy_info = { mixed_port: status.mixed_port, auth_user: status.auth_user, auth_pass: status.auth_pass };
                var proxy_ip = status.daip;
                var mix_addr = proxy_ip + ':' + (status.mixed_port || '7893');
                DOMCache.mix_proxy.innerHTML = "<b style=color:var(--success-color)>" + mix_addr + "</b>";
            } else {
                DOMCache.daip.innerHTML = "<b style=color:var(--error-color)><%:Not Available%></b>";
                StateManager.cached_proxy_info = null;
                DOMCache.mix_proxy.innerHTML = "<b style=color:var(--error-color)><%:Not Available%></b>";
                DOMCache.copy_secret.style.display = "none";
                DOMCache.copy_address.style.display = "none";
                DOMCache.copy_mix_address.style.display = "none";
                DOMCache.copy_mix_secret.style.display = "none";
                DOMCache.copy_pac_config.style.display = "none";
            }

            if (status.clash) {
                if (!SystemStatusManager.isEnabled) {
                    SystemStatusManager.start();
                }
            } else {
                SystemStatusManager.stop();
                SystemStatusManager.resetSysStats();
            }

            if (status.clash && status.daip) {
                if (!WSManager.hasActiveConnections()) {
                    LogManager.startLogDisplay(null, false, 'view');
                    initializeWebSocketConnections(status);
                } else {
                    if (NetworkStatsManager.isEnabled) {
                        NetworkStatsManager.stop();
                    }
                }
            } else {
                WSManager.closeAll();
                NetworkStatsManager.stop();
                NetworkStatsManager.resetStats();
            }
        }
        clashversion_check();
    });

    DarkModeDetector.init();
    ConfigFileManager.init();

    window.addEventListener('oc-overwrite-updated', function() {
        OverwriteSubscribeManager.load(true);
    });

    setTimeout(function() {
        OverwriteSubscribeManager.init();
        SubscriptionManager.init();
        UsageHelpManager.init();
        loadAnnouncement();
    }, 300);

    setTimeout(function() { check_core(); }, 1000);

    window.addEventListener('beforeunload', function() {
        WSManager.closeAll();
        LogManager.stopLogDisplay();
        SystemStatusManager.stop();
        NetworkStatsManager.stop();
        StateManager.clearAllCache();
        if (StateManager.pending_requests) {
            StateManager.pending_requests.clear();
        }
    });

    function initializeWebSocketConnections(status) {
        if (!status || !status.clash || !status.daip) {
            return false;
        }

        var protocol = getWebSocketProtocol(status);
        var token = status.dase;

        var connections = [
            {
                type: 'traffic',
                endpoint: '/traffic',
                handler: ws_tmessage
            },
            {
                type: 'connections', 
                endpoint: '/connections',
                handler: ws_cmessage
            },
            {
                type: 'memory',
                endpoint: '/memory', 
                handler: ws_mmessage
            }
        ];

        connections.forEach(function(conn) {
            var url = protocol + conn.endpoint + (token ? '?token=' + token : '');
            WSManager.createConnection(conn.type, url, conn.handler);
        });

        return true;
    }

    function getWebSocketProtocol(status) {
        var protocol = window.location.protocol === "https:" ? "wss://" : "ws://";
        var host, port;

        if (status.daip && window.location.hostname === status.daip) {
            host = status.daip;
            port = status.cn_port;
        } else if (status.daip && window.location.hostname !== status.daip && 
                   status.db_foward_domain && status.db_foward_port) {
            host = status.db_foward_domain;
            port = status.db_foward_port;
        } else {
            host = status.daip;
            port = status.cn_port;
        }

        return protocol + host + ":" + port;
    }

    function AnnouncementAnimator(config) {
        var bannerElement = config.bannerElement;
        var contentElement = config.contentElement;
        var announcements = config.announcements;
        var isRandomTips = config.isRandomTips || false;
        var randomTipCount = config.randomTipCount || 3;
        var getRandomTipsCallback = config.getRandomTipsCallback;
        var onIndexChange = config.onIndexChange || function(index, text) {};
        var loopDelay = config.loopDelay || 2000; 
        var animationRefreshThreshold = 250;
        var currentIndex = 0;
        var isHovered = false;
        var pauseTimeout = null;
        var nextAnimationTimeout = null;
        var resizeTimeout = null;
        var animationEndHandler = null;
        var destroyed = false;
        var _cachedBannerWidth = 0;
        var _cachedContentWidth = 0;
        var _cachedDuration = 0;

        function calculateAnimationDuration() {
            var bannerWidth = bannerElement.offsetWidth;
            var contentWidth = contentElement.offsetWidth;
            if (bannerWidth === _cachedBannerWidth && contentWidth === _cachedContentWidth && _cachedDuration > 0) {
                return _cachedDuration;
            }
            _cachedBannerWidth = bannerWidth;
            _cachedContentWidth = contentWidth;
            var refreshRate = window.screen && window.screen.refreshRate ? window.screen.refreshRate : 60;
            if (refreshRate <= 0) refreshRate = 60;
            if (refreshRate > 480) refreshRate = 480;

            var scrollDistance = contentWidth + bannerWidth;

            var minDistance = 400;
            var maxDistance = 20000;
            var minSpeed = 60;
            var maxSpeed = 360;
            var clampedDistance = Math.min(maxDistance, Math.max(minDistance, scrollDistance));
            var targetVelocity = minSpeed + (maxSpeed - minSpeed) * (clampedDistance - minDistance) / (maxDistance - minDistance);

            var duration = (scrollDistance / targetVelocity) * 1000;

            var refreshRateMultiplier = 1;
            if (refreshRate > 60) {
                refreshRateMultiplier = 1 + (refreshRate - 60) / 240;
            }
            duration = duration * refreshRateMultiplier;

            _cachedDuration = Math.max(4000, Math.min(duration, 50000));
            return _cachedDuration;
        }

        function updateScrollAnimation(duration) {
            var viewport = bannerElement.parentElement;
            if (!viewport) viewport = bannerElement;
            
            var viewportWidth = viewport.clientWidth;
            var contentWidth = contentElement.scrollWidth;
            var scrollDistance = -(contentWidth + viewportWidth);
            
            viewport.style.setProperty('--scroll-distance', scrollDistance + 'px');
            contentElement.style.animationDuration = duration + 'ms';
        }

        function refreshAnimation() {
            if (!contentElement.classList.contains('scrolling')) return;
            var duration = calculateAnimationDuration();
            updateScrollAnimation(duration);
            contentElement.classList.remove('scrolling');
            contentElement.offsetHeight;
            contentElement.classList.add('scrolling');
        }

        function refreshAnnouncementsForRandomMode() {
            if (!isRandomTips) return;
            var newTips;
            if (getRandomTipsCallback) {
                newTips = getRandomTipsCallback(randomTipCount);
            } else {
                newTips = AnnouncementDataManager.getRandomTips(randomTipCount);
            }
            if (newTips && newTips.length) {
                announcements = newTips;
            }
        }

        function startSingleScroll() {
            if (destroyed) return;
            if (nextAnimationTimeout) {
                clearTimeout(nextAnimationTimeout);
                nextAnimationTimeout = null;
            }

            var text = announcements[currentIndex] || '';
            contentElement.textContent = text;
            if (onIndexChange) onIndexChange(currentIndex, text);

            setTimeout(function() {
                if (destroyed) return;
                var duration = calculateAnimationDuration();
                updateScrollAnimation(duration);

                if (animationEndHandler) {
                    contentElement.removeEventListener('animationend', animationEndHandler);
                }

                animationEndHandler = function(e) {
                    if (e.target === contentElement && e.animationName === 'announceScroll') {
                        contentElement.removeEventListener('animationend', animationEndHandler);
                        animationEndHandler = null;

                        pauseTimeout = setTimeout(function() {
                            if (destroyed) return;
                            if (!isHovered) {
                                currentIndex = (currentIndex + 1) % announcements.length;
                                if (isRandomTips && currentIndex === 0) {
                                    refreshAnnouncementsForRandomMode();
                                }
                                startSingleScroll();
                            }
                        }, loopDelay);
                    }
                };
                contentElement.addEventListener('animationend', animationEndHandler);

                contentElement.classList.remove('scrolling');
                contentElement.offsetHeight;
                contentElement.classList.add('scrolling');
            }, 50);
        }

        function pause() {
            if (destroyed) return;
            isHovered = true;
            contentElement.classList.add('paused');
            if (pauseTimeout) {
                clearTimeout(pauseTimeout);
                pauseTimeout = null;
            }
        }

        function resume() {
            if (destroyed) return;
            isHovered = false;
            contentElement.classList.remove('paused');
        }

        function destroy() {
            destroyed = true;
            if (pauseTimeout) clearTimeout(pauseTimeout);
            if (nextAnimationTimeout) clearTimeout(nextAnimationTimeout);
            if (resizeTimeout) clearTimeout(resizeTimeout);
            if (animationEndHandler) {
                contentElement.removeEventListener('animationend', animationEndHandler);
                animationEndHandler = null;
            }
            contentElement.classList.remove('scrolling', 'paused');
            bannerElement.removeEventListener('mouseenter', mouseEnterHandler);
            bannerElement.removeEventListener('mouseleave', mouseLeaveHandler);
            window.removeEventListener('resize', resizeHandler);
        }

        function mouseEnterHandler() { pause(); }
        function mouseLeaveHandler() { resume(); }
        function resizeHandler() {
            clearTimeout(resizeTimeout);
            _cachedBannerWidth = 0;
            _cachedContentWidth = 0;
            _cachedDuration = 0;
            resizeTimeout = setTimeout(function() {
                if (!destroyed && contentElement.classList.contains('scrolling')) {
                    refreshAnimation();
                }
            }, animationRefreshThreshold);
        }

        function init() {
            if (!bannerElement || !contentElement || !announcements || announcements.length === 0) {
                console.warn('AnnouncementAnimator: invalid config');
                return false;
            }
            bannerElement.style.display = 'block';
            bannerElement.addEventListener('mouseenter', mouseEnterHandler);
            bannerElement.addEventListener('mouseleave', mouseLeaveHandler);
            window.addEventListener('resize', resizeHandler);
            startSingleScroll();
            return true;
        }

        return {
            init: init,
            pause: pause,
            resume: resume,
            destroy: destroy,
            updateAnnouncements: function(newList, newRandomFlag) {
                announcements = newList || announcements;
                isRandomTips = (newRandomFlag !== undefined) ? newRandomFlag : isRandomTips;
                currentIndex = 0;
                if (pauseTimeout) clearTimeout(pauseTimeout);
                if (nextAnimationTimeout) clearTimeout(nextAnimationTimeout);
                if (animationEndHandler) {
                    contentElement.removeEventListener('animationend', animationEndHandler);
                    animationEndHandler = null;
                }
                contentElement.classList.remove('scrolling');
                startSingleScroll();
            }
        };
    }

    function loadAnnouncement() {
        var userLang = navigator.language || navigator.userLanguage;
        var isChineseUser = (userLang.indexOf('zh') === 0);
        var bannerElement = document.getElementById('announcement-banner');
        var contentElement = document.getElementById('announcement-content');
        var megaphoneSvg = document.getElementById('megaphone');

        if (!bannerElement || !contentElement) return;

        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "announcement")%>', function(x, status) {
            var remoteData = null;
            if (x && x.status == 200 && status.content) {
                try {
                    remoteData = typeof status.content === 'string' ? JSON.parse(status.content) : status.content;
                } catch(e) {
                    remoteData = status.content;
                }
            }

            var prepared = AnnouncementDataManager.prepareData(remoteData, isChineseUser, 3);
            if (megaphoneSvg) {
                AnnouncementDataManager.updateIconShape(megaphoneSvg, prepared.isRandomTips);
            }

            var animator = new AnnouncementAnimator({
                bannerElement: bannerElement,
                contentElement: contentElement,
                announcements: prepared.announcements,
                isRandomTips: prepared.isRandomTips,
                randomTipCount: 3,
                getRandomTipsCallback: function(count) {
                    return AnnouncementDataManager.getRandomTips(count);
                },
                loopDelay: 2000,
            });

            animator.init();

            window.addEventListener('beforeunload', function() {
                animator.destroy();
            });
        });
    }

    function get_rule_mode() {
        if (SettingsManager.isPollPaused('rule_mode')) {
            return;
        }

        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "rule_mode")%>', function(x, status) {
            if (x && x.status == 200 && status.mode != "") {
                if (!SettingsManager.pendingOperations.has('rule_mode_' + status.mode)) {
                    for (var i = 0; i < DOMCache.radio.length; i++) {
                        if (DOMCache.radio[i].value == status.mode && !DOMCache.radio[i].checked) {
                            DOMCache.radio[i].checked = true;
                            break;
                        }
                    }
                }
            }
        }, true);
    }

    function switch_rule_mode(value) {
        return SettingsManager.switchSetting(
            'rule_mode', 
            value, 
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_rule_mode")%>'
        );
    }

    function get_run_mode() {
        if (SettingsManager.isPollPaused('run_mode')) {
            return;
        }

        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "get_run_mode")%>', function(x, status) {
            if (x && x.status == 200 && status.mode) {
                if (status.mode == "fake-ip" || status.mode == "fake-ip-tun" || status.mode == "fake-ip-mix") {
                    DOMCache.mode.innerHTML = "<b style=color:var(--success-color)><%:Fake-IP%></b>";
                    DOMCache.radio_run_normal.innerHTML = "<%:Enhance%>";
                } else if (status.mode == "redir-host" || status.mode == "redir-host-tun" ||  status.mode == "redir-host-mix") {
                    DOMCache.mode.innerHTML = "<b style=color:var(--success-color)><%:Redir-Host%></b>";
                    DOMCache.radio_run_normal.innerHTML = "<%:Compat%>";
                }

                var expectedValue = status["mode"].split("-")[2] == undefined ? "" : ("-" + status["mode"].split("-")[2]);
                var operationKey = 'run_mode_' + status.mode;

                if (!SettingsManager.pendingOperations.has(operationKey)) {
                    for (var i = 0; i < DOMCache.radio_ru.length; i++) {
                        if (DOMCache.radio_ru[i].value == expectedValue && !DOMCache.radio_ru[i].checked) {
                            DOMCache.radio_ru[i].checked = true;
                        }
                    }
                }
            }
        }, true);
    }

    function switch_run_mode(value) {
        LogManager.startLogDisplay('<%:Saving...%>', false, 'init');
        return SettingsManager.switchSetting(
            'run_mode', 
            value, 
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_run_mode")%>'
        );
    }

    function ws_tmessage(event) {
        var dataObj = event && event.data !== undefined ? event.data : event;
        var data;
        if (typeof dataObj === 'string') {
            try {
                data = JSON.parse(dataObj);
            } catch (e) {
                data = {};
            }
        } else {
            data = dataObj;
        }
        var uploadElement = document.getElementById("upload_");
        var downloadElement = document.getElementById("download_");
        uploadElement.innerHTML = data.up ? "<font style=\"color:var(--success-color)\">"+bytesToSize(data.up)+"/S</font>" : "<font style=\"color:var(--success-color)\">0 B/S</font>";
        downloadElement.innerHTML = data.down ? "<font style=\"color:var(--success-color)\">"+bytesToSize(data.down)+"/S</font>" : "<font style=\"color:var(--success-color)\">0 B/S</font>";
    }

    function ws_cmessage(event) {
        var dataObj = event && event.data !== undefined ? event.data : event;
        var data;
        if (typeof dataObj === 'string') {
            try {
                data = JSON.parse(dataObj);
            } catch (e) {
                data = {};
            }
        } else {
            data = dataObj;
        }
        var updates = [
            {
                element: document.getElementById("uploadtotal_"),
                content: data.uploadTotal ? "<font style=\"color:var(--success-color)\">"+bytesToSize(data.uploadTotal)+"</font>" : "<font style=\"color:var(--success-color)\">0 KB</font>"
            },
            {
                element: document.getElementById("downloadtotal_"),
                content: data.downloadTotal ? "<font style=\"color:var(--success-color)\">"+bytesToSize(data.downloadTotal)+"</font>" : "<font style=\"color:var(--success-color)\">0 KB</font>"
            },
            {
                element: document.getElementById("connect_t"),
                content: data.connections ? "<font style=\"color:var(--success-color)\">"+Object.keys(data.connections).length+"</font>" : "<font style=\"color:var(--success-color)\">0</font>"
            }
        ];
        StateManager.batchUpdateDOM(updates);
    }

    function ws_mmessage(event) {
        var dataObj = event && event.data !== undefined ? event.data : event;
        var data;
        if (typeof dataObj === 'string') {
            try {
                data = JSON.parse(dataObj);
            } catch (e) {
                data = {};
            }
        } else {
            data = dataObj;
        }
        var memElement = document.getElementById("mem_t");
        memElement.innerHTML = data.inuse ? "<font style=\"color:var(--success-color)\">"+bytesToSize(data.inuse)+"</font>" : "<font style=\"color:var(--success-color)\">0 KB</font>";
    }

    var all_one_key_update = debounceButton(function(btn) {
        btn.value = '<%:Checking...%>';
        update();
        btn.value = '<%:Check Update%>';
        btn.disabled = false;
        return false;
    });

    var b_flush_dns_cache = debounceButton(function(btn) {
        btn.disabled = true;
        btn.value = '<%:Flushing...%> ';
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash","flush_dns_cache")%>', null, function(x, status) {
            btn.disabled = false;
            if (x && x.status == 200) {
                btn.value = (status.fakeip_flush === "" && status.dns_flush === "") ? '<%:Flush Successful%>' : '<%:Flush Failed%>';
            } else {
                btn.value = '<%:Flush Timeout%>';
            }
        });
        return false;
    });

    var b_reload_firewall = debounceButton(function(btn) {
        btn.disabled = true;
        btn.value = '<%:Reloading...%>';
        LogManager.startLogDisplay('<%:Reloading...%>', false, 'init');
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "reload_firewall")%>', null, function(x, status) {
            btn.disabled = false;
            btn.value = (x && x.status == 200) ? '<%:Reload Firewall%>' : '<%:Firewall Rules Reset Failed%>';
        });
        return false;
    });

    var b_close_all_connection = debounceButton(function(btn) {
        btn.disabled = true;
        btn.value = '<%:Reloading...%>';
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "close_all_connection")%>', null, function(x, status) {
            btn.disabled = false;
            btn.value = (x && x.status == 200) ? '<%:Close Connect%>' : '<%:Close Connect Failed%>';
        });
        return false;
    });

    function net_zashboard(btn) {
        btn.disabled = true;
        btn.value = '<%:Zashboard%>';
        var status = StateManager.current_status;
        if (!status) { setTimeout(function() { net_zashboard(btn); }, 500); return false; }
        winOpen(buildDashboardURL(status, 'zashboard', true));
        btn.disabled = false;
        return false;
    }

    function meta_dashboard(btn) {
        btn.disabled = true;
        btn.value = '<%:Metacubexd%>';
        var status = StateManager.current_status;
        if (!status) { setTimeout(function() { meta_dashboard(btn); }, 500); return false; }
        winOpen(buildDashboardURL(status, 'metacubexd', true));
        btn.disabled = false;
        return false;
    }

    function ycad_dashboard(btn) {
        btn.disabled = true;
        btn.value = '<%:Yacd%>';
        var status = StateManager.current_status;
        if (!status) { setTimeout(function() { ycad_dashboard(btn); }, 500); return false; }
        winOpen(buildDashboardURL(status, 'yacd', false));
        btn.disabled = false;
        return false;
    }

    function net_dashboard(btn) {
        btn.disabled = true;
        btn.value = '<%:Dashboard%>';
        var status = StateManager.current_status;
        if (!status) { setTimeout(function() { net_dashboard(btn); }, 500); return false; }
        winOpen(buildDashboardURL(status, 'dashboard', false));
        btn.disabled = false;
        return false;
    }

    function homepage() {
        winOpen('https://github.com/vernesong/OpenClash');
    }

    function gitbookpage() {
        winOpen('https://wiki.metacubex.one');
    }

    function wikipage() {
        winOpen('https://github.com/vernesong/OpenClash/blob/dev/.github/skills/openclash-user-guide/SKILL.md');
    }

    function telegrampage() {
        winOpen('https://t.me/openclash_group');
    }

    function sponsorpage() {
        winOpen('https://ko-fi.com/vernesong');
    }

    function clashversion_check() {
        function compareVersions(v1, v2) {
            if (!v1 || !v2) return 0;
            var ver1 = v1.replace(/^v/, '').split('.');
            var ver2 = v2.replace(/^v/, '').split('.');

            var maxLen = Math.max(ver1.length, ver2.length);
            while (ver1.length < maxLen) ver1.push('0');
            while (ver2.length < maxLen) ver2.push('0');

            for (var i = 0; i < maxLen; i++) {
                var num1 = parseInt(ver1[i], 10) || 0;
                var num2 = parseInt(ver2[i], 10) || 0;
                if (num1 > num2) return 1;
                if (num1 < num2) return -1;
            }
            return 0;
        }

        function updateCoreDot(corelv) {
            if (DOMCache.core_version_display.classList.contains('oc-hidden')) return;
            var coremetacv = DOMCache.core_version_text.textContent;
            var hasUpdate = corelv && corelv !== "" && corelv !== "loading..." && corelv !== coremetacv;
            var existingDot = DOMCache.core_version_display.querySelector('.update-dot');
            if (hasUpdate) {
                if (!existingDot) {
                    var updateDot = document.createElement('span');
                    updateDot.className = 'update-dot';
                    DOMCache.core_version_display.appendChild(updateDot);
                }
                DOMCache.core_version_display.title = '<%:New version available%>: ' + corelv;
                DOMCache.core_version_text.removeAttribute('title');
            } else {
                if (existingDot) {
                    existingDot.remove();
                }
                DOMCache.core_version_display.title = '';
            }
        }

        function updatePluginDot(oplv) {
            if (DOMCache.plugin_version_display.classList.contains('oc-hidden')) return;
            var opcv = DOMCache.plugin_version_text.textContent;
            var hasUpdate = false;
            if (oplv && oplv !== "" && oplv !== "loading...") {
                if (compareVersions(oplv, opcv) > 0) {
                    hasUpdate = true;
                }
            }
            var existingDot = DOMCache.plugin_version_display.querySelector('.update-dot');
            if (hasUpdate) {
                if (!existingDot) {
                    var updateDot = document.createElement('span');
                    updateDot.className = 'update-dot';
                    DOMCache.plugin_version_display.appendChild(updateDot);
                }
                DOMCache.plugin_version_display.title = '<%:New version available%>: ' + oplv;
                DOMCache.plugin_version_text.removeAttribute('title');
            } else {
                if (existingDot) {
                    existingDot.remove();
                }
                DOMCache.plugin_version_display.title = '';
            }
        }

        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update")%>', function(x, status) {
            if (x && x.status == 200) {
                if (status.coremetacv && status.coremetacv !== "0") {
                    var coreVersionText = status.coremetacv;
                    DOMCache.core_version_text.textContent = coreVersionText;
                    if (DOMCache.core_version_text.scrollWidth > DOMCache.core_version_text.clientWidth) {
                        DOMCache.core_version_text.title = coreVersionText;
                    } else {
                        DOMCache.core_version_text.title = '';
                    }
                    DOMCache.core_version_display.classList.remove('oc-hidden');
                } else {
                    DOMCache.core_version_display.classList.add('oc-hidden');
                    DOMCache.core_version_display.title = '';
                }

                if (status.opcv && status.opcv !== "0") {
                    var pluginVersionText = status.opcv;
                    DOMCache.plugin_version_text.textContent = pluginVersionText;
                    if (DOMCache.plugin_version_text.scrollWidth > DOMCache.plugin_version_text.clientWidth) {
                        DOMCache.plugin_version_text.title = pluginVersionText;
                    } else {
                        DOMCache.plugin_version_text.title = '';
                    }
                    DOMCache.plugin_version_display.classList.remove('oc-hidden');
                } else {
                    DOMCache.plugin_version_display.classList.add('oc-hidden');
                    DOMCache.plugin_version_display.title = '';
                }
            }
            checkLatestVersions();
        });

        function checkLatestVersions() {
            StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "last_version")%>', function(x, data) {
                if (x && x.status == 200 && data) {
                    updateCoreDot(data.corelv);
                    updatePluginDot(data.oplv);
                } else {
                    updateCoreDot('');
                    updatePluginDot('');
                }
            });
        }
    }

    function logo_error(imgobj, imgSrc) {
        imgobj.src = imgSrc;
    }

    function check_core() {
        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "check_core")%>', function(x, status) {
            if (x && x.status == 200) {
                if (status.core_status != "1") {
                    var r = confirm("<%:You have not installed the core yet, do you want to download and install it now?%>");
                    if (r == true) {
                        return update("core_download");
                    }
                }
            }
        });
    }

    function copyAddress() {
        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "dashboard_type")%>', null, function(x, status) {
            if (x && x.status == 200) {
                var panel = status.default_dashboard;
                if (panel && StateManager.current_status[panel]) {
                    var url;
                    var currentStatus = StateManager.current_status;
                    if (currentStatus.daip && window.location.hostname == currentStatus.daip) {
                        url = 'http://' + window.location.hostname + ':' + currentStatus.cn_port + '/ui/' + panel;
                    } else if (currentStatus.daip && window.location.hostname != currentStatus.daip && currentStatus.db_foward_domain && currentStatus.db_foward_port) {
                        var ui_proto = currentStatus.db_forward_ssl == 0 ? 'http://' : 'https://';
                        url = ui_proto + currentStatus.db_foward_domain + ':' + currentStatus.db_foward_port + '/ui/' + panel;
                    } else {
                        url = 'http://' + window.location.hostname + ':' + currentStatus.cn_port + '/ui/' + panel;
                    }

                    if (panel === 'zashboard' || panel === 'metacubexd') {
                        url += '/#/setup?hostname=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? window.location.hostname : (currentStatus.db_foward_domain || window.location.hostname)) + '&port=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? currentStatus.cn_port : (currentStatus.db_foward_port || currentStatus.cn_port)) + (currentStatus.dase ? '&secret=' + currentStatus.dase : '');
                    } else if (panel === 'yacd') {
                        url += '/?hostname=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? window.location.hostname : (currentStatus.db_foward_domain || window.location.hostname)) + '&port=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? currentStatus.cn_port : (currentStatus.db_foward_port || currentStatus.cn_port)) + (currentStatus.dase ? '&secret=' + currentStatus.dase : '');
                    } else if (panel === 'dashboard') {
                        url += '/#/?host=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? window.location.hostname : (currentStatus.db_foward_domain || window.location.hostname)) + '&port=' + (currentStatus.daip && window.location.hostname == currentStatus.daip ? currentStatus.cn_port : (currentStatus.db_foward_port || currentStatus.cn_port)) + (currentStatus.dase ? '&secret=' + currentStatus.dase : '');
                    }

                    ocCopyToClipboard(url, DOMCache.copy_address, '<%:Control panel address copied:%> ', '<%:Copy failed, please copy manually:%>');
                } else {
                    var fallbackUrl = 'http://' + (StateManager.current_status.daip || 'unknown') + ':' + (StateManager.current_status.cn_port || '9090') + '/ui/zashboard/#/';
                    ocCopyToClipboard(fallbackUrl, DOMCache.copy_address, '<%:Control panel address copied:%> ', '<%:Copy failed, please copy manually:%>');
                }
            } else {
                var fallbackUrl = 'http://' + (StateManager.current_status.daip || 'unknown') + ':' + (StateManager.current_status.cn_port || '9090') + '/ui/zashboard/#/';
                ocCopyToClipboard(fallbackUrl, DOMCache.copy_address, '<%:Control panel address copied:%> ', '<%:Copy failed, please copy manually:%>');
            }
        });
        return false;
    }

    function copySecret() {
        var secret = StateManager.current_status.dase || '';
        if (secret === '') {
            alert('<%:No control panel secret set%>');
            return false;
        }
        ocCopyToClipboard(secret, DOMCache.copy_secret, '<%:Control panel secret copied:%> ', '<%:Copy failed, please copy manually:%>');
        return false;
    }

    function copyMixAuth() {
        if (StateManager.cached_proxy_info) {
            if (StateManager.cached_proxy_info.auth_user && StateManager.cached_proxy_info.auth_pass) {
                var authText = StateManager.cached_proxy_info.auth_user + ':' + StateManager.cached_proxy_info.auth_pass;
                ocCopyToClipboard(authText, DOMCache.copy_mix_secret, '<%:Proxy auth info copied:%> ', '<%:Copy failed, please copy manually:%>');
            } else {
                alert('<%:No proxy auth info set%>');
            }
        } else {
            alert('<%:Proxy info not available, please try again later%>');
        }
        return false;
    }

    function copyMixAddress() {
        if (StateManager.cached_proxy_info && StateManager.current_status.daip) {
            var mixPort = StateManager.cached_proxy_info.mixed_port || '7893';
            var proxyIp = StateManager.current_status.daip;
            var proxyText = proxyIp + ':' + mixPort;
            if (StateManager.cached_proxy_info.auth_user && StateManager.cached_proxy_info.auth_pass) {
                proxyText = StateManager.cached_proxy_info.auth_user + ':' + StateManager.cached_proxy_info.auth_pass + '@' + proxyText;
            }
            proxyText = 'http://' + proxyText;
            ocCopyToClipboard(proxyText, DOMCache.copy_mix_address, '<%:Mix proxy address copied:%> ', '<%:Copy failed, please copy manually:%>');
        } else {
            alert('<%:Proxy info not available, please try again later%>');
        }
        return false;
    }

    function get_oc_settings() {
        if (SettingsManager.isPollPaused('oc_settings')) {
            return;
        }

        StateManager.cachedXHRGet('<%=luci.dispatcher.build_url("admin", "services", "openclash", "oc_settings")%>', function(x, info) {
            if (x && x.status == 200) {
                if (!SettingsManager.pendingOperations.has('meta_sniffer_' + info.meta_sniffer)) {
                    DOMCache.meta_sniffer_on.checked = info.meta_sniffer == "1";
                    DOMCache.meta_sniffer_off.checked = info.meta_sniffer != "1";
                }

                if (!SettingsManager.pendingOperations.has('respect_rules_' + info.respect_rules)) {
                    DOMCache.respect_rules_on.checked = info.respect_rules == "1";
                    DOMCache.respect_rules_off.checked = info.respect_rules != "1";
                }

                if (!SettingsManager.pendingOperations.has('oversea_' + info.oversea)) {
                    if (info.oversea == "0") {
                        DOMCache.oc_setting_oversea_0.checked = true;
                    } else if (info.oversea == "1") {
                        DOMCache.oc_setting_oversea_1.checked = true;
                    } else if (info.oversea == "2") {
                        DOMCache.oc_setting_oversea_2.checked = true;
                    }
                }

                if (!SettingsManager.pendingOperations.has('stream_unlock_' + info.stream_unlock)) {
                    DOMCache.stream_unlock_on.checked = info.stream_unlock === '1';
                    DOMCache.stream_unlock_off.checked = info.stream_unlock !== '1';
                }
            }
        }, true);
    }

    function switch_oc_setting_oversea(value) {
        LogManager.startLogDisplay('<%:Saving...%>', false, 'view');
        return SettingsManager.switchSetting(
            'oversea', 
            value, 
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_oc_setting")%>'
        );
    }

    function switch_meta_sniffer(value) {
        return SettingsManager.switchSetting(
            'meta_sniffer', 
            value, 
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_oc_setting")%>'
        );
    }

    function switch_respect_rules(value) {
        return SettingsManager.switchSetting(
            'respect_rules', 
            value, 
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_oc_setting")%>'
        );
    }

    function switch_stream_unlock(value) {
        return SettingsManager.switchSetting(
            'stream_unlock',
            value,
            '<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_oc_setting")%>'
        );
    }

    function generatePacConfig() {
        if (StateManager.current_status.daip) {
            var currentUrl = {
                protocol: window.location.protocol,
                hostname: window.location.hostname,
                host: window.location.host,
                port: window.location.port,
                href: window.location.href
            };

            XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "generate_pac")%>', {
                client_protocol: currentUrl.protocol.replace(':', ''),
                client_hostname: currentUrl.hostname,
                client_host: currentUrl.host,
                client_port: currentUrl.port || '',
                client_href: currentUrl.href
            }, function(x, data) {
                if (x && x.status == 200 && data.pac_url) {
                    if (data.error && data.error !== "") {
                        alert(data.error);
                    }
                    ocCopyToClipboard(data.pac_url, DOMCache.copy_pac_config, '<%:PAC file URL copied:%> ', '<%:Copy failed, please copy manually:%>');
                } else if (data.error) {
                    errorinfos = {
                        'Proxy service not running': '<%:Proxy service not running%>',
                        'Unable to get proxy IP': '<%:Unable to get proxy IP%>',
                        'Failed to write PAC file': '<%:Failed to write PAC file%>'
                    };
                    var errorMsg = errorinfos[data.error] || data.error;
                    alert('<%:PAC file generation failed%>: ' + errorMsg);
                } else {
                    alert('<%:PAC file generation failed%>');
                }
            });
        } else {
            alert('<%:Proxy service not available, please try again later%>');
        }
        return false;
    }

    function togglePlugin(toggleElement) {
        var isEnabled = toggleElement.checked;

        if (isEnabled) {
            var currentConfig = ConfigFileManager.getCurrentConfig() || ConfigFileManager.getSelectedConfig();
            if (!currentConfig) {
                toggleElement.checked = false;
                alert('<%:Please select a config file first%>');
                return false;
            }
        }

        toggleElement.disabled = true;
        pluginToggleUserAction = true;
        var action = isEnabled ? 'start' : 'stop';

        if (DOMCache.clash) {
            if (isEnabled) {
                var coreType = (StateManager.current_status && StateManager.current_status.core_type) || '';
                DOMCache.clash.innerHTML = '<b style=color:var(--success-color)>' + coreType + '&nbsp;<%:Running%></b>';
            } else {
                DOMCache.clash.innerHTML = '<b style=color:var(--error-color)><%:Not Running%></b>';
            }
        }
        StateManager.current_status.clash = isEnabled;

        var requestParams = { action: action };

        if (isEnabled) {
            var currentConfig = ConfigFileManager.getCurrentConfig();
            var selectedConfig = ConfigFileManager.getSelectedConfig();

            if (!currentConfig && selectedConfig) {
                requestParams.config_file = selectedConfig;
            }
        }

        LogManager.startLogDisplay(isEnabled ? '<%:Starting...%>' : '<%:Stopping...%>', false, 'init');

        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "action")%>', requestParams, function(x, status) {
            if (x && x.status == 200) {
                setTimeout(function() {
                    pluginToggleUserAction = false;
                    updatePluginToggleState(StateManager.current_status.clash || false);
                }, 2000);
            } else {
                toggleElement.checked = !isEnabled;
                StateManager.current_status.clash = !isEnabled;

                var errorMessage = isEnabled ? 
                    '<%:Failed to start OpenClash%>' : 
                    '<%:Failed to stop OpenClash%>';
                alert(errorMessage);

                if (DOMCache.clash) {
                    DOMCache.clash.innerHTML = '<b style="color:var(--error-color)"><%:Operation Failed%></b>';
                }

                pluginToggleUserAction = false;
            }
            toggleElement.disabled = false;
        });
    }

    function updatePluginToggleState(isRunning) {
        if (pluginToggleUserAction) {
            return;
        }

        var toggleElement = document.getElementById('plugin_toggle');
        if (toggleElement) {
            toggleElement.checked = isRunning;
            toggleElement.disabled = false;

            if (DOMCache.clash && StateManager.current_status) {
                DOMCache.clash.innerHTML = isRunning ? 
                    '<b style=color:var(--success-color)>' + StateManager.current_status.core_type +'&nbsp;<%:Running%></b>' : 
                    '<b style=color:var(--error-color)><%:Not Running%></b>';
            }
        }
    }

    function switchConfig() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        pluginToggleUserAction = true;

        LogManager.startLogDisplay('<%:Switching Config...%>', false, 'init');

        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "switch_config")%>', {
            config_file: currentConfig
        }, function(x, status) {
            if (x && x.status == 200 && status.status === 'success') {
                ConfigFileManager.refreshConfigList();
                setTimeout(function() {
                    pluginToggleUserAction = false;
                    updatePluginToggleState(StateManager.current_status.clash || false);
                }, 2000);
            } else {
                alert('<%:Failed to switch config file:%> ' + (status.message || '<%:Unknown error%>'));

                if (DOMCache.oclog) {
                    DOMCache.oclog.innerHTML = '<b style="color:var(--error-color)"><%:Switch Failed%></b>';
                }

                pluginToggleUserAction = false;
            }
        });

        return false;
    }

    function updateConfig() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        var filename = SubscriptionManager.extractFilename(currentConfig);
        if (!filename) {
            alert('<%:Invalid config file selected%>');
            return false;
        }

        pluginToggleUserAction = true;

        LogManager.startLogDisplay('<%:Updating Config...%>', false, 'openclash.sh');

        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update_config")%>', {
            filename: filename
        }, function(x, status) {
            if (x && x.status == 200) {
                if (status.status === 'success') {
                    setTimeout(function() {

                        refreshSubscriptionInfo();

                        ConfigFileManager.refreshConfigList();

                        pluginToggleUserAction = false;

                    }, 2000);
                } else {
                    pluginToggleUserAction = false;

                    if (DOMCache.oclog) {
                        DOMCache.oclog.innerHTML = '<b style="color:var(--error-color)"><%:Update Failed%></b>';
                    }

                    alert('<%:Failed to update config file:%> ' + (status.message || status.error || '<%:Unknown error%>'));
                }
            } else {
                pluginToggleUserAction = false;

                if (DOMCache.oclog) {
                    DOMCache.oclog.innerHTML = '<b style="color:var(--error-color)"><%:Update Failed%></b>';
                }

                alert('<%:Failed to update config file, please try again later%>');
            }
        });

        return false;
    }

    function restartCore() {
        var currentConfig = ConfigFileManager.getCurrentConfig() || ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        pluginToggleUserAction = true;

        var toggleElement = document.getElementById('plugin_toggle');
        if (toggleElement) {
            toggleElement.disabled = true;
        }
        var restartBtn = document.getElementById('restart_core');
        if (restartBtn) {
            restartBtn.disabled = true;
        }

        var requestParams = { action: 'restart' };

        var currentConfigValue = ConfigFileManager.getCurrentConfig();
        var selectedConfig = ConfigFileManager.getSelectedConfig();

        if (!currentConfigValue && selectedConfig) {
            requestParams.config_file = selectedConfig;
        }

        LogManager.startLogDisplay('<%:Restarting...%>', false, 'init');

        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "action")%>', requestParams, function(x, status) {
            if (restartBtn) {
                restartBtn.disabled = false;
            }
            if (x && x.status == 200) {
                setTimeout(function() {
                    pluginToggleUserAction = false;
                    updatePluginToggleState(StateManager.current_status.clash || false);
                }, 2000);
                ConfigFileManager.refreshConfigList();
            } else {
                if (toggleElement) {
                    toggleElement.disabled = false;
                }
                alert('<%:Failed to restart core%>');

                pluginToggleUserAction = false;
            }
        });
        return false;
    }

    function refreshSubscriptionInfo() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        SubscriptionManager.currentConfigFile = currentConfig;
        SubscriptionManager.retryCount = 0;

        var filename = SubscriptionManager.extractFilename(currentConfig);
        localStorage.removeItem('sub_info_' + filename);
        SubscriptionManager.getSubscriptionInfo();
        OverwriteSubscribeManager.load(true);
        return false;
    }

    function setSubscriptionUrl() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        var filename = SubscriptionManager.extractFilename(currentConfig);
        if (!filename) {
            alert('<%:Invalid config file selected%>');
            return false;
        }

        SubscriptionUrlSetter.show(filename, "refreshSubscriptionInfo()");
        return false;
    }

    function uploadConfig() {
        if (typeof ConfigUploader !== 'undefined' && ConfigUploader.show) {
            ConfigUploader.show("ConfigFileManager.refreshConfigList()");
        } else {
            setTimeout(function() {
                if (typeof ConfigUploader !== 'undefined' && ConfigUploader.show) {
                    ConfigUploader.show("ConfigFileManager.refreshConfigList()");
                } else {
                    alert('<%:Config editor not ready, please try again%>');
                }
            }, 500);
        }

        return false;
    }

    function editSubscribe() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        var filename = SubscriptionManager.extractFilename(currentConfig);
        if (!filename) {
            alert('<%:Invalid config file selected%>');
            return false;
        }

        XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "get_subscribe_data")%>', {
            filename: filename
        }, function(x, status) {
            if (x.status == 200) {
                if (ConfigUploader && ConfigUploader.showEditSubscribe) {
                    ConfigUploader.showEditSubscribe(status, filename, "ConfigFileManager.refreshConfigList()");
                } else {
                    setTimeout(function() {
                        if (ConfigUploader && ConfigUploader.showEditSubscribe) {
                            ConfigUploader.showEditSubscribe(status, filename, "ConfigFileManager.refreshConfigList()");
                        } else {
                            alert('<%:Config editor not ready, please try again%>');
                        }
                    }, 500);
                }
            } else {
                alert('<%:Failed to get subscribe data%>');
            }
        });

        return false;
    }

    function editConfig() {
        var currentConfig = ConfigFileManager.getSelectedConfig();
        if (!currentConfig) {
            alert('<%:Please select a config file first%>');
            return false;
        }

        if (ConfigEditor.show) {
            ConfigEditor.show(currentConfig);
        } else {
            setTimeout(function() {
                if (ConfigEditor.show) {
                    ConfigEditor.show(currentConfig);
                } else {
                    alert('<%:Config editor not ready, please try again%>');
                }
            }, 500);
        }

        return false;
    }

    function editOverwrite() {
        if (ConfigEditor.showOverwrite) {
            ConfigEditor.showOverwrite();
        } else {
            setTimeout(function() {
                if (ConfigEditor.showOverwrite) {
                    ConfigEditor.showOverwrite();
                } else {
                    alert('<%:Config editor not ready, please try again%>');
                }
            }, 500);
        }

        return false;
    }

    function switchToPreviousConfig() {
        if (ConfigFileManager.configList.length > 1) {
            var newIndex = ConfigFileManager.currentConfigIndex - 1;
            if (newIndex < 0) {
                newIndex = ConfigFileManager.configList.length - 1;
            }
            ConfigFileManager.switchToConfigByIndex(newIndex);
        }
        return false;
    }

    function switchToNextConfig() {
        if (ConfigFileManager.configList.length > 1) {
            var newIndex = ConfigFileManager.currentConfigIndex + 1;
            if (newIndex >= ConfigFileManager.configList.length) {
                newIndex = 0;
            }
            ConfigFileManager.switchToConfigByIndex(newIndex);
        }
        return false;
    }

    function toggleThemeMode() {
        var themes = ['light', 'dark', 'auto'];
        var currentTheme = localStorage.getItem('oc-theme') || 'auto';
        var currentIndex = themes.indexOf(currentTheme);
        if (currentIndex === -1) currentIndex = 2;
        var newIndex = (currentIndex + 1) % themes.length;
        var newTheme = themes[newIndex];
        localStorage.setItem('oc-theme', newTheme);
        ocUpdateTheme();
        DarkModeDetector.init();
    }
//]]></script>
</html>
