<style>
@font-face {
    font-family: 'Twemoji Mozilla';
    src: url('/luci-static/resources/openclash/fonts/TwemojiMozilla-flags-B12sb_Bp.woff2') format('woff2');
}
.oc[data-darkmode="true"] .config-editor-model .CodeMirror {
    background: var(--bg-white);
    color: var(--text-primary);
}
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-gutters {
    background: var(--bg-gray);
    border-right: 1px solid var(--border-light);
}
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-linenumber {
    color: var(--text-secondary);
}
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-scrollbar-filler,
.oc[data-darkmode="true"] .config-editor-model .CodeMirror-gutter-filler {
    background: var(--bg-gray);
}
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab {
    color: var(--text-secondary);
}
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab:hover {
    color: var(--text-primary);
    background: rgba(96, 165, 250, 0.1);
}
.oc[data-darkmode="true"] #config-mode-tabs .mode-tab.active {
    background: var(--primary-color);
    color: white;
}
.oc[data-darkmode="true"] #config-mergeview-container .CodeMirror-merge-gap {
    background: var(--text-secondary) !important;
}
.oc[data-darkmode="true"] .oc .config-editor-content {
    border-bottom: 1px solid var(--border-light);
    border-top: 1px solid var(--border-light);
}
.oc[data-darkmode="true"] .overwrite-banner {
    background: rgba(255,80,80,0.18);
}
.oc[data-darkmode="true"] .overwrite-banner svg {
    stroke: var(--error-color);
}
.oc[data-darkmode="true"] .overwrite-banner svg circle {
    stroke: var(--error-color);
    fill: rgba(255,80,80,0.18);
}

.oc .config-editor-model-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    z-index: 10000;
    display: none;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(2px);
}
.oc .config-editor-model-overlay.show {
    display: flex;
}
.oc .config-editor-model {
    background: var(--bg-white);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    width: 90vw;
    height: 85vh;
    max-width: 1200px;
    min-width: 600px;
    min-height: 400px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid var(--border-light);
    position: relative;
    transition: all 0.3s ease;
}
.oc .config-editor-model.maximized {
    width: 98vw !important;
    height: 95vh !important;
    max-width: none !important;
}
.oc .config-editor-model.minimized {
    width: 70vw !important;
    height: 70vh !important;
}
.oc .config-editor-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--border-light);
    background: var(--bg-gray);
    flex-shrink: 0;
    cursor: move;
    user-select: none;
    min-width: 0;
}
.oc .config-editor-title {
    font-size: 16px;
    font-weight: 600;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
}
.oc .config-editor-title .config-file-name {
    color: var(--primary-color);
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    display: inline-block;
    vertical-align: bottom;
    padding: 0;
}
.oc .config-editor-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}
.oc .size-btn {
    width: 24px !important;
    height: 24px !important;
    min-width: 24px !important;
    padding: 0 !important;
}
.oc .size-btn svg {
    width: 12px !important;
    height: 12px !important;
}
#config-mergeview-container {
    width: 100%;
    height: 100%;
    display: block;
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    background: var(--bg-white);
    z-index: 2;
}
.oc .config-editor-content {
    flex: 1;
    position: relative;
    overflow: hidden;
    border-bottom: 1px solid var(--border-light);
    border-top: 1px solid var(--border-light);
}
.oc .config-editor-loading {
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    background: var(--bg-white);
    color: var(--text-secondary);
    font-size: 14px;
}
.oc .loading-spinner {
    width: 24px;
    height: 24px;
    border: 2px solid var(--border-light);
    border-top-color: var(--primary-color);
    border-radius: 50%;
    animation: spin 1s linear infinite;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}
