export class ImageProcessing {
  static computeGradients(data: Uint8ClampedArray, width: number, height: number): { magnitude: Float32Array, direction: Float32Array } {
    const magnitude = new Float32Array(width * height);
    const direction = new Float32Array(width * height);
    const gx_kernel = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const gy_kernel = [-1, -2, -1, 0, 0, 0, 1, 2, 1];

    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        let sumX = 0;
        let sumY = 0;

        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const pixel = data[((y + ky) * width + (x + kx)) * 4];
            sumX += pixel * gx_kernel[(ky + 1) * 3 + (kx + 1)];
            sumY += pixel * gy_kernel[(ky + 1) * 3 + (kx + 1)];
          }
        }
        magnitude[y * width + x] = Math.sqrt(sumX * sumX + sumY * sumY);
        direction[y * width + x] = Math.atan2(sumY, sumX);
      }
    }
    return { magnitude, direction };
  }

  static nonMaximumSuppression(magnitude: Float32Array, direction: Float32Array, width: number, height: number): Float32Array {
    const suppressed = new Float32Array(width * height);
    
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        const idx = y * width + x;
        const mag = magnitude[idx];
        const angle = direction[idx] * (180 / Math.PI);
        let normalizedAngle = angle < 0 ? angle + 180 : angle;

        let neighbor1 = 0;
        let neighbor2 = 0;

        if ((normalizedAngle >= 0 && normalizedAngle < 22.5) || (normalizedAngle >= 157.5 && normalizedAngle <= 180)) {
          neighbor1 = magnitude[y * width + (x + 1)];
          neighbor2 = magnitude[y * width + (x - 1)];
        } else if (normalizedAngle >= 22.5 && normalizedAngle < 67.5) {
          neighbor1 = magnitude[(y - 1) * width + (x + 1)];
          neighbor2 = magnitude[(y + 1) * width + (x - 1)];
        } else if (normalizedAngle >= 67.5 && normalizedAngle < 112.5) {
          neighbor1 = magnitude[(y - 1) * width + x];
          neighbor2 = magnitude[(y + 1) * width + x];
        } else if (normalizedAngle >= 112.5 && normalizedAngle < 157.5) {
          neighbor1 = magnitude[(y - 1) * width + (x - 1)];
          neighbor2 = magnitude[(y + 1) * width + (x + 1)];
        }

        if (mag >= neighbor1 && mag >= neighbor2) {
          suppressed[idx] = mag;
        } else {
          suppressed[idx] = 0;
        }
      }
    }
    return suppressed;
  }

  static applyHysteresis(edges: Float32Array, width: number, height: number, lowThreshold: number, highThreshold: number): Float32Array {
    const result = new Float32Array(width * height);
    const strongEdges = [];
    
    for (let i = 0; i < edges.length; i++) {
      if (edges[i] >= highThreshold) {
        result[i] = edges[i];
        strongEdges.push(i);
      } else if (edges[i] < lowThreshold) {
        result[i] = 0;
      }
    }

    const stack = [...strongEdges];
    while (stack.length > 0) {
      const idx = stack.pop()!;
      const x = idx % width;
      const y = Math.floor(idx / width);

      const neighbors = [
        [x + 1, y], [x - 1, y], [x, y + 1], [x, y - 1],
        [x + 1, y + 1], [x - 1, y + 1], [x + 1, y - 1], [x - 1, y - 1]
      ];

      for (const [nx, ny] of neighbors) {
        if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
          const nIdx = ny * width + nx;
          if (result[nIdx] === 0 && edges[nIdx] >= lowThreshold) {
            result[nIdx] = edges[nIdx];
            stack.push(nIdx);
          }
        }
      }
    }
    return result;
  }

  static rgbToHsv(r: number, g: number, b: number): { h: number, s: number, v: number } {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b), min = Math.min(r, g, b);
    let h = 0, s, v = max;
    const d = max - min;
    s = max === 0 ? 0 : d / max;

    if (max === min) {
      h = 0;
    } else {
      switch (max) {
        case r: h = (g - b) / d + (g < b ? 6 : 0); break;
        case g: h = (b - r) / d + 2; break;
        case b: h = (r - g) / d + 4; break;
      }
      h /= 6;
    }
    return { h: h * 360, s: s * 100, v: v * 100 };
  }

  static toGrayscale(imageData: ImageData): Uint8ClampedArray {
    const { data, width, height } = imageData;
    const gray = new Uint8ClampedArray(width * height);
    for (let i = 0; i < data.length; i += 4) {
      gray[i / 4] = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2];
    }
    return gray;
  }

  static detectEdges(imageData: ImageData): Float32Array {
    const gray = this.toGrayscale(imageData);
    const grayWithChannels = new Uint8ClampedArray(gray.length * 4);
    for (let i = 0; i < gray.length; i++) {
      grayWithChannels[i * 4] = gray[i];
    }
    const { magnitude, direction } = this.computeGradients(grayWithChannels, imageData.width, imageData.height);
    const nmsEdges = this.nonMaximumSuppression(magnitude, direction, imageData.width, imageData.height);
    return this.applyHysteresis(nmsEdges, imageData.width, imageData.height, 30, 70);
  }
}
