<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch, watchEffect, type PropType, readonly, defineExpose, shallowRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { storeToRefs } from 'pinia';
import { createSftpActionsManager, type WebSocketDependencies, type FileTreeNode } from '../composables/useSftpActions';
import { useFileUploader } from '../composables/useFileUploader';
import { useFileEditorStore, type FileInfo } from '../stores/fileEditor.store';
import { useSessionStore } from '../stores/session.store';
import { useSettingsStore } from '../stores/settings.store';
import { useFocusSwitcherStore } from '../stores/focusSwitcher.store';
import { useFavoritePathsStore, type FavoritePathItem } from '../stores/favoritePaths.store';
import { useFileManagerContextMenu, type ClipboardState, type CompressFormat } from '../composables/file-manager/useFileManagerContextMenu';
import { useFileManagerSelection } from '../composables/file-manager/useFileManagerSelection';
import { useFileManagerDragAndDrop } from '../composables/file-manager/useFileManagerDragAndDrop';
import { useFileManagerKeyboardNavigation } from '../composables/file-manager/useFileManagerKeyboardNavigation';
import FileUploadPopup from './FileUploadPopup.vue';
import FileManagerContextMenu from './FileManagerContextMenu.vue';
import FileManagerActionModal from './FileManagerActionModal.vue';
import type { FileListItem } from '../types/sftp.types';
import type { WebSocketMessage } from '../types/websocket.types';
import PathHistoryDropdown from './PathHistoryDropdown.vue';
import { usePathHistoryStore } from '../stores/pathHistory.store';
import FavoritePathsModal from './FavoritePathsModal.vue';
import { useUiNotificationsStore } from '../stores/uiNotifications.store';


type SftpManagerInstance = ReturnType<typeof createSftpActionsManager>;

type ExplorerRootSource = 'favorite' | 'current';

interface ExplorerRootItem {
  id: string;
  path: string;
  label: string;
  description: string;
  source: ExplorerRootSource;
}

interface ExplorerTreeRow {
  id: string;
  path: string;
  name: string;
  description?: string;
  depth: number;
  isDirectory: boolean;
  isRoot: boolean;
  loaded: boolean;
  expanded: boolean;
  source: ExplorerRootSource | 'tree';
  item: FileListItem;
}

interface ExplorerOverviewRow {
  id: string;
  path: string;
  name: string;
  depth: number;
  description?: string;
  expanded: boolean;
  loaded: boolean;
  childDirectoryCount: number;
  isRootChild: boolean;
}

interface ExplorerOverviewSection {
  id: string;
  path: string;
  label: string;
  description: string;
  loaded: boolean;
  rowCount: number;
  rows: ExplorerOverviewRow[];
}


// --- Props ---
const props = defineProps({
  sessionId: {
    type: String,
    required: true,
  },
  // 文件管理器实例 ID
  instanceId: {
    type: String,
    required: true,
  },
  // 注入数据库连接 ID
  dbConnectionId: {
    type: String,
    required: true,
  },
  // 注入此组件及其子 composables 所需的 WebSocket 依赖项
  wsDeps: {
    type: Object as PropType<WebSocketDependencies>,
    required: true,
  },
  isMobile: {
    type: Boolean,
    default: false
  }
});

// --- 核心 Composables ---
const { t } = useI18n();
const route = useRoute(); // Keep for download URL generation for now
const sessionStore = useSessionStore(); // 实例化 Session Store
const favoritePathsStore = useFavoritePathsStore();

// --- 获取并存储 SFTP 管理器实例 ---
// 使用 shallowRef 存储管理器实例，以便在 sessionId 变化时切换
const currentSftpManager = shallowRef<SftpManagerInstance | null>(null);

const initializeSftpManager = (sessionId: string, instanceId: string) => {
    const manager = sessionStore.getOrCreateSftpManager(sessionId, instanceId);
    if (!manager) {
        // 抛出错误或显示错误消息，阻止组件进一步渲染
        console.error(`[FileManager ${sessionId}-${instanceId}] Failed to get or create SFTP manager instance.`);
        // 可以设置一个错误状态 ref 在模板中显示
        // managerError.value = `Failed to get SFTP manager for instance ${instanceId}`;
        currentSftpManager.value = null; // 确保设置为 null
        // 抛出错误会阻止组件渲染，可能不是最佳用户体验
        // throw new Error(`[FileManager ${sessionId}-${instanceId}] Failed to get or create SFTP manager instance.`);
    } else {
         currentSftpManager.value = manager;
         console.log(`[FileManager ${sessionId}-${instanceId}] SFTP Manager initialized/retrieved.`);
    }
};

// 初始加载管理器
initializeSftpManager(props.sessionId, props.instanceId);


// --- 文件上传模块 ---
// 修改：依赖 currentSftpManager 的状态
const {
    uploads,
    startFileUpload,
    cancelUpload,
} = useFileUploader(
    computed(() => props.sessionId),
    // 传递 manager 的 currentPath 和 fileList ref
    computed(() => currentSftpManager.value?.currentPath.value ?? '/'),
    computed(() => currentSftpManager.value?.fileList.value ?? []),
    computed(() => props.wsDeps)
);

// 实例化其他 Stores
const fileEditorStore = useFileEditorStore(); // 实例化 File Editor Store
// const sessionStore = useSessionStore(); // 已在上面实例化
const settingsStore = useSettingsStore(); // +++ 实例化 Settings Store +++
const focusSwitcherStore = useFocusSwitcherStore(); // +++ 实例化焦点切换 Store +++
const pathHistoryStore = usePathHistoryStore(); // +++ 实例化 PathHistoryStore +++
const uiNotificationsStore = useUiNotificationsStore(); // +++ 实例化通知 store +++
 
 // 从 Settings Store 获取共享设置
const {
  shareFileEditorTabsBoolean,
  fileManagerRowSizeMultiplierNumber, // +++ 获取行大小 getter +++
  fileManagerColWidthsObject, // +++ 获取列宽 getter +++
  showPopupFileEditorBoolean, // +++ 获取弹窗设置状态 +++
  fileManagerShowDeleteConfirmationBoolean, // +++ 获取删除确认设置状态 +++
} = storeToRefs(settingsStore); // 使用 storeToRefs 保持响应性
const { favoritePaths } = storeToRefs(favoritePathsStore);
 
 

// --- UI 状态 Refs (Remain mostly the same) ---
const fileInputRef = ref<HTMLInputElement | null>(null);
const sortKey = ref<keyof FileListItem | 'type' | 'size' | 'mtime'>('filename');
const sortDirection = ref<'asc' | 'desc'>('asc');
const isEditingPath = ref(false);
const searchQuery = ref(''); // 搜索查询 ref
const isMultiSelectMode = ref(false); // 多选模式状态 (主要用于移动端)
const isSearchActive = ref(false); // 控制搜索框激活状态
const searchInputRef = ref<HTMLInputElement | null>(null); // 搜索输入框 ref
const pathInputRef = ref<HTMLInputElement | null>(null);
const editablePath = ref('');
const fileListContainerRef = ref<HTMLDivElement | null>(null); // 文件列表容器引用
const dropOverlayRef = ref<HTMLDivElement | null>(null); // +++ 拖拽蒙版引用 +++

// +++ Favorite Paths Modal State +++
const showFavoritePathsModal = ref(false);
const favoritePathsButtonRef = ref<HTMLButtonElement | null>(null); // Ref for the trigger button
const explorerExpandedPaths = ref<Record<string, boolean>>({});

// +++ Path History Refs +++
const showPathHistoryDropdown = ref(false);
const pathInputWrapperRef = ref<HTMLDivElement | null>(null); // Wrapper for path input and dropdown
const pathHistoryDropdownRef = ref<InstanceType<typeof PathHistoryDropdown> | null>(null);
const { selectedIndex: pathSelectedIndex, filteredHistory: filteredPathHistory } = storeToRefs(pathHistoryStore); // Reactive store state

// +++ 操作模态框状态 +++
const isActionModalVisible = ref(false);
const currentActionType = ref<'delete' | 'rename' | 'chmod' | 'newFile' | 'newFolder' | null>(null);
const actionItem = ref<FileListItem | null>(null); // For single item operations
const actionItems = ref<FileListItem[]>([]); // For multi-item operations (e.g., delete)
const actionInitialValue = ref(''); // For pre-filling input in modal

// +++ 剪贴板状态 +++
const clipboardState = ref<ClipboardState>({ hasContent: false });
const clipboardSourcePaths = ref<string[]>([]); // 存储源完整路径
const clipboardSourceBaseDir = ref<string>(''); // 存储源目录

const rowSizeMultiplier = ref(1.0); // 行大小（字体）乘数, 默认值会被 store 覆盖
// --- 键盘导航状态 (移至 useFileManagerKeyboardNavigation) ---
// const selectedIndex = ref<number>(-1);

// --- Column Resizing State (Remains the same) ---
const tableRef = ref<HTMLTableElement | null>(null);
const colWidths = ref({ // 默认值会被 store 覆盖
    type: 50,
    name: 300,
    size: 100,
    permissions: 120,
    modified: 180,
});
const isResizing = ref(false);
const resizingColumnIndex = ref(-1);
const startX = ref(0);
const startWidth = ref(0);

