/**
 * Image utility functions for the recognition pipeline.
 * Provides resize, rotation, and pixel manipulation helpers.
 */

export interface Point {
  x: number;
  y: number;
}

export interface Size {
  width: number;
  height: number;
}

/**
 * Resize RGBA pixel data to target dimensions using bilinear interpolation.
 */
export function resizeImage(
  pixels: Uint8Array | Uint8ClampedArray,
  srcWidth: number,
  srcHeight: number,
  dstWidth: number,
  dstHeight: number
): Uint8Array {
  const result = new Uint8Array(dstWidth * dstHeight * 4);
  
  const xRatio = srcWidth / dstWidth;
  const yRatio = srcHeight / dstHeight;
  
  for (let y = 0; y < dstHeight; y++) {
    for (let x = 0; x < dstWidth; x++) {
      const srcX = x * xRatio;
      const srcY = y * yRatio;
      
      const x0 = Math.floor(srcX);
      const y0 = Math.floor(srcY);
      const x1 = Math.min(x0 + 1, srcWidth - 1);
      const y1 = Math.min(y0 + 1, srcHeight - 1);
      
      const xFrac = srcX - x0;
      const yFrac = srcY - y0;
      
      const dstIdx = (y * dstWidth + x) * 4;
      
      for (let c = 0; c < 4; c++) {
        const idx00 = (y0 * srcWidth + x0) * 4 + c;
        const idx10 = (y0 * srcWidth + x1) * 4 + c;
        const idx01 = (y1 * srcWidth + x0) * 4 + c;
        const idx11 = (y1 * srcWidth + x1) * 4 + c;
        
        const top = pixels[idx00] + (pixels[idx10] - pixels[idx00]) * xFrac;
        const bottom = pixels[idx01] + (pixels[idx11] - pixels[idx01]) * xFrac;
        result[dstIdx + c] = Math.round(top + (bottom - top) * yFrac);
      }
    }
  }
  
  return result;
}

/**
 * Rotate RGBA pixel data by 90, 180, or 270 degrees.
 */
export function rotateImage(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number,
  degrees: 0 | 90 | 180 | 270
): { pixels: Uint8Array; width: number; height: number } {
  if (degrees === 0) {
    return { pixels: new Uint8Array(pixels), width, height };
  }
  
  const [newWidth, newHeight] = degrees === 90 || degrees === 270
    ? [height, width]
    : [width, height];
  
  const result = new Uint8Array(newWidth * newHeight * 4);
  
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      const srcIdx = (y * width + x) * 4;
      
      let dstX: number, dstY: number;
      
      switch (degrees) {
        case 90:
          dstX = height - 1 - y;
          dstY = x;
          break;
        case 180:
          dstX = width - 1 - x;
          dstY = height - 1 - y;
          break;
        case 270:
          dstX = y;
          dstY = width - 1 - x;
          break;
        default:
          dstX = x;
          dstY = y;
      }
      
      const dstIdx = (dstY * newWidth + dstX) * 4;
      result[dstIdx] = pixels[srcIdx];
      result[dstIdx + 1] = pixels[srcIdx + 1];
      result[dstIdx + 2] = pixels[srcIdx + 2];
      result[dstIdx + 3] = pixels[srcIdx + 3];
    }
  }
  
  return { pixels: result, width: newWidth, height: newHeight };
}

/**
 * Convert RGBA to grayscale using luminance formula.
 */
export function toGrayscale(pixels: Uint8Array | Uint8ClampedArray): Uint8Array {
  const numPixels = pixels.length / 4;
  const gray = new Uint8Array(numPixels);
  
  for (let i = 0; i < numPixels; i++) {
    const idx = i * 4;
    gray[i] = Math.round(
      0.299 * pixels[idx] +
      0.587 * pixels[idx + 1] +
      0.114 * pixels[idx + 2]
    );
  }
  
  return gray;
}

/**
 * Convert grayscale back to RGBA.
 */
