import type { AguiEvent } from './client';

import {
  EasyFlowAguiClient,
  EasyFlowAguiHttpError,
  EasyFlowAguiProjectionError,
} from './client';

const INITIAL_RECONNECT_DELAY_MS = 300;
const MAX_RECONNECT_DELAY_MS = 5000;
const RUN_CREATION_GRACE_MS = 30_000;

interface ResumeAguiRunOptions {
  client: EasyFlowAguiClient;
  initialCursor?: number;
  onCursor?: (cursor: number) => void;
  onEvent: (event: AguiEvent) => Promise<void> | void;
  runId: string;
  shouldContinue: () => boolean;
  startedAt: number;
}

function wait(delay: number) {
  return new Promise((resolve) => setTimeout(resolve, delay));
}

/**
 * 判断传输异常是否允许通过运行日志继续恢复。
 *
 * 404 仅在 POST 尚未建立运行记录的短窗口内重试；鉴权、参数和协议解析错误立即交给页面处理。
 */
export function isRetryableAguiTransportError(
  error: unknown,
  startedAt: number,
) {
  if (error instanceof EasyFlowAguiProjectionError) {
    return false;
  }
  if (error instanceof EasyFlowAguiHttpError) {
    if (error.status === 404) {
      return Date.now() - startedAt < RUN_CREATION_GRACE_MS;
    }
    return (
      error.status === 408 ||
      error.status === 425 ||
      error.status === 429 ||
      error.status >= 500
    );
  }
  if (error instanceof SyntaxError) {
    return false;
  }
  if (error instanceof Error && error.name === 'ZodError') {
    return false;
  }
  return true;
}

/**
 * 以服务端游标持续恢复 AG-UI 运行，直至收到权威终态或页面主动分离。
 *
 * 退避间隔有上限，网络故障期间保留可恢复态，不会用本地错误覆盖仍在执行的服务端运行。
 */
export async function resumeAguiRunUntilTerminal(
  options: ResumeAguiRunOptions,
) {
  let cursor = Math.max(0, options.initialCursor || 0);
  let delay = INITIAL_RECONNECT_DELAY_MS;
  while (options.shouldContinue()) {
    try {
      await options.client.resume({
        afterCursor: cursor,
        onCursor(nextCursor) {
          cursor = Math.max(cursor, nextCursor);
          options.onCursor?.(cursor);
        },
        onEvent: options.onEvent,
        runId: options.runId,
      });
      return options.shouldContinue();
    } catch (error) {
      if (
        !options.shouldContinue() ||
        !isRetryableAguiTransportError(error, options.startedAt)
      ) {
        throw error;
      }
      await wait(delay);
      delay = Math.min(delay * 2, MAX_RECONNECT_DELAY_MS);
    }
  }
  return false;
}