// --- 辅助函数 ---
const generateRequestId = (): string => `req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;


// UI 格式化函数保持不变
const formatSize = (size: number): string => {
    if (size < 1024) return `${size} B`;
    if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
    if (size < 1024 * 1024 * 1024) return `${(size / (1024 * 1024)).toFixed(1)} MB`;
    return `${(size / (1024 * 1024 * 1024)).toFixed(1)} GB`;
};

const formatMode = (mode: number): string => {
    const perm = mode & 0o777; let str = '';
    str += (perm & 0o400) ? 'r' : '-'; str += (perm & 0o200) ? 'w' : '-'; str += (perm & 0o100) ? 'x' : '-';
    str += (perm & 0o040) ? 'r' : '-'; str += (perm & 0o020) ? 'w' : '-'; str += (perm & 0o010) ? 'x' : '-';
    str += (perm & 0o004) ? 'r' : '-'; str += (perm & 0o002) ? 'w' : '-'; str += (perm & 0o001) ? 'x' : '-';
    return str;
};

const getPathName = (path: string): string => {
  if (!path || path === '/') {
    return '/';
  }

  const normalized = path.endsWith('/') ? path.slice(0, -1) : path;
  return normalized.substring(normalized.lastIndexOf('/') + 1) || normalized;
};

const sortTreeItems = (items: FileListItem[]): FileListItem[] => {
  return [...items].sort((left, right) => {
    if (left.attrs.isDirectory && !right.attrs.isDirectory) return -1;
    if (!left.attrs.isDirectory && right.attrs.isDirectory) return 1;
    return left.filename.localeCompare(right.filename);
  });
};

const findTreeNodeByPath = (path: string): FileTreeNode | null => {
  const root = currentSftpManager.value?.fileTree;
  if (!root) {
    return null;
  }

  if (path === '/') {
    return root;
  }

  const segments = path.split('/').filter(Boolean);
  let currentNode: FileTreeNode | null = root;

  for (const segment of segments) {
    if (!currentNode?.children) {
      return null;
    }

    currentNode = currentNode.children.find((child) => child.filename === segment) ?? null;
  }

  return currentNode;
};

const toFileListItem = (node: FileTreeNode): FileListItem => ({
  filename: node.filename,
  longname: node.longname,
  attrs: node.attrs,
});

const getDirectoryChildren = (node: FileTreeNode | null): FileTreeNode[] => {
  if (!node?.children?.length) {
    return [];
  }

  return [...node.children]
    .filter((child) => child.attrs.isDirectory)
    .sort((left, right) => left.filename.localeCompare(right.filename));
};

const openFileInWorkspace = (filePath: string, filename: string) => {
  const fileInfo: FileInfo = { name: filename, fullPath: filePath };

  if (settingsStore.showPopupFileEditorBoolean) {
    fileEditorStore.triggerPopup(filePath, props.sessionId);
  }

  if (shareFileEditorTabsBoolean.value) {
    fileEditorStore.openFile(filePath, props.sessionId, props.instanceId);
  } else {
    sessionStore.openFileInSession(props.sessionId, fileInfo);
  }
};

const explorerRoots = computed<ExplorerRootItem[]>(() => {
  const roots = new Map<string, ExplorerRootItem>();

  favoritePaths.value.forEach((favorite: FavoritePathItem) => {
    const path = favorite.path?.trim();
    if (!path) {
      return;
    }

    roots.set(path, {
      id: `favorite:${favorite.id}`,
      path,
      label: favorite.name?.trim() || getPathName(path),
      description: path,
      source: 'favorite',
    });
  });

  const currentPath = currentSftpManager.value?.currentPath.value?.trim();
  if (currentPath && !roots.has(currentPath)) {
    roots.set(currentPath, {
      id: `current:${currentPath}`,
      path: currentPath,
      label: getPathName(currentPath),
      description: currentPath,
      source: 'current',
    });
  }

  return Array.from(roots.values());
});

const explorerTreeRows = computed<ExplorerTreeRow[]>(() => {
  const rows: ExplorerTreeRow[] = [];

  const appendNodeRows = (basePath: string, nodes: FileListItem[], depth: number) => {
    sortTreeItems(nodes)
      .filter((item) => item.attrs.isDirectory)
      .forEach((item) => {
      const itemPath = currentSftpManager.value?.joinPath(basePath, item.filename) ?? `${basePath}/${item.filename}`;
      const treeNode = findTreeNodeByPath(itemPath);
      const expanded = Boolean(explorerExpandedPaths.value[itemPath]);
      const loaded = Boolean(treeNode?.childrenLoaded);

      rows.push({
        id: `tree:${itemPath}`,
        path: itemPath,
        name: item.filename,
        depth,
        isDirectory: true,
        isRoot: false,
        loaded,
        expanded,
        source: 'tree',
        item,
      });

      if (expanded && treeNode?.children?.length) {
        appendNodeRows(itemPath, treeNode.children.map(toFileListItem), depth + 1);
      }
      });
  };

  explorerRoots.value.forEach((root) => {
    const node = findTreeNodeByPath(root.path);
    const rootItem: FileListItem = node
      ? toFileListItem(node)
      : {
          filename: getPathName(root.path),
          longname: root.path,
          attrs: {
            isDirectory: true,
            isFile: false,
            isSymbolicLink: false,
            size: 0,
            uid: 0,
            gid: 0,
            mode: 0,
            atime: 0,
            mtime: 0,
          },
        };

    const expanded = explorerExpandedPaths.value[root.path] ?? true;
    const loaded = Boolean(node?.childrenLoaded);

    rows.push({
      id: root.id,
      path: root.path,
      name: root.label,
      description: root.description,
      depth: 0,
      isDirectory: true,
      isRoot: true,
      loaded,
      expanded,
      source: root.source,
      item: rootItem,
    });

    if (expanded && node?.children?.length) {
      appendNodeRows(root.path, node.children.map(toFileListItem), 1);
    }
  });

  return rows;
});

const explorerOverviewSections = computed<ExplorerOverviewSection[]>(() => {
  const buildRows = (basePath: string, nodes: FileTreeNode[], depth: number): ExplorerOverviewRow[] => {
    const rows: ExplorerOverviewRow[] = [];

    nodes.forEach((node) => {
      const itemPath = currentSftpManager.value?.joinPath(basePath, node.filename) ?? `${basePath}/${node.filename}`;
      const expanded = Boolean(explorerExpandedPaths.value[itemPath]);
      const childDirectories = getDirectoryChildren(node);

      rows.push({
        id: `overview:${itemPath}`,
        path: itemPath,
        name: node.filename,
        depth,
        expanded,
        loaded: Boolean(node.childrenLoaded),
        childDirectoryCount: childDirectories.length,
        isRootChild: depth === 0,
      });

      if (expanded && childDirectories.length) {
        rows.push(...buildRows(itemPath, childDirectories, depth + 1));
      }
    });

    return rows;
  };

  return explorerRoots.value.map((root) => {
    const rootNode = findTreeNodeByPath(root.path);
    const childDirectories = getDirectoryChildren(rootNode);

    return {
      id: `section:${root.id}`,
      path: root.path,
      label: root.label,
      description: root.description,
      loaded: Boolean(rootNode?.childrenLoaded),
      rowCount: childDirectories.length,
      rows: buildRows(root.path, childDirectories, 0),
    };
  });
});

const getFileIconClassBase = (filename: string): string => {
  const lowerFilename = filename.toLowerCase();
  let extension = '';
  const lastDotIndex = lowerFilename.lastIndexOf('.');

  if (lastDotIndex > 0 && lastDotIndex < lowerFilename.length - 1) { // e.g. file.txt
    extension = lowerFilename.substring(lastDotIndex + 1);
  } else if (lastDotIndex === 0 && lowerFilename.length > 1) { // e.g. .bashrc, .gitignore
    extension = lowerFilename.substring(1); // use 'bashrc' or 'gitignore' as extension
  }
  // Handle specific full filenames first for higher precedence
  if (lowerFilename === 'makefile') return 'fas fa-cogs';
  if (lowerFilename === 'dockerfile') return 'fab fa-docker';
  if (lowerFilename.endsWith('docker-compose.yml') || lowerFilename.endsWith('docker-compose.yaml')) return 'fab fa-docker';
  if (lowerFilename === 'package.json') return 'fab fa-npm';
  if (lowerFilename === 'package-lock.json') return 'fab fa-npm';
  if (lowerFilename === 'yarn.lock') return 'fab fa-yarn';
  if (lowerFilename === 'composer.json') return 'fab fa-php';
  if (lowerFilename === 'composer.lock') return 'fab fa-php';
  if (lowerFilename === 'gemfile') return 'fas fa-gem';
  if (lowerFilename === 'gemfile.lock') return 'fas fa-gem';
  if (lowerFilename.startsWith('.env')) return 'fas fa-shield-alt';
  if (lowerFilename === '.git') return 'fab fa-git-alt';
  if (lowerFilename === '.gitignore') return 'fab fa-git-alt';
  if (lowerFilename === '.gitattributes') return 'fab fa-git-alt';
  if (lowerFilename === '.gitmodules') return 'fab fa-git-alt';
  if (lowerFilename === 'readme' || lowerFilename.startsWith('readme.')) return 'fas fa-book-reader';
  if (lowerFilename === 'license' || lowerFilename.startsWith('license.')) return 'fas fa-balance-scale';
  if (lowerFilename === 'contributing' || lowerFilename.startsWith('contributing.')) return 'fas fa-users-cog';
  if (lowerFilename === 'code_of_conduct' || lowerFilename.startsWith('code_of_conduct.')) return 'fas fa-gavel';
  if (lowerFilename === 'changelog' || lowerFilename.startsWith('changelog.')) return 'fas fa-list-alt';
  if (lowerFilename === 'favicon.ico') return 'fas fa-icons';


  const iconMap: { [key: string]: string } = {
    // Images
    'jpg': 'fas fa-file-image', 'jpeg': 'fas fa-file-image', 'png': 'fas fa-file-image',
    'gif': 'fas fa-file-image', 'bmp': 'fas fa-file-image', 'svg': 'fas fa-file-image',
    'webp': 'fas fa-file-image', 'ico': 'fas fa-file-image', 'tiff': 'fas fa-file-image',
    // Videos
    'mp4': 'fas fa-file-video', 'mkv': 'fas fa-file-video', 'avi': 'fas fa-file-video',
    'mov': 'fas fa-file-video', 'wmv': 'fas fa-file-video', 'flv': 'fas fa-file-video', 'webm': 'fas fa-file-video',
    // Audio
    'mp3': 'fas fa-file-audio', 'wav': 'fas fa-file-audio', 'ogg': 'fas fa-file-audio',
    'flac': 'fas fa-file-audio', 'aac': 'fas fa-file-audio', 'm4a': 'fas fa-file-audio',
    // Documents
    'doc': 'fas fa-file-word', 'docx': 'fas fa-file-word',
    'xls': 'fas fa-file-excel', 'xlsx': 'fas fa-file-excel',
    'ppt': 'fas fa-file-powerpoint', 'pptx': 'fas fa-file-powerpoint',
    'pdf': 'fas fa-file-pdf', 'odt': 'fas fa-file-alt', 'ods': 'fas fa-file-alt', 'odp': 'fas fa-file-alt',
    'rtf': 'fas fa-file-alt',
    'csv': 'fas fa-file-csv', 'tsv': 'fas fa-file-csv',
    // Archives
    'zip': 'fas fa-file-archive', 'rar': 'fas fa-file-archive', 'tar': 'fas fa-file-archive',
    'gz': 'fas fa-file-archive', '7z': 'fas fa-file-archive', 'bz2': 'fas fa-file-archive', 'xz': 'fas fa-file-archive',
    'iso': 'fas fa-compact-disc',
    // Code & Config
    'js': 'fab fa-js-square', 'mjs': 'fab fa-js-square', 'cjs': 'fab fa-js-square',
    'jsx': 'fab fa-react',
    'ts': 'fas fa-file-code',
    'tsx': 'fab fa-react',
    'vue': 'fab fa-vuejs',
    'svelte': 'fas fa-file-code',
    'py': 'fab fa-python', 'pyc': 'fab fa-python', 'pyd': 'fab fa-python', 'pyw': 'fab fa-python', 'ipynb': 'fab fa-python',
    'java': 'fab fa-java', 'jar': 'fab fa-java', 'class': 'fab fa-java',
    'kt': 'fas fa-file-code', 'kts': 'fas fa-file-code',
    'cs': 'fas fa-file-code',
    'fs': 'fas fa-file-code',
    'go': 'fas fa-file-code',
    'rs': 'fas fa-file-code',
    'c': 'fas fa-file-code', 'h': 'fas fa-file-code',
    'cpp': 'fas fa-file-code', 'hpp': 'fas fa-file-code', 'cxx': 'fas fa-file-code', 'hxx': 'fas fa-file-code',
    'rb': 'fas fa-gem', 'erb': 'fas fa-gem',
    'php': 'fab fa-php',
    'swift': 'fab fa-swift',
    'scala': 'fas fa-file-code',
    'perl': 'fas fa-file-code', 'pl': 'fas fa-file-code',
    'lua': 'fas fa-file-code',
    'dart': 'fas fa-file-code',
    'r': 'fas fa-file-code',
    'html': 'fab fa-html5', 'htm': 'fab fa-html5', 'xhtml': 'fab fa-html5',
    'css': 'fab fa-css3-alt',
    'scss': 'fab fa-sass', 'sass': 'fab fa-sass',
    'less': 'fab fa-less',
    'styl': 'fas fa-file-code',
    'json': 'fas fa-file-code', 'webmanifest': 'fas fa-file-code', 'jsonc': 'fas fa-file-code',
    'xml': 'fas fa-file-code', 'xsl': 'fas fa-file-code', 'xsd': 'fas fa-file-code',
    'yml': 'fas fa-cog', 'yaml': 'fas fa-cog',
    'ini': 'fas fa-cog', 'conf': 'fas fa-cog', 'cfg': 'fas fa-cog', 'config': 'fas fa-cog',
    'toml': 'fas fa-cog',
    'md': 'fab fa-markdown', 'markdown': 'fab fa-markdown',
    'sql': 'fas fa-database', 'ddl': 'fas fa-database',
    'db': 'fas fa-database', 'sqlite': 'fas fa-database', 'mdb': 'fas fa-database',
    'lock': 'fas fa-lock',
    'gitignore': 'fab fa-git-alt', /* 'gitattributes': 'fab fa-git-alt', */ /* 'gitmodules': 'fab fa-git-alt', */ 'gitkeep': 'fab fa-git-alt', // Removed duplicate gitattributes and gitmodules
    /* 'dockerfile': 'fab fa-docker', */ 'dockerignore': 'fab fa-docker', // Removed duplicate dockerfile
    'npmrc': 'fab fa-npm', 'yarnrc': 'fab fa-yarn', 'pnpmfile.js': 'fas fa-cogs',
    'babelrc': 'fas fa-cogs', 'eslintrc': 'fas fa-cogs', 'prettierrc': 'fas fa-cogs', 'stylelintrc': 'fas fa-cogs',
    'browserslistrc': 'fas fa-cogs', 'editorconfig': 'fas fa-cog',
    'tsconfig.json': 'fas fa-cogs', 'jsconfig.json': 'fas fa-cogs',
    'webpack.config.js': 'fas fa-cogs', 'vite.config.js': 'fas fa-cogs', 'vite.config.ts': 'fas fa-cogs',
    'rollup.config.js': 'fas fa-cogs', 'postcss.config.js': 'fas fa-cogs',
    'jest.config.js': 'fas fa-cogs', 'cypress.json': 'fas fa-cogs', 'playwright.config.ts': 'fas fa-cogs',
    // Text & Others
    'txt': 'fas fa-file-alt', 'text': 'fas fa-file-alt',
    'log': 'fas fa-file-alt', 'out': 'fas fa-file-alt', 'err': 'fas fa-file-alt',
    'key': 'fas fa-key', 'pem': 'fas fa-key', 'pub': 'fas fa-key', 'asc': 'fas fa-key',
    'crt': 'fas fa-certificate', 'cer': 'fas fa-certificate', 'csr': 'fas fa-certificate', 'pfx': 'fas fa-certificate', 'p12': 'fas fa-certificate',
    // Executables & scripts
    'exe': 'fas fa-cogs', 'msi': 'fas fa-cogs', 'app': 'fas fa-cogs', 'com': 'fas fa-cogs',
    'sh': 'fas fa-terminal', 'bash': 'fas fa-terminal', 'zsh': 'fas fa-terminal', 'fish': 'fas fa-terminal', 'csh': 'fas fa-terminal', 'ksh': 'fas fa-terminal',
    'bat': 'fas fa-terminal', 'cmd': 'fas fa-terminal', 'ps1': 'fas fa-terminal', 'psm1': 'fas fa-terminal',
    'vb': 'fas fa-file-code', 'vbs': 'fas fa-file-code',
    'deb': 'fas fa-archive', 'rpm': 'fas fa-archive', 'pkg': 'fas fa-archive',
    'dmg': 'fas fa-compact-disc',  'img': 'fas fa-compact-disc', 
    // Fonts
    'ttf': 'fas fa-font', 'otf': 'fas fa-font', 'woff': 'fas fa-font', 'woff2': 'fas fa-font', 'eot': 'fas fa-font',
    // Special hidden files (extension is the part after dot)
    'bashrc': 'fas fa-cog', 'zshrc': 'fas fa-cog', 'profile': 'fas fa-cog', 'bash_profile': 'fas fa-cog',
    'vimrc': 'fas fa-cog', 'screenrc': 'fas fa-cog', 'tmux.conf': 'fas fa-cog',
    'gitconfig': 'fab fa-git-alt', 'npmignore': 'fab fa-npm',
    'htaccess': 'fas fa-cog', 'htpasswd': 'fas fa-lock',
    // Default
    'default': 'far fa-file'
  };
  return iconMap[extension] || iconMap['default'];
};

// --- 排序与过滤逻辑 ---
// 修改：依赖 currentSftpManager.value.fileList
const sortedFileList = computed(() => {
    if (!currentSftpManager.value?.fileList.value) return []; // 检查 manager 和 fileList 是否存在
    const list = [...currentSftpManager.value.fileList.value]; // 从 manager 获取列表
    const key = sortKey.value;
    const direction = sortDirection.value === 'asc' ? 1 : -1;

    list.sort((a, b) => {
        if (key !== 'type') {
            if (a.attrs.isDirectory && !b.attrs.isDirectory) return -1;
            if (!a.attrs.isDirectory && b.attrs.isDirectory) return 1;
        }
        let valA: string | number | boolean;
        let valB: string | number | boolean;
        switch (key) {
            case 'type':
                valA = a.attrs.isDirectory ? 0 : (a.attrs.isSymbolicLink ? 1 : 2);
                valB = b.attrs.isDirectory ? 0 : (b.attrs.isSymbolicLink ? 1 : 2);
                break;
            case 'filename': valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase(); break;
            case 'size': valA = a.attrs.isFile ? a.attrs.size : -1; valB = b.attrs.isFile ? b.attrs.size : -1; break;
            case 'mtime': valA = a.attrs.mtime; valB = b.attrs.mtime; break;
            default: valA = a.filename.toLowerCase(); valB = b.filename.toLowerCase();
        }
        if (valA < valB) return -1 * direction;
        if (valA > valB) return 1 * direction;
        if (key !== 'filename') return a.filename.localeCompare(b.filename);
        return 0;
    });
    return list;
});

const filteredFileList = computed(() => {
    if (!searchQuery.value) {
        return sortedFileList.value; // 如果没有搜索查询，返回原始排序列表
    }
    const lowerCaseQuery = searchQuery.value.toLowerCase();
    return sortedFileList.value.filter(item =>
        item.filename.toLowerCase().includes(lowerCaseQuery)
    );
});

const handleSort = (key: keyof FileListItem | 'type' | 'size' | 'mtime') => {
    if (sortKey.value === key) {
        sortDirection.value = sortDirection.value === 'asc' ? 'desc' : 'asc';
    } else {
        sortKey.value = key;
        sortDirection.value = 'asc';
    }
};


// --- 列表项点击与选择逻辑 (使用 Composable) ---
// 定义单击时的动作回调 (移到 Selection 实例化之前)
const handleItemAction = (item: FileListItem) => {
  if (!currentSftpManager.value) return;

  const itemPath = currentSftpManager.value.joinPath(currentSftpManager.value.currentPath.value, item.filename);

  if (item.attrs.isSymbolicLink) {
    if (currentSftpManager.value.isLoading.value) {
      return;
    }
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Symbolic link clicked: ${itemPath}. Attempting to resolve with sftp:realpath...`);

    const { sendMessage: wsSend, onMessage: wsOnMessage } = props.wsDeps;
    const requestId = generateRequestId();

    const handleResolvedPath = (realPath: string, targetType: 'file' | 'directory' | 'unknown', originalLinkItem: FileListItem) => {
      if (!currentSftpManager.value) return;


      if (targetType === 'directory') {
        currentSftpManager.value.loadDirectory(realPath);
      } else if (targetType === 'file') {
        const targetFilename = realPath.substring(realPath.lastIndexOf('/') + 1) || originalLinkItem.filename; // Get filename from realPath

        // Preserve mobile multi-select behavior for the original link item
        if (props.isMobile && isMultiSelectMode.value) {
          if (selectedItems.value.has(originalLinkItem.filename)) {
            selectedItems.value.delete(originalLinkItem.filename);
          } else {
            selectedItems.value.add(originalLinkItem.filename);
          }
          return;
        }

        openFileInWorkspace(realPath, targetFilename);
      } else { // targetType is 'unknown' or not provided as expected
        console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Symlink target '${realPath}' has an unknown type from server ('${targetType}'). Defaulting to open as file.`);
        // Fallback: attempt to open as file, or display an error
        const targetFilename = realPath.substring(realPath.lastIndexOf('/') + 1) || originalLinkItem.filename;
        openFileInWorkspace(realPath, targetFilename);
      }
    };

    let unregisterSuccess: (() => void) | undefined;
    let unregisterError: (() => void) | undefined;
    let timeoutId: NodeJS.Timeout | number | undefined;

    const cleanupListeners = () => {
      unregisterSuccess?.();
      unregisterError?.();
      if (timeoutId) clearTimeout(timeoutId as any);
      timeoutId = undefined;
    };

    unregisterSuccess = wsOnMessage('sftp:realpath:success', (payload: any, message: WebSocketMessage) => {
      if (message.requestId === requestId && payload.requestedPath === itemPath) {
        cleanupListeners();
        if (!currentSftpManager.value) return;
        // 从 payload 中获取 absolutePath 和 targetType
        const absolutePath = payload.absolutePath;
        const targetType = payload.targetType as ('file' | 'directory' | 'unknown'); // 类型断言

        if (!absolutePath) {
            console.error(`[FileManager ${props.sessionId}-${props.instanceId}] sftp:realpath:success for ${itemPath} missing absolutePath. Payload:`, payload);
            return;
        }
         if (!targetType) {
            console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] sftp:realpath:success for ${itemPath} missing targetType. Defaulting to 'file'. Payload:`, payload);
        }

        handleResolvedPath(absolutePath, targetType || 'unknown', item);
      }
    });

    unregisterError = wsOnMessage('sftp:realpath:error', (payload: any, message: WebSocketMessage) => {
      if (message.requestId === requestId && payload?.requestedPath === itemPath) {
        cleanupListeners();
        // payload.error 可能包含来自后端的具体错误信息
        // payload.absolutePath 可能在 stat 失败时仍然存在
        const serverErrorMsg = payload.error || 'Unknown error resolving symlink target type';
        const resolvedPathInfo = payload.absolutePath ? ` (Resolved path: ${payload.absolutePath})` : '';

        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to get realpath or target type for symlink '${itemPath}': ${serverErrorMsg}${resolvedPathInfo}`);
      }
    });

    timeoutId = setTimeout(() => {
      cleanupListeners();
      console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Timeout getting realpath for symlink '${itemPath}' (ID: ${requestId}).`);
    }, 10000); // 10 秒超时
    wsSend({ type: 'sftp:realpath', requestId: requestId, payload: { path: itemPath } });
    return; // Handled by async callbacks
  }

  if (item.attrs.isDirectory) {
    if (currentSftpManager.value.isLoading.value) {
      return;
    }
    const newPath = item.filename === '..'
      ? currentSftpManager.value.currentPath.value.substring(0, currentSftpManager.value.currentPath.value.lastIndexOf('/')) || '/'
      : currentSftpManager.value.joinPath(currentSftpManager.value.currentPath.value, item.filename);
    currentSftpManager.value.loadDirectory(newPath);
  } else if (item.attrs.isFile) {
    // This block now only handles regular files, as symlinks are handled above.
    if (props.isMobile && isMultiSelectMode.value) {
      if (selectedItems.value.has(item.filename)) {
        selectedItems.value.delete(item.filename);
      } else {
        selectedItems.value.add(item.filename);
      }
      return;
    }
    const filePath = itemPath; // itemPath is already calculated
    openFileInWorkspace(filePath, item.filename);
  }
};

