import type { Event as OpencodeEvent, Part } from "@opencode-ai/sdk/v2";

import { writeLlmRequestAuditLog } from "../audit/llmRequestAudit.js";
import { type SupportedModel } from "../chat/models.js";
import { logger } from "../logger.js";
import {
  type PermissionReply,
  type OpencodeRuntimeAdapter,
} from "../runtime/opencode.js";
import {
  buildPermissionDetail,
  buildPermissionV2Detail,
  buildReasoningProgressDetail,
  buildSessionStatusDetail,
  buildToolProgressDetail,
  collectTextContent,
  extractRequestReason,
  extractSkillAuditInfo,
  getErrorMessage,
  getToolProgressTitle,
  getUnknownErrorMessage,
  hasToolParams,
  isPermissionAskedEvent,
  isPermissionRepliedEvent,
  isPermissionV2AskedEvent,
  isPermissionV2RepliedEvent,
  isQuestionAskedEvent,
  isObjectRecord,
  isQuestionRejectedEvent,
  isQuestionRepliedEvent,
  isQuestionV2AskedEvent,
  isQuestionV2RejectedEvent,
  isQuestionV2RepliedEvent,
  isSessionEvent,
  isSkillEvent,
  logDevelopmentDebug,
  normalizeQuestionAnswers,
  normalizeQuestionPayload,
  normalizeQuestionToolPayload,
  normalizeTodoPriority,
  normalizeTodoStatus,
  normalizeToolParams,
  normalizeToolStatus,
  type PermissionRequestPayload,
  type QuestionRequestPayload,
  type TodoItemPayload,
  type TodoUpdatePayload,
} from "./chatStreamEvents.js";

export {
  collectTextContent,
  type PermissionRequestPayload,
  type QuestionRequestPayload,
  type TodoItemPayload,
  type TodoUpdatePayload,
} from "./chatStreamEvents.js";

export type ApprovalMode = "request" | "always";

type StreamPromptOptions = {
  runtime: OpencodeRuntimeAdapter;
  sessionId: string;
  clientSessionId: string;
  message: string;
  model?: SupportedModel;
  approvalMode?: ApprovalMode;
  traceId?: string;
  projectId?: string;
  signal?: AbortSignal;
  write: (event: string, data: Record<string, unknown>) => void;
};

type ProgressStatus = "running" | "completed" | "error";

type ProgressPayload = {
  id: string;
  phase: string;
  status: ProgressStatus;
  title: string;
  detail?: string;
};

const getPermissionTarget = (metadata: unknown) => {
  if (!isObjectRecord(metadata)) {
    return undefined;
  }
  for (const key of ["command", "path", "file", "filepath", "directory"]) {
    const value = metadata[key];
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
  }
  return undefined;
};

const toRuntimeModel = (model?: SupportedModel) => {
  if (!model) {
    return undefined;
  }
  const [providerID, modelID] = model.split("/");
  if (!providerID || !modelID) {
    return undefined;
  }
  return {
    providerID,
    modelID,
  };
};

const emitFallbackMessage = async (
  runtime: OpencodeRuntimeAdapter,
  sessionId: string,
  clientSessionId: string,
  write: (event: string, data: Record<string, unknown>) => void,
) => {
  const messages = await runtime.messages(sessionId);
  const assistantMessage = [...messages]
    .reverse()
    .find((message) => message.info.role === "assistant");
  const parts = assistantMessage?.parts ?? [];
  const text = collectTextContent(parts);
  if (text) {
    write("token", {
      session_id: clientSessionId,
      content: text,
    });
  }
};

