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

export const createResourceTabRuntime = (dependencies = {}) => {
  const {
    loadRuntime,
    createEditorViewBinding,
    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 resourceTabCloseGuardAttribute = 'data-resource-tab-close-guarded';

  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 currentWebShellWorkspaceId = () => {
    const explicit = typeof currentWorkspaceId === 'function' ? String(currentWorkspaceId() || '').trim() : '';
    if (explicit) return explicit;
    try {
      return currentUrl().searchParams.get('workspaceId') || '';
    } catch (_) {
      return '';
    }
  };

  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 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' || kind === 'code';
    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?.session) entry.session.lastActiveAt = Date.now();
  };

  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 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 syncResourceTabCloseGuard = (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 syncResourceSessionTabGuards = (session) => {
    if (!session || session.sessionKind !== 'resource') return;
    resourceTabRegistry.forEach((entry) => {
      if (entry.session === session) syncResourceTabCloseGuard(entry);
    });
  };

  const openEditorsSnapshotEntry = (entry, key) => ({
    objectIdentity: String(entry?.objectIdentity || key || '').trim(),
    title: String(entry?.title || '资源').trim() || '资源',
    kind: normalizeResourceTabKind(entry),
    badgeKind: entry?.tab instanceof HTMLElement
      ? String(entry.tab.getAttribute('data-mnote-tab-badge-kind') || resourceTabBadgeKind(entry, entry.kind)).trim()
      : resourceTabBadgeKind(entry, entry?.kind),
    active: entry?.tab instanceof HTMLElement
      ? entry.tab.getAttribute('aria-selected') === 'true'
      : false,
    dirtyGuard: resourceTabCloseGuardReason(entry?.session),
    assetId: String(entry?.assetId || entry?.session?.assetId || '').trim(),
    path: String(entry?.path || entry?.session?.resourcePath || '').trim(),
  });

  const buildOpenEditorsSnapshot = () => {
    const pageEntries = ['primary', 'secondary'].map((paneRole) => {
      const nodes = resourceTabHostNodes(paneRole);
      const pageTitle = nodes.pageTab instanceof HTMLElement
        ? String(nodes.pageTab.querySelector('.mnote-main-tab-title')?.textContent || '页面').trim()
        : '页面';
      return {
        objectIdentity: `page:${paneRole}`,
        paneRole,
        documentId: String(nodes.pageTab?.getAttribute?.('data-document-id') || '').trim(),
        workspaceId: String(nodes.pageTab?.getAttribute?.('data-workspace-id') || currentWebShellWorkspaceId() || '').trim(),
        title: pageTitle || '页面',
        kind: 'page',
        badgeKind: 'code',
        active: nodes.pageTab instanceof HTMLElement
          ? nodes.pageTab.getAttribute('aria-selected') === 'true'
          : false,
        dirtyGuard: '',
      };
    }).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));
    });
    const active = [...pageEntries, ...resources].find((entry) => entry.active);
    return {
      schema: 'mnote.open_editors_snapshot.v1',
      generatedAt: Date.now(),
      activeObjectIdentity: active?.objectIdentity || '',
      editors: [...pageEntries, ...resources],
      resourceEditors: resources,
    };
  };

  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 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)}`;
      url.searchParams.set('resourceTab', activeResource);
    } else {
      const documentId = currentWebShellDocumentId();
      if (documentId) url.pathname = `/documents/${encodeURIComponent(documentId)}`;
      url.searchParams.delete('resourceTab');
    }
    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);
    });
    if (activeEntry) markIntendedSlashRoot(activeEntry);
    if (!activeEntry && window.__mnoteIntendedSlashRoot instanceof HTMLElement) window.__mnoteIntendedSlashRoot = null;
    if (role === 'primary') syncActiveResourceFileTreeRow(activeResource);
    syncActiveResourceUrlState(activeResource, role);
    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 = (objectIdentity) => {
    const key = String(objectIdentity || '').trim();
    const entry = resourceTabRegistry.get(key);
    if (!entry) return;
    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);
      }
    }
    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.hidden = true;
    nodes.strip.append(tab);
    nodes.panelRoot.append(panel);
    return {
      objectIdentity,
      registryKey,
      paneRole,
      title,
      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(),
    };
  };

  const releaseResourceTabEntryRuntime = (entry) => {
    if (!entry) return;
    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;
    }
    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 createResourceSession = (entry, input, readResult) => {
    const resourcePath = String(input.path || '');
    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: entry.objectIdentity,
      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);
    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 openPassiveResourceTab = (entry, input) => {
    const href = String(input.officeUrl || input.href || '').trim();
    if (entry.kind === 'image') {
      entry.panel.innerHTML = '<img class="mnote-resource-tab-image" alt="">';
      const img = entry.panel.querySelector('img');
      if (img instanceof HTMLImageElement) {
        img.src = href;
        img.alt = entry.title;
      }
      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;
      frame.src = href;
    }
  };

  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 (currentHref !== nextHref) 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);
    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) {
      refreshExistingOfficeResourceTab(existing, input);
      activateMainEditorTab(registryKey, paneRole);
      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' || entry.kind === 'code') {
        await openTiptapResourceTab(entry, input);
      } else {
        openPassiveResourceTab(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();
  };

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