// 切换多选模式 (主要用于移动端)
const toggleMultiSelectMode = () => {
    isMultiSelectMode.value = !isMultiSelectMode.value;
    if (!isMultiSelectMode.value) {
        clearSelection(); // 退出多选模式时清空选择
    }
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Multi-select mode: ${isMultiSelectMode.value ? 'enabled' : 'disabled'}`);
};

// 实例化选择 Composable (需要 filteredFileList 和 handleItemAction)
const {
  selectedItems, // 使用 Composable 返回的 selectedItems
  lastClickedIndex, // 获取 lastClickedIndex 以传递给 ContextMenu
  handleItemClick: originalHandleItemClick, // 使用 Composable 返回的 handleItemClick
  clearSelection, // 获取清空选择的方法
} = useFileManagerSelection({
  // 传递当前显示的列表 (已排序和过滤)
  displayedFileList: filteredFileList, // 现在 filteredFileList 已定义
  onItemAction: handleItemAction, // 传递动作回调
});

// 自定义 handleItemClick 函数以支持移动端多选模式
const handleItemClick = (event: MouseEvent, item: FileListItem, forceMultiSelect = false) => {
  if (props.isMobile && (isMultiSelectMode.value || forceMultiSelect)) {
    if (selectedItems.value.has(item.filename)) {
      selectedItems.value.delete(item.filename);
    } else {
      selectedItems.value.add(item.filename);
    }
    return;
  }
  originalHandleItemClick(event, item);
};

// +++ 计算属性：获取选中的完整文件对象列表 +++
const computedSelectedFullItems = computed((): FileListItem[] => {
  if (!selectedItems.value || selectedItems.value.size === 0) {
    return [];
  }
  return filteredFileList.value.filter(item => selectedItems.value.has(item.filename));
});

// --- 操作模态框辅助函数 ---
const openActionModal = (
 type: 'delete' | 'rename' | 'chmod' | 'newFile' | 'newFolder',
 item?: FileListItem | null, // For single item operations like rename, chmod
 items?: FileListItem[], // For multi-item operations like delete
 initialValue?: string // For pre-filling input, e.g., old name for rename
) => {
 currentActionType.value = type;
 actionItem.value = item || null;
 actionItems.value = items || (item ? [item] : []); // Ensure actionItems has the item(s)
 actionInitialValue.value = initialValue || '';
 isActionModalVisible.value = true;
};

const handleModalClose = () => {
 isActionModalVisible.value = false;
 // Reset states if needed, though they'll be overwritten on next open
 currentActionType.value = null;
 actionItem.value = null;
 actionItems.value = [];
 actionInitialValue.value = '';
};

const handleModalConfirm = (value?: string) => {
 if (!currentSftpManager.value || !currentActionType.value) {
   handleModalClose();
   return;
 }
 const manager = currentSftpManager.value;

 switch (currentActionType.value) {
   case 'delete':
     if (actionItems.value.length > 0) {
       manager.deleteItems(actionItems.value);
       selectedItems.value.clear(); // Clear selection after delete
     }
     break;
   case 'rename':
     if (actionItem.value && value && value !== actionItem.value.filename) {
       manager.renameItem(actionItem.value, value);
     }
     break;
   case 'chmod':
     if (actionItem.value && value && /^[0-7]{3,4}$/.test(value)) {
       const newMode = parseInt(value, 8);
       manager.changePermissions(actionItem.value, newMode);
     } else if (value) { // value exists but is invalid
       // Optionally, re-open modal with error or use a notification
       // For now, just log and close
       console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Invalid chmod value from modal: ${value}`);
       // It might be better to show an error in the modal itself and not close it.
       // The modal currently has its own validation, so this path might not be hit often.
     }
     break;
   case 'newFile':
     if (value) {
       if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
         console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] File ${value} already exists. Modal should prevent this.`);
         return; // Prevent closing if error
       }
       manager.createFile(value);
     }
     break;
   case 'newFolder':
     if (value) {
       if (manager.fileList.value.some((item: FileListItem) => item.filename === value)) {
         console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Folder ${value} already exists. Modal should prevent this.`);
         return; // Prevent closing if error
       }
       manager.createDirectory(value);
     }
     break;
 }
 handleModalClose(); // Close modal after action
};