.oc .config-editor-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 20px;
    background: var(--bg-gray);
    flex-shrink: 0;
    position: relative;
}
.oc .config-editor-status {
    font-size: 12px;
    color: var(--text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 50%;
}
.oc .config-editor-help {
    font-size: 11px;
    color: var(--text-secondary);
    opacity: 0.8;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 50%;
}
.oc .config-editor-resize-handle {
    position: absolute;
    bottom: 0; right: 0;
    width: 20px; height: 20px;
    cursor: nw-resize;
    background: linear-gradient(-45deg, 
        transparent 0%, 
        transparent 40%, 
        var(--border-color) 40%, 
        var(--border-color) 45%, 
        transparent 45%, 
        transparent 50%, 
        var(--border-color) 50%, 
        var(--border-color) 55%, 
        transparent 55%, 
        transparent 60%, 
        var(--border-color) 60%, 
        var(--border-color) 65%, 
        transparent 65%);
}
.oc #config-editor-textarea {
    width: 100%;
    height: 100%;
    border: none;
    outline: none;
    resize: none;
    font-size: 14px;
    line-height: 1.5;
    padding: 12px;
    background: var(--bg-white);
    color: var(--text-primary);
}
.oc .config-editor-model .CodeMirror {
    height: 100%;
    font-size: 14px;
    line-height: 1.5;
}
.oc #config-mode-tabs {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 8px;
    background: transparent;
    border: none;
    box-shadow: none;
    border-radius: var(--radius-md);
}
.oc #config-mode-tabs .mode-tabs {
    display: flex;
    width: 100%;
    background: var(--bg-gray);
    border-radius: var(--radius-md);
    padding: 4px;
    gap: 4px;
    margin: 0 auto;
}
.oc #config-mode-tabs .mode-tab {
    flex: 1 1 0;
    min-width: 0;
    width: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 0;
    border: none;
    border-radius: calc(var(--radius-md) - 2px);
    background: transparent;
    color: var(--text-secondary);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: all var(--transition-fast);
    box-sizing: border-box;
}
.oc #config-mode-tabs .mode-tab:hover {
    color: var(--text-primary);
    background: rgba(59, 130, 246, 0.1);
}
.oc #config-mode-tabs .mode-tab.active {
    background: var(--primary-color);
    color: white;
    box-shadow: var(--shadow-sm);
}
.overwrite-banner {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 12px;
    padding: 10px 20px;
    background: rgba(255,80,80,0.12);
    font-size: 14px;
    text-align: center;
}
.overwrite-banner svg {
    flex-shrink: 0;
    display: block;
}
.overwrite-banner span {
    flex: unset;
    text-align: center;
    display: inline-block;
    color: var(--error-color);
    line-height: 1.5;
    vertical-align: middle;
}
.oc .config-editor-model .CodeMirror.zoom-75 { font-size: 10.5px; }
.oc .config-editor-model .CodeMirror.zoom-90 { font-size: 12.6px; }
.oc .config-editor-model .CodeMirror.zoom-110 { font-size: 15.4px; }
.oc .config-editor-model .CodeMirror.zoom-125 { font-size: 17.5px; }
.oc .config-editor-model .CodeMirror.zoom-150 { font-size: 21px; }
.oc .config-editor-model .CodeMirror.zoom-200 { font-size: 28px; }
.oc .config-editor-model .CodeMirror, .oc .config-editor-model .CodeMirror-line {
	font-family: 'Twemoji Mozilla', "Microsoft Yahei", "sans-serif", "Helvetica Neue", "Helvetica", "Hiragino Sans GB" !important;
}
.oc .config-editor-model .CodeMirror-hints.log {
    font-family: 'Twemoji Mozilla', "Open Sans", "PingFangSC-Regular", "Microsoft Yahei", "WenQuanYi Micro Hei", "Helvetica Neue", "Helvetica", "Hiragino Sans GB", "sans-serif" !important;
}
#config-mergeview-container .CodeMirror-merge,
#config-mergeview-container .CodeMirror-merge-pane,
#config-mergeview-container .CodeMirror,
#config-mergeview-container .CodeMirror-scroll {
    height: 100% !important;
    min-height: 0 !important;
    box-sizing: border-box;
}
#config-mergeview-container .CodeMirror-merge-gap {
    height: 100% !important;
    min-height: 0 !important;
}
#config-mergeview-container .CodeMirror-scroll {
    overflow-y: auto !important;
    overflow-x: hidden !important;
}
#config-mergeview-container .CodeMirror-merge-pane {
    overflow: hidden;
}
#config-mergeview-container .CodeMirror-merge-r-chunk {
    background: #0095ff2e !important;
}
#config-mergeview-container .CodeMirror-merge-r-connect {
    fill: #0095ff2e !important;
    stroke: #0095ff2e !important;
}
#config-mergeview-container  .CodeMirror-merge {
    border: none !important;
}
.oc .update-row {
    display: flex;
    gap: 12px;
    width: 100%;
    max-width: 100%;
}
.oc .update-row > .form-select-wrapper {
    flex: 1 1 0;
    min-width: 0;
}
.oc .update-row .form-select {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
}
.oc .overwrite-card-row {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    gap: 12px;
    padding: 8px;
    max-width: 100%;
    min-width: 0;
    align-items: stretch;
    scrollbar-width: thin;
    scrollbar-color: var(--primary-color) var(--bg-gray);
}
.oc .overwrite-card-row::-webkit-scrollbar {
    height: 8px;
}
.oc .overwrite-card-row::-webkit-scrollbar-thumb {
    background: var(--primary-color);
    border-radius: 4px;
}
.oc .overwrite-card-row::-webkit-scrollbar-track {
    background: var(--bg-gray);
    border-radius: 4px;
}
.oc .sub-card.overwrite-item {
    width: 190px;
    max-width: 220px;
    flex: 0 0 auto;
    box-sizing: border-box;
    gap: 12px;
    height: 75px;
}
.oc .sub-card.overwrite-item,
.oc .sub-card.overwrite-item:active,
.oc .sub-card.overwrite-item:focus {
    -webkit-tap-highlight-color: transparent;
}
.oc .overwrite-drag-line {
    display: block;
    min-width: 4px;
    width: 4px;
    background: var(--primary-color);
    border-radius: 2px;
    margin: 0 2px;
    align-self: stretch;
    height: auto;
}
.oc .oc-switch {
  position: relative;
  display: inline-block;
  width: 36px;
  height: 20px;
  vertical-align: middle;
}
.oc .oc-switch input {
  opacity: 0;
  width: 0;
  height: 0;
}
.oc .oc-switch-slider {
  position: absolute;
  cursor: pointer;
  top: 0; left: 0; right: 0; bottom: 0;
  background-color: #ccc;
  border-radius: 20px;
  transition: .2s;
}
.oc .oc-switch-slider:before {
  position: absolute;
  content: "";
  height: 16px; width: 16px;
  left: 2px; bottom: 2px;
  background-color: white;
  border-radius: 50%;
  transition: .2s;
}
.oc .oc-switch input:checked + .oc-switch-slider {
  background-color: var(--primary-color, #2196F3);
}
.oc .oc-switch input:checked + .oc-switch-slider:before {
  transform: translateX(16px);
}
.overwrite-add-icon {
    font-size: 32px;
    text-align: center;
    color: var(--primary-color);
}
.overwrite-title-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}
.overwrite-title {
    font-weight: bold;
    font-size: 15px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 70%;
}
.overwrite-info {
    font-size: 12px;
    color: var(--text-secondary);
}
.overwrite-refresh-btn {
    position: absolute;
    right: 72px;
    bottom: 8px;
}
.overwrite-gear-btn {
    position: absolute;
    right: 40px;
    bottom: 8px;
}
.overwrite-del-btn {
    position: absolute;
    right: 8px;
    bottom: 8px;
}
.sub-card.overwrite-item.active {
    border-color: var(--primary-color);
    box-shadow: 0 0 0 2px var(--primary-color);
}
.sub-card.overwrite-item.dragging {
    opacity: 0.5;
    z-index: 1000;
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
    transition: none !important;
}
@media screen and (max-width: 768px) {
    .oc .config-editor-model {
        width: 95vw;
        height: 80vh;
        min-width: 320px;
    }
    .oc .config-editor-actions {
        gap: 5px;
    }
    .oc .overwrite-card-row {
        gap: 6px;
    }
    .oc .sub-card.overwrite-item {
        width: 185px;
        max-width: 200px;
        gap: 10px;
    }
    .oc .overwrite-drag-line {
        margin: -2px;
        width: 2px;
    }
}
</style>

