import type {
  ChatAttachment,
  ChatStreamEvent,
  ChatStreamListener,
  DesktopApi,
  DiagnosticsExportResult,
  ExpertDefinition,
  ProjectIntentSuggestion,
  SaveConfigInput,
  WorkspaceSummary
} from "@qjclaw/shared-types"
import {
  FIXED_DIGITAL_HUMAN_CONFIG,
  FIXED_DOUYIN_RUNTIME_CONFIG,
  FIXED_EXPERT_MODEL_ENDPOINTS
} from "@qjclaw/shared-types"
import { createClientMessageId } from "./chat-utils"
import { HOME_CHAT_PROJECT_ID } from "./constants"

const mockUi = {
  app: "千匠Claw",
  bindDesc: "你好，我是千匠问天。可以先帮你整理 Excel、汇总资料、检索公开信息、拆解执行任务；如果你要做小红书或抖音内容，也可以切到对应专家继续处理。",
  preparingReply: "正在理解你的问题",
  waitingReply: "已收到问题，正在组织回答"
} as const
const mockChatStreamListeners = new Set<ChatStreamListener>();

function emitMockChatStreamEvent(event: ChatStreamEvent) {
  for (const listener of mockChatStreamListeners) {
    listener(event);
  }
}

const mockExpertDefinitions: ExpertDefinition[] = [
  {
    id: "content-account-planner",
    name: "内容账号规划专家",
    entryMode: "standalone",
    description: "聚焦内容账号定位、栏目规划与选题节奏。",
    starterPrompt: "我想做一个内容账号，请先帮我梳理定位、目标人群、栏目结构和更新节奏。",
    promptFile: "content-account-planner.md",
    promptAvailable: true,
    projectMatchKeywords: ["账号规划", "content-account", "planner"]
  },
  {
    id: "zhihu",
    name: "知乎专家",
    entryMode: "standalone",
    description: "聚焦知乎回答选题、结构设计与持续输出。",
    starterPrompt: "我想做知乎内容，请先帮我梳理选题方向、回答结构和更新节奏。",
    promptFile: "zhihu.md",
    promptAvailable: true,
    projectMatchKeywords: ["知乎", "zhihu"]
  },
  {
    id: "wechat-official-account",
    name: "公众号专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理公众号选题、排版与发布需求。",
    starterPrompt: "我现在要做公众号内容，请按公众号的结构和语气协助我。",
    promptAvailable: true,
    projectMatchKeywords: ["公众号", "wechat"]
  },
  {
    id: "x-platform",
    name: "X专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理 X 平台内容策划与表达。",
    starterPrompt: "我现在要做 X 平台内容，请按 X 的表达风格协助我。",
    promptAvailable: true,
    projectMatchKeywords: ["x", "twitter"]
  },
  {
    id: "tiktok",
    name: "Tiktok专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理 Tiktok 内容策划与脚本。",
    starterPrompt: "我现在要做 Tiktok 内容，请按 Tiktok 的节奏和表达方式协助我。",
    promptAvailable: true,
    projectMatchKeywords: ["tiktok"]
  },
  {
    id: "poster",
    name: "海报专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理海报文案与视觉需求整理。",
    starterPrompt: "我现在要做海报，请先帮我整理海报目标、卖点和视觉方向。",
    promptAvailable: true,
    projectMatchKeywords: ["海报", "poster"]
  },
  {
    id: "geo",
    name: "GEO专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理 GEO 相关策略与内容。",
    starterPrompt: "我现在要处理 GEO 相关需求，请先帮我梳理目标、约束和执行方向。",
    promptAvailable: true,
    projectMatchKeywords: ["geo"]
  },
  {
    id: "sales-champion",
    name: "销售冠军",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理销售目标拆解、成交话术、异议处理与转化推进。",
    starterPrompt: "我想以销售冠军的方式推进成交，请帮我梳理目标客户、沟通话术、异议处理和转化动作。",
    promptAvailable: true,
    projectMatchKeywords: ["销售冠军", "销冠", "sales champion", "sales-champion"]
  },
  {
    id: "precision-leads",
    name: "平台精准线索专家",
    entryMode: "home-chat-shortcut",
    description: "从首页对话继续处理平台线索筛选与转化需求。",
    starterPrompt: "我现在要做平台精准线索，请先帮我梳理目标客群、筛选条件和转化路径。",
    promptAvailable: true,
    projectMatchKeywords: ["精准线索", "线索", "leads"]
  }
];

