/**
 * Common utilities and helper functions
 */

import { randomUUID } from 'node:crypto';
import { Result, success, failure } from './types.js';

/**
 * Async wrapper that returns Result instead of throwing
 */
export async function safely<T>(operation: () => Promise<T>): Promise<Result<T, Error>> {
  try {
    const result = await operation();
    return success(result);
  } catch (error) {
    return failure(error instanceof Error ? error : new Error(String(error)));
  }
}

/**
 * Retry an async operation with exponential backoff
 */
export async function retry<T>(
  operation: () => Promise<T>,
  options: {
    maxAttempts?: number;
    baseDelay?: number;
    maxDelay?: number;
    backoffMultiplier?: number;
  } = {},
): Promise<T> {
  const { maxAttempts = 3, baseDelay = 1000, maxDelay = 10000, backoffMultiplier = 2 } = options;

  let lastError: Error = new Error('Retry failed with unknown error');

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await operation();
    } catch (error) {
      lastError = error instanceof Error ? error : new Error(String(error));

      if (attempt === maxAttempts) {
        throw lastError;
      }

      const delay = Math.min(baseDelay * Math.pow(backoffMultiplier, attempt - 1), maxDelay);
      await sleep(delay);
    }
  }

  // This point is unreachable since we either return or throw in the loop,
  // but TypeScript can't infer that. Provide a fallback error.
  throw lastError;
}

/**
 * Sleep for a given number of milliseconds
 */
export function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Deep merge objects with proper typing
 */
export function deepMerge<T extends Record<string, unknown>>(
  target: T,
  ...sources: Array<Partial<T> | null | undefined>
): T {
  if (!sources.length) return target;

  const source = sources.shift();
  if (!source) return deepMerge(target, ...sources);

  for (const key in source) {
    if (Object.prototype.hasOwnProperty.call(source, key)) {
      // Guard against prototype pollution
      if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;

      const sourceValue = source[key];
      const targetValue = target[key];

      if (
        sourceValue &&
        typeof sourceValue === 'object' &&
        !Array.isArray(sourceValue) &&
        targetValue &&
        typeof targetValue === 'object' &&
        !Array.isArray(targetValue)
      ) {
        deepMerge(targetValue as Record<string, unknown>, sourceValue as Record<string, unknown>);
      } else if (sourceValue !== undefined) {
        target[key] = sourceValue as T[Extract<keyof T, string>];
      }
    }
  }

  return deepMerge(target, ...sources);
}

/**
 * Generate a UUID v4
 */
export function generateId(): string {
  return randomUUID();
}

/**
 * Generate a short random ID for non-security purposes (request IDs, correlation IDs)
 */
export function cryptoId(prefix = ''): string {
  const id = randomUUID().replace(/-/g, '').substring(0, 12);
  return prefix ? `${prefix}_${Date.now()}_${id}` : id;
}

/**
 * Debounce a function with proper typing
 */
export function debounce<T extends (...args: never[]) => unknown>(
  func: T,
  wait: number,
): (...args: Parameters<T>) => void {
  let timeout: NodeJS.Timeout | undefined;

  return function executedFunction(...args: Parameters<T>): void {
    const later = (): void => {
      timeout = undefined;
      func(...args);
    };

    if (timeout !== undefined) {
      clearTimeout(timeout);
    }
    timeout = setTimeout(later, wait);
  };
}

/**
 * Throttle a function with proper typing
 */
export function throttle<T extends (...args: never[]) => unknown>(
  func: T,
  limit: number,
): (...args: Parameters<T>) => void {
  let inThrottle = false;

  return function executedFunction(...args: Parameters<T>): void {
    if (!inThrottle) {
      func(...args);
      inThrottle = true;
      setTimeout(() => {
        inThrottle = false;
      }, limit);
    }
  };
}

/**
 * Pick specific keys from an object
 */
export function pick<T extends Record<string, unknown>, K extends keyof T>(
  obj: T,
  keys: readonly K[],
): Pick<T, K> {
  const result = {} as Pick<T, K>;
  for (const key of keys) {
    if (key in obj) {
      result[key] = obj[key];
    }
  }
  return result;
}

/**
 * Omit specific keys from an object
 */
export function omit<T extends Record<string, unknown>, K extends keyof T>(
  obj: T,
  keys: readonly K[],
): Omit<T, K> {
  const result = { ...obj };
  for (const key of keys) {
    delete result[key];
  }
  return result;
}

/**
 * Check if a value is empty (null, undefined, empty string, empty array, empty object)
 */
export function isEmpty(value: unknown): boolean {
  if (value == null) return true;
  if (typeof value === 'string') return value.length === 0;
  if (Array.isArray(value)) return value.length === 0;
  if (typeof value === 'object') return Object.keys(value).length === 0;
  return false;
}

/**
 * Validate email format
 */
export function isValidEmail(email: string): boolean {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
}

/**
 * Sanitize string for safe usage
 */
export function sanitize(input: string): string {
  let result = input;
  const dangerousSchemes = /javascript:|data:|vbscript:/gi;
  const eventHandlers = /on\w+=/gi;

  // Loop until stable to prevent bypass via nested dangerous strings
  let prev: string;
  do {
    prev = result;
    result = result.replace(/[<>]/g, '').replace(dangerousSchemes, '').replace(eventHandlers, '');
  } while (result !== prev);

  return result.trim();
}
