/**
 * 错误处理策略（plan.md「0.3 错误处理策略」）。
 *
 * 原则：
 * - 可恢复错误（网络超时、文件读取失败）：自动重试，UI 显示提示
 * - 不可恢复错误（解析失败、格式错误）：显示错误详情，引导用户修复
 * - 缓存层错误：丢弃缓存，从 .bean 文件重建
 */

export enum ErrorCategory {
  NETWORK = 'network',
  DATABASE = 'database',
  PARSING = 'parsing',
  OCR = 'ocr',
  FILE_SYSTEM = 'file_system',
  RULE_ENGINE = 'rule_engine',
  AI = 'ai',
  UNKNOWN = 'unknown',
}

export interface AppError {
  category: ErrorCategory;
  message: string;
  recoverable: boolean;
  retryable: boolean;
  cause?: unknown;
  context?: Record<string, unknown>;
}

/** 把任意错误归一化为 AppError。 */
export function toAppError(error: unknown, category: ErrorCategory = ErrorCategory.UNKNOWN): AppError {
  if (error instanceof AppErrorImpl) return error;
  const message = error instanceof Error ? error.message : String(error);
  const retryable = RETRYABLE_CATEGORIES.has(category);
  return new AppErrorImpl(category, message, retryable, retryable, error);
}

const RETRYABLE_CATEGORIES = new Set<ErrorCategory>([
  ErrorCategory.NETWORK,
  ErrorCategory.DATABASE,
  ErrorCategory.FILE_SYSTEM,
  ErrorCategory.AI,
]);

class AppErrorImpl extends Error implements AppError {
  constructor(
    public readonly category: ErrorCategory,
    message: string,
    public readonly recoverable: boolean,
    public readonly retryable: boolean,
    public readonly cause?: unknown,
    public readonly context?: Record<string, unknown>,
  ) {
    super(message);
    this.name = 'AppError';
  }
}

/**
 * 带指数退避的重试。
 * @param fn 待重试的异步操作
 * @param retries 最大重试次数（不含首次）
 * @param delay 初始延迟（毫秒），每次翻倍
 */
export async function withRetry<T>(
  fn: () => Promise<T>,
  retries = 3,
  delay = 1000,
): Promise<T> {
  try {
    return await fn();
  } catch (error) {
    if (retries <= 0) throw error;
    await new Promise(resolve => setTimeout(resolve, delay));
    return withRetry(fn, retries - 1, delay * 2);
  }
}

/** 判断错误是否可重试。 */
export function isRetryable(error: unknown): boolean {
  if (error instanceof AppErrorImpl) return error.retryable;
  return false;
}

/** 判断错误是否可恢复（UI 可提示用户继续）。 */
export function isRecoverable(error: unknown): boolean {
  if (error instanceof AppErrorImpl) return error.recoverable;
  return true;  // 未知错误默认可恢复，避免阻断用户
}