// --- SFTP 操作处理函数 (定义在此处，供 Composable 使用) ---
const handleDeleteSelectedClick = () => {
    // 修改：检查 currentSftpManager 是否存在
    if (!currentSftpManager.value) return;
    // 使用 props.wsDeps 和 currentSftpManager.value.fileList
    if (!props.wsDeps.isConnected.value || selectedItems.value.size === 0) return;
    const itemsToDelete = Array.from(selectedItems.value)
                               .map(filename => currentSftpManager.value?.fileList.value.find((f: FileListItem) => f.filename === filename))
                               .filter((item): item is FileListItem => item !== undefined);
   if (itemsToDelete.length === 0) return;
 
    // 根据设置决定是否显示确认模态框
    if (settingsStore.fileManagerShowDeleteConfirmationBoolean) {
        openActionModal('delete', null, itemsToDelete);
    } else {
        // 直接执行删除
        if (currentSftpManager.value) {
            currentSftpManager.value.deleteItems(itemsToDelete);
            selectedItems.value.clear(); // Clear selection after delete
        }
    }
};
 
const handleRenameContextMenuClick = (item: FileListItem) => { // item 已有类型
    if (!props.wsDeps.isConnected.value || !item) return; // 恢复使用 props.wsDeps
    if (!currentSftpManager.value) return;
    openActionModal('rename', item, undefined, item.filename);
};

const handleChangePermissionsContextMenuClick = (item: FileListItem) => { // item 已有类型
    if (!props.wsDeps.isConnected.value || !item) return; // 恢复使用 props.wsDeps
    if (!currentSftpManager.value) return;
    const currentModeOctal = (item.attrs.mode & 0o777).toString(8).padStart(3, '0');
    openActionModal('chmod', item, undefined, currentModeOctal);
};

const handleNewFolderContextMenuClick = () => {
    if (!props.wsDeps.isConnected.value) return; // 恢复使用 props.wsDeps
    if (!currentSftpManager.value) return;
    openActionModal('newFolder');
};

const handleNewFileContextMenuClick = () => {
    if (!props.wsDeps.isConnected.value) return; // 恢复使用 props.wsDeps
    if (!currentSftpManager.value) return;
    openActionModal('newFile');
};

// +++ 复制、剪切、粘贴处理函数 +++
const handleCopy = () => {
    if (!currentSftpManager.value || selectedItems.value.size === 0) return;
    const manager = currentSftpManager.value;
    clipboardSourcePaths.value = Array.from(selectedItems.value)
        .map(filename => manager.joinPath(manager.currentPath.value, filename));
    clipboardState.value = { hasContent: true, operation: 'copy' };
    clipboardSourceBaseDir.value = manager.currentPath.value; // 记录源目录
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Copied to clipboard:`, clipboardSourcePaths.value);
    // 可选：添加 UI 通知
};

const handleCut = () => {
    if (!currentSftpManager.value || selectedItems.value.size === 0) return;
    const manager = currentSftpManager.value;
    clipboardSourcePaths.value = Array.from(selectedItems.value)
        .map(filename => manager.joinPath(manager.currentPath.value, filename));
    clipboardState.value = { hasContent: true, operation: 'cut' };
    clipboardSourceBaseDir.value = manager.currentPath.value; // 记录源目录
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Cut to clipboard:`, clipboardSourcePaths.value);
    // 可选：添加 UI 通知
};

const handlePaste = () => {
    if (!currentSftpManager.value || !clipboardState.value.hasContent || clipboardSourcePaths.value.length === 0) return;
    const manager = currentSftpManager.value;
    const destinationDir = manager.currentPath.value;
    const operation = clipboardState.value.operation;
    const sources = clipboardSourcePaths.value;
    const sourceBaseDir = clipboardSourceBaseDir.value; // 获取源目录

    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Pasting items. Operation: ${operation}, Sources: ${sources.join(', ')}, Destination: ${destinationDir}`);

    if (operation === 'copy') {
        // 调用 SFTP 管理器的 copyItems 方法 (稍后添加)
        manager.copyItems(sources, destinationDir);
    } else if (operation === 'cut') {
        // 调用 SFTP 管理器的 moveItems 方法 (稍后添加)
        // 检查是否在同一目录下剪切粘贴（无效操作）
        if (sourceBaseDir === destinationDir) {
             console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot cut and paste in the same directory.`);
             // 可选：显示警告通知
             return;
        }
        manager.moveItems(sources, destinationDir);
        // 剪切后清空剪贴板
        clipboardState.value = { hasContent: false };
        clipboardSourcePaths.value = [];
        clipboardSourceBaseDir.value = '';
    }
    // 粘贴后不清空复制的剪贴板，允许重复粘贴
    // 清空选择可能不是最佳体验，用户可能想继续操作粘贴后的文件
    // clearSelection();
};


// --- 文件上传触发器 (定义在此处，供 Composable 使用) ---
const triggerFileUpload = () => { fileInputRef.value?.click(); };

// --- 下载触发器 (定义在此处，供 Composable 使用) ---
const triggerDownload = (items: FileListItem[]) => { // 修改：接受 FileListItem 数组
    // 恢复使用 props.wsDeps.isConnected
    if (!props.wsDeps.isConnected.value) {
        return;
    }
    // connectionId 仍然从 props 获取
    const currentConnectionId = props.dbConnectionId;
    if (!currentConnectionId) {
        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: Missing connection ID.`);
        return;
    }
    // 修改：简化检查
    if (!currentSftpManager.value) {
        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download: SFTP manager is not available.`);
        return;
    }

    // 遍历数组中的每个文件项
    items.forEach(item => {
        // 确保只下载文件
        if (!item.attrs.isFile) {
            console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Skipping download for non-file item: ${item.filename}`);
            return;
        }

        const downloadPath = currentSftpManager.value!.joinPath(currentSftpManager.value!.currentPath.value, item.filename);
        const downloadUrl = `/api/v1/sftp/download?connectionId=${currentConnectionId}&remotePath=${encodeURIComponent(downloadPath)}`;
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Triggering download for ${item.filename}: ${downloadUrl}`);

        // 为每个文件创建一个链接并点击
        const link = document.createElement('a');
        link.href = downloadUrl;
        // --- 修正：移除文件名中的双引号以兼容 Chrome ---
        const safeFilename = item.filename.replace(/"/g, ''); // 移除所有双引号
        link.setAttribute('download', safeFilename);
        // --- 结束修正 ---
        document.body.appendChild(link);
        link.click();

        // 稍微延迟移除链接，以确保下载开始
        setTimeout(() => {
            document.body.removeChild(link);
        }, 100);
    });
};


// +++ 文件夹下载触发器 +++
const triggerDownloadDirectory = (item: FileListItem) => {
    if (!props.wsDeps.isConnected.value) {
        return;
    }
    const currentConnectionId = props.dbConnectionId;
    if (!currentConnectionId) {
        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: Missing connection ID.`);
        return;
    }
    if (!currentSftpManager.value) {
        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot download directory: SFTP manager is not available.`);
        return;
    }

    // 确保是目录
    if (!item.attrs.isDirectory) {
        console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Skipping directory download for non-directory item: ${item.filename}`);
        return;
    }

    const directoryPath = currentSftpManager.value.joinPath(currentSftpManager.value.currentPath.value, item.filename);
    // 定义新的后端 API 端点 URL (稍后实现)
    const downloadUrl = `/api/v1/sftp/download-directory?connectionId=${currentConnectionId}&remotePath=${encodeURIComponent(directoryPath)}`;

    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Attempting directory download for ${item.filename}: ${downloadUrl}`);

    // --- 修改：使用 fetch 尝试下载，并处理后端未实现的情况 ---
    fetch(downloadUrl)
        .then(async response => {
            if (response.ok) {
                // 后端实现成功，尝试触发下载
                const blob = await response.blob();
                // 从 Content-Disposition 头获取文件名 (需要后端设置)
                const contentDisposition = response.headers.get('content-disposition');
                let filename = `${item.filename}.zip`; // 默认文件名
                if (contentDisposition) {
                    const filenameMatch = contentDisposition.match(/filename="?(.+)"?/i);
                    if (filenameMatch && filenameMatch.length > 1) {
                        filename = filenameMatch[1];
                    }
                }

                const link = document.createElement('a');
                link.href = URL.createObjectURL(blob);
                // --- 修正：移除 ZIP 文件名中的双引号以兼容 Chrome ---
                const safeZipFilename = filename.replace(/"/g, '');
                link.setAttribute('download', safeZipFilename);
                // --- 结束修正 ---
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
                URL.revokeObjectURL(link.href); // 释放对象 URL
                console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Directory download triggered for: ${filename}`);
            } else {
                // 处理错误，例如 404 Not Found
                console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Directory download failed: ${response.status} ${response.statusText}`);
                // 尝试读取错误信息体
                let errorMsg = `Server responded with status ${response.status}`;
                try {
                    const errorData = await response.json(); // 假设后端返回 JSON 错误
                    errorMsg = errorData.message || errorMsg;
                } catch (e) {
                    // 如果响应体不是 JSON 或读取失败
                    try {
                       const textError = await response.text();
                       if (textError) errorMsg = textError;
                    } catch (e2) { /* ignore */}
                }

            }
        })
        .catch(error => {
            console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Network error during directory download:`, error);
        });
    
};



// +++ 压缩/解压处理函数 +++
const handleCompress = (items: FileListItem[], format: CompressFormat) => {
  if (!currentSftpManager.value) {
    console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot compress: SFTP manager not available.`);
    // TODO: Show error notification
    return;
  }
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Requesting compression for ${items.length} items, format: ${format}`);
  // 调用 SFTP 管理器上的新方法 (将在 useSftpActions.ts 中实现)
  currentSftpManager.value.compressItems(items, format);
};

const handleDecompress = (item: FileListItem) => {
  if (!currentSftpManager.value) {
    console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot decompress: SFTP manager not available.`);
    // TODO: Show error notification
    return;
  }
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Requesting decompression for item: ${item.filename}`);
  // 调用 SFTP 管理器上的新方法 (将在 useSftpActions.ts 中实现)
  currentSftpManager.value.decompressItem(item);
};


// +++ 复制路径到剪贴板 +++
const handleCopyPath = async (item: FileListItem) => {
  if (!currentSftpManager.value) return;
  const fullPath = currentSftpManager.value.joinPath(currentSftpManager.value.currentPath.value, item.filename);
  try {
    await navigator.clipboard.writeText(fullPath);
    // 可选：显示成功通知
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Copied path to clipboard: ${fullPath}`);
    uiNotificationsStore.showSuccess(t('fileManager.notifications.pathCopied', 'Path copied to clipboard'));
  } catch (err) {
    console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to copy path: `, err);
    // 可选：显示错误通知
    uiNotificationsStore.showError(t('fileManager.errors.copyPathFailed', 'Failed to copy path'));
  }
};

// --- 上下文菜单逻辑 (使用 Composable, 需要 Selection 和 Action Handlers) ---
const {
  contextMenuVisible,
  contextMenuPosition,
  contextMenuItems,
  contextMenuRef, // 获取 ref 以传递给子组件
  contextTargetItem, // Get the target item from the composable
  showContextMenu, // 使用 Composable 提供的函数
  hideContextMenu, // <-- 获取 hideContextMenu 函数
} = useFileManagerContextMenu({
  selectedItems,
  lastClickedIndex,
  // 修改：传递 manager 的 fileList 和 currentPath ref (保持 computed)
  fileList: computed(() => currentSftpManager.value?.fileList.value ?? []),
  currentPath: computed(() => currentSftpManager.value?.currentPath.value ?? '/'),
  isConnected: props.wsDeps.isConnected,
  isSftpReady: props.wsDeps.isSftpReady,
  clipboardState: readonly(clipboardState), // +++ 传递剪贴板状态 (只读) +++
  t,
  // --- 传递回调函数 ---
  // 修改：确保在调用前检查 currentSftpManager.value
  onRefresh: () => {
      if (currentSftpManager.value) {
          currentSftpManager.value.loadDirectory(currentSftpManager.value.currentPath.value, true);
      }
  },
  onUpload: triggerFileUpload,
  onDownload: triggerDownload,
  onDelete: handleDeleteSelectedClick,
  onRename: handleRenameContextMenuClick,
  onChangePermissions: handleChangePermissionsContextMenuClick,
  onNewFolder: handleNewFolderContextMenuClick,
  onNewFile: handleNewFileContextMenuClick,
  onCopy: handleCopy, // +++ 传递复制回调 +++
  onCut: handleCut, // +++ 传递剪切回调 +++
  onPaste: handlePaste, // +++ 传递粘贴回调 +++
  onDownloadDirectory: triggerDownloadDirectory, // +++ 传递文件夹下载回调 +++
  // +++ 传递压缩/解压回调 +++
  onCompressRequest: handleCompress,
  onDecompressRequest: handleDecompress,
  onCopyPath: handleCopyPath, // +++ 传递复制路径回调 +++
});

// --- 目录加载与导航 ---
// loadDirectory is provided by props.sftpManager

// --- 拖放逻辑 (使用 Composable) ---
const {
  // isDraggingOver, // 不再直接使用容器的悬停状态
  showExternalDropOverlay, // 控制蒙版显示
  dragOverTarget, // 行拖拽悬停目标 (内部)
  // draggedItem, // 内部状态，不需要在 FileManager 中直接使用
  // --- 事件处理器 ---
  handleDragEnter,
  handleDragOver, // 容器的 dragover (主要处理内部滚动)
  handleDragLeave,
  handleDrop, // 容器的 drop (主要用于清理)
  handleOverlayDrop, // 蒙版的 drop
  handleDragStart,
  handleDragEnd,
  handleDragOverRow,
  handleDragLeaveRow,
  handleDropOnRow,
} = useFileManagerDragAndDrop({
  isConnected: computed(() => props.wsDeps.isConnected.value), 
  // 修改：传递 manager 的 currentPath (保持 computed)
  currentPath: computed(() => currentSftpManager.value?.currentPath.value ?? '/'),
  fileListContainerRef: fileListContainerRef,
  // 修改：传递一个包装函数给 joinPath
  joinPath: (base: string, target: string): string => {
      return currentSftpManager.value?.joinPath(base, target) ?? `${base}/${target}`.replace(/\/+/g, '/'); // 提供简单的默认实现
  },
  onFileUpload: startFileUpload,
  // 修改：确保在调用前检查 currentSftpManager.value
  onItemMove: (item, newName) => {
      currentSftpManager.value?.renameItem(item, newName);
  },
  selectedItems: selectedItems,
  // 修改：传递 manager 的 fileList ref (保持 computed)
  fileList: computed(() => currentSftpManager.value?.fileList.value ?? []),
});


// --- 文件上传逻辑 (handleFileSelected 保持在此处，由 triggerFileUpload 调用) ---
const handleFileSelected = (event: Event) => {
    const input = event.target as HTMLInputElement;
    // 恢复使用 props.wsDeps.isConnected
    if (!input.files || !props.wsDeps.isConnected.value) return;
    // --- 修正：使用匿名函数包装 startFileUpload 调用 ---
    Array.from(input.files).forEach(file => startFileUpload(file)); // 只传递 file 参数
    // --- 结束修正 ---
    input.value = '';
};

// --- 键盘导航逻辑 (使用 Composable) ---
const {
  selectedIndex, // 使用 Composable 返回的 selectedIndex
  handleKeydown, // 使用 Composable 返回的 handleKeydown
} = useFileManagerKeyboardNavigation({
  filteredFileList: filteredFileList,
  // 修改：传递 manager 的 currentPath ref
  currentPath: computed(() => currentSftpManager.value?.currentPath.value ?? '/'),
  fileListContainerRef: fileListContainerRef,
  // 当 Enter 键按下时，模拟鼠标单击
  onEnterPress: (item) => handleItemClick(new MouseEvent('click'), item),
});


// --- 重置选中索引和清空选择的 Watchers ---
// 修改：监听 manager 的 currentPath
watch(() => currentSftpManager.value?.currentPath.value, () => {
    selectedIndex.value = -1;
    clearSelection();
});
watch(searchQuery, () => {
    selectedIndex.value = -1;
    clearSelection(); // 清空选择
});
watch(sortKey, () => {
    selectedIndex.value = -1;
    clearSelection(); // 清空选择
});
watch(sortDirection, () => {
    selectedIndex.value = -1;
    clearSelection(); // 清空选择
});


// --- 保存设置的函数 ---
const saveLayoutSettings = () => {
  // 确保 colWidths.value 是普通对象，而不是 Proxy
  const widthsToSave = JSON.parse(JSON.stringify(colWidths.value));
  // +++ 日志：记录保存的值 +++
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Triggering saveLayoutSettings: multiplier=${rowSizeMultiplier.value}, widths=${JSON.stringify(widthsToSave)}`);
  settingsStore.updateFileManagerLayoutSettings(rowSizeMultiplier.value, widthsToSave);
};