<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" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <rect x="3" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
                            <rect x="13" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
                        </svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-download" title="<%:Download%>">
                        <svg width="14" height="14" 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="7,11 12,16 17,11"></polyline>
                            <line x1="12" y1="2" x2="12" y2="16"></line>
                        </svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-save" title="<%:Save%>">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"></path>
                            <polyline points="17,21 17,13 7,13 7,21"></polyline>
                            <polyline points="7,3 7,8 15,8"></polyline>
                        </svg>
                    </button>
                    <button type="button" class="icon-btn" id="config-editor-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 id="overwrite-banner" class="overwrite-banner" style="display:none;">
                <svg style="flex-shrink:0;" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--error-color)" stroke-width="2">
                    <circle cx="12" cy="12" r="10" stroke="var(--error-color)" fill="rgba(255,80,80,0.12)"/>
                    <line x1="12" y1="5" x2="12" y2="13"/>
                    <circle cx="12" cy="16" r="0.5"/>
                </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">
                <div class="config-editor-loading" id="config-editor-loading">
                    <div class="loading-spinner"></div>
                    <span><%:Loading config file...%></span>
                </div>
                <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 F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel to zoom%></span>
                </div>
                <div class="config-editor-resize-handle" id="config-editor-resize-handle"></div>
            </div>
        </div>
    </div>