const mockProjects: WorkspaceSummary["projects"] = [
  { id: "content-account-planner", name: "content-account-planner", displayName: "内容账号规划专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "planner" },
  { id: "zhihu", name: "zhihu-expert", displayName: "知乎专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "zhihu" },
  { id: "xiaohongshu", name: "openclaw-xiaohongshu-skills-delivery", displayName: "小红书专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "xiaohongshu" },
  { id: "douyin", name: "openclaw-douyin-skills-delivery", displayName: "抖音专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 2, ready: true, platform: "douyin" },
  { id: "browser-ops", name: "browser-ops", displayName: "浏览器自动化专家", version: "demo-project", updatedAt: new Date().toISOString(), skillCount: 3, ready: true, platform: "browser" }
];

const mockSessionsByProject: Record<string, WorkspaceSummary["sessions"]> = {
  [HOME_CHAT_PROJECT_ID]: [
    { id: "project:home-chat:default", projectId: HOME_CHAT_PROJECT_ID, title: "首页对话", updatedAt: new Date().toISOString() }
  ],
  xiaohongshu: [
    { id: "project:xiaohongshu:default", projectId: "xiaohongshu", title: "\u9009\u9898\u8ba8\u8bba", updatedAt: new Date().toISOString() },
    { id: "project:xiaohongshu:publish", projectId: "xiaohongshu", title: "\u53d1\u5e03\u65f6\u95f4\u5b89\u6392", updatedAt: new Date().toISOString() }
  ],
  douyin: [
    { id: "project:douyin:default", projectId: "douyin", title: "\u8fd0\u8425\u7b56\u5212", updatedAt: new Date().toISOString() }
  ]
};

function getMockSessions(projectId?: string): WorkspaceSummary["sessions"] {
  return mockSessionsByProject[projectId ?? mockProjects[0].id] ?? [];
}
const mockProjectIntentSuggestions: Record<string, ProjectIntentSuggestion> = {
  xiaohongshu: {
    projectId: "xiaohongshu",
    projectName: "openclaw-xiaohongshu-skills-delivery",
    projectDisplayName: "小红书专家",
    score: 0.96,
    confidence: "high",
    reason: "命中了小红书内容创作与发布场景",
    matchedAliases: ["小红书", "笔记", "发布时间"]
  },
  douyin: {
    projectId: "douyin",
    projectName: "openclaw-douyin-skills-delivery",
    projectDisplayName: "抖音专家",
    score: 0.95,
    confidence: "high",
    reason: "命中了抖音脚本与镜头节奏场景",
    matchedAliases: ["抖音", "脚本", "镜头"]
  }
};

function resolveMockProjectIntentSuggestion(prompt: string): ProjectIntentSuggestion | null {
  const normalized = prompt.trim().toLowerCase();
  if (!normalized) {
    return null;
  }
  if (normalized.includes("小红书") || normalized.includes("笔记")) {
    return mockProjectIntentSuggestions.xiaohongshu;
  }
  if (normalized.includes("抖音") || normalized.includes("脚本")) {
    return mockProjectIntentSuggestions.douyin;
  }
  return null;
}

export const mockDesktopApi = {
  workspace: {
    getSummary: async () => ({
      shellReady: true,
      apiKeyConfigured: true,
      bindingRequired: false,
      setupRequired: false,
      setupMode: "employee-key",
      chatReady: true,
      chatLaunchState: "ready",
      chatStatusMessage: "\u804a\u5929\u670d\u52a1\u5df2\u5c31\u7eea\u3002",
      startupPhase: "ready",
      startupMessage: "\u804a\u5929\u670d\u52a1\u5df2\u5c31\u7eea\u3002",
      employeeId: "demo",
      employeeName: mockUi.app + " Demo",
      welcomeMessage: mockUi.bindDesc,
      modelId: "qwen3.6-plus",
      modelDisplayName: "qwen3.6-plus",
      configVersion: "demo-config",
      lastFetchedAt: new Date().toISOString(),
      runtimeCloudState: "ready",
      runtimeState: "running",
      runtimeMessage: "mock",
      currentProjectId: mockProjects[0].id,
      currentProjectName: mockProjects[0].displayName,
      projectVersion: mockProjects[0].version,
      projectReady: mockProjects[0].ready,
      projectCount: mockProjects.length,
      projects: mockProjects,
      sessions: getMockSessions(mockProjects[0].id),
      skillCount: 2,
      skills: [
        { id: "sheet", name: "Spreadsheet Tools", description: "Process spreadsheets and data summaries.", category: "office", enabled: true, ready: true, downloadState: "ready", fileName: "sheet.md" },
        { id: "doc", name: "Document Tools", description: "Process documents and content organization.", category: "office", enabled: true, ready: true, downloadState: "ready", fileName: "doc.md" }
      ],
      plugins: [
        { id: "spreadsheet-tools", name: "\u8868\u683c\u5de5\u5177", description: "\u8bfb\u53d6\u548c\u5904\u7406 Excel\u3001CSV \u7b49\u5e38\u89c1\u8868\u683c\u6587\u4ef6\u3002", status: "included", includedByDefault: true },
        { id: "document-tools", name: "\u6587\u6863\u5de5\u5177", description: "\u5904\u7406 txt\u3001md\u3001docx\u3001pdf \u7b49\u5e38\u89c1\u6587\u6863\u3002", status: "included", includedByDefault: true },
        { id: "web-tools", name: "\u7f51\u9875\u4fe1\u606f\u63d0\u53d6", description: "\u6293\u53d6\u7f51\u9875\u5185\u5bb9\u5e76\u8fdb\u884c\u63d0\u53d6\u3001\u6e05\u6d17\u548c\u6c47\u603b\u3002", status: "included", includedByDefault: true }
      ]
    }),
    warmup: async () => ({ accepted: true, state: "scheduled", message: "mock" })
  },
  gateway: {
    status: async () => ({ state: "connected", url: "ws://127.0.0.1:18789", host: "127.0.0.1", port: 18789, version: "mock-gateway", transport: "websocket", message: "mock" }),
    connect: async () => ({ state: "connected", url: "ws://127.0.0.1:18789", host: "127.0.0.1", port: 18789, version: "mock-gateway", transport: "websocket", message: "mock" }),
    disconnect: async () => ({ state: "connected", url: "ws://127.0.0.1:18789", host: "127.0.0.1", port: 18789, version: "mock-gateway", transport: "websocket", message: "mock" }),
    reconnect: async () => ({ state: "connected", url: "ws://127.0.0.1:18789", host: "127.0.0.1", port: 18789, version: "mock-gateway", transport: "websocket", message: "mock" }),
    health: async () => ({ ok: true, latencyMs: 1, checkedAt: new Date().toISOString(), transport: "websocket" }),
    tailLogs: async () => []
  },
  runtime: {
    getStatus: async () => ({ requestedMode: "bundled-runtime", selectedMode: "bundled-runtime", activeMode: "bundled-runtime", payloadState: "ready", processState: "running", runtimeDir: "D:/runtime", nodeExecutable: "node.exe", openClawEntry: "index.js", defaultConfigPath: "openClaw.json", runtimeDataDir: "D:/data", runtimeStateDir: "D:/state", runtimeLogsDir: "D:/logs", logFilePath: "D:/logs/runtime.log", gatewayTokenConfigured: true, pythonReady: true, pythonVersion: "3.12", installedPythonPackages: ["openpyxl", "pandas", "requests", "beautifulsoup4", "lxml", "pypdf", "python-docx", "charset-normalizer", "pyyaml"], message: "Mock runtime is running.", modeReason: "mock", detectedFiles: [], missingFiles: [], checkedAt: new Date().toISOString() }),
    tailLogs: async () => [],
    start: async () => mockDesktopApi.runtime.getStatus(),
    stop: async () => mockDesktopApi.runtime.getStatus(),
    restart: async () => mockDesktopApi.runtime.getStatus(),
    health: async () => ({ ok: true, checkedAt: new Date().toISOString(), processState: "running", selectedMode: "bundled-runtime", activeMode: "bundled-runtime" })
  },
  runtimeCloud: {
    getStatus: async () => ({ state: "ready", baseUrl: "https://xuphfkscoptnjoaecbvn.supabase.co/functions/v1", apiKeyConfigured: true, lastFetchedAt: new Date().toISOString(), config: { employeeId: "demo", employeeName: mockUi.app + " Demo", configVersion: "demo-config", fetchedAt: new Date().toISOString(), modelId: "gpt-5.4-mini", modelDisplayName: "GPT-5.4 Mini", skillCount: 2, channelCount: 0, personaPromptConfigured: true, welcomeMessage: mockUi.bindDesc } }),
    fetchConfig: async () => mockDesktopApi.runtimeCloud.getStatus()
  },
  runtimeTelemetry: {
    getStatus: async () => ({ state: "running", heartbeatIntervalMs: 30000, configSyncIntervalMs: 60000, eventFlushIntervalMs: 10000, eventBatchSize: 20, queuedEventCount: 0, droppedEventCount: 0, messageCount: 1, activeConversationCount: 1, errorCount: 0, lastHeartbeatAt: new Date().toISOString(), lastConfigSyncAt: new Date().toISOString(), configSyncSuccessCount: 1, heartbeatSuccessCount: 1, lastEventTypes: ["startup"], totalAcceptedEventCount: 1 })
  },
  config: {
    load: async () => ({
      setupMode: "employee-key",
      provider: "openai",
      baseUrl: "https://api.openai.com/v1",
      apiKeyConfigured: true,
      gatewayTokenConfigured: true,
      authTokenConfigured: false,
      defaultModel: "gpt-5.4-mini",
      workspacePath: "D:/workspace",
      gatewayUrl: "ws://127.0.0.1:18789",
      cloudApiBaseUrl: "",
      runtimeCloudApiBaseUrl: "https://xuphfkscoptnjoaecbvn.supabase.co/functions/v1",
      runtimeMode: "bundled-runtime",
      expertModelConfig: {
        image: { ...FIXED_EXPERT_MODEL_ENDPOINTS.image, apiKeyConfigured: true },
        video: { ...FIXED_EXPERT_MODEL_ENDPOINTS.video, apiKeyConfigured: false },
        copywriting: { ...FIXED_EXPERT_MODEL_ENDPOINTS.copywriting, apiKeyConfigured: true },
        digitalHuman: {
          ...FIXED_DIGITAL_HUMAN_CONFIG,
          volcAccessKeyConfigured: false,
          volcSecretKeyConfigured: false,
          qiniuAccessKeyConfigured: false,
          qiniuSecretKeyConfigured: false
        }
      },
      douyinRuntimeConfig: {
        videoAnalyzer: {
          ...FIXED_DOUYIN_RUNTIME_CONFIG.videoAnalyzer,
          apiKeyConfigured: false,
        },
        replicationBrief: {
          ...FIXED_DOUYIN_RUNTIME_CONFIG.replicationBrief,
          apiKeyConfigured: false,
        },
        vectcut: {
          ...FIXED_DOUYIN_RUNTIME_CONFIG.vectcut,
          apiKeyConfigured: false
        }
      },
      xhsFeishuConfig: {
        appIdConfigured: false,
        appSecretConfigured: false,
        appTokenConfigured: false,
        tableIdConfigured: false
      }
    }),
    pickWorkspaceDirectory: async (currentPath?: string) => currentPath || "D:/workspace",
    save: async (input: SaveConfigInput) => ({
      setupMode: input.setupMode,
      provider: input.provider,
      baseUrl: input.baseUrl,
      apiKeyConfigured: true,
      gatewayTokenConfigured: true,
      authTokenConfigured: false,
      defaultModel: input.defaultModel,
      workspacePath: input.workspacePath,
      gatewayUrl: input.gatewayUrl,
      cloudApiBaseUrl: input.cloudApiBaseUrl,
      runtimeCloudApiBaseUrl: input.runtimeCloudApiBaseUrl,
      runtimeMode: input.runtimeMode,
      expertModelConfig: {
        image: {
          ...FIXED_EXPERT_MODEL_ENDPOINTS.image,
          apiKeyConfigured: Boolean(input.expertModelConfig?.image?.apiKey?.trim()),
        },
        video: {
          ...FIXED_EXPERT_MODEL_ENDPOINTS.video,
          apiKeyConfigured: Boolean(input.expertModelConfig?.video?.apiKey?.trim())
        },
        copywriting: {
          ...FIXED_EXPERT_MODEL_ENDPOINTS.copywriting,
          apiKeyConfigured: Boolean(input.expertModelConfig?.copywriting?.apiKey?.trim()),
        },
        digitalHuman: {
          ...FIXED_DIGITAL_HUMAN_CONFIG,
          volcAccessKeyConfigured: Boolean(input.expertModelConfig?.digitalHuman?.volcAccessKey?.trim()),
          volcSecretKeyConfigured: Boolean(input.expertModelConfig?.digitalHuman?.volcSecretKey?.trim()),
          qiniuAccessKeyConfigured: Boolean(input.expertModelConfig?.digitalHuman?.qiniuAccessKey?.trim()),
          qiniuSecretKeyConfigured: Boolean(input.expertModelConfig?.digitalHuman?.qiniuSecretKey?.trim())
        }
      },
      douyinRuntimeConfig: {
        videoAnalyzer: {
          baseUrl: input.douyinRuntimeConfig?.videoAnalyzer?.baseUrl?.trim() || FIXED_DOUYIN_RUNTIME_CONFIG.videoAnalyzer.baseUrl,
          apiKeyConfigured: Boolean(input.douyinRuntimeConfig?.videoAnalyzer?.apiKey?.trim()),
          modelId: input.douyinRuntimeConfig?.videoAnalyzer?.modelId?.trim() || ""
        },
        replicationBrief: {
          baseUrl: input.douyinRuntimeConfig?.replicationBrief?.baseUrl?.trim() || FIXED_DOUYIN_RUNTIME_CONFIG.replicationBrief.baseUrl,
          apiKeyConfigured: Boolean(input.douyinRuntimeConfig?.replicationBrief?.apiKey?.trim()),
          modelId: input.douyinRuntimeConfig?.replicationBrief?.modelId?.trim() || ""
        },
        vectcut: {
          baseUrl: input.douyinRuntimeConfig?.vectcut?.baseUrl?.trim() || FIXED_DOUYIN_RUNTIME_CONFIG.vectcut.baseUrl,
          fileBaseUrl: input.douyinRuntimeConfig?.vectcut?.fileBaseUrl?.trim() || FIXED_DOUYIN_RUNTIME_CONFIG.vectcut.fileBaseUrl,
          apiKeyConfigured: Boolean(input.douyinRuntimeConfig?.vectcut?.apiKey?.trim())
        }
      },
      xhsFeishuConfig: {
        appIdConfigured: Boolean(input.xhsFeishuConfig?.appId?.trim()),
        appSecretConfigured: Boolean(input.xhsFeishuConfig?.appSecret?.trim()),
        appTokenConfigured: Boolean(input.xhsFeishuConfig?.appToken?.trim()),
        tableIdConfigured: Boolean(input.xhsFeishuConfig?.tableId?.trim())
      }
    })
  },
  window: {
    minimize: async () => undefined,
    maximize: async () => undefined,
    close: async () => undefined
  },
  auth: { getSessionSummary: async () => ({ state: "anonymous", tokenConfigured: false, message: "mock" }), signIn: async () => ({ state: "authenticated", tokenConfigured: true, message: "mock" }), signOut: async () => ({ state: "anonymous", tokenConfigured: false, message: "mock" }) },
  profile: { getSummary: async () => ({ userId: "demo", displayName: "demo", email: "demo@example.com", organizationName: "demo" }) },
  credits: { getSummary: async () => ({ balance: 0, granted: 0, used: 0, currency: "credits", status: "ok", updatedAt: new Date().toISOString() }) },
  skills: { list: async () => [] },
  experts: { list: async () => mockExpertDefinitions },
  projects: {
    list: async () => mockProjects,
    setActive: async (projectId: string) => {
      const activeProject = mockProjects.find((project) => project.id === projectId) ?? mockProjects[0];
      return {
        ...(await mockDesktopApi.workspace.getSummary()),
        currentProjectId: activeProject?.id,
        currentProjectName: activeProject?.displayName,
        projectVersion: activeProject?.version,
        projectReady: activeProject?.ready,
        sessions: getMockSessions(activeProject?.id)
      };
    },
    resolveIntent: async (prompt: string) => resolveMockProjectIntentSuggestion(prompt)
  },
  modelConfig: { getSummary: async () => ({ source: "cloud", updatedAt: new Date().toISOString(), fetchedAt: new Date().toISOString(), routingMode: "platform-managed", fallbackMode: "cloud-required", defaultChatModelId: "gpt-5.4-mini", defaultChatModelLabel: "GPT-5.4 Mini", items: [], skillBindings: [], message: "mock" }) },
  system: { getSummary: async () => ({ appName: "千匠问天", appVersion: "0.1.0", isPackaged: false, platform: "win32", arch: "x64", appPath: "D:/qjclaw/apps/desktop", resourcesPath: "D:/qjclaw/apps/desktop/dist", userDataPath: "D:/qjclaw/.tmp/user-data", logsPath: "D:/qjclaw/.tmp/logs" }) },
  tasks: { listByDate: async () => [] },
  chat: {
    listSessions: async () => getMockSessions(),
    listSessionsByProject: async (projectId: string) => getMockSessions(projectId),
    createSession: async (title?: string) => ({ id: `project:xiaohongshu:${createClientMessageId("session")}`, projectId: "xiaohongshu", title: title || "\u65b0\u5bf9\u8bdd", updatedAt: new Date().toISOString() }),
    createSessionForProject: async (projectId: string, title?: string) => ({ id: `project:${projectId}:${createClientMessageId("session")}`, projectId, title: title || "\u65b0\u5bf9\u8bdd", updatedAt: new Date().toISOString() }),
    closeSession: async (sessionId: string) => getMockSessions(sessionId.split(":")[1]),
    listMessages: async () => [],
    pickAttachments: async () => [],
    pickImageAttachment: async () => null,
    readImageAttachmentDataUrl: async () => null,
    sendPrompt: async (sessionId: string, prompt: string, skillId?: string, _attachments?: ChatAttachment[]) => ({ sessionId: sessionId || "project:xiaohongshu:default", reply: { id: "reply-1", role: "assistant", content: "Mock: " + prompt, createdAt: new Date().toISOString() }, executionPolicy: { source: "client-config", modelId: "qwen3.6-plus", modelLabel: "qwen3.6-plus", routingMode: "platform-managed", skillId, skillName: skillId, message: "mock" } }),
    streamPrompt: async (_sessionId: string, prompt: string, skillId?: string, _attachments?: ChatAttachment[]) => {
      const requestId = createClientMessageId("mock-request");
      const runId = createClientMessageId("mock-run");
      const sessionId = _sessionId || "project:xiaohongshu:default";
      const userMessageId = createClientMessageId("mock-user");
      const assistantMessageId = createClientMessageId("mock-assistant");
      const executionPolicy = { source: "client-config" as const, modelId: "qwen3.6-plus", modelLabel: "qwen3.6-plus", routingMode: "platform-managed" as const, skillId, skillName: skillId, message: "mock" };
      const replyText = "Mock: " + prompt;
      const chunks = replyText.match(/.{1,6}/g) ?? [replyText];
      let fullText = "";
      window.setTimeout(() => {
        emitMockChatStreamEvent({ type: "status", requestId, sessionId, runId, stage: "prepare-request", label: mockUi.preparingReply });
        emitMockChatStreamEvent({ type: "started", requestId, sessionId, runId, executionPolicy });
      }, 0);
      window.setTimeout(() => {
        emitMockChatStreamEvent({ type: "status", requestId, sessionId, runId, stage: "await-model", label: mockUi.waitingReply });
      }, 30);
      chunks.forEach((chunk, index) => {
        window.setTimeout(() => {
          fullText += chunk;
          emitMockChatStreamEvent({ type: "delta", requestId, sessionId, runId, textDelta: chunk, fullText });
        }, 90 * (index + 1));
      });
      window.setTimeout(() => {
        emitMockChatStreamEvent({
          type: "completed",
          requestId,
          sessionId,
          runId,
          reply: { id: assistantMessageId, role: "assistant", content: replyText, createdAt: new Date().toISOString() },
          executionPolicy
        });
      }, 90 * (chunks.length + 1));
      return { requestId, sessionId, runId, userMessageId, assistantMessageId, executionPolicy };
    },
    onStreamEvent: (listener: ChatStreamListener) => {
      mockChatStreamListeners.add(listener);
      return () => {
        mockChatStreamListeners.delete(listener);
      };
    }
  },
  diagnostics: { openControlUi: async () => undefined, exportSnapshot: async (): Promise<DiagnosticsExportResult> => ({ filePath: "D:/qjclaw/.tmp/mock-diagnostics.json", createdAt: new Date().toISOString() }) }
} as unknown as DesktopApi;
