export function debounce<T extends (...args: any[]) => any>(
  func: T,
  wait: number
): (...args: Parameters<T>) => void {
  let timeout: number | undefined;

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

    clearTimeout(timeout);
    timeout = window.setTimeout(later, wait);
  };
}

export function throttle<T extends (...args: any[]) => any>(
  func: T,
  limit: number
): (...args: Parameters<T>) => void {
  let inThrottle: boolean;

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

export function formatFileSize(bytes: number): string {
  if (bytes === 0) return '0 Bytes';

  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));

  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
}

export function isInViewport(element: Element, margin = 0): boolean {
  const rect = element.getBoundingClientRect();
  return (
    rect.top >= -margin &&
    rect.left >= -margin &&
    rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) + margin &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth) + margin
  );
}

export class PerformanceMonitor {
  private static marks = new Map<string, number>();
  private static measures: Array<{ name: string; duration: number }> = [];

  public static mark(name: string) {
    this.marks.set(name, performance.now());
  }

  public static measure(name: string, startMark: string) {
    const start = this.marks.get(startMark);
    if (start) {
      const duration = performance.now() - start;
      this.measures.push({ name, duration });
      this.marks.delete(startMark);
      return duration;
    }
    return 0;
  }

  public static getReport() {
    const report = {
      measures: [...this.measures],
      averages: {} as Record<string, number>,
    };

    // Calculate averages for repeated measures
    const grouped = new Map<string, number[]>();
    for (const measure of this.measures) {
      if (!grouped.has(measure.name)) {
        grouped.set(measure.name, []);
      }
      grouped.get(measure.name)!.push(measure.duration);
    }

    for (const [name, durations] of grouped) {
      report.averages[name] = durations.reduce((a, b) => a + b, 0) / durations.length;
    }

    return report;
  }

  public static clear() {
    this.marks.clear();
    this.measures = [];
  }
}