</div>

<link rel="stylesheet" href="/luci-static/resources/openclash/lib/codemirror.css"/>
<link rel="stylesheet" href="/luci-static/resources/openclash/theme/material.css"/>
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/fold/foldgutter.css"/>
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/lint/lint.css">
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/display/fullscreen.css">
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/dialog/dialog.css">
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/search/matchesonscrollbar.css">
<script src="/luci-static/resources/openclash/lib/codemirror.js"></script>
<script src="/luci-static/resources/openclash/mode/yaml/yaml.js"></script>
<script src="/luci-static/resources/openclash/mode/shell/shell.js"></script>
<script src="/luci-static/resources/openclash/mode/properties/properties.js"></script>
<script src="/luci-static/resources/openclash/addon/fold/foldcode.js"></script>
<script src="/luci-static/resources/openclash/addon/fold/foldgutter.js"></script>
<script src="/luci-static/resources/openclash/addon/fold/indent-fold.js"></script>
<script src="/luci-static/resources/openclash/addon/edit/matchbrackets.js"></script>
<script src="/luci-static/resources/openclash/addon/selection/active-line.js"></script>
<script src="/luci-static/resources/openclash/addon/lint/lint.js"></script>
<script src="/luci-static/resources/openclash/addon/lint/yaml-lint.js"></script>
<script src="/luci-static/resources/openclash/addon/lint/js-yaml.min.js"></script>
<script src="/luci-static/resources/openclash/addon/display/fullscreen.js"></script>
<script src="/luci-static/resources/openclash/addon/display/autorefresh.js"></script>
<script src="/luci-static/resources/openclash/addon/dialog/dialog.js"></script>
<script src="/luci-static/resources/openclash/addon/search/searchcursor.js"></script>
<script src="/luci-static/resources/openclash/addon/search/search.js"></script>
<script src="/luci-static/resources/openclash/addon/scroll/annotatescrollbar.js"></script>
<script src="/luci-static/resources/openclash/addon/search/matchesonscrollbar.js"></script>
<script src="/luci-static/resources/openclash/addon/search/jump-to-line.js"></script>
<script src="/luci-static/resources/openclash/addon/merge/diff_match_patch.js"></script>
<script src="/luci-static/resources/openclash/addon/merge/merge.js"></script>
<link rel="stylesheet" href="/luci-static/resources/openclash/addon/merge/merge.css">

<script type="text/javascript">
var levelTranslations = {
    'info': '<%:Info%>',
    'warning': '<%:Warning%>',
    'error': '<%:Error%>',
    'debug': '<%:Debug%>',
    'tip': '<%:Tip%>',
    'watchdog': '<%:Watchdog%>',
	'fatal': '<%:Fatal%>'
};

