/**
 * SidebarManager - Manages hierarchical folder navigation sidebar
 */
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
import { getModelApiClient } from '../api/modelApiFactory.js';
import { translate } from '../utils/i18nHelpers.js';
import { state, getCurrentPageState } from '../state/index.js';
import { bulkManager } from '../managers/BulkManager.js';
import { showToast } from '../utils/uiHelpers.js';
import { performFolderUpdateCheck } from '../utils/updateCheckHelpers.js';
import { escapeHtml, escapeAttribute } from './shared/utils.js';
import { MODEL_CARD_DRAG_MIME_TYPE } from '../utils/constants.js';

export class SidebarManager {
    constructor() {
        this.pageControls = null;
        this.pageType = null;
        this.treeData = {};
        this.folderTreeLoaded = false;
        this.selectedPath = '';
        this.expandedNodes = new Set();
        this.apiClient = null;
        this.openDropdown = null;
        this.isInitialized = false;
        this.displayMode = 'tree'; // 'tree' or 'list'
        this.foldersList = [];
        this.recursiveSearchEnabled = true;
        this.draggedFilePaths = null;
        this.draggedRootPath = null;
        this.draggedFromBulk = false;
        this.dragHandlersInitialized = false;
        this.sidebarDragHandlersInitialized = false;
        this.folderTreeElement = null;
        this.currentDropTarget = null;
        this.lastPageControls = null;
        this.isDisabledByPage = false;
        this.initializationPromise = null;
        this.isCreatingFolder = false;
        this._pendingDragState = null; // 用于保存拖拽创建文件夹时的状态

        // Bind methods
        this.handleTreeClick = this.handleTreeClick.bind(this);
        this.handleTreeContextMenu = this.handleTreeContextMenu.bind(this);
        this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this);
        this.handleDocumentClick = this.handleDocumentClick.bind(this);
        this.handleSidebarHeaderClick = this.handleSidebarHeaderClick.bind(this);
        this.handleCollapseAll = this.handleCollapseAll.bind(this);
        this.updateContainerMargin = this.updateContainerMargin.bind(this);
        this.handleDisplayModeToggle = this.handleDisplayModeToggle.bind(this);
        this.handleFolderListClick = this.handleFolderListClick.bind(this);
        this.handleRecursiveToggle = this.handleRecursiveToggle.bind(this);
        this.handleCardDragStart = this.handleCardDragStart.bind(this);
        this.handleCardDragEnd = this.handleCardDragEnd.bind(this);
        this.handleFolderDragEnter = this.handleFolderDragEnter.bind(this);
        this.handleFolderDragOver = this.handleFolderDragOver.bind(this);
        this.handleFolderDragLeave = this.handleFolderDragLeave.bind(this);
        this.handleFolderDrop = this.handleFolderDrop.bind(this);
        this.handleSidebarDragEnter = this.handleSidebarDragEnter.bind(this);
        this.handleSidebarDragOver = this.handleSidebarDragOver.bind(this);
        this.handleSidebarDragLeave = this.handleSidebarDragLeave.bind(this);
        this.handleSidebarDrop = this.handleSidebarDrop.bind(this);
        this.handleCreateFolderSubmit = this.handleCreateFolderSubmit.bind(this);
        this.handleCreateFolderCancel = this.handleCreateFolderCancel.bind(this);
        this.handleHideToggle = this.handleHideToggle.bind(this);
        this.getPageDisplayName = this.getPageDisplayName.bind(this);
    }

    setHostPageControls(pageControls) {
        this.lastPageControls = pageControls;
    }

    async initialize(pageControls, options = {}) {
        // Clean up previous initialization if exists
        if (this.isInitialized) {
            this.cleanup();
        }

        this.pageControls = pageControls;
        this.pageType = pageControls.pageType;
        this.lastPageControls = pageControls;
        this.apiClient = pageControls?.getSidebarApiClient?.()
            || pageControls?.sidebarApiClient
            || getModelApiClient();

        this.setupEventHandlers();
        this.initializeDragAndDrop();
        this.updateSidebarTitle();
        this.restoreSidebarState();
        // Apply DOM visibility based on per-page state
        this.updateDomVisibility();
        await this.loadFolderTree();
        this.restoreSelectedFolder();

        // Update container margin based on initial sidebar state
        this.updateContainerMargin();

        this.isInitialized = true;
        console.log(`SidebarManager initialized for ${this.pageType} page`);
    }

    cleanup() {
        if (!this.isInitialized) return;

        // Clean up event handlers
        this.removeEventHandlers();

        this.clearAllDropHighlights();
        this.resetDragState();
        this.hideCreateFolderInput();

        // Cleanup sidebar drag handlers
        const sidebar = document.getElementById('folderSidebar');
        if (sidebar && this.sidebarDragHandlersInitialized) {
            sidebar.removeEventListener('dragenter', this.handleSidebarDragEnter);
            sidebar.removeEventListener('dragover', this.handleSidebarDragOver);
            sidebar.removeEventListener('dragleave', this.handleSidebarDragLeave);
            sidebar.removeEventListener('drop', this.handleSidebarDrop);
            this.sidebarDragHandlersInitialized = false;
        }

        this.hideSidebarHiddenIndicator();

        // Reset state
        this.pageControls = null;
        this.pageType = null;
        this.treeData = {};
        this.selectedPath = '';
        this.expandedNodes = new Set();
        this.openDropdown = null;
        this.isDisabledByPage = false;
        this.apiClient = null;
        this.isInitialized = false;
        this.recursiveSearchEnabled = true;

        // Reset container margin
        const container = document.querySelector('.container');
        if (container) {
            container.style.marginLeft = '';
        }

        // Remove resize event listener
        window.removeEventListener('resize', this.updateContainerMargin);

        console.log('SidebarManager cleaned up');
        this.initializationPromise = null;
    }

    removeEventHandlers() {
        const collapseAllBtn = document.getElementById('sidebarCollapseAll');
        const folderTree = document.getElementById('sidebarFolderTree');
        const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav');
        const sidebarHeader = document.getElementById('sidebarHeader');
        const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle');
        const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle');

        if (collapseAllBtn) {
            collapseAllBtn.removeEventListener('click', this.handleCollapseAll);
        }
        if (folderTree) {
            folderTree.removeEventListener('click', this.handleTreeClick);
            folderTree.removeEventListener('contextmenu', this.handleTreeContextMenu);
            folderTree.removeEventListener('dragover', this.handleFolderDragOver);
        }
        if (sidebarBreadcrumbNav) {
            sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick);
        }
        if (sidebarHeader) {
            sidebarHeader.removeEventListener('click', this.handleSidebarHeaderClick);
        }

        // Remove document click handler
        document.removeEventListener('click', this.handleDocumentClick);

        // Remove resize event handler
        window.removeEventListener('resize', this.updateContainerMargin);

        if (displayModeToggleBtn) {
            displayModeToggleBtn.removeEventListener('click', this.handleDisplayModeToggle);
        }
        if (recursiveToggleBtn) {
            recursiveToggleBtn.removeEventListener('click', this.handleRecursiveToggle);
        }

        const hideToggle = document.getElementById('sidebarHideToggle');
        if (hideToggle) {
            hideToggle.removeEventListener('click', this.handleHideToggle);
        }
    }

    initializeDragAndDrop() {
        if (this.apiClient?.apiConfig?.config?.supportsMove === false) {
            return;
        }

        if (!this.dragHandlersInitialized) {
            document.addEventListener('dragstart', this.handleCardDragStart);
            document.addEventListener('dragend', this.handleCardDragEnd);
            this.dragHandlersInitialized = true;
        }

        const folderTree = document.getElementById('sidebarFolderTree');
        if (folderTree && this.folderTreeElement !== folderTree) {
            if (this.folderTreeElement) {
                this.folderTreeElement.removeEventListener('dragenter', this.handleFolderDragEnter);
                this.folderTreeElement.removeEventListener('dragover', this.handleFolderDragOver);
                this.folderTreeElement.removeEventListener('dragleave', this.handleFolderDragLeave);
                this.folderTreeElement.removeEventListener('drop', this.handleFolderDrop);
            }

            folderTree.addEventListener('dragenter', this.handleFolderDragEnter);
            folderTree.addEventListener('dragover', this.handleFolderDragOver);
            folderTree.addEventListener('dragleave', this.handleFolderDragLeave);
            folderTree.addEventListener('drop', this.handleFolderDrop);

            this.folderTreeElement = folderTree;
        }

        // Add sidebar-level drag handlers for creating new folders
        const sidebar = document.getElementById('folderSidebar');
        if (sidebar && !this.sidebarDragHandlersInitialized) {
            sidebar.addEventListener('dragenter', this.handleSidebarDragEnter);
            sidebar.addEventListener('dragover', this.handleSidebarDragOver);
            sidebar.addEventListener('dragleave', this.handleSidebarDragLeave);
            sidebar.addEventListener('drop', this.handleSidebarDrop);
            this.sidebarDragHandlersInitialized = true;
        }
    }

    handleCardDragStart(event) {
        const card = event.target.closest('.model-card');
        if (!card) return;

        const filePath = card.dataset.filepath;
        if (!filePath) return;

        const selectedSet = state.selectedModels instanceof Set
            ? state.selectedModels
            : new Set(state.selectedModels || []);
        const cardIsSelected = card.classList.contains('selected');
        const usingBulkSelection = Boolean(state.bulkMode && cardIsSelected && selectedSet && selectedSet.size > 0);

        const paths = usingBulkSelection ? Array.from(selectedSet) : [filePath];
        const filePaths = Array.from(new Set(paths.filter(Boolean)));

        if (filePaths.length === 0) {
            return;
        }

        this.draggedFilePaths = filePaths;
        this.draggedRootPath = this.getRootPathFromCard(card);
        this.draggedFromBulk = usingBulkSelection;

        const dataTransfer = event.dataTransfer;
        if (dataTransfer) {
            dataTransfer.effectAllowed = 'move';
            dataTransfer.setData('text/plain', filePaths.join(','));
            // Tag the drag as an internal card drag so preview-drop handlers on
            // other cards ignore it (no highlight, no preview replacement).
            dataTransfer.setData(MODEL_CARD_DRAG_MIME_TYPE, filePaths.join(','));
            try {
                dataTransfer.setData('application/json', JSON.stringify({ filePaths }));
            } catch (error) {
                // Ignore serialization errors
            }
        }

        card.classList.add('dragging');

        // Add dragging state to sidebar for visual feedback
        const sidebar = document.getElementById('folderSidebar');
        if (sidebar) {
            sidebar.classList.add('dragging-active');
        }
    }

    handleCardDragEnd(event) {
        const card = event.target.closest('.model-card');
        if (card) {
            card.classList.remove('dragging');
        }
        
        // Remove dragging state from sidebar
        const sidebar = document.getElementById('folderSidebar');
        if (sidebar) {
            sidebar.classList.remove('dragging-active');
        }
        
        this.clearAllDropHighlights();
        this.resetDragState();
    }

    getRootPathFromCard(card) {
        if (!card) return null;

        const filePathRaw = card.dataset.filepath || '';
        const normalizedFilePath = filePathRaw.replace(/\\/g, '/');
        const lastSlashIndex = normalizedFilePath.lastIndexOf('/');
        if (lastSlashIndex === -1) {
            return null;
        }

        const directory = normalizedFilePath.substring(0, lastSlashIndex);
        let folderValue = card.dataset.folder;
        if (!folderValue || folderValue === 'undefined') {
            folderValue = '';
        }
        const normalizedFolder = folderValue.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');

        if (!normalizedFolder) {
            return directory;
        }

        const suffix = `/${normalizedFolder}`;
        if (directory.endsWith(suffix)) {
            return directory.slice(0, -suffix.length);
        }

        return directory;
    }

    combineRootAndRelativePath(root, relative) {
        const normalizedRoot = (root || '').replace(/\\/g, '/').replace(/\/+$/g, '');
        const normalizedRelative = (relative || '').replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');

        if (!normalizedRoot) {
            return normalizedRelative;
        }

        if (!normalizedRelative) {
            return normalizedRoot;
        }

        return `${normalizedRoot}/${normalizedRelative}`;
    }

    getFolderElementFromEvent(event) {
        const folderTree = this.folderTreeElement || document.getElementById('sidebarFolderTree');
        if (!folderTree) return null;

        const target = event.target instanceof Element ? event.target.closest('[data-path]') : null;
        if (!target || !folderTree.contains(target)) {
            return null;
        }

        return target;
    }

    setDropTargetHighlight(element, shouldAdd) {
        if (!element) return;

        let targetElement = element;
        if (!targetElement.classList.contains('sidebar-tree-node-content') &&
            !targetElement.classList.contains('sidebar-node-content')) {
            targetElement = element.querySelector('.sidebar-tree-node-content, .sidebar-node-content');
        }

        if (targetElement) {
            targetElement.classList.toggle('drop-target', shouldAdd);
        }
    }

    handleFolderDragEnter(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (!folderElement) return;

        event.preventDefault();

        if (event.dataTransfer) {
            event.dataTransfer.dropEffect = 'move';
        }

        this.setDropTargetHighlight(folderElement, true);
        this.currentDropTarget = folderElement;
    }

    handleFolderDragOver(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (!folderElement) return;

        event.preventDefault();

        if (event.dataTransfer) {
            event.dataTransfer.dropEffect = 'move';
        }
    }

    handleFolderDragLeave(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (!folderElement) return;

        const relatedTarget = event.relatedTarget instanceof Element ? event.relatedTarget : null;
        if (!relatedTarget || !folderElement.contains(relatedTarget)) {
            this.setDropTargetHighlight(folderElement, false);
            if (this.currentDropTarget === folderElement) {
                this.currentDropTarget = null;
            }
        }
    }

    async handleFolderDrop(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (!folderElement) return;

        event.preventDefault();
        event.stopPropagation();

        this.setDropTargetHighlight(folderElement, false);
        this.currentDropTarget = null;

        const targetPath = folderElement.dataset.path || '';

        await this.performDragMove(targetPath);

        this.resetDragState();
        this.clearAllDropHighlights();
    }

    async performDragMove(targetRelativePath) {
        console.log('[SidebarManager] performDragMove called with targetRelativePath:', targetRelativePath);
        console.log('[SidebarManager] draggedFilePaths:', this.draggedFilePaths);
        console.log('[SidebarManager] draggedRootPath:', this.draggedRootPath);
        
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) {
            console.log('[SidebarManager] performDragMove returning false - no draggedFilePaths');
            return false;
        }

        if (!this.apiClient) {
            this.apiClient = this.pageControls?.getSidebarApiClient?.()
                || this.pageControls?.sidebarApiClient
                || getModelApiClient();
        }

        if (this.apiClient?.apiConfig?.config?.supportsMove === false) {
            console.log('[SidebarManager] performDragMove returning false - supportsMove is false');
            showToast('toast.models.moveFailed', { message: translate('sidebar.dragDrop.moveUnsupported', {}, 'Move not supported for this page') }, 'error');
            return false;
        }

        const rootPath = this.draggedRootPath ? this.draggedRootPath.replace(/\\/g, '/') : '';
        console.log('[SidebarManager] rootPath:', rootPath);
        if (!rootPath) {
            console.log('[SidebarManager] performDragMove returning false - no rootPath');
            showToast(
                'toast.models.moveFailed',
                { message: translate('sidebar.dragDrop.unableToResolveRoot', {}, 'Unable to determine destination path for move.') },
                'error'
            );
            return false;
        }

        const destination = this.combineRootAndRelativePath(rootPath, targetRelativePath);
        const useBulkMove = this.draggedFromBulk || this.draggedFilePaths.length > 1;

        try {
            console.log('[SidebarManager] calling apiClient.move, useBulkMove:', useBulkMove);
            let movedFiles = []; // Array of { original_file_path, new_file_path }

            if (useBulkMove) {
                const results = await this.apiClient.moveBulkModels(this.draggedFilePaths, destination);
                movedFiles = (results || [])
                    .filter(r => r.success)
                    .map(r => ({ original_file_path: r.original_file_path, new_file_path: r.new_file_path }));
            } else {
                const result = await this.apiClient.moveSingleModel(this.draggedFilePaths[0], destination);
                if (result) {
                    movedFiles.push({
                        original_file_path: result.original_file_path || this.draggedFilePaths[0],
                        new_file_path: result.new_file_path
                    });
                }
            }
            console.log('[SidebarManager] apiClient.move successful');

            // Update VirtualScroller in-place instead of full reload
            if (movedFiles.length > 0 && state.virtualScroller) {
                const pageState = getCurrentPageState();
                const normalizedActive = (pageState.activeFolder || '').replace(/\\/g, '/').replace(/\/$/, '');
                const isRecursive = pageState.searchOptions?.recursive ?? true;
                const isFolderFiltered = pageState.activeFolder !== null;

                const normalizedTarget = targetRelativePath.replace(/\\/g, '/').replace(/\/$/, '');

                // Determine if items in the target folder are visible in the current view
                let itemsRemainVisible = true;
                if (isFolderFiltered) {
                    if (isRecursive) {
                        itemsRemainVisible = normalizedActive === '' ||
                            normalizedTarget === normalizedActive ||
                            normalizedTarget.startsWith(normalizedActive + '/');
                    } else {
                        itemsRemainVisible = normalizedTarget === normalizedActive;
                    }
                }

                if (itemsRemainVisible) {
                    // Items stay visible — update each item's file_path to reflect new location
                    for (const moved of movedFiles) {
                        if (moved.original_file_path && moved.new_file_path) {
                            state.virtualScroller.updateSingleItem(moved.original_file_path, {
                                file_path: moved.new_file_path,
                                folder: normalizedTarget
                            });
                        }
                    }
                } else {
                    // Items no longer visible in current folder — remove from VirtualScroller
                    const pathsToRemove = movedFiles
                        .map(m => m.original_file_path)
                        .filter(Boolean);
                    if (pathsToRemove.length > 0) {
                        state.virtualScroller.removeMultipleItemsByFilePath(pathsToRemove);
                    }
                }
            }

            // Refresh sidebar folder tree only (no model data reload)
            await this.refresh();

            if (this.draggedFromBulk && state.bulkMode && typeof bulkManager?.toggleBulkMode === 'function') {
                bulkManager.toggleBulkMode();
            }

            console.log('[SidebarManager] performDragMove returning true');
            return true;
        } catch (error) {
            console.error('[SidebarManager] Error moving model(s) via drag-and-drop:', error);
            showToast('toast.models.moveFailed', { message: error.message || 'Unknown error' }, 'error');
            console.log('[SidebarManager] performDragMove returning false due to error');
            return false;
        }
    }

    resetDragState() {
        this.draggedFilePaths = null;
        this.draggedRootPath = null;
        this.draggedFromBulk = false;
    }

    // Version of performDragMove that accepts state as parameters (for create folder submit)
    async performDragMoveWithState(targetRelativePath, draggedFilePaths, draggedRootPath, draggedFromBulk) {
        console.log('[SidebarManager] performDragMoveWithState called with:', { targetRelativePath, draggedFilePaths, draggedRootPath, draggedFromBulk });

        if (!draggedFilePaths || draggedFilePaths.length === 0) {
            console.log('[SidebarManager] performDragMoveWithState returning false - no draggedFilePaths');
            return false;
        }

        if (!this.apiClient) {
            this.apiClient = this.pageControls?.getSidebarApiClient?.()
                || this.pageControls?.sidebarApiClient
                || getModelApiClient();
        }

        if (this.apiClient?.apiConfig?.config?.supportsMove === false) {
            console.log('[SidebarManager] performDragMoveWithState returning false - supportsMove is false');
            showToast('toast.models.moveFailed', { message: translate('sidebar.dragDrop.moveUnsupported', {}, 'Move not supported for this page') }, 'error');
            return false;
        }

        const rootPath = draggedRootPath ? draggedRootPath.replace(/\\/g, '/') : '';
        console.log('[SidebarManager] rootPath:', rootPath);
        if (!rootPath) {
            console.log('[SidebarManager] performDragMoveWithState returning false - no rootPath');
            showToast(
                'toast.models.moveFailed',
                { message: translate('sidebar.dragDrop.unableToResolveRoot', {}, 'Unable to determine destination path for move.') },
                'error'
            );
            return false;
        }

        const destination = this.combineRootAndRelativePath(rootPath, targetRelativePath);
        const useBulkMove = draggedFromBulk || draggedFilePaths.length > 1;

        try {
            console.log('[SidebarManager] calling apiClient.move, useBulkMove:', useBulkMove);
            let movedFiles = []; // Array of { original_file_path, new_file_path }

            if (useBulkMove) {
                const results = await this.apiClient.moveBulkModels(draggedFilePaths, destination);
                movedFiles = (results || [])
                    .filter(r => r.success)
                    .map(r => ({ original_file_path: r.original_file_path, new_file_path: r.new_file_path }));
            } else {
                const result = await this.apiClient.moveSingleModel(draggedFilePaths[0], destination);
                if (result) {
                    movedFiles.push({
                        original_file_path: result.original_file_path || draggedFilePaths[0],
                        new_file_path: result.new_file_path
                    });
                }
            }
            console.log('[SidebarManager] apiClient.move successful');

            // Update VirtualScroller in-place instead of full reload
            if (movedFiles.length > 0 && state.virtualScroller) {
                const pageState = getCurrentPageState();
                const normalizedActive = (pageState.activeFolder || '').replace(/\\/g, '/').replace(/\/$/, '');
                const isRecursive = pageState.searchOptions?.recursive ?? true;
                const isFolderFiltered = pageState.activeFolder !== null;

                const normalizedTarget = targetRelativePath.replace(/\\/g, '/').replace(/\/$/, '');

                // Determine if items in the target folder are visible in the current view
                let itemsRemainVisible = true;
                if (isFolderFiltered) {
                    if (isRecursive) {
                        itemsRemainVisible = normalizedActive === '' ||
                            normalizedTarget === normalizedActive ||
                            normalizedTarget.startsWith(normalizedActive + '/');
                    } else {
                        itemsRemainVisible = normalizedTarget === normalizedActive;
                    }
                }

                if (itemsRemainVisible) {
                    // Items stay visible — update each item's file_path to reflect new location
                    for (const moved of movedFiles) {
                        if (moved.original_file_path && moved.new_file_path) {
                            state.virtualScroller.updateSingleItem(moved.original_file_path, {
                                file_path: moved.new_file_path,
                                folder: normalizedTarget
                            });
                        }
                    }
                } else {
                    // Items no longer visible in current folder — remove from VirtualScroller
                    const pathsToRemove = movedFiles
                        .map(m => m.original_file_path)
                        .filter(Boolean);
                    if (pathsToRemove.length > 0) {
                        state.virtualScroller.removeMultipleItemsByFilePath(pathsToRemove);
                    }
                }
            }

            // Refresh sidebar folder tree only (no model data reload)
            await this.refresh();

            if (draggedFromBulk && state.bulkMode && typeof bulkManager?.toggleBulkMode === 'function') {
                bulkManager.toggleBulkMode();
            }

            console.log('[SidebarManager] performDragMoveWithState returning true');
            return true;
        } catch (error) {
            console.error('[SidebarManager] Error moving model(s) via drag-and-drop:', error);
            showToast('toast.models.moveFailed', { message: error.message || 'Unknown error' }, 'error');
            console.log('[SidebarManager] performDragMoveWithState returning false due to error');
            return false;
        }
    }

    // ===== Sidebar-level drag handlers for creating new folders =====

    handleSidebarDragEnter(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const sidebar = document.getElementById('folderSidebar');
        if (!sidebar) return;

        // Only show create folder zone if not hovering over an existing folder
        const folderElement = this.getFolderElementFromEvent(event);
        if (folderElement) {
            this.hideCreateFolderZone();
            return;
        }

        // Check if drag is within the sidebar tree container area
        const treeContainer = document.querySelector('.sidebar-tree-container');
        if (treeContainer && treeContainer.contains(event.target)) {
            event.preventDefault();
            this.showCreateFolderZone();
        }
    }

    handleSidebarDragOver(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (folderElement) {
            this.hideCreateFolderZone();
            return;
        }

        const treeContainer = document.querySelector('.sidebar-tree-container');
        if (treeContainer && treeContainer.contains(event.target)) {
            event.preventDefault();
            if (event.dataTransfer) {
                event.dataTransfer.dropEffect = 'move';
            }
        }
    }

    handleSidebarDragLeave(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const sidebar = document.getElementById('folderSidebar');
        if (!sidebar) return;

        const relatedTarget = event.relatedTarget instanceof Element ? event.relatedTarget : null;

        // Only hide if leaving the sidebar entirely
        if (!relatedTarget || !sidebar.contains(relatedTarget)) {
            this.hideCreateFolderZone();
        }
    }

    async handleSidebarDrop(event) {
        if (!this.draggedFilePaths || this.draggedFilePaths.length === 0) return;

        const folderElement = this.getFolderElementFromEvent(event);
        if (folderElement) {
            // Let the folder drop handler take over
            return;
        }

        const treeContainer = document.querySelector('.sidebar-tree-container');
        if (!treeContainer || !treeContainer.contains(event.target)) {
            return;
        }

        event.preventDefault();
        event.stopPropagation();

        // Show create folder input
        this.showCreateFolderInput();
    }

    showCreateFolderZone() {
        if (this.isCreatingFolder) return;

        const treeContainer = document.querySelector('.sidebar-tree-container');
        if (!treeContainer) return;

        let zone = document.getElementById('sidebarCreateFolderZone');
        if (!zone) {
            zone = document.createElement('div');
            zone.id = 'sidebarCreateFolderZone';
            zone.className = 'sidebar-create-folder-zone';
            zone.innerHTML = `
                <div class="sidebar-create-folder-content">
                    <i class="fas fa-plus-circle"></i>
                    <span>${translate('sidebar.dragDrop.createFolderHint', {}, 'Release to create new folder')}</span>
                </div>
            `;
            treeContainer.appendChild(zone);
        }

        zone.classList.add('active');
    }

    hideCreateFolderZone() {
        const zone = document.getElementById('sidebarCreateFolderZone');
        if (zone) {
            zone.classList.remove('active');
        }
    }

    showCreateFolderInput() {
        console.log('[SidebarManager] showCreateFolderInput called');
        this.isCreatingFolder = true;
        
        // 立即保存拖拽状态，防止后续事件（如blur）清空状态
        this._pendingDragState = {
            filePaths: this.draggedFilePaths ? [...this.draggedFilePaths] : null,
            rootPath: this.draggedRootPath,
            fromBulk: this.draggedFromBulk
        };
        console.log('[SidebarManager] saved pending drag state:', this._pendingDragState);
        
        this.hideCreateFolderZone();

        const treeContainer = document.querySelector('.sidebar-tree-container');
        if (!treeContainer) return;

        // Remove existing input if any
        this.hideCreateFolderInput();

        const inputContainer = document.createElement('div');
        inputContainer.id = 'sidebarCreateFolderInput';
        inputContainer.className = 'sidebar-create-folder-input-container';
        inputContainer.innerHTML = `
            <div class="sidebar-create-folder-input-wrapper">
                <i class="fas fa-folder-plus"></i>
                <input type="text" 
                       class="sidebar-create-folder-input" 
                       placeholder="${translate('sidebar.dragDrop.newFolderName', {}, 'New folder name')}" 
                       autofocus />
                <button class="sidebar-create-folder-btn sidebar-create-folder-confirm" title="${translate('common.confirm', {}, 'Confirm')}">
                    <i class="fas fa-check"></i>
                </button>
                <button class="sidebar-create-folder-btn sidebar-create-folder-cancel" title="${translate('common.cancel', {}, 'Cancel')}">
                    <i class="fas fa-times"></i>
                </button>
            </div>
            <div class="sidebar-create-folder-hint">
                ${translate('sidebar.dragDrop.folderNameHint', {}, 'Press Enter to confirm, Escape to cancel')}
            </div>
        `;

        treeContainer.appendChild(inputContainer);

        // Focus input
        const input = inputContainer.querySelector('.sidebar-create-folder-input');
        if (input) {
            input.focus();
        }

        // Bind events
        const confirmBtn = inputContainer.querySelector('.sidebar-create-folder-confirm');
        const cancelBtn = inputContainer.querySelector('.sidebar-create-folder-cancel');

        // Flag to prevent blur from canceling when clicking buttons
        let isButtonClick = false;

        confirmBtn?.addEventListener('mousedown', () => { 
            isButtonClick = true; 
            console.log('[SidebarManager] confirmBtn mousedown - isButtonClick set to true');
        });
        cancelBtn?.addEventListener('mousedown', () => { 
            isButtonClick = true; 
            console.log('[SidebarManager] cancelBtn mousedown - isButtonClick set to true');
        });

        confirmBtn?.addEventListener('click', (e) => {
            console.log('[SidebarManager] confirmBtn click event triggered');
            this.handleCreateFolderSubmit();
        });
        cancelBtn?.addEventListener('click', () => {
            console.log('[SidebarManager] cancelBtn click event triggered');
            this.handleCreateFolderCancel();
        });
        input?.addEventListener('keydown', (e) => {
            console.log('[SidebarManager] input keydown:', e.key);
            if (e.key === 'Enter') {
                console.log('[SidebarManager] Enter pressed, calling handleCreateFolderSubmit');
                this.handleCreateFolderSubmit();
            } else if (e.key === 'Escape') {
                console.log('[SidebarManager] Escape pressed, calling handleCreateFolderCancel');
                this.handleCreateFolderCancel();
            }
        });
        input?.addEventListener('blur', () => {
            console.log('[SidebarManager] input blur event - isButtonClick:', isButtonClick);
            // Delay to allow button clicks to process first
            setTimeout(() => {
                console.log('[SidebarManager] blur timeout - isButtonClick:', isButtonClick, 'activeElement:', document.activeElement?.className);
                if (!isButtonClick && document.activeElement !== confirmBtn && document.activeElement !== cancelBtn) {
                    console.log('[SidebarManager] blur timeout - calling handleCreateFolderCancel');
                    this.handleCreateFolderCancel();
                } else {
                    console.log('[SidebarManager] blur timeout - NOT canceling (button click detected)');
                }
                isButtonClick = false;
            }, 200);
        });
    }

    hideCreateFolderInput() {
        console.log('[SidebarManager] hideCreateFolderInput called');
        const inputContainer = document.getElementById('sidebarCreateFolderInput');
        console.log('[SidebarManager] inputContainer:', inputContainer);
        if (inputContainer) {
            inputContainer.remove();
            console.log('[SidebarManager] inputContainer removed');
        }
        this.isCreatingFolder = false;
        console.log('[SidebarManager] isCreatingFolder set to false');
    }

    async handleCreateFolderSubmit() {
        console.log('[SidebarManager] handleCreateFolderSubmit called');
        const input = document.querySelector('#sidebarCreateFolderInput .sidebar-create-folder-input');
        console.log('[SidebarManager] input element:', input);
        if (!input) {
            console.log('[SidebarManager] input not found, returning');
            return;
        }

        const folderName = input.value.trim();
        console.log('[SidebarManager] folderName:', folderName);
        if (!folderName) {
            showToast('sidebar.dragDrop.emptyFolderName', {}, 'warning');
            return;
        }

        // Validate folder name (no slashes, no special chars)
        if (/[\\/:*?"<>|]/.test(folderName)) {
            showToast('sidebar.dragDrop.invalidFolderName', {}, 'error');
            return;
        }

        // Build target path - use selected path as parent, or root if none selected
        const parentPath = this.selectedPath || '';
        const targetRelativePath = parentPath ? `${parentPath}/${folderName}` : folderName;
        console.log('[SidebarManager] targetRelativePath:', targetRelativePath);

        // 使用 showCreateFolderInput 时保存的拖拽状态
        const pendingState = this._pendingDragState;
        console.log('[SidebarManager] using pending drag state:', pendingState);
        
        if (!pendingState || !pendingState.filePaths || pendingState.filePaths.length === 0) {
            console.log('[SidebarManager] no pending drag state found, cannot proceed');
            showToast('sidebar.dragDrop.noDragState', {}, 'error');
            this.hideCreateFolderInput();
            return;
        }

        this.hideCreateFolderInput();

        // Perform the move with saved state
        console.log('[SidebarManager] calling performDragMove with pending state');
        const success = await this.performDragMoveWithState(targetRelativePath, pendingState.filePaths, pendingState.rootPath, pendingState.fromBulk);
        console.log('[SidebarManager] performDragMove result:', success);

        if (success) {
            // Expand the parent folder to show the new folder
            if (parentPath) {
                this.expandedNodes.add(parentPath);
                this.saveExpandedState();
            }
            // Refresh the tree to show the newly created folder
            // restoreSelectedFolder() inside refresh() will maintain the current active folder
            await this.refresh();
        }

        // 清理待处理的拖拽状态
        this._pendingDragState = null;
        this.resetDragState();
        this.clearAllDropHighlights();
    }

    handleCreateFolderCancel() {
        this.hideCreateFolderInput();
        // 清理待处理的拖拽状态
        this._pendingDragState = null;
        this.resetDragState();
        this.clearAllDropHighlights();
    }

    saveSelectedFolder() {
        setStorageItem(`${this.pageType}_activeFolder`, this.selectedPath);
    }

    clearAllDropHighlights() {
        const highlighted = document.querySelectorAll('.sidebar-tree-node-content.drop-target, .sidebar-node-content.drop-target');
        highlighted.forEach((element) => element.classList.remove('drop-target'));
        this.currentDropTarget = null;
    }

    updateSidebarTitle() {
        const sidebarTitle = document.getElementById('sidebarTitle');
        if (sidebarTitle) {
            sidebarTitle.textContent = translate('sidebar.modelRoot');
        }
    }

    setupEventHandlers() {
        // Sidebar header (root selection) - only trigger on title area
        const sidebarHeader = document.getElementById('sidebarHeader');
        if (sidebarHeader) {
            sidebarHeader.addEventListener('click', this.handleSidebarHeaderClick);
        }

        // Collapse all button
        const collapseAllBtn = document.getElementById('sidebarCollapseAll');
        if (collapseAllBtn) {
            collapseAllBtn.addEventListener('click', this.handleCollapseAll);
        }

        // Recursive toggle button
        const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle');
        if (recursiveToggleBtn) {
            recursiveToggleBtn.addEventListener('click', this.handleRecursiveToggle);
        }

        // Tree click handler
        const folderTree = document.getElementById('sidebarFolderTree');
        if (folderTree) {
            folderTree.addEventListener('click', this.handleTreeClick);
            folderTree.addEventListener('contextmenu', this.handleTreeContextMenu);
        }

        // Breadcrumb click handler
        const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav');
        if (sidebarBreadcrumbNav) {
            sidebarBreadcrumbNav.addEventListener('click', this.handleBreadcrumbClick);
        }

        // Close sidebar when clicking outside on mobile
        document.addEventListener('click', (e) => {
            if (window.innerWidth <= 1024) {
                const sidebar = document.getElementById('folderSidebar');
                if (sidebar && !sidebar.contains(e.target) && !this.isDisabledByPage) {
                    sidebar.classList.remove('visible');
                }
            }
        });

        // Handle window resize
        window.addEventListener('resize', () => {
            this.updateContainerMargin();
        });

        // Add document click handler for closing dropdowns
        document.addEventListener('click', this.handleDocumentClick);

        // Add dedicated resize listener for container margin updates
        window.addEventListener('resize', this.updateContainerMargin);

        // Display mode toggle button
        const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle');
        if (displayModeToggleBtn) {
            displayModeToggleBtn.addEventListener('click', this.handleDisplayModeToggle);
        }

        // Sidebar folder context menu click handler
        const sidebarFolderMenu = document.getElementById('sidebarFolderContextMenu');
        if (sidebarFolderMenu) {
            sidebarFolderMenu.addEventListener('click', (e) => {
                const item = e.target.closest('.context-menu-item');
                if (!item) return;
                const action = item.dataset.action;
                if (action) {
                    this.handleFolderContextMenuAction(action);
                }
            });
        }

        // Dedicated hide sidebar button
        const hideToggle = document.getElementById('sidebarHideToggle');
        if (hideToggle) {
            hideToggle.addEventListener('click', this.handleHideToggle);
        }
    }

    handleDocumentClick(event) {
        // Close open dropdown when clicking outside
        if (this.openDropdown && !event.target.closest('.breadcrumb-dropdown')) {
            this.closeDropdown();
        }
    }

    handleSidebarHeaderClick(event) {
        // Only trigger root selection if clicking on the title area, not the buttons
        if (!event.target.closest('.sidebar-header-actions')) {
            this.selectFolder(null);
        }
    }

    handleHideToggle(event) {
        event.stopPropagation();
        this.toggleHideOnThisPage();
    }

    handleCollapseAll(event) {
        event.stopPropagation();
        this.expandedNodes.clear();
        this.renderFolderDisplay();
        this.saveExpandedState();
    }

    // ===== Sidebar visibility (per-page) and container margin =====

    updateContainerMargin() {
        const container = document.querySelector('.container');
        const sidebar = document.getElementById('folderSidebar');

        if (!container || !sidebar) return;

        // Always reset margin first — needed when transitioning from visible to hidden
        container.style.marginLeft = '';

        // When per-page disabled, skip adjustment but margin is already reset
        if (this.isDisabledByPage) return;

        // Sidebar is visible — adjust margin if we need room
        const sidebarWidth = sidebar.offsetWidth;
        const viewportWidth = window.innerWidth;
        const containerWidth = container.offsetWidth;

        if (sidebarWidth + containerWidth + sidebarWidth > viewportWidth) {
            container.style.marginLeft = `${sidebarWidth + 10}px`;
        }
    }

    updateDomVisibility() {
        const isHidden = this.isDisabledByPage;
        const sidebar = document.getElementById('folderSidebar');

        if (sidebar) {
            sidebar.classList.toggle('visible', !isHidden);
            sidebar.classList.toggle('hidden-by-setting', isHidden);
            sidebar.setAttribute('aria-hidden', isHidden.toString());
        }

        // Show or hide the "sidebar hidden" edge indicator
        if (isHidden) {
            this.showSidebarHiddenIndicator();
        } else {
            this.hideSidebarHiddenIndicator();
        }
    }


    toggleHideOnThisPage() {
        this.isDisabledByPage = !this.isDisabledByPage;
        setStorageItem(`${this.pageType}_sidebarDisabled`, this.isDisabledByPage);
        this.updateDomVisibility();
        this.updateContainerMargin();
    }

    getPageDisplayName() {
        const names = {
            loras: 'LoRAs',
            recipes: 'Recipes',
            checkpoints: 'Checkpoints',
            embeddings: 'Embeddings',
        };
        return names[this.pageType] || this.pageType;
    }

    showSidebarHiddenIndicator() {
        if (document.getElementById('sidebarHiddenIndicator')) return;

        const indicator = document.createElement('div');
        indicator.id = 'sidebarHiddenIndicator';
        indicator.className = 'sidebar-hidden-indicator';
        indicator.innerHTML = `
            <i class="fas fa-chevron-right"></i>
            <span class="sidebar-hidden-indicator-tooltip">${translate('sidebar.showSidebar')}</span>
        `;

        // Subtle breathing animation on first sight to aid discoverability;
        // stops permanently after user clicks the restore button once
        const restoreKey = `${this.pageType}_restoreButtonUsed`;
        if (!getStorageItem(restoreKey, false)) {
            indicator.classList.add('breathing');
        }

        indicator.addEventListener('click', () => {
            setStorageItem(restoreKey, true);
            this.toggleHideOnThisPage();
        });

        document.body.appendChild(indicator);
    }

    hideSidebarHiddenIndicator() {
        const indicator = document.getElementById('sidebarHiddenIndicator');
        if (indicator) {
            indicator.remove();
        }
    }

    async loadFolderTree() {
        try {
            if (this.displayMode === 'tree') {
                const response = await this.apiClient.fetchUnifiedFolderTree();
                this.treeData = response.tree || {};
            } else {
                const response = await this.apiClient.fetchModelFolders();
                this.foldersList = response.folders || [];
            }
            this.folderTreeLoaded = true;
            this.renderFolderDisplay();
        } catch (error) {
            this.folderTreeLoaded = false;
            console.error('Failed to load folder data:', error);
            this.renderEmptyState();
        }
    }

    folderExistsInTree(path) {
        if (!path) return true;

        if (this.displayMode === 'tree') {
            let node = this.treeData;
            for (const segment of path.split('/')) {
                if (!node || typeof node !== 'object' || !(segment in node)) {
                    return false;
                }
                node = node[segment];
            }
            return true;
        }

        return this.foldersList.includes(path);
    }

    renderFolderDisplay() {
        if (this.displayMode === 'tree') {
            this.renderTree();
        } else {
            this.renderFolderList();
        }
        this.initializeDragAndDrop();
    }

    renderTree() {
        const folderTree = document.getElementById('sidebarFolderTree');
        if (!folderTree) return;

        if (!this.treeData || Object.keys(this.treeData).length === 0) {
            this.renderEmptyState();
            return;
        }

        folderTree.innerHTML = this.renderTreeNode(this.treeData, '');
    }

    renderTreeNode(nodeData, basePath) {
        const entries = Object.entries(nodeData);
        if (entries.length === 0) return '';

        return entries.map(([folderName, children]) => {
            const currentPath = basePath ? `${basePath}/${folderName}` : folderName;
            const hasChildren = Object.keys(children).length > 0;
            const isExpanded = this.expandedNodes.has(currentPath);
            const isSelected = this.selectedPath === currentPath;

            const escapedPath = escapeAttribute(currentPath);
            const escapedFolderName = escapeHtml(folderName);
            const escapedTitle = escapeAttribute(folderName);

            return `
                <div class="sidebar-tree-node" data-path="${escapedPath}">
                    <div class="sidebar-tree-node-content ${isSelected ? 'selected' : ''}" data-path="${escapedPath}">
                        <div class="sidebar-tree-expand-icon ${isExpanded ? 'expanded' : ''}" 
                             style="${hasChildren ? '' : 'opacity: 0; pointer-events: none;'}">
                            <i class="fas fa-chevron-right"></i>
                        </div>
                        <i class="fas fa-folder sidebar-tree-folder-icon"></i>
                        <div class="sidebar-tree-folder-name" title="${escapedTitle}">${escapedFolderName}</div>
                    </div>
                    ${hasChildren ? `
                        <div class="sidebar-tree-children ${isExpanded ? 'expanded' : ''}">
                            ${this.renderTreeNode(children, currentPath)}
                        </div>
                    ` : ''}
                </div>
            `;
        }).join('');
    }

    renderEmptyState() {
        const folderTree = document.getElementById('sidebarFolderTree');
        if (!folderTree) return;

        folderTree.innerHTML = `
            <div class="sidebar-tree-placeholder">
                <i class="fas fa-folder-open"></i>
                <div>${translate('sidebar.empty.noFolders', {}, 'No folders found')}</div>
                <div class="sidebar-empty-hint">
                    <i class="fas fa-hand-pointer"></i>
                    ${translate('sidebar.empty.dragHint', {}, 'Drag items here to create folders')}
                </div>
            </div>
        `;
    }

    renderFolderList() {
        const folderTree = document.getElementById('sidebarFolderTree');
        if (!folderTree) return;

        if (!this.foldersList || this.foldersList.length === 0) {
            this.renderEmptyState();
            return;
        }

        const foldersHtml = this.foldersList.map(folder => {
            const displayName = folder === '' ? '/' : folder;
            const isSelected = this.selectedPath === folder;
            const escapedPath = escapeAttribute(folder);
            const escapedDisplayName = escapeHtml(displayName);
            const escapedTitle = escapeAttribute(displayName);

            return `
                <div class="sidebar-folder-item ${isSelected ? 'selected' : ''}" data-path="${escapedPath}">
                    <div class="sidebar-node-content" data-path="${escapedPath}">
                        <i class="fas fa-folder sidebar-folder-icon"></i>
                        <div class="sidebar-folder-name" title="${escapedTitle}">${escapedDisplayName}</div>
                    </div>
                </div>
            `;
        }).join('');

        folderTree.innerHTML = foldersHtml;
    }

    handleTreeClick(event) {
        if (this.displayMode === 'list') {
            this.handleFolderListClick(event);
            return;
        }

        const expandIcon = event.target.closest('.sidebar-tree-expand-icon');
        const nodeContent = event.target.closest('.sidebar-tree-node-content');

        if (expandIcon) {
            // Toggle expand/collapse
            const treeNode = expandIcon.closest('.sidebar-tree-node');
            const path = treeNode.dataset.path;
            const children = treeNode.querySelector('.sidebar-tree-children');

            if (this.expandedNodes.has(path)) {
                this.expandedNodes.delete(path);
                expandIcon.classList.remove('expanded');
                if (children) children.classList.remove('expanded');
            } else {
                this.expandedNodes.add(path);
                expandIcon.classList.add('expanded');
                if (children) children.classList.add('expanded');
            }

            this.saveExpandedState();
        } else if (nodeContent) {
            // Select folder
            const treeNode = nodeContent.closest('.sidebar-tree-node');
            const path = treeNode.dataset.path;
            this.selectFolder(path);
        }
    }

    handleTreeContextMenu(event) {
        const nodeContent = event.target.closest('.sidebar-tree-node, .sidebar-folder-item');
        if (!nodeContent) return;

        event.preventDefault();
        event.stopPropagation();

        const path = nodeContent.dataset.path;
        if (path === undefined || path === null || path === '') return;

        this._showFolderContextMenu(event.clientX, event.clientY, path);
    }

    _showFolderContextMenu(x, y, path) {
        this._closeFolderContextMenu();

        const menu = document.getElementById('sidebarFolderContextMenu');
        if (!menu) return;

        menu.style.left = `${x}px`;
        menu.style.top = `${y}px`;
        menu.style.display = 'block';
        menu.dataset.folderPath = path;

        this._folderContextOpen = true;

        // Close on next click outside
        this._folderContextCloseHandler = (e) => {
            if (!menu.contains(e.target)) {
                this._closeFolderContextMenu();
            }
        };
        setTimeout(() => {
            document.addEventListener('click', this._folderContextCloseHandler);
        }, 0);
    }

    _closeFolderContextMenu() {
        const menu = document.getElementById('sidebarFolderContextMenu');
        if (menu) {
            menu.style.display = 'none';
            delete menu.dataset.folderPath;
        }
        if (this._folderContextCloseHandler) {
            document.removeEventListener('click', this._folderContextCloseHandler);
            this._folderContextCloseHandler = null;
        }
        this._folderContextOpen = false;
    }

    handleFolderContextMenuAction(action) {
        const menu = document.getElementById('sidebarFolderContextMenu');
        if (!menu) return;

        const path = menu.dataset.folderPath;
        this._closeFolderContextMenu();

        if (!path) return;

        this._performFolderAction(action, path);
    }

    async _performFolderAction(action, path) {
        switch (action) {
            case 'check-folder-updates':
                try {
                    await performFolderUpdateCheck(path);
                } catch (error) {
                    console.error('Folder update check failed:', error);
                }
                break;
            default:
                console.warn('Unknown folder action:', action);
        }
    }

    handleBreadcrumbClick(event) {
        const breadcrumbItem = event.target.closest('.sidebar-breadcrumb-item');
        const dropdownItem = event.target.closest('.breadcrumb-dropdown-item');

        if (dropdownItem) {
            // Handle dropdown item selection
            const path = dropdownItem.dataset.path || '';
            this.selectFolder(path);
            this.closeDropdown();
        } else if (breadcrumbItem) {
            // Handle breadcrumb item click
            const path = breadcrumbItem.dataset.path || null;   // null for showing all models
            const isPlaceholder = breadcrumbItem.classList.contains('placeholder');
            const isActive = breadcrumbItem.classList.contains('active');
            const dropdown = breadcrumbItem.closest('.breadcrumb-dropdown');

            if (isPlaceholder || (isActive && path === this.selectedPath)) {
                // Open dropdown for placeholders or active items
                // Close any open dropdown first
                if (this.openDropdown && this.openDropdown !== dropdown) {
                    this.openDropdown.classList.remove('open');
                }

                // Toggle current dropdown
                dropdown.classList.toggle('open');

                // Update open dropdown reference
                this.openDropdown = dropdown.classList.contains('open') ? dropdown : null;
            } else {
                // Navigate to the selected path
                this.selectFolder(path);
            }
        }
    }

    closeDropdown() {
        if (this.openDropdown) {
            this.openDropdown.classList.remove('open');
            this.openDropdown = null;
        }
    }

    async selectFolder(path) {
        // Normalize path: null or undefined means root
        const normalizedPath = (path === null || path === undefined) ? '' : path;

        // Update selected path
        this.selectedPath = normalizedPath;

        // Update UI
        this.updateTreeSelection();
        this.updateBreadcrumbs();
        this.updateSidebarHeader();

        // Update page state
        this.pageControls.pageState.activeFolder = normalizedPath;
        setStorageItem(`${this.pageType}_activeFolder`, normalizedPath);

        // Reload models with new filter (loadMoreWithVirtualScroll will scroll to top)
        await this.pageControls.resetAndReload();
    }

    handleFolderListClick(event) {
        const folderItem = event.target.closest('.sidebar-folder-item');

        if (folderItem) {
            const path = folderItem.dataset.path;
            this.selectFolder(path);
        }
    }

    handleDisplayModeToggle(event) {
        event.stopPropagation();
        this.displayMode = this.displayMode === 'tree' ? 'list' : 'tree';
        this.updateDisplayModeButton();
        this.updateCollapseAllButton();
        this.updateRecursiveToggleButton();
        this.updateSearchRecursiveOption();
        this.saveDisplayMode();
        this.loadFolderTree(); // Reload with new display mode
    }

    async handleRecursiveToggle(event) {
        event.stopPropagation();

        if (this.displayMode !== 'tree') {
            return;
        }

        this.recursiveSearchEnabled = !this.recursiveSearchEnabled;
        setStorageItem(`${this.pageType}_recursiveSearch`, this.recursiveSearchEnabled);
        this.updateSearchRecursiveOption();
        this.updateRecursiveToggleButton();

        if (this.pageControls && typeof this.pageControls.resetAndReload === 'function') {
            try {
                await this.pageControls.resetAndReload(true);
            } catch (error) {
                console.error('Failed to reload models after toggling recursive search:', error);
            }
        }
    }

    updateDisplayModeButton() {
        const displayModeBtn = document.getElementById('sidebarDisplayModeToggle');
        if (displayModeBtn) {
            const icon = displayModeBtn.querySelector('i');
            if (this.displayMode === 'tree') {
                icon.className = 'fas fa-sitemap';
                displayModeBtn.title = translate('sidebar.switchToListView');
            } else {
                icon.className = 'fas fa-list';
                displayModeBtn.title = translate('sidebar.switchToTreeView');
            }
        }
    }

    updateCollapseAllButton() {
        const collapseAllBtn = document.getElementById('sidebarCollapseAll');
        if (collapseAllBtn) {
            if (this.displayMode === 'list') {
                collapseAllBtn.disabled = true;
                collapseAllBtn.classList.add('disabled');
                collapseAllBtn.title = translate('sidebar.collapseAllDisabled');
            } else {
                collapseAllBtn.disabled = false;
                collapseAllBtn.classList.remove('disabled');
                collapseAllBtn.title = translate('sidebar.collapseAll');
            }
        }
    }

    updateRecursiveToggleButton() {
        const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle');
        if (!recursiveToggleBtn) return;

        const icon = recursiveToggleBtn.querySelector('i');
        const isTreeMode = this.displayMode === 'tree';
        const isActive = isTreeMode && this.recursiveSearchEnabled;

        recursiveToggleBtn.classList.toggle('active', isActive);
        recursiveToggleBtn.classList.toggle('disabled', !isTreeMode);
        recursiveToggleBtn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
        recursiveToggleBtn.setAttribute('aria-disabled', isTreeMode ? 'false' : 'true');

        if (icon) {
            icon.className = 'fas fa-code-branch';
        }

        if (!isTreeMode) {
            recursiveToggleBtn.title = translate('sidebar.recursiveUnavailable');
        } else if (this.recursiveSearchEnabled) {
            recursiveToggleBtn.title = translate('sidebar.recursiveOn');
        } else {
            recursiveToggleBtn.title = translate('sidebar.recursiveOff');
        }
    }

    updateSearchRecursiveOption() {
        const isRecursive = this.displayMode === 'tree' && this.recursiveSearchEnabled;
        this.pageControls.pageState.searchOptions.recursive = isRecursive;
    }

    updateTreeSelection() {
        const folderTree = document.getElementById('sidebarFolderTree');
        if (!folderTree) return;

        if (this.displayMode === 'list') {
            // Remove all selections in list mode
            folderTree.querySelectorAll('.sidebar-folder-item').forEach(item => {
                item.classList.remove('selected');
            });

            // Add selection to current path
            if (this.selectedPath !== null && this.selectedPath !== undefined) {
                const escapedPathSelector = CSS.escape(this.selectedPath);
                const selectedItem = folderTree.querySelector(`[data-path="${escapedPathSelector}"]`);
                if (selectedItem) {
                    selectedItem.classList.add('selected');
                }
            }
        } else {
            folderTree.querySelectorAll('.sidebar-tree-node-content').forEach(node => {
                node.classList.remove('selected');
            });

            if (this.selectedPath !== null && this.selectedPath !== undefined) {
                const escapedPathSelector = CSS.escape(this.selectedPath);
                const selectedNode = folderTree.querySelector(`[data-path="${escapedPathSelector}"] .sidebar-tree-node-content`);
                if (selectedNode) {
                    selectedNode.classList.add('selected');
                    this.expandPathParents(this.selectedPath);
                }
            }
        }
    }

    expandPathParents(path) {
        if (!path) return;

        const parts = path.split('/');
        let currentPath = '';

        for (let i = 0; i < parts.length - 1; i++) {
            currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i];
            this.expandedNodes.add(currentPath);
        }

        this.renderTree();
    }

    // Get sibling folders for a given path level
    getSiblingFolders(pathParts, level) {
        if (level === 0) {
            // Root level siblings are top-level folders
            return Object.keys(this.treeData);
        }

        // Navigate to the parent folder to get siblings
        let currentNode = this.treeData;
        for (let i = 0; i < level; i++) {
            if (!currentNode[pathParts[i]]) {
                return [];
            }
            currentNode = currentNode[pathParts[i]];
        }

        return Object.keys(currentNode);
    }

    // Get child folders for a given path
    getChildFolders(path) {
        if (!path) {
            return Object.keys(this.treeData);
        }

        const parts = path.split('/');
        let currentNode = this.treeData;

        for (const part of parts) {
            if (!currentNode[part]) {
                return [];
            }
            currentNode = currentNode[part];
        }

        return Object.keys(currentNode);
    }

    updateBreadcrumbs() {
        const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav');
        if (!sidebarBreadcrumbNav) return;

        const parts = this.selectedPath ? this.selectedPath.split('/') : [];
        let currentPath = '';

        // Start with root breadcrumb
        const rootSiblings = Object.keys(this.treeData);
        const isRootSelected = !this.selectedPath;
        const breadcrumbs = [`
            <div class="breadcrumb-dropdown">
                <span class="sidebar-breadcrumb-item ${isRootSelected ? 'active' : ''}" data-path="">
                    <i class="fas fa-home"></i> ${escapeHtml(this.apiClient.apiConfig.config.displayName)} root
                </span>
            </div>
        `];

        // Add separator and placeholder for next level if we're at root
        if (!this.selectedPath) {
            const nextLevelFolders = rootSiblings;
            if (nextLevelFolders.length > 0) {
                breadcrumbs.push(`<span class="sidebar-breadcrumb-separator">/</span>`);
                breadcrumbs.push(`
                    <div class="breadcrumb-dropdown">
                        <span class="sidebar-breadcrumb-item placeholder">
                            --
                            <span class="breadcrumb-dropdown-indicator">
                                <i class="fas fa-caret-down"></i>
                            </span>
                        </span>
                        <div class="breadcrumb-dropdown-menu">
                            ${nextLevelFolders.map(folder => `
                                <div class="breadcrumb-dropdown-item" data-path="${escapeAttribute(folder)}">
                                    ${escapeHtml(folder)}
                                </div>`).join('')
                    }
                        </div>
                    </div>
                `);
            }
        }

        // Add breadcrumb items for each path segment
        parts.forEach((part, index) => {
            currentPath = currentPath ? `${currentPath}/${part}` : part;
            const isLast = index === parts.length - 1;

            // Get siblings for this level
            const siblings = this.getSiblingFolders(parts, index);
            const escapedCurrentPath = escapeAttribute(currentPath);
            const escapedPart = escapeHtml(part);

            breadcrumbs.push(`<span class="sidebar-breadcrumb-separator">/</span>`);
            breadcrumbs.push(`
                <div class="breadcrumb-dropdown">
                    <span class="sidebar-breadcrumb-item ${isLast ? 'active' : ''}" data-path="${escapedCurrentPath}">
                        ${escapedPart}
                        ${siblings.length > 1 ? `
                            <span class="breadcrumb-dropdown-indicator">
                                <i class="fas fa-caret-down"></i>
                            </span>
                        ` : ''}
                    </span>
                    ${siblings.length > 1 ? `
                        <div class="breadcrumb-dropdown-menu">
                            ${siblings.map(folder => {
                                const siblingPath = parts.slice(0, index).concat(folder).join('/');
                                return `
                                    <div class="breadcrumb-dropdown-item ${folder === part ? 'active' : ''}" 
                                         data-path="${escapeAttribute(siblingPath)}">
                                        ${escapeHtml(folder)}
                                    </div>`;
                            }).join('')
                    }
                        </div>
                    ` : ''}
                </div>
            `);

            // Add separator and placeholder for next level if not the last item
            if (isLast) {
                const childFolders = this.getChildFolders(currentPath);
                if (childFolders.length > 0) {
                    breadcrumbs.push(`<span class="sidebar-breadcrumb-separator">/</span>`);
                    breadcrumbs.push(`
                        <div class="breadcrumb-dropdown">
                            <span class="sidebar-breadcrumb-item placeholder">
                                --
                                <span class="breadcrumb-dropdown-indicator">
                                    <i class="fas fa-caret-down"></i>
                                </span>
                            </span>
                            <div class="breadcrumb-dropdown-menu">
                                ${childFolders.map(folder => `
                                    <div class="breadcrumb-dropdown-item" data-path="${escapeAttribute(currentPath + '/' + folder)}">
                                        ${escapeHtml(folder)}
                                    </div>`).join('')
                        }
                            </div>
                        </div>
                    `);
                }
            }
        });

        sidebarBreadcrumbNav.innerHTML = breadcrumbs.join('');
    }

    updateSidebarHeader() {
        const sidebarHeader = document.getElementById('sidebarHeader');
        if (!sidebarHeader) return;

        if (!this.selectedPath) {
            sidebarHeader.classList.add('root-selected');
        } else {
            sidebarHeader.classList.remove('root-selected');
        }
    }

    restoreSidebarState() {
        // Migration: old pin/unpin and global hide → per-page hide
        this._migrateOldSettings();

        const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []);
        const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree'); // 'tree' or 'list', default to 'tree'
        const recursiveSearchEnabled = getStorageItem(`${this.pageType}_recursiveSearch`, true);
        this.isDisabledByPage = getStorageItem(`${this.pageType}_sidebarDisabled`, false);

        this.expandedNodes = new Set(expandedPaths);
        this.displayMode = displayMode;
        this.recursiveSearchEnabled = recursiveSearchEnabled;

        this.updateDisplayModeButton();
        this.updateCollapseAllButton();
        this.updateSearchRecursiveOption();
        this.updateRecursiveToggleButton();
    }

    /**
     * One-time migration: old pin/unpin and global show_folder_sidebar → per-page hide
     * - sidebarPinned=false (was auto-hide) → sidebarDisabled=true for that page
     * - show_folder_sidebar=false (global) → sidebarDisabled=true for ALL pages
     */
    _migrateOldSettings() {
        if (getStorageItem('_sidebar_migration_done')) return;

        const PAGES = ['loras', 'recipes', 'checkpoints', 'embeddings'];

        // 1. Migrate global hide setting to per-page
        if (state?.global?.settings?.show_folder_sidebar === false) {
            PAGES.forEach(p => setStorageItem(`${p}_sidebarDisabled`, true));
        }

        // 2. Migrate unpinned (auto-hide) to per-page hide
        PAGES.forEach(p => {
            const wasPinned = getStorageItem(`${p}_sidebarPinned`, true);
            const alreadyDisabled = getStorageItem(`${p}_sidebarDisabled`, false);
            if (wasPinned === false && !alreadyDisabled) {
                // Was auto-hide → user didn't want sidebar taking space
                setStorageItem(`${p}_sidebarDisabled`, true);
            }
            // Clean up old keys
            localStorage.removeItem(`${p}_sidebarPinned`);
        });

        setStorageItem('_sidebar_migration_done', true);
    }

    restoreSelectedFolder() {
        const activeFolder = getStorageItem(`${this.pageType}_activeFolder`);
        if (activeFolder && typeof activeFolder === 'string') {
            // Fall back to the root when the persisted folder no longer
            // exists in the freshly loaded tree (e.g. it was moved or
            // deleted); otherwise the grid stays empty with a phantom
            // breadcrumb. Skip validation when the tree failed to load so a
            // transient API error doesn't wipe the saved location.
            if (this.folderTreeLoaded && !this.folderExistsInTree(activeFolder)) {
                console.warn(`Persisted folder "${activeFolder}" not found in folder tree, falling back to root`);
                this.selectedPath = '';
                if (this.pageControls?.pageState) {
                    this.pageControls.pageState.activeFolder = '';
                }
                setStorageItem(`${this.pageType}_activeFolder`, '');
                // When the reset happens after initialization (e.g. via
                // refresh() after a drag move emptied the folder), reload the
                // listing so the grid shows the root contents instead of
                // staying empty. Skipped during initialize() — the first load
                // picks up the cleared filter on its own.
                if (this.isInitialized && typeof this.pageControls?.resetAndReload === 'function') {
                    this.pageControls.resetAndReload().catch((error) => {
                        console.error('Failed to reload after resetting folder selection:', error);
                    });
                }
            } else {
                this.selectedPath = activeFolder;
            }
            this.updateTreeSelection();
            this.updateBreadcrumbs();
            this.updateSidebarHeader();
        } else {
            this.selectedPath = '';
            this.updateSidebarHeader();
            this.updateBreadcrumbs(); // Always update breadcrumbs
        }
    }

    saveExpandedState() {
        setStorageItem(`${this.pageType}_expandedNodes`, Array.from(this.expandedNodes));
    }

    saveDisplayMode() {
        setStorageItem(`${this.pageType}_displayMode`, this.displayMode);
    }

    async refresh() {
        if (!this.isInitialized) {
            return;
        }

        await this.loadFolderTree();
        this.restoreSelectedFolder();
    }

    destroy() {
        this.cleanup();
    }
}

// Create and export global instance
export const sidebarManager = new SidebarManager();
