/**
 * @file 边缘检测器
 * @description 提供边缘检测算法（Sobel、Canny等）
 * @module utils/edge-detector
 */

/**
 * 边缘检测器类
 * 提供各种边缘检测算法用于图像处理
 */
export class EdgeDetector {
  /**
   * 使用Sobel算子进行边缘检测
   * @param imageData 灰度图像数据
   * @param threshold 边缘阈值，默认为30
   * @returns 检测到边缘的图像数据
   */
  static detectEdges(imageData: ImageData, threshold: number = 30): ImageData {
    const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
    const width = grayscaleImage.width;
    const height = grayscaleImage.height;
    const inputData = grayscaleImage.data;
    const outputData = new Uint8ClampedArray(inputData.length);
    const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const sobelY = [-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 gx = 0, gy = 0;
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const pixelPos = ((y + ky) * width + (x + kx)) * 4;
            const pixelVal = inputData[pixelPos];
            const kernelIdx = (ky + 1) * 3 + (kx + 1);
            gx += pixelVal * sobelX[kernelIdx];
            gy += pixelVal * sobelY[kernelIdx];
          }
        }
        let magnitude = Math.sqrt(gx * gx + gy * gy);
        magnitude = magnitude > threshold ? 255 : 0;
        const pos = (y * width + x) * 4;
        outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = magnitude;
        outputData[pos + 3] = 255;
      }
    }
    
    // 处理边缘
    for (let i = 0; i < width * 4; i++) {
      outputData[i] = 0;
      outputData[(height - 1) * width * 4 + i] = 0;
    }
    for (let i = 0; i < height; i++) {
      const leftPos = i * width * 4;
      const rightPos = (i * width + width - 1) * 4;
      for (let j = 0; j < 4; j++) {
        outputData[leftPos + j] = 0;
        outputData[rightPos + j] = 0;
      }
    }
    
    return new ImageData(outputData, width, height);
  }

  /**
   * 卡尼-德里奇边缘检测
   */
  static cannyEdgeDetection(imageData: ImageData, lowThreshold: number = 20, highThreshold: number = 50): ImageData {
    const grayscaleImage = this.toGrayscale(new ImageData(new Uint8ClampedArray(imageData.data), imageData.width, imageData.height));
    const blurredImage = this.gaussianBlur(grayscaleImage, 1.5);
    const { gradientMagnitude, gradientDirection } = this.computeGradients(blurredImage);
    const nonMaxSuppressed = this.nonMaxSuppression(gradientMagnitude, gradientDirection, blurredImage.width, blurredImage.height);
    const thresholdResult = this.hysteresisThresholding(nonMaxSuppressed, blurredImage.width, blurredImage.height, lowThreshold, highThreshold);
    
    const outputData = new Uint8ClampedArray(imageData.data.length);
    for (let i = 0; i < thresholdResult.length; i++) {
      const pos = i * 4;
      const value = thresholdResult[i] ? 255 : 0;
      outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
      outputData[pos + 3] = 255;
    }
    return new ImageData(outputData, blurredImage.width, blurredImage.height);
  }

  private static toGrayscale(imageData: ImageData): ImageData {
    const srcData = imageData.data;
    const destData = new Uint8ClampedArray(srcData);
    for (let i = 0; i < srcData.length; i += 4) {
      const gray = srcData[i] * 0.3 + srcData[i + 1] * 0.59 + srcData[i + 2] * 0.11;
      destData[i] = destData[i + 1] = destData[i + 2] = gray;
      destData[i + 3] = srcData[i + 3];
    }
    return new ImageData(destData, imageData.width, imageData.height);
  }

  private static gaussianBlur(imageData: ImageData, sigma: number = 1.5): ImageData {
    const width = imageData.width, height = imageData.height;
    const inputData = imageData.data, outputData = new Uint8ClampedArray(inputData.length);
    const kernelSize = Math.max(3, Math.floor(sigma * 3) * 2 + 1);
    const halfKernel = Math.floor(kernelSize / 2);
    const kernel = this.generateGaussianKernel(kernelSize, sigma);
    
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        let sum = 0, weightSum = 0;
        for (let ky = -halfKernel; ky <= halfKernel; ky++) {
          for (let kx = -halfKernel; kx <= halfKernel; kx++) {
            const pixelY = Math.min(Math.max(y + ky, 0), height - 1);
            const pixelX = Math.min(Math.max(x + kx, 0), width - 1);
            const pixelPos = (pixelY * width + pixelX) * 4;
            const kernelY = ky + halfKernel, kernelX = kx + halfKernel;
            const weight = kernel[kernelY * kernelSize + kernelX];
            sum += inputData[pixelPos] * weight;
            weightSum += weight;
          }
        }
        const pos = (y * width + x) * 4;
        const value = Math.round(sum / weightSum);
        outputData[pos] = outputData[pos + 1] = outputData[pos + 2] = value;
        outputData[pos + 3] = 255;
      }
    }
    return new ImageData(outputData, width, height);
  }

  private static generateGaussianKernel(size: number, sigma: number): number[] {
    const kernel = new Array(size * size);
    const center = Math.floor(size / 2);
    let sum = 0;
    for (let y = 0; y < size; y++) {
      for (let x = 0; x < size; x++) {
        const distance = Math.sqrt((x - center) ** 2 + (y - center) ** 2);
        kernel[y * size + x] = Math.exp(-(distance ** 2) / (2 * sigma ** 2));
        sum += kernel[y * size + x];
      }
    }
    for (let i = 0; i < kernel.length; i++) kernel[i] /= sum;
    return kernel;
  }

  private static computeGradients(imageData: ImageData): { gradientMagnitude: number[], gradientDirection: number[] } {
    const width = imageData.width, height = imageData.height;
    const inputData = imageData.data;
    const gradientMagnitude = new Array(width * height);
    const gradientDirection = new Array(width * height);
    const sobelX = [-1, 0, 1, -2, 0, 2, -1, 0, 1];
    const sobelY = [-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 gx = 0, gy = 0;
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const pixelPos = ((y + ky) * width + (x + kx)) * 4;
            const pixelVal = inputData[pixelPos];
            const kernelIdx = (ky + 1) * 3 + (kx + 1);
            gx += pixelVal * sobelX[kernelIdx];
            gy += pixelVal * sobelY[kernelIdx];
          }
        }
        const idx = y * width + x;
        gradientMagnitude[idx] = Math.sqrt(gx * gx + gy * gy);
        gradientDirection[idx] = Math.atan2(gy, gx);
      }
    }
    return { gradientMagnitude, gradientDirection };
  }

  private static nonMaxSuppression(gradientMagnitude: number[], gradientDirection: number[], width: number, height: number): number[] {
    const result = new Array(width * height).fill(0);
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        const idx = y * width + x;
        const magnitude = gradientMagnitude[idx];
        const direction = gradientDirection[idx];
        const degrees = (direction * 180 / Math.PI + 180) % 180;
        let neighbor1Idx: number, neighbor2Idx: number;
        
        if ((degrees >= 0 && degrees < 22.5) || (degrees >= 157.5 && degrees <= 180)) {
          neighbor1Idx = idx - 1; neighbor2Idx = idx + 1;
        } else if (degrees >= 22.5 && degrees < 67.5) {
          neighbor1Idx = (y - 1) * width + (x + 1); neighbor2Idx = (y + 1) * width + (x - 1);
        } else if (degrees >= 67.5 && degrees < 112.5) {
          neighbor1Idx = (y - 1) * width + x; neighbor2Idx = (y + 1) * width + x;
        } else {
          neighbor1Idx = (y - 1) * width + (x - 1); neighbor2Idx = (y + 1) * width + (x + 1);
        }
        
        if (magnitude >= gradientMagnitude[neighbor1Idx] && magnitude >= gradientMagnitude[neighbor2Idx]) {
          result[idx] = magnitude;
        }
      }
    }
    return result;
  }

  private static hysteresisThresholding(nonMaxSuppressed: number[], width: number, height: number, lowThreshold: number, highThreshold: number): boolean[] {
    const result = new Array(width * height).fill(false);
    const visited = new Array(width * height).fill(false);
    const stack: number[] = [];
    
    for (let i = 0; i < nonMaxSuppressed.length; i++) {
      if (nonMaxSuppressed[i] >= highThreshold) {
        result[i] = true;
        stack.push(i);
        visited[i] = true;
      }
    }
    
    const dx = [-1, 0, 1, -1, 1, -1, 0, 1];
    const dy = [-1, -1, -1, 0, 0, 1, 1, 1];
    
    while (stack.length > 0) {
      const currentIdx = stack.pop()!;
      const currentX = currentIdx % width;
      const currentY = Math.floor(currentIdx / width);
      
      for (let i = 0; i < 8; i++) {
        const newX = currentX + dx[i];
        const newY = currentY + dy[i];
        if (newX >= 0 && newX < width && newY >= 0 && newY < height) {
          const newIdx = newY * width + newX;
          if (!visited[newIdx] && nonMaxSuppressed[newIdx] >= lowThreshold) {
            result[newIdx] = true;
            stack.push(newIdx);
            visited[newIdx] = true;
          }
        }
      }
    }
    return result;
  }
}
