import type {
  ChatDocumentAttachment,
  ChatImageAttachment,
  ChatTimelineItem,
  ChatTimelineMessageItem,
} from '@easyflow/common-ui';

import type { AgentChatCapabilityPayload } from './api';

import { ChatTimelineBuilder } from '@easyflow/common-ui';
import { useUserStore } from '@easyflow/stores';

import { EventType } from '@ag-ui/client';

import {
  onAgentChatCacheClear,
  resolveAgentChatIdentity,
  RUNTIME_STORAGE_PREFIX,
} from '#/utils/agent-chat-cache';

import {
  createAguiRunId,
  EasyFlowAguiClient,
} from '../shared/agent-agui/client';
import {
  applyAguiEventToTimeline,
  createAguiTimelineProjectionState,
} from '../shared/agent-agui/projection';
import {
  isRetryableAguiTransportError,
  resumeAguiRunUntilTerminal,
} from '../shared/agent-agui/reconnect';
import { generateAgentSessionId } from './api';

interface RuntimeSessionState {
  agentId: string;
  agentName?: string;
  completed: boolean;
  error?: string;
  identity: string;
  inputAccepted: boolean;
  inputAcceptedNotified: boolean;
  items: ChatTimelineItem[];
  lastCursor: number;
  prompt: string;
  projectionToolArgs: Record<string, string>;
  projectionToolNames: Record<string, string>;
  runId?: string;
  roundId: string;
  sending: boolean;
  sessionId: string;
  startedAt: number;
  terminalOutcome?: 'failed' | 'success';
  updatedAt: number;
}

interface StoredRuntimeSession {
  agentId: string;
  agentName?: string;
  completed: boolean;
  error?: string;
  inputAccepted: boolean;
  items: ChatTimelineItem[];
  lastCursor?: number;
  prompt: string;
  projectionToolArgs?: Record<string, string>;
  projectionToolNames?: Record<string, string>;
  runId?: string;
  roundId: string;
  sending: boolean;
  sessionId: string;
  startedAt: number;
  terminalOutcome?: 'failed' | 'success';
  updatedAt: number;
  version: number;
}

interface StartOptions {
  agentId: string;
  agentName?: string;
  baseItems?: ChatTimelineItem[];
  capabilities?: AgentChatCapabilityPayload[];
  documentUploadIds?: string[];
  documents?: ChatDocumentAttachment[];
  imageUploadIds?: string[];
  images?: ChatImageAttachment[];
  onInputAccepted?: () => Promise<void> | void;
  prompt: string;
  sessionId?: string;
}

const STORAGE_VERSION = 5;
const STREAM_NOTIFY_INTERVAL_MS = 50;
const STREAM_PERSIST_INTERVAL_MS = 300;
const MAX_RUNTIME_SESSIONS_PER_IDENTITY = 10;

const sessions = new Map<string, RuntimeSessionState>();
const listeners = new Set<() => void>();
const latestSessionIds = new Map<string, string>();
const persistTimers = new Map<string, ReturnType<typeof setTimeout>>();
const runClients = new Map<string, EasyFlowAguiClient>();
const stoppingSessions = new Set<string>();
const inputAcceptedCallbacks = new Map<string, () => Promise<void> | void>();
let notifyTimer: ReturnType<typeof setTimeout> | undefined;
let pageDetaching = false;

function clone<T>(value: T): T {
  const serialized = JSON.stringify(value);
  return JSON.parse(serialized) as T;
}

