// src/middleware/retry.ts

import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { IRrRequestOptions } from '../engine/remoting/RequestReply';

export interface IRetryOptions {
  maxAttempts?: number;
  baseDelayMs?: number;
  maxDelayMs?: number;
  jitter?: boolean;
  shouldRetry?: (err: Error) => boolean;
}

/**
 * Creates a retry wrapper for a function that might fail.
 * Uses unknown[] for maximum type safety while preserving function signature inference.
 */
export function withRetry<TArgs extends unknown[], TResult>(
  fn: (...args: TArgs) => Promise<TResult>,
  options: IRetryOptions = {}
): (...args: TArgs) => Promise<TResult> {
  const {
    maxAttempts = 3,
    baseDelayMs = 100,
    maxDelayMs = 2000,
    jitter = true,
    shouldRetry = () => true,
  } = options;

  return (async (...args: TArgs): Promise<TResult> => {
    let lastError: Error | undefined;

    for (let attempt = 1; attempt <= maxAttempts; attempt++) {
      try {
        return await fn(...args);
      } catch (err) {
        lastError = err as Error;

        if (attempt >= maxAttempts || !shouldRetry(lastError)) {
          throw lastError;
        }

        // Exponential backoff with optional jitter
        let delay = Math.min(
          maxDelayMs,
          baseDelayMs * Math.pow(2, attempt - 1)
        );

        if (jitter) {
          delay = delay * (0.5 + Math.random() * 0.5);
        }

        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }

    // This should never happen, but TypeScript needs it
    throw lastError;
  });
}

/**
 * Creates a retry-enabled version of RequestReply.execute.
 */
export function createRetryingRR(
  context: IMatrixContext,
  options: IRetryOptions = {}
) {
  const originalRR = require('../engine/remoting/RequestReply').requestReply;

  return withRetry(
    async <TReq, TRes>(
      childMount: string,
      op: string,
      params: TReq,
      rrOptions?: IRrRequestOptions
    ): Promise<TRes> => {
      return originalRR(context, childMount, op, params, rrOptions) as Promise<TRes>;
    },
    options
  );
}
