/**
 * OmicVerse Single Cell Analysis — File System, Browser & Tab Management
 */

Object.assign(SingleCellAnalysis.prototype, {

    destroyActiveEditors() {
        if (this._cmEditor) {
            this._cmEditor.toTextArea();
            this._cmEditor = null;
        }
        if (this._mdEditor) {
            this._mdEditor.toTextArea();
            this._mdEditor = null;
        }
    },

    refreshMarkdownPreview(preview, content) {
        if (!preview) return;
        preview.innerHTML = this.renderMarkdown(content || '');
        if (window.Prism && typeof window.Prism.highlightAllUnder === 'function') {
            window.Prism.highlightAllUnder(preview);
        }
    },

    setupFileUpload() {
        const dropZone = document.getElementById('dropZone');
        const fileInput = document.getElementById('fileInput');

        if (!dropZone || !fileInput) return;

        // Drag and drop events
        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            dropZone.classList.add('dragover');
        });

        dropZone.addEventListener('dragleave', (e) => {
            e.preventDefault();
            dropZone.classList.remove('dragover');
        });

        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            dropZone.classList.remove('dragover');
            const files = e.dataTransfer.files;
            if (files.length > 0) {
                this.handleFileUpload(files[0]);
            }
        });

        // File input change
        fileInput.addEventListener('change', (e) => {
            if (e.target.files.length > 0) {
                this.handleFileUpload(e.target.files[0]);
            }
        });

        // Click: open server file browser instead of local file picker
        dropZone.addEventListener('click', (e) => {
            this.openServerFileBrowser('analysis');
        });

        // ── Preview Mode drop zone ────────────────────────────────────────────
        const dropZonePreview = document.getElementById('dropZonePreview');
        const fileInputPreview = document.getElementById('fileInputPreview');

        if (dropZonePreview && fileInputPreview) {
            dropZonePreview.addEventListener('dragover', (e) => {
                e.preventDefault();
                dropZonePreview.classList.add('dragover');
            });
            dropZonePreview.addEventListener('dragleave', (e) => {
                e.preventDefault();
                dropZonePreview.classList.remove('dragover');
            });
            dropZonePreview.addEventListener('drop', (e) => {
                e.preventDefault();
                dropZonePreview.classList.remove('dragover');
                const files = e.dataTransfer.files;
                if (files.length > 0) this.handleFileUploadPreview(files[0]);
            });
            fileInputPreview.addEventListener('change', (e) => {
                if (e.target.files.length > 0) this.handleFileUploadPreview(e.target.files[0]);
            });
            dropZonePreview.addEventListener('click', () => this.openServerFileBrowser('preview'));
        }
    },

    // ── Server file browser modal ─────────────────────────────────────────────

    openServerFileBrowser(mode) {
        let modal = document.getElementById('server-file-browser-modal');
        if (!modal) {
            modal = this._createServerFileBrowserModal();
            document.body.appendChild(modal);
        }
        modal.dataset.mode = mode;
        const title = modal.querySelector('.modal-title');
        if (title) {
            title.textContent = mode === 'preview'
                ? (this.t('upload.previewMode') || '仅预览模式 — 选择服务器文件')
                : (this.t('upload.analysisMode') || '分析读取模式 — 选择服务器文件');
        }
        this._serverBrowserSelectedPath = null;
        const loadBtn = document.getElementById('server-browser-load-btn');
        if (loadBtn) loadBtn.disabled = true;
        const selectedEl = document.getElementById('server-browser-selected');
        if (selectedEl) selectedEl.textContent = '';
        this._loadServerBrowserPath('');
        const bsModal = new bootstrap.Modal(modal);
        bsModal.show();
    },

    _createServerFileBrowserModal() {
        const div = document.createElement('div');
        div.id = 'server-file-browser-modal';
        div.className = 'modal fade';
        div.tabIndex = -1;
        div.innerHTML = `
            <div class="modal-dialog modal-lg">
                <div class="modal-content">
                    <div class="modal-header py-2">
                        <h6 class="modal-title mb-0"></h6>
                        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
                    </div>
                    <div class="modal-body p-0">
                        <div class="d-flex align-items-center px-2 py-1"
                             style="background:var(--bs-light,#f8f9fa);border-bottom:1px solid var(--bs-border-color,#dee2e6);">
                            <i class="feather-folder text-muted me-2" style="font-size:.85rem;flex-shrink:0;"></i>
                            <input id="server-browser-path-input" type="text"
                                   class="form-control form-control-sm border-0 bg-transparent p-0 shadow-none"
                                   style="font-size:0.75rem;font-family:monospace;"
                                   placeholder="输入路径后按 Enter 跳转…" autocomplete="off" spellcheck="false">
                        </div>
                        <div id="server-browser-back-row"
                             style="display:none;border-bottom:1px solid var(--bs-border-color,#dee2e6);">
                            <div class="d-flex align-items-center px-2 py-1 small server-browser-back-btn"
                                 style="cursor:pointer;user-select:none;">
                                <i class="feather-arrow-left me-2 text-muted" style="font-size:.85rem;"></i>
                                <span class="text-muted" data-i18n="file.goUp">返回上层</span>
                            </div>
                        </div>
                        <div style="max-height:52vh;overflow-y:auto;">
                            <div id="server-browser-list" class="p-2"></div>
                        </div>
                    </div>
                    <div class="modal-footer py-2">
                        <span id="server-browser-selected" class="text-muted small me-auto text-truncate" style="max-width:50%;"></span>
                        <button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">取消</button>
                        <button type="button" id="server-browser-load-btn" class="btn btn-sm btn-primary" disabled>加载</button>
                    </div>
                </div>
            </div>`;

        // Load button
        div.querySelector('#server-browser-load-btn').addEventListener('click', () => {
            const path = this._serverBrowserSelectedPath;
            const mode = div.dataset.mode;
            if (!path) return;
            bootstrap.Modal.getInstance(div)?.hide();
            this.loadH5adFromServer(path, mode);
        });

        // Editable path input: press Enter to navigate
        div.querySelector('#server-browser-path-input').addEventListener('keydown', (e) => {
            if (e.key !== 'Enter') return;
            e.preventDefault();
            const typed = e.target.value.trim();
            const rel = this._absToServerRelPath(typed);
            this._loadServerBrowserPath(rel);
        });

        return div;
    },

    // Navigate the server file browser to an absolute path (empty = default to file_root).
    _loadServerBrowserPath(absPath) {
        const listEl   = document.getElementById('server-browser-list');
        const pathInput = document.getElementById('server-browser-path-input');
        const backRow  = document.getElementById('server-browser-back-row');
        if (!listEl) return;
        listEl.innerHTML = `<div class="text-muted small p-2">${this.t('common.loading') || '加载中…'}</div>`;

        const url = new URL('/api/files/list_abs', window.location.origin);
        if (absPath) url.searchParams.set('abspath', absPath);

        fetch(url.toString())
            .then(r => r.json())
            .then(data => {
                if (data.error) {
                    listEl.innerHTML = `<div class="text-danger small p-2">${data.error}</div>`;
                    return;
                }

                // Update editable path input
                if (pathInput) pathInput.value = data.current_abs || '';

                // Back button: disabled at filesystem root
                if (backRow) {
                    backRow.style.display = 'block';
                    const btn = backRow.querySelector('.server-browser-back-btn');
                    if (btn) {
                        if (data.is_fs_root) {
                            btn.style.opacity = '0.4';
                            btn.style.cursor = 'default';
                            btn.onclick = null;
                            btn.onmouseenter = null;
                            btn.onmouseleave = null;
                        } else {
                            btn.style.opacity = '';
                            btn.style.cursor = 'pointer';
                            btn.onclick = () => this._loadServerBrowserPath(data.parent_abs);
                            btn.onmouseenter = () => btn.classList.add('bg-light');
                            btn.onmouseleave = () => btn.classList.remove('bg-light');
                        }
                    }
                }

                this._renderServerBrowserList(listEl, data.entries || [], data.current_abs, data.parent_abs || '');
            })
            .catch(err => { listEl.innerHTML = `<div class="text-danger small p-2">${err.message}</div>`; });
    },

    _renderServerBrowserList(container, entries, currentPath, parentPath) {
        container.innerHTML = '';

        if (!entries.length) {
            const empty = document.createElement('div');
            empty.className = 'text-muted small text-center py-4';
            empty.textContent = this.t('file.empty') || '目录为空';
            container.appendChild(empty);
            return;
        }

        entries.forEach(entry => {
            const isH5ad = entry.type === 'file' && (entry.ext || '').toLowerCase() === '.h5ad';
            const isDir = entry.type === 'dir';
            const disabled = !isH5ad && !isDir;

            const item = document.createElement('div');
            item.className = 'server-browser-item d-flex align-items-center px-2 py-1 rounded mb-1';
            item.style.cursor = disabled ? 'default' : 'pointer';
            if (disabled) item.style.opacity = '0.45';

            const iconClass = isDir ? 'feather-folder text-warning' : (isH5ad ? 'feather-database text-primary' : 'feather-file text-muted');
            const nameClass = isH5ad ? 'fw-semibold text-primary' : '';
            item.innerHTML = `<i class="${iconClass} me-2" style="font-size:.85rem;"></i><span class="small ${nameClass}">${entry.name}</span>`;

            if (isDir) {
                item.addEventListener('click', () => {
                    // currentPath is now an absolute path
                    const sep = currentPath.includes('\\') ? '\\' : '/';
                    const next = currentPath.endsWith(sep)
                        ? currentPath + entry.name
                        : currentPath + sep + entry.name;
                    this._loadServerBrowserPath(next);
                });
                item.addEventListener('mouseenter', () => item.classList.add('bg-light'));
                item.addEventListener('mouseleave', () => item.classList.remove('bg-light'));
            } else if (isH5ad) {
                const sep = currentPath.includes('\\') ? '\\' : '/';
                const filePath = currentPath.endsWith(sep)
                    ? currentPath + entry.name
                    : currentPath + sep + entry.name;
                const select = () => {
                    container.querySelectorAll('.sb-selected').forEach(el => {
                        el.classList.remove('sb-selected', 'bg-primary');
                        el.querySelectorAll('.text-white').forEach(c => c.classList.remove('text-white'));
                    });
                    item.classList.add('sb-selected', 'bg-primary');
                    item.querySelectorAll('i, span').forEach(c => c.classList.add('text-white'));
                    this._serverBrowserSelectedPath = filePath;
                    const sel = document.getElementById('server-browser-selected');
                    if (sel) sel.textContent = filePath;
                    const btn = document.getElementById('server-browser-load-btn');
                    if (btn) btn.disabled = false;
                };
                item.addEventListener('click', select);
                item.addEventListener('dblclick', () => {
                    select();
                    const modal = document.getElementById('server-file-browser-modal');
                    bootstrap.Modal.getInstance(modal)?.hide();
                    this.loadH5adFromServer(filePath, modal?.dataset.mode || 'analysis');
                });
                item.addEventListener('mouseenter', () => { if (!item.classList.contains('sb-selected')) item.classList.add('bg-light'); });
                item.addEventListener('mouseleave', () => { if (!item.classList.contains('sb-selected')) item.classList.remove('bg-light'); });
            }

            container.appendChild(item);
        });
    },

    handleFileUploadPreview(file) {
        if (!file.name.endsWith('.h5ad')) {
            alert(this.t('upload.invalidFormat'));
            return;
        }

        const formData = new FormData();
        formData.append('file', file);

        this.showStatus(this.t('upload.previewUploading') || '正在以预览模式读取…', true);
        this.addToLog((this.t('upload.previewStart') || '预览读取') + ': ' + file.name);

        fetch('/api/upload_preview', {
            method: 'POST',
            body: formData
        })
        .then(async response => {
            const contentType = response.headers.get('content-type') || '';
            if (!response.ok) {
                let text = '';
                try { text = await response.text(); } catch (e) {}
                try {
                    const js = JSON.parse(text);
                    throw new Error(js.error || text || `HTTP ${response.status}`);
                } catch (e) {
                    if (text && text.trim().startsWith('<!DOCTYPE')) {
                        throw new Error(this.t('upload.htmlError') + ` (HTTP ${response.status})`);
                    }
                    throw new Error(text || `HTTP ${response.status}`);
                }
            }
            if (contentType.includes('application/json')) return response.json();
            const text = await response.text();
            try { return JSON.parse(text); } catch (e) { throw new Error(this.t('upload.invalidResponse')); }
        })
        .then(data => {
            this.hideStatus();
            if (data.error) {
                this.addToLog(this.t('common.error') + ': ' + data.error, 'error');
                this.showStatus(this.t('upload.failed') + ': ' + data.error, false);
                alert(this.t('upload.failed') + ': ' + data.error);
            } else {
                // Mark frontend as preview mode
                data.preview_mode = true;
                this.isPreviewMode = true;
                this.currentData = data;
                this.updateUI(data);
                this.updateAdataStatus(data);
                this.updatePreviewModeBanner(true);
                this.addToLog((this.t('upload.previewSuccess') || '预览读取成功') + ': ' + data.n_cells + ' ' + this.t('status.cells') + ', ' + data.n_genes + ' ' + this.t('status.genes'));
                this.showStatus(this.t('upload.previewSuccess') || '预览模式已加载', false);
            }
        })
        .catch(error => {
            this.hideStatus();
            this.addToLog(this.t('upload.failed') + ': ' + error.message, 'error');
            this.showStatus(this.t('upload.failed') + ': ' + error.message, false);
            alert(this.t('upload.failed') + ': ' + error.message);
        });
    },

    /** Show/hide the preview-mode banner and dim the Save button */
    updatePreviewModeBanner(isPreview) {
        const banner    = document.getElementById('preview-mode-banner');
        const switchBtn = document.getElementById('switch-analysis-btn');
        const saveBtn   = document.getElementById('save-btn');
        if (banner)    banner.classList.toggle('d-none', !isPreview);
        if (switchBtn) switchBtn.classList.toggle('d-none', !isPreview);
        if (saveBtn) {
            saveBtn.disabled = isPreview;
            saveBtn.title = isPreview ? (this.t('preview.saveDisabled') || '预览模式下无法保存') : '';
        }
    },

    /** Switch current backed file to full analysis mode by re-uploading from cache */
    switchToAnalysisMode() {
        if (!this.currentData || !this.currentData.filename) return;
        const msg = this.t('preview.switchConfirm') || '切换分析模式将完整加载数据到内存，可能需要一些时间。确定继续？';
        if (!confirm(msg)) return;
        // Reset preview flag immediately so the upload section reappears
        this.isPreviewMode = false;
        this.currentData = null;
        document.getElementById('upload-section').style.display = '';
        document.getElementById('data-status').classList.add('d-none');
        document.getElementById('viz-controls').style.display = 'none';
        document.getElementById('viz-panel').style.display = 'none';
        const _lp = document.getElementById('viz-legend-panel');
        if (_lp) _lp.style.display = 'none';
        this.updatePreviewModeBanner(false);
        this.addToLog(this.t('preview.switchHint') || '请在左侧"分析读取模式"区域重新上传文件以完整加载数据。');
    },

    triggerNotebookUpload() {
        const fileInput = document.getElementById('notebook-file-input');
        if (fileInput) fileInput.click();
    },

    fetchFileTree() {
        const tree = document.getElementById('file-tree');
        if (!tree) return;
        tree.oncontextmenu = (e) => {
            e.preventDefault();
            this.openContextMenu(e.clientX, e.clientY, this.currentBrowsePath || '', true);
        };
        tree.innerHTML = `<li class="file-tree-node">${this.t('common.loading')}</li>`;
        this.loadTreeNode('', tree);
    },

    loadTreeNode(path, container) {
        const url = new URL('/api/files/list', window.location.origin);
        if (path) url.searchParams.set('path', path);
        fetch(url.toString())
        .then(response => response.json())
        .then(data => {
            if (data.error) {
                container.innerHTML = `<li class="file-tree-node">${data.error}</li>`;
                return;
            }
            this.currentBrowsePath = data.path || '';
            this.renderFileTree(container, data.entries || [], data.path || '');
            const rootLabel = document.getElementById('file-browser-root');
            if (rootLabel) {
                rootLabel.textContent = data.path ? `./${data.path}` : './';
            }
        })
        .catch(error => {
            container.innerHTML = `<li class="file-tree-node">${error.message}</li>`;
        });
    },

    renderFileTree(container, entries, path) {
        container.innerHTML = '';
        if (!entries.length) {
            const empty = document.createElement('li');
            empty.className = 'file-tree-node';
            empty.textContent = this.t('file.empty');
            container.appendChild(empty);
            return;
        }
        entries.forEach(entry => {
            const li = document.createElement('li');
            const node = document.createElement('div');
            node.className = 'file-tree-node';
            const toggle = document.createElement('span');
            toggle.className = 'node-toggle';
            toggle.textContent = entry.type === 'dir' ? '▸' : '';
            const icon = document.createElement('i');
            icon.className = entry.type === 'dir' ? 'feather-folder' : 'feather-file-text';
            node.appendChild(toggle);
            node.appendChild(icon);
            node.appendChild(document.createTextNode(entry.name));
            li.appendChild(node);
            if (entry.type === 'dir') {
                const children = document.createElement('ul');
                children.className = 'file-tree-children';
                li.appendChild(children);
                node.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const isOpen = children.classList.contains('open');
                    if (isOpen) {
                        children.classList.remove('open');
                        toggle.textContent = '▸';
                        return;
                    }
                    toggle.textContent = '▾';
                    children.classList.add('open');
                    if (!children.dataset.loaded) {
                        children.innerHTML = `<li class="file-tree-node">${this.t('common.loading')}</li>`;
                        const nextPath = path ? `${path}/${entry.name}` : entry.name;
                        this.loadTreeNode(nextPath, children);
                        children.dataset.loaded = '1';
                    }
                });
                node.addEventListener('contextmenu', (e) => {
                    e.preventDefault();
                    const nextPath = path ? `${path}/${entry.name}` : entry.name;
                    this.openContextMenu(e.clientX, e.clientY, nextPath, true);
                });
            } else {
                node.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const filePath = path ? `${path}/${entry.name}` : entry.name;
                    if ((entry.ext || '').toLowerCase() === '.h5ad') {
                        this.openH5adFromServer(filePath);
                    } else {
                        this.openFileFromServer(filePath);
                    }
                });
                node.addEventListener('contextmenu', (e) => {
                    e.preventDefault();
                    const filePath = path ? `${path}/${entry.name}` : entry.name;
                    this.openContextMenu(e.clientX, e.clientY, filePath, false);
                });
            }
            container.appendChild(li);
        });
    },

    handleFileUpload(file) {
        if (!file.name.endsWith('.h5ad')) {
            alert(this.t('upload.invalidFormat'));
            return;
        }

        const formData = new FormData();
        formData.append('file', file);

        this.showStatus(this.t('upload.uploading'), true);
        this.addToLog(this.t('upload.start') + ': ' + file.name);

        fetch('/api/upload', {
            method: 'POST',
            body: formData
        })
        .then(async response => {
            const contentType = response.headers.get('content-type') || '';
            if (!response.ok) {
                let text = '';
                try { text = await response.text(); } catch (e) {}
                // Try JSON
                try {
                    const js = JSON.parse(text);
                    throw new Error(js.error || text || `HTTP ${response.status}`);
                } catch (e) {
                    if (text && text.trim().startsWith('<!DOCTYPE')) {
                        throw new Error(this.t('upload.htmlError') + ` (HTTP ${response.status})`);
                    }
                    throw new Error(text || `HTTP ${response.status}`);
                }
            }
            if (contentType.includes('application/json')) {
                return response.json();
            }
            // Fallback: try parse text as JSON
            const text = await response.text();
            try { return JSON.parse(text); } catch (e) { throw new Error(this.t('upload.invalidResponse')); }
        })
        .then(data => {
            this.hideStatus();
            if (data.error) {
                this.addToLog(this.t('common.error') + ': ' + data.error, 'error');
                this.showStatus(this.t('upload.failed') + ': ' + data.error, false);
                alert(this.t('upload.failed') + ': ' + data.error);
            } else {
                // Normal full-load — clear preview mode
                this.isPreviewMode = false;
                data.preview_mode = false;
                this.currentData = data;
                this.updateUI(data);
                this.updateAdataStatus(data);
                this.updatePreviewModeBanner(false);
                this.addToLog(this.t('upload.successDetail') + ': ' + data.n_cells + ' ' + this.t('status.cells') + ', ' + data.n_genes + ' ' + this.t('status.genes'));
                this.showStatus(this.t('upload.success'), false);
            }
        })
        .catch(error => {
            this.hideStatus();
            this.addToLog(this.t('upload.failed') + ': ' + error.message, 'error');
            this.showStatus(this.t('upload.failed') + ': ' + error.message, false);
            alert(this.t('upload.failed') + ': ' + error.message);
        });
    },

    openH5adFromServer(path) {
        const msg = (this.t('upload.serverModePrompt') ||
            '选择加载模式\n\n点击「确定」→ 分析读取模式（完整加载到内存，支持所有分析）\n点击「取消」→ 仅预览模式（低内存读取）');
        const fullMode = confirm(msg);
        this.loadH5adFromServer(path, fullMode ? 'analysis' : 'preview');
    },

    loadH5adFromServer(path, mode) {
        const isPreview = mode === 'preview';
        const endpoint = isPreview ? '/api/load_preview_from_server' : '/api/load_from_server';
        const statusMsg = isPreview
            ? (this.t('upload.previewUploading') || '正在以预览模式读取…')
            : (this.t('upload.uploading') || '正在读取数据…');

        this.showStatus(statusMsg, true);
        this.addToLog((isPreview ? (this.t('upload.previewStart') || '预览读取') : (this.t('upload.start') || '读取')) + ': ' + path);

        fetch(endpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ path })
        })
        .then(async response => {
            if (!response.ok) {
                const text = await response.text().catch(() => '');
                try { const js = JSON.parse(text); throw new Error(js.error || `HTTP ${response.status}`); }
                catch (e) { throw new Error(text || `HTTP ${response.status}`); }
            }
            return response.json();
        })
        .then(data => {
            this.hideStatus();
            if (data.error) {
                this.addToLog(this.t('common.error') + ': ' + data.error, 'error');
                this.showStatus(this.t('upload.failed') + ': ' + data.error, false);
                alert(this.t('upload.failed') + ': ' + data.error);
                return;
            }
            if (isPreview) {
                data.preview_mode = true;
                this.isPreviewMode = true;
                this.currentData = data;
                this.updateUI(data);
                this.updateAdataStatus(data);
                this.updatePreviewModeBanner(true);
                this.addToLog((this.t('upload.previewSuccess') || '预览读取成功') + ': ' + data.n_cells + ' ' + this.t('status.cells') + ', ' + data.n_genes + ' ' + this.t('status.genes'));
            } else {
                this.isPreviewMode = false;
                data.preview_mode = false;
                this.currentData = data;
                this.updateUI(data);
                this.updateAdataStatus(data);
                this.updatePreviewModeBanner(false);
                this.addToLog(this.t('upload.successDetail') + ': ' + data.n_cells + ' ' + this.t('status.cells') + ', ' + data.n_genes + ' ' + this.t('status.genes'));
            }
        })
        .catch(error => {
            this.hideStatus();
            this.addToLog(this.t('upload.failed') + ': ' + error.message, 'error');
            this.showStatus(this.t('upload.failed') + ': ' + error.message, false);
            alert(this.t('upload.failed') + ': ' + error.message);
        });
    },

    openFileFromServer(path) {
        if (!path) return;
        this.persistActiveTab();
        fetch('/api/files/open', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ path })
        })
        .then(response => response.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.openFailed')}: ${data.error}`);
                return;
            }
            if (data.type === 'notebook') {
                this.openNotebookTab({
                    name: data.name,
                    path: data.path,
                    cells: data.cells || []
                });
            } else if (data.type === 'text') {
                if ((data.ext || '').toLowerCase() === '.md') {
                    this.openMarkdownTab({
                        name: data.name,
                        path: data.path,
                        content: data.content || ''
                    });
                } else {
                    this.openTextTab({
                        name: data.name,
                        path: data.path,
                        content: data.content || ''
                    });
                }
            } else if (data.type === 'image') {
                this.openImageTab({
                    name: data.name,
                    path: data.path,
                    content: data.content,
                    mime: data.mime || 'image/png'
                });
            }
        })
        .catch(error => {
            alert(`${this.t('status.openFailed')}: ${error.message}`);
        });
    },

    openNotebookTab(tab) {
        this.persistActiveTab();
        const hasCells = this.codeCells.length > 0;
        const activeTab = this.getActiveTab();
        if (hasCells && this.activeTabId && activeTab && activeTab.type !== 'notebook') {
            const ok = confirm(this.t('notebook.openConfirm'));
            if (!ok) return;
        }
        const existing = this.openTabs.find(t => t.path === tab.path);
        if (existing) {
            this.setActiveTab(existing.id);
            return;
        }
        const id = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        const kernelId = tab.path || 'default.ipynb';
        const kernelName = tab.kernelName || 'python3';
        this.openTabs.push({
            id,
            name: tab.name,
            path: tab.path,
            type: 'notebook',
            cells: tab.cells,
            kernelId,
            kernelName
        });
        this.setActiveTab(id);
    },

    openTextTab(tab) {
        this.persistActiveTab();
        const existing = this.openTabs.find(t => t.path === tab.path);
        if (existing) {
            this.setActiveTab(existing.id);
            return;
        }
        const id = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        this.openTabs.push({
            id,
            name: tab.name,
            path: tab.path,
            type: 'text',
            content: tab.content
        });
        this.setActiveTab(id);
    },

    openMarkdownTab(tab) {
        this.persistActiveTab();
        const existing = this.openTabs.find(t => t.path === tab.path);
        if (existing) {
            this.setActiveTab(existing.id);
            return;
        }
        const id = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        this.openTabs.push({
            id,
            name: tab.name,
            path: tab.path,
            type: 'markdown',
            content: tab.content
        });
        this.setActiveTab(id);
    },

    openSkillTab(tab) {
        this.persistActiveTab();
        const existing = this.openTabs.find(t => t.type === 'skill' && t.path === tab.path);
        if (existing) {
            existing.content = tab.content || existing.content || '';
            existing.name = tab.name || existing.name;
            existing.referenceContent = tab.referenceContent || existing.referenceContent || '';
            existing.referencePath = tab.referencePath || existing.referencePath || '';
            existing.editable = tab.editable !== false;
            this.setActiveTab(existing.id);
            return;
        }
        const id = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        this.openTabs.push({
            id,
            name: tab.name || 'SKILL.md',
            path: tab.path,
            type: 'skill',
            content: tab.content || '',
            referenceContent: tab.referenceContent || '',
            referencePath: tab.referencePath || '',
            editable: tab.editable !== false,
        });
        this.setActiveTab(id);
    },

    openImageTab(tab) {
        this.persistActiveTab();
        const existing = this.openTabs.find(t => t.path === tab.path);
        if (existing) {
            this.setActiveTab(existing.id);
            return;
        }
        const id = `tab-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
        this.openTabs.push({
            id,
            name: tab.name,
            path: tab.path,
            type: 'image',
            content: tab.content,
            mime: tab.mime
        });
        this.setActiveTab(id);
    },

    setActiveTab(tabId, shouldPersist = true) {
        if (shouldPersist) {
            this.persistActiveTab();
        }
        this.activeTabId = tabId;
        const tab = this.getActiveTab();
        this.renderTabs();
        if (!tab) return;
        if (tab.type === 'notebook') {
            this.loadNotebookCells(tab.cells || []);
            if (tab.outputs) {
                this.restoreNotebookOutputs(tab.outputs);
            }
            if (tab.scrollPos) {
                const savedPos = tab.scrollPos;
                requestAnimationFrame(() => this._setNotebookScrollTop(savedPos));
            }
            this.updateKernelSelectorForTab(tab);
            this.fetchKernelStats(tab.kernelId);
            this.fetchKernelVars(tab.kernelId);
        } else if (tab.type === 'text') {
            this.showTextFile(tab.content || '', tab.name || '');
            this.updateKernelSelectorForTab(null);
        } else if (tab.type === 'skill') {
            this.showMarkdownFile(tab.content || '', {
                filename: tab.name || 'SKILL.md',
                editable: tab.editable !== false,
            });
            this.updateKernelSelectorForTab(null);
        } else if (tab.type === 'markdown') {
            this.showMarkdownFile(tab.content || '', {
                filename: tab.name || 'README.md',
                editable: true,
            });
            this.updateKernelSelectorForTab(null);
        } else if (tab.type === 'image') {
            this.showImageFile(tab);
            this.updateKernelSelectorForTab(null);
        } else if (tab.type === 'var') {
            this.showVarDetail(tab.detail || {});
            this.updateKernelSelectorForTab(null);
        }
    },

    getActiveTab() {
        return this.openTabs.find(t => t.id === this.activeTabId);
    },

    getActiveKernelId() {
        const tab = this.getActiveTab();
        if (tab && tab.type === 'notebook') {
            return tab.kernelId || tab.path || 'default.ipynb';
        }
        if (tab && tab.type === 'var') {
            return tab.kernelId || 'default.ipynb';
        }
        return null;
    },

    renderTabs() {
        const tabs = document.getElementById('editor-tabs');
        if (!tabs) return;
        if (this.openTabs.length === 0) {
            tabs.style.display = 'none';
            return;
        }
        tabs.style.display = 'flex';
        tabs.innerHTML = '';
        this.openTabs.forEach(tab => {
            const item = document.createElement('div');
            item.className = `editor-tab ${tab.id === this.activeTabId ? 'active' : ''}`;
            const name = document.createElement('span');
            name.textContent = tab.name;
            const close = document.createElement('span');
            close.className = 'tab-close';
            close.textContent = '×';
            close.addEventListener('click', (e) => {
                e.stopPropagation();
                this.closeTab(tab.id);
            });
            item.appendChild(name);
            item.appendChild(close);
            item.addEventListener('click', () => this.setActiveTab(tab.id));
            tabs.appendChild(item);
        });
    },

    closeTab(tabId) {
        const index = this.openTabs.findIndex(t => t.id === tabId);
        if (index === -1) return;
        if (this.activeTabId === tabId) {
            this.persistActiveTab();
        }
        this.openTabs.splice(index, 1);
        if (this.activeTabId === tabId) {
            const next = this.openTabs[index] || this.openTabs[index - 1];
            this.activeTabId = next ? next.id : null;
            if (next) {
                this.setActiveTab(next.id, false);
            } else {
                this.renderTabs();
                this.showTextFile('');
                const container = document.getElementById('code-cells-container');
                if (container) container.innerHTML = '';
                this.codeCells = [];
                this.cellCounter = 0;
                this.updateKernelSelectorForTab(null);
            }
        } else {
            this.renderTabs();
        }
    },

    showTextFile(content, filename) {
        const container  = document.getElementById('code-cells-container');
        const textView   = document.getElementById('text-file-view');
        const textEditor = document.getElementById('text-file-editor');
        const cmWrap     = document.getElementById('cm-editor-wrap');
        const varView    = document.getElementById('var-detail-view');
        const mdView     = document.getElementById('md-file-view');
        const imageView  = document.getElementById('image-file-view');

        if (container)  container.style.display  = 'none';
        if (varView)    varView.style.display     = 'none';
        if (mdView)     mdView.style.display      = 'none';
        if (imageView)  imageView.style.display   = 'none';
        if (textView)   textView.style.display    = 'block';

        this.destroyActiveEditors();

        // Determine CodeMirror mode from file extension
        const normalizedName = String(filename || '').trim().toLowerCase();
        const ext = normalizedName.includes('.') ? normalizedName.split('.').pop() : normalizedName;
        const CM_MODE_MAP = {
            py:   'python',
            js:   'javascript',
            ts:   'javascript',
            json: 'application/json',
            sh:   'shell',
            bash: 'shell',
            zsh:  'shell',
            yaml: 'yaml',
            yml:  'yaml',
            toml: 'toml',
            ini:  'properties',
            cfg:  'properties',
            conf: 'properties',
            env:  'properties',
            properties: 'properties',
            html: 'htmlmixed',
            htm:  'htmlmixed',
            css:  'css',
            xml:  'xml',
            r:    'r',
        };
        let cmMode = CM_MODE_MAP[ext];
        if (!cmMode) {
            if (normalizedName === 'dockerfile' || normalizedName.endsWith('/dockerfile')) {
                cmMode = 'shell';
            } else if (
                normalizedName.endsWith('.bashrc') ||
                normalizedName.endsWith('.bash_profile') ||
                normalizedName.endsWith('.zshrc') ||
                normalizedName.endsWith('.zprofile')
            ) {
                cmMode = 'shell';
            }
        }

        if (cmMode && window.CodeMirror) {
            // ── CodeMirror highlighted editor ────────────────────────────
            if (textEditor) textEditor.style.display = 'none';
            if (cmWrap)     cmWrap.style.display     = 'block';

            // Move textarea value first so CM can seed from it
            if (textEditor) textEditor.value = content;

            const isDark = document.documentElement.classList.contains('app-skin-dark') ||
                           document.body.classList.contains('app-skin-dark');

            this._cmEditor = window.CodeMirror.fromTextArea(textEditor, {
                mode:          cmMode,
                theme:         isDark ? 'dracula' : 'default',
                lineNumbers:   true,
                lineWrapping:  false,
                indentUnit:    4,
                tabSize:       4,
                indentWithTabs: false,
                autofocus:     true,
                extraKeys:     { 'Tab': cm => cm.replaceSelection('    ') },
            });

            // Fill the cmWrap container (not the original textarea location)
            if (cmWrap) cmWrap.appendChild(this._cmEditor.getWrapperElement());
            this._cmEditor.getWrapperElement().style.cssText =
                'height:100%; font-size:13px; font-family:"JetBrains Mono","Fira Code",Menlo,monospace;';
            this._cmEditor.setValue(content);
            // Let CM measure itself after the container becomes visible
            setTimeout(() => { if (this._cmEditor) this._cmEditor.refresh(); }, 50);

        } else {
            // ── plain textarea (non-code or CM not loaded) ───────────────
            if (cmWrap)     cmWrap.style.display     = 'none';
            if (textEditor) { textEditor.style.display = ''; textEditor.value = content; }
        }
    },

    showMarkdownFile(content, options = {}) {
        const container = document.getElementById('code-cells-container');
        const textView = document.getElementById('text-file-view');
        const varView = document.getElementById('var-detail-view');
        const mdView = document.getElementById('md-file-view');
        const imageView = document.getElementById('image-file-view');
        const editor = document.getElementById('md-file-editor');
        const preview = document.getElementById('md-file-preview');
        if (container) container.style.display = 'none';
        if (textView) textView.style.display = 'none';
        if (varView) varView.style.display = 'none';
        if (imageView) imageView.style.display = 'none';
        if (mdView) mdView.style.display = 'flex';
        this.destroyActiveEditors();
        if (!editor) {
            this.refreshMarkdownPreview(preview, content);
            return;
        }

        editor.value = content || '';
        editor.style.display = '';
        editor.readOnly = options.editable === false;

        const isDark = document.documentElement.classList.contains('app-skin-dark') ||
                       document.body.classList.contains('app-skin-dark');

        if (window.CodeMirror) {
            this._mdEditor = window.CodeMirror.fromTextArea(editor, {
                mode: 'markdown',
                theme: isDark ? 'dracula' : 'default',
                lineNumbers: true,
                lineWrapping: true,
                indentUnit: 2,
                tabSize: 2,
                indentWithTabs: false,
                autofocus: true,
                readOnly: options.editable === false,
            });
            this._mdEditor.on('change', (cm) => {
                this.refreshMarkdownPreview(preview, cm.getValue());
            });
            this._mdEditor.setValue(content || '');
            setTimeout(() => {
                if (this._mdEditor) this._mdEditor.refresh();
            }, 50);
        } else {
            editor.oninput = () => {
                this.refreshMarkdownPreview(preview, editor.value);
            };
        }

        this.refreshMarkdownPreview(preview, content);
    },

    showImageFile(tab) {
        const container = document.getElementById('code-cells-container');
        const textView = document.getElementById('text-file-view');
        const varView = document.getElementById('var-detail-view');
        const mdView = document.getElementById('md-file-view');
        const imageView = document.getElementById('image-file-view');
        const image = document.getElementById('image-file-content');
        if (container) container.style.display = 'none';
        if (textView) textView.style.display = 'none';
        if (varView) varView.style.display = 'none';
        if (mdView) mdView.style.display = 'none';
        if (imageView) imageView.style.display = 'block';
        this.destroyActiveEditors();
        if (image && tab.content) {
            image.src = `data:${tab.mime};base64,${tab.content}`;
        }
    },

    openContextMenu(x, y, path, isDir) {
        const menu = document.getElementById('file-context-menu');
        if (!menu) return;
        this.contextTargetPath = path || '';
        this.contextTargetIsDir = isDir;
        menu.style.left = `${x}px`;
        menu.style.top = `${y}px`;
        menu.style.display = 'block';
        const deleteItem = menu.querySelector('[data-action="delete"]');
        if (deleteItem) deleteItem.style.display = path ? 'flex' : 'none';
        const renameItem = menu.querySelector('[data-action="rename"]');
        if (renameItem) renameItem.style.display = path ? 'flex' : 'none';
        const copyItem = menu.querySelector('[data-action="copy"]');
        if (copyItem) copyItem.style.display = path ? 'flex' : 'none';
        const moveItem = menu.querySelector('[data-action="move"]');
        if (moveItem) moveItem.style.display = path ? 'flex' : 'none';
        const pasteItem = menu.querySelector('[data-action="paste"]');
        if (pasteItem) pasteItem.style.display = this.contextClipboard ? 'flex' : 'none';
    },

    hideContextMenu() {
        const menu = document.getElementById('file-context-menu');
        if (menu) menu.style.display = 'none';
    },

    contextNewFile() {
        const name = prompt(this.t('prompt.newFile'));
        if (!name) return;
        const base = this.contextTargetIsDir ? this.contextTargetPath : (this.contextTargetPath.split('/').slice(0, -1).join('/'));
        const path = base ? `${base}/${name}` : name;
        fetch('/api/files/create', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ path, type: 'file' })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.createFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.createFailed')}: ${err.message}`));
    },

    contextNewFolder() {
        const name = prompt(this.t('prompt.newFolder'));
        if (!name) return;
        const base = this.contextTargetIsDir ? this.contextTargetPath : (this.contextTargetPath.split('/').slice(0, -1).join('/'));
        const path = base ? `${base}/${name}` : name;
        fetch('/api/files/create', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ path, type: 'folder' })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.createFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.createFailed')}: ${err.message}`));
    },

    contextRefresh() {
        this.fetchFileTree();
    },

    contextDelete() {
        if (!this.contextTargetPath) return;
        const ok = confirm(this.t('prompt.deleteConfirm') + ` ${this.contextTargetPath}?`);
        if (!ok) return;
        fetch('/api/files/delete', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ path: this.contextTargetPath })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.deleteFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.deleteFailed')}: ${err.message}`));
    },

    contextRename() {
        if (!this.contextTargetPath) return;
        const base = this.contextTargetPath.split('/').slice(0, -1).join('/');
        const currentName = this.contextTargetPath.split('/').pop();
        const name = prompt(this.t('prompt.renameTo'), currentName);
        if (!name || name === currentName) return;
        const dst = base ? `${base}/${name}` : name;
        fetch('/api/files/rename', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ src: this.contextTargetPath, dst })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.renameFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.renameFailed')}: ${err.message}`));
    },

    contextCopy() {
        if (!this.contextTargetPath) return;
        this.contextClipboard = { path: this.contextTargetPath, mode: 'copy' };
    },

    contextPaste() {
        if (!this.contextClipboard) return;
        const base = this.contextTargetIsDir ? this.contextTargetPath : (this.contextTargetPath.split('/').slice(0, -1).join('/'));
        const name = this.contextClipboard.path.split('/').pop();
        const dst = base ? `${base}/${name}` : name;
        fetch('/api/files/copy', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ src: this.contextClipboard.path, dst })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.pasteFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.pasteFailed')}: ${err.message}`));
    },

    contextMove() {
        if (!this.contextTargetPath) return;
        const dst = prompt(this.t('prompt.moveTo'), this.contextTargetPath);
        if (!dst || dst === this.contextTargetPath) return;
        fetch('/api/files/move', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ src: this.contextTargetPath, dst })
        })
        .then(res => res.json())
        .then(data => {
            if (data.error) {
                alert(`${this.t('status.moveFailed')}: ${data.error}`);
                return;
            }
            this.fetchFileTree();
        })
        .catch(err => alert(`${this.t('status.moveFailed')}: ${err.message}`));
    },

    _getNotebookScrollTop() {
        // Find and return the current scroll position of the notebook area
        const mc = document.querySelector('.main-content');
        if (mc && mc.scrollTop > 0) return { target: 'main-content', top: mc.scrollTop };
        const cc = document.getElementById('code-cells-container');
        if (cc && cc.scrollTop > 0) return { target: 'code-cells-container', top: cc.scrollTop };
        return { target: 'main-content', top: 0 };
    },

    _setNotebookScrollTop(saved) {
        if (!saved) return;
        const el = saved.target === 'code-cells-container'
            ? document.getElementById('code-cells-container')
            : document.querySelector('.main-content');
        if (el) el.scrollTop = saved.top || 0;
    },

    persistActiveTab() {
        const active = this.getActiveTab();
        if (!active) return;
        if (active.type === 'notebook') {
            active.cells = this.buildNotebookCellsFromUI();
            active.outputs = this.captureNotebookOutputs();
            active.scrollPos = this._getNotebookScrollTop();
            return;
        }
        if (active.type === 'markdown' || active.type === 'text' || active.type === 'skill') {
            active.content = this.getTextEditorContent();
            return;
        }
    }

});
