export interface RetryConfig {
  maxRetries: number;
  baseDelay: number;
  maxDelay: number;
  backoffMultiplier: number;
}

export interface AgentConfig {
  name: string;
  dependencies: string[];
  critical: boolean;
  retryConfig: RetryConfig;
  timeout: number;
}

export class AgentError extends Error {
  constructor(
    public agentName: string,
    message: string,
    public isRetryable: boolean = true,
    public originalError?: Error
  ) {
    super(`[${agentName}] ${message}`);
    this.name = 'AgentError';
  }
}

export class OrchestrationError extends Error {
  constructor(
    message: string,
    public failedAgents: string[],
    public completedAgents: string[]
  ) {
    super(message);
    this.name = 'OrchestrationError';
  }
}

export const calculateDelay = (attempt: number, config: RetryConfig): number => {
  const delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1);
  return Math.min(delay, config.maxDelay);
};

export const sleep = (ms: number): Promise<void> => 
  new Promise(resolve => setTimeout(resolve, ms));

export const withRetry = async <T>(
  operation: () => Promise<T>,
  agentName: string,
  config: RetryConfig
): Promise<T> => {
  let lastError: Error | null = null;
  
  for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error as Error;
      
      if (error instanceof AgentError && !error.isRetryable) {
        throw error;
      }
      
      if (attempt < config.maxRetries) {
        const delay = calculateDelay(attempt, config);
        console.warn(`⚠️  [${agentName}] Attempt ${attempt} failed: ${error}. Retrying in ${delay}ms...`);
        await sleep(delay);
      }
    }
  }
  
  throw new AgentError(
    agentName,
    `Failed after ${config.maxRetries} attempts. Last error: ${lastError?.message || 'Unknown error'}`,
    false,
    lastError || new Error('Unknown error')
  );
};

export const withTimeout = <T>(
  promise: Promise<T>,
  timeoutMs: number,
  agentName: string
): Promise<T> => {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      reject(new AgentError(agentName, `Operation timed out after ${timeoutMs}ms`, true));
    }, timeoutMs);

    promise
      .then(resolve)
      .catch(reject)
      .finally(() => clearTimeout(timeoutId));
  });
};

export const createAgentRunner = (config: AgentConfig) => {
  return async <T>(operation: () => Promise<T>): Promise<T> => {
    const timedOperation = () => withTimeout(operation(), config.timeout, config.name);
    return withRetry(timedOperation, config.name, config.retryConfig);
  };
};

// Default configurations
export const DEFAULT_RETRY_CONFIG: RetryConfig = {
  maxRetries: 3,
  baseDelay: 1000, // 1 second
  maxDelay: 30000, // 30 seconds
  backoffMultiplier: 2,
};

export const DEFAULT_AGENT_CONFIG: Omit<AgentConfig, 'name' | 'dependencies'> = {
  critical: false,
  retryConfig: DEFAULT_RETRY_CONFIG,
  timeout: 90000, // 90 seconds
};

export const CRITICAL_RETRY_CONFIG: RetryConfig = {
  maxRetries: 5,
  baseDelay: 2000, // 2 seconds
  maxDelay: 60000, // 60 seconds
  backoffMultiplier: 2,
};
