import {
  localFileOpenPathFromTiptapHref,
  localMarkdownDocumentIdFromRelativePath,
  localMarkdownRelativePathFromDocumentId,
  localizeTiptapAssetUrls,
} from './document-tiptap-conversion-runtime.js';

/**
 * @typedef {Object} MNoteWorkspacePath
 * @property {'mnote.workspace_path.v1'} schema
 * @property {string} workspaceId
 * @property {string} sourceKind
 * @property {string} rootUri
 * @property {string} relativePath
 * @property {string} documentId
 * @property {string} objectIdentity
 * @property {string} assetId
 * @property {string} resourceKind
 *
 * @typedef {Object} MNoteOpenEditorEntry
 * @property {string} objectIdentity
 * @property {MNoteWorkspacePath|null} workspacePath
 * @property {'primary'|'secondary'} paneRole
 * @property {string} editorKind
 * @property {boolean} active
 * @property {string} dirtyState
 * @property {boolean} preview
 * @property {boolean} pinned
 * @property {number} lastActiveAt
 *
 * @typedef {Object} MNoteOpenEditorsSnapshot
 * @property {'mnote.open_editors_snapshot.v1'} schema
 * @property {number} generatedAt
 * @property {string} activeObjectIdentity
 * @property {MNoteOpenEditorEntry[]} editors
 * @property {{primary: Object, secondary: Object}} groups
 */