var ConfigEditor = {
    overlay: null,
    model: null,
    editorInstance: null,
    originalContent: '',
    isModified: false,
    currentZoom: 100,
    currentConfigFile: '',
    zoomLevels: [75, 90, 100, 110, 125, 150, 200],
    isOverwrite: false,
    currentViewMode: 'original',
    runtimeContent: '',
    mergeViewActive: false,
    SVG_COMPARE: `
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <rect x="3" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
            <rect x="13" y="3" width="8" height="18" rx="2" stroke="currentColor" stroke-width="2" fill="none"/>
        </svg>
    `,
    SVG_RESTORE: `
        <svg width="14" height="14" 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>
    `,
    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.ctrlKey || e.metaKey) && (e.key === '=' || e.key === '+')) {
                e.preventDefault();
                self.zoomIn();
            } else if ((e.ctrlKey || e.metaKey) && e.key === '-') {
                e.preventDefault();
                self.zoomOut();
            } else if ((e.ctrlKey || e.metaKey) && e.key === '0') {
                e.preventDefault();
                self.resetZoom();
            } else if (e.key === 'Escape' && (!self.editorInstance || !self.editorInstance.getOption("fullScreen"))) {
                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 = self.SVG_RESTORE;
                } else {
                    self.hideMergeView();
                    layoutBtn.title = "<%:Compare%>";
                    layoutBtn.innerHTML = self.SVG_COMPARE;
                }
            });
        };

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

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

        if (this.editorInstance) {
            if (this.editorInstance.toTextArea) this.editorInstance.toTextArea();
            this.editorInstance = null;
        }
        var textarea = document.getElementById('config-editor-textarea');
        if (textarea) {
            textarea.value = '';
            textarea.style.display = 'none';
        }
        var loadingDiv = document.getElementById('config-editor-loading');
        if (loadingDiv) loadingDiv.style.display = 'flex';
        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();
        this.loadConfigContent();
    },

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

        if (this.editorInstance) {
            if (this.editorInstance.toTextArea) this.editorInstance.toTextArea();
            this.editorInstance = null;
        }
        var textarea = document.getElementById('config-editor-textarea');
        if (textarea) {
            textarea.value = '';
            textarea.style.display = 'none';
        }
        var loadingDiv = document.getElementById('config-editor-loading');
        if (loadingDiv) loadingDiv.style.display = 'flex';
        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();
        this.loadConfigContent();
    },

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

        if (this.editorInstance) {
            if (this.editorInstance.toTextArea) this.editorInstance.toTextArea();
            this.editorInstance = null;
        }

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

        var loadingDiv = document.getElementById('config-editor-loading');
        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 (loadingDiv) loadingDiv.style.display = 'flex';
        if (textarea) textarea.style.display = 'none';
        if (mergeview) mergeview.style.display = 'none';
        if (layoutBtn) {
            layoutBtn.classList.remove('active');
            layoutBtn.title = "<%:Compare%>";
            layoutBtn.innerHTML = this.SVG_COMPARE;
        }
        if (editor_help) editor_help.textContent = '<%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel 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 loadingDiv = document.getElementById('config-editor-loading');
        var textarea = document.getElementById('config-editor-textarea');
        var mergeview = document.getElementById('config-mergeview-container');
        if (mergeview) mergeview.style.display = 'none';
        if (textarea) textarea.style.display = 'block';

        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) {
            loadingDiv.style.display = 'none';
            textarea.value = content;
            textarea.style.display = 'block';

            if (self.editorInstance) {
                if (self.editorInstance.toTextArea) self.editorInstance.toTextArea();
                self.editorInstance = null;
            }
            self.editorInstance = CodeMirror.fromTextArea(textarea, {
                mode: mode,
                autoRefresh: true,
                styleActiveLine: true,
                lineNumbers: true,
                theme: "material",
                lineWrapping: true,
                matchBrackets: true,
                foldGutter: true,
                lint: lint,
                readOnly: readOnly,
                gutters: lint
                    ? ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"]
                    : ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
                extraKeys: {
                    "F11": function(cm) {
                        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    },
                    "Esc": function(cm) {
                        if (cm.getOption("fullScreen")) {
                            cm.setOption("fullScreen", false);
                        }
                    },
                    "Tab": function(cm) {
                        if (cm.somethingSelected()) {
                            cm.indentSelection('add');
                        } else {
                            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
                            cm.replaceSelection(spaces);
                        }
                    },
                    "Ctrl-S": function(cm) {
                        if (!readOnly) self.saveConfigContent();
                    }
                }
            });
            self.editorInstance.setSize('100%', '100%');
            self.editorInstance.setValue(content);
            self.editorInstance.refresh();
            if (!readOnly) {
                self.editorInstance.on("change", function() {
                    self.isModified = self.editorInstance.getValue() !== self.originalContent;
                    self.updateSaveButtonState();
                });
            }
        }

        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(response) {
                if (!response.ok) {
                    throw new Error('HTTP error! status: ' + response.status);
                }
                return response.json();
            })
            .then(function(data) {
                if (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 {
                    throw new Error('Invalid response data');
                }
            })
            .catch(function(error) {
                loadingDiv.querySelector('span').textContent = '<%:Failed to load config file%>';
                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 loadingDiv = document.getElementById('config-editor-loading');
        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';
        if (loadingDiv) loadingDiv.style.display = 'none';
        if (container) container.style.display = 'block';
        if (statusText) statusText.textContent = '<%:Loading...%>';
        if (editor_help) editor_help.textContent = '<%:Press F10 to toggle differences, F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel 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('')});
            });
        };

        let showDifferences = true;

        Promise.all([getOriginal(), getRuntime()]).then(function(contents){
            var original = contents[0] || '';
            var runtime = contents[1] || '';
            container.innerHTML = '';
            if (self.editorInstance && self.editorInstance.toTextArea) self.editorInstance.toTextArea();
            self.editorInstance = CodeMirror.MergeView(container, {
                value: original,
                orig: runtime,
                mode: "text/yaml",
                theme: "material",
                lineNumbers: true,
                autoRefresh: true,
                styleActiveLine: true,
                lineWrapping: true,
                matchBrackets: true,
                foldGutter: true,
                lint: true,
                highlightDifferences: showDifferences,
                connect: null,
                collapseIdentical: false,
                readOnly: false,
                gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter", "CodeMirror-lint-markers"],
                extraKeys: {
                    "F10": function() {
                        showDifferences = !showDifferences;
                        if (self.editorInstance && self.editorInstance.setShowDifferences) {
                            self.editorInstance.setShowDifferences(showDifferences);
                        }
                    },
                    "F11": function(cm) {
                        cm.setOption("fullScreen", !cm.getOption("fullScreen"));
                    },
                    "Esc": function(cm) {
                        if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
                    },
                    "Tab": function(cm) {
                        if (cm.somethingSelected()) {
                            cm.indentSelection('add');
                        } else {
                            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
                            cm.replaceSelection(spaces);
                        }
                    },
                    "Ctrl-S": function(cm) {
                        self.saveConfigContent();
                    }
                }
            });
            var leftEditor = self.editorInstance.edit;
            if (leftEditor) {
                leftEditor.on("change", function() {
                    self.isModified = leftEditor.getValue() !== self.originalContent;
                    self.updateSaveButtonState();
                });
            }
            if (self.editorInstance.editor)
                self.editorInstance.editor().setSize('100%', '100%');
            if (self.editorInstance.rightOriginal && self.editorInstance.rightOriginal())
                self.editorInstance.rightOriginal().setSize('100%', '100%');
            self.mergeViewActive = true;
            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;
        this.loadConfigContent();

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

        if (editor_help) editor_help.textContent = '<%:Press F11 for fullscreen, Esc to exit fullscreen, Ctrl+Mouse Wheel 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.edit) {
            content = this.editorInstance.edit.getValue();
        } else {
            content = this.editorInstance.getValue();
        }

        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(response) {
            if (!response.ok) {
                throw new Error('HTTP error! status: ' + response.status);
            }
            return response.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(error) {
            saveBtn.disabled = false;
            statusText.textContent = '<%:Save failed%>';
            alert('<%:Save config failed:%> ' + error.message);

            setTimeout(function() {
                if (statusText && statusText.textContent === '<%:Save failed%>') {
                    statusText.textContent = '<%:Ready%>';
                }
            }, 3000);
        });
    },

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

        var content;
        if (this.mergeViewActive && this.editorInstance && this.editorInstance.edit) {
            content = this.editorInstance.edit.getValue();
        } else {
            content = this.editorInstance.getValue();
        }

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

    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 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="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><%: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>
                    <input type="text" class="form-input" name="param" id="overwrite-subscribe-param" value="${sub.param||''}" placeholder="key1=value1;key2=value2">
                </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';
        var mainOc = document.querySelector('.oc[data-darkmode="true"]');
        if (mainOc) {
            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);

        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);
        };
        overlay.onclick = function(e) {
            if (e.target === overlay) {
                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);
        }
        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 formData = new FormData();
                        formData.append('filename', filename);
                        formData.append('config_file', fileContent);
                        formData.append('order', self.overwriteFileCardCount + 1);
                        formData.append('enable', '0');

                        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 = form.querySelector('#overwrite-subscribe-param').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 (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');
                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';
        var mainOc = document.querySelector('.oc[data-darkmode="true"]');
        if (mainOc) {
            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);

        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 = form.querySelector('#overwrite-subscribe-param').value.trim();

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

    updateZoom: function(newZoom) {
        this.currentZoom = newZoom;

        if (this.editorInstance) {
            var cmWrapper = this.model.querySelector('.CodeMirror');
            if (cmWrapper) {
                this.zoomLevels.forEach(function(level) {
                    cmWrapper.classList.remove('zoom-' + level);
                });

                if (this.currentZoom !== 100) {
                    cmWrapper.classList.add('zoom-' + this.currentZoom);
                }

                this.editorInstance.refresh();
            }
        }
    },

    zoomIn: function() {
        var currentIndex = this.zoomLevels.indexOf(this.currentZoom);
        if (currentIndex < this.zoomLevels.length - 1) {
            this.updateZoom(this.zoomLevels[currentIndex + 1]);
        }
    },

    zoomOut: function() {
        var currentIndex = this.zoomLevels.indexOf(this.currentZoom);
        if (currentIndex > 0) {
            this.updateZoom(this.zoomLevels[currentIndex - 1]);
        }
    },

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

    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) {
            if (e.target.closest('.config-editor-actions')) {
                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) {
            if (e.target.closest('.config-editor-actions')) {
                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';

            if (self.editorInstance) {
                requestAnimationFrame(function() {
                    if (self.mergeViewActive && self.editorInstance.editor) {
                        self.editorInstance.editor().refresh();
                        if (self.editorInstance.rightOriginal)
                            self.editorInstance.rightOriginal().refresh();
                    } else {
                        self.editorInstance.refresh();
                    }
                });
            }
        }

        function onMouseUp() {
            isResizing = false;

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

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

            if (self.editorInstance) {
                setTimeout(function() {
                    if (self.mergeViewActive && self.editorInstance.editor) {
                        self.editorInstance.editor().refresh();
                        if (self.editorInstance.rightOriginal)
                            self.editorInstance.rightOriginal().refresh();
                    } else {
                        self.editorInstance.refresh();
                    }
                }, 50);
            }
        }

        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';

            if (self.editorInstance) {
                requestAnimationFrame(function() {
                    if (self.mergeViewActive && self.editorInstance.editor) {
                        self.editorInstance.editor().refresh();
                        if (self.editorInstance.rightOriginal)
                            self.editorInstance.rightOriginal().refresh();
                    } else {
                        self.editorInstance.refresh();
                    }
                });
            }

            e.preventDefault();
        }

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

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

            if (self.editorInstance) {
                setTimeout(function() {
                    if (self.mergeViewActive && self.editorInstance.editor) {
                        self.editorInstance.editor().refresh();
                        if (self.editorInstance.rightOriginal)
                            self.editorInstance.rightOriginal().refresh();
                    } else {
                        self.editorInstance.refresh();
                    }
                }, 50);
            }
        }
    }
};

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