// --- 生命周期钩子 ---
onMounted(() => {
    // --- 移除 onMounted 中的加载逻辑 ---
    // Initial load logic is handled by watchEffect below and the main sftp loading watchEffect
});

// +++ 使用 watchEffect 响应式地加载和应用布局设置 +++
watchEffect(() => {
  // 检查 store 中的值是否有效 (避免在 store 加载完成前使用默认值覆盖本地 ref)
  // fileManagerColWidthsObject 初始可能是空对象 {}，需要检查其是否有键
  const storeMultiplier = fileManagerRowSizeMultiplierNumber.value;
  const storeWidths = fileManagerColWidthsObject.value;

  // +++ 日志：记录从 store 获取的值 +++
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] watchEffect triggered. Store values: multiplier=${storeMultiplier}, widths=${JSON.stringify(storeWidths)}`);

  // 只有当 store 加载完成并提供了有效值时才更新
  // 假设 store 加载完成后 multiplier > 0 且 widths 对象有内容
  if (storeMultiplier > 0 && Object.keys(storeWidths).length > 0) {
    const currentMultiplier = rowSizeMultiplier.value;
    const currentWidthsString = JSON.stringify(colWidths.value);
    const storeWidthsString = JSON.stringify(storeWidths);

    // +++ 日志：记录当前值和 store 值，以及是否更新 +++
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Comparing values: Current Multiplier=${currentMultiplier}, Store Multiplier=${storeMultiplier}. Update needed: ${storeMultiplier !== currentMultiplier}`);
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Comparing values: Current Widths=${currentWidthsString}, Store Widths=${storeWidthsString}. Update needed: ${storeWidthsString !== currentWidthsString}`);

    // 仅在值不同时更新，避免不必要的重渲染和潜在的循环更新
    if (storeMultiplier !== currentMultiplier) {
      rowSizeMultiplier.value = storeMultiplier;
      console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Row size multiplier updated from store: ${storeMultiplier}`);
    }
    if (storeWidthsString !== currentWidthsString) {
      // --- 修改：合并 storeWidths 到 colWidths.value ---
      // 确保 colWidths.value 的所有键都存在，并用 store 的值更新（如果存在且有效）
      const updatedWidths = { ...colWidths.value }; // 创建当前值的副本
      for (const key in updatedWidths) {
        if (storeWidths[key] !== undefined && typeof storeWidths[key] === 'number' && storeWidths[key] > 0) {
          updatedWidths[key as keyof typeof updatedWidths] = storeWidths[key];
        }
      }
      colWidths.value = updatedWidths; // 赋值更新后的对象
      console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Column widths updated from store: ${JSON.stringify(updatedWidths)}`);
    }
  } else {
    // +++ 日志：记录等待 store 加载 +++
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Waiting for valid layout settings from store... Store Multiplier=${storeMultiplier}, Store Widths Keys=${Object.keys(storeWidths).length}`);
  }
});

// 使用 watchEffect 监听连接和 SFTP 就绪状态以触发初始加载
// 恢复使用 props.wsDeps
watchEffect((onCleanup) => {
    let unregisterSuccess: (() => void) | undefined;
    let unregisterError: (() => void) | undefined;
    let timeoutId: NodeJS.Timeout | number | undefined; // 修正类型以兼容 Node 和浏览器环境

    const cleanupListeners = () => {
        unregisterSuccess?.();
        unregisterError?.();
        if (timeoutId) clearTimeout(timeoutId);
        // isFetchingInitialPath 状态移除
    };

    onCleanup(cleanupListeners);

    // 修改：添加 ?. 访问 isLoading, 检查 manager 的 initialLoadDone
    // 只有在连接就绪、SFTP 就绪、管理器存在、未加载且 initialLoadDone 为 false 时才获取初始路径
    if (currentSftpManager.value && props.wsDeps.isConnected.value && props.wsDeps.isSftpReady.value && !currentSftpManager.value.isLoading.value && !currentSftpManager.value.initialLoadDone.value) {
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Connection ready for manager, fetching initial path for the first time (isLoading: ${currentSftpManager.value.isLoading.value}, initialLoadDone: ${currentSftpManager.value.initialLoadDone.value}).`);
        // isFetchingInitialPath 状态移除, 使用 isLoading 状态

        // 仍然使用 props.wsDeps 中的 sendMessage 和 onMessage
        const { sendMessage: wsSend, onMessage: wsOnMessage } = props.wsDeps;
        const requestId = generateRequestId(); // 使用本地辅助函数
        const requestedPath = '.';

        unregisterSuccess = wsOnMessage('sftp:realpath:success', (payload: any, message: WebSocketMessage) => { // message 已有类型
            if (message.requestId === requestId && payload.requestedPath === requestedPath) {
                // 修改：检查 currentSftpManager 是否存在
                if (!currentSftpManager.value) return;
                const absolutePath = payload.absolutePath;
                console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Received initial absolute path for '.': ${absolutePath}. Loading directory.`);
                // 修改：添加 ?. 访问 loadDirectory 和 setInitialLoadDone
                currentSftpManager.value?.loadDirectory(absolutePath);
                currentSftpManager.value?.setInitialLoadDone(true); // 设置 manager 内部状态
                cleanupListeners();
            }
        });

        unregisterError = wsOnMessage('sftp:realpath:error', (payload: any, message: WebSocketMessage) => { // message 已有类型
            // 修改：使用 payload.requestedPath (如果存在) 或 message.requestId 匹配
            if (message.requestId === requestId && payload?.requestedPath === requestedPath) {
                console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to get realpath for '${requestedPath}':`, payload);
                // TODO: 可以考虑通过 manager instance 暴露错误状态
                // 目前仅记录日志。
                // 即使获取 realpath 失败，也标记初始加载尝试完成，避免重复尝试
                currentSftpManager.value?.setInitialLoadDone(true);
                cleanupListeners();
            }
        });

        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Sending initial sftp:realpath request (ID: ${requestId}) for path: ${requestedPath}`);
        wsSend({ type: 'sftp:realpath', requestId: requestId, payload: { path: requestedPath } });

        timeoutId = setTimeout(() => {
            console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Timeout getting initial realpath for '.' (ID: ${requestId}).`);
            // 超时也标记初始加载尝试完成
            currentSftpManager.value?.setInitialLoadDone(true);
            cleanupListeners();
        }, 10000); // 10 秒超时

    } else if (currentSftpManager.value && props.wsDeps.isConnected.value && props.wsDeps.isSftpReady.value && currentSftpManager.value.initialLoadDone.value) {
        // 连接恢复，并且之前已经加载过 (initialLoadDone is true)
        // 显式地重新加载管理器中记录的当前路径，以防内部状态被重置
        const pathBeforeReconnect = currentSftpManager.value.currentPath.value;
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Connection re-established. Explicitly reloading previous path: ${pathBeforeReconnect}`);
        // 检查是否正在加载，避免并发请求
        if (!currentSftpManager.value.isLoading.value) {
             // 使用 false 参数可能表示非强制刷新，如果 SFTP 管理器支持的话
             // 主要目的是确保视图与管理器状态同步到重连前的路径
            currentSftpManager.value.loadDirectory(pathBeforeReconnect, false);
        } else {
            console.log(`[FileManager ${props.sessionId}-${props.instanceId}] SFTP manager is currently loading, skipping explicit path reload on reconnect.`);
        }
        cleanupListeners(); // 清理可能存在的旧监听器

    } else if (!props.wsDeps.isConnected.value && currentSftpManager.value?.initialLoadDone.value) { // 检查 manager 的 initialLoadDone
        // 连接丢失，不需要重置 initialLoadDone，因为我们希望在重连时恢复状态
        // 只需要清理监听器
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Connection lost (was previously loaded).`);
        // clearSelection(); // 可以在连接丢失时不清空选择，看产品需求
        // currentSftpManager.value?.setInitialLoadDone(false); // 不再重置，保持状态
        cleanupListeners();
    }
});

// +++ 监听 Store 中的触发器以激活搜索 +++
watch(() => focusSwitcherStore.activateFileManagerSearchTrigger, (newValue, oldValue) => { // 修改监听器
    // 确保只在触发器值增加时执行（避免初始加载或重置时触发）
    // 并且当前组件的 sessionId 与活动 sessionId 匹配
    // 检查 newValue > oldValue 确保是递增触发，避免重复执行
    // 检查是否是当前活动会话的此实例（如果需要区分实例）
    // 目前假设搜索触发器对会话内的所有 FileManager 生效
    if (newValue > (oldValue ?? 0) && props.sessionId === sessionStore.activeSessionId) {
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Received search activation trigger for active session.`);
        activateSearch(); // 调用组件内部的激活搜索方法
    }
}, { immediate: false }); // 添加 immediate: false 避免初始值为 0 时触发


