<%
    local fs = require "luci.openclash"
    local plugin_version = fs.oc_version()
%>
<div class="oc">
    <div class="config-editor-model-overlay" id="config-editor-overlay">
        <div class="config-editor-model" id="config-editor-model">
            <div class="config-editor-header">
                <div class="config-editor-title">
                    <span id="editTitle"><%:File Edit%>: </span>
                    <span class="config-file-name" id="config-file-name"><%:Loading...%></span>
                </div>
                <div class="config-editor-actions">
                    <button type="button" class="icon-btn" id="config-editor-layout" title="<%:Compare%>" style="display:none;">
                        <svg width="14" height="14"><use href="#oc-icon-compare"/></svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-download" title="<%:Download%>">
                        <svg width="14" height="14"><use href="#oc-icon-download"/></svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-save" title="<%:Save%>">
                        <svg width="14" height="14"><use href="#oc-icon-save"/></svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-close" title="<%:Close%>">
                        <svg width="14" height="14"><use href="#oc-icon-close"/></svg>
                    </button>
                </div>
            </div>

            <div id="overwrite-banner" class="overwrite-banner" style="display:none;">
                <svg style="flex-shrink:0;" width="20" height="20"><use href="#oc-icon-alert-circle"/></svg>
                <span>
                    <%:You are editing the overwrite script, please note that some settings may cause the abnormal, be careful with the modification!%>
                </span>
            </div>

            <div id="config-mode-tabs" style="display:none;">
                <div class="mode-tabs">
                    <button type="button" class="mode-tab active" id="tab-original-config" data-mode="original">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="vertical-align:middle;margin-right:4px;">
                            <rect x="4" y="4" width="16" height="16" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
                            <line x1="8" y1="8" x2="16" y2="8" stroke="currentColor" stroke-width="2"/>
                            <line x1="8" y1="12" x2="16" y2="12" stroke="currentColor" stroke-width="2"/>
                            <line x1="8" y1="16" x2="12" y2="16" stroke="currentColor" stroke-width="2"/>
                        </svg>
                        <%:Original Config%>
                    </button>
                    <button type="button" class="mode-tab" id="tab-runtime-config" data-mode="runtime">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" style="vertical-align:middle;margin-right:4px;">
                            <polygon points="13 2 3 14 12 14 11 22 21 10 13 10 13 2" stroke="currentColor" stroke-width="2" fill="none"/>
                        </svg>
                        <%:Runtime Config%>
                    </button>
                </div>
            </div>

            <div class="config-editor-content">
                <textarea id="config-editor-textarea" style="display: none;"></textarea>
                <div id="config-mergeview-container" style="display:none;width:100%;height:100%;"></div>
            </div>

            <div id="overwrite-card-bar" style="display:none;position:relative;margin:12px;">
                <div class="overwrite-card-row" id="overwrite-card-list"></div>
            </div>

            <div class="config-editor-footer">
                <div class="config-editor-status">
                    <span id="config-editor-status-text"><%:Ready%></span>
                </div>
                <div class="config-editor-help">
                    <span id="config-editor-help"><%:Press%> <b style="color:var(--success-color)">F11</b> <%:for fullscreen,%> <b style="color:var(--success-color)">Esc</b> <%:to exit fullscreen,%> <b style="color:var(--success-color)">Ctrl + <%:Mouse Wheel%></b> <%:to zoom%></span>
                </div>
                <div class="config-editor-resize-handle" id="config-editor-resize-handle"></div>
            </div>
        </div>
    </div>
</div>

<script src="/luci-static/resources/openclash/js/common.js?v=<%=plugin_version%>"></script>
<script src="/luci-static/resources/openclash/js/oc-icons.js?v=<%=plugin_version%>"></script>
<script src="/luci-static/resources/openclash/js/cm6.min.js?v=<%=plugin_version%>"></script>
<%+openclash/common%>
<script type="text/javascript">
ocRegisterEditorHotkeys();

var _ocIcons = {
	SVG_COMPARE: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/><rect x="14" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/></svg>',
	SVG_RESTORE: '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/></svg>'
};

// Uses EditorView.updateListener instead of view.dispatch override.
// dispatch() can receive TransactionSpec objects which lack docChanged.