export function grayscaleToRgba(
  gray: Uint8Array,
  width: number,
  height: number
): Uint8Array {
  const rgba = new Uint8Array(width * height * 4);
  
  for (let i = 0; i < gray.length; i++) {
    const idx = i * 4;
    rgba[idx] = gray[i];
    rgba[idx + 1] = gray[i];
    rgba[idx + 2] = gray[i];
    rgba[idx + 3] = 255;
  }
  
  return rgba;
}

/**
 * Apply Gaussian blur to grayscale image.
 * Uses separable 1D convolution for efficiency.
 */
export function gaussianBlur(
  gray: Uint8Array,
  width: number,
  height: number,
  radius: number = 5
): Uint8Array {
  // Generate Gaussian kernel
  const sigma = radius / 2;
  const size = radius * 2 + 1;
  const kernel = new Float32Array(size);
  let sum = 0;
  
  for (let i = 0; i < size; i++) {
    const x = i - radius;
    kernel[i] = Math.exp(-(x * x) / (2 * sigma * sigma));
    sum += kernel[i];
  }
  
  // Normalize
  for (let i = 0; i < size; i++) {
    kernel[i] /= sum;
  }
  
  // Horizontal pass
  const temp = new Uint8Array(width * height);
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      let value = 0;
      for (let k = -radius; k <= radius; k++) {
        const sx = Math.max(0, Math.min(width - 1, x + k));
        value += gray[y * width + sx] * kernel[k + radius];
      }
      temp[y * width + x] = Math.round(value);
    }
  }
  
  // Vertical pass
  const result = new Uint8Array(width * height);
  for (let y = 0; y < height; y++) {
    for (let x = 0; x < width; x++) {
      let value = 0;
      for (let k = -radius; k <= radius; k++) {
        const sy = Math.max(0, Math.min(height - 1, y + k));
        value += temp[sy * width + x] * kernel[k + radius];
      }
      result[y * width + x] = Math.round(value);
    }
  }
  
  return result;
}

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

/**
 * Calculate cross product of vectors (b-a) and (c-b).
 */
export function crossProduct(a: Point, b: Point, c: Point): number {
  return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}

/**
 * Calculate point-to-line distance.
 */
export function pointToLineDistance(point: Point, lineStart: Point, lineEnd: Point): number {
  const dx = lineEnd.x - lineStart.x;
  const dy = lineEnd.y - lineStart.y;
  const lengthSquared = dx * dx + dy * dy;
  
  if (lengthSquared === 0) {
    return distance(point, lineStart);
  }
  
  let t = ((point.x - lineStart.x) * dx + (point.y - lineStart.y) * dy) / lengthSquared;
  t = Math.max(0, Math.min(1, t));
  
  const projection = {
    x: lineStart.x + t * dx,
    y: lineStart.y + t * dy,
  };
  
  return distance(point, projection);
}

/**
 * Sample a pixel from RGBA data using bilinear interpolation.
 */
export function sampleBilinear(
  pixels: Uint8Array | Uint8ClampedArray,
  width: number,
  height: number,
  x: number,
  y: number
): [number, number, number, number] {
  // Clamp to valid range
  x = Math.max(0, Math.min(width - 1, x));
  y = Math.max(0, Math.min(height - 1, y));
  
  const x0 = Math.floor(x);
  const y0 = Math.floor(y);
  const x1 = Math.min(x0 + 1, width - 1);
  const y1 = Math.min(y0 + 1, height - 1);
  
  const xFrac = x - x0;
  const yFrac = y - y0;
  
  const idx00 = (y0 * width + x0) * 4;
  const idx10 = (y0 * width + x1) * 4;
  const idx01 = (y1 * width + x0) * 4;
  const idx11 = (y1 * width + x1) * 4;
  
  const result: [number, number, number, number] = [0, 0, 0, 0];
  
  for (let c = 0; c < 4; c++) {
    const c00 = pixels[idx00 + c];
    const c10 = pixels[idx10 + c];
    const c01 = pixels[idx01 + c];
    const c11 = pixels[idx11 + c];
    
    const top = c00 + (c10 - c00) * xFrac;
    const bottom = c01 + (c11 - c01) * xFrac;
    result[c] = Math.round(top + (bottom - top) * yFrac);
  }
  
  return result;
}