function createRoundId() {
  return `agent-chat-round-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
}

function identityScope() {
  return resolveAgentChatIdentity(useUserStore().userInfo);
}

function sessionKey(identity: string, sessionId: string) {
  return `${identity}:${sessionId}`;
}

function storageKey(identity: string, sessionId: string) {
  return `${RUNTIME_STORAGE_PREFIX}:${identity}:${sessionId}`;
}

function latestStorageKey(identity: string) {
  return `${RUNTIME_STORAGE_PREFIX}:${identity}:latest`;
}

function safeSessionStorage() {
  try {
    return globalThis.sessionStorage;
  } catch {
    return undefined;
  }
}

function notify() {
  for (const listener of listeners) {
    listener();
  }
}

function notifyNow() {
  if (notifyTimer) {
    clearTimeout(notifyTimer);
    notifyTimer = undefined;
  }
  notify();
}

function scheduleNotify() {
  if (notifyTimer || listeners.size === 0) {
    return;
  }
  notifyTimer = setTimeout(() => {
    notifyTimer = undefined;
    notify();
  }, STREAM_NOTIFY_INTERVAL_MS);
}

function persistSession(state: RuntimeSessionState) {
  const storage = safeSessionStorage();
  if (!storage) {
    return;
  }
  const snapshot: StoredRuntimeSession = {
    agentId: state.agentId,
    agentName: state.agentName,
    completed: state.completed,
    error: state.error,
    inputAccepted: state.inputAccepted,
    items: state.items,
    lastCursor: state.lastCursor,
    prompt: state.prompt,
    projectionToolArgs: state.projectionToolArgs,
    projectionToolNames: state.projectionToolNames,
    runId: state.runId,
    roundId: state.roundId,
    sending: state.sending,
    sessionId: state.sessionId,
    startedAt: state.startedAt,
    terminalOutcome: state.terminalOutcome,
    updatedAt: state.updatedAt,
    version: STORAGE_VERSION,
  };
  try {
    storage.setItem(
      storageKey(state.identity, state.sessionId),
      JSON.stringify(snapshot),
    );
    storage.setItem(latestStorageKey(state.identity), state.sessionId);
  } catch {
    // 缓存失败不影响正式聊天主流程。
  }
}

function cancelPersistTimer(scopedSessionKey: string) {
  const timer = persistTimers.get(scopedSessionKey);
  if (!timer) {
    return;
  }
  clearTimeout(timer);
  persistTimers.delete(scopedSessionKey);
}

function removeSessionCache(
  scopedSessionKey: string,
  state: RuntimeSessionState,
) {
  cancelPersistTimer(scopedSessionKey);
  inputAcceptedCallbacks.delete(scopedSessionKey);
  sessions.delete(scopedSessionKey);
  try {
    safeSessionStorage()?.removeItem(
      storageKey(state.identity, state.sessionId),
    );
  } catch {
    // 本地缓存清理失败不影响服务端会话。
  }
}

function pruneRuntimeSessions(identity: string, currentSessionId: string) {
  const scopedSessions = [...sessions.entries()]
    .filter(([, session]) => session.identity === identity)
    .sort((first, second) => second[1].updatedAt - first[1].updatedAt);
  if (scopedSessions.length <= MAX_RUNTIME_SESSIONS_PER_IDENTITY) {
    return;
  }
  const protectedKeys = new Set(
    scopedSessions
      .filter(
        ([, session]) =>
          session.sending || session.sessionId === currentSessionId,
      )
      .map(([key]) => key),
  );
  let retainedCount = protectedKeys.size;
  for (const [key, session] of scopedSessions) {
    if (protectedKeys.has(key)) {
      continue;
    }
    if (retainedCount < MAX_RUNTIME_SESSIONS_PER_IDENTITY) {
      retainedCount += 1;
      continue;
    }
    removeSessionCache(key, session);
  }
}

function touchState(state: RuntimeSessionState) {
  state.updatedAt = Date.now();
  latestSessionIds.set(state.identity, state.sessionId);
  sessions.set(sessionKey(state.identity, state.sessionId), state);
}

function scheduleStateUpdate(state: RuntimeSessionState, immediate = false) {
  touchState(state);
  const scopedSessionKey = sessionKey(state.identity, state.sessionId);
  if (!persistTimers.has(scopedSessionKey)) {
    persistTimers.set(
      scopedSessionKey,
      setTimeout(() => {
        persistTimers.delete(scopedSessionKey);
        const current = sessions.get(scopedSessionKey);
        if (current) {
          persistSession(current);
        }
      }, STREAM_PERSIST_INTERVAL_MS),
    );
  }
  if (immediate) {
    notifyNow();
  } else {
    scheduleNotify();
  }
}

function restoreSession(identity: string, sessionId: string) {
  if (!identity) {
    return undefined;
  }
  const scopedSessionKey = sessionKey(identity, sessionId);
  const existing = sessions.get(scopedSessionKey);
  if (existing) {
    return existing;
  }
  const storage = safeSessionStorage();
  if (!storage) {
    return undefined;
  }
  try {
    const raw = storage.getItem(storageKey(identity, sessionId));
    if (!raw) {
      return undefined;
    }
    const parsed = JSON.parse(raw) as StoredRuntimeSession;
    if (
      ![3, 4, STORAGE_VERSION].includes(parsed.version) ||
      parsed.sessionId !== sessionId
    ) {
      return undefined;
    }
    const restored: RuntimeSessionState = {
      agentId: parsed.agentId,
      agentName: parsed.agentName,
      completed: parsed.completed,
      error: parsed.error,
      identity,
      inputAccepted: Boolean(parsed.inputAccepted),
      inputAcceptedNotified: false,
      items: Array.isArray(parsed.items) ? parsed.items : [],
      lastCursor: Math.max(0, Number(parsed.lastCursor || 0)),
      prompt: parsed.prompt,
      projectionToolArgs:
        parsed.projectionToolArgs &&
        typeof parsed.projectionToolArgs === 'object'
          ? parsed.projectionToolArgs
          : {},
      projectionToolNames:
        parsed.projectionToolNames &&
        typeof parsed.projectionToolNames === 'object'
          ? parsed.projectionToolNames
          : {},
      runId: parsed.runId,
      roundId: parsed.roundId,
      sending: Boolean(parsed.sending && parsed.runId),
      sessionId,
      startedAt: Number(parsed.startedAt || parsed.updatedAt || Date.now()),
      terminalOutcome:
        parsed.terminalOutcome === 'failed' ||
        parsed.terminalOutcome === 'success'
          ? parsed.terminalOutcome
          : undefined,
      updatedAt: parsed.updatedAt,
    };
    sessions.set(scopedSessionKey, restored);
    return restored;
  } catch {
    return undefined;
  }
}

function upsertState(state: RuntimeSessionState) {
  touchState(state);
  cancelPersistTimer(sessionKey(state.identity, state.sessionId));
  persistSession(state);
  pruneRuntimeSessions(state.identity, state.sessionId);
  notifyNow();
}

function notifyInputAccepted(state: RuntimeSessionState) {
  if (state.inputAcceptedNotified) {
    return;
  }
  const callback = inputAcceptedCallbacks.get(
    sessionKey(state.identity, state.sessionId),
  );
  if (!callback) {
    return;
  }
  state.inputAcceptedNotified = true;
  void callback();
}

function registerInputAcceptedCallback(
  state: RuntimeSessionState,
  callback?: () => Promise<void> | void,
) {
  if (callback) {
    inputAcceptedCallbacks.set(
      sessionKey(state.identity, state.sessionId),
      callback,
    );
  }
  if (state.inputAccepted) {
    notifyInputAccepted(state);
  }
}

function acceptInput(
  state: RuntimeSessionState,
  payload: Record<string, unknown>,
) {
  replaceAcceptedAttachments(state.items, state.roundId, payload);
  state.inputAccepted = true;
  persistSession(state);
  notifyInputAccepted(state);
}

function runningSession(identity = identityScope()) {
  return [...sessions.values()].find(
    (session) => session.identity === identity && session.sending,
  );
}

function restoreLatestSession(identity = identityScope()) {
  if (!identity) {
    return undefined;
  }
  const running = runningSession(identity);
  if (running) {
    return running;
  }
  const storage = safeSessionStorage();
  const storedSessionId = storage?.getItem(latestStorageKey(identity)) || '';
  const sessionId = latestSessionIds.get(identity) || storedSessionId;
  return sessionId ? restoreSession(identity, sessionId) : undefined;
}

async function resolveSessionId(sessionId?: string) {
  if (sessionId) {
    return sessionId;
  }
  const res = await generateAgentSessionId();
  if (res.errorCode !== 0 || !res.data) {
    throw new Error(res.message || '会话创建失败');
  }
  return String(res.data);
}

function errorMessage(error: unknown) {
  return error instanceof Error ? error.message : '发送失败，请稍后再试';
}

function normalizeAcceptedImages(payload: Record<string, any>) {
  if (!Array.isArray(payload.images)) {
    return [];
  }
  return payload.images
    .map((value): ChatImageAttachment | undefined => {
      if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return undefined;
      }
      const image = value as Record<string, any>;
      const previewUrl = String(image.previewUrl || '');
      if (!previewUrl) {
        return undefined;
      }
      return {
        height: Number(image.height || 0) || undefined,
        imageRef: String(image.imageRef || ''),
        mimeType: String(image.mimeType || ''),
        name: String(image.name || '图片'),
        previewUrl,
        size: Number(image.size || 0) || undefined,
        status: 'ready',
        width: Number(image.width || 0) || undefined,
      };
    })
    .filter((item): item is ChatImageAttachment => item !== undefined);
}

function normalizeAcceptedDocuments(payload: Record<string, any>) {
  if (!Array.isArray(payload.attachments)) {
    return [];
  }
  return payload.attachments
    .map((value): ChatDocumentAttachment | undefined => {
      if (!value || typeof value !== 'object' || Array.isArray(value)) {
        return undefined;
      }
      const document = value as Record<string, any>;
      const attachmentRef = String(document.attachmentRef || '');
      if (!attachmentRef) {
        return undefined;
      }
      return {
        attachmentRef,
        downloadUrl: String(document.downloadUrl || ''),
        mimeType: String(document.mimeType || ''),
        name: String(document.name || '文档'),
        readSnapshotId: String(document.readSnapshotId || ''),
        size: Number(document.size || 0) || undefined,
        status: 'ready',
      };
    })
    .filter((item): item is ChatDocumentAttachment => item !== undefined);
}

function replaceAcceptedAttachments(
  items: ChatTimelineItem[],
  roundId: string,
  payload: Record<string, any>,
) {
  const acceptedImages = normalizeAcceptedImages(payload);
  const acceptedDocuments = normalizeAcceptedDocuments(payload);
  if (acceptedImages.length === 0 && acceptedDocuments.length === 0) {
    return;
  }
  const userMessage = items.find(
    (item): item is ChatTimelineMessageItem =>
      item.type === 'message' &&
      item.role === 'user' &&
      item.roundId === roundId,
  );
  if (userMessage) {
    if (acceptedImages.length > 0) {
      userMessage.images = acceptedImages;
    }
    if (acceptedDocuments.length > 0) {
      userMessage.documents = acceptedDocuments;
    }
  }
}

function resetRoundForReplay(state: RuntimeSessionState) {
  state.items = state.items.filter(
    (item) =>
      item.roundId !== state.roundId ||
      (item.type === 'message' && item.role === 'user'),
  );
  ChatTimelineBuilder.ensureAssistantTurn(state.items, {
    id: `turn-${state.roundId}`,
    roundId: state.roundId,
    turnStartedAt: state.startedAt,
  });
  state.completed = false;
  state.error = undefined;
  state.sending = true;
  state.terminalOutcome = undefined;
}

function restoreProjectionState(state: RuntimeSessionState) {
  const projectionState = createAguiTimelineProjectionState(state.startedAt);
  projectionState.toolArgs = new Map(Object.entries(state.projectionToolArgs));
  projectionState.toolNames = new Map(
    Object.entries(state.projectionToolNames),
  );
  return projectionState;
}

function captureProjectionCursor(
  state: RuntimeSessionState,
  projectionState: ReturnType<typeof createAguiTimelineProjectionState>,
  cursor: number,
) {
  state.lastCursor = Math.max(state.lastCursor, cursor);
  state.projectionToolArgs = Object.fromEntries(projectionState.toolArgs);
  state.projectionToolNames = Object.fromEntries(projectionState.toolNames);
}

function finishRuntimeSuccess(state: RuntimeSessionState) {
  state.sending = false;
  state.completed = true;
  const cancelled = state.items.some(
    (item) =>
      item.roundId === state.roundId &&
      item.turnFinishedAt !== undefined &&
      item.turnSucceeded === false,
  );
  if (!cancelled) {
    ChatTimelineBuilder.finalize(state.items, {
      roundCompleted: true,
      roundId: state.roundId,
      turnFinishedAt: Date.now(),
      turnSucceeded: true,
    });
  }
  upsertState(state);
}

function finishRuntimeFailure(state: RuntimeSessionState, error: unknown) {
  state.error = errorMessage(error);
  state.sending = false;
  state.completed = true;
  const last = state.items[state.items.length - 1];
  if (last?.type !== 'error') {
    ChatTimelineBuilder.appendError(state.items, state.error, {
      roundId: state.roundId,
    });
  }
  ChatTimelineBuilder.finalize(state.items, {
    roundId: state.roundId,
    turnFinishedAt: Date.now(),
    turnSucceeded: false,
  });
  upsertState(state);
}

function observeTerminalEvent(state: RuntimeSessionState, eventType: string) {
  if (eventType === EventType.RUN_FINISHED) {
    state.terminalOutcome = 'success';
  } else if (eventType === EventType.RUN_ERROR) {
    state.terminalOutcome = 'failed';
  }
}

onAgentChatCacheClear((identity) => {
  for (const [key, session] of sessions) {
    if (session.identity === identity) {
      cancelPersistTimer(key);
      inputAcceptedCallbacks.delete(key);
      sessions.delete(key);
    }
  }
  latestSessionIds.delete(identity);
  notifyNow();
});

export const agentChatRuntimeManager = {
  getSnapshot(sessionId?: string) {
    if (!sessionId) {
      return undefined;
    }
    const state = restoreSession(identityScope(), sessionId);
    return state ? clone(state) : undefined;
  },

  getLatestSnapshot() {
    const state = restoreLatestSession();
    return state ? clone(state) : undefined;
  },

  hasRunning() {
    return Boolean(runningSession());
  },

  replaceItems(sessionId: string, items: ChatTimelineItem[]) {
    const state = restoreSession(identityScope(), sessionId);
    if (!state) {
      return;
    }
    state.items = clone(items);
    upsertState(state);
  },

  resume(sessionId?: string, onInputAccepted?: () => Promise<void> | void) {
    const identity = identityScope();
    const state = sessionId
      ? restoreSession(identity, sessionId)
      : runningSession(identity);
    if (state) {
      registerInputAcceptedCallback(state, onInputAccepted);
    }
    if (!state?.sending || !state.runId) {
      return;
    }
    if (state.terminalOutcome) {
      finishRuntimeSuccess(state);
      return;
    }
    const scopedSessionKey = sessionKey(identity, state.sessionId);
    if (runClients.has(scopedSessionKey)) {
      return;
    }
    if (state.lastCursor === 0) {
      resetRoundForReplay(state);
    }
    upsertState(state);
    const projectionState = restoreProjectionState(state);
    const runClient = new EasyFlowAguiClient();
    runClients.set(scopedSessionKey, runClient);
    void resumeAguiRunUntilTerminal({
      client: runClient,
      onEvent(event) {
        const current = sessions.get(scopedSessionKey);
        if (!current || current !== state || !current.sending) {
          return;
        }
        if (
          event.type === EventType.RUN_ERROR &&
          event.code !== 'RUN_CANCELLED'
        ) {
          current.error = event.message || '发送失败，请稍后再试';
        }
        observeTerminalEvent(current, event.type);
        applyAguiEventToTimeline(
          current.items,
          event,
          {
            onInputAccepted(payload) {
              acceptInput(current, payload);
            },
            roundId: current.roundId,
            startedAt: current.startedAt,
          },
          projectionState,
        );
        scheduleStateUpdate(current, event.type === EventType.TOOL_CALL_START);
      },
      initialCursor: state.lastCursor,
      onCursor(cursor) {
        captureProjectionCursor(state, projectionState, cursor);
      },
      runId: state.runId,
      shouldContinue: () => {
        const current = sessions.get(scopedSessionKey);
        return Boolean(
          runClients.get(scopedSessionKey) === runClient &&
            !pageDetaching &&
            current &&
            current === state &&
            current.sending,
        );
      },
      startedAt: state.startedAt,
    })
      .then((terminalReceived) => {
        const current = sessions.get(scopedSessionKey);
        if (
          terminalReceived &&
          runClients.get(scopedSessionKey) === runClient &&
          !pageDetaching &&
          !stoppingSessions.has(scopedSessionKey) &&
          current &&
          current === state &&
          current.sending
        ) {
          finishRuntimeSuccess(current);
        }
      })
      .catch((error) => {
        const current = sessions.get(scopedSessionKey);
        if (
          runClients.get(scopedSessionKey) === runClient &&
          !pageDetaching &&
          !stoppingSessions.has(scopedSessionKey) &&
          current &&
          current === state &&
          current.sending
        ) {
          finishRuntimeFailure(current, error);
        }
      })
      .finally(() => {
        if (runClients.get(scopedSessionKey) === runClient) {
          runClients.delete(scopedSessionKey);
        }
      });
  },

  async start(options: StartOptions) {
    const identity = identityScope();
    if (!identity) {
      throw new Error('当前登录状态失效');
    }
    const active = runningSession(identity);
    if (active) {
      throw new Error('当前回复完成后再发送新消息');
    }
    const sessionId = await resolveSessionId(options.sessionId);
    const roundId = createRoundId();
    const runId = createAguiRunId();
    const startedAt = Date.now();
    const state: RuntimeSessionState = {
      agentId: options.agentId,
      agentName: options.agentName,
      completed: false,
      identity,
      inputAccepted: false,
      inputAcceptedNotified: false,
      items: clone(options.baseItems || []),
      lastCursor: 0,
      prompt: options.prompt,
      projectionToolArgs: {},
      projectionToolNames: {},
      runId,
      roundId,
      sending: true,
      sessionId,
      startedAt,
      terminalOutcome: undefined,
      updatedAt: startedAt,
    };
    registerInputAcceptedCallback(state, options.onInputAccepted);
    ChatTimelineBuilder.appendUserMessage(state.items, options.prompt, {
      documents: options.documents,
      images: options.images,
      roundId,
    });
    ChatTimelineBuilder.ensureAssistantTurn(state.items, {
      id: `turn-${roundId}`,
      roundId,
      turnStartedAt: startedAt,
    });
    upsertState(state);

    const scopedSessionKey = sessionKey(identity, sessionId);
    const projectionState = createAguiTimelineProjectionState(startedAt);
    const runClient = new EasyFlowAguiClient();
    runClients.set(scopedSessionKey, runClient);
    let lastCursor = 0;
    const projectEvent: Parameters<EasyFlowAguiClient['run']>[0]['onEvent'] = (
      event,
    ) => {
      const current = sessions.get(sessionKey(identity, sessionId));
      if (!current || current !== state || !current.sending) {
        return;
      }
      if (
        event.type === EventType.RUN_ERROR &&
        event.code !== 'RUN_CANCELLED'
      ) {
        current.error = event.message || '发送失败，请稍后再试';
      }
      observeTerminalEvent(current, event.type);
      applyAguiEventToTimeline(
        current.items,
        event,
        {
          onInputAccepted(payload) {
            acceptInput(current, payload);
          },
          roundId,
          startedAt,
        },
        projectionState,
      );
      scheduleStateUpdate(current, event.type === EventType.TOOL_CALL_START);
    };
    void runClient
      .run({
        forwardedProps: {
          easyflow: {
            input: {
              capabilities: options.capabilities,
              documentUploadIds: options.documentUploadIds,
              imageUploadIds: options.imageUploadIds,
            },
          },
        },
        onCursor(cursor) {
          lastCursor = Math.max(lastCursor, cursor);
          captureProjectionCursor(state, projectionState, cursor);
        },
        onEvent: projectEvent,
        runId,
        threadId: sessionId,
        url: `/api/v1/agent/${encodeURIComponent(options.agentId)}/agui/run`,
        userMessage: {
          content: options.prompt,
          id: `user-${roundId}`,
          role: 'user',
        },
      })
      .catch(async (error) => {
        if (pageDetaching || !state.sending) {
          return;
        }
        if (!isRetryableAguiTransportError(error, state.startedAt)) {
          throw error;
        }
        return resumeAguiRunUntilTerminal({
          client: runClient,
          initialCursor: lastCursor,
          onCursor(cursor) {
            captureProjectionCursor(state, projectionState, cursor);
          },
          onEvent: projectEvent,
          runId,
          shouldContinue: () => {
            const current = sessions.get(scopedSessionKey);
            return Boolean(
              runClients.get(scopedSessionKey) === runClient &&
                !pageDetaching &&
                current &&
                current === state &&
                current.sending,
            );
          },
          startedAt,
        });
      })
      .then((terminalReceived = true) => {
        const current = sessions.get(scopedSessionKey);
        if (
          !terminalReceived ||
          runClients.get(scopedSessionKey) !== runClient ||
          pageDetaching ||
          stoppingSessions.has(scopedSessionKey) ||
          !current ||
          current !== state ||
          !current.sending
        ) {
          return;
        }
        finishRuntimeSuccess(current);
      })
      .catch((error) => {
        const current = sessions.get(scopedSessionKey);
        if (
          runClients.get(scopedSessionKey) !== runClient ||
          pageDetaching ||
          stoppingSessions.has(scopedSessionKey) ||
          !current ||
          current !== state ||
          !current.sending
        ) {
          return;
        }
        finishRuntimeFailure(current, error);
      })
      .finally(() => {
        if (runClients.get(scopedSessionKey) === runClient) {
          runClients.delete(scopedSessionKey);
        }
      });

    return sessionId;
  },

  async stop(sessionId?: string) {
    const identity = identityScope();
    const state = sessionId
      ? restoreSession(identity, sessionId)
      : runningSession(identity);
    if (!state || !state.sending) {
      return;
    }
    const scopedSessionKey = sessionKey(identity, state.sessionId);
    if (stoppingSessions.has(scopedSessionKey)) {
      return;
    }
    stoppingSessions.add(scopedSessionKey);
    const runClient = runClients.get(scopedSessionKey);
    if (state.runId) {
      try {
        await (runClient || new EasyFlowAguiClient()).cancel(state.runId);
      } catch (error) {
        stoppingSessions.delete(scopedSessionKey);
        runClient?.detach();
        if (runClients.get(scopedSessionKey) === runClient) {
          runClients.delete(scopedSessionKey);
        }
        if (state.terminalOutcome) {
          finishRuntimeSuccess(state);
        } else {
          state.error = errorMessage(error);
          upsertState(state);
          this.resume(state.sessionId);
        }
        throw error;
      }
    } else {
      runClient?.detach();
    }
    stoppingSessions.delete(scopedSessionKey);
    if (runClients.get(scopedSessionKey) === runClient) {
      runClients.delete(scopedSessionKey);
    }
    if (state.terminalOutcome) {
      finishRuntimeSuccess(state);
      return;
    }
    // 取消接口成功只说明服务端已接受命令，最终结果仍以可重放事件流中的权威终态为准。
    upsertState(state);
    this.resume(state.sessionId);
  },

  subscribe(listener: () => void) {
    listeners.add(listener);
    return () => {
      listeners.delete(listener);
    };
  },
};

if (typeof globalThis.addEventListener === 'function') {
  globalThis.addEventListener('pagehide', () => {
    pageDetaching = true;
    for (const client of runClients.values()) {
      client.detach();
    }
    runClients.clear();
    stoppingSessions.clear();
    for (const state of sessions.values()) {
      if (state.sending) {
        persistSession(state);
      }
    }
  });
  globalThis.addEventListener('pageshow', () => {
    if (!pageDetaching) return;
    pageDetaching = false;
    for (const state of sessions.values()) {
      if (state.sending) {
        agentChatRuntimeManager.resume(state.sessionId);
      }
    }
  });
}