// --- 监听 sessionId prop 的变化 ---
watch(() => props.sessionId, (newSessionId, oldSessionId) => {
    if (newSessionId && newSessionId !== oldSessionId) {
        closePathHistory(); // 关闭可能打开的路径历史下拉菜单
        pathHistoryStore.setSearchTerm(''); // 清空搜索词
        // 1. 重新初始化 SFTP 管理器
        initializeSftpManager(newSessionId, props.instanceId);

        // 2. 重置 UI 状态
        clearSelection();
        searchQuery.value = '';
        isSearchActive.value = false;
        isEditingPath.value = false;
        sortKey.value = 'filename'; // 重置排序
        sortDirection.value = 'asc';
    }
}, { immediate: false }); // immediate: false 避免初始挂载时触发



// +++ 注册/注销自定义聚焦动作 +++
let unregisterSearchFocusAction: (() => void) | null = null; // 搜索框注销函数
let unregisterPathFocusAction: (() => void) | null = null; // 路径编辑框注销函数

onMounted(() => {
  // 注册搜索框聚焦动作
  const focusSearchActionWrapper = async (): Promise<boolean | undefined> => {
    if (props.sessionId === sessionStore.activeSessionId) {
      console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Executing search focus action for active session.`);
      closePathHistory(); // Close path history if open
      return focusSearchInput();
    } else {
      console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Search focus action skipped for inactive session.`);
      return undefined;
    }
  };
  unregisterSearchFocusAction = focusSwitcherStore.registerFocusAction('fileManagerSearch', focusSearchActionWrapper);

  // 注册路径编辑框聚焦动作
  const focusPathActionWrapper = async (): Promise<boolean | undefined> => {
     if (props.sessionId === sessionStore.activeSessionId) {
       console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Executing path edit focus action for active session.`);
       // startPathEdit 本身不是 async，但注册时需要包装成 async 以匹配类型
       startPathEdit(); // 调用暴露的方法
       return true;
     } else {
       console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Path edit focus action skipped for inactive session.`);
       return undefined;
     }
  };
  unregisterPathFocusAction = focusSwitcherStore.registerFocusAction('fileManagerPathInput', focusPathActionWrapper);
  document.addEventListener('click', handleClickOutsidePathInput);
});

onBeforeUnmount(() => {
 // 注销搜索框动作
 if (unregisterSearchFocusAction) {
   unregisterSearchFocusAction();
   console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Unregistered search focus action on unmount.`);
 }
 unregisterSearchFocusAction = null;

 // 注销路径编辑框动作
 if (unregisterPathFocusAction) {
   unregisterPathFocusAction();
   console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Unregistered path edit focus action on unmount.`);
 }
 unregisterPathFocusAction = null;
 document.removeEventListener('click', handleClickOutsidePathInput);
 sessionStore.removeSftpManager(props.sessionId, props.instanceId);
});

// +++ 监听蒙版可见性，动态调整高度 +++
watch(showExternalDropOverlay, (isVisible) => {
  if (isVisible) {
    nextTick(() => { // 确保 refs 可用且 scrollHeight 已计算
      if (dropOverlayRef.value && fileListContainerRef.value) {
        const scrollHeight = fileListContainerRef.value.scrollHeight;
        dropOverlayRef.value.style.height = `${scrollHeight}px`;
      }
    });
  } else {
    // 蒙版隐藏时重置高度
    if (dropOverlayRef.value) {
      dropOverlayRef.value.style.height = ''; // 移除内联样式
      // console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Overlay hidden. Resetting height.`);
    }
  }
});

// --- 列宽调整逻辑 (保持不变) ---
const getColumnKeyByIndex = (index: number): keyof typeof colWidths.value | null => {
    const keys = Object.keys(colWidths.value) as Array<keyof typeof colWidths.value>;
    return keys[index] ?? null;
};

const startResize = (event: MouseEvent, index: number) => {
    event.stopPropagation();
    event.preventDefault();
    isResizing.value = true;
    resizingColumnIndex.value = index;
    startX.value = event.clientX;
    const colKey = getColumnKeyByIndex(index);
    if (colKey) {
        startWidth.value = colWidths.value[colKey];
    } else {
        const thElement = (event.target as HTMLElement).closest('th');
        startWidth.value = thElement?.offsetWidth ?? 100;
    }
    document.addEventListener('mousemove', handleResize);
    document.addEventListener('mouseup', stopResize);
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
};

const handleResize = (event: MouseEvent) => {
    if (!isResizing.value || resizingColumnIndex.value < 0) return;
    const currentX = event.clientX;
    const diffX = currentX - startX.value;
    const newWidth = Math.max(30, startWidth.value + diffX);
    const colKey = getColumnKeyByIndex(resizingColumnIndex.value);
    if (colKey) {
        colWidths.value[colKey] = newWidth;
    }
};

const stopResize = () => {
    if (isResizing.value) {
        isResizing.value = false;
        resizingColumnIndex.value = -1;
        document.removeEventListener('mousemove', handleResize);
        document.removeEventListener('mouseup', stopResize);
        document.body.style.cursor = '';
        document.body.style.userSelect = '';
        // +++ 在调整结束后保存列宽 +++
        // +++ 日志：记录触发保存 +++
        console.log(`[FileManager ${props.sessionId}-${props.instanceId}] stopResize triggered saveLayoutSettings.`);
        saveLayoutSettings();
    }
};

// --- 路径编辑逻辑 (包含路径历史) ---

const openPathHistory = () => {
  showPathHistoryDropdown.value = true; // 总是尝试显示下拉框
  // 如果列表为空，则尝试获取历史记录。
  // pathHistoryStore.fetchHistory() 应该能够处理未连接时 apiClient 的失败。
  if (pathHistoryStore.historyList.length === 0) {
    pathHistoryStore.fetchHistory();
  }
  // 总是设置搜索词，以便即使历史记录是旧的或空的，也能基于当前输入进行过滤或显示。
  pathHistoryStore.setSearchTerm(editablePath.value);
};

const closePathHistory = () => {
  showPathHistoryDropdown.value = false;
  pathHistoryStore.resetSelection();
};

const handlePathInputFocus = () => {
  isEditingPath.value = true; // Keep existing behavior
  if (!currentSftpManager.value || currentSftpManager.value.isLoading.value || !props.wsDeps.isConnected.value) return;
  editablePath.value = currentSftpManager.value.currentPath.value; // Set editable path on focus
  openPathHistory();
  nextTick(() => {
    pathInputRef.value?.select();
  });
};

const handlePathInputChange = () => {
  if (showPathHistoryDropdown.value) {
    pathHistoryStore.setSearchTerm(editablePath.value);
  }
};

const navigateToPath = async (path: string) => {
  if (!currentSftpManager.value || !path || path.trim().length === 0) return;
  const trimmedPath = path.trim();
  isEditingPath.value = false;
  closePathHistory();

  if (trimmedPath === currentSftpManager.value.currentPath.value) {
    return;
  }

  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] 尝试导航到新路径: ${trimmedPath}`);
  try {
    await currentSftpManager.value.loadDirectory(trimmedPath);
    // 如果 loadDirectory 没有抛出错误，我们认为它成功了
    pathHistoryStore.addPath(trimmedPath); // 导航成功后添加到历史
    editablePath.value = trimmedPath; // 更新输入框内容
  } catch (error) {
    console.error(`[FileManager ${props.sessionId}-${props.instanceId}] 导航到路径 ${trimmedPath} 失败:`, error);
    // 导航失败，不添加到历史记录，也不更新输入框内容 (除非有特定需求)
  }
};

const handlePathInputKeydown = (event: KeyboardEvent) => {
  if (!showPathHistoryDropdown.value) {
    if (event.key === 'Enter') {
      navigateToPath(editablePath.value);
    } else if (event.key === 'Escape') {
      cancelPathEdit();
    }
    return;
  }

  switch (event.key) {
    case 'ArrowDown':
      event.preventDefault();
      pathHistoryStore.selectNextPath();
      // Dropdown component handles scrolling
      break;
    case 'ArrowUp':
      event.preventDefault();
      pathHistoryStore.selectPreviousPath();
      // Dropdown component handles scrolling
      break;
    case 'Enter':
      event.preventDefault();
      if (pathSelectedIndex.value >= 0 && filteredPathHistory.value[pathSelectedIndex.value]) {
        navigateToPath(filteredPathHistory.value[pathSelectedIndex.value].path);
      } else {
        navigateToPath(editablePath.value);
      }
      closePathHistory();
      break;
    case 'Escape':
      event.preventDefault();
      closePathHistory();
      // Keep isEditingPath true to allow user to continue editing or blur
      break;
  }
};

const handlePathSelectedFromDropdown = (path: string) => {
  editablePath.value = path; // Update input field
  navigateToPath(path); // Navigate and add to history
  closePathHistory();
};

const startPathEdit = () => {
    if (!currentSftpManager.value || currentSftpManager.value.isLoading.value || !props.wsDeps.isConnected.value) return;
    editablePath.value = currentSftpManager.value.currentPath.value;
    isEditingPath.value = true;
    openPathHistory(); // 打开历史记录
    nextTick(() => {
        pathInputRef.value?.focus();
        pathInputRef.value?.select();
    });
};

// Modified to handle path history logic
const handlePathInput = async (event?: Event | FocusEvent) => {
    // This function is now primarily for blur handling or if Enter is pressed outside keydown.
    // Most Enter logic is in handlePathInputKeydown.
    if (event && event instanceof KeyboardEvent && event.key !== 'Enter') {
      // If it's a key event but not Enter, it's handled by keydown or change.
      return;
    }

    if (event && event.type === 'blur') {
      setTimeout(() => {
        const activeEl = document.activeElement;
        const dropdownEl = pathHistoryDropdownRef.value?.$el;
        if (dropdownEl && dropdownEl.contains(activeEl)) {
          // Focus is within the dropdown, do nothing yet
          return;
        }
        if (pathInputRef.value !== activeEl) { 
            isEditingPath.value = false;
            closePathHistory();
        }
      }, 150); 
      return; 
    }

  
    if (!currentSftpManager.value) return;

    const newPath = editablePath.value.trim();
    // Check if dropdown has a selection, if so, it should have been handled by Enter in keydown
    if (pathSelectedIndex.value >= 0 && filteredPathHistory.value[pathSelectedIndex.value]) {
        // This case should ideally not be hit if keydown is working correctly
        navigateToPath(filteredPathHistory.value[pathSelectedIndex.value].path);
    } else {
        navigateToPath(newPath);
    }
    isEditingPath.value = false; // Ensure editing mode is exited
    closePathHistory(); // Ensure dropdown is closed
};


const cancelPathEdit = () => {
    isEditingPath.value = false;
    closePathHistory();
    // Optionally, revert editablePath to currentSftpManager.currentPath.value
    if (currentSftpManager.value) {
        editablePath.value = currentSftpManager.value.currentPath.value;
    }
};

const handleClickOutsidePathInput = (event: MouseEvent) => {
  if (pathInputWrapperRef.value && !pathInputWrapperRef.value.contains(event.target as Node)) {
    if (isEditingPath.value || showPathHistoryDropdown.value) {
        isEditingPath.value = false;
        closePathHistory();
    }
  }
};


// --- 搜索框激活/取消逻辑 ---
const activateSearch = () => {
  isSearchActive.value = true;
  nextTick(() => {
    searchInputRef.value?.focus();
  });
};

const deactivateSearch = () => {
        isSearchActive.value = false;

};

const cancelSearch = () => {
    searchQuery.value = ''; // 按 Esc 清空并失活
    isSearchActive.value = false;
};

// --- 发送 CD 命令到终端的方法 ---
const sendCdCommandToTerminal = () => {
  if (!currentSftpManager.value || !props.wsDeps.isConnected.value) {
    console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot send CD command: SFTP manager not ready or not connected.`);
    return;
  }
  const currentPath = currentSftpManager.value.currentPath.value;
  if (!currentPath) {
    console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Cannot send CD command: Current path is empty.`);
    return;
  }

  // 路径可能包含空格，需要用引号括起来以确保在各种 shell 中正确处理
  const escapedPath = `"${currentPath}"`;
  // 添加换行符以模拟按下 Enter 键执行命令
  const command = `cd ${escapedPath}\n`;

  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Sending command to terminal: ${command.trim()}`);
  try {
    // 获取当前活动会话
    const activeSession = sessionStore.activeSession;
    if (!activeSession) {
      console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to send command: No active session found.`);
      // 可选：添加 UI 通知
      // uiNotificationsStore.addNotification({ message: t('fileManager.errors.noActiveSession', 'No active session found.'), type: 'error' });
      return;
    }
    // 检查 terminalManager 是否存在
    if (!activeSession.terminalManager) {
        console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to send command: Terminal manager not found for active session.`);
        // 可选：添加 UI 通知
        // uiNotificationsStore.addNotification({ message: t('fileManager.errors.terminalManagerNotFound', 'Terminal manager not found.'), type: 'error' });
        return;
    }
    // 使用 terminalManager 的 sendData 方法发送命令
    activeSession.terminalManager.sendData(command);
  } catch (error) {
    console.error(`[FileManager ${props.sessionId}-${props.instanceId}] Failed to send command to terminal:`, error);
  }
};