export const streamPromptResponse = async ({
  runtime,
  sessionId,
  clientSessionId,
  message,
  model,
  approvalMode = "request",
  traceId,
  projectId,
  signal,
  write,
}: StreamPromptOptions): Promise<{
  aborted: boolean;
  failed: boolean;
  toolCallCount: number;
}> => {
  const eventStream = await runtime.subscribeEvents();
  const iterator = eventStream[Symbol.asyncIterator]();
  const requestStartedAt = Date.now();
  const promptStartedAt = Date.now();
  const progressStartedAtMap = new Map<string, number>();
  const finalizedProgressIds = new Set<string>();
  const emittedToolParts = new Set<string>();
  const emittedQuestionToolParts = new Set<string>();
  const emittedQuestionRequestIds = new Set<string>();
  const partTypes = new Map<string, Part["type"]>();
  const pendingPartTextDeltas = new Map<string, string[]>();
  const reasoningDeltas = new Map<string, string[]>();
  const reasoningStatuses = new Map<string, "running" | "completed">();
  const toolStatuses = new Map<string, string>();
  let firstSessionEventLogged = false;
  let firstNonStatusEventLogged = false;
  let firstTokenLogged = false;
  let firstReasoningLogged = false;
  let firstToolEventLogged = false;
  let lastSessionStatus: string | null = null;
  let lastSessionStatusMessage: string | null = null;
  let sawResponseActivity = false;
  let emittedText = false;
  let toolCallCount = 0;
  let done = false;
  let promptSettled = false;
  let aborted = signal?.aborted ?? false;
  let failed = false;
  const debugContext = {
    sessionId,
    clientSessionId,
    traceId,
    projectId,
    model: model ?? null,
  };

  logDevelopmentDebug("chat stream started", {
    ...debugContext,
    messageChars: message.length,
  });

  const abortPromise = signal
    ? new Promise<{ type: "abort" }>((resolve) => {
        if (signal.aborted) {
          resolve({ type: "abort" });
          return;
        }
        signal.addEventListener("abort", () => resolve({ type: "abort" }), {
          once: true,
        });
      })
    : null;

  const emitProgress = ({ id, phase, status, title, detail }: ProgressPayload) => {
    if (status === "running" && finalizedProgressIds.has(id)) {
      return;
    }

    const now = Date.now();
    const startedAt = progressStartedAtMap.get(id) ?? now;
    if (!progressStartedAtMap.has(id)) {
      progressStartedAtMap.set(id, startedAt);
    }

    if (status === "running") {
      write("progress", {
        session_id: clientSessionId,
        id,
        phase,
        status,
        title,
        detail,
        started_at: startedAt,
        elapsed_ms: Math.max(0, now - startedAt),
      });
      return;
    }

    const durationMs = Math.max(0, now - startedAt);
    finalizedProgressIds.add(id);
    progressStartedAtMap.delete(id);
    write("progress", {
      session_id: clientSessionId,
      id,
      phase,
      status,
      title,
      detail,
      started_at: startedAt,
      ended_at: now,
      duration_ms: durationMs,
    });
  };

  emitProgress({
    id: "request-received",
    phase: "start",
    status: "running",
    title: "已收到请求，正在启动 Agent 分析",
    detail: "已接收用户消息，正在建立会话并准备进入分析、规划和工具调用阶段。",
  });

  const promptPromise = runtime
    .prompt(sessionId, message, toRuntimeModel(model))
    .then(() => {
      promptSettled = true;
      logDevelopmentDebug("runtime.prompt resolved", {
        ...debugContext,
        elapsedMs: Math.max(0, Date.now() - promptStartedAt),
      });
    })
    .catch((error: unknown) => {
      promptSettled = true;
      logDevelopmentDebug("runtime.prompt failed", {
        ...debugContext,
        elapsedMs: Math.max(0, Date.now() - promptStartedAt),
        error: getUnknownErrorMessage(error),
      });
      throw error;
    });

  logDevelopmentDebug("runtime.prompt dispatched", {
    ...debugContext,
  });

  try {
    while (!done) {
      if (signal?.aborted) {
        aborted = true;
        logDevelopmentDebug("chat stream noticed abort signal", {
          ...debugContext,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        break;
      }

      const nextEvent = iterator
        .next()
        .then((result) => ({ type: "event" as const, result }));
      const nextPrompt = promptSettled
        ? null
        : promptPromise.then(
            () => ({ type: "prompt" as const }),
            (error: unknown) => ({ type: "prompt-error" as const, error }),
          );
      const next = await Promise.race(
        [
          ...(nextPrompt ? [nextEvent, nextPrompt] : [nextEvent]),
          ...(abortPromise ? [abortPromise] : []),
        ],
      );

      if (next.type === "abort") {
        aborted = true;
        break;
      }

      if (next.type === "prompt-error") {
        throw next.error;
      }
      if (next.type === "prompt") {
        continue;
      }
      if (next.result.done) {
        break;
      }

      const event = next.result.value as OpencodeEvent;
      if (!isSessionEvent(event, sessionId)) {
        continue;
      }

      if (!firstSessionEventLogged) {
        firstSessionEventLogged = true;
        logDevelopmentDebug("first session event received", {
          ...debugContext,
          eventType: event.type,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
          sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
        });
      }

      if (event.type === "session.status") {
        const nextStatus = event.properties.status.type;
        const nextStatusMessage =
          "message" in event.properties.status &&
          typeof event.properties.status.message === "string"
            ? event.properties.status.message
            : null;
        if (
          nextStatus !== lastSessionStatus ||
          nextStatusMessage !== lastSessionStatusMessage
        ) {
          lastSessionStatus = nextStatus;
          lastSessionStatusMessage = nextStatusMessage;
          logDevelopmentDebug("session status updated", {
            ...debugContext,
            status: nextStatus,
            statusMessage: nextStatusMessage,
            elapsedMs: Math.max(0, Date.now() - requestStartedAt),
          });
        }
        emitProgress({
          id: "session-status",
          phase: "session",
          status: event.properties.status.type === "idle" ? "completed" : "running",
          title:
            event.properties.status.type === "retry"
              ? `模型请求重试中：${event.properties.status.message}`
              : event.properties.status.type === "busy"
                ? "Agent 正在处理请求"
                : "Agent 已空闲",
          detail: buildSessionStatusDetail(event.properties.status),
        });
        continue;
      }

      if (!firstNonStatusEventLogged) {
        firstNonStatusEventLogged = true;
        logDevelopmentDebug("first non-status session event received", {
          ...debugContext,
          eventType: event.type,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
          sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
        });
      }

      if (isPermissionAskedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("permission request received", {
          ...debugContext,
          requestId: event.properties.id,
          permission: event.properties.permission,
          patterns: event.properties.patterns,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `permission-${event.properties.id}`,
          phase: "permission",
          status: approvalMode === "always" ? "completed" : "running",
          title: approvalMode === "always" ? "已自动允许权限请求" : "等待权限确认",
          detail:
            approvalMode === "always"
              ? "当前批准模式为始终允许，已自动允许本次权限请求。"
              : buildPermissionDetail(event),
        });
        if (approvalMode === "always") {
          await runtime.replyPermission({
            requestId: event.properties.id,
            sessionId,
            reply: "always",
          });
          write("permission_response", {
            session_id: clientSessionId,
            request_id: event.properties.id,
            reply: "always" satisfies PermissionReply,
          });
          continue;
        }
        write("permission_request", {
          session_id: clientSessionId,
          request_id: event.properties.id,
          permission: event.properties.permission,
          patterns: event.properties.patterns,
          target: getPermissionTarget(event.properties.metadata),
          always: event.properties.always,
          tool: event.properties.tool,
          created_at: Date.now(),
        } satisfies PermissionRequestPayload);
        continue;
      }

      if (isPermissionV2AskedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("permission v2 request received", {
          ...debugContext,
          requestId: event.properties.id,
          action: event.properties.action,
          resources: event.properties.resources,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `permission-${event.properties.id}`,
          phase: "permission",
          status: approvalMode === "always" ? "completed" : "running",
          title: approvalMode === "always" ? "已自动允许权限请求" : "等待权限确认",
          detail:
            approvalMode === "always"
              ? "当前批准模式为始终允许，已自动允许本次权限请求。"
              : buildPermissionV2Detail(event),
        });
        if (approvalMode === "always") {
          await runtime.replyPermission({
            requestId: event.properties.id,
            sessionId,
            reply: "always",
          });
          write("permission_response", {
            session_id: clientSessionId,
            request_id: event.properties.id,
            reply: "always" satisfies PermissionReply,
          });
          continue;
        }
        write("permission_request", {
          session_id: clientSessionId,
          request_id: event.properties.id,
          permission: event.properties.action,
          patterns: event.properties.resources,
          target: getPermissionTarget(event.properties.metadata),
          always: event.properties.save ?? [],
          tool: undefined,
          created_at: Date.now(),
        } satisfies PermissionRequestPayload);
        continue;
      }

      if (isPermissionRepliedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("permission request replied", {
          ...debugContext,
          requestId: event.properties.requestID,
          reply: event.properties.reply,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `permission-${event.properties.requestID}`,
          phase: "permission",
          status: event.properties.reply === "reject" ? "error" : "completed",
          title:
            event.properties.reply === "reject"
              ? "权限请求已拒绝"
              : "权限请求已允许",
          detail:
            event.properties.reply === "always"
              ? "已允许本次请求，并记住同类权限。"
              : event.properties.reply === "once"
                ? "已允许本次请求。"
                : "已拒绝本次请求。",
        });
        write("permission_response", {
          session_id: clientSessionId,
          request_id: event.properties.requestID,
          reply: event.properties.reply satisfies PermissionReply,
        });
        continue;
      }

      if (isPermissionV2RepliedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("permission v2 request replied", {
          ...debugContext,
          requestId: event.properties.requestID,
          reply: event.properties.reply,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `permission-${event.properties.requestID}`,
          phase: "permission",
          status: event.properties.reply === "reject" ? "error" : "completed",
          title:
            event.properties.reply === "reject"
              ? "权限请求已拒绝"
              : "权限请求已允许",
          detail:
            event.properties.reply === "always"
              ? "已允许本次请求，并记住同类权限。"
              : event.properties.reply === "once"
                ? "已允许本次请求。"
                : "已拒绝本次请求。",
        });
        write("permission_response", {
          session_id: clientSessionId,
          request_id: event.properties.requestID,
          reply: event.properties.reply satisfies PermissionReply,
        });
        continue;
      }

      if (isQuestionAskedEvent(event) || isQuestionV2AskedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("question request received", {
          ...debugContext,
          requestId: event.properties.id,
          questionCount: event.properties.questions.length,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `question-${event.properties.id}`,
          phase: "question",
          status: "running",
          title: "等待用户补充信息",
          detail: event.properties.questions
            .map((question) => question.question)
            .join("\n"),
        });
        const payload = normalizeQuestionPayload(event, clientSessionId);
        emittedQuestionRequestIds.add(payload.request_id);
        write("question_request", payload);
        continue;
      }

      if (isQuestionRepliedEvent(event) || isQuestionV2RepliedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("question request replied", {
          ...debugContext,
          requestId: event.properties.requestID,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `question-${event.properties.requestID}`,
          phase: "question",
          status: "completed",
          title: "已收到补充信息",
          detail: normalizeQuestionAnswers(event.properties.answers)
            .map((answer) => answer.join("、"))
            .filter(Boolean)
            .join("\n"),
        });
        write("question_response", {
          session_id: clientSessionId,
          request_id: event.properties.requestID,
          answers: normalizeQuestionAnswers(event.properties.answers),
        });
        continue;
      }

      if (isQuestionRejectedEvent(event) || isQuestionV2RejectedEvent(event)) {
        sawResponseActivity = true;
        logDevelopmentDebug("question request rejected", {
          ...debugContext,
          requestId: event.properties.requestID,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: `question-${event.properties.requestID}`,
          phase: "question",
          status: "completed",
          title: "已跳过补充信息",
          detail: "用户选择跳过本次补充信息。",
        });
        write("question_response", {
          session_id: clientSessionId,
          request_id: event.properties.requestID,
          rejected: true,
        });
        continue;
      }

      if (isSkillEvent(event)) {
        sawResponseActivity = true;
        const { name, reason, payload } = extractSkillAuditInfo(event);
        logDevelopmentDebug("skill event received", {
          ...debugContext,
          skill: name,
          reason: reason || null,
          payloadKeys: Object.keys(payload).slice(0, 8),
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        void writeLlmRequestAuditLog({
          kind: "skill",
          sessionId: sessionId,
          clientSessionId,
          traceId,
          projectId,
          target: name,
          reason,
          reasonProvided: Boolean(reason),
          payload,
        }).catch((error) => {
          logger.warn({ err: error }, "failed to write skill audit log");
        });
      }

      if (event.type === "message.updated") {
        if (event.properties.info.role === "assistant") {
          sawResponseActivity = true;
        }
        continue;
      }

      if (event.type === "message.part.delta" && event.properties.field === "text") {
        sawResponseActivity = true;
        const partType = partTypes.get(event.properties.partID);
        if (partType === "text") {
          if (!firstTokenLogged) {
            firstTokenLogged = true;
            logDevelopmentDebug("first response token emitted", {
              ...debugContext,
              partId: event.properties.partID,
              elapsedMs: Math.max(0, Date.now() - requestStartedAt),
              sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
            });
          }
          emittedText = true;
          write("token", {
            session_id: clientSessionId,
            content: event.properties.delta,
          });
        } else if (partType === "reasoning") {
          if (!firstReasoningLogged) {
            firstReasoningLogged = true;
            logDevelopmentDebug("first reasoning delta received", {
              ...debugContext,
              partId: event.properties.partID,
              elapsedMs: Math.max(0, Date.now() - requestStartedAt),
              sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
            });
          }
          const pending = reasoningDeltas.get(event.properties.partID) ?? [];
          pending.push(event.properties.delta);
          reasoningDeltas.set(event.properties.partID, pending);
        } else if (!partType) {
          const pending = pendingPartTextDeltas.get(event.properties.partID) ?? [];
          pending.push(event.properties.delta);
          pendingPartTextDeltas.set(event.properties.partID, pending);
        }
        continue;
      }

      if (event.type === "message.part.updated") {
        sawResponseActivity = true;
        const part = event.properties.part;
        partTypes.set(part.id, part.type);
        if (part.type === "text") {
          const pending = pendingPartTextDeltas.get(part.id) ?? [];
          pendingPartTextDeltas.delete(part.id);
          for (const content of pending) {
            emittedText = true;
            write("token", {
              session_id: clientSessionId,
              content,
            });
          }
        } else if (part.type === "reasoning") {
          const pending = pendingPartTextDeltas.get(part.id) ?? [];
          if (pending.length > 0) {
            const existing = reasoningDeltas.get(part.id) ?? [];
            reasoningDeltas.set(part.id, existing.concat(pending));
          }
          pendingPartTextDeltas.delete(part.id);
          const reasoningStatus = part.time.end ? "completed" : "running";
          if (reasoningStatuses.get(part.id) !== reasoningStatus) {
            reasoningStatuses.set(part.id, reasoningStatus);
            logDevelopmentDebug("reasoning part status changed", {
              ...debugContext,
              partId: part.id,
              status: reasoningStatus,
              chunkCount: (reasoningDeltas.get(part.id) ?? []).length,
              elapsedMs: Math.max(0, Date.now() - requestStartedAt),
            });
          }
          const reasoningDetail = buildReasoningProgressDetail(
            reasoningDeltas.get(part.id) ?? [],
            part.time.end,
          );
          emitProgress({
            id: part.id,
            phase: "planning",
            status: part.time.end ? "completed" : "running",
            title: part.time.end ? "分析规划完成" : "正在规划分析步骤",
            detail: reasoningDetail,
          });
        }
        if (part.type === "tool") {
          if (!firstToolEventLogged) {
            firstToolEventLogged = true;
            logDevelopmentDebug("first tool event received", {
              ...debugContext,
              partId: part.id,
              tool: part.tool,
              status: part.state.status,
              elapsedMs: Math.max(0, Date.now() - requestStartedAt),
              sincePromptDispatchMs: Math.max(0, Date.now() - promptStartedAt),
            });
          }
          const toolParams = normalizeToolParams(part.state.input);
          const reason = extractRequestReason(toolParams);
          const isToolFinalState =
            part.state.status === "completed" || part.state.status === "error";
          const nextToolStatus = String(part.state.status);

          if (toolStatuses.get(part.id) !== nextToolStatus) {
            toolStatuses.set(part.id, nextToolStatus);
            logDevelopmentDebug("tool part status changed", {
              ...debugContext,
              partId: part.id,
              tool: part.tool,
              status: nextToolStatus,
              reason: reason || null,
              inputKeys: Object.keys(toolParams).slice(0, 8),
              error:
                part.state.status === "error" ? (part.state.error ?? "unknown") : null,
              elapsedMs: Math.max(0, Date.now() - requestStartedAt),
            });
          }

          const questionToolPayload = normalizeQuestionToolPayload(
            part,
            toolParams,
            clientSessionId,
          );
          if (questionToolPayload) {
            if (!emittedQuestionToolParts.has(part.id)) {
              emittedQuestionToolParts.add(part.id);
              emittedQuestionRequestIds.add(questionToolPayload.request_id);
              logDevelopmentDebug("question tool request received", {
                ...debugContext,
                requestId: questionToolPayload.request_id,
                tool: part.tool,
                questionCount: questionToolPayload.questions.length,
                elapsedMs: Math.max(0, Date.now() - requestStartedAt),
              });
              emitProgress({
                id: `question-${questionToolPayload.request_id}`,
                phase: "question",
                status: "running",
                title: "等待用户补充信息",
                detail: questionToolPayload.questions
                  .map((question) => question.question)
                  .join("\n"),
              });
              write("question_request", questionToolPayload);
            }
            continue;
          }

          emitProgress({
            id: part.id,
            phase: "tool",
            status: normalizeToolStatus(part.state.status),
            title: getToolProgressTitle(part.tool, part.state.status),
            detail: buildToolProgressDetail(
              part.tool,
              part.state.status,
              toolParams,
              reason,
              part.state.status === "error" ? part.state.error : undefined,
            ),
          });
          if (
            !emittedToolParts.has(part.id) &&
            (hasToolParams(toolParams) || isToolFinalState)
          ) {
            emittedToolParts.add(part.id);
            toolCallCount += 1;
            if (!reason) {
              logger.warn(
                {
                  tool: part.tool,
                  sessionId: sessionId,
                  clientSessionId,
                },
                "llm tool request missing reason",
              );
            }
            void writeLlmRequestAuditLog({
              kind: "tool",
              sessionId: sessionId,
              clientSessionId,
              traceId,
              projectId,
              target: part.tool,
              reason,
              reasonProvided: Boolean(reason),
              payload: toolParams,
            }).catch((error) => {
              logger.warn({ err: error }, "failed to write tool audit log");
            });
            write("tool_call", {
              session_id: clientSessionId,
              tool: part.tool,
              params: toolParams,
              reason,
            });
          }
        }
        continue;
      }

      if (event.type === "todo.updated") {
        sawResponseActivity = true;
        const todos = event.properties.todos as Array<{
          content: string;
          status: string;
          priority: string;
        }>;
        const normalizedTodos = todos.map((todo, index) => ({
          id: `todo-${index}-${todo.content.slice(0, 24)}`,
          content: todo.content,
          status: normalizeTodoStatus(todo.status),
          priority: normalizeTodoPriority(todo.priority),
          updated_at: Date.now(),
        }));
        const completed = todos.filter(
          (todo) => todo.status === "completed",
        ).length;
        emitProgress({
          id: "todo-progress",
          phase: "planning",
          status: completed === todos.length ? "completed" : "running",
          title: `计划进度 ${completed}/${todos.length}`,
          detail: todos
            .map((todo) => `${todo.status}: ${todo.content}`)
            .join("\n"),
        });
        write("todo_update", {
          session_id: clientSessionId,
          todos: normalizedTodos,
          created_at: Date.now(),
        } satisfies TodoUpdatePayload);
        continue;
      }

      if (event.type === "session.error") {
        sawResponseActivity = true;
        logDevelopmentDebug("session error received", {
          ...debugContext,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
          error: event.properties.error
            ? getErrorMessage(event.properties.error)
            : "opencode session error",
        });
        write("error", {
          session_id: clientSessionId,
          message: event.properties.error
            ? getErrorMessage(event.properties.error)
            : "opencode session error",
          detail: event.properties.error?.name,
          total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
        });
        failed = true;
        done = true;
        continue;
      }

      if (event.type === "session.idle") {
        if (!sawResponseActivity) {
          logDevelopmentDebug("ignoring session idle before response activity", {
            ...debugContext,
            elapsedMs: Math.max(0, Date.now() - requestStartedAt),
          });
          continue;
        }
        logDevelopmentDebug("session idle received", {
          ...debugContext,
          emittedText,
          toolCallCount,
          elapsedMs: Math.max(0, Date.now() - requestStartedAt),
        });
        emitProgress({
          id: "session-status",
          phase: "session",
          status: "completed",
          title: "Agent 已完成处理",
          detail: "当前会话已无待执行任务，正在收尾并准备返回最终结果。",
        });
        done = true;
      }
    }

    if (aborted) {
      logDevelopmentDebug("chat stream aborting session", {
        ...debugContext,
        elapsedMs: Math.max(0, Date.now() - requestStartedAt),
      });
      await runtime.abortSession(sessionId).catch((error) => {
        logger.warn({ sessionId: sessionId, err: error }, "failed to abort opencode session");
      });
      await runtime.waitForSessionIdle(sessionId).catch((error) => {
        logger.warn(
          { sessionId: sessionId, err: error },
          "failed while waiting for aborted opencode session to become idle",
        );
      });
      return { aborted: true, failed: false, toolCallCount };
    }

    if (failed) {
      return { aborted: false, failed: true, toolCallCount };
    }

    await promptPromise;
    if (!emittedText) {
      logDevelopmentDebug("no streamed text emitted, falling back to messages()", {
        ...debugContext,
        elapsedMs: Math.max(0, Date.now() - requestStartedAt),
      });
      await emitFallbackMessage(runtime, sessionId, clientSessionId, write);
    }
    emitProgress({
      id: "request-received",
      phase: "start",
      status: "completed",
      title: "请求处理完成",
      detail: "本次请求的分析、工具执行和结果整理流程已经完成。",
    });
    emitProgress({
      id: "request-completed",
      phase: "complete",
      status: "completed",
      title: "分析完成",
      detail: emittedText
        ? "最终回答已生成并推送到前端。"
        : "已完成分析，并通过兜底消息补发最终回答内容。",
    });
    write("done", {
      session_id: clientSessionId,
      total_duration_ms: Math.max(0, Date.now() - requestStartedAt),
    });
    logDevelopmentDebug("chat stream completed", {
      ...debugContext,
      emittedText,
      toolCallCount,
      totalDurationMs: Math.max(0, Date.now() - requestStartedAt),
    });
    return { aborted: false, failed: false, toolCallCount };
  } finally {
    await iterator.return?.(undefined);
    if (!promptSettled && !aborted) {
      await promptPromise.catch(() => undefined);
    } else if (!promptSettled) {
      void promptPromise.catch(() => undefined);
    }
    logDevelopmentDebug("chat stream cleanup finished", {
      ...debugContext,
      promptSettled,
      totalDurationMs: Math.max(0, Date.now() - requestStartedAt),
    });
  }
};