export const createResourceTabRuntime = (dependencies = {}) => {
  const {
    loadRuntime,
    createEditorViewBinding,
    ensureLocalFolderEventChannel,
    unmountEditorViewBinding,
    setStatus,
    documentSessionRegistry,
    sessionHasRecentLocalInput,
    currentUrl,
    replaceUrlState,
    applyStoredSecondaryWidth,
    workspace,
    setSecondaryEditorHostVisible,
    markIntendedSlashRoot,
    toTiptapDocument,
    openMindmapResourceTab,
    unmountMindmapPane,
    paneViewRegistry,
    rootSelector,
    currentDocumentId,
    currentWorkspaceId,
    secondaryQueryParamNames = [],
  } = dependencies;
  const selector = rootSelector || '[data-testid="mnote-leptos-tiptap-island-editor-root"]';
  const resourceTabRegistry = new Map();
  const resourceTabMru = { primary: [], secondary: [] };
  const resourceTabMruMax = 20;
  const readonlyCodePreviewMaxChars = 1_500_000;
  const resourceTabCloseGuardAttribute = 'data-resource-tab-close-guarded';
  let onlyofficeBridgeReadyListenerBound = false;

  const normalizePaneRole = (paneRole) => String(paneRole || '').trim() === 'secondary' ? 'secondary' : 'primary';

  const resourceTabRegistryKey = (paneRole, objectIdentity) => `${normalizePaneRole(paneRole)}::${String(objectIdentity || '').trim()}`;

  const resourceTabHostNodes = (paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    return {
      paneRole: role,
      strip: document.querySelector(`[data-mnote-main-tab-strip][data-pane-role="${role}"]`),
      pageTab: document.querySelector(`[data-mnote-main-tab="page"][data-pane-role="${role}"]`),
      pagePanel: document.querySelector(`[data-mnote-page-tab-panel][data-pane-role="${role}"]`),
      host: document.querySelector(`[data-mnote-resource-tab-host][data-pane-role="${role}"]`),
      panelRoot: document.querySelector(`[data-mnote-resource-tab-panel-root][data-pane-role="${role}"]`),
    };
  };

  const resourceTabBadgeKind = (input, kind) => {
    const title = String(input?.title || input?.fileName || input?.path || '').trim().toLowerCase();
    if (kind === 'mindmap') return 'mindmap';
    if (kind === 'office') {
      if (/\.(ppt|pptx|odp)$/i.test(title)) return 'ppt';
      if (/\.(xls|xlsx|ods|csv)$/i.test(title)) return 'sheet';
      return 'word';
    }
    if (kind === 'pdf') return 'pdf';
    if (kind === 'markdown' || kind === 'text' || kind === 'code') return 'code';
    if (kind === 'image') return 'image';
    return 'file';
  };

  const resourceTabWidthType = (entry) => {
    if (!entry) return '';
    const kind = String(entry.kind || '').trim();
    if (kind === 'office') {
      const badgeKind = String(entry.badgeKind || resourceTabBadgeKind(entry, kind) || '').trim();
      if (badgeKind === 'ppt') return 'ppt';
      if (badgeKind === 'sheet') return 'excel';
      return 'word';
    }
    if (kind === 'pdf') return 'pdf';
    if (kind === 'mindmap') return 'mindmap';
    if (kind === 'markdown' || kind === 'text' || kind === 'code') return 'markdown';
    return '';
  };

  const currentWebShellWorkspaceId = () => {
    const explicit = typeof currentWorkspaceId === 'function' ? String(currentWorkspaceId() || '').trim() : '';
    if (explicit) return explicit;
    const fromBody = document.body?.dataset?.workspaceId || '';
    if (fromBody) return String(fromBody).trim();
    const shell = document.querySelector('.document-shell[data-workspace-id], [data-document-pane="true"][data-pane-role="primary"][data-pane-workspace-id]');
    if (shell instanceof HTMLElement) {
      const value = shell.getAttribute('data-workspace-id') || shell.getAttribute('data-pane-workspace-id') || '';
      if (value) return value.trim();
    }
    try {
      return currentUrl().searchParams.get('workspaceId') || '';
    } catch (_) {
      return '';
    }
  };

  const currentFileTreeWorkspacePath = () => {
    try {
      const row = document.querySelector('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-local-relative-path]');
      const reader = window.__mnoteFileTreeRuntime?.readWorkspacePathFromRow;
      if (row instanceof HTMLElement && typeof reader === 'function') {
        const workspacePath = reader(row);
        if (workspacePath && typeof workspacePath === 'object') {
          return {
            ...workspacePath,
            sourceKind: String(workspacePath.sourceKind || row.getAttribute('data-source-kind') || '').trim(),
            rootUri: String(workspacePath.rootUri || row.getAttribute('data-root-uri') || '').trim(),
          };
        }
      }
      if (row instanceof HTMLElement) {
        return {
          sourceKind: String(row.getAttribute('data-source-kind') || '').trim(),
          rootUri: String(row.getAttribute('data-root-uri') || '').trim(),
        };
      }
    } catch (_) {}
    return null;
  };

  const currentWebShellDocumentId = () => {
    const explicit = typeof currentDocumentId === 'function' ? String(currentDocumentId() || '').trim() : '';
    if (explicit) return explicit;
    const fromBody = document.body?.dataset?.documentId || '';
    if (fromBody) return String(fromBody).trim();
    const pageTab = document.querySelector('[data-mnote-main-tab="page"]');
    if (pageTab instanceof HTMLElement) return String(pageTab.getAttribute('data-document-id') || '').trim();
    return '';
  };

  const documentIdForPane = (paneRole) => {
    const role = normalizePaneRole(paneRole);
    const nodes = resourceTabHostNodes(role);
    const fromTab = String(nodes.pageTab?.getAttribute?.('data-document-id') || '').trim();
    if (fromTab) return fromTab;
    const pane = document.querySelector(`[data-document-pane="true"][data-pane-role="${role}"]`);
    if (pane instanceof HTMLElement) {
      const fromPane = String(pane.getAttribute('data-pane-document-id') || '').trim();
      if (fromPane) return fromPane;
      const root = pane.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"][data-document-id], .document-shell[data-document-id]');
      if (root instanceof HTMLElement) {
        const fromRoot = String(root.getAttribute('data-document-id') || '').trim();
        if (fromRoot) return fromRoot;
      }
    }
    return role === 'primary' ? currentWebShellDocumentId() : '';
  };

  const currentWebShellSourceKind = () => {
    try {
      return currentUrl().searchParams.get('sourceKind')
        || document.body?.dataset?.mnoteSourceKind
        || currentFileTreeWorkspacePath()?.sourceKind
        || '';
    } catch (_) {
      return document.body?.dataset?.mnoteSourceKind || currentFileTreeWorkspacePath()?.sourceKind || '';
    }
  };

  const currentWebShellRootUri = () => {
    try {
      return currentUrl().searchParams.get('rootUri')
        || document.body?.dataset?.mnoteRootUri
        || currentFileTreeWorkspacePath()?.rootUri
        || '';
    } catch (_) {
      return document.body?.dataset?.mnoteRootUri || currentFileTreeWorkspacePath()?.rootUri || '';
    }
  };

  const buildWorkspacePath = ({
    workspacePath,
    workspaceId,
    sourceKind,
    rootUri,
    relativePath,
    documentId,
    objectIdentity,
    assetId,
    resourceKind,
  } = {}) => {
    const fromInput = workspacePath && typeof workspacePath === 'object' ? workspacePath : null;
    if (fromInput && String(fromInput.schema || '') === 'mnote.workspace_path.v1') {
      return {
        schema: 'mnote.workspace_path.v1',
        workspaceId: String(fromInput.workspaceId || workspaceId || '').trim(),
        sourceKind: String(fromInput.sourceKind || sourceKind || '').trim(),
        rootUri: String(fromInput.rootUri || rootUri || '').trim(),
        relativePath: String(fromInput.relativePath || fromInput.localRelativePath || relativePath || '').trim(),
        documentId: String(fromInput.documentId || documentId || '').trim(),
        objectIdentity: fromInput.objectIdentity ?? String(objectIdentity || '').trim(),
        assetId: String(fromInput.assetId || assetId || '').trim(),
        resourceKind: String(fromInput.resourceKind || fromInput.objectKind || resourceKind || '').trim(),
      };
    }
    const structuredObjectIdentity = objectIdentity && typeof objectIdentity === 'object' ? objectIdentity : null;
    const objectIdentityText = structuredObjectIdentity ? '' : String(objectIdentity || '').trim();
    const id = String(objectIdentityText || documentId || assetId || relativePath || '').trim();
    if (!id) return null;
    return {
      schema: 'mnote.workspace_path.v1',
      workspaceId: String(workspaceId || '').trim(),
      sourceKind: String(sourceKind || '').trim(),
      rootUri: String(rootUri || '').trim(),
      relativePath: String(relativePath || '').trim(),
      documentId: String(documentId || '').trim(),
      objectIdentity: structuredObjectIdentity || objectIdentityText,
      assetId: String(assetId || '').trim(),
      resourceKind: String(resourceKind || '').trim(),
    };
  };

  const normalizeResourceTabKind = (input) => {
    const kind = String(input?.kind || '').trim().toLowerCase();
    const title = String(input?.title || input?.fileName || input?.path || '').trim().toLowerCase();
    if (kind === 'mindmap' || kind === 'office' || kind === 'pdf' || kind === 'image' || kind === 'markdown' || kind === 'text' || kind === 'code') return kind;
    if (/\.(doc|docx|ppt|pptx|xls|xlsx|odt|odp|ods)$/i.test(title)) return 'office';
    if (/\.pdf$/i.test(title)) return 'pdf';
    if (/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(title)) return 'image';
    if (/\.(md|markdown)$/i.test(title)) return 'markdown';
    if (/\.(txt|log)$/i.test(title)) return 'text';
    if (/\.(rs|ts|tsx|js|jsx|json|css|scss|html|xml|py|go|java|kt|swift|c|h|cpp|hpp|sh|bash|zsh|toml|yaml|yml|sql)$/i.test(title)) return 'code';
    return 'file';
  };

  const resolveResourceOpen = (input = {}) => {
    const kind = normalizeResourceTabKind(input);
    const badgeKind = resourceTabBadgeKind(input, kind);
    const rawTarget = String(input?.openTarget || '').trim().toLowerCase();
    const openTarget = rawTarget === 'new-window' || rawTarget === 'side' ? rawTarget : 'active-tab';
    const editable = kind === 'markdown' || kind === 'text';
    const defaultOpenMode = kind === 'office' && openTarget === 'active-tab' ? 'active-tab-iframe' : openTarget;
    const viewerUrl = kind === 'office'
      ? String(input?.officeUrl || input?.href || '').trim()
      : String(input?.href || '').trim();
    return { editorKind: kind, badgeKind, defaultOpenMode, editable, viewerUrl, openTarget };
  };

  const touchResourceTabMru = (paneRole, key) => {
    const role = normalizePaneRole(paneRole);
    const id = String(key || '').trim();
    if (!id) return;
    const list = resourceTabMru[role] || (resourceTabMru[role] = []);
    const index = list.indexOf(id);
    if (index >= 0) list.splice(index, 1);
    list.unshift(id);
    if (list.length > resourceTabMruMax) list.length = resourceTabMruMax;
  };

  const setResourceTabLastActive = (key) => {
    const entry = resourceTabRegistry.get(String(key || '').trim());
    if (!entry) return;
    const timestamp = Date.now();
    entry.lastActiveAt = timestamp;
    if (entry.session) entry.session.lastActiveAt = timestamp;
  };

  const removeFromResourceTabMru = (paneRole, key) => {
    const list = resourceTabMru[normalizePaneRole(paneRole)] || [];
    const index = list.indexOf(key);
    if (index >= 0) list.splice(index, 1);
  };

  const lastActiveResourceTabKey = (paneRole = 'primary') => {
    const list = resourceTabMru[normalizePaneRole(paneRole)] || [];
    for (const key of list) {
      if (resourceTabRegistry.has(key)) return key;
    }
    return '';
  };

  const resourceTabCloseGuardReason = (session) => {
    if (!session) return '';
    const bufferDirtyState = String(session.bufferDirtyState || '').trim();
    if (bufferDirtyState === 'Dirty' || bufferDirtyState === 'Stale' || bufferDirtyState === 'Deleted' || bufferDirtyState === 'ExternalModified') {
      return bufferDirtyState;
    }
    const hasUnsavedChanges = session.dirty
      || Boolean(session.saveTimer)
      || sessionHasRecentLocalInput(session)
      || (session.currentSerialized && session.currentSerialized !== session.lastPersistedSerialized);
    if (hasUnsavedChanges) return 'dirty';
    if (session.saving) return 'saving';
    if (session.hasExternalConflict) return 'hasExternalConflict';
    return '';
  };

  const documentSessionForPane = (paneRole, documentId) => {
    const id = String(documentId || '').trim();
    if (!id || !documentSessionRegistry || typeof documentSessionRegistry.values !== 'function') return null;
    const role = normalizePaneRole(paneRole);
    return Array.from(documentSessionRegistry.values()).find((session) => (
      session
      && session.sessionKind !== 'resource'
      && String(session.documentId || '').trim() === id
      && Array.from(session.views?.values?.() || []).some((view) => normalizePaneRole(view?.runtimeDescriptor?.paneRole) === role)
    )) || Array.from(documentSessionRegistry.values()).find((session) => (
      session
      && session.sessionKind !== 'resource'
      && String(session.documentId || '').trim() === id
    )) || null;
  };

  const documentSessionDirtyState = (session) => {
    if (!session) return '';
    const bufferDirtyState = String(session.bufferDirtyState || '').trim();
    if (bufferDirtyState === 'Dirty' || bufferDirtyState === 'Stale' || bufferDirtyState === 'Deleted' || bufferDirtyState === 'ExternalModified') return bufferDirtyState;
    if (session.hasExternalConflict) return 'ExternalModified';
    if (session.dirty || Boolean(session.saveTimer) || sessionHasRecentLocalInput(session)) return 'Dirty';
    if (session.saving) return 'Saving';
    return '';
  };

  const resourceTabBufferStateUrl = (entry) => {
    const session = entry?.session;
    if (!session || session.sourceKind !== 'local_folder') return null;
    const relativePath = String(session.resourcePath || entry?.workspacePath?.relativePath || '').trim();
    const rootUri = String(session.rootUri || entry?.workspacePath?.rootUri || '').trim();
    if (!relativePath || !rootUri) return null;
    const url = new URL('/api/documents/buffer-state', window.location.origin);
    url.searchParams.set('documentId', String(session.documentId || entry.objectIdentity || ''));
    url.searchParams.set('sourceKind', 'local_folder');
    url.searchParams.set('rootUri', rootUri);
    url.searchParams.set('relativePath', relativePath);
    const workspaceId = String(session.workspaceId || entry?.workspacePath?.workspaceId || '').trim();
    if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
    return url;
  };

  const applyResourceTabCloseGuard = (entry) => {
    if (!entry?.tab || !(entry.tab instanceof HTMLElement)) return;
    const reason = resourceTabCloseGuardReason(entry.session);
    if (reason) {
      entry.tab.setAttribute(resourceTabCloseGuardAttribute, reason);
      entry.tab.classList.add('is-close-guarded');
    } else {
      entry.tab.removeAttribute(resourceTabCloseGuardAttribute);
      entry.tab.classList.remove('is-close-guarded');
    }
  };

  const refreshResourceTabBufferState = async (entry) => {
    const url = resourceTabBufferStateUrl(entry);
    if (!url || !entry?.session) return null;
    try {
      const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
      const payload = await response.json().catch(() => null);
      if (!response.ok || !payload || payload.ok !== true || !payload.result) return null;
      const dirtyState = String(payload.result.dirtyState || '').trim();
      if (dirtyState) entry.session.bufferDirtyState = dirtyState;
      if (typeof payload.result.fileVersion === 'string' && payload.result.fileVersion.trim()) {
        entry.session.fileVersion = payload.result.fileVersion.trim();
      }
      applyResourceTabCloseGuard(entry);
      return payload.result;
    } catch (_) {
      return null;
    }
  };

  const syncResourceTabCloseGuard = (entry) => {
    applyResourceTabCloseGuard(entry);
    void refreshResourceTabBufferState(entry);
  };

  const syncResourceSessionTabGuards = (session) => {
    if (!session || session.sessionKind !== 'resource') return;
    resourceTabRegistry.forEach((entry) => {
      if (entry.session === session) syncResourceTabCloseGuard(entry);
    });
  };

  const officeBridgeDebugForEntry = (entry) => {
    if (!entry?.panel || !(entry.panel instanceof HTMLElement)) return null;
    const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
    if (!(frame instanceof HTMLIFrameElement)) return null;
    try {
      const debug = frame.contentWindow?.__MNOTE_ONLYOFFICE_DEBUG__;
      return debug && typeof debug === 'object' ? debug : null;
    } catch (_) {
      return null;
    }
  };

  const officeOpenModeForEntry = (entry) => {
    if (!entry || normalizeResourceTabKind(entry) !== 'office') return '';
    const href = String(entry.passiveFrameSrc || entry.officeUrl || entry.href || '').trim()
      || String(entry.panel?.querySelector?.('iframe.mnote-resource-tab-frame')?.getAttribute?.('src') || '').trim();
    if (href) {
      try {
        const url = new URL(href, window.location.origin);
        if (url.pathname === '/office-preview' || url.pathname === '/office-n') return 'preview';
        if (url.pathname === '/onlyoffice' || url.pathname.startsWith('/office/')) return 'onlyoffice_live';
      } catch (_) {}
    }
    if (entry.onlyofficeSessionId || entry.bridgeSessionId) return 'onlyoffice_live';
    return 'preview';
  };

  const openEditorsSnapshotEntry = (entry, key, generatedAt = Date.now()) => {
    const active = entry?.tab instanceof HTMLElement
      ? entry.tab.getAttribute('aria-selected') === 'true'
      : false;
    const documentId = String(entry?.documentId || entry?.ownerDocumentId || entry?.session?.ownerDocumentId || '').trim();
    const workspaceId = String(entry?.workspaceId || entry?.session?.workspaceId || currentWebShellWorkspaceId() || '').trim();
    const sourceKind = String(entry?.sourceKind || entry?.session?.sourceKind || currentWebShellSourceKind() || '').trim();
    const rootUri = String(entry?.rootUri || entry?.session?.rootUri || currentWebShellRootUri() || '').trim();
    const relativePath = String(entry?.path || entry?.session?.resourcePath || '').trim();
    const objectIdentity = String(entry?.objectIdentity || key || '').trim();
    const assetId = String(entry?.assetId || entry?.session?.assetId || '').trim();
    const kind = normalizeResourceTabKind(entry);
    const dirtyState = resourceTabCloseGuardReason(entry?.session);
    const officeBridgeDebug = kind === 'office' ? officeBridgeDebugForEntry(entry) : null;
    const officeOpenMode = kind === 'office' ? officeOpenModeForEntry(entry) : '';
    const onlyofficeSessionId = String(
      officeBridgeDebug?.bridgeSessionId
        || entry?.onlyofficeSessionId
        || entry?.bridgeSessionId
        || '',
    ).trim();
    const snapshotResourceKind = kind === 'office'
      ? (officeOpenMode === 'onlyoffice_live' || onlyofficeSessionId ? 'only_office' : 'attachment')
      : kind;
    const workspacePathSeed = entry?.workspacePath && typeof entry.workspacePath === 'object'
      ? { ...entry.workspacePath, resourceKind: snapshotResourceKind }
      : entry?.workspacePath;
    return {
      objectIdentity,
      workspacePath: buildWorkspacePath({
        workspacePath: workspacePathSeed,
        workspaceId,
        sourceKind,
        rootUri,
        relativePath,
        documentId,
        objectIdentity,
        assetId,
        resourceKind: snapshotResourceKind,
      }),
      paneRole: normalizePaneRole(entry?.paneRole),
      documentId,
      workspaceId,
      title: String(entry?.title || '资源').trim() || '资源',
      kind,
      editorKind: kind,
      badgeKind: entry?.tab instanceof HTMLElement
        ? String(entry.tab.getAttribute('data-mnote-tab-badge-kind') || resourceTabBadgeKind(entry, entry.kind)).trim()
        : resourceTabBadgeKind(entry, entry?.kind),
      active,
      dirtyState,
      dirtyGuard: dirtyState,
      assetId,
      path: relativePath,
      officeOpenMode,
      onlyofficeSessionId,
      bridgeSessionId: onlyofficeSessionId,
      bridgeSessionReady: Boolean(onlyofficeSessionId),
      bridgeDocumentId: String(officeBridgeDebug?.documentId || '').trim(),
      bridgeAssetId: String(officeBridgeDebug?.assetId || '').trim(),
      lastActiveAt: Number(entry?.session?.lastActiveAt || entry?.lastActiveAt || 0) || (active ? generatedAt : 0),
      preview: officeOpenMode === 'preview',
      pinned: false,
    };
  };

  const buildOpenEditorsSnapshot = () => {
    const generatedAt = Date.now();
    const pageEntries = ['primary', 'secondary'].map((paneRole) => {
      const nodes = resourceTabHostNodes(paneRole);
      const documentId = documentIdForPane(paneRole);
      const workspaceId = String(nodes.pageTab?.getAttribute?.('data-workspace-id') || currentWebShellWorkspaceId() || '').trim();
      const sourceKind = currentWebShellSourceKind();
      const rootUri = currentWebShellRootUri();
      const relativePath = sourceKind === 'local_folder' ? localMarkdownRelativePathFromDocumentId(documentId) : '';
      const objectIdentity = `page:${paneRole}`;
      const workspaceObjectIdentity = {
        objectKind: 'page',
        documentId,
        blockId: null,
        assetId: null,
      };
      const active = nodes.pageTab instanceof HTMLElement
        ? nodes.pageTab.getAttribute('aria-selected') === 'true'
        : false;
      const pageSession = documentSessionForPane(paneRole, documentId);
      const dirtyState = documentSessionDirtyState(pageSession);
      const pageTitle = nodes.pageTab instanceof HTMLElement
        ? String(nodes.pageTab.querySelector('.mnote-main-tab-title')?.textContent || '页面').trim()
        : '页面';
      return {
        objectIdentity,
        workspacePath: buildWorkspacePath({
          workspaceId,
          sourceKind,
          rootUri,
          relativePath,
          documentId,
          objectIdentity: workspaceObjectIdentity,
          assetId: '',
          resourceKind: 'page',
        }),
        paneRole,
        documentId,
        workspaceId,
        title: pageTitle || '页面',
        kind: 'page',
        editorKind: 'page',
        badgeKind: 'code',
        active,
        dirtyState,
        dirtyGuard: dirtyState,
        lastActiveAt: active ? generatedAt : 0,
        preview: false,
        pinned: true,
      };
    }).filter((entry) => entry.paneRole === 'primary' || entry.documentId || document.querySelector('[data-document-pane="true"][data-pane-role="secondary"][data-pane-visible="true"]'));
    const resources = [];
    resourceTabRegistry.forEach((entry, key) => {
      resources.push(openEditorsSnapshotEntry(entry, key, generatedAt));
    });
    const editors = [...pageEntries, ...resources];
    const groupForPane = (paneRole) => {
      const role = normalizePaneRole(paneRole);
      const groupEditors = editors.filter((entry) => normalizePaneRole(entry.paneRole) === role);
      const groupResources = resources.filter((entry) => normalizePaneRole(entry.paneRole) === role);
      const groupActive = groupEditors.find((entry) => entry.active);
      return {
        paneRole: role,
        activeObjectIdentity: groupActive?.objectIdentity || '',
        editors: groupEditors,
        resourceEditors: groupResources,
      };
    };
    const groups = {
      primary: groupForPane('primary'),
      secondary: groupForPane('secondary'),
    };
    const activeObjectIdentity = groups.primary.activeObjectIdentity || groups.secondary.activeObjectIdentity || '';
    return {
      schema: 'mnote.open_editors_snapshot.v1',
      generatedAt,
      activeObjectIdentity,
      editors,
      resourceEditors: resources,
      groups,
    };
  };

  const syncOpenEditorsSnapshot = () => {
    const snapshot = buildOpenEditorsSnapshot();
    window.__mnoteOpenEditorsSnapshot = snapshot;
    document.documentElement.setAttribute('data-mnote-open-editors-count', String(snapshot.editors.length));
    document.documentElement.setAttribute('data-mnote-active-editor', snapshot.activeObjectIdentity || '');
    window.dispatchEvent(new CustomEvent('mnote:open-editors-snapshot', { detail: snapshot }));
    return snapshot;
  };

  const showResourceTabCloseGuardNotice = (entry, reason) => {
    const nodes = resourceTabHostNodes(entry?.paneRole || 'primary');
    const messages = {
      dirty: '当前资源有未保存的修改，保存完成后再关闭。',
      saving: '当前资源正在保存中，请稍后再关闭。',
      hasExternalConflict: '当前资源存在外部冲突，请先处理冲突。',
    };
    const message = messages[reason] || '当前资源暂时无法关闭。';
    let notice = document.getElementById('mnote-resource-close-guard-notice');
    if (!notice) {
      notice = document.createElement('div');
      notice.id = 'mnote-resource-close-guard-notice';
      notice.className = 'mnote-close-guard-notice';
      notice.setAttribute('role', 'status');
      notice.setAttribute('aria-live', 'polite');
      notice.setAttribute('data-mnote-resource-close-guard', '');
      const parent = nodes.strip?.parentNode;
      if (parent instanceof HTMLElement) {
        const panels = parent.querySelector('.mnote-main-tab-panels');
        if (panels && panels.parentNode === parent) parent.insertBefore(notice, panels);
        else parent.append(notice);
      }
    }
    notice.textContent = `${entry?.title || '资源'}：${message}`;
    notice.setAttribute('data-mnote-resource-close-guard', reason || 'blocked');
    notice.className = `mnote-close-guard-notice is-${reason || 'blocked'}`;
    if (notice._mnoteHideTimer) window.clearTimeout(notice._mnoteHideTimer);
    notice._mnoteHideTimer = window.setTimeout(() => {
      notice.classList.add('is-hiding');
      window.setTimeout(() => {
        if (notice.parentNode) notice.remove();
      }, 260);
    }, 4000);
  };

  const cssSafe = (value) => {
    const text = String(value || '');
    if (window.CSS && typeof window.CSS.escape === 'function') return window.CSS.escape(text);
    return text.replace(/["\\]/g, '\\$&');
  };

  const syncActiveResourceFileTreeRow = (activeResource) => {
    document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-active="true"]').forEach((row) => {
      if (row instanceof HTMLElement) row.setAttribute('data-active', 'false');
    });
    const entry = activeResource ? resourceTabRegistry.get(activeResource) : null;
    if (!entry) return;
    const assetId = String(entry.assetId || entry.session?.assetId || '').trim();
    const path = String(entry.path || entry.session?.resourcePath || '').trim();
    const identity = String(entry.objectIdentity || activeResource || '').trim();
    if (assetId) {
      const row = document.querySelector(`#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-asset-id="${cssSafe(assetId)}"]`);
      if (row instanceof HTMLElement) {
        row.setAttribute('data-active', 'true');
        return;
      }
    }
    const rows = document.querySelectorAll('#sidebar-file-tree-root .tree-row[data-shell-mode="filetree"][data-object-identity]');
    for (const row of rows) {
      if (!(row instanceof HTMLElement)) continue;
      const objectIdentity = row.getAttribute('data-object-identity') || '';
      if ((identity && objectIdentity.includes(identity)) || (path && objectIdentity.includes(path))) {
        row.setAttribute('data-active', 'true');
        return;
      }
    }
  };

  const syncActiveResourceWidthType = (activeEntry, paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    const widthType = role === 'primary' ? resourceTabWidthType(activeEntry) : '';
    const targetNodes = [document.documentElement, document.body].filter((node) => node instanceof HTMLElement);
    targetNodes.forEach((node) => {
      if (widthType) node.setAttribute('data-mnote-active-resource-width-type', widthType);
      else node.removeAttribute('data-mnote-active-resource-width-type');
    });
    const shell = document.querySelector(`.document-pane[data-pane-role="${role}"] .document-shell`);
    if (shell instanceof HTMLElement) {
      if (widthType) shell.setAttribute('data-mnote-active-resource-width-type', widthType);
      else shell.removeAttribute('data-mnote-active-resource-width-type');
    }
    window.dispatchEvent(new CustomEvent('mnote:active-resource-tab-changed', {
      detail: {
        paneRole: role,
        active: Boolean(activeEntry),
        widthType,
        objectIdentity: String(activeEntry?.objectIdentity || '')
      }
    }));
  };

  const syncActiveResourceUrlState = (activeResource, paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    if (role !== 'primary') return;
    const url = currentUrl();
    if (activeResource) {
      const entry = resourceTabRegistry.get(activeResource);
      const documentId = String(entry?.ownerDocumentId || entry?.documentId || '').trim();
      if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`;
      const sourceKind = String(entry?.sourceKind || currentWebShellSourceKind() || '').trim();
      const rootUri = String(entry?.rootUri || currentWebShellRootUri() || '').trim();
      const resourcePath = String(entry?.path || entry?.session?.resourcePath || '').trim();
      if (sourceKind) url.searchParams.set('sourceKind', sourceKind);
      if (rootUri) url.searchParams.set('rootUri', rootUri);
      if (resourcePath) url.searchParams.set('resourcePath', resourcePath);
      url.searchParams.set('resourceTab', activeResource);
    } else {
      const documentId = currentWebShellDocumentId();
      if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`;
      url.searchParams.delete('resourceTab');
      url.searchParams.delete('resourcePath');
    }
    replaceUrlState(url);
  };

  const activateMainEditorTab = (objectIdentity, paneRole = 'primary') => {
    const activeResource = String(objectIdentity || '').trim();
    const activeEntry = activeResource ? resourceTabRegistry.get(activeResource) : null;
    const role = normalizePaneRole(activeEntry?.paneRole || paneRole);
    const nodes = resourceTabHostNodes(role);
    if (activeResource) {
      touchResourceTabMru(role, activeResource);
      setResourceTabLastActive(activeResource);
    }
    if (nodes.pageTab instanceof HTMLElement) {
      const activePage = !activeResource;
      nodes.pageTab.classList.toggle('is-active', activePage);
      nodes.pageTab.setAttribute('aria-selected', activePage ? 'true' : 'false');
      nodes.pageTab.setAttribute('tabindex', activePage ? '0' : '-1');
    }
    if (nodes.pagePanel instanceof HTMLElement) nodes.pagePanel.hidden = Boolean(activeResource);
    if (nodes.host instanceof HTMLElement) nodes.host.hidden = !activeResource;
    resourceTabRegistry.forEach((entry, key) => {
      if (normalizePaneRole(entry.paneRole) !== role) return;
      const active = key === activeResource;
      if (entry.tab instanceof HTMLElement) {
        entry.tab.classList.toggle('is-active', active);
        entry.tab.setAttribute('aria-selected', active ? 'true' : 'false');
        entry.tab.setAttribute('tabindex', active ? '0' : '-1');
      }
      if (entry.panel instanceof HTMLElement) entry.panel.hidden = !active;
      syncResourceTabCloseGuard(entry);
    });
    syncActiveResourceWidthType(activeEntry, role);
    if (activeEntry) markIntendedSlashRoot(activeEntry);
    if (!activeEntry && window.__mnoteIntendedSlashRoot instanceof HTMLElement) window.__mnoteIntendedSlashRoot = null;
    if (role === 'primary') syncActiveResourceFileTreeRow(activeResource);
    syncActiveResourceUrlState(activeResource, role);
    syncActivePassiveResourceWatch(role, activeResource);
    syncOpenEditorsSnapshot();
  };

  const bindMainEditorTabStrip = (paneRole = 'primary') => {
    const nodes = resourceTabHostNodes(paneRole);
    if (!(nodes.strip instanceof HTMLElement)) return;
    if (nodes.strip.getAttribute('data-mnote-tab-strip-bound') === 'true') return;
    nodes.strip.setAttribute('data-mnote-tab-strip-bound', 'true');
    const collectTabs = () => Array.from(nodes.strip.querySelectorAll('[data-mnote-main-tab]'))
      .filter((tab) => tab instanceof HTMLElement && tab.isConnected);
    const selectedIndex = (tabs) => tabs.findIndex((tab) => tab.getAttribute('aria-selected') === 'true');
    nodes.strip.addEventListener('keydown', (event) => {
      const tabs = collectTabs();
      if (!tabs.length) return;
      const focusedIndex = tabs.findIndex((tab) => tab === document.activeElement);
      const baseIndex = focusedIndex >= 0 ? focusedIndex : Math.max(0, selectedIndex(tabs));
      let targetIndex = -1;
      if (event.key === 'ArrowRight') {
        targetIndex = (baseIndex + 1) % tabs.length;
      } else if (event.key === 'ArrowLeft') {
        targetIndex = (baseIndex - 1 + tabs.length) % tabs.length;
      } else if (event.key === 'Home') {
        targetIndex = 0;
      } else if (event.key === 'End') {
        targetIndex = tabs.length - 1;
      } else if (event.key === 'Enter' || event.key === ' ') {
        event.preventDefault();
        const tab = tabs[baseIndex];
        if (tab instanceof HTMLElement) tab.click();
        return;
      } else {
        return;
      }
      event.preventDefault();
      const target = tabs[targetIndex];
      if (target instanceof HTMLElement) target.focus();
    });
  };

  const bindMainEditorPageTab = (paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    const nodes = resourceTabHostNodes(role);
    if (!(nodes.pageTab instanceof HTMLElement)) return;
    bindMainEditorTabStrip(role);
    if (nodes.pageTab.getAttribute('data-mnote-page-tab-bound') === 'true') return;
    nodes.pageTab.setAttribute('data-mnote-page-tab-bound', 'true');
    nodes.pageTab.addEventListener('click', (event) => {
      const target = event.target;
      if (target instanceof HTMLElement && target.closest('[data-mnote-pane-close="secondary"]')) return;
      event.preventDefault();
      activateMainEditorTab('', role);
    });
    syncOpenEditorsSnapshot();
  };

  const closeResourceTab = async (objectIdentity) => {
    const key = String(objectIdentity || '').trim();
    const entry = resourceTabRegistry.get(key);
    if (!entry) return;
    await refreshResourceTabBufferState(entry);
    const guardReason = resourceTabCloseGuardReason(entry.session);
    if (guardReason) {
      console.warn(`mnote resource tab 关闭阻止: ${entry.title} (${guardReason})`);
      syncResourceTabCloseGuard(entry);
      showResourceTabCloseGuardNotice(entry, guardReason);
      return;
    }
    removeFromResourceTabMru(entry.paneRole || 'primary', key);
    if (entry.view) unmountEditorViewBinding(entry.view, { releaseSession: true });
    if (entry.mindmapRuntime?.runtime && entry.mindmapRuntime?.mountId != null) {
      try {
        entry.mindmapRuntime.runtime.unmount(entry.mindmapRuntime.mountId);
      } catch (error) {
        console.warn('mnote mindmap resource tab unmount failed', error);
      }
    }
    releaseInlinePdfResource(entry);
    if (entry.tab instanceof HTMLElement) entry.tab.remove();
    if (entry.panel instanceof HTMLElement) entry.panel.remove();
    resourceTabRegistry.delete(key);
    const role = normalizePaneRole(entry.paneRole);
    const nextKey = lastActiveResourceTabKey(role);
    activateMainEditorTab(nextKey, role);
    const nextTab = nextKey ? resourceTabRegistry.get(nextKey)?.tab : resourceTabHostNodes(role).pageTab;
    if (nextTab instanceof HTMLElement) nextTab.focus();
  };

  const markResourceTabError = (entry) => {
    if (!entry) return;
    entry.kind = 'error';
    if (entry.tab instanceof HTMLElement) {
      entry.tab.setAttribute('data-mnote-tab-kind', 'error');
      entry.tab.setAttribute('data-mnote-tab-badge-kind', 'file');
      entry.tab.classList.add('is-error');
    }
    if (entry.panel instanceof HTMLElement) {
      entry.panel.setAttribute('data-resource-kind', 'error');
      entry.panel.innerHTML = '<div class="mnote-resource-tab-error" data-resource-tab-error="true"><div class="mnote-resource-tab-error-inner"><h1>资源打开失败</h1><p>无法加载此资源，请检查文件路径和访问权限。</p></div></div>';
    }
  };

  const createResourceTabDom = (input) => {
    const paneRole = normalizePaneRole(input.paneRole);
    const nodes = resourceTabHostNodes(paneRole);
    if (!(nodes.strip instanceof HTMLElement) || !(nodes.panelRoot instanceof HTMLElement)) return null;
    const objectIdentity = String(input.objectIdentity || '').trim();
    const registryKey = resourceTabRegistryKey(paneRole, objectIdentity);
    const title = String(input.title || input.fileName || input.path || '资源').trim() || '资源';
    const kind = normalizeResourceTabKind(input);
    const tab = document.createElement('button');
    tab.type = 'button';
    tab.className = 'mnote-main-tab';
    tab.setAttribute('role', 'tab');
    tab.setAttribute('data-mnote-main-tab', registryKey);
    tab.setAttribute('data-mnote-object-identity', objectIdentity);
    tab.setAttribute('data-pane-role', paneRole);
    tab.setAttribute('data-mnote-tab-kind', kind);
    tab.setAttribute('data-mnote-tab-badge-kind', resourceTabBadgeKind(input, kind));
    tab.setAttribute('tabindex', '-1');
    tab.innerHTML = '<span class="mnote-main-tab-badge" aria-hidden="true"></span><span class="mnote-main-tab-title"></span><span class="mnote-main-tab-close" role="button" aria-label="关闭标签页">×</span>';
    const titleNode = tab.querySelector('.mnote-main-tab-title');
    if (titleNode) titleNode.textContent = title;
    tab.addEventListener('click', (event) => {
      const target = event.target;
      if (target instanceof HTMLElement && target.closest('.mnote-main-tab-close')) {
        event.preventDefault();
        event.stopPropagation();
        closeResourceTab(registryKey);
        return;
      }
      activateMainEditorTab(registryKey, paneRole);
    });
    const panel = document.createElement('section');
    panel.className = 'mnote-resource-tab-panel';
    panel.setAttribute('data-mnote-resource-tab-panel', registryKey);
    panel.setAttribute('data-mnote-object-identity', objectIdentity);
    panel.setAttribute('data-pane-role', paneRole);
    panel.setAttribute('data-resource-kind', kind);
    panel.setAttribute('data-resource-path', String(input.path || '').trim());
    panel.hidden = true;
    nodes.strip.append(tab);
    nodes.panelRoot.append(panel);
    return {
      objectIdentity,
      registryKey,
      paneRole,
      title,
      kind,
      badgeKind: resourceTabBadgeKind(input, kind),
      tab,
      panel,
      view: null,
      session: null,
      assetId: String(input.assetId || '').trim(),
      path: String(input.path || '').trim(),
      documentId: String(input.documentId || '').trim(),
      ownerDocumentId: String(input.ownerDocumentId || input.documentId || '').trim(),
      workspaceId: String(input.workspaceId || currentWebShellWorkspaceId() || '').trim(),
      sourceKind: String(input.sourceKind || currentWebShellSourceKind() || '').trim(),
      rootUri: String(input.rootUri || currentWebShellRootUri() || '').trim(),
      workspacePath: buildWorkspacePath({
        workspacePath: input.workspacePath,
        workspaceId: input.workspaceId || currentWebShellWorkspaceId(),
        sourceKind: input.sourceKind || currentWebShellSourceKind(),
        rootUri: input.rootUri || currentWebShellRootUri(),
        relativePath: input.path,
        documentId: input.documentId,
        objectIdentity,
        assetId: input.assetId,
        resourceKind: kind,
      }),
      lastActiveAt: 0,
    };
  };

  const releaseResourceTabEntryRuntime = (entry) => {
    if (!entry) return;
    releaseInlinePdfResource(entry);
    if (entry.view) {
      unmountEditorViewBinding(entry.view, { releaseSession: true });
      entry.view = null;
      entry.session = null;
    }
    if (entry.mindmapRuntime?.runtime && entry.mindmapRuntime?.mountId != null) {
      try {
        entry.mindmapRuntime.runtime.unmount(entry.mindmapRuntime.mountId);
      } catch (error) {
        console.warn('mnote resource tab runtime unmount failed', error);
      }
      entry.mindmapRuntime = null;
    }
    closePassiveResourceWatch(entry);
    if (entry.panel instanceof HTMLElement) entry.panel.replaceChildren();
  };

  const localResourceReadUrl = (rootUri, path) => {
    const url = new URL('/api/local-folder/resource/read', window.location.origin);
    url.searchParams.set('rootUri', rootUri || '');
    url.searchParams.set('path', path || '');
    return url.toString();
  };

  const localResourceStatUrl = (rootUri, path) => {
    const url = new URL('/api/local-folder/files/stat', window.location.origin);
    url.searchParams.set('rootUri', rootUri || '');
    url.searchParams.set('path', path || '');
    return url.toString();
  };

  const withResourceReloadToken = (href) => {
    if (!href) return href;
    const url = new URL(href, window.location.origin);
    url.searchParams.set('mnoteResourceReload', String(Date.now()));
    return url.toString();
  };

  const normalizeEvidenceBBox = (value) => {
    if (!value) return null;
    if (Array.isArray(value) && value.length >= 4) {
      const values = value.slice(0, 4).map((item) => Number(item));
      return values.every(Number.isFinite) ? { x0: values[0], y0: values[1], x1: values[2], y1: values[3] } : null;
    }
    if (typeof value === 'object') {
      const bbox = {
        x0: Number(value.x0),
        y0: Number(value.y0),
        x1: Number(value.x1),
        y1: Number(value.y1),
      };
      return Object.values(bbox).every(Number.isFinite) ? bbox : null;
    }
    if (typeof value === 'string') {
      const parts = value.split(',').map((item) => Number(item.trim()));
      return parts.length >= 4 && parts.slice(0, 4).every(Number.isFinite)
        ? { x0: parts[0], y0: parts[1], x1: parts[2], y1: parts[3] }
        : null;
    }
    return null;
  };

  const normalizeEvidenceLocatorInput = (input = {}) => {
    const locator = input.evidenceLocator && typeof input.evidenceLocator === 'object'
      ? input.evidenceLocator
      : input.locator && typeof input.locator === 'object'
        ? input.locator
        : null;
    const params = locator?.openAction?.params && typeof locator.openAction.params === 'object' ? locator.openAction.params : {};
    const page = Number(input.page ?? locator?.page ?? params.page);
    const bbox = normalizeEvidenceBBox(input.bbox ?? locator?.bbox ?? params.bbox);
    const sourceMapPath = String(input.sourceMapPath || locator?.sourceMapPath || params.sourceMapPath || '').trim();
    const blockId = String(input.blockId || locator?.blockId || params.blockId || '').trim();
    const evidenceText = String(input.evidenceText || input.query || locator?.evidenceText || locator?.query || params.evidenceText || params.query || '').trim();
    const searchQuery = String(input.searchQuery || locator?.searchQuery || params.searchQuery || '').trim();
    const lineRange = input.lineRange || locator?.lineRange || params.lineRange || null;
    const charRange = input.charRange || locator?.charRange || params.charRange || null;
    const paragraphOrdinal = String(input.paragraphOrdinal ?? locator?.paragraphOrdinal ?? params.paragraphOrdinal ?? '').trim();
    const paraIdStart = String(input.paraIdStart || locator?.paraIdStart || params.paraIdStart || '').trim();
    const paraIdEnd = String(input.paraIdEnd || locator?.paraIdEnd || params.paraIdEnd || '').trim();
    const textFingerprint = String(input.textFingerprint || locator?.textFingerprint || params.textFingerprint || '').trim();
    if (!locator && !Number.isFinite(page) && !bbox && !sourceMapPath && !blockId && !evidenceText && !lineRange && !charRange && !paragraphOrdinal && !paraIdStart && !textFingerprint) return null;
    return {
      schema: 'mnote.evidence_locator.v1',
      ...(locator || {}),
      page: Number.isFinite(page) ? page : null,
      bbox,
      sourceMapPath,
      blockId,
      evidenceText,
      searchQuery,
      lineRange,
      charRange,
      paragraphOrdinal,
      paraIdStart,
      paraIdEnd,
      textFingerprint,
    };
  };

  const evidenceBBoxParam = (bbox) => {
    const normalized = normalizeEvidenceBBox(bbox);
    return normalized ? [normalized.x0, normalized.y0, normalized.x1, normalized.y1].join(',') : '';
  };

  const applyEvidenceLocatorToFrame = (frame, locator) => {
    if (!(frame instanceof HTMLIFrameElement) || !locator) return;
    try {
      const url = new URL(frame.getAttribute('src') || frame.src || '', window.location.origin);
      const locatorPage = Number(locator.page);
      if (Number.isFinite(locatorPage) && locatorPage > 0) url.searchParams.set('page', String(locatorPage));
      const bbox = evidenceBBoxParam(locator.bbox);
      if (bbox) url.searchParams.set('bbox', bbox);
      if (locator.sourceMapPath) url.searchParams.set('sourceMapPath', String(locator.sourceMapPath));
      if (locator.blockId) url.searchParams.set('blockId', String(locator.blockId));
      if (locator.paragraphOrdinal) url.searchParams.set('paragraphOrdinal', String(locator.paragraphOrdinal));
      if (locator.paraIdStart) url.searchParams.set('paraIdStart', String(locator.paraIdStart));
      if (locator.paraIdEnd) url.searchParams.set('paraIdEnd', String(locator.paraIdEnd));
      if (locator.textFingerprint) url.searchParams.set('textFingerprint', String(locator.textFingerprint));
      if (locator.evidenceText) url.searchParams.set('evidenceText', String(locator.evidenceText));
      if (locator.searchQuery) url.searchParams.set('searchQuery', String(locator.searchQuery));
      if (url.pathname === '/office-preview' && frame.contentWindow) {
        const currentSrc = new URL(frame.getAttribute('src') || frame.src || '', window.location.origin);
        if (officePreviewBaseHref(currentSrc.toString()) !== officePreviewBaseHref(url.toString())) {
          frame.src = url.toString();
          return;
        }
        const frameDoc = frame.contentDocument || null;
        const previewReady = frameDoc?.documentElement?.getAttribute('data-mnote-office-preview-status') === '完成'
          || (frameDoc?.readyState === 'complete' && Boolean(frameDoc?.body?.dataset?.fileUrl));
        if (!previewReady) {
          frame.src = url.toString();
          return;
        }
        frame.contentWindow.postMessage({
          type: 'mnote:office-evidence-locator',
          page: locator.page,
          bbox,
          sourceMapPath: locator.sourceMapPath || '',
          blockId: locator.blockId || '',
          paragraphOrdinal: locator.paragraphOrdinal || '',
          paraIdStart: locator.paraIdStart || '',
          paraIdEnd: locator.paraIdEnd || '',
          textFingerprint: locator.textFingerprint || '',
          evidenceText: locator.evidenceText || '',
          searchQuery: locator.searchQuery || '',
        }, window.location.origin);
        return;
      }
      frame.src = url.toString();
    } catch (_) {}
  };

  const applyEvidenceLocatorToImagePanel = (entry, locator) => {
    if (!(entry?.panel instanceof HTMLElement) || !locator) return;
    const bbox = normalizeEvidenceBBox(locator.bbox);
    let overlay = entry.panel.querySelector('[data-mnote-evidence-bbox-highlight]');
    if (!bbox) {
      if (overlay instanceof HTMLElement) overlay.hidden = true;
      return;
    }
    if (!(overlay instanceof HTMLElement)) {
      overlay = document.createElement('div');
      overlay.className = 'mnote-resource-tab-bbox-highlight';
      overlay.setAttribute('data-mnote-evidence-bbox-highlight', 'true');
      entry.panel.append(overlay);
    }
    overlay.hidden = false;
    overlay.style.left = `${Math.max(0, bbox.x0)}px`;
    overlay.style.top = `${Math.max(0, bbox.y0)}px`;
    overlay.style.width = `${Math.max(1, bbox.x1 - bbox.x0)}px`;
    overlay.style.height = `${Math.max(1, bbox.y1 - bbox.y0)}px`;
  };

  const applyPdfEvidenceOverlayToCanvas = (canvas, locator) => {
    if (!(canvas instanceof HTMLCanvasElement)) return null;
    const frame = canvas.parentElement instanceof HTMLElement && canvas.parentElement.hasAttribute('data-mnote-pdf-page-frame')
      ? canvas.parentElement
      : null;
    if (!(frame instanceof HTMLElement)) return null;
    const bbox = normalizeEvidenceBBox(locator?.bbox);
    let overlay = frame.querySelector('[data-mnote-evidence-bbox-highlight]');
    if (!bbox) {
      if (overlay instanceof HTMLElement) overlay.hidden = true;
      return null;
    }
    const viewport = canvas.__mnotePdfViewport;
    if (!viewport || typeof viewport.convertToViewportRectangle !== 'function') return null;
    if (!(overlay instanceof HTMLElement)) {
      overlay = document.createElement('div');
      overlay.className = 'mnote-resource-tab-bbox-highlight';
      overlay.setAttribute('data-mnote-evidence-bbox-highlight', 'true');
      frame.append(overlay);
    }
    const normalizedMineruBox = bbox.x0 >= 0 && bbox.y0 >= 0 && bbox.x1 <= 1000 && bbox.y1 <= 1000;
    let x;
    let y;
    let width;
    let height;
    if (normalizedMineruBox) {
      x = Math.min(bbox.x0, bbox.x1) / 1000 * viewport.width;
      y = Math.min(bbox.y0, bbox.y1) / 1000 * viewport.height;
      width = Math.max(1, Math.abs(bbox.x1 - bbox.x0) / 1000 * viewport.width);
      height = Math.max(1, Math.abs(bbox.y1 - bbox.y0) / 1000 * viewport.height);
    } else {
      const rect = viewport.convertToViewportRectangle([bbox.x0, bbox.y0, bbox.x1, bbox.y1]);
      x = Math.min(rect[0], rect[2]);
      y = Math.min(rect[1], rect[3]);
      width = Math.max(1, Math.abs(rect[2] - rect[0]));
      height = Math.max(1, Math.abs(rect[3] - rect[1]));
    }
    overlay.hidden = false;
    overlay.style.left = `${Math.max(0, x)}px`;
    overlay.style.top = `${Math.max(0, y)}px`;
    overlay.style.width = `${width}px`;
    overlay.style.height = `${height}px`;
    return overlay;
  };

  const scrollPdfEvidenceTargetIntoView = (canvas, overlay) => {
    const target = overlay instanceof HTMLElement && !overlay.hidden
      ? overlay
      : (canvas?.parentElement instanceof HTMLElement ? canvas.parentElement : canvas);
    if (!(target instanceof HTMLElement)) return;
    window.setTimeout(() => target.scrollIntoView({ block: 'center', inline: 'nearest' }), 0);
  };

  const applyEvidenceLocatorToTextPanel = (entry, locator) => {
    if (!(entry?.panel instanceof HTMLElement) || !locator) return;
    const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
    if (!(root instanceof HTMLElement)) return;
    const selector = locator.blockId ? `[data-block-id="${cssSafe(locator.blockId)}"]` : '';
    let target = selector ? root.querySelector(selector) : null;
    const query = String(locator?.openAction?.params?.query || locator?.query || '').trim();
    if (!(target instanceof HTMLElement) && query) {
      target = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]'))
        .find((node) => node instanceof HTMLElement && String(node.textContent || '').includes(query)) || null;
    }
    const lineStart = Number(locator?.lineRange?.start ?? locator?.line_range?.start ?? 0);
    if (!(target instanceof HTMLElement) && Number.isFinite(lineStart) && lineStart > 0) {
      const blocks = Array.from(root.querySelectorAll('.ProseMirror [data-block-id]'))
        .filter((node) => node instanceof HTMLElement);
      target = blocks[Math.max(0, Math.min(blocks.length - 1, lineStart - 1))] || null;
    }
    if (!(target instanceof HTMLElement)) return;
    root.querySelectorAll('[data-mnote-evidence-text-highlight="true"]').forEach((node) => {
      if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-text-highlight');
    });
    target.setAttribute('data-mnote-evidence-text-highlight', 'true');
    target.scrollIntoView({ block: 'center', inline: 'nearest' });
  };

  const applyEvidenceLocatorToInlinePdfPanel = (entry, locator) => {
    if (!(entry?.panel instanceof HTMLElement) || !locator) return;
    const pageNumber = Number(locator.page || 0);
    if (!Number.isFinite(pageNumber) || pageNumber <= 0) return;
    const canvas = entry.panel.querySelector(`canvas.mnote-pdf-page[data-page-number="${pageNumber}"]`);
    if (!(canvas instanceof HTMLCanvasElement)) return;
    entry.panel.querySelectorAll('canvas.mnote-pdf-page[data-mnote-evidence-page="true"]').forEach((node) => {
      if (node instanceof HTMLElement) node.removeAttribute('data-mnote-evidence-page');
    });
    entry.panel.querySelectorAll('[data-mnote-evidence-bbox-highlight]').forEach((node) => {
      if (node instanceof HTMLElement) node.hidden = true;
    });
    canvas.setAttribute('data-mnote-evidence-page', 'true');
    const overlay = applyPdfEvidenceOverlayToCanvas(canvas, locator);
    scrollPdfEvidenceTargetIntoView(canvas, overlay);
  };

  const applyEvidenceLocatorToEntry = (entry, input = {}) => {
    if (!(entry?.panel instanceof HTMLElement)) return;
    const locator = normalizeEvidenceLocatorInput(input);
    if (!locator) return;
    entry.evidenceLocator = locator;
    entry.panel.setAttribute('data-mnote-evidence-locator', JSON.stringify(locator));
    entry.panel.setAttribute('data-mnote-evidence-open', 'true');
    const locatorPage = Number(locator.page);
    if (Number.isFinite(locatorPage) && locatorPage > 0) entry.panel.setAttribute('data-mnote-evidence-page', String(locatorPage));
    if (locator.blockId) entry.panel.setAttribute('data-mnote-evidence-block-id', String(locator.blockId));
    if (locator.evidenceText) entry.panel.setAttribute('data-mnote-evidence-text', String(locator.evidenceText).slice(0, 240));
    if (locator.sourceMapPath) entry.panel.setAttribute('data-mnote-evidence-source-map-path', String(locator.sourceMapPath));
    const bbox = evidenceBBoxParam(locator.bbox);
    if (bbox) entry.panel.setAttribute('data-mnote-evidence-bbox', bbox);
    const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
    if (frame instanceof HTMLIFrameElement) applyEvidenceLocatorToFrame(frame, locator);
    if (entry.kind === 'image') applyEvidenceLocatorToImagePanel(entry, locator);
    if (entry.kind === 'pdf') applyEvidenceLocatorToInlinePdfPanel(entry, locator);
    if (entry.kind === 'markdown' || entry.kind === 'text' || entry.kind === 'code') {
      window.setTimeout(() => applyEvidenceLocatorToTextPanel(entry, locator), 80);
      window.setTimeout(() => applyEvidenceLocatorToTextPanel(entry, locator), 450);
    }
  };

  const renderPassiveResourceMissing = (entry, message) => {
    if (!(entry?.panel instanceof HTMLElement)) return;
    entry.panel.setAttribute('data-mnote-resource-missing', 'true');
    entry.panel.innerHTML = '<div class="mnote-resource-tab-error" data-resource-tab-error="true" data-mnote-resource-missing="true"><div class="mnote-resource-tab-error-inner"><h1>资源已不可用</h1><p></p></div></div>';
    const text = entry.panel.querySelector('p');
    if (text instanceof HTMLElement) text.textContent = message || '本地资源文件已被删除或移动。';
  };

  const reloadPassiveResourceTab = (entry) => {
    if (!(entry?.panel instanceof HTMLElement)) return;
    entry.panel.removeAttribute('data-mnote-resource-missing');
    const img = entry.panel.querySelector('img.mnote-resource-tab-image');
    if (img instanceof HTMLImageElement) {
      img.src = withResourceReloadToken(img.getAttribute('src') || img.src || '');
      return;
    }
    const frame = entry.panel.querySelector('iframe.mnote-resource-tab-frame');
    if (frame instanceof HTMLIFrameElement) {
      frame.src = withResourceReloadToken(frame.getAttribute('src') || frame.src || '');
      return;
    }
    if (entry.kind === 'pdf' && entry.inlinePdfSourceHref) {
      void openPassiveResourceTab(entry, { ...(entry.lastPassiveInput || {}), href: withResourceReloadToken(entry.inlinePdfSourceHref) });
    }
  };

  const closePassiveResourceWatch = (entry) => {
    if (!entry?.resourceWatchEventSource) return;
    try {
      entry.resourceWatchEventSource.close();
    } catch (_) {}
    entry.resourceWatchEventSource = null;
    if (entry.panel instanceof HTMLElement) entry.panel.removeAttribute('data-mnote-resource-watch-ready');
  };

  const isActiveResourceTabEntry = (entry) => {
    if (!(entry?.panel instanceof HTMLElement)) return false;
    if (entry.panel.hidden === false) return true;
    return entry.tab instanceof HTMLElement && entry.tab.getAttribute('aria-selected') === 'true';
  };

  const closeInactivePassiveResourceWatches = (activeEntry) => {
    const role = normalizePaneRole(activeEntry?.paneRole || 'primary');
    resourceTabRegistry.forEach((entry) => {
      if (entry === activeEntry) return;
      if (normalizePaneRole(entry?.paneRole) !== role) return;
      closePassiveResourceWatch(entry);
    });
  };

  const resourceChangedPathsFromDetail = (detail) => {
    const payload = detail?.payload && typeof detail.payload === 'object' ? detail.payload : detail;
    const changedPaths = Array.isArray(detail?.changedPaths)
      ? detail.changedPaths
      : (Array.isArray(payload?.changedPaths) ? payload.changedPaths : []);
    return changedPaths.map((item) => {
      if (typeof item === 'string') return item.trim();
      if (!item || typeof item !== 'object') return '';
      return String(item.relativePath || item.relative_path || item.path || '').trim();
    }).filter(Boolean);
  };

  const resourceEventMatchesPath = (detail, rootUri, path) => {
    const eventRootUri = String(detail?.rootUri || detail?.payload?.rootUri || '').trim();
    if (eventRootUri && eventRootUri !== rootUri) return false;
    const changedPaths = resourceChangedPathsFromDetail(detail);
    return changedPaths.length === 0 || changedPaths.includes(path);
  };

  const refreshPassiveResourceEntryFromWatch = async (entry, rootUri, path) => {
    try {
      const response = await fetch(localResourceStatUrl(rootUri, path), {
        cache: 'no-store',
        headers: { accept: 'application/json' },
      });
      const payload = await response.json().catch(() => null);
      const exists = Boolean(response.ok && payload?.ok === true && payload?.result?.exists);
      if (!exists) {
        renderPassiveResourceMissing(entry, '本地资源文件已被删除或移动。');
        return;
      }
      reloadPassiveResourceTab(entry);
    } catch (error) {
      renderPassiveResourceMissing(entry, error instanceof Error ? error.message : String(error));
    }
  };

  const installPassiveResourceWatch = (entry) => {
    if (!entry || entry.session) return;
    const rootUri = String(entry.rootUri || '').trim();
    const path = String(entry.path || '').trim();
    if (!rootUri || !path) return;
    if (!isActiveResourceTabEntry(entry)) {
      closePassiveResourceWatch(entry);
      return;
    }
    closeInactivePassiveResourceWatches(entry);
    if (entry.resourceWatchEventSource) {
      try {
        entry.resourceWatchEventSource.close();
      } catch (_) {}
      entry.resourceWatchEventSource = null;
    }
    const eventBus = window.__mnoteLocalFolderEventBus;
    if (eventBus && typeof eventBus.startLocalFolderWatcher === 'function') {
      const workspaceId = String(currentWebShellWorkspaceId() || '').trim();
      eventBus.startLocalFolderWatcher({
        rootUri,
        workspaceId,
        bootstrap: {
          schema: 'mnote.resource_tab.local_folder_event_bus.v1',
          transport: 'local-folder-events',
          workspaceId,
        },
      });
      const handler = (event) => {
        const detail = event?.detail || {};
        if (!resourceEventMatchesPath(detail, rootUri, path)) return;
        void refreshPassiveResourceEntryFromWatch(entry, rootUri, path);
      };
      window.addEventListener('mnote:local-folder:resource-changed', handler);
      entry.resourceWatchEventSource = {
        close: () => window.removeEventListener('mnote:local-folder:resource-changed', handler),
      };
      if (entry.panel instanceof HTMLElement) entry.panel.setAttribute('data-mnote-resource-watch-ready', 'event-bus');
      return;
    }
    if (typeof window.EventSource !== 'function') return;
    const url = new URL('/api/local-folder/events', window.location.origin);
    url.searchParams.set('rootUri', rootUri);
    url.searchParams.set('resourcePath', path);
    const eventSource = new EventSource(url.toString());
    entry.resourceWatchEventSource = eventSource;
    eventSource.addEventListener('ready', () => {
      if (entry.panel instanceof HTMLElement) entry.panel.setAttribute('data-mnote-resource-watch-ready', 'true');
    });
    eventSource.addEventListener('change', async () => {
      await refreshPassiveResourceEntryFromWatch(entry, rootUri, path);
    });
  };

  const syncActivePassiveResourceWatch = (paneRole, activeResource) => {
    const activeEntry = activeResource ? resourceTabRegistry.get(activeResource) : null;
    if (!activeEntry) {
      resourceTabRegistry.forEach((entry) => {
        if (normalizePaneRole(entry?.paneRole) === normalizePaneRole(paneRole)) closePassiveResourceWatch(entry);
      });
      return;
    }
    installPassiveResourceWatch(activeEntry);
  };

  const releaseInlinePdfResource = (entry) => {
    if (!entry) return;
    const pdf = entry.inlinePdfDocument;
    entry.inlinePdfDocument = null;
    entry.inlinePdfRenderToken = null;
    if (!pdf) return;
    try {
      void pdf.destroy();
    } catch (_) {}
  };

  const pdfFileUrlFromPreviewHref = (href) => {
    const value = String(href || '').trim();
    if (!value) return '';
    try {
      const url = new URL(value, window.location.origin);
      return String(url.searchParams.get('fileUrl') || value).trim();
    } catch (_) {
      return value;
    }
  };

  const renderInlinePdfPage = async (entry, viewer, pdf, pageNumber, evidenceLocator) => {
    const page = await pdf.getPage(pageNumber);
    const baseViewport = page.getViewport({ scale: 1 });
    const availableWidth = Math.max(280, viewer.clientWidth - 20);
    const scale = Math.max(0.6, Math.min(2.4, availableWidth / baseViewport.width));
    const viewport = page.getViewport({ scale });
    const outputScale = Math.min(2.5, Math.max(2, window.devicePixelRatio || 1));
    const frame = document.createElement('div');
    frame.setAttribute('data-mnote-pdf-page-frame', 'true');
    frame.style.position = 'relative';
    frame.style.width = `${Math.floor(viewport.width)}px`;
    frame.style.maxWidth = '100%';
    frame.style.margin = '0 auto 14px';
    const canvas = document.createElement('canvas');
    canvas.className = 'mnote-pdf-page';
    canvas.setAttribute('data-page-number', String(pageNumber));
    canvas.__mnotePdfViewport = viewport;
    canvas.width = Math.floor(viewport.width * outputScale);
    canvas.height = Math.floor(viewport.height * outputScale);
    canvas.style.display = 'block';
    canvas.style.maxWidth = '100%';
    canvas.style.width = `${Math.floor(viewport.width)}px`;
    canvas.style.height = `${Math.floor(viewport.height)}px`;
    canvas.style.margin = '0';
    canvas.style.background = '#fff';
    canvas.style.border = '1px solid #d8d8d2';
    canvas.style.boxShadow = '0 2px 10px rgba(25, 25, 22, .08)';
    const context = canvas.getContext('2d', { alpha: false });
    if (!context) return;
    await page.render({
      canvasContext: context,
      viewport,
      transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null,
    }).promise;
    const evidencePage = Number(evidenceLocator?.page || 0);
    if (evidencePage === pageNumber) {
      canvas.setAttribute('data-mnote-evidence-page', 'true');
    }
    frame.append(canvas);
    let evidenceOverlay = null;
    if (evidencePage === pageNumber) evidenceOverlay = applyPdfEvidenceOverlayToCanvas(canvas, evidenceLocator);
    if (entry.inlinePdfDocument === pdf) viewer.append(frame);
    if (evidencePage === pageNumber) scrollPdfEvidenceTargetIntoView(canvas, evidenceOverlay);
  };

  const openInlinePdfResourceTab = async (entry, input) => {
    const href = String(input.officeUrl || input.href || '').trim();
    const fileUrl = pdfFileUrlFromPreviewHref(href);
    if (!(entry?.panel instanceof HTMLElement) || !fileUrl) return false;
    releaseInlinePdfResource(entry);
    const renderToken = {};
    entry.passiveFrameSrc = href;
    entry.inlinePdfSourceHref = href;
    entry.inlinePdfRenderToken = renderToken;
    entry.lastPassiveInput = { ...input };
    entry.panel.replaceChildren();
    const viewer = document.createElement('div');
    viewer.className = 'mnote-pdf-viewer';
    viewer.setAttribute('data-mnote-inline-pdf-viewer', 'true');
    viewer.style.width = '100%';
    viewer.style.maxWidth = '1180px';
    viewer.style.margin = '0 auto';
    viewer.style.padding = '8px 12px 28px';
    entry.panel.append(viewer);
    const pdfjsLib = await import('/api/pdfjs/pdf.mjs');
    pdfjsLib.GlobalWorkerOptions.workerSrc = '/api/pdfjs/pdf.worker.mjs';
    const sameOrigin = fileUrl.startsWith('/') || fileUrl.startsWith(window.location.origin);
    const pdf = await pdfjsLib.getDocument({ url: fileUrl, withCredentials: sameOrigin, disableWorker: true }).promise;
    entry.inlinePdfDocument = pdf;
    const total = Number(pdf.numPages || 0);
    viewer.setAttribute('data-mnote-pdf-status', `0 / ${total}`);
    const evidenceLocator = normalizeEvidenceLocatorInput(input);
    for (let pageNumber = 1; pageNumber <= total; pageNumber += 1) {
      if (entry.inlinePdfDocument !== pdf || entry.inlinePdfRenderToken !== renderToken) return true;
      await renderInlinePdfPage(entry, viewer, pdf, pageNumber, evidenceLocator);
      viewer.setAttribute('data-mnote-pdf-status', `${pageNumber} / ${total}`);
    }
    installPassiveResourceWatch(entry);
    return true;
  };

  const isKnowledgeRagSourceEntry = (entry) => {
    if (!entry || String(entry.sourceKind || '').trim() !== 'local_folder') return false;
    if (!String(entry.rootUri || '').trim() || !String(entry.path || '').trim()) return false;
    return entry.kind === 'image' || entry.kind === 'pdf';
  };

  const knowledgeRagProvider = () => {
    return 'lightrag';
  };

  const setKnowledgeRagStatus = (entry, status, message, job) => {
    if (!(entry?.panel instanceof HTMLElement)) return;
    const normalizedStatus = String(status || '').trim() || 'unknown';
    entry.knowledgeRagJob = job && typeof job === 'object' ? job : entry.knowledgeRagJob || null;
    entry.panel.setAttribute('data-mnote-knowledge-rag-status', normalizedStatus);
    if (entry.knowledgeRagJob?.artifactRootRelativePath) {
      entry.panel.setAttribute('data-mnote-knowledge-rag-path', String(entry.knowledgeRagJob.artifactRootRelativePath));
    }
    const statusNode = entry.panel.querySelector('[data-mnote-knowledge-rag-status-text]');
    if (statusNode instanceof HTMLElement) {
      statusNode.textContent = message || (
        normalizedStatus === 'done' ? '资料库已索引'
          : normalizedStatus === 'running' ? '资料库索引中'
            : normalizedStatus === 'failed' ? '资料库索引失败'
              : normalizedStatus === 'stale' ? '资料库需更新'
                : '资料库未索引'
      );
    }
    const openButton = entry.panel.querySelector('[data-mnote-knowledge-rag-action="open"]');
    if (openButton instanceof HTMLButtonElement) openButton.disabled = true;
    const insertButton = entry.panel.querySelector('[data-mnote-knowledge-rag-action="insert"]');
    if (insertButton instanceof HTMLButtonElement) insertButton.disabled = true;
    window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-job-updated', {
      detail: {
        status: normalizedStatus,
        job: entry.knowledgeRagJob || null,
        rootUri: entry.rootUri || '',
        sourceRootRelativePath: entry.path || '',
      },
    }));
  };

  const knowledgeRagJobFromRegistryEntry = (registryEntry, rootUri) => {
    if (!registryEntry || typeof registryEntry !== 'object') return null;
    const sourceRootRelativePath = String(registryEntry.sourceRootRelativePath || registryEntry.source_path || '').trim();
    if (!sourceRootRelativePath) return null;
    const indexed = Boolean(registryEntry.indexedAtMs || registryEntry.indexed_at_ms) && Boolean(registryEntry.lightRagDocId || registryEntry.light_rag_doc_id);
    const failed = registryEntry.deletedAtMs != null || registryEntry.deleted_at_ms != null || registryEntry.stale === true;
    const status = failed ? 'failed' : indexed ? 'done' : 'running';
    return {
      taskKind: 'knowledge_rag',
      jobId: String(registryEntry.sourceId || registryEntry.source_id || `knowledge-rag:${sourceRootRelativePath}`),
      sourceRootRelativePath,
      rootUri: String(registryEntry.rootUri || rootUri || '').trim(),
      artifactRootRelativePath: '',
      provider: 'lightrag',
      status,
      stageLabel: status === 'done' ? '资料库已索引' : status === 'failed' ? '资料库需重建' : '资料库索引中',
      stale: registryEntry.stale === true,
      updatedAtMs: Number(registryEntry.updatedAtMs || registryEntry.updated_at_ms || Date.now()),
      finishedAtMs: status === 'done' ? Number(registryEntry.indexedAtMs || registryEntry.indexed_at_ms || Date.now()) : null,
      error: status === 'failed' ? 'knowledge_rag_source_stale_or_deleted' : '',
      lightRagDocId: String(registryEntry.lightRagDocId || registryEntry.light_rag_doc_id || ''),
      lightRagFilePath: String(registryEntry.lightRagFilePath || registryEntry.light_rag_file_path || ''),
    };
  };

  const knowledgeRagRegistryEntries = (payload) => {
    const registry = payload && payload.registry && typeof payload.registry === 'object' ? payload.registry : null;
    return Array.isArray(registry?.entries) ? registry.entries : [];
  };

  const readKnowledgeRagStatus = async (entry) => {
    if (!isKnowledgeRagSourceEntry(entry)) return null;
    const url = new URL('/api/knowledge-rag/status', window.location.origin);
    url.searchParams.set('rootUri', entry.rootUri);
    const workspaceId = String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim();
    if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
    const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) return null;
    const targetPath = String(entry.path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
    const registryEntry = knowledgeRagRegistryEntries(payload).find((candidate) => {
      return String(candidate?.sourceRootRelativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '') === targetPath;
    });
    return knowledgeRagJobFromRegistryEntry(registryEntry, entry.rootUri);
  };

  const knowledgeRagTaskState = {
    rootUri: '',
    jobsBySource: new Map(),
    drawerOpen: false,
    taskFilter: 'active',
    eventSource: null,
  };

  const knowledgeRagTaskKey = (job) => String(job?.sourceRootRelativePath || job?.jobId || '').trim();

  const updateKnowledgeRagTaskState = (job) => {
    const key = knowledgeRagTaskKey(job);
    if (!key) return;
    if (String(job?.status || '').trim() === 'deleted') {
      knowledgeRagTaskState.jobsBySource.delete(key);
      renderKnowledgeRagTaskDock();
      return;
    }
    knowledgeRagTaskState.jobsBySource.set(key, job);
    renderKnowledgeRagTaskDock();
  };

  const bindKnowledgeRagTopbarAction = () => {
    const toggle = document.querySelector('[data-testid="mnote-knowledge-rag-task-toggle"]');
    if (!(toggle instanceof HTMLButtonElement)) return null;
    if (toggle.getAttribute('data-mnote-knowledge-rag-bound') === 'true') return toggle;
    toggle.setAttribute('data-mnote-knowledge-rag-bound', 'true');
    toggle.addEventListener('click', (event) => {
      if (toggle.getAttribute('data-mnote-action') === 'open-knowledge-rag-settings') {
        event.preventDefault();
        window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
        return;
      }
      void runManualKnowledgeRagForActiveTarget(toggle).catch((error) => {
        console.warn('mnote Knowledge RAG 手动入口失败', error);
      });
    });
    return toggle;
  };

  const knowledgeRagTaskSnapshotFromEntry = (entry, status, provider, message, timestamp = Date.now()) => {
    const sourceRootRelativePath = String(entry?.path || '').trim();
    return {
      jobId: `knowledge-rag-${status}:${sourceRootRelativePath || 'unknown'}:${timestamp}`,
      ownerDocumentId: String(entry?.ownerDocumentId || entry?.documentId || currentWebShellDocumentId() || '').trim(),
      sourceRootRelativePath,
      rootUri: String(entry?.rootUri || knowledgeRagTaskState.rootUri || '').trim(),
      artifactRootRelativePath: '',
      provider: String(provider || knowledgeRagProvider()),
      status,
      stageLabel: message || statusTextForKnowledgeRagTask({ status }),
      stale: false,
      updatedAtMs: timestamp,
      finishedAtMs: status === 'failed' ? timestamp : null,
      error: status === 'failed' ? String(message || '') : '',
    };
  };

  const statusTextForKnowledgeRagTask = (job) => {
    const status = String(job?.status || '').trim();
    if (job?.stageLabel) return String(job.stageLabel);
    if (job?.taskKind === 'local_index' || job?.taskKind === 'knowledge_rag') {
      return status === 'done' ? '索引已完成'
        : status === 'failed' ? '索引失败'
          : status === 'running' ? '索引中'
            : status || '未知';
    }
    return status === 'done' ? '已完成'
      : status === 'failed' ? '任务失败'
        : status === 'stale' ? '来源已变化'
          : status === 'running' ? '处理中'
            : status || '未知';
  };

  const knowledgeRagDisplayStatus = (job, fallback = 'done') => {
    const status = String(job?.status || fallback).trim() || fallback;
    return job?.stale === true && status === 'done' ? 'stale' : status;
  };

  const knowledgeRagTaskCategory = (job) => {
    const status = String(job?.status || '').trim();
    if (['failed', 'stale', 'retry_scheduled'].includes(status)) return 'attention';
    if (['done', 'succeeded', 'success'].includes(status)) return 'completed';
    return 'active';
  };

  const knowledgeRagTaskProgress = (job) => {
    const current = Number(job?.progressCurrent ?? job?.currentProgress);
    const total = Number(job?.progressTotal ?? job?.maxProgress);
    if (Number.isFinite(current) && Number.isFinite(total) && total > 0) {
      return Math.max(0, Math.min(100, Math.round((current / total) * 100)));
    }
    return null;
  };

  const knowledgeRagTaskFilterLabel = (filter) => {
    return filter === 'active' ? '进行中'
      : filter === 'completed' ? '已完成'
        : filter === 'attention' ? '需处理'
          : '全部';
  };

  const ensureKnowledgeRagTaskDock = () => {
    let dock = document.querySelector('[data-testid="mnote-knowledge-rag-task-dock"]');
    if (!(dock instanceof HTMLElement)) {
      dock = document.createElement('section');
      dock.className = 'mnote-knowledge-rag-task-dock';
      dock.setAttribute('data-testid', 'mnote-knowledge-rag-task-dock');
      dock.innerHTML = '<div class="mnote-knowledge-rag-task-drawer" data-testid="mnote-knowledge-rag-task-drawer" hidden><div class="mnote-knowledge-rag-task-panel"><div class="mnote-knowledge-rag-task-head"><div><strong>后台任务</strong><span data-mnote-knowledge-rag-task-summary>暂无任务</span></div><button type="button" class="mnote-knowledge-rag-task-close" data-mnote-knowledge-rag-task-close aria-label="收起后台任务">×</button></div><div class="mnote-knowledge-rag-task-tabs" data-mnote-knowledge-rag-task-tabs></div><div class="mnote-knowledge-rag-task-toolbar"><span data-mnote-knowledge-rag-task-filter-label>进行中</span><button type="button" data-mnote-knowledge-rag-task-clear-completed>清除已完成</button></div><div class="mnote-knowledge-rag-task-list" data-testid="mnote-knowledge-rag-task-list"></div></div></div>';
      document.body.appendChild(dock);
    }
    let toggle = document.querySelector('[data-testid="mnote-knowledge-rag-task-toggle"]');
    if (toggle instanceof HTMLButtonElement) bindKnowledgeRagTopbarAction();
    if (dock.getAttribute('data-mnote-knowledge-rag-bound') === 'true') return dock;
    dock.setAttribute('data-mnote-knowledge-rag-bound', 'true');
    dock.addEventListener('click', (event) => {
      const closeButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-close]') : null;
      if (closeButton instanceof HTMLElement) {
        knowledgeRagTaskState.drawerOpen = false;
        renderKnowledgeRagTaskDock();
        return;
      }
      const clearButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-clear]') : null;
      if (clearButton instanceof HTMLElement) {
        const sourcePath = clearButton.getAttribute('data-mnote-knowledge-rag-task-clear') || '';
        if (sourcePath) {
          knowledgeRagTaskState.jobsBySource.delete(sourcePath);
          renderKnowledgeRagTaskDock();
        }
        return;
      }
      const tabButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-tab]') : null;
      if (tabButton instanceof HTMLElement) {
        knowledgeRagTaskState.taskFilter = tabButton.getAttribute('data-mnote-knowledge-rag-task-tab') || 'active';
        renderKnowledgeRagTaskDock();
        return;
      }
      const clearCompleted = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-clear-completed]') : null;
      if (clearCompleted instanceof HTMLElement) {
        Array.from(knowledgeRagTaskState.jobsBySource.entries()).forEach(([key, job]) => {
          if (knowledgeRagTaskCategory(job) === 'completed') knowledgeRagTaskState.jobsBySource.delete(key);
        });
        renderKnowledgeRagTaskDock();
        return;
      }
      const deleteButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-delete]') : null;
      if (deleteButton instanceof HTMLElement) {
        const sourcePath = deleteButton.getAttribute('data-mnote-knowledge-rag-task-delete') || '';
        if (sourcePath) {
          void deleteKnowledgeRagSourceTask(sourcePath).catch((error) => {
            const message = error instanceof Error ? error.message : String(error);
            document.documentElement.setAttribute('data-mnote-knowledge-rag-delete-error', message);
            console.warn('mnote Knowledge RAG 删除失败', error);
          });
        }
        return;
      }
      const openButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-open]') : null;
      if (openButton instanceof HTMLElement) {
        window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
      }
      const retryButton = event.target instanceof Element ? event.target.closest('[data-mnote-knowledge-rag-task-retry]') : null;
      if (retryButton instanceof HTMLElement) {
        const sourcePath = retryButton.getAttribute('data-mnote-knowledge-rag-task-retry') || '';
        const job = knowledgeRagTaskState.jobsBySource.get(sourcePath) || null;
        if (job) {
          const entry = {
            sourceKind: 'local_folder',
            rootUri: knowledgeRagTaskState.rootUri,
            path: sourcePath,
            kind: sourcePath.toLowerCase().endsWith('.pdf') ? 'pdf' : 'image',
            title: sourcePath.split('/').filter(Boolean).pop() || sourcePath,
            documentId: job.ownerDocumentId || currentWebShellDocumentId() || '',
            ownerDocumentId: job.ownerDocumentId || currentWebShellDocumentId() || '',
            workspaceId: currentWebShellWorkspaceId() || '',
            knowledgeRagJob: job,
          };
          void createKnowledgeRagIngestTask(entry).catch((error) => console.warn('mnote Knowledge RAG 任务重试失败', error));
        }
      }
    });
    return dock;
  };

  const renderKnowledgeRagTaskDock = () => {
    const dock = ensureKnowledgeRagTaskDock();
    const jobs = Array.from(knowledgeRagTaskState.jobsBySource.values())
      .sort((left, right) => Number(right.updatedAtMs || 0) - Number(left.updatedAtMs || 0));
    const activeJobs = jobs.filter((job) => knowledgeRagTaskCategory(job) === 'active');
    const attentionJobs = jobs.filter((job) => knowledgeRagTaskCategory(job) === 'attention');
    const completedJobs = jobs.filter((job) => knowledgeRagTaskCategory(job) === 'completed');
    const runningCount = activeJobs.length;
    const taskToggles = Array.from(document.querySelectorAll('[data-testid="mnote-knowledge-rag-task-toggle"], [data-testid="mnote-floating-task-toggle"]'))
      .filter((node) => node instanceof HTMLButtonElement);
    taskToggles.forEach((toggle) => {
      const opensSettings = toggle.getAttribute('data-mnote-action') === 'open-knowledge-rag-settings';
      const label = runningCount > 0 ? `${runningCount} 个资料库索引处理中，打开资料库设置` : (jobs.length > 0 ? `${jobs.length} 个资料库任务，打开资料库设置` : '资料库设置');
      const taskLabel = runningCount > 0 ? `${runningCount} 个后台任务正在运行` : (jobs.length > 0 ? `${jobs.length} 个后台任务` : '后台任务');
      toggle.setAttribute('title', opensSettings ? label : taskLabel);
      toggle.setAttribute('aria-label', opensSettings ? label : taskLabel);
      toggle.setAttribute('aria-expanded', knowledgeRagTaskState.drawerOpen ? 'true' : 'false');
      toggle.classList.toggle('has-mnote-knowledge-rag-tasks', jobs.length > 0);
      const badge = toggle.querySelector('[data-mnote-knowledge-rag-task-count]');
      if (badge instanceof HTMLElement) {
        badge.textContent = String(runningCount > 0 ? runningCount : jobs.length);
        badge.hidden = jobs.length === 0;
      } else {
        toggle.textContent = runningCount > 0 ? `索引 ${runningCount} 处理中` : `索引 ${jobs.length}`;
      }
    });
    const drawer = dock.querySelector('[data-testid="mnote-knowledge-rag-task-drawer"]');
    if (drawer instanceof HTMLElement) drawer.hidden = !knowledgeRagTaskState.drawerOpen;
    const summary = dock.querySelector('[data-mnote-knowledge-rag-task-summary]');
    if (summary instanceof HTMLElement) {
      summary.textContent = `${runningCount} 进行中 · ${attentionJobs.length} 需处理 · ${completedJobs.length} 已完成`;
    }
    const tabs = dock.querySelector('[data-mnote-knowledge-rag-task-tabs]');
    if (tabs instanceof HTMLElement) {
      const tabItems = [
        ['active', '进行中', activeJobs.length],
        ['attention', '需处理', attentionJobs.length],
        ['completed', '已完成', completedJobs.length],
        ['all', '全部', jobs.length],
      ];
      tabs.replaceChildren();
      tabItems.forEach(([key, label, count]) => {
        const button = document.createElement('button');
        button.type = 'button';
        button.setAttribute('data-mnote-knowledge-rag-task-tab', key);
        button.setAttribute('aria-selected', knowledgeRagTaskState.taskFilter === key ? 'true' : 'false');
        button.textContent = `${label} ${count}`;
        tabs.appendChild(button);
      });
    }
    const filterLabel = dock.querySelector('[data-mnote-knowledge-rag-task-filter-label]');
    if (filterLabel instanceof HTMLElement) filterLabel.textContent = knowledgeRagTaskFilterLabel(knowledgeRagTaskState.taskFilter);
    const clearCompleted = dock.querySelector('[data-mnote-knowledge-rag-task-clear-completed]');
    if (clearCompleted instanceof HTMLButtonElement) clearCompleted.disabled = completedJobs.length === 0;
    const list = dock.querySelector('[data-testid="mnote-knowledge-rag-task-list"]');
    if (!(list instanceof HTMLElement)) return;
    list.replaceChildren();
    const visibleJobs = jobs.filter((job) => {
      return knowledgeRagTaskState.taskFilter === 'all' || knowledgeRagTaskCategory(job) === knowledgeRagTaskState.taskFilter;
    });
    if (!visibleJobs.length) {
      const empty = document.createElement('div');
      empty.className = 'mnote-knowledge-rag-task-empty';
      empty.textContent = jobs.length ? `暂无${knowledgeRagTaskFilterLabel(knowledgeRagTaskState.taskFilter)}任务` : '暂无后台任务';
      list.appendChild(empty);
      return;
    }
    visibleJobs.forEach((job) => {
      const row = document.createElement('div');
      row.className = 'mnote-knowledge-rag-task-row';
      row.setAttribute('data-mnote-knowledge-rag-task-row', String(job.sourceRootRelativePath || ''));
      row.setAttribute('data-mnote-knowledge-rag-task-status', String(job.status || ''));
      row.setAttribute('data-mnote-knowledge-rag-task-category', knowledgeRagTaskCategory(job));
      const title = String(job.sourceRootRelativePath || '').split('/').filter(Boolean).pop() || '资料库索引';
      const category = knowledgeRagTaskCategory(job);
      const progress = knowledgeRagTaskProgress(job);
      row.innerHTML = '<div class="mnote-knowledge-rag-task-main"><div class="mnote-knowledge-rag-task-title-line"><strong></strong><em></em></div><span></span><div class="mnote-knowledge-rag-task-progress" role="progressbar"><i></i></div></div><div class="mnote-knowledge-rag-task-actions"><button type="button" data-mnote-knowledge-rag-task-open>打开</button><button type="button" data-mnote-knowledge-rag-task-retry>重试</button><button type="button" data-mnote-knowledge-rag-task-clear>清除</button><button type="button" data-mnote-knowledge-rag-task-delete>删除</button></div>';
      const titleNode = row.querySelector('strong');
      if (titleNode instanceof HTMLElement) {
        titleNode.textContent = title;
        titleNode.setAttribute('title', String(job.sourceRootRelativePath || title));
      }
      const categoryNode = row.querySelector('em');
      if (categoryNode instanceof HTMLElement) {
        categoryNode.textContent = category === 'active' ? '进行中' : category === 'attention' ? '需处理' : '已完成';
      }
      const statusNode = row.querySelector('span');
      if (statusNode instanceof HTMLElement) statusNode.textContent = statusTextForKnowledgeRagTask(job);
      const progressBar = row.querySelector('.mnote-knowledge-rag-task-progress');
      const progressValue = row.querySelector('.mnote-knowledge-rag-task-progress i');
      if (progressBar instanceof HTMLElement && progressValue instanceof HTMLElement) {
        progressBar.hidden = category !== 'active' && progress === null;
        progressBar.setAttribute('aria-valuemin', '0');
        progressBar.setAttribute('aria-valuemax', '100');
        if (progress === null) {
          progressBar.setAttribute('data-progress-mode', 'indeterminate');
          progressBar.removeAttribute('aria-valuenow');
          progressValue.style.width = '';
        } else {
          progressBar.setAttribute('data-progress-mode', 'determinate');
          progressBar.setAttribute('aria-valuenow', String(progress));
          progressValue.style.width = `${progress}%`;
        }
      }
      const open = row.querySelector('[data-mnote-knowledge-rag-task-open]');
      if (open instanceof HTMLButtonElement) {
        open.setAttribute('data-mnote-knowledge-rag-task-open', String(job.sourceRootRelativePath || ''));
        open.disabled = !job.artifactRootRelativePath;
        open.hidden = job.taskKind === 'local_index' || job.taskKind === 'knowledge_rag';
      }
      const retry = row.querySelector('[data-mnote-knowledge-rag-task-retry]');
      if (retry instanceof HTMLButtonElement) {
        retry.setAttribute('data-mnote-knowledge-rag-task-retry', String(job.sourceRootRelativePath || ''));
        retry.hidden = job.taskKind === 'local_index' || !['failed', 'stale'].includes(String(job.status || ''));
      }
      const clear = row.querySelector('[data-mnote-knowledge-rag-task-clear]');
      if (clear instanceof HTMLButtonElement) {
        clear.setAttribute('data-mnote-knowledge-rag-task-clear', String(job.sourceRootRelativePath || ''));
      }
      const deleteSource = row.querySelector('[data-mnote-knowledge-rag-task-delete]');
      if (deleteSource instanceof HTMLButtonElement) {
        deleteSource.setAttribute('data-mnote-knowledge-rag-task-delete', String(job.sourceRootRelativePath || ''));
        deleteSource.hidden = job.taskKind === 'local_index' || !String(job.sourceRootRelativePath || '').trim();
      }
      list.appendChild(row);
    });
  };

  const loadKnowledgeRagTasks = async (rootUri) => {
    const normalizedRoot = String(rootUri || '').trim();
    if (!normalizedRoot) return;
    knowledgeRagTaskState.rootUri = normalizedRoot;
    const url = new URL('/api/knowledge-rag/status', window.location.origin);
    url.searchParams.set('rootUri', normalizedRoot);
    const workspaceId = String(currentWebShellWorkspaceId() || '').trim();
    if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
    const response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) return;
    knowledgeRagRegistryEntries(payload).forEach((entry) => {
      const job = knowledgeRagJobFromRegistryEntry(entry, normalizedRoot);
      updateKnowledgeRagTaskState(job);
    });
    renderKnowledgeRagTaskDock();
  };

  const markKnowledgeRagTaskEventStreamRetired = (rootUri) => {
    const normalizedRoot = String(rootUri || '').trim();
    if (!normalizedRoot) return;
    if (knowledgeRagTaskState.eventSource) {
      try { knowledgeRagTaskState.eventSource.close(); } catch (_) {}
      knowledgeRagTaskState.eventSource = null;
    }
    knowledgeRagTaskState.rootUri = normalizedRoot;
    document.documentElement.setAttribute('data-mnote-local-ocr-event-stream-retired', 'true');
  };

  const openKnowledgeRagSourcePanel = async (entry, job) => {
    window.dispatchEvent(new CustomEvent('mnote:open-knowledge-rag-settings'));
    return false;
  };

  const insertRetiredOcrLink = async (entry, job) => {
    throw new Error('OCR sidecar 已退役，请使用 LightRAG 资料库引用与问答。');
  };

  const createKnowledgeRagIngestTask = async (entry) => {
    if (!isKnowledgeRagSourceEntry(entry)) return null;
    const entryRootUri = String(entry.rootUri || '').trim();
    if (entryRootUri) knowledgeRagTaskState.rootUri = entryRootUri;
    const startedAt = Date.now();
    const pendingJob = knowledgeRagTaskSnapshotFromEntry(entry, 'running', 'lightrag', '资料库索引中', startedAt);
    pendingJob.taskKind = 'knowledge_rag';
    setKnowledgeRagStatus(entry, 'running', '资料库索引中', pendingJob);
    updateKnowledgeRagTaskState(pendingJob);
    const workspaceId = String(entry.workspaceId || currentWebShellWorkspaceId() || '').trim();
    const body = {
      workspaceId,
      rootUri: String(entry.rootUri || '').trim(),
      sources: [{ sourcePath: String(entry.path || '').trim() }],
    };
    const response = await fetch('/api/knowledge-rag/ingest', {
      method: 'POST',
      headers: { 'content-type': 'application/json', accept: 'application/json' },
      body: JSON.stringify(body),
    });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) {
      const message = payload?.error?.message || payload?.message || payload?.error?.code || `knowledge_rag_ingest_failed_${response.status}`;
      const failedJob = {
        ...pendingJob,
        status: 'failed',
        stageLabel: message,
        updatedAtMs: Date.now(),
        finishedAtMs: Date.now(),
        error: message,
      };
      setKnowledgeRagStatus(entry, 'failed', message, failedJob);
      updateKnowledgeRagTaskState(failedJob);
      throw new Error(message);
    }
    const registryEntry = knowledgeRagRegistryEntries(payload).find((candidate) => {
      return String(candidate?.sourceRootRelativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '') === String(entry.path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
    });
    const job = knowledgeRagJobFromRegistryEntry(registryEntry, entryRootUri) || {
      ...pendingJob,
      status: payload.retryRequired ? 'running' : 'done',
      stageLabel: payload.retryRequired ? 'LightRAG 忙碌，等待重试' : '资料库已提交索引',
      updatedAtMs: Date.now(),
      finishedAtMs: payload.retryRequired ? null : Date.now(),
    };
    const displayStatus = knowledgeRagDisplayStatus(job, 'done');
    setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForKnowledgeRagTask(job), job);
    if (job) updateKnowledgeRagTaskState(job);
    window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
      detail: { rootUri: entryRootUri, workspaceId, sourceRootRelativePath: String(entry.path || '').trim(), result: payload }
    }));
    return job;
  };

  const deleteKnowledgeRagSourceTask = async (sourceRootRelativePath) => {
    const sourcePath = String(sourceRootRelativePath || '').trim();
    if (!sourcePath) return false;
    const job = knowledgeRagTaskState.jobsBySource.get(sourcePath) || null;
    const rootUri = String(job?.rootUri || knowledgeRagTaskState.rootUri || currentWebShellRootUri() || '').trim();
    const response = await fetch('/api/knowledge-rag/delete-source', {
      method: 'POST',
      headers: { 'content-type': 'application/json', accept: 'application/json' },
      body: JSON.stringify({
        rootUri,
        workspaceId: String(currentWebShellWorkspaceId() || '').trim(),
        sourcePath,
      }),
    });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) {
      throw new Error(payload?.error?.message || `knowledge_rag_delete_failed_${response.status}`);
    }
    knowledgeRagTaskState.jobsBySource.delete(sourcePath);
    renderKnowledgeRagTaskDock();
    window.dispatchEvent(new CustomEvent('mnote:knowledge-rag-source-updated', {
      detail: { rootUri, workspaceId: String(currentWebShellWorkspaceId() || '').trim(), sourceRootRelativePath: sourcePath, result: payload }
    }));
    return true;
  };

  const activeResourceTabEntry = (paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    for (const entry of resourceTabRegistry.values()) {
      if (normalizePaneRole(entry?.paneRole) !== role) continue;
      if (entry?.tab instanceof HTMLElement && entry.tab.getAttribute('aria-selected') === 'true') return entry;
    }
    return null;
  };

  const knowledgeRagCandidatesFromActiveMarkdown = (paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    const sourceKind = currentWebShellSourceKind();
    const rootUri = currentWebShellRootUri();
    if (sourceKind !== 'local_folder' || !rootUri) return [];
    const documentId = documentIdForPane(role) || currentWebShellDocumentId();
    if (!documentId) return [];
    const workspaceId = currentWebShellWorkspaceId();
    const seen = new Set();
    const candidates = [];
    document.querySelectorAll(`.document-pane[data-pane-role="${role}"] .ProseMirror img`).forEach((image) => {
      if (!(image instanceof HTMLImageElement)) return;
      const sourcePath = localFileOpenPathFromTiptapHref(image.getAttribute('src') || image.src || '');
      if (!sourcePath || seen.has(sourcePath)) return;
      seen.add(sourcePath);
      const title = sourcePath.split('/').filter(Boolean).pop() || sourcePath;
      candidates.push({
        sourceKind: 'local_folder',
        rootUri,
        path: sourcePath,
        kind: sourcePath.toLowerCase().endsWith('.pdf') ? 'pdf' : 'image',
        title,
        fileName: title,
        documentId,
        ownerDocumentId: documentId,
        workspaceId,
        objectIdentity: `local-file:${sourcePath}`,
        assetId: `local-file:${sourcePath}`,
      });
    });
    return candidates;
  };

  const knowledgeRagCandidatesForActiveTarget = (paneRole = 'primary') => {
    const activeResource = activeResourceTabEntry(paneRole);
    if (isKnowledgeRagSourceEntry(activeResource)) return [activeResource];
    return knowledgeRagCandidatesFromActiveMarkdown(paneRole);
  };

  const runManualKnowledgeRagForActiveTarget = async (toggle) => {
    ensureKnowledgeRagTaskDock();
    const candidates = knowledgeRagCandidatesForActiveTarget('primary');
    if (!candidates.length) {
      if (knowledgeRagTaskState.jobsBySource.size > 0) {
        knowledgeRagTaskState.drawerOpen = !knowledgeRagTaskState.drawerOpen;
        renderKnowledgeRagTaskDock();
      }
      return [];
    }
    if (toggle instanceof HTMLButtonElement) toggle.disabled = true;
    const jobs = [];
    let createdCount = 0;
    try {
      for (const entry of candidates) {
        try {
          const existing = await readKnowledgeRagStatus(entry);
          const existingStatus = String(existing?.status || '').trim();
          if (existing && !existing.stale && ['done', 'running'].includes(existingStatus)) {
            updateKnowledgeRagTaskState(existing);
            jobs.push(existing);
            continue;
          }
          const job = await createKnowledgeRagIngestTask(entry);
          if (job) {
            createdCount += 1;
            jobs.push(job);
          }
        } catch (error) {
          console.warn('mnote Knowledge RAG 手动任务失败', entry?.path, error);
          if (knowledgeRagTaskState.jobsBySource.has(String(entry?.path || '').trim())) {
            createdCount += 1;
          }
        }
      }
      if (createdCount === 0 && knowledgeRagTaskState.jobsBySource.size > 0) {
        knowledgeRagTaskState.drawerOpen = !knowledgeRagTaskState.drawerOpen;
      }
    } finally {
      if (toggle instanceof HTMLButtonElement) toggle.disabled = false;
      renderKnowledgeRagTaskDock();
    }
    return jobs;
  };

  window.addEventListener('mnote:knowledge-rag-settings-action', (event) => {
    const action = String(event?.detail?.action || '').trim();
    if (action === 'run-active') {
      const toggle = document.querySelector('[data-testid="mnote-knowledge-rag-task-toggle"]');
      void runManualKnowledgeRagForActiveTarget(toggle instanceof HTMLButtonElement ? toggle : null).catch((error) => {
        console.warn('mnote Knowledge RAG 设置入口识别失败', error);
      });
      return;
    }
    if (action === 'tasks') {
      ensureKnowledgeRagTaskDock();
      knowledgeRagTaskState.drawerOpen = true;
      renderKnowledgeRagTaskDock();
    }
  });

  window.addEventListener('mnote:local-background-task-updated', (event) => {
    const task = event?.detail?.task && typeof event.detail.task === 'object' ? event.detail.task : null;
    if (!task) return;
    updateKnowledgeRagTaskState({
      taskKind: String(task.taskKind || 'local_index'),
      jobId: String(task.jobId || task.taskId || `local-index-${Date.now()}`),
      sourceRootRelativePath: String(task.sourceRootRelativePath || task.taskId || 'local-index'),
      rootUri: String(task.rootUri || knowledgeRagTaskState.rootUri || currentWebShellRootUri() || '').trim(),
      artifactRootRelativePath: '',
      provider: String(task.provider || 'mnote-web'),
      status: String(task.status || 'running'),
      stageLabel: String(task.stageLabel || ''),
      stale: false,
      updatedAtMs: Number(task.updatedAtMs || Date.now()),
      finishedAtMs: task.finishedAtMs == null ? null : Number(task.finishedAtMs),
      error: task.error || null,
    });
  });

  const renderKnowledgeRagToolbar = (entry) => {
    if (!isKnowledgeRagSourceEntry(entry) || !(entry.panel instanceof HTMLElement)) return;
    const toolbar = entry.panel.querySelector('[data-testid="mnote-knowledge-rag-toolbar"]');
    if (!(toolbar instanceof HTMLElement)) return;
    const runButton = toolbar.querySelector('[data-mnote-knowledge-rag-action="run"]');
    const openButton = toolbar.querySelector('[data-mnote-knowledge-rag-action="open"]');
    const insertButton = toolbar.querySelector('[data-mnote-knowledge-rag-action="insert"]');
    if (runButton instanceof HTMLButtonElement) {
      runButton.addEventListener('click', async () => {
        runButton.disabled = true;
        try {
          await createKnowledgeRagIngestTask(entry);
        } catch (error) {
          console.warn('mnote Knowledge RAG 生成失败', error);
        } finally {
          runButton.disabled = false;
        }
      });
    }
    if (openButton instanceof HTMLButtonElement) {
      openButton.addEventListener('click', () => {
        void openKnowledgeRagSourcePanel(entry, entry.knowledgeRagJob).catch((error) => {
          console.warn('mnote Knowledge RAG 打开失败', error);
        });
      });
    }
    if (insertButton instanceof HTMLButtonElement) {
      insertButton.addEventListener('click', () => {
        void insertRetiredOcrLink(entry, entry.knowledgeRagJob).catch((error) => {
          console.warn('mnote Knowledge RAG 插入失败', error);
          setKnowledgeRagStatus(entry, 'failed', error instanceof Error ? error.message : String(error), entry.knowledgeRagJob || null);
        });
      });
    }
    setKnowledgeRagStatus(entry, 'idle', '资料库未索引', null);
    ensureKnowledgeRagTaskDock();
    void loadKnowledgeRagTasks(entry.rootUri).catch(() => undefined);
    void readKnowledgeRagStatus(entry).then((job) => {
      if (!job) return;
      const displayStatus = knowledgeRagDisplayStatus(job, 'done');
        setKnowledgeRagStatus(entry, displayStatus, displayStatus === 'stale' ? '资料库需更新' : statusTextForKnowledgeRagTask(job), job);
      updateKnowledgeRagTaskState(job);
    }).catch(() => undefined);
  };

  const createResourceSession = (entry, input, readResult) => {
    const resourcePath = String(input.path || '');
    const resourceDocumentId = localMarkdownDocumentIdFromRelativePath(resourcePath) || entry.objectIdentity;
    const tiptapDocument = localizeTiptapAssetUrls(
      toTiptapDocument(readResult?.content, readResult?.text || ''),
      {
        sourceKind: 'local_folder',
        rootUri: String(input.rootUri || ''),
        documentId: localMarkdownDocumentIdFromRelativePath(resourcePath),
      }
    );
    const conflictDetectionKey = String(readResult?.fileVersion || readResult?.conflictDetectionKey || '').trim();
    const session = {
      key: `resource:${input.rootUri || ''}:${input.path || entry.objectIdentity}`,
      sessionKind: 'resource',
      documentId: resourceDocumentId,
      ownerDocumentId: String(input.documentId || currentWebShellDocumentId() || '').trim(),
      workspaceId: String(input.workspaceId || currentWebShellWorkspaceId() || ''),
      sourceKind: 'local_folder',
      rootUri: String(input.rootUri || ''),
      resourcePath,
      saveEndpoint: '/api/local-folder/resource/write',
      pageAggregateScriptId: '',
      latestAggregate: null,
      title: entry.title,
      currentTiptapDocument: tiptapDocument,
      currentSerialized: JSON.stringify(tiptapDocument),
      lastPersistedSerialized: JSON.stringify(tiptapDocument),
      revision: null,
      conflictDetectionKey,
      fileVersion: conflictDetectionKey,
      lastExternalConflictDetectionKey: conflictDetectionKey,
      readOnly: false,
      lastActiveAt: 0,
      dirty: false,
      saving: false,
      hasExternalConflict: false,
      externalChangePending: false,
      externalRefreshSource: '',
      lastExternalChangeSignalAt: 0,
      lastSelfSaveSignalAt: 0,
      lastExternalWriteSource: '',
      lastExternalWriteRunId: '',
      lastUserInputAt: 0,
      saveTimer: 0,
      externalRefreshTimer: 0,
      releaseTimer: 0,
      views: new Map(),
      localFolderChannel: null,
      status: 'ready',
      error: null,
    };
    documentSessionRegistry.set(session.key, session);
    if (typeof ensureLocalFolderEventChannel === 'function') {
      ensureLocalFolderEventChannel(session);
    }
    return session;
  };

  const openTiptapResourceTab = async (entry, input) => {
    const runtime = await loadRuntime();
    const paneRole = normalizePaneRole(entry.paneRole);
    entry.panel.innerHTML = `<main class="document-shell mnote-resource-tab-text-shell" data-editor-host="leptos_tiptap_resource" data-mnote-editor-kind="resource" data-pane-role="${paneRole}"><div class="mnote-resource-tab-editor-root" data-testid="mnote-leptos-tiptap-island-editor-root" data-editor-host-kind="leptos_tiptap_resource" data-mnote-editor-kind="resource" data-runtime-editor-status="booting" data-pane-role="${paneRole}"></div><div class="sr-only" data-editor-host-observability="rust-web-resource-tab" data-mnote-editor-kind="resource" data-pane-role="${paneRole}"></div></main>`;
    const root = entry.panel.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"]');
    const observability = entry.panel.querySelector('[data-editor-host-observability]');
    if (!(root instanceof HTMLElement)) throw new Error('resource_tab_root_missing');
    const response = await fetch(localResourceReadUrl(input.rootUri, input.path), { cache: 'no-store', headers: { accept: 'application/json' } });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) throw new Error(payload?.error?.message || `resource_read_failed_${response.status}`);
    const readResult = payload.result || {};
    const session = createResourceSession(entry, input, readResult);
    const runtimeDescriptor = {
      paneRole,
      root,
      observability,
      aggregate: { layout: { pageOptions: {} } },
      bootstrap: {
        documentId: String(input.documentId || currentWebShellDocumentId() || '').trim(),
        workspaceId: String(input.workspaceId || currentWebShellWorkspaceId() || ''),
        sourceKind: 'local_folder',
        rootUri: String(input.rootUri || ''),
        saveEndpoint: '/api/local-folder/resource/write',
      },
    };
    const view = createEditorViewBinding(session, runtime, runtimeDescriptor);
    const mountId = runtime.mount(root, {
      documentId: session.documentId,
      workspaceId: session.workspaceId,
      title: session.title,
      content: session.currentTiptapDocument,
      revision: session.revision,
      conflictDetectionKey: session.conflictDetectionKey,
      readOnly: false,
      editable: true,
      pageOptions: {},
    });
    view.mountId = mountId;
    root.setAttribute('data-runtime-mount-id', String(mountId));
    root.setAttribute('data-editor-host-kind', 'leptos_tiptap_resource');
    root.setAttribute('data-document-id', session.ownerDocumentId || '');
    root.setAttribute('data-workspace-id', session.workspaceId || '');
    if (entry.panel instanceof HTMLElement) {
      const shell = entry.panel.querySelector('.document-shell');
      if (shell instanceof HTMLElement) {
        shell.setAttribute('data-document-id', session.ownerDocumentId || '');
        shell.setAttribute('data-workspace-id', session.workspaceId || '');
      }
    }
    setStatus(runtimeDescriptor, 'mounting-editor');
    entry.view = view;
    entry.session = session;
    markIntendedSlashRoot(entry);
  };

  const openReadonlyCodeResourceTab = async (entry, input) => {
    entry.panel.innerHTML = '';
    const shell = document.createElement('div');
    shell.className = 'mnote-resource-tab-code-shell';
    shell.setAttribute('data-mnote-resource-readonly-preview', 'true');

    const header = document.createElement('div');
    header.className = 'mnote-resource-tab-code-header';
    const title = document.createElement('div');
    title.className = 'mnote-resource-tab-code-title';
    title.textContent = entry.title;
    const meta = document.createElement('div');
    meta.className = 'mnote-resource-tab-code-meta';
    meta.textContent = '只读预览';
    header.append(title, meta);

    const pre = document.createElement('pre');
    pre.className = 'mnote-resource-tab-code-preview';
    const code = document.createElement('code');
    pre.append(code);
    shell.append(header, pre);
    entry.panel.append(shell);

    const response = await fetch(localResourceReadUrl(input.rootUri, input.path), { cache: 'no-store', headers: { accept: 'application/json' } });
    const payload = await response.json().catch(() => null);
    if (!response.ok || !payload || payload.ok !== true) throw new Error(payload?.error?.message || `resource_read_failed_${response.status}`);
    const readResult = payload.result || {};
    const rawText = typeof readResult.text === 'string' ? readResult.text : '';
    const truncated = rawText.length > readonlyCodePreviewMaxChars;
    code.textContent = truncated
      ? `${rawText.slice(0, readonlyCodePreviewMaxChars)}\n\n/* 预览已截断，文件过大，请用外部编辑器查看完整内容。 */`
      : rawText;
    const fileSize = new Blob([rawText]).size;
    meta.textContent = truncated
      ? `只读预览 · 已截断 · ${fileSize.toLocaleString()} bytes`
      : `只读预览 · ${fileSize.toLocaleString()} bytes`;
    entry.session = null;
    entry.view = null;
  };

  const ensureOnlyofficeBridgeReadyListener = () => {
    if (onlyofficeBridgeReadyListenerBound) return;
    onlyofficeBridgeReadyListenerBound = true;
    window.addEventListener('message', (event) => {
      if (event.origin !== window.location.origin) return;
      const detail = event.data && typeof event.data === 'object' ? event.data : null;
      if (!detail || detail.type !== 'mnote:onlyoffice-bridge-ready') return;
      syncOpenEditorsSnapshot();
    });
  };

  const openPassiveResourceTab = async (entry, input) => {
    const href = String(input.officeUrl || input.href || '').trim();
    if (entry.kind === 'image') {
      entry.panel.innerHTML = '<div class="mnote-resource-tab-image-shell"><img class="mnote-resource-tab-image" alt=""><div class="mnote-resource-tab-bbox-highlight" data-mnote-evidence-bbox-highlight="true" hidden></div></div>';
      const img = entry.panel.querySelector('img');
      if (img instanceof HTMLImageElement) {
        img.src = href;
        img.alt = entry.title;
      }
      applyEvidenceLocatorToEntry(entry, input);
      ensureKnowledgeRagTaskDock();
      void loadKnowledgeRagTasks(entry.rootUri).catch(() => undefined);
      installPassiveResourceWatch(entry);
      return;
    }
    if (entry.kind === 'pdf') {
      await openInlinePdfResourceTab(entry, input);
      applyEvidenceLocatorToEntry(entry, input);
      if (isKnowledgeRagSourceEntry(entry)) {
        ensureKnowledgeRagTaskDock();
        void loadKnowledgeRagTasks(entry.rootUri).catch(() => undefined);
      }
      installPassiveResourceWatch(entry);
      return;
    }
    entry.panel.innerHTML = '<iframe class="mnote-resource-tab-frame" title=""></iframe>';
    const frame = entry.panel.querySelector('iframe');
    if (frame instanceof HTMLIFrameElement) {
      frame.title = entry.title;
      if (entry.kind === 'office') {
        ensureOnlyofficeBridgeReadyListener();
        frame.addEventListener('load', () => {
          syncOpenEditorsSnapshot();
        }, { once: true });
      }
      frame.src = href;
      entry.passiveFrameSrc = href;
    }
    applyEvidenceLocatorToEntry(entry, input);
    if (isKnowledgeRagSourceEntry(entry)) {
      ensureKnowledgeRagTaskDock();
      void loadKnowledgeRagTasks(entry.rootUri).catch(() => undefined);
    }
    installPassiveResourceWatch(entry);
  };

  const refreshExistingPdfResourceTab = async (entry, input) => {
    if (!entry || entry.kind !== 'pdf') return false;
    const nextHref = String(input.officeUrl || input.href || '').trim();
    if (!nextHref) return false;
    if (nextHref !== String(entry.inlinePdfSourceHref || entry.passiveFrameSrc || '').trim()) {
      await openPassiveResourceTab(entry, input);
    }
    return true;
  };

  const officePreviewBaseHref = (href) => {
    const raw = String(href || '').trim();
    if (!raw) return '';
    try {
      const url = new URL(raw, window.location.origin);
      ['page', 'bbox', 'sourceMapPath', 'blockId', 'lineRange', 'charRange', 'paragraphOrdinal', 'paraIdStart', 'paraIdEnd', 'textFingerprint', 'evidenceText', 'query', 'searchQuery', 'mnoteResourceReload'].forEach((key) => {
        url.searchParams.delete(key);
      });
      return url.pathname + '?' + url.searchParams.toString();
    } catch (_) {
      return raw;
    }
  };

  const refreshExistingOfficeResourceTab = (entry, input) => {
    if (!entry || entry.kind !== 'office') return false;
    const nextHref = String(input.officeUrl || input.href || '').trim();
    if (!nextHref) return false;
    const frame = entry.panel?.querySelector?.('iframe.mnote-resource-tab-frame');
    const currentHref = frame instanceof HTMLIFrameElement
      ? String(frame.getAttribute('src') || frame.src || '').trim()
      : '';
    if (officePreviewBaseHref(currentHref) !== officePreviewBaseHref(nextHref)) void openPassiveResourceTab(entry, input);
    return true;
  };

  const openUnsupportedSideTarget = (input = {}) => {
    const url = currentUrl();
    secondaryQueryParamNames.forEach((name) => url.searchParams.delete(name));
    const previousView = paneViewRegistry.get('secondary');
    if (previousView) {
      unmountEditorViewBinding(previousView);
      paneViewRegistry.delete('secondary');
    }
    unmountMindmapPane('secondary');
    const workspace = document.querySelector('.mnote-document-workspace');
    if (workspace instanceof HTMLElement) {
      workspace.setAttribute('data-has-secondary-pane', 'true');
      setSecondaryEditorHostVisible(true);
      applyStoredSecondaryWidth();
    }
    const resizerNode = document.querySelector('[data-document-pane-resizer="true"]');
    if (resizerNode instanceof HTMLElement) resizerNode.hidden = false;
    const pane = document.querySelector('[data-document-pane="true"][data-pane-role="secondary"]');
    if (pane instanceof HTMLElement) {
      pane.hidden = false;
      pane.setAttribute('data-pane-visible', 'true');
      pane.setAttribute('data-mnote-side-target', 'unsupported-resource');
      pane.removeAttribute('data-pane-document-id');
      pane.removeAttribute('data-pane-workspace-id');
    }
    const root = document.querySelector(`${selector}[data-pane-role="secondary"]`);
    const title = String(input.title || input.fileName || input.assetId || '资源').trim() || '资源';
    if (root instanceof HTMLElement) {
      root.replaceChildren();
      document.querySelectorAll('[data-document-pane="true"][data-pane-role="secondary"] [data-testid="mnote-leptos-tiptap-slash-menu"]').forEach((node) => {
        if (node instanceof HTMLElement) node.remove();
      });
      root.setAttribute('data-editor-host-kind', 'unsupported_resource_side_target');
      root.setAttribute('data-mnote-side-target-unsupported', 'true');
      root.setAttribute('data-mnote-side-target-asset-id', String(input.assetId || ''));
      const placeholder = document.createElement('div');
      placeholder.className = 'mnote-resource-tab-error';
      placeholder.setAttribute('data-mnote-side-target-placeholder', 'true');
      placeholder.innerHTML = '<div class="mnote-resource-tab-error-inner"><h1>暂不支持在侧栏打开此资源</h1><p></p></div>';
      const text = placeholder.querySelector('p');
      if (text) text.textContent = title;
      root.append(placeholder);
    }
    document.documentElement.setAttribute('data-mnote-side-target-unsupported', 'true');
    document.documentElement.setAttribute('data-mnote-side-target-asset-id', String(input.assetId || ''));
    replaceUrlState(url);
    return true;
  };

  const openResourceInActiveTab = async (input = {}) => {
    const paneRole = normalizePaneRole(input.paneRole || input.targetPaneRole || 'primary');
    if (paneRole === 'secondary') {
      if (workspace instanceof HTMLElement) {
        workspace.setAttribute('data-has-secondary-pane', 'true');
        applyStoredSecondaryWidth();
      }
      setSecondaryEditorHostVisible(true);
      const resizerNode = document.querySelector('[data-document-pane-resizer="true"]');
      if (resizerNode instanceof HTMLElement) resizerNode.hidden = false;
      const pane = document.querySelector('[data-document-pane="true"][data-pane-role="secondary"]');
      if (pane instanceof HTMLElement) {
        pane.hidden = false;
        pane.setAttribute('data-pane-visible', 'true');
      }
    }
    bindMainEditorPageTab(paneRole);
    const objectIdentity = String(input.objectIdentity || input.assetId || input.href || '').trim();
    if (!objectIdentity) return false;
    const registryKey = resourceTabRegistryKey(paneRole, objectIdentity);
    const requestedKind = normalizeResourceTabKind(input);
    if (paneRole === 'secondary') {
      resourceTabRegistry.forEach((entry, key) => {
        if (normalizePaneRole(entry.paneRole) !== paneRole) return;
        if (key === registryKey) return;
        releaseResourceTabEntryRuntime(entry);
        if (entry.tab instanceof HTMLElement) entry.tab.remove();
        if (entry.panel instanceof HTMLElement) entry.panel.remove();
        resourceTabRegistry.delete(key);
        removeFromResourceTabMru(paneRole, key);
      });
    }
    const existing = resourceTabRegistry.get(registryKey);
    if (existing) {
      activateMainEditorTab(registryKey, paneRole);
      await refreshExistingPdfResourceTab(existing, input);
      refreshExistingOfficeResourceTab(existing, input);
      applyEvidenceLocatorToEntry(existing, input);
      return true;
    }
    const entry = createResourceTabDom({ ...input, objectIdentity, paneRole });
    if (!entry) return false;
    resourceTabRegistry.set(registryKey, entry);
    activateMainEditorTab(registryKey, paneRole);
    try {
      if (entry.kind === 'mindmap') {
        await openMindmapResourceTab(entry, input);
      } else if (entry.kind === 'markdown' || entry.kind === 'text') {
        await openTiptapResourceTab(entry, input);
      } else if (entry.kind === 'code') {
        await openReadonlyCodeResourceTab(entry, input);
      } else {
        await openPassiveResourceTab(entry, input);
      }
      applyEvidenceLocatorToEntry(entry, input);
      activateMainEditorTab(registryKey, paneRole);
      return true;
    } catch (error) {
      console.warn('mnote resource tab 打开失败', error);
      markResourceTabError(entry);
      return true;
    }
  };


  const closeResourceTabsForPane = (paneRole = 'primary') => {
    const role = normalizePaneRole(paneRole);
    Array.from(resourceTabRegistry.entries()).forEach(([key, entry]) => {
      if (normalizePaneRole(entry?.paneRole) !== role) return;
      releaseResourceTabEntryRuntime(entry);
      if (entry.tab instanceof HTMLElement) entry.tab.remove();
      if (entry.panel instanceof HTMLElement) entry.panel.remove();
      resourceTabRegistry.delete(key);
      removeFromResourceTabMru(role, key);
    });
    syncOpenEditorsSnapshot();
  };

  try {
    bindKnowledgeRagTopbarAction();
  } catch (_) {}

  return {
    activateMainEditorTab,
    bindMainEditorPageTab,
    buildOpenEditorsSnapshot,
    closeResourceTabsForPane,
    normalizePaneRole,
    openResourceInActiveTab,
    resolveResourceOpen,
    syncResourceSessionTabGuards,
  };
};