// --- 打开弹窗编辑器的方法 ---
const openPopupEditor = () => {
  if (!props.sessionId) {
    console.error('[FileManager] Cannot open popup editor: Missing session ID.');
    // 可以添加 UI 通知
    return;
  }
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Triggering popup editor without specific file.`);
  fileEditorStore.triggerPopup('', props.sessionId); // 修复：使用空字符串触发空编辑器
};
// --- 行大小调整逻辑 ---
const handleWheel = (event: WheelEvent) => {
    if (event.ctrlKey) {
        event.preventDefault(); // 阻止页面默认滚动行为
        const delta = event.deltaY > 0 ? -0.05 : 0.05; // 滚轮向下减小，向上增大
        // 限制字体大小乘数在 0.5 到 2 之间
        const newMultiplier = Math.max(0.5, Math.min(2, rowSizeMultiplier.value + delta));
        const oldMultiplier = rowSizeMultiplier.value;
        rowSizeMultiplier.value = parseFloat(newMultiplier.toFixed(2)); // 保留两位小数避免浮点数问题
        if (rowSizeMultiplier.value !== oldMultiplier) {
            // +++ 日志：记录触发保存 +++
            console.log(`[FileManager ${props.sessionId}-${props.instanceId}] handleWheel triggered saveLayoutSettings.`);
            saveLayoutSettings();
        }
    }
};

// +++ 聚焦搜索框的方法 +++
const focusSearchInput = (): boolean => {
  // 检查当前会话是否激活，防止后台实例响应
  if (props.sessionId !== sessionStore.activeSessionId) {
      console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Ignoring focus request for inactive session.`);
      return false;
  }

  if (!isSearchActive.value) {
    activateSearch(); // Activate search first
    // nextTick 确保 DOM 更新后再聚焦
    nextTick(() => {
        if (searchInputRef.value) {
            searchInputRef.value.focus();
            console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Search activated and input focused.`);
        } else {
            console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Search activated but input ref not found after nextTick.`);
        }
    });
    return true; // 假设会成功
  } else if (searchInputRef.value) {
    searchInputRef.value.focus();
    console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Search already active, input focused.`);
    return true;
  }
  console.warn(`[FileManager ${props.sessionId}-${props.instanceId}] Could not focus search input.`);
  return false;
};
defineExpose({ focusSearchInput, startPathEdit });

// --- 处理“打开编辑器”按钮点击 ---
const handleOpenEditorClick = () => {
  if (!props.sessionId) {
    console.error(`[FileManager ${props.instanceId}] Cannot open editor: Missing session ID.`);
    // TODO: Show error notification to user
    return;
  }
  console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Triggering popup editor directly.`);
  fileEditorStore.triggerPopup('', props.sessionId); // 修复：传递空字符串而不是 null
 };
 
 // +++ Favorite Paths Modal Logic +++
 const toggleFavoritePathsModal = () => {
   showFavoritePathsModal.value = !showFavoritePathsModal.value;
   console.log(`[FileManager ${props.sessionId}-${props.instanceId}] Toggled FavoritePathsModal. Visible: ${showFavoritePathsModal.value}`);
 };
 
const handleNavigateToPathFromFavorites = (path: string) => {
  if (currentSftpManager.value) {
     currentSftpManager.value.loadDirectory(path);
     explorerExpandedPaths.value[path] = true;
  }
  showFavoritePathsModal.value = false; // Close modal after navigation
};

const toggleDirectoryPath = (path: string, currentExpanded = false) => {
  const nextExpanded = !(explorerExpandedPaths.value[path] ?? currentExpanded);
  explorerExpandedPaths.value[path] = nextExpanded;

  if (nextExpanded && currentSftpManager.value) {
    currentSftpManager.value.loadDirectory(path);
  }
};

const focusDirectoryPath = (path: string) => {
  explorerExpandedPaths.value[path] = true;
  currentSftpManager.value?.loadDirectory(path);
};

const isPathActive = (path: string) => {
  return currentSftpManager.value?.currentPath.value === path;
};

const handleExplorerToggle = (row: ExplorerTreeRow) => {
  if (!row.isDirectory) {
    return;
  }

  toggleDirectoryPath(row.path, row.expanded);
};

const handleExplorerOpen = (row: ExplorerTreeRow) => {
  if (row.isDirectory) {
    focusDirectoryPath(row.path);
    return;
  }

  openFileInWorkspace(row.path, row.name);
};

const handleOverviewSectionOpen = (section: ExplorerOverviewSection) => {
  focusDirectoryPath(section.path);
};

const handleOverviewRowToggle = (row: ExplorerOverviewRow) => {
  toggleDirectoryPath(row.path, row.expanded);
};

const handleOverviewRowOpen = (row: ExplorerOverviewRow) => {
  focusDirectoryPath(row.path);
};

const handleOverviewRefresh = (section: ExplorerOverviewSection) => {
  explorerExpandedPaths.value[section.path] = true;
  currentSftpManager.value?.loadDirectory(section.path, true);
};

const isExplorerRowActive = (row: ExplorerTreeRow) => {
  return isPathActive(row.path);
};

const isExplorerRowRelated = (row: ExplorerTreeRow) => {
  const currentPath = currentSftpManager.value?.currentPath.value;
  if (!currentPath) {
    return false;
  }

  if (row.path === '/') {
    return true;
  }

  return currentPath === row.path || currentPath.startsWith(`${row.path}/`);
};

watch(
  explorerRoots,
  (roots) => {
    roots.forEach((root) => {
      if (explorerExpandedPaths.value[root.path] === undefined) {
        explorerExpandedPaths.value[root.path] = true;
      }
    });
  },
  { immediate: true },
);
 </script>

