export const createSidebarFileTreeCommandRuntime = (dependencies = {}) => {
  const {
    applyRemoveDocumentDelta,
    createFileTreeFolder,
    createPage,
    cssEscape,
    currentDocumentId,
    currentRootUri,
    currentSourceKind,
    deleteSingleFileTreeAsset,
    dispatchSidebarEvent,
    dispatchTreeCommand,
    ensureFileTreeWritableTarget,
    fileTreeRuntimeDeps,
    fileTreeRuntimeFunction,
    fileTreeSelectionRuntimeDeps,
    fileTreeSelectionRuntimeFunction,
    isFileTreePageRow,
    localFilePathFromAssetId,
    normalizeFileTreePageRenameTitle,
    openConvexAssetFromFileTree,
    openEditorAttachmentDetail,
    openEditorAttachmentDownload,
    openEditorAttachmentEditTab,
    openEditorAttachmentNewWindow,
    refreshLocalFolderSidebarSnapshot,
    removeFileTreeAssetRow,
    revealFileTreeResource,
    resolveWorkspaceId,
    runtimeState,
    selectedSidebarFileTreeSelection,
    updateTitleEverywhere,
    validateFileTreeRename,
  } = dependencies;
  const sidebarFileTreeSelection = selectedSidebarFileTreeSelection;
  var fileTreeOperationBatchCounter = 0;

  function nextFileTreeOperationBatchId(action) {
    fileTreeOperationBatchCounter += 1;
    return 'filetree-' + String(action || 'operation') + '-' + Date.now() + '-' + fileTreeOperationBatchCounter;
  }

  function rowTitle(row) {
    var title = row ? row.querySelector(':scope > .tree-link > .tree-link-title') : null;
    return title && title.textContent ? title.textContent.trim() : '无标题';
  }

  function rowCenter(row) {
    var rect = row.getBoundingClientRect();
    return { x: rect.left + Math.min(rect.width - 12, 180), y: rect.top + Math.min(rect.height, 22) };
  }

  async function renameFileTreeAsset(assetId, title) {
    var response = await fetch('/api/media/batch', {
      method: 'POST',
      credentials: 'include',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ action: 'rename', assetIds: [assetId], newName: title })
    });
    var payload = await response.json().catch(function(){ return null; });
    if (!response.ok) throw new Error((payload && payload.error) || '重命名附件失败');
    return payload;
  }

  function beginFileTreeInlineRename(row) {
    if (!(row instanceof HTMLElement)) return false;
    if (row.querySelector('.tree-rename-input')) return true;
    var rowKind = row.getAttribute('data-row-kind') || '';
    var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '';
    var assetId = row.getAttribute('data-asset-id') || '';
    var rowId = row.getAttribute('data-row-id') || '';
    var localFolderSource = currentSourceKind() === 'local_folder';
    var localFolderDirectory = localFolderSource && (rowKind === 'folder' || rowKind === 'directory');
    var commandTargetId = documentId || (localFolderSource ? (assetId || rowId) : '');
    if (!commandTargetId && !assetId) return false;
    if (rowKind && ['document', 'doc', 'index', 'markdown', 'asset'].indexOf(rowKind) < 0 && !assetId && !localFolderDirectory) return false;
    var link = row.querySelector(':scope > .tree-link');
    var title = rowTitle(row);
    if (!(link instanceof HTMLElement)) return false;
    link.hidden = true;
    var input = document.createElement('input');
    input.type = 'text';
    input.className = 'tree-rename-input';
    input.setAttribute('data-rename-id', rowId);
    input.value = title;
    input.style.minWidth = '0';
    input.style.flex = '1 1 auto';
    input.style.height = '22px';
    input.style.border = '1px solid #93c5fd';
    input.style.borderRadius = '3px';
    input.style.padding = '0 4px';
    input.style.font = 'inherit';
    input.style.background = '#fff';
    input.style.color = '#1f2937';
    var validation = document.createElement('div');
    validation.setAttribute('data-testid', 'tree-rename-validation');
    validation.setAttribute('data-mnote-rename-validation', 'true');
    validation.style.fontSize = '12px';
    validation.style.color = '#dc2626';
    validation.style.padding = '2px 4px';
    validation.hidden = true;
    var setValidation = function(message) {
      var text = String(message || '').trim();
      validation.textContent = text;
      validation.hidden = !text;
      input.setAttribute('aria-invalid', text ? 'true' : 'false');
    };
    var closed = false;
    var committing = false;
    var close = function() {
      if (closed) return;
      closed = true;
      if (input.parentElement) input.parentElement.removeChild(input);
      if (validation.parentElement) validation.parentElement.removeChild(validation);
      link.hidden = false;
    };
    var commit = function() {
      if (closed || committing) return;
      var nextTitle = input.value.trim();
      if (!nextTitle || nextTitle === title) {
        close();
        return;
      }
      var validationMessage = validateFileTreeRename(row, nextTitle);
      if (validationMessage) {
        setValidation(validationMessage);
        return;
      }
      setValidation('');
      var commandTitle = isFileTreePageRow(row) ? normalizeFileTreePageRenameTitle(nextTitle) : nextTitle;
      committing = true;
      input.disabled = true;
      var work = assetId && !localFolderSource
        ? Promise.resolve().then(function(){
            document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-asset-id="' + cssEscape(assetId) + '"] .tree-link-title').forEach(function(titleNode) {
              titleNode.textContent = commandTitle;
            });
            close();
            return renameFileTreeAsset(assetId, commandTitle);
          }).then(function(){ return null; })
        : dispatchTreeCommand(row, {
            action: 'rename',
            workspaceId: resolveWorkspaceId(row),
            documentId: commandTargetId,
            title: commandTitle
          }).then(function(){
            if (!localFolderSource && documentId) updateTitleEverywhere(documentId, commandTitle);
          });
      void work.then(close).catch(function(error) {
        committing = false;
        input.disabled = false;
        window.alert(error && error.message ? error.message : '重命名失败');
      });
    };
    input.addEventListener('click', function(event) { event.stopPropagation(); });
    input.addEventListener('dblclick', function(event) { event.stopPropagation(); });
    input.addEventListener('keydown', function(event) {
      event.stopPropagation();
      if (event.key === 'Enter') {
        event.preventDefault();
        commit();
      } else if (event.key === 'Escape') {
        event.preventDefault();
        close();
      }
    });
    input.addEventListener('blur', commit);
    link.parentElement.insertBefore(input, link.nextSibling);
    link.parentElement.insertBefore(validation, input.nextSibling);
    window.requestAnimationFrame(function() {
      input.focus();
      input.select();
    });
    return true;
  }

  function closeTreeContextMenu() {
    if (runtimeState.activeTreeContextMenu && runtimeState.activeTreeContextMenu.parentElement) {
      runtimeState.activeTreeContextMenu.parentElement.removeChild(runtimeState.activeTreeContextMenu);
    }
    runtimeState.activeTreeContextMenu = null;
  }

  function copyTreeContextValue(value, actionName) {
    var text = String(value || '');
    var done = function() {
      document.documentElement.setAttribute('data-mnote-tree-context-last-copy', actionName || 'copy');
    };
    if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
      return navigator.clipboard.writeText(text).then(done).catch(function(){});
    }
    var textarea = document.createElement('textarea');
    textarea.value = text;
    textarea.setAttribute('readonly', 'readonly');
    textarea.style.position = 'fixed';
    textarea.style.left = '-9999px';
    document.body.appendChild(textarea);
    textarea.select();
    try { document.execCommand('copy'); } catch (_) {}
    document.body.removeChild(textarea);
    done();
    return Promise.resolve();
  }

  function triggerBrowserDownload(url) {
    if (!url) return false;
    var link = document.createElement('a');
    link.href = url;
    link.target = '_blank';
    link.rel = 'noopener noreferrer';
    link.download = '';
    link.style.position = 'fixed';
    link.style.left = '-9999px';
    link.style.top = '0';
    document.body.appendChild(link);
    try {
      link.click();
    } catch (_) {
      if (typeof window.open === 'function') window.open(url, '_blank', 'noopener,noreferrer');
    }
    window.setTimeout(function() {
      if (link.parentElement) link.parentElement.removeChild(link);
    }, 1000);
    return true;
  }

  function recordFileTreeAction(action, detail) {
    var normalized = String(action || '').trim() || 'unknown';
    var rowId = String(detail && detail.rowId || '').trim();
    var documentId = String(detail && detail.documentId || '').trim();
    var assetId = String(detail && detail.assetId || '').trim();
    document.documentElement.setAttribute('data-mnote-filetree-last-action', normalized);
    if (rowId) document.documentElement.setAttribute('data-mnote-filetree-last-action-row-id', rowId);
    if (documentId) document.documentElement.setAttribute('data-mnote-filetree-last-action-document-id', documentId);
    if (assetId) document.documentElement.setAttribute('data-mnote-filetree-last-action-asset-id', assetId);
    window.dispatchEvent(new CustomEvent('tree.filetree.action', {
      detail: Object.assign({}, detail || {}, { action: normalized })
    }));
  }

  function recordFileTreeActionStatus(status, detail) {
    var normalized = String(status || '').trim() || 'unknown';
    document.documentElement.setAttribute('data-mnote-filetree-last-action-status', normalized);
    window.dispatchEvent(new CustomEvent('tree.filetree.action.status', {
      detail: Object.assign({}, detail || {}, { status: normalized })
    }));
  }

  function documentHref(documentId, workspaceId) {
    var url = new URL('/documents/' + encodeURIComponent(documentId), window.location.origin);
    if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
    copyWorkspaceSourceParams(url);
    return url.toString();
  }

  function convertToPreviousSiblingChild(trigger, detail) {
    var documentId = detail.documentId || '';
    var row = document.querySelector('#sidebar-tree-root .tree-row[data-node-id="' + cssEscape(documentId) + '"]');
    if (!(row instanceof HTMLElement)) return;
    var parentId = row.getAttribute('data-parent-id') || '';
    var siblings = Array.from(document.querySelectorAll('#sidebar-tree-root .tree-row[data-shell-mode="page"]')).filter(function(candidate) {
      return (candidate.getAttribute('data-parent-id') || '') === parentId;
    });
    var index = siblings.indexOf(row);
    if (index <= 0) {
      window.alert('当前页面前面没有同级页面。');
      return;
    }
    var previous = siblings[index - 1];
    var previousId = previous.getAttribute('data-node-id') || '';
    var children = previous.parentElement ? previous.parentElement.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="page"]') : [];
    void dispatchTreeCommand(trigger || row, {
      action: 'move',
      workspaceId: detail.workspaceId || resolveWorkspaceId(row),
      documentId: documentId,
      parentId: previousId,
      sortOrder: children.length
    });
  }

  function fileTreeCopyPath(detail, trigger) {
    // 对 local_folder 复制真实相对路径，不复制标题
    if (currentSourceKind() === 'local_folder') {
      if (detail.assetId) {
        var path = localFilePathFromAssetId(detail.assetId);
        if (path) {
          try {
            return decodeURIComponent(path.replace(/~2F/g, '/'));
          } catch (_) {
            return path.replace(/~2F/g, '/');
          }
        }
      }
      if (detail.documentId) {
        var docPath = String(detail.documentId || '')
          .replace(/^local-md:/, '')
          .replace(/^local-dir:/, '')
          .replace(/~2F/g, '/');
        if (docPath) {
          try {
            return decodeURIComponent(docPath);
          } catch (_) {
            return docPath;
          }
        }
      }
    }
    return detail.title || '';
  }

  function localRootPathFromRootUri(rootUri) {
    var value = String(rootUri || '').trim();
    if (!value) return '';
    if (!/^file:\/\//i.test(value)) return value.charAt(0) === '/' ? value : '';
    var pathPart = value.replace(/^file:\/\//i, '');
    if (pathPart.indexOf('localhost/') === 0) pathPart = pathPart.slice('localhost'.length);
    if (pathPart.charAt(0) !== '/') pathPart = '/' + pathPart;
    try {
      return decodeURIComponent(pathPart);
    } catch (_) {
      return pathPart;
    }
  }

  function joinLocalAbsolutePath(rootUri, relativePath) {
    var rootPath = localRootPathFromRootUri(rootUri);
    var normalized = decodeLocalEncodedPath(relativePath).replace(/^\/+/, '');
    if (!rootPath || !normalized) return rootPath || normalized;
    return rootPath.replace(/\/+$/g, '') + '/' + normalized;
  }

  function fileTreeCopyLocalAbsolutePath(detail, trigger) {
    var rootUri = typeof currentRootUri === 'function' ? currentRootUri() : '';
    if (!rootUri && trigger && typeof trigger.closest === 'function') {
      var row = trigger.closest('.tree-row[data-shell-mode="filetree"]');
      if (row instanceof HTMLElement) rootUri = row.getAttribute('data-root-uri') || '';
    }
    if (!rootUri && document.body) rootUri = document.body.getAttribute('data-mnote-root-uri') || '';
    var relativePath = String(detail && detail.localRelativePath || '').trim();
    if (!relativePath && detail && detail.assetId) relativePath = localFilePathFromAssetId(detail.assetId);
    if (!relativePath && detail && detail.documentId) {
      relativePath = String(detail.documentId || '')
        .replace(/^local-md:/, '')
        .replace(/^local-dir:/, '');
    }
    if (!relativePath && detail && detail.rowId) {
      relativePath = String(detail.rowId || '')
        .replace(/^local:asset:/, '')
        .replace(/^local:markdown:/, '')
        .replace(/^local:folder:/, '')
        .replace(/^local:node:/, '');
    }
    return joinLocalAbsolutePath(rootUri, relativePath);
  }

  function fileTreeMenuTargetParentId(detail, trigger) {
    var rowKind = String(detail && detail.rowKind || '').trim();
    var rowId = String(detail && detail.rowId || '').trim();
    var documentId = String(detail && detail.documentId || '').trim();
    if (rowKind === 'folder' && rowId) return rowId;
    if (rowKind === 'directory' && rowId) return rowId;
    if (documentId) return documentId;
    if (trigger && typeof trigger.getAttribute === 'function') {
      var triggerKind = String(trigger.getAttribute('data-row-kind') || '').trim();
      var triggerRowId = String(trigger.getAttribute('data-row-id') || '').trim();
      if ((triggerKind === 'folder' || triggerKind === 'directory') && triggerRowId) return triggerRowId;
    }
    return '';
  }

  function withOfficeEditModeGuard(callback) {
    document.documentElement.setAttribute('data-mnote-last-office-edit-mode-requested', 'true');
    document.documentElement.setAttribute('data-mnote-last-office-edit-mode-guard', 'silent');
    callback();
  }

  function handleTreeContextMenuAction(action, detail, trigger) {
    closeTreeContextMenu();
    detail = detail || {};
    if (detail.contextKind === 'attachment') {
      if (action === 'copy-link') {
        void copyTreeContextValue(detail.href || '', 'attachment-copy-link');
        return;
      }
      if (action === 'download') {
        openEditorAttachmentDownload(detail);
        return;
      }
      if (action === 'popup-preview') {
        openEditorAttachmentDetail(detail);
        return;
      }
      if (action === 'new-window') {
        openEditorAttachmentNewWindow(detail);
        return;
      }
      if (action === 'new-window-edit') {
        withOfficeEditModeGuard(function() {
          openEditorAttachmentNewWindow(detail, 'edit');
        });
        return;
      }
      if (action === 'open-edit-mode') {
        withOfficeEditModeGuard(function() {
          void openEditorAttachmentEditTab(detail);
        });
        return;
      }
      if (action === 'right-preview') {
        dispatchSidebarEvent('tree.attachment.open-right', detail);
        return;
      }
      if (action === 'copy-id') {
        void copyTreeContextValue(detail.assetId || '', 'attachment-copy-id');
        return;
      }
      dispatchSidebarEvent('tree.attachment.action', { action: action, attachment: detail });
      return;
    }
    var documentId = detail.documentId || '';
    var workspaceId = detail.workspaceId || resolveWorkspaceId(trigger || document.body);
    var title = detail.title || '无标题';
    var isAsset = detail.contextKind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
    if (detail.contextKind === 'filetree' && action === 'toggle-sidebar-folder-shortcut') {
      dispatchSidebarEvent('tree.sidebarShortcut.toggleFolder', detail);
      return;
    }
    if (detail.contextKind === 'filetree' && action === 'download') {
      downloadSelectedFileTreeAssetRows(detail, trigger);
      return;
    }
    if (isAsset && action === 'new-window') {
      recordFileTreeAction('new-window', detail);
      void openConvexAssetFromFileTree({ ...detail, openTarget: 'new-window' });
      return;
    }
    if (isAsset && action === 'open-edit-mode') {
      withOfficeEditModeGuard(function() {
        recordFileTreeAction('open-edit-mode', detail);
        void openConvexAssetFromFileTree({ ...detail, openTarget: 'edit-mode' });
      });
      return;
    }
    if (isAsset && action === 'open-right') {
      recordFileTreeAction('open-right', detail);
      void openConvexAssetFromFileTree({ ...detail, openTarget: 'side' });
      return;
    }
    if (action === 'open-right') {
      recordFileTreeAction('open-right', detail);
      dispatchSidebarEvent('tree.page.open-right', detail);
      return;
    }
    if (action === 'share') {
      dispatchSidebarEvent('tree.page.share', detail);
      return;
    }
    if (action === 'copy-link') {
      void copyTreeContextValue(documentHref(documentId, workspaceId), 'copy-link');
      return;
    }
    if (action === 'copy-link-title') {
      void copyTreeContextValue(title + ' ' + documentHref(documentId, workspaceId), 'copy-link-title');
      return;
    }
    if (action === 'copy-reference-inline') {
      void copyTreeContextValue('((' + title + ' ' + documentId + '))', 'copy-reference-inline');
      return;
    }
    if (action === 'copy-reference-embed') {
      void copyTreeContextValue('{{' + title + ' ' + documentId + '}}', 'copy-reference-embed');
      return;
    }
    if (action === 'copy-id') {
      var copyIdValue = currentSourceKind() === 'local_folder'
        ? fileTreeCopyLocalAbsolutePath(detail, trigger)
        : '';
      void copyTreeContextValue(copyIdValue || documentId || detail.assetId || detail.rowId || '', 'copy-id');
      return;
    }
    if (action === 'delete-trash' && detail.contextKind === 'filetree') {
      var selectedFileTreeRows = selectedSidebarFileTreeRows();
      if (selectedFileTreeRows.length > 1) {
        void deleteSelectedSidebarFileTreeRows(trigger || document.body);
        return;
      }
    }
    if (action === 'delete-trash' && isAsset) {
      if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗？')) return;
      recordFileTreeAction('delete-trash', detail);
      recordFileTreeActionStatus('pending', detail);
      void deleteSingleFileTreeAsset(detail, trigger).then(function() {
        recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
        if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
      }).catch(function(error) {
        recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
        window.alert(error && error.message ? error.message : '资源删除失败');
      });
      return;
    }
    if (action === 'new-file') {
      void createPage(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null);
      return;
    }
    if (action === 'new-folder') {
      recordFileTreeAction('new-folder', detail);
      void createFileTreeFolder(trigger || document.body, fileTreeMenuTargetParentId(detail, trigger) || null).then(function(ok) {
        recordFileTreeActionStatus(ok ? 'created' : 'skipped', detail);
      }).catch(function(error) {
        recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
        window.alert(error && error.message ? error.message : '新建文件夹失败');
      });
      return;
    }
    if (action === 'paste-into') {
      var pasteRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
      recordFileTreeAction('paste-into', detail);
      void pasteSidebarFileTreeClipboard(pasteRow).then(function(ok) {
        recordFileTreeActionStatus(ok ? 'applied' : 'skipped', detail);
      }).catch(function(error) {
        recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
        window.alert(error && error.message ? error.message : '粘贴失败');
      });
      return;
    }
    if (action === 'copy-path') {
      void copyTreeContextValue(fileTreeCopyPath(detail, trigger), 'copy-path');
      return;
    }
    if (action === 'refresh') {
      document.documentElement.setAttribute('data-mnote-filetree-refresh-requested', 'true');
      dispatchSidebarEvent('tree.filetree.refresh', detail);
      return;
    }
    if (action === 'collapse-all') {
      document.querySelectorAll('#sidebar-file-tree-root .tree-row[aria-expanded="true"]').forEach(function(row) {
        if (!(row instanceof HTMLElement)) return;
        row.setAttribute('aria-expanded', 'false');
        var node = row.closest('.tree-node');
        var children = node ? node.querySelector(':scope > .tree-children') : null;
        if (children) children.classList.add('tree-children--collapsed');
      });
      return;
    }
    if (action === 'reveal') {
      if (trigger && typeof trigger.scrollIntoView === 'function') {
        trigger.scrollIntoView({ block: 'nearest' });
        trigger.focus && trigger.focus();
      }
      return;
    }
    if (action === 'duplicate') {
      dispatchSidebarEvent('tree.page.duplicate', detail);
      return;
    }
    if (action === 'rename') {
      if (detail.contextKind === 'filetree' && trigger) {
        var renameRow = trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
        if (beginFileTreeInlineRename(renameRow)) return;
      }
      var nextTitle = window.prompt('重命名页面', title);
      if (nextTitle && nextTitle.trim() && documentId) {
        void dispatchTreeCommand(trigger || document.body, {
          action: 'rename',
          workspaceId: workspaceId,
          documentId: documentId,
          title: nextTitle.trim()
        }).then(function(){ updateTitleEverywhere(documentId, nextTitle.trim()); });
      }
      return;
    }
    if (action === 'create-child') {
      void createPage(trigger || document.body, documentId);
      return;
    }
    if (action === 'convert-child') {
      convertToPreviousSiblingChild(trigger, detail);
      return;
    }
    var deleteTargetId = documentId || String(detail.rowId || '').trim();
    if (action === 'delete-trash' && deleteTargetId) {
      if (!window.confirm('确定要将“' + title + '”删除到垃圾桶吗？')) return;
      recordFileTreeAction('delete-trash', detail);
      recordFileTreeActionStatus('pending', detail);
      void dispatchTreeCommand(trigger || document.body, {
        action: 'archive',
        workspaceId: workspaceId,
        documentId: deleteTargetId
      }).then(function() {
        recordFileTreeActionStatus('archived', Object.assign({}, detail, { undo: 'trash-modal' }));
        if (currentSourceKind() === 'local_folder') void refreshLocalFolderSidebarSnapshot();
      }).catch(function(error) {
        recordFileTreeActionStatus('failed', Object.assign({}, detail, { fallback: 'alert' }));
        window.alert(error && error.message ? error.message : '删除失败');
      });
    }
  }

  function appendTreeContextMenuButton(menu, item, detail, trigger) {
    if (item.separator) {
      var sep = document.createElement('div');
      sep.className = 'mnote-tree-context-menu__separator';
      sep.setAttribute('role', 'separator');
      menu.appendChild(sep);
      return;
    }
    var button = document.createElement('button');
    button.type = 'button';
    button.className = item.danger ? 'mnote-tree-context-menu__item mnote-tree-context-menu__item--danger' : 'mnote-tree-context-menu__item';
    button.setAttribute('role', 'menuitem');
    button.setAttribute('data-action', item.action);
    button.disabled = item.disabled === true;
    if (item.title) button.title = item.title;
    var icon = document.createElement('span');
    icon.className = 'material-symbols-outlined mnote-tree-context-menu__icon';
    icon.setAttribute('aria-hidden', 'true');
    icon.setAttribute('data-icon', item.icon || 'radio_button_unchecked');
    var label = document.createElement('span');
    label.className = 'mnote-tree-context-menu__label';
    label.textContent = item.label;
    button.appendChild(icon);
    button.appendChild(label);
    if (item.shortcut) {
      var shortcut = document.createElement('span');
      shortcut.className = 'mnote-tree-context-menu__shortcut';
      shortcut.textContent = item.shortcut;
      button.appendChild(shortcut);
    }
    button.addEventListener('click', function(event) {
      event.preventDefault();
      event.stopPropagation();
      handleTreeContextMenuAction(item.action, detail, trigger);
    });
    menu.appendChild(button);
  }

  // ── CommandContext 启用态辅助：与 Rust core-protocol CommandContext 保持同一口径 ──

  function buildSidebarFileTreeContext(kind) {
    var runtime = window.__mnoteFileTreeContextMenuRuntime;
    if (runtime && typeof runtime.buildSidebarFileTreeContext === 'function') {
      try {
        return runtime.buildSidebarFileTreeContext(kind, {
          selection: sidebarFileTreeSelection,
          currentSourceKind: currentSourceKind,
          workspaceReadonly: function() {
            return document.documentElement.getAttribute('data-mnote-workspace-readonly') === 'true';
          },
          queryRowById: function(rowId) {
            return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]');
          }
        });
      } catch (_) {}
    }
    var s = sidebarFileTreeSelection;
    var rowIds = Array.from(s.selectedRowIds || []);
    var rows = [];
    for (var i = 0; i < rowIds.length; i += 1) {
      var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowIds[i]) + '"]');
      if (row instanceof HTMLElement) rows.push(row);
    }
    var resourceKinds = {};
    for (var i = 0; i < rows.length; i += 1) {
      var rk = rows[i].getAttribute('data-row-kind') || 'unknown';
      resourceKinds[rk] = true;
    }
    var rkKeys = Object.keys(resourceKinds);
    return {
      'workspace.sourceKind': currentSourceKind() || '',
      'workspace.readonly': document.documentElement.getAttribute('data-mnote-workspace-readonly') === 'true',
      'tree.focusKind': kind === 'filetree' ? 'file_tree' : kind === 'page' ? 'page_tree' : kind,
      'tree.selectionCount': rows.length,
      'tree.selectionResourceKind': rkKeys.length === 1 ? rkKeys[0] : 'mixed'
    };
  }

  function evaluateSidebarFileTreeWhen(ctx, expr) {
    var runtime = window.__mnoteFileTreeContextMenuRuntime;
    if (runtime && typeof runtime.evaluateSidebarFileTreeWhen === 'function') {
      return runtime.evaluateSidebarFileTreeWhen(ctx || {}, expr || '');
    }
    if (!expr) return true;
    try {
      expr = expr.trim();
      // 词法切分：支持 key、!key、==、!=、&&、|| 和括号分组
      var tokens = [];
      var i = 0;
      while (i < expr.length) {
        var ch = expr[i];
        if (ch === ' ' || ch === '\t') { i++; continue; }
        if (ch === '(') { tokens.push({ t: '(' }); i++; continue; }
        if (ch === ')') { tokens.push({ t: ')' }); i++; continue; }
        if (ch === '&' && expr[i+1] === '&') { tokens.push({ t: '&&' }); i += 2; continue; }
        if (ch === '|' && expr[i+1] === '|') { tokens.push({ t: '||' }); i += 2; continue; }
        if (ch === '=' && expr[i+1] === '=') { tokens.push({ t: '==' }); i += 2; continue; }
        if (ch === '!' && expr[i+1] === '=') { tokens.push({ t: '!=' }); i += 2; continue; }
        if (ch === '!') { tokens.push({ t: '!' }); i++; continue; }
        if (ch === '"' || ch === "'") {
          i++;
          var str = '';
          while (i < expr.length && expr[i] !== ch) { str += expr[i]; i++; }
          if (i < expr.length) i++;
          tokens.push({ t: 'str', v: str });
          continue;
        }
        if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_' || ch === '.' || (ch >= '0' && ch <= '9')) {
          var id = '';
          while (i < expr.length && ((expr[i] >= 'a' && expr[i] <= 'z') || (expr[i] >= 'A' && expr[i] <= 'Z') || (expr[i] >= '0' && expr[i] <= '9') || expr[i] === '_' || expr[i] === '.')) { id += expr[i]; i++; }
          tokens.push({ t: 'id', v: id });
          continue;
        }
        throw new Error('Unexpected char: ' + ch);
      }
      var pos = 0;
      function peek() { return tokens[pos]; }
      function consume() { return tokens[pos++]; }
      function expect(t) {
        var tok = consume();
        if (!tok || tok.t !== t) throw new Error('Expected ' + t + ' got ' + (tok ? tok.t : 'EOF'));
        return tok;
      }
      function ctxVal(key) {
        var v = ctx[key];
        if (v === undefined || v === null) return '';
        return v;
      }
      function parseOr() {
        var left = parseAnd();
        while (peek() && peek().t === '||') { consume(); var right = parseAnd(); left = left || right; }
        return left;
      }
      function parseAnd() {
        var left = parsePrimary();
        while (peek() && peek().t === '&&') { consume(); var right = parsePrimary(); left = left && right; }
        return left;
      }
      function parsePrimary() {
        if (!peek()) throw new Error('Unexpected EOF');
        if (peek().t === '(') {
          consume();
          var val = parseOr();
          expect(')');
          return val;
        }
        if (peek().t === '!') {
          consume();
          var keyTok = consume();
          if (!keyTok || keyTok.t !== 'id') throw new Error('Expected key after !');
          return !ctxVal(keyTok.v);
        }
        if (peek().t === 'id') {
          var keyTok = consume();
          if (peek() && peek().t === '==') {
            consume();
            var valTok = consume();
            if (!valTok) throw new Error('Expected value after ==');
            var val = valTok.t === 'str' ? valTok.v : (valTok.v || '');
            // 与 Rust evaluate_when 保持一致：按 string / bool / number 比较。
            var cv = ctxVal(keyTok.v);
            if (val === 'true') return cv === true || cv === 'true';
            if (val === 'false') return cv === false || cv === '' || cv === 'false';
            var num = Number(val);
            if (!isNaN(num) && String(num) === val) return Number(cv) === num;
            return String(cv) === val;
          }
          if (peek() && peek().t === '!=') {
            consume();
            var valTok = consume();
            if (!valTok) throw new Error('Expected value after !=');
            var val = valTok.t === 'str' ? valTok.v : (valTok.v || '');
            var cv = ctxVal(keyTok.v);
            if (val === 'true') return !(cv === true || cv === 'true');
            if (val === 'false') return !(cv === false || cv === '' || cv === 'false');
            var num = Number(val);
            if (!isNaN(num) && String(num) === val) return Number(cv) !== num;
            return String(cv) !== val;
          }
          // 单独 key：按 truthy 规则判断。
          var cv = ctxVal(keyTok.v);
          if (typeof cv === 'boolean') return cv;
          if (typeof cv === 'number') return cv !== 0;
          return cv !== '' && cv !== undefined && cv !== null;
        }
        throw new Error('Unexpected token: ' + peek().t);
      }
      return parseOr();
    } catch(e) {
      return true;
    }
  }

  function openTreeContextMenu(kind, detail, x, y, trigger) {
    closeTreeContextMenu();
    detail = Object.assign({}, detail || {}, { contextKind: kind });
    var menu = document.createElement('div');
    menu.className = 'mnote-tree-context-menu';
    menu.setAttribute('role', 'menu');
    menu.setAttribute('data-testid', 'mnote-tree-context-menu');
    menu.setAttribute('data-kind', kind);
    var isAttachment = kind === 'attachment';
    var isAsset = kind === 'filetree' && detail.assetId && detail.rowKind !== 'document' && detail.rowKind !== 'index';
    var isFileTreeDownload = kind === 'filetree' && detail.downloadable;
    var ctx = buildSidebarFileTreeContext(kind);
    var items = isAttachment ? [
      { action: 'duplicate', icon: 'file_copy', label: '拷贝副本', shortcut: 'Ctrl + D' },
      { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' },
      { separator: true },
      { action: 'copy-link', icon: 'link', label: '复制链接' },
      { action: 'move-embed', icon: 'subdirectory_arrow_right', label: '移动/嵌入到...', shortcut: 'Alt+Shift+M/G' },
      { action: 'history', icon: 'history', label: '块历史...' },
      { separator: true },
      { action: 'popup-preview', icon: 'preview', label: '弹窗预览' },
      { action: 'right-preview', icon: 'right_panel_open', label: '右侧预览' },
      { action: 'open-edit-mode', icon: 'edit_note', label: '弹窗编辑' },
      { action: 'new-window-edit', icon: 'open_in_new', label: '新窗口编辑' },
      { action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' },
      { action: 'download', icon: 'download', label: '下载' },
      { action: 'replace-file', icon: 'sync', label: '更换文件' },
      { action: 'rename-attachment', icon: 'drive_file_rename_outline', label: '重命名' },
      { action: 'comment', icon: 'mode_comment', label: '评论', shortcut: 'Ctrl+Alt+M' },
      { action: 'caption', icon: 'notes', label: '添加说明文字' },
      { separator: true },
      { action: 'color', icon: 'format_paint', label: '颜色' }
    ] : isAsset ? [
      { action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' },
      { action: 'new-window', icon: 'open_in_new', label: '在新窗口打开' },
      { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || detail.selectedAssetCount || 0) + ' 个项目' : '下载' },
      { action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly' },
      { action: 'copy-id', icon: 'tag', label: '复制资源 ID' },
      { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' },
      { separator: true },
      { action: 'copy-path', icon: 'content_copy', label: 'Copy Path' },
      { action: 'refresh', icon: 'refresh', label: 'Refresh' },
      { action: 'reveal', icon: 'my_location', label: 'Reveal' }
    ] : kind === 'filetree' ? [
      { action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' },
      { action: 'download', icon: 'download', label: Number(detail.selectedDownloadCount || 0) > 1 ? '下载 ' + Number(detail.selectedDownloadCount || 0) + ' 个项目' : '下载', disabled: !isFileTreeDownload, title: isFileTreeDownload ? '下载当前本地文件或文件夹' : '当前项目没有可下载的本地路径' },
      { separator: true },
      { action: 'copy-link', icon: 'link', label: '复制访问链接' },
      { action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' },
      { action: 'copy-id', icon: 'tag', label: '复制页面ID' },
      { action: 'copy-path', icon: 'content_copy', label: 'Copy Path' },
      { separator: true },
      { action: 'new-file', icon: 'note_add', label: 'New File', when: '!workspace.readonly' },
      { action: 'new-folder', icon: 'create_new_folder', label: 'New Folder', disabled: currentSourceKind() !== 'local_folder', title: currentSourceKind() === 'local_folder' ? '在当前目录下创建子文件夹' : '仅 local folder 支持创建文件夹', when: '!workspace.readonly' },
      { action: 'toggle-sidebar-folder-shortcut', icon: 'star', label: '加入/取消星标置顶', disabled: currentSourceKind() !== 'local_folder' || (detail.rowKind !== 'folder' && detail.rowKind !== 'directory'), title: currentSourceKind() === 'local_folder' ? '把当前文件夹加入或移出星标置顶' : '仅 local folder 文件夹支持星标置顶' },
      { action: 'paste-into', icon: 'content_paste', label: '粘贴到此处', disabled: !runtimeState.sidebarFileTreeClipboard, title: runtimeState.sidebarFileTreeClipboard ? '粘贴到当前文件树目标' : '剪贴板为空', when: '!workspace.readonly' },
      { action: 'refresh', icon: 'refresh', label: 'Refresh' },
      { action: 'collapse-all', icon: 'unfold_less', label: 'Collapse All' },
      { action: 'reveal', icon: 'my_location', label: 'Reveal' },
      { separator: true },
      { action: 'rename', icon: 'edit', label: '重命名', shortcut: 'F2', when: '!workspace.readonly' },
      { separator: true },
      { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
    ] : [
      { action: 'open-right', icon: 'right_panel_open', label: '在右侧边栏打开', shortcut: 'Alt+' },
      { separator: true },
      { action: 'copy-link', icon: 'link', label: '复制访问链接' },
      { action: 'copy-reference-inline', icon: 'content_copy', label: '复制页面引用链接' },
      { action: 'copy-id', icon: 'tag', label: '复制页面ID' },
      { separator: true },
      { action: 'rename', icon: 'edit', label: '重命名', when: '!workspace.readonly' },
      { separator: true },
      { action: 'delete-trash', icon: 'delete', label: '删除', shortcut: 'Del', danger: true, when: '!workspace.readonly' }
    ];
    items.forEach(function(item) {
      if (item.when !== undefined && !evaluateSidebarFileTreeWhen(ctx, item.when)) {
        item = Object.assign({}, item, { disabled: true, title: item.title || '当前上下文不支持此操作' });
      }
      appendTreeContextMenuButton(menu, item, detail, trigger);
    });
    document.body.appendChild(menu);
    var rect = menu.getBoundingClientRect();
    var left = Math.min(Math.max(8, x || 8), Math.max(8, window.innerWidth - rect.width - 8));
    var top = Math.min(Math.max(8, y || 8), Math.max(8, window.innerHeight - rect.height - 8));
    menu.style.left = left + 'px';
    menu.style.top = top + 'px';
    runtimeState.activeTreeContextMenu = menu;
  }

  function openPageTreeContextMenu(row, x, y, trigger) {
    if (!(row instanceof HTMLElement)) return;
    var documentId = row.getAttribute('data-node-id') || '';
    openTreeContextMenu('page', {
      documentId: documentId,
      rowId: documentId,
      rowKind: 'document',
      title: rowTitle(row),
      workspaceId: resolveWorkspaceId(row)
    }, x, y, trigger || row);
  }

  function openFileTreeContextMenu(row, x, y, trigger) {
    if (!(row instanceof HTMLElement)) return;
    var documentId = row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '';
    var selectedDownloadRows = selectedSidebarFileTreeRowsForDownload(row);
    var selectedAssetRows = selectedDownloadRows.filter(isFileTreeDownloadableAssetRow);
    openTreeContextMenu('filetree', {
      documentId: documentId,
      rowId: row.getAttribute('data-row-id') || '',
      rowKind: row.getAttribute('data-row-kind') || '',
      assetId: row.getAttribute('data-asset-id') || '',
      localRelativePath: fileTreeRowLocalRelativePath(row),
      title: rowTitle(row),
      workspaceId: resolveWorkspaceId(row),
      downloadable: isFileTreeDownloadableRow(row),
      selectedDownloadCount: selectedDownloadRows.length,
      selectedDownloadRowIds: selectedDownloadRows.map(function(downloadRow) { return downloadRow.getAttribute('data-row-id') || ''; }).filter(Boolean),
      selectedAssetCount: selectedAssetRows.length,
      selectedAssetRowIds: selectedAssetRows.map(function(assetRow) { return assetRow.getAttribute('data-row-id') || ''; }).filter(Boolean)
    }, x, y, trigger || row);
  }

  function visibleFileTreeRows() {
    var runtimeFn = fileTreeSelectionRuntimeFunction('visibleFileTreeRows');
    if (runtimeFn) return runtimeFn(fileTreeSelectionRuntimeDeps());
    return Array.from(document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"]'))
      .filter(function(row) {
        if (!(row instanceof HTMLElement)) return false;
        if (row.closest('.tree-children--collapsed')) return false;
        return row.offsetParent !== null || row.getClientRects().length > 0;
      });
  }

  function syncSidebarFileTreeSelection() {
    var runtimeFn = fileTreeSelectionRuntimeFunction('syncSidebarFileTreeSelection');
    if (runtimeFn) {
      runtimeFn(sidebarFileTreeSelection, fileTreeSelectionRuntimeDeps());
      return;
    }
    var rows = visibleFileTreeRows();
    rows.forEach(function(row) {
      var rowId = row.getAttribute('data-row-id') || '';
      row.setAttribute('data-selected', String(Boolean(rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId))));
      row.setAttribute('data-focused', String(rowId === sidebarFileTreeSelection.focusedRowId));
    });
    window.dispatchEvent(new CustomEvent('tree.filetree.selection.changed', {
      detail: {
        selectedRowIds: Array.from(sidebarFileTreeSelection.selectedRowIds),
        anchorRowId: sidebarFileTreeSelection.anchorRowId,
        focusedRowId: sidebarFileTreeSelection.focusedRowId
      }
    }));
  }

  function selectSidebarFileTreeRow(row, modifiers) {
    var runtimeFn = fileTreeSelectionRuntimeFunction('selectSidebarFileTreeRow');
    if (runtimeFn) return runtimeFn(row, sidebarFileTreeSelection, modifiers, fileTreeSelectionRuntimeDeps());
    if (!(row instanceof HTMLElement)) return [];
    var rowId = row.getAttribute('data-row-id') || '';
    if (!rowId) return [];
    var rows = visibleFileTreeRows();
    var visibleRowIds = rows.map(function(item) { return item.getAttribute('data-row-id') || ''; }).filter(Boolean);
    var selected = new Set(sidebarFileTreeSelection.selectedRowIds);
    var shiftKey = Boolean(modifiers && modifiers.shiftKey);
    var ctrlKey = Boolean(modifiers && (modifiers.ctrlKey || modifiers.metaKey));
    if (shiftKey && sidebarFileTreeSelection.anchorRowId) {
      var anchorIndex = visibleRowIds.indexOf(sidebarFileTreeSelection.anchorRowId);
      var targetIndex = visibleRowIds.indexOf(rowId);
      if (anchorIndex >= 0 && targetIndex >= 0) {
        selected = new Set(visibleRowIds.slice(Math.min(anchorIndex, targetIndex), Math.max(anchorIndex, targetIndex) + 1));
      } else {
        selected = new Set([rowId]);
        sidebarFileTreeSelection.anchorRowId = rowId;
      }
    } else if (ctrlKey) {
      if (selected.has(rowId) && selected.size > 1) selected.delete(rowId);
      else selected.add(rowId);
      sidebarFileTreeSelection.anchorRowId = rowId;
    } else {
      selected = new Set([rowId]);
      sidebarFileTreeSelection.anchorRowId = rowId;
    }
    sidebarFileTreeSelection.selectedRowIds = selected;
    sidebarFileTreeSelection.focusedRowId = rowId;
    syncSidebarFileTreeSelection();
    return Array.from(selected);
  }

  function selectSidebarFileTreeDocument(documentId, options) {
    var id = String(documentId || '').trim();
    if (!id) return false;
    var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + id) + '"]')
      || document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-document-id="' + cssEscape(id) + '"]')
      || document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-doc-id="' + cssEscape(id) + '"]');
    if (!(row instanceof HTMLElement)) {
      var relativePath = options && options.relativePath
        ? String(options.relativePath || '').trim()
        : id.indexOf('local-md:') === 0
          ? decodeLocalEncodedPath(id.slice('local-md:'.length))
          : '';
      var bundleParentPath = localMarkdownBundleParentPath(relativePath);
      var bundleRow = bundleParentPath ? visibleFileTreeRowByRelativePath(bundleParentPath) : null;
      if (bundleRow instanceof HTMLElement) {
        return activateSidebarFileTreeRow(bundleRow, options);
      }
      if (relativePath && typeof revealFileTreeResource === 'function') {
        void revealFileTreeResource({
          rootUri: options && options.rootUri,
          relativePath: relativePath,
          rowId: options && options.rowId,
          select: true,
          focus: true,
          scroll: options ? options.scrollIntoView !== false : true
        });
        return true;
      }
      return false;
    }
    return activateSidebarFileTreeRow(row, options);
  }

  function selectSidebarFileTreeRowById(rowId, options) {
    var id = String(rowId || '').trim();
    if (!id) return false;
    var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(id) + '"]');
    if (!(row instanceof HTMLElement)) return false;
    selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false });
    document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) {
      if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false');
    });
    row.setAttribute('data-active', 'true');
    if (options && options.scrollIntoView !== false) {
      try {
        row.scrollIntoView({ block: 'nearest' });
      } catch (_) {
        row.scrollIntoView();
      }
    }
    return true;
  }

  function pendingLocalFolderRestoreRowId() {
    if (window.__mnotePendingLocalFolderRestoreFiletreeRowId) {
      return String(window.__mnotePendingLocalFolderRestoreFiletreeRowId || '').trim();
    }
    if (!window.localStorage) return '';
    var rowId = '';
    try {
      rowId = String(window.localStorage.getItem('mnote.pendingLocalFolderRestoreFiletreeRowId') || '').trim();
    } catch (_) {
      rowId = '';
    }
    if (!rowId && typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) {
      rowId = decodeURIComponent(window.name.slice('mnote.pendingLocalFolderRestoreFiletreeRowId='.length));
    }
    if (rowId) window.__mnotePendingLocalFolderRestoreFiletreeRowId = rowId;
    return rowId;
  }

  function clearPendingLocalFolderRestoreRowId() {
    window.__mnotePendingLocalFolderRestoreFiletreeRowId = '';
    try {
      window.localStorage.removeItem('mnote.pendingLocalFolderRestoreFiletreeRowId');
    } catch (_) {}
    if (typeof window.name === 'string' && window.name.indexOf('mnote.pendingLocalFolderRestoreFiletreeRowId=') === 0) {
      window.name = '';
    }
  }

  function applyPendingLocalFolderRestoreFocusOnce() {
    var rowId = pendingLocalFolderRestoreRowId();
    if (!rowId) return false;
    document.documentElement.setAttribute('data-mnote-local-folder-restore-pending-row-id', rowId);
    if (!selectSidebarFileTreeRowById(rowId, { scrollIntoView: true })) {
      document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'row-missing');
      return false;
    }
    document.documentElement.setAttribute('data-mnote-local-folder-restore-focused-row-id', rowId);
    document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'focused');
    return true;
  }

  function schedulePendingLocalFolderRestoreFocus() {
    if (!pendingLocalFolderRestoreRowId()) return false;
    if (window.__mnotePendingLocalFolderRestoreFocusTimer) return false;
    var attempts = 0;
    var focused = false;
    focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
    window.__mnotePendingLocalFolderRestoreFocusTimer = window.setInterval(function() {
      attempts += 1;
      focused = applyPendingLocalFolderRestoreFocusOnce() || focused;
      if (attempts >= 20) {
        if (focused) clearPendingLocalFolderRestoreRowId();
        else document.documentElement.setAttribute('data-mnote-local-folder-restore-focus-status', 'timeout');
        window.clearInterval(window.__mnotePendingLocalFolderRestoreFocusTimer);
        window.__mnotePendingLocalFolderRestoreFocusTimer = 0;
      }
    }, 250);
    return focused;
  }

  function selectedSidebarFileTreeRowIdsForDrag(row) {
    var runtimeFn = fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRowIdsForDrag');
    if (runtimeFn) return runtimeFn(row, sidebarFileTreeSelection);
    var rowId = row instanceof HTMLElement ? row.getAttribute('data-row-id') || '' : '';
    if (rowId && sidebarFileTreeSelection.selectedRowIds.has(rowId)) {
      return Array.from(sidebarFileTreeSelection.selectedRowIds);
    }
    return rowId ? [rowId] : [];
  }

  function selectedSidebarFileTreeRows() {
    var runtimeFn = fileTreeSelectionRuntimeFunction('selectedSidebarFileTreeRows');
    if (runtimeFn) return runtimeFn(sidebarFileTreeSelection, fileTreeSelectionRuntimeDeps());
    var selectedIds = sidebarFileTreeSelection.selectedRowIds;
    var rows = visibleFileTreeRows().filter(function(row) {
      var rowId = row.getAttribute('data-row-id') || '';
      return rowId && selectedIds.has(rowId);
    });
    if (rows.length > 0) return rows;
    if (sidebarFileTreeSelection.focusedRowId) {
      var focused = document.querySelector(
        '#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]'
      );
      if (focused instanceof HTMLElement) return [focused];
    }
    return [];
  }

  function fileTreeRowDocumentId(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowDocumentId');
    if (runtimeFn) return runtimeFn(row);
    if (!(row instanceof HTMLElement)) return '';
    return String(row.getAttribute('data-document-id') || row.getAttribute('data-doc-id') || '').trim();
  }

  function fileTreeRowAssetId(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowAssetId');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!(row instanceof HTMLElement)) return '';
    var assetId = String(row.getAttribute('data-asset-id') || '').trim();
    if (assetId) return assetId;
    if (currentSourceKind() === 'local_folder' && fileTreeRowKind(row) === 'asset') {
      return String(row.getAttribute('data-row-id') || '').trim();
    }
    return '';
  }

  function decodeLocalEncodedPath(value) {
    var runtimeFn = fileTreeRuntimeFunction('decodeLocalEncodedPath');
    if (runtimeFn) return runtimeFn(value);
    var path = String(value || '').trim().replace(/~([0-9A-Fa-f]{2})/g, '%$1');
    if (!path) return '';
    try {
      return decodeURIComponent(path);
    } catch (_) {
      return path;
    }
  }

  function normalizeFileTreeRelativePath(value) {
    var normalized = String(value || '').trim().replace(/^\/+|\/+$/g, '');
    return normalized === '.' ? '' : normalized;
  }

  function parentRelativePathForFileTreePath(relativePath) {
    var normalized = normalizeFileTreeRelativePath(relativePath);
    if (!normalized) return '';
    var index = normalized.lastIndexOf('/');
    return index > 0 ? normalized.slice(0, index) : '';
  }

  function fileNameStem(value) {
    var name = String(value || '').trim();
    var dot = name.lastIndexOf('.');
    return dot > 0 ? name.slice(0, dot) : name;
  }

  function localMarkdownBundleParentPath(relativePath) {
    var normalized = normalizeFileTreeRelativePath(relativePath);
    if (!/\.md$/i.test(normalized)) return '';
    var parent = parentRelativePathForFileTreePath(normalized);
    if (!parent) return '';
    var fileName = normalized.slice(normalized.lastIndexOf('/') + 1);
    var parentName = parent.slice(parent.lastIndexOf('/') + 1);
    return fileNameStem(fileName) === parentName ? parent : '';
  }

  function visibleFileTreeRowByRelativePath(relativePath) {
    var normalized = normalizeFileTreeRelativePath(relativePath);
    if (!normalized) return null;
    var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path="' + cssEscape(normalized) + '"]');
    if (!(row instanceof HTMLElement)) return null;
    if (row.closest('.tree-children--collapsed')) return null;
    return row;
  }

  function activateSidebarFileTreeRow(row, options) {
    if (!(row instanceof HTMLElement)) return false;
    var rowId = row.getAttribute('data-row-id') || '';
    if (!rowId) return false;
    selectSidebarFileTreeRow(row, { ctrlKey: false, metaKey: false, shiftKey: false });
    document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach(function(activeRow) {
      if (activeRow instanceof HTMLElement) activeRow.setAttribute('data-active', 'false');
    });
    row.setAttribute('data-active', 'true');
    if (options && options.scrollIntoView !== false) {
      try {
        row.scrollIntoView({ block: 'nearest' });
      } catch (_) {
        row.scrollIntoView();
      }
    }
    return true;
  }

  function fileTreeRowLocalRelativePath(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowLocalRelativePath');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!(row instanceof HTMLElement)) return '';
    var direct = String(row.getAttribute('data-local-relative-path') || '').trim();
    if (direct) return direct;
    var assetPath = localFilePathFromAssetId(fileTreeRowAssetId(row));
    if (assetPath) return assetPath;
    var rowId = String(row.getAttribute('data-row-id') || '').trim();
    var nodeId = String(row.getAttribute('data-node-id') || '').trim();
    var documentId = fileTreeRowDocumentId(row);
    var kind = fileTreeRowKind(row);
    if (rowId.indexOf('local:asset:') === 0) return rowId.slice('local:asset:'.length);
    if (rowId.indexOf('local:markdown:') === 0) return rowId.slice('local:markdown:'.length);
    if (rowId.indexOf('local:folder:') === 0) return rowId.slice('local:folder:'.length);
    if (rowId.indexOf('local:node:') === 0) return rowId.slice('local:node:'.length);
    if (nodeId.indexOf('local:node:') === 0) return nodeId.slice('local:node:'.length);
    if ((kind === 'document' || kind === 'doc' || kind === 'markdown') && documentId.indexOf('local-md:') === 0) {
      return decodeLocalEncodedPath(documentId.slice('local-md:'.length));
    }
    if ((kind === 'folder' || kind === 'directory' || kind === 'index') && documentId.indexOf('local-dir:') === 0) {
      return decodeLocalEncodedPath(documentId.slice('local-dir:'.length));
    }
    return '';
  }

  function fileTreeRowLocalUploadTargetRelativePath(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowLocalUploadTargetRelativePath');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!(row instanceof HTMLElement)) return '';
    var kind = fileTreeRowKind(row);
    var relativePath = fileTreeRowLocalRelativePath(row);
    if (!relativePath) return '';
    if (kind === 'folder' || kind === 'directory') return relativePath;
    var lastSlash = relativePath.lastIndexOf('/');
    return lastSlash >= 0 ? relativePath.slice(0, lastSlash) : '';
  }

  function fileTreeRowKind(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowKind');
    if (runtimeFn) return runtimeFn(row);
    if (!(row instanceof HTMLElement)) return '';
    return String(row.getAttribute('data-row-kind') || '').trim();
  }

  function isFileTreeDownloadableAssetRow(row) {
    var runtimeFn = fileTreeRuntimeFunction('isFileTreeDownloadableAssetRow');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!(row instanceof HTMLElement)) return false;
    var kind = fileTreeRowKind(row);
    if (kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || kind === 'folder' || kind === 'directory') return false;
    return Boolean(fileTreeRowAssetId(row));
  }

  function isFileTreeDownloadableRow(row) {
    var runtimeFn = fileTreeRuntimeFunction('isFileTreeDownloadableRow');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!(row instanceof HTMLElement)) return false;
    if (currentSourceKind() !== 'local_folder') return isFileTreeDownloadableAssetRow(row);
    return Boolean(fileTreeRowLocalRelativePath(row) || isFileTreeDownloadableAssetRow(row));
  }

  function fileTreeAssetDownloadDetail(row) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeAssetDownloadDetail');
    if (runtimeFn) return runtimeFn(row, fileTreeRuntimeDeps());
    if (!isFileTreeDownloadableRow(row)) return null;
    var title = rowTitle(row);
    var relativePath = fileTreeRowLocalRelativePath(row);
    var assetId = fileTreeRowAssetId(row) || (relativePath ? 'local-file:' + relativePath : '');
    return {
      contextKind: 'filetree',
      documentId: fileTreeRowDocumentId(row),
      rowId: row.getAttribute('data-row-id') || '',
      rowKind: fileTreeRowKind(row),
      assetId: assetId,
      localRelativePath: relativePath,
      title: title,
      fileName: title,
      workspaceId: resolveWorkspaceId(row)
    };
  }

  function selectedSidebarFileTreeRowsForDownload(contextRow) {
    var contextRowId = contextRow instanceof HTMLElement ? contextRow.getAttribute('data-row-id') || '' : '';
    var selectedRows = selectedSidebarFileTreeRows().filter(isFileTreeDownloadableRow);
    if (contextRowId && sidebarFileTreeSelection.selectedRowIds.has(contextRowId) && selectedRows.length > 0) return selectedRows;
    return isFileTreeDownloadableRow(contextRow) ? [contextRow] : selectedRows;
  }

  function selectedSidebarFileTreeAssetRowsForDownload(contextRow) {
    return selectedSidebarFileTreeRowsForDownload(contextRow).filter(isFileTreeDownloadableAssetRow);
  }

  function downloadSelectedFileTreeAssetRows(detail, trigger) {
    var contextRow = trigger && trigger.closest ? trigger.closest('.tree-row[data-shell-mode="filetree"]') : trigger;
    if (!(contextRow instanceof HTMLElement) && detail && detail.rowId) {
      contextRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(detail.rowId) + '"]');
    }
    var rows = selectedSidebarFileTreeRowsForDownload(contextRow);
    var seen = new Set();
    var downloads = [];
    rows.forEach(function(row) {
      var item = fileTreeAssetDownloadDetail(row);
      var key = item && (item.localRelativePath || item.assetId || item.rowId);
      if (!item || !key || seen.has(key)) return;
      seen.add(key);
      downloads.push(item);
    });
    if (downloads.length === 0 && detail && detail.assetId) {
      downloads.push(Object.assign({}, detail, { fileName: detail.fileName || detail.title || '附件' }));
    } else if (downloads.length === 0 && detail && detail.localRelativePath) {
      downloads.push(Object.assign({}, detail, {
        assetId: detail.assetId || 'local-file:' + detail.localRelativePath,
        fileName: detail.fileName || detail.title || '附件'
      }));
    }
    if (downloads.length === 0) {
      recordFileTreeActionStatus('blocked', Object.assign({}, detail || {}, { reason: 'no-downloadable-assets' }));
      return false;
    }
    var primary = Object.assign({}, downloads[0], {
      count: downloads.length,
      assetIds: downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean),
      localRelativePaths: downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean)
    });
    recordFileTreeAction(downloads.length > 1 ? 'bulk-download' : 'download', primary);
    recordFileTreeActionStatus('requested', primary);
    document.documentElement.setAttribute('data-mnote-filetree-download-count', String(downloads.length));
    document.documentElement.setAttribute('data-mnote-filetree-download-asset-ids', downloads.map(function(item) { return item.assetId || ''; }).filter(Boolean).join(','));
    document.documentElement.setAttribute('data-mnote-filetree-download-paths', downloads.map(function(item) { return item.localRelativePath || ''; }).filter(Boolean).join(','));
    downloads.forEach(function(item) {
      void openEditorAttachmentDownload(item);
    });
    return true;
  }

  function hasSelectedDocumentAncestor(row, selectedDocRowIds) {
    var node = row instanceof HTMLElement ? row.closest('.tree-node') : null;
    while (node && node.parentElement) {
      var parentChildren = node.parentElement.closest('.tree-children');
      var parentNode = parentChildren ? parentChildren.closest('.tree-node') : null;
      var parentRow = parentNode ? parentNode.querySelector(':scope > .tree-row[data-shell-mode="filetree"]') : null;
      if (parentRow instanceof HTMLElement) {
        var parentRowId = parentRow.getAttribute('data-row-id') || '';
        if (selectedDocRowIds.has(parentRowId)) return true;
      }
      node = parentNode;
    }
    return false;
  }

  function classifySidebarFileTreeAsset(row) {
    var badge = row instanceof HTMLElement ? row.querySelector('.tree-kind-badge') : null;
    var iconKind = badge instanceof HTMLElement ? String(badge.getAttribute('data-kind') || '').trim() : '';
    var objectKind = row instanceof HTMLElement ? String(row.getAttribute('data-object-kind') || '').trim() : '';
    var title = rowTitle(row).toLowerCase();
    if (objectKind === 'mindmap' || iconKind === 'mindmap') return 'mindmap';
    if (objectKind === 'table' || iconKind === 'table' || iconKind === 'luckysheet' || title.indexOf('.luckysheet') >= 0) return 'table';
    return 'file';
  }

  function buildSidebarFileTreeDeletePlan(rows) {
    var docRows = [];
    var folderRows = [];
    var assetRows = [];
    rows.forEach(function(row) {
      var kind = fileTreeRowKind(row);
      var documentId = fileTreeRowDocumentId(row);
      if ((kind === 'document' || kind === 'doc' || kind === 'index' || kind === 'markdown' || documentId.indexOf('local-md:') === 0) && documentId) {
        docRows.push(row);
        return;
      }
      if (kind === 'folder' && currentSourceKind() === 'local_folder') {
        folderRows.push(row);
        return;
      }
      if (fileTreeRowAssetId(row)) assetRows.push(row);
    });
    var selectedContainerRowIds = new Set(docRows.concat(folderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
    var topDocRows = [];
    var topFolderRows = [];
    var seenDocs = new Set();
    var seenFolders = new Set();
    docRows.forEach(function(row) {
      var documentId = fileTreeRowDocumentId(row);
      if (!documentId || seenDocs.has(documentId)) return;
      if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
      seenDocs.add(documentId);
      topDocRows.push(row);
    });
    folderRows.forEach(function(row) {
      var rowId = row.getAttribute('data-row-id') || '';
      if (!rowId || seenFolders.has(rowId)) return;
      if (hasSelectedDocumentAncestor(row, selectedContainerRowIds)) return;
      seenFolders.add(rowId);
      topFolderRows.push(row);
    });
    var selectedTopContainerRows = new Set(topDocRows.concat(topFolderRows).map(function(row) { return row.getAttribute('data-row-id') || ''; }).filter(Boolean));
    var fileAssetRows = [];
    var mindmapRows = [];
    var tableRows = [];
    var seenAssets = new Set();
    assetRows.forEach(function(row) {
      var assetId = fileTreeRowAssetId(row);
      if (!assetId || seenAssets.has(assetId)) return;
      if (hasSelectedDocumentAncestor(row, selectedTopContainerRows)) return;
      seenAssets.add(assetId);
      var assetKind = classifySidebarFileTreeAsset(row);
      if (assetKind === 'mindmap') mindmapRows.push(row);
      else if (assetKind === 'table') tableRows.push(row);
      else fileAssetRows.push(row);
    });
    return {
      docRows: topDocRows,
      folderRows: topFolderRows,
      fileAssetRows: fileAssetRows,
      mindmapRows: mindmapRows,
      tableRows: tableRows
    };
  }

  function sidebarFileTreeDeleteConfirmText(plan) {
    var docCount = plan.docRows.length;
    var folderCount = plan.folderRows.length;
    var fileCount = plan.fileAssetRows.length;
    var mindmapCount = plan.mindmapRows.length;
    var tableCount = plan.tableRows.length;
    var parts = [];
    if (docCount > 0) parts.push(docCount + ' 个页面（删除到垃圾桶）');
    if (folderCount > 0) parts.push(folderCount + ' 个文件夹（删除到垃圾桶）');
    if (fileCount > 0) parts.push(fileCount + ' 个附件（删除，10 分钟内可撤销）');
    if (mindmapCount > 0) parts.push(mindmapCount + ' 个思维导图（移入垃圾桶，10 分钟内可恢复）');
    if (tableCount > 0) parts.push(tableCount + (currentSourceKind() === 'local_folder' ? ' 个在线表格（移入垃圾桶，10 分钟内可恢复）' : ' 个在线表格（删除）'));
    return '确认删除选中的 ' + parts.join(' + ') + ' 吗？';
  }

  async function postSidebarFileTreeJson(url, body) {
    var response = await fetch(url, {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify(body || {})
    });
    var payload = await response.json().catch(function() { return null; });
    if (!response.ok) {
      throw new Error((payload && (payload.error || payload.message)) || (url + ' failed: ' + response.status));
    }
    return payload;
  }

  function fileTreeRowsByRowIds(rowIds) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeRowsByRowIds');
    if (runtimeFn) return runtimeFn(rowIds, fileTreeRuntimeDeps());
    return (rowIds || []).map(function(rowId) {
      return document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(rowId) + '"]');
    }).filter(function(row) { return row instanceof HTMLElement; });
  }

  function fileTreeChildCount(documentId) {
    var runtimeFn = fileTreeRuntimeFunction('fileTreeChildCount');
    if (runtimeFn) return runtimeFn(documentId, fileTreeRuntimeDeps());
    if (!documentId) return 0;
    var row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape('doc:' + documentId) + '"]');
    var node = row ? row.closest('.tree-node') : null;
    var children = node ? node.querySelectorAll(':scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="document"], :scope > .tree-children > .tree-node > .tree-row[data-shell-mode="filetree"][data-row-kind="doc"]') : [];
    return children.length;
  }

  function fileTreeMoveTargetParentId(targetRow) {
    if (!(targetRow instanceof HTMLElement)) return null;
    return fileTreeMenuTargetParentId({
      rowKind: fileTreeRowKind(targetRow),
      rowId: targetRow.getAttribute('data-row-id') || '',
      documentId: fileTreeRowDocumentId(targetRow)
    }, targetRow) || null;
  }

  function fileTreeMoveSourceId(row) {
    if (!(row instanceof HTMLElement)) return '';
    return fileTreeRowDocumentId(row)
      || (currentSourceKind() === 'local_folder'
        ? (fileTreeRowAssetId(row) || row.getAttribute('data-row-id') || '')
        : fileTreeRowAssetId(row));
  }

  async function moveSidebarFileTreeRows(rowIds, targetRow, options) {
    var rows = fileTreeRowsByRowIds(rowIds || []);
    if (rows.length === 0) return false;
    var copy = Boolean(options && options.copy);
    var targetParentId = fileTreeMoveTargetParentId(targetRow);
    var workspaceId = resolveWorkspaceId(targetRow || document.body);
    var writable = await ensureFileTreeWritableTarget('move', targetRow, rowIds || [], copy);
    if (!writable) return false;
    var batchId = nextFileTreeOperationBatchId(copy ? 'copy' : 'move');
    var plan = buildSidebarFileTreeDeletePlan(rows);
    var failures = [];
    if (copy) {
      dispatchSidebarEvent('tree.filetree.copy-requested', { rowIds: rowIds || [], targetDocumentId: targetParentId });
      recordFileTreeActionStatus('copy-requested', { documentId: targetParentId, batchId: batchId });
      return true;
    }
    var movableRows = plan.docRows.concat(plan.folderRows);
    if (currentSourceKind() === 'local_folder') {
      movableRows = movableRows.concat(plan.fileAssetRows, plan.mindmapRows, plan.tableRows);
    }
    for (var i = 0; i < movableRows.length; i += 1) {
      var sourceRow = movableRows[i];
      var sourceId = fileTreeMoveSourceId(sourceRow);
      if (!sourceId || sourceId === targetParentId) continue;
      try {
        await dispatchTreeCommand(targetRow || sourceRow, {
          action: 'move',
          batchId: batchId,
          workspaceId: workspaceId,
          documentId: sourceId,
          parentId: targetParentId,
          sortOrder: targetParentId ? fileTreeChildCount(targetParentId) + i : i
        });
      } catch (error) {
        failures.push(sourceId + ': ' + (error && error.message ? error.message : '移动失败'));
      }
    }
    if (currentSourceKind() !== 'local_folder') {
      var assetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean);
      if (assetIds.length > 0) {
        try {
          await postSidebarFileTreeJson('/api/media/batch', {
            action: 'move',
            assetIds: assetIds,
            targetDocumentId: targetParentId
          });
        } catch (error) {
          failures.push(assetIds.join(',') + ': ' + (error && error.message ? error.message : '移动附件失败'));
        }
      }
    }
    window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'move', failed: failures.length, count: movableRows.length } }));
    if (failures.length > 0) {
      recordFileTreeActionStatus('failed', { documentId: targetParentId, fallback: 'alert', batchId: batchId });
      window.alert('部分对象移动失败：' + failures.join('；'));
      return false;
    }
    recordFileTreeActionStatus('applied', { documentId: targetParentId, batchId: batchId });
    return true;
  }

  async function pasteSidebarFileTreeClipboard(trigger) {
    if (!runtimeState.sidebarFileTreeClipboard || !Array.isArray(runtimeState.sidebarFileTreeClipboard.rowIds) || runtimeState.sidebarFileTreeClipboard.rowIds.length === 0) return false;
    var targetRow = trigger instanceof HTMLElement ? trigger : null;
    if (!targetRow && sidebarFileTreeSelection.focusedRowId) {
      targetRow = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-row-id="' + cssEscape(sidebarFileTreeSelection.focusedRowId) + '"]');
    }
    var targetDocumentId = fileTreeMoveTargetParentId(targetRow) || currentDocumentId();
    var action = runtimeState.sidebarFileTreeClipboard.action === 'cut' ? 'move' : 'copy';
    recordFileTreeAction('paste', {
      rowId: targetRow ? targetRow.getAttribute('data-row-id') || '' : '',
      documentId: targetDocumentId,
      sourceRowIds: runtimeState.sidebarFileTreeClipboard.rowIds,
      clipboardAction: runtimeState.sidebarFileTreeClipboard.action
    });
    var ok = await moveSidebarFileTreeRows(runtimeState.sidebarFileTreeClipboard.rowIds, targetRow, { copy: action === 'copy' });
    if (ok && action === 'move') runtimeState.sidebarFileTreeClipboard = null;
    return ok;
  }

  async function deleteSelectedSidebarFileTreeRows(trigger) {
    var rows = selectedSidebarFileTreeRows();
    var plan = buildSidebarFileTreeDeletePlan(rows);
    var total = plan.docRows.length + plan.folderRows.length + plan.fileAssetRows.length + plan.mindmapRows.length + plan.tableRows.length;
    if (total === 0) return false;
    if (!window.confirm(sidebarFileTreeDeleteConfirmText(plan))) return false;
    var batchId = nextFileTreeOperationBatchId('bulk-delete');
    document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-batch-id', batchId);
    recordFileTreeAction('bulk-delete', { rowId: trigger instanceof HTMLElement ? trigger.getAttribute('data-row-id') || '' : '', count: total, batchId: batchId });
    recordFileTreeActionStatus('pending', { count: total, batchId: batchId });
    var failures = [];
    for (var i = 0; i < plan.docRows.length; i += 1) {
      var docRow = plan.docRows[i];
      var documentId = fileTreeRowDocumentId(docRow);
      try {
        await dispatchTreeCommand(trigger || docRow, {
          action: 'archive',
          batchId: batchId,
          workspaceId: resolveWorkspaceId(docRow),
          documentId: documentId
        });
        applyRemoveDocumentDelta({ documentId: documentId });
      } catch (error) {
        failures.push(documentId);
      }
    }
    for (var fd = 0; fd < plan.folderRows.length; fd += 1) {
      var folderRow = plan.folderRows[fd];
      var folderId = folderRow.getAttribute('data-row-id') || folderRow.getAttribute('data-node-id') || '';
      try {
        await dispatchTreeCommand(trigger || folderRow, {
          action: 'archive',
          batchId: batchId,
          workspaceId: resolveWorkspaceId(folderRow),
          documentId: folderId
        });
      } catch (error) {
        failures.push(folderId);
      }
    }
    if (plan.fileAssetRows.length > 0) {
      var fileAssetIds = plan.fileAssetRows.map(fileTreeRowAssetId).filter(Boolean);
      try {
        if (currentSourceKind() === 'local_folder') {
          for (var lf = 0; lf < fileAssetIds.length; lf += 1) {
            await dispatchTreeCommand(trigger || plan.fileAssetRows[lf], {
              action: 'archive',
              batchId: batchId,
              workspaceId: resolveWorkspaceId(plan.fileAssetRows[lf]),
              documentId: fileAssetIds[lf]
            });
          }
        } else {
          await postSidebarFileTreeJson('/api/media/batch', { action: 'delete', assetIds: fileAssetIds });
        }
        fileAssetIds.forEach(removeFileTreeAssetRow);
      } catch (error) {
        failures = failures.concat(fileAssetIds.map(function(assetId) {
          return assetId + ': ' + (error && error.message ? error.message : '删除附件失败');
        }));
      }
    }
    for (var m = 0; m < plan.mindmapRows.length; m += 1) {
      var mindmapRow = plan.mindmapRows[m];
      var mindmapId = fileTreeRowAssetId(mindmapRow);
      try {
        if (currentSourceKind() === 'local_folder') {
          await dispatchTreeCommand(trigger || mindmapRow, {
            action: 'archive',
            batchId: batchId,
            workspaceId: resolveWorkspaceId(mindmapRow),
            documentId: mindmapId
          });
        } else {
          var mindmapDocId = fileTreeRowDocumentId(mindmapRow);
          var mindmapResponse = await fetch('/api/mindmap/' + encodeURIComponent(mindmapDocId) + '/' + encodeURIComponent(mindmapId), { method: 'DELETE' });
          if (!mindmapResponse.ok) throw new Error('mindmap_delete_failed_' + mindmapResponse.status);
        }
        removeFileTreeAssetRow(mindmapId);
      } catch (error) {
        failures.push(mindmapId);
      }
    }
    for (var t = 0; t < plan.tableRows.length; t += 1) {
      var tableRow = plan.tableRows[t];
      var tableId = fileTreeRowAssetId(tableRow);
      try {
        if (currentSourceKind() === 'local_folder') {
          await dispatchTreeCommand(trigger || tableRow, {
            action: 'archive',
            batchId: batchId,
            workspaceId: resolveWorkspaceId(tableRow),
            documentId: tableId
          });
        } else {
          var tableResponse = await fetch('/api/tables/' + encodeURIComponent(tableId), { method: 'DELETE' });
          if (!tableResponse.ok) throw new Error('table_delete_failed_' + tableResponse.status);
          window.dispatchEvent(new CustomEvent('online-table-deleted', { detail: { tableId: tableId } }));
        }
        removeFileTreeAssetRow(tableId);
      } catch (error) {
        failures.push(tableId);
      }
    }
    sidebarFileTreeSelection.selectedRowIds = new Set();
    sidebarFileTreeSelection.focusedRowId = null;
    syncSidebarFileTreeSelection();
    if (failures.length > 0) {
      window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'bulk-delete', failed: failures.length, count: total } }));
      recordFileTreeActionStatus('failed', { count: total, failures: failures.slice(0, 20), fallback: 'alert', batchId: batchId });
      window.alert('部分对象删除失败：' + failures.slice(0, 5).join(', ') + (failures.length > 5 ? '…' : ''));
      return false;
    }
    document.documentElement.setAttribute('data-mnote-filetree-bulk-delete-applied', 'true');
    window.dispatchEvent(new CustomEvent('tree:local-command-batch-complete', { detail: { batchId: batchId, action: 'bulk-delete', failed: 0, count: total } }));
    recordFileTreeActionStatus('archived', { count: total, undo: 'trash-modal', batchId: batchId });
    if (currentSourceKind() !== 'local_folder') void refreshLocalFolderSidebarSnapshot();
    return true;
  }


  return {
    rowTitle,
    rowCenter,
    renameFileTreeAsset,
    beginFileTreeInlineRename,
    closeTreeContextMenu,
    copyTreeContextValue,
    triggerBrowserDownload,
    recordFileTreeAction,
    recordFileTreeActionStatus,
    documentHref,
    convertToPreviousSiblingChild,
    fileTreeCopyPath,
    fileTreeMenuTargetParentId,
    withOfficeEditModeGuard,
    handleTreeContextMenuAction,
    appendTreeContextMenuButton,
    buildSidebarFileTreeContext,
    evaluateSidebarFileTreeWhen,
    openTreeContextMenu,
    openPageTreeContextMenu,
    openFileTreeContextMenu,
    visibleFileTreeRows,
    syncSidebarFileTreeSelection,
    selectSidebarFileTreeRow,
    selectSidebarFileTreeDocument,
    selectSidebarFileTreeRowById,
    pendingLocalFolderRestoreRowId,
    clearPendingLocalFolderRestoreRowId,
    applyPendingLocalFolderRestoreFocusOnce,
    schedulePendingLocalFolderRestoreFocus,
    selectedSidebarFileTreeRowIdsForDrag,
    selectedSidebarFileTreeRows,
    fileTreeRowDocumentId,
    fileTreeRowAssetId,
    decodeLocalEncodedPath,
    fileTreeRowLocalRelativePath,
    fileTreeRowLocalUploadTargetRelativePath,
    fileTreeRowKind,
    isFileTreeDownloadableAssetRow,
    isFileTreeDownloadableRow,
    fileTreeAssetDownloadDetail,
    selectedSidebarFileTreeRowsForDownload,
    selectedSidebarFileTreeAssetRowsForDownload,
    downloadSelectedFileTreeAssetRows,
    hasSelectedDocumentAncestor,
    classifySidebarFileTreeAsset,
    buildSidebarFileTreeDeletePlan,
    sidebarFileTreeDeleteConfirmText,
    postSidebarFileTreeJson,
    fileTreeRowsByRowIds,
    fileTreeChildCount,
    moveSidebarFileTreeRows,
    pasteSidebarFileTreeClipboard,
    deleteSelectedSidebarFileTreeRows,
  };
};