var ConfigEditor = {
    overlay: null,
    model: null,
    editorInstance: null,
    _currentEditorMode: null,
    originalContent: '',
    isModified: false,
    currentZoom: 100,
    currentConfigFile: '',
    zoomLevels: [75, 90, 100, 110, 125, 150, 200],
    isOverwrite: false,
    currentViewMode: 'original',
    runtimeContent: '',
    mergeViewActive: false,
    isFullscreen: false,
    mergeShowDifferences: true,

    overwriteFiles: [],
    overwriteSubInfo: {},
    overwriteCardBar: null,
    overwriteDrag: {
        dragging: null,
        startIndex: null,
        touchTimer: null,
        isTouchDragging: false,
        touchDraggingMoved: false,
        touchDraggingCard: null,
        startTouch: null
    },

    init: function() {
        this.overlay = document.getElementById('config-editor-overlay');
        this.model = document.getElementById('config-editor-model');
        this.overwriteCardBar = document.getElementById('overwrite-card-bar');
        this.mergeViewActive = false;

        if (!this.overlay || !this.model) {
            return;
        }

        this.bindEvents();
    },

    bindEvents: function() {
        var self = this;

        document.getElementById('config-editor-save').addEventListener('click', function() {
            self.saveConfigContent();
        });

        document.getElementById('config-editor-download').addEventListener('click', function() {
            self.downloadConfigContent();
        });

        document.getElementById('config-editor-close').addEventListener('click', function() {
            self.closeEditor();
        });

        document.addEventListener('keydown', function(e) {
            if (!self.overlay.classList.contains('show')) return;

            if (e.key === 'Escape' && self.isFullscreen) {
                e.preventDefault();
                self.toggleFullscreen(false);
            } else if (e.key === 'Escape' && !self.isFullscreen) {
                self.closeEditor();
            }
        });

        this.overlay.addEventListener('wheel', function(e) {
            if (e.ctrlKey || e.metaKey) {
                e.preventDefault();

                if (e.deltaY < 0) {
                    self.zoomIn();
                } else {
                    self.zoomOut();
                }
            }
        });

        var tabOriginal = document.getElementById('tab-original-config');
        var tabRuntime = document.getElementById('tab-runtime-config');
        if (tabOriginal && tabRuntime) {
            tabOriginal.addEventListener('click', function() {
                if (self.currentViewMode !== 'original') {
                    self.currentViewMode = 'original';
                    self.loadConfigContent();
                    self.updateModeTabs();
                }
            });
            tabRuntime.addEventListener('click', function() {
                if (self.currentViewMode !== 'runtime') {
                    self.currentViewMode = 'runtime';
                    self.loadConfigContent();
                    self.updateModeTabs();
                }
            });
        };

        var layoutBtn = document.getElementById('config-editor-layout');
        if (layoutBtn) {
            layoutBtn.addEventListener('click', function() {
                if (!self.mergeViewActive) {
                    self.showMergeView();
                    self.currentViewMode = 'original';
                    layoutBtn.title = "<%:Restore%>";
                    layoutBtn.innerHTML = _ocIcons.SVG_RESTORE;
                } else {
                    self.hideMergeView();
                    layoutBtn.title = "<%:Compare%>";
                    layoutBtn.innerHTML = _ocIcons.SVG_COMPARE;
                }
            });
        };

        this.makeDraggable();
        this.makeResizable();
    },

    show: function(configFile) {
        var self = this;
        this.isOverwrite = false;
        this.currentViewMode = 'original';
        this.currentConfigFile = '';
        this.originalContent = '';
        this.runtimeContent = '';
        this.isModified = false;
        this.mergeViewActive = false;
        this.isFullscreen = false;

        if (this.editorInstance) {
            this._destroyEditor();
            this.editorInstance = null;
        }
        var textarea = document.getElementById('config-editor-textarea');
        if (textarea) {
            textarea.value = '';
            textarea.style.display = 'none';
        }
        ocShowLoading(textarea.parentNode, '<%:Loading config file...%>');
        var mergeview = document.getElementById('config-mergeview-container');
        if (mergeview) mergeview.style.display = 'none';

        var banner = document.getElementById('overwrite-banner');
        if (banner) banner.style.display = 'none';

        var tabs = document.getElementById('config-mode-tabs');
        if (tabs) tabs.style.display = 'flex';

        var layoutBtn = document.getElementById('config-editor-layout');
        if (layoutBtn) layoutBtn.style.display = 'inline-flex';

        if (!configFile) {
            alert('<%:Please select a config file first%>');
            return;
        }

        this.currentConfigFile = configFile;
        this.overlay.classList.add('show');

        this.model.classList.remove('maximized');
        this.model.classList.remove('minimized');

        var editTitle = document.getElementById('editTitle');
        if (editTitle) {
            editTitle.textContent = '<%:File Edit%>: ';
        }

        var configNameElement = document.getElementById('config-file-name');
        if (configNameElement) {
            configNameElement.textContent = this.formatDisplayName(configFile);
        }

        this.hideMergeView();
        this.updateModeTabs();
        self.loadConfigContent();
    },

    showOverwrite: function() {
        var self = this;
        this.isOverwrite = true;
        this.currentViewMode = 'original';
        this.originalContent = '';
        this.runtimeContent = '';
        this.isModified = false;
        this.mergeViewActive = false;
        this.isFullscreen = false;

        if (this.editorInstance) {
            this._destroyEditor();
            this.editorInstance = null;
        }
        var textarea = document.getElementById('config-editor-textarea');
        if (textarea) {
            textarea.value = '';
            textarea.style.display = 'none';
        }
        ocShowLoading(textarea.parentNode, '<%:Loading config file...%>');
        var mergeview = document.getElementById('config-mergeview-container');
        if (mergeview) mergeview.style.display = 'none';

        var banner = document.getElementById('overwrite-banner');
        if (banner) banner.style.display = 'flex';

        var tabs = document.getElementById('config-mode-tabs');
        if (tabs) tabs.style.display = 'none';

        var layoutBtn = document.getElementById('config-editor-layout');
        if (layoutBtn) layoutBtn.style.display = 'none';

        if (!this.currentConfigFile) {
            this.currentConfigFile = '/etc/openclash/custom/openclash_custom_overwrite.sh';
        }
        this.overlay.classList.add('show');

        this.model.classList.remove('maximized');
        this.model.classList.remove('minimized');

        var editTitle = document.getElementById('editTitle');
        if (editTitle) {
            editTitle.textContent = '<%:Overwrite Edit%>: ';
        }

        if (this.overwriteCardBar) this.overwriteCardBar.style.display = 'block';
        this.loadOverwriteFiles();

        var configNameElement = document.getElementById('config-file-name');
        if (configNameElement) {
            configNameElement.textContent = this.formatDisplayName(this.currentConfigFile);
        }

        this.hideMergeView();
        self.loadConfigContent();
    },

    _destroyEditor: function() {
        this.toggleFullscreen(false);
        if (this.editorInstance) {
            if (this.editorInstance.a && this.editorInstance.a.destroy) {
                this.editorInstance.a.destroy();
                if (this.editorInstance.b && this.editorInstance.b.destroy) this.editorInstance.b.destroy();
            } else if (this.editorInstance.destroy) {
                this.editorInstance.destroy();
            } else if (this.editorInstance.toTextArea) {
                this.editorInstance.toTextArea();
            }
            this.editorInstance = null;
        }
        this._currentEditorMode = null;
        var textarea = document.getElementById('config-editor-textarea');
        if (textarea) textarea.style.display = '';
    },

    toggleFullscreen: function(forceState) {
        var nextState = typeof forceState === 'boolean' ? forceState : !this.isFullscreen;
        this.isFullscreen = nextState;
        window._ocFullscreenActive = nextState;
        if (typeof CM6 !== 'undefined' && CM6.toggleFullscreen) {
            if (nextState) {
                var dom = this.editorInstance;
                if (dom && dom.dom) dom = dom.dom;
                if (dom && dom.classList) {
                    if (typeof _ocEnterFullscreen === 'function') _ocEnterFullscreen(dom);
                    CM6.toggleFullscreen(dom);
                }
            } else {
                var fsEl = document.getElementById('oc-fullscreen-active');
                if (fsEl) CM6.toggleFullscreen(fsEl);
                if (typeof _ocExitFullscreen === 'function') _ocExitFullscreen();
            }
        }
        ocUpdateTheme();
    },

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

        this._destroyEditor();
        window._mergeViewInstance = null;

        this.isModified = false;
        this.originalContent = '';
        this.overwriteCardBar.style.display = 'none';
        if (!this.isOverwrite) {
            this.currentConfigFile = '';
        }

        var textarea = document.getElementById('config-editor-textarea');
        var mergeview = document.getElementById('config-mergeview-container');
        var editor_help = document.getElementById('config-editor-help');
        var layoutBtn = document.getElementById('config-editor-layout');

        if (textarea) {
            ocHideLoading(textarea.parentNode);
            textarea.style.display = 'none';
        }
        if (mergeview) mergeview.style.display = 'none';
        if (layoutBtn) {
            layoutBtn.classList.remove('active');
            layoutBtn.title = "<%:Compare%>";
            layoutBtn.innerHTML = _ocIcons.SVG_COMPARE;
        }
        if (editor_help) editor_help.innerHTML = '<%:Press%> <b style="color:var(--success-color)">F11</b> <%:for fullscreen,%> <b style="color:var(--success-color)">Esc</b> <%:to exit fullscreen,%> <b style="color:var(--success-color)">Ctrl + <%:Mouse Wheel%></b> <%:to zoom%>';
    },

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

        if (fileName === '/etc/openclash/custom/openclash_custom_overwrite.sh') {
            return 'openclash_custom_overwrite.sh';
        }

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

    updateModeTabs: function() {
        var tabOriginal = document.getElementById('tab-original-config');
        var tabRuntime = document.getElementById('tab-runtime-config');
        var saveBtn = document.getElementById('config-editor-save');
        if (this.isOverwrite) {
            if (tabOriginal && tabRuntime) {
                tabOriginal.classList.remove('active');
                tabRuntime.classList.remove('active');
            }
            if (saveBtn) {
                saveBtn.disabled = !this.isModified;
                saveBtn.style.opacity = this.isModified ? '1' : '0.5';
                saveBtn.style.cursor = this.isModified ? 'pointer' : 'not-allowed';
            }
            return;
        }
        if (tabOriginal && tabRuntime) {
            if (this.currentViewMode === 'original') {
                tabOriginal.classList.add('active');
                tabRuntime.classList.remove('active');
                if (saveBtn) {
                    saveBtn.disabled = !this.isModified;
                    saveBtn.style.opacity = this.isModified ? '1' : '0.5';
                    saveBtn.style.cursor = this.isModified ? 'pointer' : 'not-allowed';
                }
            } else {
                tabOriginal.classList.remove('active');
                tabRuntime.classList.add('active');
                if (saveBtn) {
                    saveBtn.disabled = true;
                    saveBtn.style.opacity = '0.5';
                    saveBtn.style.cursor = 'not-allowed';
                }
            }
        }
    },

    loadConfigContent: function() {
        var self = this;
        var statusText = document.getElementById('config-editor-status-text');
        var textarea = document.getElementById('config-editor-textarea');
        var mergeview = document.getElementById('config-mergeview-container');
        if (mergeview) mergeview.style.display = 'none';

        statusText.textContent = '<%:Loading...%>';

        var url, mode;
        if (this.isOverwrite) {
            var file = this.currentConfigFile || '/etc/openclash/custom/openclash_custom_overwrite.sh';
            url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent(file);
            if (file.endsWith('.yaml') || file.endsWith('.yml')) {
                mode = "text/yaml";
            } else if (file.endsWith('.sh')) {
                mode = "text/x-sh";
            } else {
                mode = "text/x-properties";
            }
        } else if (this.currentViewMode === 'runtime') {
            var runtimePath = '/etc/openclash/' + encodeURIComponent(this.formatDisplayName(this.currentConfigFile));
            url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + runtimePath;
            mode = "text/yaml";
        } else {
            url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent(this.currentConfigFile);
            mode = "text/yaml";
        }

        function renderEditor(content, mode, readOnly, lint) {
            textarea.value = content;

            var editorKey = mode + '|' + (readOnly ? 'r' : 'w') + '|' + (lint ? 'l' : 'n');

            if (self.editorInstance && self._currentEditorMode === editorKey && !self.mergeViewActive) {
                self.originalContent = content;
                self.editorInstance.dispatch({
                    changes: { from: 0, to: self.editorInstance.state.doc.length, insert: content }
                });
                self.isModified = false;
                self.updateSaveButtonState();
                textarea.style.display = 'none';
                ocHideLoading(textarea.parentNode);
                return;
            }

            if (self.editorInstance) {
                self._destroyEditor();
            }
            self._currentEditorMode = editorKey;

            var exts = [];
            var isDark = isDarkBackground(document.body);
            var topSearch = (CM6 && CM6.topSearchExtension) ? CM6.topSearchExtension() : null;
            exts.push(CM6.themeExtension(isDark));
            if (topSearch) exts.push(topSearch);

            if (mode === 'text/yaml' || mode === 'text/x-yaml') {
                exts.push(CM6.yaml());
                if (lint) {
                    exts.push(CM6.yamlLinter());
                    exts.push(CM6.lintGutter());
                }
                exts.push(CM6.autocompletion({ override: [CM6.mihomoCompletion] }));
                exts.push(CM6.indentUnit.of("  "));
                exts.push(CM6.indentMarkerExtension());
            } else if (mode === 'text/x-sh' || mode === 'shell') {
                exts.push(CM6.StreamLanguage.define(CM6.shell));
            } else if (mode === 'text/x-properties' || mode === 'properties') {
                exts.push(CM6.StreamLanguage.define(CM6.properties));
            }

            if (readOnly) {
                exts.push(CM6.EditorState.readOnly.of(true));
            } else {
                if (mode === 'text/yaml' || mode === 'text/x-yaml') {
                    exts.push(CM6.placeholderExtension('<%:Enter YAML configuration...%>'));
                } else if (mode === 'text/x-sh' || mode === 'shell') {
                    exts.push(CM6.placeholderExtension('<%:Enter shell script...%>'));
                } else {
                    exts.push(CM6.placeholderExtension('<%:Enter configuration...%>'));
                }
            }

            exts.push(CM6.keymap.of([
                { key: 'Ctrl-s', run: function() {
                    if (!readOnly) self.saveConfigContent();
                    return true;
                }}
            ]));

            if (!readOnly) {
                exts.push(CM6.EditorView.updateListener.of(function(update) {
                    if (update.docChanged) {
                        self.isModified = update.state.doc.toString() !== self.originalContent;
                        self.updateSaveButtonState();
                    }
                }));
            }

            var parent = textarea.parentNode;
            self.editorInstance = new CM6.EditorView({
                state: CM6.EditorState.create({
                    doc: content,
                    extensions: [CM6.baseExtensions(), ...exts]
                }),
                parent: parent
            });

            textarea.style.display = 'none';
            self.editorInstance.dom.style.height = '100%';
            ocHideLoading(textarea.parentNode);

            if (CM6.mirrorThemeScrollbar) CM6.mirrorThemeScrollbar();
        }

        if (!this.isOverwrite) {
            if (this.currentViewMode === 'original' && this.originalContent) {
                renderEditor(this.originalContent, "text/yaml", false, true);
                statusText.textContent = '<%:Ready%>';
                self.updateModeTabs();
                return;
            }
            if (this.currentViewMode === 'runtime' && this.runtimeContent) {
                renderEditor(this.runtimeContent, "text/yaml", true, false);
                statusText.textContent = '<%:Runtime config (read only)%>';
                self.updateModeTabs();
                return;
            }
        }

        fetch(url)
            .then(function(r) {
                if (!r.ok) throw new Error('HTTP ' + r.status);
                return r.json();
            })
            .then(function(data) {
                if (data.status === 'success' && data.content !== undefined) {
                    if (self.currentViewMode === 'runtime' && !self.isOverwrite) {
                        self.runtimeContent = data.content;
                        renderEditor(self.runtimeContent, "text/yaml", true, false);
                        statusText.textContent = '<%:Runtime config (read only)%>';
                    } else {
                        self.originalContent = data.content;
                        renderEditor(self.originalContent, mode, self.isOverwrite ? false : false, !self.isOverwrite);
                        statusText.textContent = '<%:Ready%>';
                    }
                    self.updateModeTabs();
                } else {
                    ocHideLoading(textarea.parentNode);
                    statusText.textContent = '<%:Load failed%>';
                }
            })
            .catch(function(err) {
                ocHideLoading(textarea.parentNode);
                statusText.textContent = '<%:Load failed%>';
            });
    },

    showMergeView: function() {
        var self = this;
        if (this.isOverwrite) return;
        var container = document.getElementById('config-mergeview-container');
        var textarea = document.getElementById('config-editor-textarea');
        var tabs = document.getElementById('config-mode-tabs');
        var editor_help = document.getElementById('config-editor-help');
        var statusText = document.getElementById('config-editor-status-text');

        if (tabs) tabs.style.display = 'none';
        if (textarea) {
            textarea.style.display = 'none';
            ocHideLoading(textarea.parentNode);
        }
        if (container) container.style.display = 'block';
        if (statusText) statusText.textContent = '<%:Loading...%>';
        if (editor_help) editor_help.innerHTML = '<%:Press%> <b style="color:var(--success-color)">F10</b> <%:to toggle differences,%> <b style="color:var(--success-color)">F11</b> <%:for fullscreen,%> <b style="color:var(--success-color)">Esc</b> <%:to exit fullscreen,%> <b style="color:var(--success-color)">Ctrl + <%:Mouse Wheel%></b> <%:to zoom%>';

        var getOriginal = function() {
            return new Promise(function(resolve, reject) {
                if (self.originalContent) return resolve(self.originalContent);
                var url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + encodeURIComponent(self.currentConfigFile);
                fetch(url).then(function(r){return r.json()}).then(function(data){
                    resolve(data.content || '');
                }).catch(function(){resolve('')});
            });
        };
        var getRuntime = function() {
            return new Promise(function(resolve, reject) {
                if (self.runtimeContent) return resolve(self.runtimeContent);
                var runtimePath = '/etc/openclash/' + encodeURIComponent(self.formatDisplayName(self.currentConfigFile));
                var url = '/cgi-bin/luci/admin/services/openclash/config_file_read?config_file=' + runtimePath;
                fetch(url).then(function(r){return r.json()}).then(function(data){
                    resolve(data.content || '');
                }).catch(function(){resolve('')});
            });
        };

        this.mergeShowDifferences = true;

        Promise.all([getOriginal(), getRuntime()]).then(function(contents){
            var original = contents[0] || '';
            var runtime = contents[1] || '';
            container.innerHTML = '';
            if (self.editorInstance) self._destroyEditor();

            var isDark = isDarkBackground(document.body);
            var themeExt = CM6.themeExtension(isDark);
            var topSearch = (CM6 && CM6.topSearchExtension) ? CM6.topSearchExtension() : null;
            var extA = [CM6.baseExtensions(), themeExt, CM6.placeholderExtension('<%:Edit original config...%>'), CM6.yaml(), CM6.yamlLinter(), CM6.lintGutter(), CM6.autocompletion({ override: [CM6.mihomoCompletion] }), CM6.indentUnit.of("  "), CM6.indentMarkerExtension()];
            var extB = [CM6.baseExtensions(), themeExt, CM6.placeholderExtension('<%:Runtime config (read only)%>'), CM6.yaml(), CM6.indentUnit.of("  "), CM6.indentMarkerExtension(), CM6.EditorState.readOnly.of(true)];
            var mergeDefaults = CM6.mergeDefaultConfig || {};
            if (topSearch) {
                extA.push(topSearch);
                extB.push(topSearch);
            }

            extA.push(CM6.EditorView.updateListener.of(function(update) {
                if (update.docChanged) {
                    self.isModified = update.state.doc.toString() !== self.originalContent;
                    self.updateSaveButtonState();
                }
            }));

            self.editorInstance = new CM6.MergeView({
                a: {
                    doc: original,
                    extensions: extA
                },
                b: {
                    doc: runtime,
                    extensions: extB
                },
                parent: container,
                diffConfig: mergeDefaults.diffConfig || { scanLimit: 5000 },
                gutter: mergeDefaults.gutter !== false,
                revertControls: mergeDefaults.revertControls || 'b-to-a',
                highlightChanges: self.mergeShowDifferences,
                collapseUnchanged: mergeDefaults.collapseUnchanged || false
            });

            self.mergeViewActive = true;
            window._mergeViewInstance = self.editorInstance;

            if (CM6.mirrorThemeScrollbar) CM6.mirrorThemeScrollbar();

            if (statusText) statusText.textContent = '<%:Compare mode: left(Original Config), right(Runtime Config)%>';
            var layoutBtn = document.getElementById('config-editor-layout');
            if (layoutBtn) layoutBtn.classList.add('active');
        });
    },

    hideMergeView: function() {
        var container = document.getElementById('config-mergeview-container');
        var textarea = document.getElementById('config-editor-textarea');
        var tabs = document.getElementById('config-mode-tabs');
        var editor_help = document.getElementById('config-editor-help');
        var layoutBtn = document.getElementById('config-editor-layout');
        if (container) {
            container.innerHTML = '';
            container.style.display = 'none';
        }
        if (textarea) textarea.style.display = 'block';
        if (!this.isOverwrite && tabs) tabs.style.display = 'flex';
        this.mergeViewActive = false;
        window._mergeViewInstance = null;
        this.loadConfigContent();

        if (layoutBtn) {
            layoutBtn.classList.remove('active');
            layoutBtn.title = "<%:Compare%>";
            layoutBtn.innerHTML = _ocIcons.SVG_COMPARE;
        }

        if (editor_help) editor_help.innerHTML = '<%:Press%> <b style="color:var(--success-color)">F11</b> <%:for fullscreen,%> <b style="color:var(--success-color)">Esc</b> <%:to exit fullscreen,%> <b style="color:var(--success-color)">Ctrl + <%:Mouse Wheel%></b> <%:to zoom%>';
    },

    saveConfigContent: function() {
        if (!this.editorInstance || !this.isModified) {
            return;
        }

        var self = this;
        var statusText = document.getElementById('config-editor-status-text');
        var saveBtn = document.getElementById('config-editor-save');

        statusText.textContent = '<%:Saving...%>';
        saveBtn.disabled = true;

        var content;
        if (this.mergeViewActive && this.editorInstance && this.editorInstance.a) {
            content = this.editorInstance.a.state.doc.toString();
        } else if (this.editorInstance.state) {
            content = this.editorInstance.state.doc.toString();
        } else {
            content = '';
        }

        if (!content) {
            saveBtn.disabled = false;
            statusText.textContent = '<%:Save failed%>';
            alert('<%:Config file content is empty%>');
            return;
        }

        var formData = new FormData();
        if (this.isOverwrite) {
            formData.append('config_file', this.currentConfigFile || '/etc/openclash/custom/openclash_custom_overwrite.sh');
        } else {
            formData.append('config_file', this.currentConfigFile);
        }
        formData.append('content', content);

        fetch('/cgi-bin/luci/admin/services/openclash/config_file_save', {
            method: 'POST',
            body: formData
        })
        .then(function(r) {
            if (!r.ok) throw new Error('HTTP ' + r.status);
            return r.json();
        })
        .then(function(data) {
            saveBtn.disabled = false;

            if (data.status === 'success') {
                self.originalContent = content;
                self.isModified = false;
                self.updateSaveButtonState();
                statusText.textContent = '<%:Saved successfully%>';

                setTimeout(function() {
                    if (statusText && statusText.textContent === '<%:Saved successfully%>') {
                        statusText.textContent = '<%:Ready%>';
                    }
                }, 3000);
            } else {
                statusText.textContent = '<%:Save failed%>';
                alert('<%:Failed to save config file:%> ' + (data.message || '<%:Unknown error%>'));

                setTimeout(function() {
                    if (statusText && statusText.textContent === '<%:Save failed%>') {
                        statusText.textContent = '<%:Ready%>';
                    }
                }, 3000);
            }
        })
        .catch(function(err) {
            saveBtn.disabled = false;
            statusText.textContent = '<%:Save failed%>';
            alert('<%:Failed to save config file:%> ' + err.message);
        });
    },

    downloadConfigContent: function() {
        if (!this.editorInstance) {
            alert('<%:Editor not ready%>');
            return;
        }

        var content;
        if (this.mergeViewActive && this.editorInstance && this.editorInstance.a) {
            content = this.editorInstance.a.state.doc.toString();
        } else if (this.editorInstance.state) {
            content = this.editorInstance.state.doc.toString();
        } else {
            content = '';
        }

        var filename;
        if (this.isOverwrite) {
            filename = this.formatDisplayName(this.currentConfigFile);
            if (!filename) filename = 'openclash_custom_overwrite.sh';
        } else {
            filename = this.formatDisplayName(this.currentConfigFile);
            if (!filename.toLowerCase().endsWith('.yaml') && !filename.toLowerCase().endsWith('.yml')) {
                filename += '.yaml';
            }
        }

        try {
            var blob = new Blob([content], { type: 'text/yaml;charset=utf-8' });
            var url = window.URL.createObjectURL(blob);
            var link = document.createElement('a');
            link.href = url;
            link.download = filename;
            link.style.display = 'none';

            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
            window.URL.revokeObjectURL(url);

            var statusText = document.getElementById('config-editor-status-text');
            if (statusText) {
                var originalText = statusText.textContent;
                statusText.textContent = '<%:Download started%>';

                setTimeout(function() {
                    if (statusText && statusText.textContent === '<%:Download started%>') {
                        statusText.textContent = originalText;
                    }
                }, 2000);
            }

        } catch (error) {
            alert('<%:Download failed:%> ' + error.message);
        }
    },

    updateSaveButtonState: function() {
        this.updateModeTabs();
    },

    loadOverwriteFiles: function() {
        var self = this;
        fetch('/cgi-bin/luci/admin/services/openclash/overwrite_file_list')
        .then(r => r.json())
        .then(function(data) {
            var files = [];
            if (data.overwrite_files) {
                files = data.overwrite_files.filter(function(f) {
                    return (f.path && (f.path.indexOf('/etc/openclash/overwrite/') === 0 || f.path === '/etc/openclash/custom/openclash_custom_overwrite.sh'));
                });
            }
            files = files.filter(f => f.path !== '/etc/openclash/custom/openclash_custom_overwrite.sh');
            files.unshift({
                path: '/etc/openclash/custom/openclash_custom_overwrite.sh',
                name: 'openclash_custom_overwrite.sh'
            });
            self.overwriteFiles = files;
            self.loadOverwriteSubInfo();
        });
    },

    loadOverwriteSubInfo: function() {
        var self = this;
        fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info')
        .then(r => r.json())
        .then(function(data) {
            if (data.status === 'success') {
                self.overwriteSubInfo = (data && data.data) ? data.data : {};
                Object.keys(self.overwriteSubInfo).forEach(function(key) {
                    var subInfo = self.overwriteSubInfo[key] || {};
                    subInfo.config = self.parseOverwriteConfigValue(subInfo.config);
                    self.overwriteSubInfo[key] = subInfo;
                });
                self.overwriteFiles.sort(function(a, b) {
                    var an = a.name || (a.path ? a.path.split('/').pop() : '');
                    var bn = b.name || (b.path ? b.path.split('/').pop() : '');
                    var ao = self.overwriteSubInfo[an] && self.overwriteSubInfo[an].order ? parseInt(self.overwriteSubInfo[an].order) : 0;
                    var bo = self.overwriteSubInfo[bn] && self.overwriteSubInfo[bn].order ? parseInt(self.overwriteSubInfo[bn].order) : 0;
                    return ao - bo;
                });
                self.renderOverwriteCards();
            } else {
                var statusText = document.getElementById('overwrite-edit-status-text');
                statusText.textContent = '<%:Failed to save subscription info:%> ' + (data.message || '');
            }
        });
    },

    renderOverwriteCards: function() {
        var self = this;
        var bar = this.overwriteCardBar;
        if (!bar) return;
        var list = document.getElementById('overwrite-card-list');
        if (!list) return;
        list.innerHTML = '';

        var statusText = document.getElementById('config-editor-status-text');

        var addCard = document.createElement('div');
        addCard.className = 'sub-card overwrite-item';
        addCard.style.cursor = 'pointer';
        addCard.innerHTML = '<div class="overwrite-add-icon">+</div>';
        addCard.title = '<%:Add New File%>';
        addCard.onclick = function() {
            ConfigEditor.showAddOverwritemodel();
        };
        list.appendChild(addCard);

        var customIdx = self.overwriteFiles.findIndex(f => (f.name === 'openclash_custom_overwrite.sh' || (f.path && f.path.endsWith('/openclash_custom_overwrite.sh'))));
        var customFile = customIdx !== -1 ? self.overwriteFiles[customIdx] : null;

        if (customFile) {
            var name = customFile.name || (customFile.path ? customFile.path.split('/').pop() : '');
            var card = document.createElement('div');
            card.className = 'sub-card overwrite-item';
            card.dataset.file = customFile.path;
            card.dataset.index = 'custom';

            if (self.currentConfigFile === customFile.path) {
                card.classList.add('active');
            }

            var titleRow = document.createElement('div');
            titleRow.className = 'overwrite-title-row';

            var title = document.createElement('div');
            title.textContent = name;
            title.className = 'overwrite-title';
            title.title = name;

            titleRow.appendChild(title);
            card.appendChild(titleRow);

            var sub = self.overwriteSubInfo[name] || {};
            var info = document.createElement('div');
            info.className = 'overwrite-info';
            info.textContent = sub.url ? '<%:Subscription%>' : '<%:Local File%>';
            card.appendChild(info);

            card.onclick = function(e) {
                if (self.currentConfigFile !== customFile.path) {
                    self.currentConfigFile = customFile.path;
                    self.loadConfigContent();
                    self.renderOverwriteCards();
                    var configNameElement = document.getElementById('config-file-name');
                    if (configNameElement) {
                        configNameElement.textContent = self.formatDisplayName(customFile.path);
                    }
                }
            };

            card.draggable = false;

            list.appendChild(card);
        }

        var files = self.overwriteFiles.filter((f, i) => i !== customIdx);
        self.overwriteFileCardCount = files.length;
        files.forEach(function(file, idx) {
            var name = file.name || (file.path ? file.path.split('/').pop() : '');
            var card = document.createElement('div');
            card.className = 'sub-card overwrite-item';
            card.dataset.file = file.path;
            card.dataset.index = idx;

            if (self.currentConfigFile === file.path) {
                card.classList.add('active');
            }

            var titleRow = document.createElement('div');
            titleRow.className = 'overwrite-title-row';

            var title = document.createElement('div');
            title.textContent = name;
            title.className = 'overwrite-title';
            card.appendChild(title);

            var sub = self.overwriteSubInfo[name] || {};
            var enable = typeof sub.enable !== 'undefined' ? sub.enable : 0;
            var switchLabel = document.createElement('label');
            switchLabel.className = 'oc-switch';

            var switchInput = document.createElement('input');
            switchInput.type = 'checkbox';
            switchInput.checked = enable == 1 ? true : false;
            switchInput.onchange = function(e) {
                e.stopPropagation();
                var newEnable = switchInput.checked ? 1 : 0;
                var formData = new FormData();
                formData.append('filename', name);
                formData.append('type', sub.type || 'file');
                formData.append('url', sub.url || '');
                formData.append('update_days', sub.update_days || '');
                formData.append('update_hour', sub.update_hour || '');
                formData.append('order', (typeof sub.order !== 'undefined' && sub.order !== null && sub.order !== '') ? sub.order : idx);
                formData.append('param', sub.param || '');
                formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
                formData.append('enable', newEnable);
                fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
                    method: 'POST',
                    body: formData
                }).then(r=>r.json()).then(function(data){
                    if (data.status === 'success') {
                        self.overwriteSubInfo[name] = self.overwriteSubInfo[name] || {};
                        self.overwriteSubInfo[name].enable = newEnable;
                    } else {
                        statusText.textContent = '<%:Failed to update enable status%>';
                        switchInput.checked = !switchInput.checked;
                    }
                }).catch(function(){
                    statusText.textContent = '<%:Failed to update enable status%>';
                    switchInput.checked = !switchInput.checked;
                });
            };
            switchInput.onclick = function(e) { e.stopPropagation(); };
            switchInput.onmousedown = function(e) { e.stopPropagation(); };
            switchInput.ontouchstart = function(e) { e.stopPropagation(); };
            switchLabel.onclick = function(e) { e.stopPropagation(); };
            switchLabel.onmousedown = function(e) { e.stopPropagation(); };
            switchLabel.ontouchstart = function(e) { e.stopPropagation(); };

            var switchSpan = document.createElement('span');
            switchSpan.className = 'oc-switch-slider';

            switchLabel.appendChild(switchInput);
            switchLabel.appendChild(switchSpan);

            titleRow.appendChild(title);
            titleRow.appendChild(switchLabel);
            card.appendChild(titleRow);

            var info = document.createElement('div');
            info.className = 'overwrite-info';
            info.textContent = sub.url ? '<%:Subscription%>' : '<%:Local File%>';
            card.appendChild(info);

            if (sub.type === 'http') {
                var refresh = document.createElement('button');
                refresh.className = 'icon-btn overwrite-refresh-btn';
                refresh.type = 'button';
                refresh.title = '<%:Refresh Subscription%>';
                refresh.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><path d="M1 20v-6h6"/><path d="M3.51 9a9 9 0 0 1 14.13-3.36L23 10"/><path d="M1 14l4.35 4.35A9 9 0 0 0 20.49 15"/></svg>';
                refresh.onclick = function(e) {
                    e.stopPropagation();
                    var statusText = document.getElementById('config-editor-status-text');
                    if (statusText) {
                        statusText.textContent = '<%:Refreshing subscription...%>';
                    }
                    refresh.disabled = true;
                    var formData = new FormData();
                    formData.append('filename', name);
                    formData.append('type', sub.type || 'http');
                    formData.append('url', sub.url || '');
                    formData.append('update_days', sub.update_days || '');
                    formData.append('update_hour', sub.update_hour || '');
                    formData.append('order', (typeof sub.order !== 'undefined' && sub.order !== null && sub.order !== '') ? sub.order : idx);
                    formData.append('param', sub.param || '');
                    formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
                    formData.append('enable', typeof sub.enable !== 'undefined' ? sub.enable : 1);
                    fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
                        method: 'POST',
                        body: formData
                    }).then(r=>r.json()).then(function(data){
                        refresh.disabled = false;
                        if (statusText) {
                            if (data.status === 'success') {
                                statusText.textContent = '<%:Subscription refreshed successfully%>';
                            } else {
                                statusText.textContent = '<%:Subscription refresh failed%>: ' + (data.message || '');
                            }
                        }
                        setTimeout(function(){
                            if (statusText && (statusText.textContent.startsWith('<%:Subscription refreshed successfully%>') || statusText.textContent.startsWith('<%:Subscription refresh failed%>'))) {
                                statusText.textContent = '<%:Ready%>';
                            }
                        }, 2000);
                    }).catch(function(){
                        refresh.disabled = false;
                        if (statusText) statusText.textContent = '<%:Subscription refresh failed%>';
                        setTimeout(function(){
                            if (statusText && statusText.textContent.startsWith('<%:Subscription refresh failed%>')) {
                                statusText.textContent = '<%:Ready%>';
                            }
                        }, 2000);
                    });
                    setTimeout(function(){
                        self.loadConfigContent();
                    }, 1000);
                };
                card.appendChild(refresh);
            }

            var gear = document.createElement('button');
            gear.className = 'icon-btn overwrite-gear-btn';
            gear.type = 'button';
            gear.title = '<%:Edit Module Info%>';
            gear.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33h.09a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51h.09a1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82v.09a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>';
            gear.onclick = function(e) {
                e.stopPropagation();
                ConfigEditor.showOverwriteSubmodel(name, sub, file.path);
            };
            card.appendChild(gear);

            var del = document.createElement('button');
            del.className = 'icon-btn overwrite-del-btn';
            del.type = 'button';
            del.title = '<%:Delete%>';
            del.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>';
            del.onclick = function(e) {
                e.stopPropagation();
                if (confirm('<%:Are you sure you want to delete this file and its subscription info?%>')) {
                    fetch('/cgi-bin/luci/admin/services/openclash/delete_overwrite_file', {
                        method: 'POST',
                        body: new URLSearchParams({ filename: name })
                    })
                    .then(r => r.json())
                    .then(function(data) {
                        if (data.status === 'success') {
                            self.loadOverwriteFiles();
                            setTimeout(function() {
                                var files = self.overwriteFiles.filter(f => f.name !== name && f.name !== 'openclash_custom_overwrite.sh');
                                if (files.length > 0) {
                                    self.currentConfigFile = files[0].path;
                                    self.loadConfigContent();
                                    self.renderOverwriteCards();
                                    var configNameElement = document.getElementById('config-file-name');
                                    if (configNameElement) {
                                        configNameElement.textContent = self.formatDisplayName(files[0].path);
                                    }
                                } else {
                                    self.currentConfigFile = '/etc/openclash/custom/openclash_custom_overwrite.sh';
                                    self.loadConfigContent();
                                    self.renderOverwriteCards();
                                    var configNameElement = document.getElementById('config-file-name');
                                    if (configNameElement) {
                                        configNameElement.textContent = self.formatDisplayName(self.currentConfigFile);
                                    }
                                }
                            }, 300);
                        } else {
                            statusText.textContent = '<%:Delete failed%>: ' + (data.message || '');
                        }
                    });
                }
            };
            card.appendChild(del);

            card.onclick = function(e) {
                if (self.currentConfigFile !== file.path) {
                    self.currentConfigFile = file.path;
                    self.loadConfigContent();
                    self.renderOverwriteCards();
                    var configNameElement = document.getElementById('config-file-name');
                    if (configNameElement) {
                        configNameElement.textContent = self.formatDisplayName(file.path);
                    }
                }
            };

            card.draggable = true;
            card.addEventListener('dragstart', function(e) {
                self.overwriteDrag.dragging = card;
                self.overwriteDrag.startIndex = idx;
                card.classList.add('dragging');
                e.dataTransfer.effectAllowed = 'move';
            });
            card.addEventListener('dragend', function(e) {
                card.classList.remove('dragging');
                self.overwriteDrag.dragging = null;
                self.overwriteDrag.startIndex = null;
                list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
            });
            card.addEventListener('dragover', function(e) {
                e.preventDefault();
                e.dataTransfer.dropEffect = 'move';

                var rect = card.getBoundingClientRect();
                var listRect = list.getBoundingClientRect();
                var cardIndex = parseInt(card.dataset.index);

                var mid = rect.left + rect.width * 0.5;

                let insertPos = null;
                if (e.clientX < mid) {
                    insertPos = 'before';
                } else if (e.clientX >= mid) {
                    insertPos = 'after';
                }

                let currentLine = list.querySelector('.overwrite-drag-line');

                if (!currentLine && insertPos) {
                    var line = document.createElement('div');
                    line.className = 'overwrite-drag-line';
                    if (insertPos === 'before') {
                        list.insertBefore(line, card);
                    } else if (insertPos === 'after') {
                        if (card.nextSibling) {
                            list.insertBefore(line, card.nextSibling);
                        } else {
                            list.appendChild(line);
                        }
                    }
                }

                var scrollZone = 40;
                if (e.clientX - listRect.left < scrollZone) {
                    list.scrollLeft -= 5;
                } else if (listRect.right - e.clientX < scrollZone) {
                    list.scrollLeft += 5;
                }
            });

            card.addEventListener('drop', function(e) {
                e.preventDefault();
                var dragging = self.overwriteDrag.dragging;
                if (!dragging || dragging === card) return;
                var from = parseInt(dragging.dataset.index);
                var to = parseInt(card.dataset.index);
                var rect = card.getBoundingClientRect();
                var midX = rect.left + rect.width / 2;
                if (e.clientX < midX) {
                } else {
                    to++;
                }
                if (from === to || from + 1 === to) {
                    list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
                    self.saveOverwriteSort();
                    return;
                }
                var arr = files;
                var moved = arr.splice(from, 1)[0];
                arr.splice(to > from ? to - 1 : to, 0, moved);
                var newArr = [];
                if (customFile) newArr.push(customFile);
                newArr = newArr.concat(arr);
                self.overwriteFiles = newArr;
                self.renderOverwriteCards();
                self.saveOverwriteSort();
                list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
            });

            card.addEventListener('touchstart', function(e) {
                if (card.dataset.index === 'custom') return;
                if (e.touches.length !== 1) return;

                var target = e.target;
                var isButton = target.closest('.overwrite-refresh-btn, .overwrite-gear-btn, .overwrite-del-btn, .oc-switch');
                if (isButton) return;

                self.overwriteDrag.touchDraggingCard = card;
                self.overwriteDrag.touchDraggingMoved = false;
                self.overwriteDrag.isTouchDragging = false;

                self.overwriteDrag.startTouch = {
                    clientX: e.touches[0].clientX,
                    clientY: e.touches[0].clientY
                };

                self.overwriteDrag.touchTimer = setTimeout(function() {
                    if (!self.overwriteDrag.touchDraggingMoved && self.overwriteDrag.touchDraggingCard === card) {
                        self.overwriteDrag.isTouchDragging = true;
                        self.overwriteDrag.dragging = card;
                        self.overwriteDrag.startIndex = parseInt(card.dataset.index);
                        card.classList.add('dragging');

                        if (navigator.vibrate) {
                            navigator.vibrate(50);
                        }
                    }
                }, 500);
            });

            card.addEventListener('touchmove', function(e) {
                if (card.dataset.index === 'custom') return;
                if (e.touches.length !== 1) return;

                var touch = e.touches[0];

                if (!self.overwriteDrag.touchDraggingMoved && self.overwriteDrag.startTouch) {
                    var dx = Math.abs(touch.clientX - self.overwriteDrag.startTouch.clientX);
                    var dy = Math.abs(touch.clientY - self.overwriteDrag.startTouch.clientY);
                    if (dx > 15 || dy > 15) {
                        self.overwriteDrag.touchDraggingMoved = true;
                    }
                }

                if (self.overwriteDrag.isTouchDragging) {
                    list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());

                    var elementBelow = document.elementFromPoint(touch.clientX, touch.clientY);
                    var targetCard = null;

                    if (elementBelow) {
                        targetCard = elementBelow.closest('.sub-card.overwrite-item');
                    }

                    if (targetCard && targetCard !== card && targetCard.dataset.index !== 'custom') {
                        var rect = targetCard.getBoundingClientRect();
                        var midX = rect.left + rect.width / 2;

                        var line = document.createElement('div');
                        line.className = 'overwrite-drag-line';

                        if (touch.clientX < midX) {
                            list.insertBefore(line, targetCard);
                        } else {
                            if (targetCard.nextSibling) {
                                list.insertBefore(line, targetCard.nextSibling);
                            } else {
                                list.appendChild(line);
                            }
                        }
                    }

                    var listRect = list.getBoundingClientRect();
                    var scrollZone = 60;
                    if (touch.clientX - listRect.left < scrollZone && list.scrollLeft > 0) {
                        list.scrollLeft -= 10;
                    } else if (listRect.right - touch.clientX < scrollZone) {
                        list.scrollLeft += 10;
                    }

                    e.preventDefault();
                }
            });

            card.addEventListener('touchend', function(e) {
                if (self.overwriteDrag.touchTimer) {
                    clearTimeout(self.overwriteDrag.touchTimer);
                    self.overwriteDrag.touchTimer = null;
                }

                var wasInDraggingMode = self.overwriteDrag.isTouchDragging;

                if (wasInDraggingMode) {
                    card.classList.remove('dragging');

                    var line = list.querySelector('.overwrite-drag-line');
                    if (line) {
                        var from = parseInt(card.dataset.index);
                        var allCards = Array.from(list.querySelectorAll('.sub-card.overwrite-item'));
                        var fileCards = allCards.filter(c => c.dataset && typeof c.dataset.index !== 'undefined' && c.dataset.index !== 'custom');
                        var lineRect = line.getBoundingClientRect();
                        var lineCenterX = lineRect.left + lineRect.width / 2;

                        var to = fileCards.length;
                        for (var i = 0; i < fileCards.length; i++) {
                            var rect = fileCards[i].getBoundingClientRect();
                            var cardMid = rect.left + rect.width / 2;
                            if (lineCenterX < cardMid) {
                                to = i;
                                break;
                            }
                        }

                        if (!(from === to || from + 1 === to)) {
                            var arr = self.overwriteFiles.filter(function(f){ return f.name !== 'openclash_custom_overwrite.sh'; });
                            var customFile = self.overwriteFiles.find(function(f){ return f.name === 'openclash_custom_overwrite.sh'; });
                            var moved = arr.splice(from, 1)[0];
                            arr.splice(to > from ? to - 1 : to, 0, moved);
                            var newArr = [];
                            if (customFile) newArr.push(customFile);
                            newArr = newArr.concat(arr);
                            self.overwriteFiles = newArr;
                            self.renderOverwriteCards();
                            self.saveOverwriteSort();
                        }
                    }

                    list.querySelectorAll('.overwrite-drag-line').forEach(function(l){ l.remove(); });

                    self.overwriteDrag.dragging = null;
                    self.overwriteDrag.startIndex = null;
                    self.overwriteDrag.isTouchDragging = false;

                    if (self.overwriteDrag.touchDraggingMoved) {
                        e.preventDefault();
                        e.stopPropagation();
                    }
                }

                self.overwriteDrag.touchDraggingMoved = false;
                self.overwriteDrag.touchDraggingCard = null;
                self.overwriteDrag.startTouch = null;
            });

            card.addEventListener('touchcancel', function() {
                if (self.overwriteDrag.touchTimer) {
                    clearTimeout(self.overwriteDrag.touchTimer);
                    self.overwriteDrag.touchTimer = null;
                }

                if (self.overwriteDrag.isTouchDragging) {
                    card.classList.remove('dragging');
                    list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
                    self.overwriteDrag.dragging = null;
                    self.overwriteDrag.startIndex = null;
                    self.saveOverwriteSort();
                }

                self.overwriteDrag.isTouchDragging = false;
                self.overwriteDrag.touchDraggingMoved = false;
                self.overwriteDrag.touchDraggingCard = null;
                self.overwriteDrag.startTouch = null;
            });

            list.appendChild(card);
        });

        list.addEventListener('dragover', function(e) {
            e.preventDefault();

            const cards = Array.from(list.querySelectorAll('.sub-card.overwrite-item'));
            if (cards.length === 0) return;

            const realCards = cards.slice(2);
            if (realCards.length === 0) return;

            const x = e.clientX;

            let insertBefore = null;
            for (let i = 0; i < realCards.length; i++) {
                const rect = realCards[i].getBoundingClientRect();
                if (x < rect.left + rect.width / 2) {
                    insertBefore = realCards[i];
                    break;
                }
            }

            const line = document.createElement('div');
            line.className = 'overwrite-drag-line';
            if (insertBefore) {
                list.insertBefore(line, insertBefore);
            } else {
                list.appendChild(line);
            }

            const lines = Array.from(list.querySelectorAll('.overwrite-drag-line'));
            lines.forEach(l => {
                if (l !== line) l.remove();
            });
        });

        list.addEventListener('dragleave', function(e) {
            if (!list.contains(e.relatedTarget)) {
                list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
            }
        });

        list.addEventListener('drop', function(e) {
            e.preventDefault();
            const dragging = ConfigEditor.overwriteDrag.dragging;
            if (!dragging) return;
            const from = parseInt(dragging.dataset.index);
            const lines = Array.from(list.querySelectorAll('.overwrite-drag-line'));
            if (lines.length === 0) return;
            const line = lines[0];
            const allCards = Array.from(list.querySelectorAll('.sub-card.overwrite-item'));
            const fileCards = allCards.filter(c => typeof c.dataset.index !== 'undefined' && c.dataset.index !== 'custom');
            let nextNode = line.nextSibling;
            while (nextNode && !(nextNode.classList && nextNode.classList.contains('sub-card') && typeof nextNode.dataset.index !== 'undefined' && nextNode.dataset.index !== 'custom')) {
                nextNode = nextNode.nextSibling;
            }
            let to = fileCards.indexOf(nextNode);
            if (to === -1) to = fileCards.length;
            if (from === to || from + 1 === to) {
                list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
                ConfigEditor.saveOverwriteSort();
                return;
            }
            const arr = ConfigEditor.overwriteFiles.filter((f, i) => f.name !== 'openclash_custom_overwrite.sh');
            const customFile = ConfigEditor.overwriteFiles.find(f => f.name === 'openclash_custom_overwrite.sh');
            const moved = arr.splice(from, 1)[0];
            arr.splice(to > from ? to - 1 : to, 0, moved);
            let newArr = [];
            if (customFile) newArr.push(customFile);
            newArr = newArr.concat(arr);
            ConfigEditor.overwriteFiles = newArr;
            ConfigEditor.renderOverwriteCards();
            ConfigEditor.saveOverwriteSort();
            list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());
        });

        list.addEventListener('touchmove', function(e) {
            if (!ConfigEditor.overwriteDrag.isTouchDragging) return;
            const touch = e.touches[0];
            const cards = Array.from(list.querySelectorAll('.sub-card.overwrite-item'));
            if (cards.length === 0) return;
            const realCards = cards.slice(2);
            if (realCards.length === 0) return;

            let insertBefore = null;
            for (let i = 0; i < realCards.length; i++) {
                const rect = realCards[i].getBoundingClientRect();
                if (touch.clientX < rect.left + rect.width / 2) {
                    insertBefore = realCards[i];
                    break;
                }
            }

            list.querySelectorAll('.overwrite-drag-line').forEach(line => line.remove());

            if (insertBefore) {
                const line = document.createElement('div');
                line.className = 'overwrite-drag-line';
                list.insertBefore(line, insertBefore);
            } else {
                const line = document.createElement('div');
                line.className = 'overwrite-drag-line';
                list.appendChild(line);
            }
        });

        list.addEventListener('touchend', function(e) {
            var dragging = ConfigEditor.overwriteDrag.dragging;
            if (!dragging) {
                list.querySelectorAll('.overwrite-drag-line').forEach(function(line){ line.remove(); });
                return;
            }
            var from = parseInt(dragging.dataset.index);

            var line = list.querySelector('.overwrite-drag-line');
            if (!line) {
                ConfigEditor.overwriteDrag.dragging = null;
                ConfigEditor.overwriteDrag.startIndex = null;
                ConfigEditor.overwriteDrag.isTouchDragging = false;
                return;
            }

            var allCards = Array.from(list.querySelectorAll('.sub-card.overwrite-item'));
            var fileCards = allCards.filter(function(c){ return c.dataset && typeof c.dataset.index !== 'undefined' && c.dataset.index !== 'custom'; });

            var lineRect = line.getBoundingClientRect();
            var lineCenterX = lineRect.left + lineRect.width / 2;

            var to = fileCards.length;
            for (var i = 0; i < fileCards.length; i++) {
                var rect = fileCards[i].getBoundingClientRect();
                var cardMid = rect.left + rect.width / 2;
                if (lineCenterX < cardMid) {
                    to = i;
                    break;
                }
            }

            if (!(from === to || from + 1 === to)) {
                var arr = ConfigEditor.overwriteFiles.filter(function(f){ return f.name !== 'openclash_custom_overwrite.sh'; });
                var customFile = ConfigEditor.overwriteFiles.find(function(f){ return f.name === 'openclash_custom_overwrite.sh'; });
                var moved = arr.splice(from, 1)[0];
                arr.splice(to > from ? to - 1 : to, 0, moved);
                var newArr = [];
                if (customFile) newArr.push(customFile);
                newArr = newArr.concat(arr);
                ConfigEditor.overwriteFiles = newArr;
                ConfigEditor.renderOverwriteCards();
                ConfigEditor.saveOverwriteSort();
            } else {
                ConfigEditor.saveOverwriteSort();
            }

            list.querySelectorAll('.overwrite-drag-line').forEach(function(l){ l.remove(); });
            ConfigEditor.overwriteDrag.dragging = null;
            ConfigEditor.overwriteDrag.startIndex = null;
            ConfigEditor.overwriteDrag.isTouchDragging = false;
        });

        bar.style.display = self.isOverwrite ? 'block' : 'none';
    },

    saveOverwriteSort: function() {
        var self = this;
        var reqs = [];
        this.overwriteFiles.forEach(function(file, idx) {
            var name = file.name || (file.path ? file.path.split('/').pop() : '');
            if (name === 'openclash_custom_overwrite.sh') return;
            var sub = self.overwriteSubInfo[name] || {};
            var formData = new FormData();
            formData.append('filename', name);
            formData.append('type', sub.type || 'file');
            formData.append('url', sub.url || '');
            formData.append('update_days', sub.update_days || '');
            formData.append('update_hour', sub.update_hour || '');
            formData.append('param', sub.param || '');
            formData.append('config', self.parseOverwriteConfigValue(sub.config).join('\n'));
            formData.append('order', idx);
            reqs.push(fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
                method: 'POST',
                body: formData
            }));
        });
        Promise.all(reqs).then(function() {
            self.loadOverwriteSubInfo();
        });
    },

    getOverwriteConfigFiles: function() {
        var rawList = [];
        if (window.configFiles && Array.isArray(window.configFiles)) {
            rawList = window.configFiles;
        } else if (window.ConfigFileManager && Array.isArray(window.ConfigFileManager.configList)) {
            rawList = window.ConfigFileManager.configList;
        }

        return rawList.map(function(file) {
            if (typeof file === 'string') {
                return {
                    name: file,
                    path: file
                };
            }
            return {
                name: file.name || file.filename || file.path || '',
                path: file.path || file.filepath || file.name || ''
            };
        }).filter(function(file) {
            return !!file.path;
        });
    },

    parseOverwriteConfigValue: function(value) {
        if (!value) return [];
        if (Array.isArray(value)) {
            return value.filter(function(item) { return !!item; });
        }
        return String(value).split(/[\r\n,;]+/).map(function(item) {
            return item.trim();
        }).filter(function(item) {
            return !!item;
        });
    },

    renderOverwriteConfigDropdown: function(configValue, dropdownId) {
        var files = this.getOverwriteConfigFiles();
        var selected = this.parseOverwriteConfigValue(configValue);
        var selectedMap = {};
        selected.forEach(function(item) {
            selectedMap[item] = true;
        });

        if (!selected.length) {
            selectedMap['all'] = true;
        }

        var allChecked = !!selectedMap['all'];

        var allOptionHtml = `
            <label class="overwrite-config-option${allChecked ? ' selected' : ''}" data-path="all" data-all-option="1">
                <span class="overwrite-config-option-left">
                    <input type="checkbox" value="all"${allChecked ? ' checked' : ''}>
                    <span class="overwrite-config-option-name" title="<%:Use For All Config File%>"><%:Use For All Config File%></span>
                </span>
                <span class="overwrite-config-option-state">${allChecked ? '✓' : ''}</span>
            </label>
        `;

        var fileOptionsHtml = files.length ? files.map(function(file) {
            var checked = (!allChecked && (selectedMap[file.path] || selectedMap[file.name])) ? ' checked' : '';
            var selectedClass = checked ? ' selected' : '';
            var disabledClass = allChecked ? ' disabled-by-all' : '';
            var stateText = checked ? '✓' : '';
            var disabled = allChecked;
            return `
                <label class="overwrite-config-option${selectedClass}${disabledClass}" data-path="${file.path}">
                    <span class="overwrite-config-option-left">
                        <input type="checkbox" value="${file.path}"${checked}${disabled ? ' disabled' : ''}>
                        <span class="overwrite-config-option-name" title="${file.name}">${file.name}</span>
                    </span>
                    <span class="overwrite-config-option-state">${stateText}</span>
                </label>
            `;
        }).join('') : '<div class="overwrite-config-option"><span class="overwrite-config-option-name"><%:No config files found%></span></div>';

        var optionsHtml = allOptionHtml + fileOptionsHtml;

        return `
            <div class="overwrite-config-dropdown form-select-wrapper" id="${dropdownId}">
                <button type="button" class="overwrite-config-dropdown-btn form-select">
                    <span class="overwrite-config-dropdown-text"><%:Use For All Config File%></span>
                    <span class="overwrite-config-dropdown-arrow"></span>
                </button>
                <div class="overwrite-config-dropdown-panel">
                    ${optionsHtml}
                </div>
            </div>
        `;
    },

    updateOverwriteConfigDropdownLabel: function(dropdown) {
        if (!dropdown) return;
        var textEl = dropdown.querySelector('.overwrite-config-dropdown-text');
        if (!textEl) return;
        var checked = dropdown.querySelectorAll('.overwrite-config-option input[type="checkbox"]:checked');
        if (!checked.length) {
            textEl.textContent = '<%:Use For All Config File%>';
            return;
        }
        if (checked.length === 1) {
            var oneName = checked[0].closest('.overwrite-config-option').querySelector('.overwrite-config-option-name');
            textEl.textContent = oneName ? oneName.textContent : '<%:1 file selected%>';
            return;
        }
        textEl.textContent = checked.length + ' <%:files selected%>';
    },

    bindOverwriteConfigDropdown: function(container) {
        if (!container || container.dataset.inited === '1') return;
        container.dataset.inited = '1';

        var self = this;
        var btn = container.querySelector('.overwrite-config-dropdown-btn');
        var panel = container.querySelector('.overwrite-config-dropdown-panel');

        function syncAllExclusiveState() {
            var allInput = container.querySelector('.overwrite-config-option input[type="checkbox"][value="all"]');
            var allOption = allInput ? allInput.closest('.overwrite-config-option') : null;
            var allState = allOption ? allOption.querySelector('.overwrite-config-option-state') : null;
            var allSelected = !!(allInput && allInput.checked);

            if (allOption) {
                allOption.classList.toggle('selected', allSelected);
            }
            if (allState) {
                allState.textContent = allSelected ? '✓' : '';
            }

            container.querySelectorAll('.overwrite-config-option input[type="checkbox"]').forEach(function(input) {
                if (input.value === 'all') return;
                var option = input.closest('.overwrite-config-option');
                var state = option ? option.querySelector('.overwrite-config-option-state') : null;
                if (allSelected) {
                    input.checked = false;
                }
                input.disabled = allSelected;
                if (option) {
                    option.classList.toggle('selected', input.checked);
                    option.classList.toggle('disabled-by-all', allSelected);
                    option.setAttribute('aria-disabled', allSelected ? 'true' : 'false');
                }
                if (state) {
                    state.textContent = input.checked ? '✓' : '';
                }
            });
        }

        this.updateOverwriteConfigDropdownLabel(container);
        syncAllExclusiveState();
        this.updateOverwriteConfigDropdownLabel(container);

        if (btn && !btn.disabled) {
            btn.addEventListener('click', function(e) {
                e.stopPropagation();
                container.classList.toggle('open');
            });
        }

        container.querySelectorAll('.overwrite-config-option input[type="checkbox"]').forEach(function(input) {
            input.addEventListener('change', function() {
                if (input.value === 'all') {
                    syncAllExclusiveState();
                } else if (input.checked) {
                    var allInput = container.querySelector('.overwrite-config-option input[type="checkbox"][value="all"]');
                    if (allInput) {
                        allInput.checked = false;
                    }
                    syncAllExclusiveState();
                }

                var option = input.closest('.overwrite-config-option');
                var state = option ? option.querySelector('.overwrite-config-option-state') : null;
                if (option) {
                    option.classList.toggle('selected', input.checked);
                }
                if (state) {
                    state.textContent = input.checked ? '✓' : '';
                }
                self.updateOverwriteConfigDropdownLabel(container);
            });
        });

        if (panel) {
            panel.addEventListener('click', function(e) {
                e.stopPropagation();
            });
        }

        document.addEventListener('click', function(e) {
            if (!container.contains(e.target)) {
                container.classList.remove('open');
            }
        });
    },

    addOverwriteParamRow: function(container, name, value) {
        var row = document.createElement('div');
        row.className = 'form-row overwrite-param-row';
        row.style.cssText = 'align-items: center; gap: 6px; margin-bottom: 6px;';

        var nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.className = 'form-input';
        nameInput.placeholder = 'Key';
        nameInput.value = name || '';
        nameInput.style.cssText = 'flex: 1; height: 32px;';

        var valueInput = document.createElement('input');
        valueInput.type = 'text';
        valueInput.className = 'form-input';
        valueInput.placeholder = 'Value';
        valueInput.value = value || '';
        valueInput.style.cssText = 'flex: 1.5; height: 32px;';

        var removeBtn = document.createElement('button');
        removeBtn.type = 'button';
        removeBtn.className = 'icon-btn';
        removeBtn.title = '<%:Remove%>';
        removeBtn.style.cssText = 'flex-shrink: 0;';
        removeBtn.innerHTML = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
        removeBtn.addEventListener('click', function() {
            row.parentNode.removeChild(row);
            if (container.children.length === 0) {
                container.style.display = 'none';
            }
        });

        row.appendChild(nameInput);
        row.appendChild(valueInput);
        row.appendChild(removeBtn);
        container.appendChild(row);
        container.style.display = 'block';
    },

    buildOverwriteParamRows: function(container, paramStr) {
        if (!paramStr) return;
        var pairs = paramStr.split(';');
        for (var i = 0; i < pairs.length; i++) {
            var pair = pairs[i].trim();
            if (pair) {
                var eqIdx = pair.indexOf('=');
                var name = eqIdx > 0 ? pair.substring(0, eqIdx).trim() : pair;
                var value = eqIdx > 0 ? pair.substring(eqIdx + 1).trim() : '';
                this.addOverwriteParamRow(container, name, value);
            }
        }
    },

    collectOverwriteParams: function(container) {
        var rows = container.querySelectorAll('.overwrite-param-row');
        var params = [];
        for (var i = 0; i < rows.length; i++) {
            var inputs = rows[i].querySelectorAll('input');
            var name = inputs[0].value.trim();
            var value = inputs[1].value.trim();
            if (name) {
                params.push(name + '=' + value);
            }
        }
        return params.join(';');
    },

    getOverwriteConfigSelection: function(root, dropdownId) {
        var dropdown = root.querySelector('#' + dropdownId);
        if (!dropdown) return [];
        var selected = Array.from(dropdown.querySelectorAll('.overwrite-config-option input[type="checkbox"]:checked')).map(function(input) {
            return input.value;
        }).filter(function(value) {
            return !!value;
        });
        if (selected.indexOf('all') !== -1) {
            return ['all'];
        }
        return selected;
    },

    renderOverwriteSubForm: function(options) {
        var name = options.name || '';
        var sub = options.sub || {};
        var readonly = !!options.readonly;
        var showTabs = !!options.showTabs;
        var activeTab = options.activeTab || 'file';
        var fileConfigDropdown = this.renderOverwriteConfigDropdown(sub.config || '', 'overwrite-upload-config-dropdown');
        var subscribeConfigDropdown = this.renderOverwriteConfigDropdown(sub.config || '', 'overwrite-subscribe-config-dropdown');

        var tabsHtml = showTabs ? `
            <div class="upload-mode-selector">
                <div class="mode-tabs">
                    <button type="button" class="mode-tab${activeTab==='file'?' active':''}" id="overwrite-upload-mode-file" data-mode="file">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                            <polyline points="17,11 12,6 7,11"></polyline>
                            <line x1="12" y1="18" x2="12" y2="6"></line>
                        </svg>
                        <%:Upload File%>
                    </button>
                    <button type="button" class="mode-tab${activeTab==='subscribe'?' active':''}" id="overwrite-upload-mode-subscribe" data-mode="subscribe">
                        <svg width="15" height="15" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                            <path d="M26.2401 16.373L17.1001 7.23303C14.4388 4.57168 10.0653 4.6303 7.33158 7.36397C4.59791 10.0976 4.53929 14.4712 7.20064 17.1325L15.1359 25.0678" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
                            <path d="M32.9027 23.0031L40.838 30.9384C43.4994 33.5998 43.4407 37.9733 40.7071 40.707C37.9734 43.4407 33.5999 43.4993 30.9385 40.8379L21.7985 31.6979" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
                            <path d="M26.1093 26.1416C28.843 23.4079 28.9016 19.0344 26.2403 16.373" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
                            <path d="M21.7989 21.7984C19.0652 24.5321 19.0066 28.9056 21.6679 31.5669" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
                        </svg>
                        <%:Subscribe Link%>
                    </button>
                </div>
            </div>
        ` : '';

        var fileContent = `
            <form id="overwrite-upload-form-file" style="display:${activeTab==='file'?'block':'none'};">
                <div class="form-group" style="margin-bottom:20px;">
                    <label for="overwrite-upload-config"><%:Config File%>:</label>
                    ${fileConfigDropdown}
                </div>
                <div class="upload-zone" id="overwrite-upload-zone">
                    <div class="upload-icon">
                        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
                            <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
                            <polyline points="17,11 12,6 7,11"></polyline>
                            <line x1="12" y1="18" x2="12" y2="6"></line>
                        </svg>
                    </div>
                    <div class="upload-text">
                        <p class="upload-primary"><%:Click to select file or drag and drop%></p>
                        <p class="upload-secondary"><%:Support txt,conf files, max size 10MB%></p>
                    </div>
                    <input type="file" id="overwrite-upload-file-input" accept=".txt,.conf,*" style="display: none;">
                </div>
                <div class="filename-input-container" style="margin-top:20px;">
                    <label for="overwrite-upload-filename-input"><%:File Name%>:</label>
                    <input type="text" id="overwrite-upload-filename-input" placeholder="<%:Please enter a filename%>" class="form-input" value="${name}" ${readonly ? 'readonly' : ''}/>
                </div>
            </form>
        `;

        var subscribeContent = `
            <form id="overwrite-upload-form-subscribe" class="subscribe-form">
                <div class="form-group">
                    <label><%:File Name%>:</label>
                    <input type="text" class="form-input" name="filename" id="overwrite-subscribe-filename" value="${name}" ${readonly ? 'readonly' : ''} placeholder="<%:Please enter a filename%>">
                </div>
                <div class="form-group">
                    <label for="overwrite-subscribe-config"><%:Config File%>:</label>
                    ${subscribeConfigDropdown}
                </div>
                <div class="form-group">
                    <label><%:Type%>:</label>
                    <div class="form-select-wrapper">
                        <select class="form-select" name="type" id="overwrite-subscribe-type" ${readonly ? 'disabled' : ''}>
                            <option value="file" ${(sub.type === 'file' || !sub.type) ? 'selected' : ''}>file</option>
                            <option value="http" ${sub.type === 'http' ? 'selected' : ''}>http</option>
                        </select>
                    </div>
                </div>
                <div class="form-group" id="overwrite-subscribe-url-group" style="display:${sub.type === 'http' ? 'block' : 'none'};">
                    <label><%:Subscription URL%>:</label>
                    <input type="text" class="form-input" name="url" id="overwrite-subscribe-url" value="${sub.url||''}">
                </div>
                <div class="form-group" id="overwrite-subscribe-update-group" style="display:${sub.type === 'http' ? 'block' : 'none'};">
                    <label><%:Update Time%>:</label>
                    <div class="update-row">
                        <div class="form-select-wrapper">
                            <select class="form-select" name="update_days" id="overwrite-subscribe-update-days">
                                <option value="off" ${sub.update_days==='off'?'selected':''}><%:OFF%></option>
                                <option value="*" ${sub.update_days==='*'?'selected':''}><%:Every Day%></option>
                                <option value="1" ${sub.update_days==='1'?'selected':''}><%:Every Monday%></option>
                                <option value="2" ${sub.update_days==='2'?'selected':''}><%:Every Tuesday%></option>
                                <option value="3" ${sub.update_days==='3'?'selected':''}><%:Every Wednesday%></option>
                                <option value="4" ${sub.update_days==='4'?'selected':''}><%:Every Thursday%></option>
                                <option value="5" ${sub.update_days==='5'?'selected':''}><%:Every Friday%></option>
                                <option value="6" ${sub.update_days==='6'?'selected':''}><%:Every Saturday%></option>
                                <option value="0" ${sub.update_days==='0'?'selected':''}><%:Every Sunday%></option>
                            </select>
                        </div>
                        <div class="form-select-wrapper">
                            <select class="form-select" name="update_hour" id="overwrite-subscribe-update-hour">
                                <option value="off" ${sub.update_hour==='off'?'selected':''}><%:OFF%></option>
                                ${[...Array(24).keys()].map(h=>`<option value="${h}" ${sub.update_hour==h?'selected':''}>${h}:00</option>`).join('')}
                            </select>
                        </div>
                    </div>
                </div>
                <div class="form-group" id="overwrite-subscribe-param-group" style="display:'block';">
                    <label><%:Environment variable%>:</label>
                    <div class="form-help" style="margin-bottom: unset;"><%:Set environment variables for the overwrite script%></div>
                    <div id="overwrite-subscribe-param-container" style="display:none;"></div>
                    <button type="button" id="overwrite-subscribe-param-add" class="btn upload-btn" style="align-self: flex-start; margin-top: 4px; padding: 4px 12px; font-size: 13px; border: 1px solid var(--primary-color); border-radius: var(--radius-sm); cursor: pointer;">+ <%:Add Param%></button>
                </div>
                <div class="upload-progress" id="overwrite-upload-progress" style="display:none;">
                    <div class="progress-bar">
                        <div class="progress-fill" id="overwrite-progress-fill" style="width:0%;"></div>
                    </div>
                    <div class="progress-text" id="overwrite-progress-text">0%</div>
                </div>
            </form>
        `;

        return `
            ${tabsHtml}
            <div class="config-upload-content" id="overwrite-upload-content-file" style="${activeTab==='file'?'':'display:none;'}">
                ${fileContent}
            </div>
            <div class="config-upload-content" id="overwrite-upload-content-subscribe" style="${activeTab==='subscribe'?'':'display:none;'}">
                ${subscribeContent}
            </div>
        `;
    },

    showAddOverwritemodel: function() {
        var self = this;
        if (document.getElementById('overwrite-add-model')) return;

        var ocDiv = document.createElement('div');
        ocDiv.className = 'oc';
		if (document.documentElement.getAttribute('data-darkmode') === 'true') {
			ocDiv.setAttribute('data-darkmode', 'true');
		}

        var overlay = document.createElement('div');
        overlay.className = 'config-upload-model-overlay show';
        overlay.style.zIndex = 10001;
        overlay.id = 'overwrite-add-model';

        var model = document.createElement('div');
        model.className = 'config-upload-model';

        model.innerHTML = `
            <div class="config-upload-header">
                <div class="config-upload-title">
                    <span><%:Add Overwrite Module%></span>
                </div>
                <div class="config-upload-actions">
                    <button type="button" class="icon-btn" id="overwrite-add-close" title="<%:Close%>">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <line x1="18" y1="6" x2="6" y2="18"></line>
                            <line x1="6" y1="6" x2="18" y2="18"></line>
                        </svg>
                    </button>
                </div>
            </div>
            <div class="config-upload-content">
                ${self.renderOverwriteSubForm({name:'', sub:{}, readonly:false, showTabs:true, activeTab:'file'})}
            </div>
            <div class="config-upload-footer">
                <div class="config-upload-status">
                    <span id="overwrite-upload-status-text"><%:Ready to add file%></span>
                </div>
                <div class="config-upload-buttons">
                    <button type="button" class="btn cancel-btn" id="overwrite-upload-cancel"><%:Cancel%></button>
                    <button type="button" class="btn upload-btn" id="overwrite-upload-submit"><%:Add%></button>
                </div>
            </div>
        `;

        overlay.appendChild(model);
        ocDiv.appendChild(overlay);
        document.body.appendChild(ocDiv);

        this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-upload-config-dropdown'));
        this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-subscribe-config-dropdown'));

        var tabFile = model.querySelector('#overwrite-upload-mode-file');
        var tabSub = model.querySelector('#overwrite-upload-mode-subscribe');
        var contentFile = model.querySelector('#overwrite-upload-content-file');
        var contentSub = model.querySelector('#overwrite-upload-content-subscribe');
        tabFile.onclick = function() {
            tabFile.classList.add('active');
            tabSub.classList.remove('active');
            contentFile.style.display = 'block';
            contentSub.style.display = 'none';
        };
        tabSub.onclick = function() {
            tabFile.classList.remove('active');
            tabSub.classList.add('active');
            contentFile.style.display = 'none';
            contentSub.style.display = 'block';
        };

        var typeSelect = model.querySelector('#overwrite-subscribe-type');
        var urlGroup = model.querySelector('#overwrite-subscribe-url-group');
        var updateGroup = model.querySelector('#overwrite-subscribe-update-group');
        if (typeSelect) {
            if (typeSelect.value === 'http') {
                urlGroup.style.display = 'block';
                updateGroup.style.display = 'block';
            } else {
                urlGroup.style.display = 'none';
                updateGroup.style.display = 'none';
            }
            typeSelect.addEventListener('change', function() {
                if (typeSelect.value === 'http') {
                    urlGroup.style.display = 'block';
                    updateGroup.style.display = 'block';
                } else {
                    urlGroup.style.display = 'none';
                    updateGroup.style.display = 'none';
                }
            });
        }

        var urlInput = model.querySelector('#overwrite-subscribe-url');
        var filenameInputSub = model.querySelector('#overwrite-subscribe-filename');
        if (urlInput && filenameInputSub) {
            urlInput.addEventListener('input', function() {
                if (!filenameInputSub.value.trim() && urlInput.value.trim()) {
                    try {
                        var url = urlInput.value.trim();
                        var name = url.split('?')[0].split('/').pop();
                        if (name && /^[\w\.\-\_]+$/.test(name)) {
                            filenameInputSub.value = name;
                        }
                    } catch (e) {}
                }
            });
        }

        model.querySelector('#overwrite-add-close').onclick = function() {
            document.body.removeChild(ocDiv);
        };

        model.querySelector('#overwrite-upload-cancel').onclick = function() {
            document.body.removeChild(ocDiv);
        };

        var uploadZone = model.querySelector('#overwrite-upload-zone');
        var fileInput = model.querySelector('#overwrite-upload-file-input');
        var filenameInput = model.querySelector('#overwrite-upload-filename-input');
        var statusText = model.querySelector('#overwrite-upload-status-text');
        var selectedFile = null;

        uploadZone.onclick = function() {
            fileInput.click();
        };
        fileInput.onchange = function(e) {
            if (e.target.files.length > 0) {
                selectedFile = e.target.files[0];
                uploadZone.classList.add('has-file');
                uploadZone.querySelector('.upload-primary').textContent = '<%:File selected:%> ' + selectedFile.name;
                uploadZone.querySelector('.upload-secondary').textContent = '<%:Size:%> ' + (selectedFile.size/1024/1024).toFixed(2) + ' MB';
                filenameInput.value = selectedFile.name;
            }
        };

        var submitBtn = model.querySelector('#overwrite-upload-submit');
        function validateAddOverwriteForm() {
            if (tabFile.classList.contains('active')) {
                const filename = filenameInput.value.trim();
                submitBtn.disabled = !(selectedFile && filename && filename !== 'openclash_custom_overwrite.sh');
            } else {
                const filename = model.querySelector('#overwrite-subscribe-filename').value.trim();
                const type = model.querySelector('#overwrite-subscribe-type').value;
                const url = model.querySelector('#overwrite-subscribe-url').value.trim();
                let valid = !!filename && filename !== 'openclash_custom_overwrite.sh';
                if (type === 'http') {
                    valid = valid && !!url && /^https?:\/\/[^ \n|]+$/.test(url);
                }
                submitBtn.disabled = !valid;
            }
        }
        validateAddOverwriteForm();

        filenameInput.addEventListener('input', validateAddOverwriteForm);

        if (model.querySelector('#overwrite-subscribe-filename')) {
            model.querySelector('#overwrite-subscribe-filename').addEventListener('input', validateAddOverwriteForm);
        }
        var paramContainer = model.querySelector('#overwrite-subscribe-param-container');
        if (paramContainer) {
            self.buildOverwriteParamRows(paramContainer, '');
            model.querySelector('#overwrite-subscribe-param-add').addEventListener('click', function() {
                self.addOverwriteParamRow(paramContainer, '', '');
            });
        }

        if (model.querySelector('#overwrite-subscribe-type')) {
            model.querySelector('#overwrite-subscribe-type').addEventListener('change', validateAddOverwriteForm);
        }
        if (model.querySelector('#overwrite-subscribe-url')) {
            model.querySelector('#overwrite-subscribe-url').addEventListener('input', validateAddOverwriteForm);
        }
        tabFile.addEventListener('click', function() {
            setTimeout(validateAddOverwriteForm, 0);
        });
        tabSub.addEventListener('click', function() {
            setTimeout(validateAddOverwriteForm, 0);
        });


        fileInput.addEventListener('change', validateAddOverwriteForm);

        model.querySelector('#overwrite-upload-submit').onclick = function() {
            if (tabFile.classList.contains('active')) {
                var filename = filenameInput.value.trim();
                if (!filename) {
                    alert('<%:Please enter a filename%>');
                    return;
                }
                if (filename === 'openclash_custom_overwrite.sh') {
                    alert('<%:openclash_custom_overwrite.sh already exists and cannot be added again%>');
                    return;
                }
                if (selectedFile) {
                    var reader = new FileReader();
                    reader.onload = function(e) {
                        var fileContent = e.target.result;
                        var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-upload-config-dropdown');
                        var formData = new FormData();
                        formData.append('filename', filename);
                        formData.append('config_file', fileContent);
                        formData.append('order', self.overwriteFileCardCount + 1);
                        formData.append('enable', '0');
                        formData.append('config', selectedConfigPaths.join('\n'));

                        fetch('/cgi-bin/luci/admin/services/openclash/upload_overwrite', {
                            method: 'POST',
                            body: formData
                        }).then(r=>r.json()).then(function(data){
                            if (data.status === 'success') {
                                statusText.textContent = '<%:Upload successful%>';
                                document.body.removeChild(ocDiv);
                                self.loadOverwriteFiles();
                                setTimeout(function() {
                                    self.currentConfigFile = '/etc/openclash/overwrite/' + filename;
                                    self.isOverwrite = true;
                                    self.showOverwrite(self.currentConfigFile);
                                }, 300);
                            } else {
                                statusText.textContent = '<%:Upload failed:%> ' + (data.message || '');
                            }
                        });
                    };
                    reader.onerror = function() {
                        statusText.textContent = '<%:Failed to read file%>';
                    };
                    reader.readAsText(selectedFile, 'UTF-8');
                } else {
                    alert('<%:No Specify Upload File%>');
                    return;
                }
            } else {
                var form = model.querySelector('#overwrite-upload-form-subscribe');
                var filename = form.querySelector('#overwrite-subscribe-filename').value.trim();
                var url = form.querySelector('#overwrite-subscribe-url').value.trim();
                var update_days = form.querySelector('#overwrite-subscribe-update-days').value;
                var update_hour = form.querySelector('#overwrite-subscribe-update-hour').value;
                var type = form.querySelector('#overwrite-subscribe-type').value;
                var param = self.collectOverwriteParams(form.querySelector('#overwrite-subscribe-param-container'));
                var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-subscribe-config-dropdown');
                if (!filename) {
                    alert('<%:Please enter a filename%>');
                    return;
                }
                if (filename === 'openclash_custom_overwrite.sh') {
                    alert('<%:openclash_custom_overwrite.sh already exists and cannot be added again%>');
                    return;
                }
                if (type === 'http' && !url) {
                    alert('<%:Please enter subscription URL%>');
                    return;
                }
                if (type === 'http' && !/^https?:\/\/[^ \n|]+$/.test(url)) {
                    alert('<%:Invalid subscription URL format, only single HTTP/HTTPS link is supported%>');
                    return;
                }
                var formData = new FormData();
                formData.append('filename', filename);
                formData.append('type', type);
                formData.append('param', param);
                formData.append('order', self.overwriteFileCardCount + 1);
                formData.append('enable', '0');
                formData.append('config', selectedConfigPaths.join('\n'));
                if (type === 'http') {
                    formData.append('url', url);
                    formData.append('update_days', update_days);
                    formData.append('update_hour', update_hour);
                }
                var progressContainer = form.querySelector('#overwrite-upload-progress');
                var progressFill = form.querySelector('#overwrite-progress-fill');
                var progressText = form.querySelector('#overwrite-progress-text');
                if (type === 'http') {
                    statusText.textContent = '<%:Downloading subscription content...%>';
                    progressContainer.style.display = 'block';
                    var progress = 0;
                    var interval = setInterval(function() {
                        progress = Math.min(progress + Math.random() * 20, 90);
                        progressFill.style.width = progress + '%';
                        progressText.textContent = '<%:Downloading...%> ' + Math.floor(progress) + '%';
                    }, 200);
                }
                fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
                    method: 'POST',
                    body: formData
                }).then(r=>r.json()).then(function(data){
                    if (type === 'http') {
                        clearInterval(interval);
                        if (data.status !== 'success') {
                            progressFill.style.width = '0%';
                            progressText.textContent = '<%:Download failed%> 0%';
                        } else {
                            progressFill.style.width = '100%';
                            progressText.textContent = '<%:Download completed%> 100%';
                        }
                    }
                    if (data.status === 'success') {
                        statusText.textContent = '<%:Subscription added successfully%>';
                    } else {
                        statusText.textContent = '<%:Failed to save subscription info:%> ' + (data.message || '');
                    }
                    setTimeout(function() {
                        document.body.removeChild(ocDiv);
                        self.loadOverwriteFiles();
                        setTimeout(function() {
                            self.currentConfigFile = '/etc/openclash/overwrite/' + filename;
                            self.isOverwrite = true;
                            self.showOverwrite(self.currentConfigFile);
                        }, 300);
                    }, 800);
                });
            }
        };
    },

    showOverwriteSubmodel: function(name, sub, filePath) {
        var self = this;
        if (document.getElementById('overwrite-sub-model')) return;

        var ocDiv = document.createElement('div');
        ocDiv.className = 'oc';
		if (document.documentElement.getAttribute('data-darkmode') === 'true') {
			ocDiv.setAttribute('data-darkmode', 'true');
		}

        var overlay = document.createElement('div');
        overlay.className = 'config-upload-model-overlay show';
        overlay.style.zIndex = 10001;
        overlay.id = 'overwrite-sub-model';

        var model = document.createElement('div');
        model.className = 'config-upload-model';

        model.innerHTML = `
            <div class="config-upload-header">
                <div class="config-upload-title">
                    <span><%:Overwrite Module Edit%></span>
                </div>
                <div class="config-upload-actions">
                    <button type="button" class="icon-btn" id="overwrite-sub-close" title="<%:Close%>">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <line x1="18" y1="6" x2="6" y2="18"></line>
                            <line x1="6" y1="6" x2="18" y2="18"></line>
                        </svg>
                    </button>
                </div>
            </div>
            <div class="config-upload-content">
                ${self.renderOverwriteSubForm({name:name, sub:sub, readonly:false, showTabs:false, activeTab:'subscribe'})}
            </div>
            <div class="config-upload-footer">
                <div class="config-upload-status">
                    <span id="overwrite-edit-status-text"><%:Ready to edit%></span>
                </div>
                <div class="config-upload-buttons">
                    <button type="button" class="btn cancel-btn" id="overwrite-edit-cancel"><%:Cancel%></button>
                    <button type="button" class="btn upload-btn" id="overwrite-edit-submit"><%:Save%></button>
                </div>
            </div>
        `;

        overlay.appendChild(model);
        ocDiv.appendChild(overlay);
        document.body.appendChild(ocDiv);

        this.bindOverwriteConfigDropdown(model.querySelector('#overwrite-subscribe-config-dropdown'));

        var paramContainer = model.querySelector('#overwrite-subscribe-param-container');
        if (paramContainer && sub && sub.param) {
            self.buildOverwriteParamRows(paramContainer, sub.param);
        }
        var paramAddBtn = model.querySelector('#overwrite-subscribe-param-add');
        if (paramAddBtn) {
            paramAddBtn.addEventListener('click', function() {
                self.addOverwriteParamRow(paramContainer, '', '');
            });
        }

        var typeSelect = model.querySelector('#overwrite-subscribe-type');
        var urlGroup = model.querySelector('#overwrite-subscribe-url-group');
        var updateGroup = model.querySelector('#overwrite-subscribe-update-group');
        if (typeSelect) {
            if (typeSelect.value === 'http') {
                urlGroup.style.display = 'block';
                updateGroup.style.display = 'block';
            } else {
                urlGroup.style.display = 'none';
                updateGroup.style.display = 'none';
            }
            typeSelect.addEventListener('change', function() {
                if (typeSelect.value === 'http') {
                    urlGroup.style.display = 'block';
                    updateGroup.style.display = 'block';
                } else {
                    urlGroup.style.display = 'none';
                    updateGroup.style.display = 'none';
                }
            });
        }

        model.querySelector('#overwrite-sub-close').onclick = function() {
            document.body.removeChild(ocDiv);
        };
        overlay.onclick = function(e) {
            if (e.target === overlay) {
                document.body.removeChild(ocDiv);
            }
        };
        model.querySelector('#overwrite-edit-cancel').onclick = function() {
            document.body.removeChild(ocDiv);
        };

        var submitBtn = model.querySelector('#overwrite-edit-submit');

        function validateEditOverwriteForm() {
            const form = model.querySelector('#overwrite-upload-form-subscribe');
            const filename = form.querySelector('#overwrite-subscribe-filename').value.trim();
            const type = form.querySelector('#overwrite-subscribe-type').value;
            const url = form.querySelector('#overwrite-subscribe-url').value.trim();
            let valid = !!filename && filename !== 'openclash_custom_overwrite.sh';
            if (type === 'http') {
                valid = valid && !!url && /^https?:\/\/[^ \n|]+$/.test(url);
            }
            submitBtn.disabled = !valid;
        }

        validateEditOverwriteForm();

        model.querySelector('#overwrite-subscribe-filename').addEventListener('input', validateEditOverwriteForm);
        model.querySelector('#overwrite-subscribe-type').addEventListener('change', validateEditOverwriteForm);
        model.querySelector('#overwrite-subscribe-url').addEventListener('input', validateEditOverwriteForm);

        model.querySelector('#overwrite-edit-submit').onclick = function() {
            var form = model.querySelector('#overwrite-upload-form-subscribe');
            var newName = form.querySelector('#overwrite-subscribe-filename').value.trim();
            var type = form.querySelector('#overwrite-subscribe-type').value;
            var url = form.querySelector('#overwrite-subscribe-url').value.trim();
            var param = self.collectOverwriteParams(form.querySelector('#overwrite-subscribe-param-container'));
            var selectedConfigPaths = self.getOverwriteConfigSelection(model, 'overwrite-subscribe-config-dropdown');

            if (!newName) {
                alert('<%:Please enter a filename%>');
                return;
            }
            if (newName === 'openclash_custom_overwrite.sh') {
                alert('<%:openclash_custom_overwrite.sh already exists and cannot be added again%>');
                return;
            }
            if (type === 'http') {
                if (!url) {
                    alert('<%:Please enter subscription URL%>');
                    return;
                }
                if (!/^https?:\/\/[^ \n|]+$/.test(url)) {
                    alert('<%:Invalid subscription URL format, only single HTTP/HTTPS link is supported%>');
                    return;
                }
            }

            var formData = new FormData(form);
            if (type !== 'http') {
                formData.delete('url');
                formData.delete('update_days');
                formData.delete('update_hour');
            }
            formData.delete('config');
            formData.append('config', selectedConfigPaths.join('\n'));
            formData.append('param', param);
            if (newName !== name) {
                formData.append('old_filename', name);
            }
            if (typeof sub.enable !== 'undefined') {
                formData.append('enable', sub.enable);
            } else {
                formData.append('enable', '0');
            }
            formData.append('order', sub.order || 1);

            var progressContainer = form.querySelector('#overwrite-upload-progress');
            var progressFill = form.querySelector('#overwrite-progress-fill');
            var progressText = form.querySelector('#overwrite-progress-text');
            var statusText = model.querySelector('#overwrite-edit-status-text');
            if (type === 'http') {
                statusText.textContent = '<%:Downloading subscription content...%>';
                progressContainer.style.display = 'block';
                var progress = 0;
                var interval = setInterval(function() {
                    progress = Math.min(progress + Math.random() * 20, 90);
                    progressFill.style.width = progress + '%';
                    progressText.textContent = '<%:Downloading...%> ' + Math.floor(progress) + '%';
                }, 200);
            }
            fetch('/cgi-bin/luci/admin/services/openclash/overwrite_subscribe_info', {
                method: 'POST',
                body: formData
            }).then(r=>r.json()).then(function(data){
                if (type === 'http') {
                    clearInterval(interval);
                    progressFill.style.width = '100%';
                    progressText.textContent = '<%:Download completed%> 100%';
                }
                if (data.status === 'success') {
                    if (type === 'http') {
                        statusText.textContent = '<%:Subscription saved successfully%>';
                    } else {
                        statusText.textContent = '<%:Saved successfully%>';
                    }
                    setTimeout(function() {
                        document.body.removeChild(ocDiv);

                        if (newName !== name) {
                            self.currentConfigFile = '/etc/openclash/overwrite/' + newName;
                        }

                        self.loadOverwriteFiles();
                        setTimeout(function() {
                            self.loadConfigContent();
                            var configNameElement = document.getElementById('config-file-name');
                            if (configNameElement) {
                                configNameElement.textContent = self.formatDisplayName(self.currentConfigFile);
                            }
                        }, 300);

                    }, 800);
                } else {
                    statusText.textContent = '<%:Failed to save subscription info:%> ' + (data.message || '');
                }
            });
        };
    },

    closeEditor: function() {
        if (this.isModified) {
            var r = confirm('<%:You have unsaved changes. Are you sure you want to close?%>');
            if (!r) {
                return;
            }
        }
        this.hideMergeView();
        this.hide();

        if (window.OverwriteSubscribeManager && window.OverwriteSubscribeManager.load) {
            window.OverwriteSubscribeManager.load(true);
        }
        try {
            window.dispatchEvent(new Event('oc-overwrite-updated'));
        } catch (e) {}
    },

    updateZoom: function(newZoom) {
        this.currentZoom = newZoom;
        ocApplyZoom(this.editorInstance, newZoom);
    },

    zoomIn: function() {
        var newZoom = ocZoomIn(this.currentZoom);
        if (newZoom !== this.currentZoom) this.updateZoom(newZoom);
    },

    zoomOut: function() {
        var newZoom = ocZoomOut(this.currentZoom);
        if (newZoom !== this.currentZoom) this.updateZoom(newZoom);
    },

    resetZoom: function() {
        this.updateZoom(ocResetZoom());
    },

    isPointOnTextContent: function(el, clientX, clientY) {
        if (!el) return false;

        var textWalker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
        var textNode;
        while ((textNode = textWalker.nextNode())) {
            if (!textNode.nodeValue || !textNode.nodeValue.trim()) {
                continue;
            }
            var range = document.createRange();
            range.selectNodeContents(textNode);
            var rects = range.getClientRects();
            for (var i = 0; i < rects.length; i++) {
                var rect = rects[i];
                if (clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom) {
                    return true;
                }
            }
        }

        return false;
    },

    makeDraggable: function() {
        var self = this;
        var header = this.model.querySelector('.config-editor-header');
        var startX, startY, startLeft, startTop;
        var isDragging = false;

        header.addEventListener('mousedown', function(e) {
            var target = e.target && e.target.nodeType === 1 ? e.target : e.target.parentElement;
            if (target && target.closest('.config-editor-actions')) {
                return;
            }
            var textEl = target ? target.closest('#editTitle, #config-file-name') : null;
            if (textEl && self.isPointOnTextContent(textEl, e.clientX, e.clientY)) {
                return;
            }

            isDragging = true;
            startX = e.clientX;
            startY = e.clientY;

            var rect = self.model.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;

            self.model.style.position = 'fixed';
            self.model.style.left = startLeft + 'px';
            self.model.style.top = startTop + 'px';
            self.model.style.margin = '0';
            self.model.style.transform = 'none';
            self.model.style.transition = 'none';

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            e.preventDefault();
        });

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

            var deltaX = e.clientX - startX;
            var deltaY = e.clientY - startY;

            var newLeft = startLeft + deltaX;
            var newTop = startTop + deltaY;

            var modelRect = self.model.getBoundingClientRect();
            var maxLeft = window.innerWidth - modelRect.width;
            var maxTop = window.innerHeight - modelRect.height;

            newLeft = Math.max(0, Math.min(newLeft, maxLeft));
            newTop = Math.max(0, Math.min(newTop, maxTop));

            self.model.style.left = newLeft + 'px';
            self.model.style.top = newTop + 'px';
        }

        function onMouseUp() {
            isDragging = false;

            setTimeout(function() {
                self.model.style.transition = 'all 0.3s ease';
            }, 50);

            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }

        header.addEventListener('touchstart', function(e) {
            var target = e.target && e.target.nodeType === 1 ? e.target : e.target.parentElement;
            if (target && target.closest('.config-editor-actions')) {
                return;
            }
            var touch = e.touches && e.touches[0] ? e.touches[0] : null;
            var textEl = target ? target.closest('#editTitle, #config-file-name') : null;
            if (touch && textEl && self.isPointOnTextContent(textEl, touch.clientX, touch.clientY)) {
                return;
            }
            if (e.touches.length !== 1) return;

            isDragging = true;
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;

            var rect = self.model.getBoundingClientRect();
            startLeft = rect.left;
            startTop = rect.top;

            self.model.style.position = 'fixed';
            self.model.style.left = startLeft + 'px';
            self.model.style.top = startTop + 'px';
            self.model.style.margin = '0';
            self.model.style.transform = 'none';
            self.model.style.transition = 'none';

            document.addEventListener('touchmove', onTouchMove, {passive: false});
            document.addEventListener('touchend', onTouchEnd);

            e.preventDefault();
        });

        function onTouchMove(e) {
            if (!isDragging || e.touches.length !== 1) return;

            var deltaX = e.touches[0].clientX - startX;
            var deltaY = e.touches[0].clientY - startY;

            var newLeft = startLeft + deltaX;
            var newTop = startTop + deltaY;

            var modelRect = self.model.getBoundingClientRect();
            var maxLeft = window.innerWidth - modelRect.width;
            var maxTop = window.innerHeight - modelRect.height;

            newLeft = Math.max(0, Math.min(newLeft, maxLeft));
            newTop = Math.max(0, Math.min(newTop, maxTop));

            self.model.style.left = newLeft + 'px';
            self.model.style.top = newTop + 'px';

            e.preventDefault();
        }

        function onTouchEnd() {
            isDragging = false;
            setTimeout(function() {
                self.model.style.transition = 'all 0.3s ease';
            }, 50);

            document.removeEventListener('touchmove', onTouchMove);
            document.removeEventListener('touchend', onTouchEnd);
        }
    },

    makeResizable: function() {
        var self = this;
        var resizeHandle = document.getElementById('config-editor-resize-handle');
        var isResizing = false;
        var startX, startY, startWidth, startHeight;

        resizeHandle.addEventListener('mousedown', function(e) {
            isResizing = true;
            startX = e.clientX;
            startY = e.clientY;

            var rect = self.model.getBoundingClientRect();
            startWidth = rect.width;
            startHeight = rect.height;

            self.model.style.transition = 'none';
            self.model.style.width = startWidth + 'px';
            self.model.style.height = startHeight + 'px';

            document.addEventListener('mousemove', onMouseMove);
            document.addEventListener('mouseup', onMouseUp);

            e.preventDefault();
        });

        function onMouseMove(e) {
            if (!isResizing) return;

            var deltaX = e.clientX - startX;
            var deltaY = e.clientY - startY;

            var newWidth = Math.max(400, startWidth + deltaX);
            var newHeight = Math.max(300, startHeight + deltaY);

            var maxWidth = window.innerWidth * 0.98;
            var maxHeight = window.innerHeight * 0.95;

            newWidth = Math.min(newWidth, maxWidth);
            newHeight = Math.min(newHeight, maxHeight);

            self.model.style.width = newWidth + 'px';
            self.model.style.height = newHeight + 'px';
        }

        function onMouseUp() {
            isResizing = false;

            self.model.style.transition = 'all 0.3s ease';

            document.removeEventListener('mousemove', onMouseMove);
            document.removeEventListener('mouseup', onMouseUp);
        }

        resizeHandle.addEventListener('touchstart', function(e) {
            if (e.touches.length !== 1) return;
            isResizing = true;
            startX = e.touches[0].clientX;
            startY = e.touches[0].clientY;

            var rect = self.model.getBoundingClientRect();
            startWidth = rect.width;
            startHeight = rect.height;

            self.model.style.transition = 'none';
            self.model.style.width = startWidth + 'px';
            self.model.style.height = startHeight + 'px';

            document.addEventListener('touchmove', onTouchMove, {passive: false});
            document.addEventListener('touchend', onTouchEnd);

            e.preventDefault();
        });

        function onTouchMove(e) {
            if (!isResizing || e.touches.length !== 1) return;

            var deltaX = e.touches[0].clientX - startX;
            var deltaY = e.touches[0].clientY - startY;

            var newWidth = Math.max(320, startWidth + deltaX);
            var newHeight = Math.max(200, startHeight + deltaY);

            var maxWidth = window.innerWidth * 0.98;
            var maxHeight = window.innerHeight * 0.95;

            newWidth = Math.min(newWidth, maxWidth);
            newHeight = Math.min(newHeight, maxHeight);

            self.model.style.width = newWidth + 'px';
            self.model.style.height = newHeight + 'px';

            e.preventDefault();
        }

        function onTouchEnd() {
            isResizing = false;
            self.model.style.transition = 'all 0.3s ease';

            document.removeEventListener('touchmove', onTouchMove);
            document.removeEventListener('touchend', onTouchEnd);
        }
    }
};

document.addEventListener('DOMContentLoaded', function() {
    ConfigEditor.init();
});
</script>