<template>
  <div class="flex flex-col h-full overflow-hidden bg-background text-foreground text-sm font-sans">
    <div class="flex items-center justify-between flex-wrap gap-2 p-2 bg-header  flex-shrink-0">
        <!-- Wrapper for Path Actions and Path Bar -->
        <div class="flex items-center gap-2 flex-grow min-w-0"> <!-- Added gap-2, flex-grow, min-w-0 -->
            <!-- Path Actions -->
            <div class="flex items-center flex-shrink-0"> <!-- Removed mr-auto -->
              <!-- CD 到终端按钮 -->
              <button
                class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-black/10 hover:enabled:text-foreground"
                @click.stop="sendCdCommandToTerminal"
                :disabled="!currentSftpManager || !props.wsDeps.isConnected.value || isEditingPath"
                :title="t('fileManager.actions.cdToTerminal', 'Change terminal directory to current path')"
              >
                <i class="fas fa-terminal text-base"></i>
              </button>
              <!-- 刷新按钮 -->
              <button
                class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-black/10 hover:enabled:text-foreground"
                @click.stop="currentSftpManager?.loadDirectory(currentSftpManager?.currentPath?.value ?? '/', true)"
                :disabled="!currentSftpManager || !props.wsDeps.isConnected.value || isEditingPath"
                :title="t('fileManager.actions.refresh')"
              >
                <i class="fas fa-sync-alt text-base"></i>
              </button>
              <button
                class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-black/10 hover:enabled:text-foreground"
                @click.stop="handleItemClick($event, { filename: '..', longname: '..', attrs: { isDirectory: true, isFile: false, isSymbolicLink: false, size: 0, uid: 0, gid: 0, mode: 0, atime: 0, mtime: 0 } })"
                :disabled="!currentSftpManager || !props.wsDeps.isConnected.value || currentSftpManager?.currentPath?.value === '/' || isEditingPath"
                :title="t('fileManager.actions.parentDirectory')"
              >
                <i class="fas fa-arrow-up text-base"></i>
              </button>
             <!-- Search Area -->
             <div class="flex items-center flex-shrink-0">
                 <button
                     v-if="!isSearchActive"
                     class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-black/10 hover:enabled:text-foreground"
                     @click.stop="activateSearch"
                     :disabled="!currentSftpManager || !props.wsDeps.isConnected.value"
                     :title="t('fileManager.searchPlaceholder')"
                 >
                     <i class="fas fa-search text-base"></i>
                 </button>
                 <div v-else class="relative flex items-center min-w-[150px] flex-shrink">
                     <i class="fas fa-search absolute left-2 top-1/2 -translate-y-1/2 text-text-secondary pointer-events-none"></i>
                     <input
                         ref="searchInputRef"
                         type="text"
                         v-model="searchQuery"
                         :placeholder="t('fileManager.searchPlaceholder')"
                         class="flex-grow bg-background border border-border rounded pl-7 pr-2 py-1 text-foreground text-sm outline-none focus:border-primary focus:ring-1 focus:ring-primary min-w-[10px] transition-colors duration-200"
                         data-focus-id="fileManagerSearch"
                         @blur="deactivateSearch"
                         @keyup.esc="cancelSearch"
                         @keydown.up.prevent="handleKeydown"
                         @keydown.down.prevent="handleKeydown"
                         @keydown.enter.prevent="handleKeydown"
                     />
                     <!-- Optional: Clear button -->
                     <!-- <button @click="searchQuery = ''; searchInputRef?.focus()" v-if="searchQuery" class="absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-foreground">&times;</button> -->
                 </div>
             </div>
             <div class="relative flex-shrink-0">
              <!-- Favorite Paths Button -->
              <button
                  ref="favoritePathsButtonRef"
                  class="flex items-center justify-center w-7 h-7 text-text-secondary rounded transition-colors duration-200 hover:enabled:bg-black/10 hover:enabled:text-foreground"
                  @click="toggleFavoritePathsModal"
              >
                  <i class="fas fa-star text-base"></i>
              </button>
              <!-- Favorite Paths Modal -->
              <FavoritePathsModal
                :is-visible="showFavoritePathsModal"
                :trigger-element="favoritePathsButtonRef"
                @close="showFavoritePathsModal = false"
                @navigate-to-path="handleNavigateToPathFromFavorites"
              />
            </div>
            </div> 

            

           
            <div ref="pathInputWrapperRef" class="relative flex items-center bg-background border border-border rounded px-1.5 py-0.5"
                 :class="{ 'flex-grow min-w-0': isEditingPath || showPathHistoryDropdown, 'w-fit max-w-full': !isEditingPath && !showPathHistoryDropdown }">
              <span v-show="!isEditingPath && !showPathHistoryDropdown" @click="startPathEdit" class="text-text-secondary pr-2 cursor-text truncate">
                <strong
                  :title="t('fileManager.editPathTooltip')"
                  class="font-medium text-link px-1 rounded transition-colors duration-200"
                  :class="{
                    'hover:bg-black/5': currentSftpManager && props.wsDeps.isConnected.value,
                    'opacity-60 cursor-not-allowed': !currentSftpManager || !props.wsDeps.isConnected.value
                  }"
                >
                  {{ currentSftpManager?.currentPath?.value ?? '/' }}
                </strong>
              </span>
              <input
                v-show="isEditingPath || showPathHistoryDropdown"
                ref="pathInputRef"
                type="text"
                v-model="editablePath"
                class="flex-grow bg-transparent text-foreground p-0.5 outline-none min-w-[100px]"
                data-focus-id="fileManagerPathInput"
                @focus="handlePathInputFocus"
                @input="handlePathInputChange"
                @keydown="handlePathInputKeydown"
                @blur="handlePathInput"
              />
              <PathHistoryDropdown
                v-if="showPathHistoryDropdown"
                ref="pathHistoryDropdownRef"
                @pathSelected="handlePathSelectedFromDropdown"
                @closeDropdown="closePathHistory"
                class="left-0 right-0 top-full mt-1"
              />
            </div>
        </div> <!-- End Wrapper -->
       <!-- Main Actions Bar -->
       <div class="flex items-center gap-2 flex-shrink-0">
            <input type="file" ref="fileInputRef" @change="handleFileSelected" multiple class="hidden" />
            <!-- 打开编辑器按钮 -->
            <button
              v-if="showPopupFileEditorBoolean"
              @click="openPopupEditor"
              :disabled="!currentSftpManager || !props.wsDeps.isConnected.value"
              :title="t('fileManager.actions.openEditor', 'Open Popup Editor')"
              class="flex items-center gap-1 px-2.5 py-1 bg-background border border-border rounded text-foreground text-xs transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-header hover:enabled:border-primary hover:enabled:text-primary"
              :class="{ 'px-1.5': props.isMobile }"
            >
              <i class="far fa-edit text-sm"></i> <!-- 使用编辑图标 -->
              <span v-if="!props.isMobile">{{ t('fileManager.actions.openEditor', 'Open Editor') }}</span> <!-- 添加 i18n key -->
            </button>
            <!-- 上传按钮 -->
            <button
              @click="triggerFileUpload"
              :disabled="!currentSftpManager || !props.wsDeps.isConnected.value"
              :title="t('fileManager.actions.uploadFile')"
              class="flex items-center gap-1 px-2.5 py-1 bg-background border border-border rounded text-foreground text-xs transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-header hover:enabled:border-primary hover:enabled:text-primary"
              :class="{ 'px-1.5': props.isMobile }"
            >
              <i class="fas fa-upload text-sm"></i>
              <span v-if="!props.isMobile">{{ t('fileManager.actions.upload') }}</span>
            </button>
            <button
              @click="handleNewFolderContextMenuClick"
              :disabled="!currentSftpManager || !props.wsDeps.isConnected.value"
              :title="t('fileManager.actions.newFolder')"
              class="flex items-center gap-1 px-2.5 py-1 bg-background border border-border rounded text-foreground text-xs transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-header hover:enabled:border-primary hover:enabled:text-primary"
              :class="{ 'px-1.5': props.isMobile }"
            >
              <i class="fas fa-folder-plus text-sm"></i>
              <span v-if="!props.isMobile">{{ t('fileManager.actions.newFolder') }}</span>
            </button>
            <button
              @click="handleNewFileContextMenuClick"
              :disabled="!currentSftpManager || !props.wsDeps.isConnected.value"
              :title="t('fileManager.actions.newFile')"
              class="flex items-center gap-1 px-2.5 py-1 bg-background border border-border rounded text-foreground text-xs transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed hover:enabled:bg-header hover:enabled:border-primary hover:enabled:text-primary"
              :class="{ 'px-1.5': props.isMobile }"
            >
              <i class="far fa-file-alt text-sm"></i>
              <span v-if="!props.isMobile">{{ t('fileManager.actions.newFile') }}</span>
            </button>
            <!-- 多选模式切换按钮 (仅移动端) -->
            <button
              v-if="props.isMobile"
              @click="toggleMultiSelectMode"
              :title="isMultiSelectMode ? t('fileManager.actions.exitMultiSelect', 'Exit Multi-Select Mode') : t('fileManager.actions.multiSelect', 'Enter Multi-Select Mode')"
              class="flex items-center gap-1 px-1.5 py-1 bg-background border border-border rounded text-foreground text-xs transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
              :class="{
                'hover:bg-header hover:border-primary hover:text-primary': !isMultiSelectMode,
                'bg-primary text-white border-primary': isMultiSelectMode
              }"
            >
              <i class="fas fa-check-square text-sm"></i>
            </button>
         </div>
     </div>

    <div class="flex flex-grow min-h-0 overflow-hidden border-t border-border/60">
      <aside class="w-[260px] flex-shrink-0 border-r border-border/60 bg-header/40 flex flex-col min-h-0">
        <div class="px-3 py-3 border-b border-border/60">
          <div class="flex items-center justify-between gap-2">
            <div>
              <div class="text-[11px] uppercase tracking-[0.18em] text-text-secondary">{{ t('fileManager.explorer.title', '目录资源管理器') }}</div>
              <div class="mt-1 text-xs text-text-secondary">{{ explorerRoots.length }} {{ t('fileManager.explorer.rootCount', '个根目录') }}</div>
            </div>
            <button
              @click="toggleFavoritePathsModal"
              class="w-8 h-8 rounded-lg border border-border bg-background text-text-secondary hover:bg-header hover:text-foreground transition-colors"
              :title="t('favoritePaths.addNew', 'Add new favorite path')"
            >
              <i class="fas fa-plus text-xs"></i>
            </button>
          </div>
        </div>

        <div class="flex-1 min-h-0 overflow-y-auto px-2 py-2">
          <div v-if="explorerRoots.length === 0" class="px-3 py-6 text-xs text-text-secondary text-center">
            {{ t('fileManager.explorer.noRoots', '暂无目录根，请先添加收藏路径或连接后浏览当前目录。') }}
          </div>

          <div v-else class="space-y-1">
            <div
              v-for="row in explorerTreeRows"
              :key="row.id"
              :class="[
                'group flex items-center gap-2 rounded-lg border px-2 py-1.5 transition-colors cursor-pointer',
                isExplorerRowActive(row)
                  ? 'bg-primary text-white border-primary shadow-sm'
                  : isExplorerRowRelated(row)
                    ? 'border-primary/20 bg-primary/8 text-foreground'
                    : 'border-transparent text-text-secondary hover:bg-background hover:text-foreground'
              ]"
              :style="{ paddingLeft: `${0.6 + row.depth * 0.85}rem` }"
              @click="handleExplorerOpen(row)"
            >
              <button
                v-if="row.isDirectory"
                @click.stop="handleExplorerToggle(row)"
                class="w-4 h-4 flex items-center justify-center flex-shrink-0 text-[10px]"
              >
                <i :class="row.expanded ? 'fas fa-chevron-down' : 'fas fa-chevron-right'"></i>
              </button>
              <span v-else class="w-4 h-4 flex items-center justify-center flex-shrink-0 text-[9px] opacity-60">
                <i class="fas fa-circle"></i>
              </span>

              <i
                :class="[
                  row.isDirectory
                    ? (row.isRoot ? 'fas fa-folder-tree' : 'fas fa-folder')
                    : getFileIconClassBase(row.name),
                  'w-4 text-center flex-shrink-0',
                  isExplorerRowActive(row) ? 'text-white' : (row.isDirectory ? 'text-primary' : 'text-text-secondary')
                ]"
              ></i>

              <div class="min-w-0 flex-1">
                <div class="truncate text-sm font-medium" :title="row.description || row.path">{{ row.name }}</div>
                <div
                  v-if="row.isRoot"
                  class="truncate text-[10px]"
                  :class="isExplorerRowActive(row) ? 'text-white/75' : 'text-text-secondary/80'"
                >
                  {{ row.description }}
                </div>
              </div>
            </div>
          </div>
        </div>
      </aside>

      <!-- File List Container -->
      <div
        ref="fileListContainerRef"
        class="flex-grow overflow-y-auto relative outline-none"
        @dragenter.prevent="handleDragEnter"
        @dragover.prevent="handleDragOver"
        @dragleave.prevent="handleDragLeave"
        @drop.prevent="handleDrop"
        @click="fileListContainerRef?.focus()"
        @keydown="handleKeydown"
        @wheel="handleWheel"
        @contextmenu.prevent="showContextMenu($event)"
        tabindex="0"
        :style="{ '--row-size-multiplier': rowSizeMultiplier }"
      >
        <!-- 外部文件拖拽蒙版 -->
        <div
          v-if="showExternalDropOverlay"
          ref="dropOverlayRef"
          class="absolute inset-0 flex items-center justify-center bg-black/70 text-white text-xl font-semibold rounded z-50 pointer-events-auto"
          @dragover.prevent
          @dragleave.prevent="handleDragLeave"
          @drop.prevent="handleOverlayDrop"
        >
          {{ t('fileManager.dropFilesHere', 'Drop files here to upload') }}
        </div>

        <div class="min-h-full p-4 md:p-5 space-y-4" :class="{ 'pointer-events-none': showExternalDropOverlay }">
          <div class="rounded-2xl border border-border/60 bg-header/30 px-4 py-3">
            <div class="text-[11px] uppercase tracking-[0.18em] text-text-secondary">
              {{ t('fileManager.explorer.overviewTitle', '文件夹总览') }}
            </div>
            <div class="mt-2 flex flex-wrap items-center gap-2 text-sm text-foreground">
              <span class="inline-flex items-center gap-2 rounded-full border border-primary/25 bg-primary/10 px-3 py-1">
                <i class="fas fa-crosshairs text-[11px] text-primary"></i>
                <span class="truncate max-w-[420px]">{{ currentSftpManager?.currentPath?.value ?? '/' }}</span>
              </span>
              <span class="text-text-secondary text-xs">
                {{ t('fileManager.explorer.overviewHint', '点击目录只展开和聚焦，不再切成单独目录列表。') }}
              </span>
            </div>
          </div>

          <div v-if="!currentSftpManager || currentSftpManager.isLoading.value" class="rounded-2xl border border-border/60 bg-background px-4 py-10 text-center text-text-secondary italic">
            {{ t('fileManager.loading') }}
          </div>

          <div v-else-if="explorerOverviewSections.length === 0" class="rounded-2xl border border-border/60 bg-background px-4 py-10 text-center text-text-secondary italic">
            {{ t('fileManager.explorer.noRoots', '暂无目录根，请先添加收藏路径或连接后浏览当前目录。') }}
          </div>

          <div v-else class="space-y-4">
            <section
              v-for="section in explorerOverviewSections"
              :key="section.id"
              class="rounded-2xl border border-border/60 bg-background/95 shadow-sm overflow-hidden"
            >
              <div class="flex items-center justify-between gap-3 border-b border-border/60 bg-header/35 px-4 py-3">
                <button
                  class="min-w-0 flex items-center gap-3 text-left"
                  @click="handleOverviewSectionOpen(section)"
                >
                  <i class="fas fa-folder-tree text-primary"></i>
                  <span class="min-w-0">
                    <span class="block truncate text-sm font-semibold text-foreground">{{ section.label }}</span>
                    <span class="block truncate text-[11px] text-text-secondary">{{ section.description }}</span>
                  </span>
                </button>
                <div class="flex items-center gap-2">
                  <span class="inline-flex items-center rounded-full border border-border/60 bg-background px-2.5 py-1 text-xs text-text-secondary">
                    {{ section.rowCount }} {{ t('fileManager.explorer.folderCount', '个文件夹') }}
                  </span>
                  <button
                    class="w-8 h-8 rounded-lg border border-border bg-background text-text-secondary hover:bg-header hover:text-foreground transition-colors"
                    :title="t('common.refresh', '刷新')"
                    @click="handleOverviewRefresh(section)"
                  >
                    <i class="fas fa-sync-alt text-xs"></i>
                  </button>
                </div>
              </div>

              <div v-if="section.rows.length === 0" class="px-4 py-6 text-sm text-text-secondary">
                {{ t('fileManager.explorer.emptyFolders', '这个根目录下暂时没有已加载的子文件夹，展开左侧目录可继续浏览。') }}
              </div>

              <div v-else class="p-3 space-y-1">
                <div
                  v-for="row in section.rows"
                  :key="row.id"
                  class="group flex items-center gap-3 rounded-xl border px-3 py-2 transition-colors"
                  :class="isPathActive(row.path) ? 'border-primary bg-primary/10 text-foreground' : 'border-transparent text-text-secondary hover:border-border/60 hover:bg-header/40 hover:text-foreground'"
                  :style="{ paddingLeft: `${0.9 + row.depth * 1.1}rem` }"
                >
                  <button
                    class="w-5 h-5 flex items-center justify-center flex-shrink-0 text-[10px]"
                    @click.stop="handleOverviewRowToggle(row)"
                  >
                    <i :class="row.expanded ? 'fas fa-chevron-down' : 'fas fa-chevron-right'"></i>
                  </button>

                  <button class="min-w-0 flex items-center gap-3 flex-1 text-left" @click="handleOverviewRowOpen(row)">
                    <i class="fas fa-folder w-4 text-center text-primary flex-shrink-0"></i>
                    <span class="min-w-0 flex-1">
                      <span class="block truncate text-sm font-medium">{{ row.name }}</span>
                      <span class="block truncate text-[11px]" :class="isPathActive(row.path) ? 'text-primary/80' : 'text-text-secondary/80'">
                        {{ row.path }}
                      </span>
                    </span>
                  </button>

                  <span class="inline-flex items-center rounded-full border border-current/10 bg-black/5 px-2 py-0.5 text-[11px] flex-shrink-0">
                    {{ row.childDirectoryCount }}
                  </span>
                </div>
              </div>
            </section>
          </div>
        </div>
      </div>
    </div>

     <!-- 使用 FileUploadPopup 组件 -->
     <FileUploadPopup :uploads="uploads" @cancel-upload="cancelUpload" />

    <FileManagerContextMenu
      ref="contextMenuRef"
      :is-visible="contextMenuVisible"
      :position="contextMenuPosition"
      :items="contextMenuItems"
      :active-context-item="contextTargetItem"
      :selected-file-items="computedSelectedFullItems"
      :current-directory-path="currentSftpManager?.currentPath?.value ?? '/'"
     @close-request="hideContextMenu"
   />

   <!-- Action Modal -->
   <FileManagerActionModal
     :is-visible="isActionModalVisible"
     :action-type="currentActionType"
     :item="actionItem"
     :items="actionItems"
     :initial-value="actionInitialValue"
     @close="handleModalClose"
     @confirm="handleModalConfirm"
   />

  <!-- Favorite Paths Modal is now positioned near its button -->


</div>
</template>

<style scoped>
/* Scoped styles removed for Tailwind CSS refactoring */
</style>


