/**
 * Utility functions for the Riichi Mahjong Tutorial
 */

import type { AnimationConfig, AnimationState, Point, Rectangle } from '../types';

// ============ Canvas Utilities ============

/**
 * Get a canvas context with optimizations
 */
export function getOptimizedContext(
  canvas: HTMLCanvasElement,
  alpha: boolean = false,
): CanvasRenderingContext2D | null {
  return canvas.getContext('2d', {
    alpha,
    desynchronized: true,
    willReadFrequently: false,
  });
}

/**
 * Clear a canvas with a solid color
 */
export function clearCanvas(
  ctx: CanvasRenderingContext2D,
  color: string,
  width: number,
  height: number,
): void {
  ctx.fillStyle = color;
  ctx.fillRect(0, 0, width, height);
}

/**
 * Create an offscreen canvas for double buffering
 */
export function createOffscreenCanvas(
  width: number,
  height: number,
): { canvas: HTMLCanvasElement; ctx: CanvasRenderingContext2D } | null {
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  
  const ctx = getOptimizedContext(canvas);
  if (!ctx) {
    return null;
  }
  
  return { canvas, ctx };
}

// ============ Math Utilities ============

/**
 * Clamp a value between min and max
 */
export function clamp(value: number, min: number, max: number): number {
  return Math.min(Math.max(value, min), max);
}

/**
 * Linear interpolation
 */
export function lerp(start: number, end: number, t: number): number {
  return start + (end - start) * clamp(t, 0, 1);
}

/**
 * Convert degrees to radians
 */
export function degToRad(degrees: number): number {
  return degrees * (Math.PI / 180);
}

/**
 * Convert radians to degrees
 */
export function radToDeg(radians: number): number {
  return radians * (180 / Math.PI);
}

/**
 * Get a random number between min and max
 */
export function randomRange(min: number, max: number): number {
  return min + Math.random() * (max - min);
}

/**
 * Get a random integer between min and max (inclusive)
 */
export function randomInt(min: number, max: number): number {
  return Math.floor(randomRange(min, max + 1));
}

// ============ Geometry Utilities ============

/**
 * Check if a point is inside a rectangle
 */
export function pointInRect(point: Point, rect: Rectangle): boolean {
  return (
    point.x >= rect.x &&
    point.x <= rect.x + rect.width &&
    point.y >= rect.y &&
    point.y <= rect.y + rect.height
  );
}

/**
 * Get the distance between two points
 */
export function distance(p1: Point, p2: Point): number {
  const dx = p2.x - p1.x;
  const dy = p2.y - p1.y;
  return Math.sqrt(dx * dx + dy * dy);
}

/**
 * Normalize an angle to be between 0 and 2π
 */
export function normalizeAngle(angle: number): number {
  const TWO_PI = Math.PI * 2;
  return ((angle % TWO_PI) + TWO_PI) % TWO_PI;
}

// ============ Animation Utilities ============

/**
 * Easing functions
 */
export const Easing = {
  linear: (t: number): number => t,
  
  easeIn: (t: number): number => t * t,
  
  easeOut: (t: number): number => t * (2 - t),
  
  easeInOut: (t: number): number => 
    t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
  
  easeInCubic: (t: number): number => t * t * t,
  
  easeOutCubic: (t: number): number => (--t) * t * t + 1,
  
  easeInOutCubic: (t: number): number => 
    t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
  
  easeOutBounce: (t: number): number => {
    if (t < 1 / 2.75) {
      return 7.5625 * t * t;
    } else if (t < 2 / 2.75) {
      return 7.5625 * (t -= 1.5 / 2.75) * t + 0.75;
    } else if (t < 2.5 / 2.75) {
      return 7.5625 * (t -= 2.25 / 2.75) * t + 0.9375;
    } else {
      return 7.5625 * (t -= 2.625 / 2.75) * t + 0.984375;
    }
  },
};

/**
 * Create an animation state
 */
export function createAnimation(config: AnimationConfig): AnimationState & { config: AnimationConfig } {
  return {
    config,
    startTime: 0,
    progress: 0,
    isComplete: false,
  };
}

/**
 * Update an animation state
 */
export function updateAnimation(
  state: AnimationState & { config: AnimationConfig },
  currentTime: number,
): void {
  if (state.startTime === 0) {
    state.startTime = currentTime + (state.config.delay ?? 0);
  }
  
  const elapsed = currentTime - state.startTime;
  state.progress = clamp(elapsed / state.config.duration, 0, 1);
  state.isComplete = state.progress >= 1;
}

// ============ Array Utilities ============

/**
 * Shuffle an array in place (Fisher-Yates)
 */
export function shuffleArray<T>(array: T[]): T[] {
  for (let i = array.length - 1; i > 0; i--) {
    const j = randomInt(0, i);
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}

/**
 * Remove an item from an array by index
 */
export function removeAt<T>(array: T[], index: number): T | undefined {
  if (index >= 0 && index < array.length) {
    return array.splice(index, 1)[0];
  }
  return undefined;
}

/**
 * Get unique values from an array
 */
export function unique<T>(array: T[]): T[] {
  return [...new Set(array)];
}

// ============ DOM Utilities ============

/**
 * Get element by ID with type safety
 */
export function getElementById<T extends HTMLElement>(id: string): T | null {
  return document.getElementById(id) as T | null;
}

/**
 * Create an element with attributes
 */
export function createElement<K extends keyof HTMLElementTagNameMap>(
  tag: K,
  attributes?: Partial<HTMLElementTagNameMap[K]>,
): HTMLElementTagNameMap[K] {
  const element = document.createElement(tag);
  if (attributes) {
    Object.assign(element, attributes);
  }
  return element;
}

// ============ Image Utilities ============

/**
 * Load an image as a Promise
 */
export function loadImage(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
    img.src = src;
  });
}

/**
 * Preload multiple images
 */
export async function preloadImages(sources: string[]): Promise<HTMLImageElement[]> {
  return Promise.all(sources.map(loadImage));
}

// ============ Time Utilities ============

/**
 * Create a debounced function
 */
export function debounce<T extends (...args: unknown[]) => void>(
  fn: T,
  delay: number,
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
  
  return (...args: Parameters<T>) => {
    if (timeoutId) {
      clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => fn(...args), delay);
  };
}

/**
 * Create a throttled function
 */
export function throttle<T extends (...args: unknown[]) => void>(
  fn: T,
  limit: number,
): (...args: Parameters<T>) => void {
  let inThrottle = false;
  
  return (...args: Parameters<T>) => {
    if (!inThrottle) {
      fn(...args);
      inThrottle = true;
      setTimeout(() => { inThrottle = false; }, limit);
    }
  };
}

/**
 * Wait for a specified duration
 */
export function wait(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// ============ Storage Utilities ============

/**
 * Safe localStorage get with JSON parsing
 */
export function getStorageItem<T>(key: string, defaultValue: T): T {
  try {
    const item = localStorage.getItem(key);
    return item ? JSON.parse(item) as T : defaultValue;
  } catch {
    return defaultValue;
  }
}

/**
 * Safe localStorage set with JSON stringify
 */
export function setStorageItem<T>(key: string, value: T): void {
  try {
    localStorage.setItem(key, JSON.stringify(value));
  } catch {
    // Storage might be full or disabled
    console.warn(`Failed to save to localStorage: ${key}`);
  }
}
