export function createSidebarPageAiTargetRuntime(context) {
  const {
    currentDocumentId,
    currentRootUri,
    currentSourceKind,
    currentPageOptions,
    documentRef,
    escapeHtml,
    pageAiEnsureContextRefState,
    pageUiState,
    resolveWorkspaceId,
    searchText,
    pageAiNormalizeArray,
  } = context;

  function pageAiCloneJson(value) {
    if (value == null) return null;
    try {
      return JSON.parse(JSON.stringify(value));
    } catch (_) {
      return null;
    }
  }

  function localMarkdownRelativePathFromPageAiDocumentId(documentId) {
    var value = String(documentId || '').trim();
    if (!value.startsWith('local-md:')) return '';
    return value.slice('local-md:'.length).replace(/~2F/g, '/');
  }

  function localMarkdownDocumentIdFromPageAiRelativePath(relativePath) {
    var normalized = String(relativePath || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
    if (!normalized) return '';
    return 'local-md:' + normalized.split('/').map(function(segment) {
      return encodeURIComponent(segment).replace(/%20/g, '~20');
    }).join('~2F');
  }

  function pageAiWorkspacePathForDocument(documentId, seed) {
    var relativePath = String(seed && seed.relativePath || localMarkdownRelativePathFromPageAiDocumentId(documentId) || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
    var resolvedDocumentId = String(documentId || seed && seed.documentId || '').trim()
      || localMarkdownDocumentIdFromPageAiRelativePath(relativePath);
    return {
      schema: 'mnote.workspace_path.v1',
      workspaceId: String(seed && seed.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim(),
      sourceKind: String(seed && seed.sourceKind || currentSourceKind() || '').trim(),
      rootUri: String(seed && seed.rootUri || currentRootUri() || '').trim(),
      relativePath: relativePath,
      documentId: resolvedDocumentId,
      objectIdentity: seed && seed.objectIdentity && typeof seed.objectIdentity === 'object'
        ? seed.objectIdentity
        : String(seed && seed.objectIdentity || resolvedDocumentId || '').trim(),
      assetId: String(seed && seed.assetId || '').trim(),
      resourceKind: String(seed && seed.resourceKind || 'markdown_page').trim()
    };
  }

  function pageAiResourceKindForTarget(entry) {
    var kind = String(entry && (entry.editorKind || entry.kind) || '').trim().toLowerCase();
    var assetId = String(entry && entry.assetId || '').trim();
    var path = String(entry && entry.path || '').trim().toLowerCase();
    var workspacePath = entry && entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {};
    var workspaceResourceKind = String(workspacePath.resourceKind || '').trim().toLowerCase();
    var officeOpenMode = String(entry && entry.officeOpenMode || '').trim().toLowerCase();
    var onlyofficeSessionId = String(entry && (entry.onlyofficeSessionId || entry.bridgeSessionId) || '').trim();
    if (kind === 'page' || kind === 'markdown_page') return 'markdown_page';
    if (kind === 'mindmap' || path.endsWith('.mindmap.json')) return 'mindmap';
    if (kind === 'office' || kind === 'onlyoffice' || kind === 'only_office' || workspaceResourceKind === 'only_office' || /\.(doc|docx|ppt|pptx|xls|xlsx)$/.test(path)) {
      return officeOpenMode === 'onlyoffice_live' || onlyofficeSessionId ? 'only_office' : 'attachment';
    }
    if (kind === 'resource' && assetId) return 'resource';
    return kind || 'markdown_page';
  }

  function pageAiIsOnlyOfficeLiveTarget(editorTarget) {
    var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
    var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim().toLowerCase();
    var officeOpenMode = String(editorTarget && editorTarget.officeOpenMode || '').trim().toLowerCase();
    return resourceKind === 'only_office' || resourceKind === 'onlyoffice' || officeOpenMode === 'onlyoffice_live';
  }

  function pageAiTargetId(entry) {
    if (!entry || typeof entry !== 'object') return '';
    var workspacePath = entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {};
    var entryIdentity = typeof entry.objectIdentity === 'string' ? entry.objectIdentity : '';
    var workspaceIdentity = typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : '';
    return String(entryIdentity || workspaceIdentity || entry.documentId || entry.assetId || entry.path || '').trim();
  }

  function pageAiWorkspacePathForTarget(entry) {
    var seed = Object.assign({}, entry && entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : {});
    var resourceKind = pageAiResourceKindForTarget(entry);
    if (!seed.relativePath && entry && entry.path) seed.relativePath = entry.path;
    if (!seed.assetId && entry && entry.assetId) seed.assetId = entry.assetId;
    var seedResourceKind = String(seed.resourceKind || '').trim();
    if (!seedResourceKind || seedResourceKind === 'page' || seedResourceKind === 'office') seed.resourceKind = resourceKind;
    if (!seed.objectIdentity && entry && entry.objectIdentity) seed.objectIdentity = entry.objectIdentity;
    if (!seed.workspaceId && entry && entry.workspaceId) seed.workspaceId = entry.workspaceId;
    return pageAiWorkspacePathForDocument(entry && entry.documentId || currentDocumentId(), seed);
  }

  function pageAiTargetFromOpenEditor(entry, source) {
    if (!entry || typeof entry !== 'object') return null;
    var workspacePath = pageAiWorkspacePathForTarget(entry);
    var targetId = pageAiTargetId(entry) || workspacePath.objectIdentity || workspacePath.documentId;
    if (!targetId) return null;
    var objectIdentity = typeof entry.objectIdentity === 'string'
      ? entry.objectIdentity
      : (typeof workspacePath.objectIdentity === 'string' ? workspacePath.objectIdentity : targetId);
    return {
      schema: 'mnote.ai_editor_target.v1',
      source: source || 'open_editors_snapshot',
      targetId: targetId,
      objectIdentity: objectIdentity,
      workspacePath: workspacePath,
      paneRole: entry.paneRole || 'primary',
      documentId: entry.documentId || workspacePath.documentId,
      workspaceId: entry.workspaceId || workspacePath.workspaceId || resolveWorkspaceId(documentRef.body),
      editorKind: entry.editorKind || entry.kind || workspacePath.resourceKind,
      resourceKind: workspacePath.resourceKind,
      title: entry.title || '',
      active: entry.active === true,
      dirtyState: entry.dirtyState || '',
      preview: entry.preview === true,
      pinned: entry.pinned === true,
      lastActiveAt: entry.lastActiveAt || 0,
      assetId: entry.assetId || workspacePath.assetId || '',
      path: entry.path || workspacePath.relativePath || '',
      officeOpenMode: entry.officeOpenMode || '',
      onlyofficeSessionId: entry.onlyofficeSessionId || entry.bridgeSessionId || '',
      bridgeSessionId: entry.bridgeSessionId || entry.onlyofficeSessionId || '',
      bridgeSessionReady: entry.bridgeSessionReady === true
    };
  }

  function normalizeOpenEditorEntry(entry) {
    if (!entry || typeof entry !== 'object') return null;
    return {
      objectIdentity: String(entry.objectIdentity || '').trim(),
      workspacePath: entry.workspacePath && typeof entry.workspacePath === 'object' ? entry.workspacePath : null,
      paneRole: String(entry.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary',
      documentId: String(entry.documentId || '').trim(),
      workspaceId: String(entry.workspaceId || '').trim(),
      title: String(entry.title || '').trim(),
      kind: String(entry.kind || entry.editorKind || '').trim(),
      editorKind: String(entry.editorKind || entry.kind || '').trim(),
      active: entry.active === true,
      dirtyState: String(entry.dirtyState || entry.dirtyGuard || '').trim(),
      preview: entry.preview === true,
      pinned: entry.pinned === true,
      lastActiveAt: Number(entry.lastActiveAt || 0) || 0,
      assetId: String(entry.assetId || '').trim(),
      path: String(entry.path || '').trim(),
      officeOpenMode: String(entry.officeOpenMode || '').trim(),
      onlyofficeSessionId: String(entry.onlyofficeSessionId || entry.bridgeSessionId || '').trim(),
      bridgeSessionId: String(entry.bridgeSessionId || entry.onlyofficeSessionId || '').trim(),
      bridgeSessionReady: entry.bridgeSessionReady === true
    };
  }

  function currentPageAiOpenEditorsSnapshot() {
    var snapshot = null;
    try {
      if (window.__mnoteDocumentPaneRuntime && typeof window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot === 'function') {
        snapshot = window.__mnoteDocumentPaneRuntime.getOpenEditorsSnapshot();
      }
    } catch (_) {}
    if (!snapshot || typeof snapshot !== 'object') snapshot = window.__mnoteOpenEditorsSnapshot || null;
    if (!snapshot || typeof snapshot !== 'object') return null;
    var editors = Array.isArray(snapshot.editors)
      ? snapshot.editors.map(normalizeOpenEditorEntry).filter(Boolean)
      : [];
    var resources = Array.isArray(snapshot.resourceEditors)
      ? snapshot.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean)
      : editors.filter(function(entry) { return entry.kind !== 'page'; });
    var normalizeGroup = function(group, paneRole) {
      var groupEditors = group && Array.isArray(group.editors)
        ? group.editors.map(normalizeOpenEditorEntry).filter(Boolean)
        : editors.filter(function(entry) { return entry.paneRole === paneRole; });
      var groupResources = group && Array.isArray(group.resourceEditors)
        ? group.resourceEditors.map(normalizeOpenEditorEntry).filter(Boolean)
        : resources.filter(function(entry) { return entry.paneRole === paneRole; });
      var groupActive = groupEditors.find(function(entry) { return entry.active; }) || null;
      return {
        paneRole: paneRole,
        activeObjectIdentity: String(group && group.activeObjectIdentity || (groupActive ? groupActive.objectIdentity : '') || '').trim(),
        editors: groupEditors,
        resourceEditors: groupResources
      };
    };
    var groups = {
      primary: normalizeGroup(snapshot.groups && snapshot.groups.primary, 'primary'),
      secondary: normalizeGroup(snapshot.groups && snapshot.groups.secondary, 'secondary')
    };
    var activeObjectIdentity = String(snapshot.activeObjectIdentity || '').trim();
    var allTargets = editors.concat(resources);
    var activeEditor = allTargets.find(function(entry) {
      return entry.active && (!activeObjectIdentity || entry.objectIdentity === activeObjectIdentity);
    }) || groups.primary.editors.find(function(entry) {
      return entry.active;
    }) || groups.primary.resourceEditors.find(function(entry) {
      return entry.active;
    }) || groups.secondary.editors.find(function(entry) {
      return entry.active;
    }) || groups.secondary.resourceEditors.find(function(entry) {
      return entry.active;
    }) || null;
    return {
      schema: String(snapshot.schema || 'mnote.open_editors_snapshot.v1'),
      generatedAt: Number(snapshot.generatedAt || 0) || Date.now(),
      activeObjectIdentity: activeObjectIdentity || (activeEditor ? activeEditor.objectIdentity : ''),
      activeEditor: activeEditor,
      editors: editors,
      resourceEditors: resources,
      groups: groups
    };
  }

  function pageAiFallbackEditorTarget() {
    var fallbackDocumentId = currentDocumentId();
    var fallbackWorkspacePath = pageAiWorkspacePathForDocument(fallbackDocumentId, null);
    var fallbackTargetId = fallbackWorkspacePath.objectIdentity || fallbackDocumentId || '';
    return {
      schema: 'mnote.ai_editor_target.v1',
      source: 'fallback_current_document',
      targetId: fallbackTargetId,
      objectIdentity: fallbackTargetId,
      workspacePath: fallbackWorkspacePath,
      paneRole: 'primary',
      documentId: fallbackDocumentId,
      workspaceId: resolveWorkspaceId(documentRef.body),
      editorKind: 'page',
      active: true,
      dirtyState: '',
      preview: false,
      pinned: true,
      lastActiveAt: Date.now(),
      assetId: '',
      path: ''
    };
  }

  function pageAiEditorTargetCandidates() {
    var snapshot = currentPageAiOpenEditorsSnapshot();
    var entries = [];
    if (snapshot) {
      entries = pageAiNormalizeArray(snapshot.editors).concat(pageAiNormalizeArray(snapshot.resourceEditors));
    }
    var seen = {};
    var targets = entries.map(function(entry) {
      return pageAiTargetFromOpenEditor(entry, 'open_editors_snapshot');
    }).filter(function(target) {
      var id = String(target && target.targetId || '').trim();
      if (!id || seen[id]) return false;
      seen[id] = true;
      return true;
    });
    if (!targets.length) targets.push(pageAiFallbackEditorTarget());
    return targets;
  }

  function currentPageAiEditorTarget() {
    var targets = pageAiEditorTargetCandidates();
    var selectedId = String(pageUiState.pageAiSelectedTargetId || '').trim();
    var selected = selectedId ? targets.find(function(target) { return target.targetId === selectedId; }) : null;
    return selected
      || targets.find(function(target) { return target.active === true; })
      || targets[0]
      || pageAiFallbackEditorTarget();
  }

  function currentPageAiPageEditorTarget() {
    var snapshot = currentPageAiOpenEditorsSnapshot();
    var documentId = currentDocumentId();
    var workspaceId = resolveWorkspaceId(documentRef.body);
    var sourceKind = currentSourceKind();
    var rootUri = currentRootUri();
    var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId);
    var fallbackWorkspacePath = pageAiWorkspacePathForDocument(documentId, {
      relativePath: relativePath
    });
    var pageEditor = snapshot && Array.isArray(snapshot.editors)
      ? snapshot.editors.find(function(entry) {
          return entry
            && String(entry.editorKind || entry.kind || '') === 'page'
            && String(entry.documentId || '').trim() === String(documentId || '').trim();
        })
      : null;
    var workspacePath = pageEditor && pageEditor.workspacePath
      ? Object.assign({}, pageEditor.workspacePath, {
          workspaceId: workspaceId,
          sourceKind: sourceKind,
          rootUri: rootUri,
          relativePath: relativePath,
          documentId: documentId,
          resourceKind: pageEditor.workspacePath.resourceKind || 'markdown_page',
          objectIdentity: pageEditor.workspacePath.objectIdentity || fallbackWorkspacePath.objectIdentity
        })
      : fallbackWorkspacePath;
    var objectIdentity = String(pageEditor && pageEditor.objectIdentity || workspacePath.objectIdentity || documentId || '').trim();
    return {
      schema: 'mnote.ai_editor_target.v1',
      source: pageEditor ? 'current_page_from_open_editors_snapshot' : 'current_page_fallback',
      targetId: objectIdentity,
      objectIdentity: objectIdentity,
      workspacePath: Object.assign({}, workspacePath, { objectIdentity: objectIdentity }),
      paneRole: String(pageEditor && pageEditor.paneRole || 'primary').trim() === 'secondary' ? 'secondary' : 'primary',
      documentId: documentId,
      workspaceId: workspaceId,
      editorKind: 'page',
      active: pageEditor ? pageEditor.active === true : true,
      dirtyState: String(pageEditor && pageEditor.dirtyState || '').trim(),
      preview: pageEditor ? pageEditor.preview === true : false,
      pinned: true,
      lastActiveAt: Number(pageEditor && pageEditor.lastActiveAt || 0) || Date.now(),
      assetId: '',
      path: relativePath
    };
  }

  function currentPageAiScopedEditorTarget() {
    var selected = pageAiEnsureContextRefState();
    return selected.active_editor ? currentPageAiEditorTarget() : currentPageAiPageEditorTarget();
  }

  function pageAiSetRunTargetSnapshot(snapshot) {
    pageUiState.pageAiCurrentRunTargetSnapshot = snapshot || null;
    var target = snapshot && snapshot.editorTarget ? snapshot.editorTarget : null;
    var documentId = String(target && target.documentId || snapshot && snapshot.documentId || '').trim();
    var workspaceId = String(target && target.workspaceId || snapshot && snapshot.workspaceId || '').trim();
    var rootUri = String(target && target.workspacePath && target.workspacePath.rootUri || snapshot && snapshot.rootUri || '').trim();
    if (documentId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-document-id', documentId);
    if (workspaceId) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-workspace-id', workspaceId);
    if (rootUri) documentRef.documentElement.setAttribute('data-mnote-page-ai-run-target-root-uri', rootUri);
  }

  function assertPageAiTargetInCurrentWorkspace(editorTarget) {
    var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {};
    var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
    var targetSourceKind = String(workspacePath.sourceKind || '').trim();
    var currentKind = String(currentSourceKind() || '').trim();
    if (targetSourceKind && currentKind && targetSourceKind !== currentKind) {
      var sourceError = new Error('AI target 与当前页面 sourceKind 不一致，请重新选择当前工作区内的目标。');
      sourceError.code = 'page_ai_target_workspace_mismatch';
      throw sourceError;
    }
    var targetRootUri = String(workspacePath.rootUri || '').trim();
    var currentRoot = String(currentRootUri() || '').trim();
    if (targetRootUri && currentRoot && targetRootUri !== currentRoot) {
      var rootError = new Error('AI target 与当前本地工作区 rootUri 不一致，请重新选择当前工作区内的目标。');
      rootError.code = 'page_ai_target_workspace_mismatch';
      throw rootError;
    }
    var targetWorkspaceId = String(target.workspaceId || workspacePath.workspaceId || '').trim();
    var currentWorkspaceId = String(resolveWorkspaceId(documentRef.body) || '').trim();
    if (targetWorkspaceId && currentWorkspaceId && targetWorkspaceId !== currentWorkspaceId) {
      var workspaceError = new Error('AI target 与当前 workspaceId 不一致，请重新选择当前工作区内的目标。');
      workspaceError.code = 'page_ai_target_workspace_mismatch';
      throw workspaceError;
    }
  }

  function pageAiBuildContextRefs(scopedContext, runTargetSnapshot) {
    var selected = pageAiEnsureContextRefState();
    var refs = [];
    var documentId = currentDocumentId();
    var rootUri = currentRootUri();
    var workspaceId = resolveWorkspaceId(documentRef.body);
    var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
    if (selected.current_page) {
      refs.push({
        kind: 'current_page',
        documentId: documentId,
        rootUri: rootUri,
        workspaceId: workspaceId
      });
    }
    if (selected.selection && scopedContext && scopedContext.selectedText) {
      refs.push({
        kind: 'selection',
        documentId: documentId,
        rootUri: rootUri,
        selectedBlockId: scopedContext.selectedBlockId || ''
      });
    }
    if (selected.active_editor && editorTarget) {
      var workspacePath = editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
      refs.push({
        kind: 'active_editor',
        documentId: editorTarget.documentId || documentId,
        workspaceId: editorTarget.workspaceId || workspacePath.workspaceId || workspaceId,
        rootUri: workspacePath.rootUri || rootUri,
        relativePath: workspacePath.relativePath || '',
        editorKind: editorTarget.editorKind || '',
        resourceKind: editorTarget.resourceKind || workspacePath.resourceKind || '',
        targetId: editorTarget.targetId || workspacePath.objectIdentity || '',
        objectIdentity: editorTarget.objectIdentity || workspacePath.objectIdentity || '',
        assetId: editorTarget.assetId || workspacePath.assetId || '',
        onlyofficeSessionId: editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId || '',
        bridgeSessionId: editorTarget.bridgeSessionId || editorTarget.onlyofficeSessionId || ''
      });
    }
    if (selected.file) {
      refs.push({
        kind: 'file',
        documentId: documentId,
        rootUri: rootUri,
        relativePath: localMarkdownRelativePathFromPageAiDocumentId(documentId)
      });
    }
    if (selected.folder) {
      refs.push({
        kind: 'folder',
        rootUri: rootUri,
        relativePath: ''
      });
    }
    if (selected.changed_files) {
      refs.push({
        kind: 'changed_files',
        rootUri: rootUri,
        sinceRunTargetSnapshot: runTargetSnapshot && runTargetSnapshot.frozenAt || null
      });
    }
    return refs.filter(function(ref) {
      return ref && String(ref.kind || '').trim();
    });
  }

  function pageAiBuildAllowedRoots() {
    return pageAiNormalizeArray(pageUiState.pageAiAllowedRoots).map(function(root) {
      return {
        rootUri: root.rootUri,
        permission: root.permission === 'write' || root.permission === 'read_write' ? 'write' : 'read',
        recursive: root.recursive !== false,
        source: root.source === 'auto' || root.source === 'user' || root.source === 'admin'
          ? 'sqlite_directory_grant'
          : (root.source || 'sqlite_directory_grant'),
        grantId: root.id || ''
      };
    });
  }

  function pageAiBuildRunTargetSnapshot(scopedContext, prompt) {
    var aiContext = scopedContext && scopedContext.pageContext && scopedContext.pageContext.aiContext
      ? scopedContext.pageContext.aiContext
      : {};
    var editorTarget = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
    return {
      schema: 'mnote.page_ai_run_target_snapshot.v1',
      source: 'open_editors_snapshot',
      frozenAt: Date.now(),
      workspaceId: resolveWorkspaceId(documentRef.body),
      documentId: currentDocumentId(),
      sourceKind: currentSourceKind(),
      rootUri: currentRootUri(),
      contextScope: pageUiState.pageAiContextScope || 'page',
      promptPreview: searchText(prompt || '').slice(0, 160),
      editorTarget: pageAiCloneJson(editorTarget),
      activeEditorTarget: pageAiCloneJson(aiContext.activeEditorTarget || editorTarget),
      openEditorsSnapshot: pageAiCloneJson(aiContext.openEditorsSnapshot || currentPageAiOpenEditorsSnapshot())
    };
  }

  function pageAiContextKindsFromRefs(contextRefs) {
    var kinds = {};
    pageAiNormalizeArray(contextRefs).forEach(function(ref) {
      var kind = String(ref && ref.kind || '').trim();
      if (kind) kinds[kind] = true;
    });
    return kinds;
  }

  function pageAiPageContextForRefs(pageContext, contextRefs) {
    var cloned = pageAiCloneJson(pageContext) || {};
    var aiContext = cloned.aiContext && typeof cloned.aiContext === 'object' ? cloned.aiContext : {};
    var kinds = pageAiContextKindsFromRefs(contextRefs);
    delete cloned.documentBlocks;
    delete cloned.evidence;
    delete aiContext.contextBlocks;
    delete aiContext.pageText;
    delete aiContext.pageXml;
    delete aiContext.truncated;
    delete aiContext.warnings;
    if (!kinds.selection) {
      delete aiContext.selectedText;
      delete aiContext.selectedBlockIds;
      delete aiContext.selectedBlocks;
      delete aiContext.allowedTargetBlockIds;
    }
    cloned.aiContext = aiContext;
    return cloned;
  }

  function pageAiBuildAgentTargetPackage(scopedContext, runTargetSnapshot) {
    var target = scopedContext && scopedContext.editorTarget ? scopedContext.editorTarget : currentPageAiEditorTarget();
    var workspacePath = target && target.workspacePath && typeof target.workspacePath === 'object'
      ? pageAiWorkspacePathForDocument(target.documentId || currentDocumentId(), target.workspacePath)
      : pageAiWorkspacePathForDocument(target && target.documentId || currentDocumentId(), null);
    var relativePath = String(workspacePath.relativePath || '').trim();
    var allowedFiles = relativePath ? [relativePath] : [];
    var writable = pageAiBuildAllowedRoots().some(function(root) {
      return String(root && root.rootUri || '').trim() === String(workspacePath.rootUri || '').trim()
        && String(root && root.permission || '').trim() === 'write';
    });
    var primaryTargetId = String(target && target.targetId || workspacePath.objectIdentity || workspacePath.documentId || '').trim();
    var onlyofficeSessionId = String(target && (target.onlyofficeSessionId || target.bridgeSessionId) || '').trim();
    var targetEntry = {
      targetId: primaryTargetId,
      objectIdentity: primaryTargetId,
      documentId: workspacePath.documentId,
      workspaceId: workspacePath.workspaceId,
      sourceKind: workspacePath.sourceKind,
      rootUri: workspacePath.rootUri,
      relativePath: relativePath,
      resourceKind: workspacePath.resourceKind,
      assetId: workspacePath.assetId || target && target.assetId || '',
      onlyofficeSessionId: onlyofficeSessionId,
      bridgeSessionId: onlyofficeSessionId,
      paneRole: target && target.paneRole || 'primary',
      title: target && target.title || '',
      policy: {
        permission: allowedFiles.length && writable ? 'read_write' : 'read',
        writeRequiresCleanBuffer: true,
        conflictPolicy: 'fail_on_dirty_or_stale'
      }
    };
    return {
      schema: 'mnote.agent_target_package.v1',
      source: 'page_ai_run_target_snapshot',
      frozenAt: runTargetSnapshot && runTargetSnapshot.frozenAt || Date.now(),
      primaryTargetId: primaryTargetId,
      onlyofficeSessionId: onlyofficeSessionId,
      bridgeSessionId: onlyofficeSessionId,
      workspaceId: workspacePath.workspaceId,
      sourceKind: workspacePath.sourceKind,
      rootUri: workspacePath.rootUri,
      documentId: workspacePath.documentId,
      objectIdentity: primaryTargetId,
      resourceKind: workspacePath.resourceKind,
      workspacePath: workspacePath,
      currentFile: relativePath ? {
        rootUri: workspacePath.rootUri,
        relativePath: relativePath,
        documentId: workspacePath.documentId,
      objectIdentity: primaryTargetId,
        resourceKind: workspacePath.resourceKind
      } : null,
      allowedFiles: allowedFiles,
      targets: [targetEntry],
      policy: {
        writeRequiresExplicitTarget: true,
        allowedFilesSource: 'selected_page_ai_target',
        conflictPolicy: 'fail_on_dirty_or_stale'
      }
    };
  }

  function pageAiBlockingDirtyState(dirtyState) {
    var state = String(dirtyState || '').trim();
    var normalized = state.toLowerCase();
    if (normalized === 'dirty') return 'Dirty';
    if (normalized === 'stale') return 'Stale';
    if (normalized === 'deleted') return 'Deleted';
    if (normalized === 'externalmodified' || normalized === 'external-change-conflict' || normalized === 'hasexternalconflict') return 'ExternalModified';
    return '';
  }

  async function fetchPageAiTargetBufferState(editorTarget) {
    if (currentSourceKind() !== 'local_folder') return null;
    var target = editorTarget && typeof editorTarget === 'object' ? editorTarget : {};
    var workspacePath = target.workspacePath && typeof target.workspacePath === 'object' ? target.workspacePath : {};
    var resourceKind = String(target.resourceKind || target.editorKind || workspacePath.resourceKind || '').trim();
    if (resourceKind === 'office' || resourceKind === 'only_office' || resourceKind === 'onlyoffice') return null;
    var documentId = String(target.documentId || currentDocumentId() || '').trim();
    var rootUri = String(workspacePath.rootUri || currentRootUri() || '').trim();
    if (!documentId || !rootUri) return null;
    var relativePath = String(workspacePath.relativePath || '').trim()
      || localMarkdownRelativePathFromPageAiDocumentId(documentId);
    var url = new URL('/api/documents/buffer-state', window.location.origin);
    url.searchParams.set('documentId', documentId);
    url.searchParams.set('sourceKind', 'local_folder');
    url.searchParams.set('rootUri', rootUri);
    var workspaceId = String(target.workspaceId || resolveWorkspaceId(documentRef.body) || '').trim();
    if (workspaceId) url.searchParams.set('workspaceId', workspaceId);
    if (relativePath) url.searchParams.set('relativePath', relativePath);
    try {
      var response = await fetch(url.toString(), { cache: 'no-store', headers: { accept: 'application/json' } });
      var payload = await response.json().catch(function() { return null; });
      if (!response.ok || !payload || payload.ok !== true) return null;
      return payload.result || null;
    } catch (_) {
      return null;
    }
  }

  async function assertPageAiTargetWritable(editorTarget) {
    var workspacePath = editorTarget && editorTarget.workspacePath && typeof editorTarget.workspacePath === 'object' ? editorTarget.workspacePath : {};
    var resourceKind = String(editorTarget && (editorTarget.resourceKind || editorTarget.editorKind) || workspacePath.resourceKind || '').trim();
    var onlyofficeSessionId = String(editorTarget && (editorTarget.onlyofficeSessionId || editorTarget.bridgeSessionId) || '').trim();
    if (pageAiIsOnlyOfficeLiveTarget(editorTarget) && !onlyofficeSessionId) {
      var sessionError = new Error('ONLYOFFICE 资源仍在连接 MNote bridge，请等待 Office 页面加载完成后再让 AI 操作。');
      sessionError.code = 'page_ai_onlyoffice_bridge_not_ready';
      throw sessionError;
    }
    var snapshotState = pageAiBlockingDirtyState(editorTarget && editorTarget.dirtyState);
    var bufferState = await fetchPageAiTargetBufferState(editorTarget);
    var bufferDirtyState = pageAiBlockingDirtyState(bufferState && bufferState.dirtyState);
    var blockedState = bufferDirtyState || snapshotState;
    if (!blockedState) return bufferState;
    var documentId = String(editorTarget && editorTarget.documentId || currentDocumentId() || '').trim();
    var error = new Error('目标文档存在未保存或外部变更状态（' + blockedState + '），请先保存、解决冲突或刷新后再让 AI 写入。');
    error.code = 'page_ai_target_buffer_not_writable';
    error.documentId = documentId;
    error.dirtyState = blockedState;
    throw error;
  }

  function currentPageAiSelectedText() {
    try {
      var selection = window.getSelection ? window.getSelection() : null;
      return selection ? searchText(selection.toString() || '') : '';
    } catch (_) {
      return '';
    }
  }

  function pageAiProjectionBlocks(aggregate) {
    var blocks = aggregate && aggregate.body && aggregate.body.blockDocument && aggregate.body.blockDocument.blocks;
    return Array.isArray(blocks) ? blocks : [];
  }

  function pageAiBlockText(block) {
    return searchText(block && (block.text || block.title || block.content) || '');
  }

  function pageAiSelectedBlockIdsFromSelection() {
    try {
      var selection = window.getSelection ? window.getSelection() : null;
      if (!selection || selection.rangeCount === 0 || searchText(selection.toString() || '') === '') return [];
      var range = selection.getRangeAt(0);
      var editor = documentRef.querySelector('[data-testid="mnote-leptos-tiptap-island-editor-root"] .editor-surface .ProseMirror');
      if (!(editor instanceof HTMLElement)) return [];
      return Array.from(editor.children).filter(function(node) {
        if (!(node instanceof HTMLElement)) return false;
        try {
          return range.intersectsNode(node);
        } catch (_) {
          return false;
        }
      }).map(function(node) {
        return searchText(node.getAttribute('data-id') || node.id || '');
      }).filter(Boolean);
    } catch (_) {
      return [];
    }
  }

  function pageAiBlocksToPageXml(blocks, aggregate) {
    var revision = aggregate && aggregate.body ? String(aggregate.body.revision || '') : '';
    var pageId = currentDocumentId() || 'current-page';
    var lines = ['<page id="' + escapeHtml(pageId) + '" revision="' + escapeHtml(revision) + '">'];
    blocks.forEach(function(block) {
      var blockId = String(block && (block.blockId || block.id) || '');
      var type = String(block && block.type || 'paragraph');
      var revisionRef = String(block && block.revisionRef || '');
      var level = block && block.attrs && block.attrs.level ? ' level="' + escapeHtml(block.attrs.level) + '"' : '';
      lines.push('  <block id="' + escapeHtml(blockId) + '" type="' + escapeHtml(type) + '" revisionRef="' + escapeHtml(revisionRef) + '"' + level + '>' + escapeHtml(pageAiBlockText(block)) + '</block>');
    });
    lines.push('</page>');
    return lines.join('\n');
  }

  function buildPageAiContext(contextSnapshot, scope, selectedText) {
    var aggregate = contextSnapshot.aggregate || {};
    var body = aggregate.body || {};
    var allBlocks = pageAiProjectionBlocks(aggregate);
    var selectedBlockIds = scope === 'selection' ? pageAiSelectedBlockIdsFromSelection() : [];
    var selectedSet = {};
    selectedBlockIds.forEach(function(id) { selectedSet[id] = true; });
    var selectedBlocks = selectedBlockIds.length
      ? allBlocks.filter(function(block) { return selectedSet[String(block.blockId || block.id || '')]; })
      : [];
    var contextBlocks = selectedBlocks.length ? selectedBlocks : allBlocks.slice(0, 120);
    var truncated = !selectedBlocks.length && allBlocks.length > contextBlocks.length;
    return {
      schema: 'mnote.page_ai_context.v1',
      workspaceId: resolveWorkspaceId(documentRef.body),
      documentId: currentDocumentId(),
      activeEditorTarget: currentPageAiScopedEditorTarget(),
      openEditorsSnapshot: currentPageAiOpenEditorsSnapshot(),
      scope: scope,
      revision: body.revision || null,
      conflictDetectionKey: body.conflictDetectionKey || null,
      selectedText: selectedText || '',
      selectedBlockIds: selectedBlockIds,
      allowedTargetBlockIds: selectedBlockIds,
      selectedBlocks: selectedBlocks,
      contextBlocks: contextBlocks,
      pageText: contextBlocks.map(pageAiBlockText).filter(Boolean).join('\n'),
      pageXml: pageAiBlocksToPageXml(contextBlocks, aggregate),
      truncated: truncated,
      warnings: truncated ? [{ code: 'page_ai_context_truncated', message: '页面 AI context 已按前 120 个块裁剪' }] : []
    };
  }

  function pageAiScopedPageContext(contextSnapshot) {
    var aggregate = contextSnapshot.aggregate || {};
    var body = contextSnapshot.body || {};
    var subtree = contextSnapshot.subtree || null;
    var outline = subtree && subtree.outline ? subtree.outline : null;
    var scope = pageUiState.pageAiContextScope || 'page';
    var title = aggregate.head && aggregate.head.title ? aggregate.head.title : '';
    var selectedText = scope === 'selection' ? currentPageAiSelectedText() : '';
    var aiContext = buildPageAiContext(contextSnapshot, scope, selectedText);
    var editorTarget = aiContext.activeEditorTarget || currentPageAiScopedEditorTarget();
    return {
      pageContext: {
        contextScope: scope,
        documentBlocks: null,
        node: {
          documentId: currentDocumentId(),
          title: title
        },
        subtree: null,
        outline: null,
        pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
        evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
        pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
        contentAccess: 'mnote.context.read_current_page',
        aiContext: aiContext
      },
      editorTarget: editorTarget,
      selectedText: selectedText,
      selectedBlockId: aiContext.selectedBlockIds[0] || null
    };
  }

  return {
    currentPageAiSelectedText,
    currentPageAiEditorTarget,
    currentPageAiPageEditorTarget,
    currentPageAiScopedEditorTarget,
    currentPageAiOpenEditorsSnapshot,
    pageAiBlockText,
    pageAiBlockingDirtyState,
    pageAiBlocksToPageXml,
    pageAiBuildAgentTargetPackage,
    pageAiBuildAllowedRoots,
    pageAiBuildContextRefs,
    pageAiBuildRunTargetSnapshot,
    pageAiCloneJson,
    pageAiContextKindsFromRefs,
    pageAiEditorTargetCandidates,
    pageAiFallbackEditorTarget,
    pageAiPageContextForRefs,
    pageAiProjectionBlocks,
    pageAiResourceKindForTarget,
    pageAiSelectedBlockIdsFromSelection,
    pageAiSetRunTargetSnapshot,
    pageAiScopedPageContext,
    pageAiTargetFromOpenEditor,
    pageAiTargetId,
    pageAiWorkspacePathForDocument,
    pageAiWorkspacePathForTarget,
    assertPageAiTargetInCurrentWorkspace,
    assertPageAiTargetWritable,
    buildPageAiContext,
    fetchPageAiTargetBufferState,
    localMarkdownDocumentIdFromPageAiRelativePath,
    localMarkdownRelativePathFromPageAiDocumentId,
  };
}
