/**
 * AI-Powered Image Sharpening
 * Uses edge detection and adaptive sharpening algorithms
 */

class AISharpener {
  constructor() {
    this.edgeDetectionKernel = [
      [-1, -1, -1],
      [-1,  8, -1],
      [-1, -1, -1]
    ];
    
    this.sharpenKernel = [
      [ 0, -1,  0],
      [-1,  5, -1],
      [ 0, -1,  0]
    ];
    
    this.strongSharpenKernel = [
      [-1, -1, -1],
      [-1,  9, -1],
      [-1, -1, -1]
    ];
  }

  /**
   * Apply AI-powered sharpening to an image
   * @param {HTMLImageElement|Blob} input - Input image
   * @param {Object} options - Sharpening options
   * @returns {Promise<Blob>} - Sharpened image blob
   */
  async sharpen(input, options = {}) {
    const {
      strength = 'moderate', // 'subtle', 'moderate', 'strong', 'extreme'
      preserveDetails = true,
      reduceNoise = true,
      adaptiveSharpening = true,
      progress = null
    } = options;

    return new Promise((resolve, reject) => {
      const img = new Image();
      
      const loadImage = (src) => {
        img.onload = () => {
          try {
            const result = this._processImage(img, {
              strength,
              preserveDetails,
              reduceNoise,
              adaptiveSharpening,
              progress
            });
            resolve(result);
          } catch (error) {
            reject(error);
          }
        };
        
        img.onerror = () => {
          reject(new Error('Failed to load image'));
        };
        
        img.src = src;
      };

      if (input instanceof Blob) {
        const reader = new FileReader();
        reader.onload = (e) => loadImage(e.target.result);
        reader.onerror = () => reject(new Error('Failed to read image'));
        reader.readAsDataURL(input);
      } else if (input instanceof HTMLImageElement) {
        loadImage(input.src);
      } else {
        reject(new Error('Invalid input type'));
      }
    });
  }

  /**
   * Process the image with AI sharpening
   * @private
   */
  _processImage(img, options) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    
    canvas.width = img.width;
    canvas.height = img.height;
    
    // Draw original image
    ctx.drawImage(img, 0, 0);
    
    // Get image data
    const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
    const data = imageData.data;
    const width = canvas.width;
    const height = canvas.height;
    
    if (options.progress) {
      options.progress('analyzing', 0, 100);
    }
    
    // Step 1: Detect edges (AI-like feature detection)
    const edgeMap = this._detectEdges(data, width, height);
    
    if (options.progress) {
      options.progress('processing', 25, 100);
    }
    
    // Step 2: Apply noise reduction if enabled
    let processedData = new Uint8ClampedArray(data);
    if (options.reduceNoise) {
      processedData = this._reduceNoise(processedData, width, height);
    }
    
    if (options.progress) {
      options.progress('processing', 50, 100);
    }
    
    // Step 3: Apply adaptive sharpening based on edge map
    if (options.adaptiveSharpening) {
      processedData = this._adaptiveSharpen(
        processedData,
        edgeMap,
        width,
        height,
        options.strength
      );
    } else {
      processedData = this._uniformSharpen(
        processedData,
        width,
        height,
        options.strength
      );
    }
    
    if (options.progress) {
      options.progress('processing', 75, 100);
    }
    
    // Step 4: Preserve details in important areas
    if (options.preserveDetails) {
      processedData = this._preserveDetails(
        data,
        processedData,
        edgeMap,
        width,
        height
      );
    }
    
    if (options.progress) {
      options.progress('finalizing', 90, 100);
    }
    
    // Copy processed data back
    for (let i = 0; i < data.length; i++) {
      data[i] = processedData[i];
    }
    
    ctx.putImageData(imageData, 0, 0);
    
    if (options.progress) {
      options.progress('complete', 100, 100);
    }
    
    // Convert to blob
    return new Promise((resolve, reject) => {
      canvas.toBlob((blob) => {
        if (blob) {
          resolve(blob);
        } else {
          reject(new Error('Failed to create blob'));
        }
      }, 'image/png');
    });
  }

  /**
   * Detect edges in the image (AI feature detection)
   * @private
   */
  _detectEdges(data, width, height) {
    const edgeMap = new Float32Array(width * height);
    
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        let edgeStrength = 0;
        
        // Apply edge detection kernel
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const px = x + kx;
            const py = y + ky;
            const idx = (py * width + px) * 4;
            
            // Calculate luminance
            const lum = 0.299 * data[idx] + 0.587 * data[idx + 1] + 0.114 * data[idx + 2];
            
            edgeStrength += lum * this.edgeDetectionKernel[ky + 1][kx + 1];
          }
        }
        
        edgeMap[y * width + x] = Math.abs(edgeStrength) / 255;
      }
    }
    
    return edgeMap;
  }

  /**
   * Reduce noise using bilateral filter
   * @private
   */
  _reduceNoise(data, width, height) {
    const result = new Uint8ClampedArray(data);
    const radius = 2;
    const sigmaSpace = 2.0;
    const sigmaColor = 25.0;
    
    for (let y = radius; y < height - radius; y++) {
      for (let x = radius; x < width - radius; x++) {
        const idx = (y * width + x) * 4;
        
        let sumR = 0, sumG = 0, sumB = 0, sumWeight = 0;
        
        const centerR = data[idx];
        const centerG = data[idx + 1];
        const centerB = data[idx + 2];
        
        for (let ky = -radius; ky <= radius; ky++) {
          for (let kx = -radius; kx <= radius; kx++) {
            const px = x + kx;
            const py = y + ky;
            const pidx = (py * width + px) * 4;
            
            // Spatial weight
            const spatialDist = kx * kx + ky * ky;
            const spatialWeight = Math.exp(-spatialDist / (2 * sigmaSpace * sigmaSpace));
            
            // Color weight
            const colorDist = Math.sqrt(
              Math.pow(data[pidx] - centerR, 2) +
              Math.pow(data[pidx + 1] - centerG, 2) +
              Math.pow(data[pidx + 2] - centerB, 2)
            );
            const colorWeight = Math.exp(-colorDist / (2 * sigmaColor * sigmaColor));
            
            const weight = spatialWeight * colorWeight;
            
            sumR += data[pidx] * weight;
            sumG += data[pidx + 1] * weight;
            sumB += data[pidx + 2] * weight;
            sumWeight += weight;
          }
        }
        
        result[idx] = sumR / sumWeight;
        result[idx + 1] = sumG / sumWeight;
        result[idx + 2] = sumB / sumWeight;
      }
    }
    
    return result;
  }

  /**
   * Apply adaptive sharpening based on edge map
   * @private
   */
  _adaptiveSharpen(data, edgeMap, width, height, strength) {
    const result = new Uint8ClampedArray(data);
    
    // Determine kernel based on strength
    let kernel, amount;
    switch (strength) {
      case 'subtle':
        kernel = this.sharpenKernel;
        amount = 0.3;
        break;
      case 'moderate':
        kernel = this.sharpenKernel;
        amount = 0.6;
        break;
      case 'strong':
        kernel = this.strongSharpenKernel;
        amount = 0.8;
        break;
      case 'extreme':
        kernel = this.strongSharpenKernel;
        amount = 1.2;
        break;
      default:
        kernel = this.sharpenKernel;
        amount = 0.6;
    }
    
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        const idx = (y * width + x) * 4;
        const edgeStrength = edgeMap[y * width + x];
        
        // Apply more sharpening to edges
        const localAmount = amount * (0.5 + edgeStrength * 1.5);
        
        let r = 0, g = 0, b = 0;
        
        // Apply convolution kernel
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const px = x + kx;
            const py = y + ky;
            const pidx = (py * width + px) * 4;
            const kernelValue = kernel[ky + 1][kx + 1];
            
            r += data[pidx] * kernelValue;
            g += data[pidx + 1] * kernelValue;
            b += data[pidx + 2] * kernelValue;
          }
        }
        
        // Blend with original based on local amount
        result[idx] = Math.max(0, Math.min(255, 
          data[idx] * (1 - localAmount) + r * localAmount
        ));
        result[idx + 1] = Math.max(0, Math.min(255, 
          data[idx + 1] * (1 - localAmount) + g * localAmount
        ));
        result[idx + 2] = Math.max(0, Math.min(255, 
          data[idx + 2] * (1 - localAmount) + b * localAmount
        ));
      }
    }
    
    return result;
  }

  /**
   * Apply uniform sharpening
   * @private
   */
  _uniformSharpen(data, width, height, strength) {
    const result = new Uint8ClampedArray(data);
    
    let kernel, amount;
    switch (strength) {
      case 'subtle':
        kernel = this.sharpenKernel;
        amount = 0.4;
        break;
      case 'moderate':
        kernel = this.sharpenKernel;
        amount = 0.7;
        break;
      case 'strong':
        kernel = this.strongSharpenKernel;
        amount = 0.9;
        break;
      case 'extreme':
        kernel = this.strongSharpenKernel;
        amount = 1.3;
        break;
      default:
        kernel = this.sharpenKernel;
        amount = 0.7;
    }
    
    for (let y = 1; y < height - 1; y++) {
      for (let x = 1; x < width - 1; x++) {
        const idx = (y * width + x) * 4;
        
        let r = 0, g = 0, b = 0;
        
        for (let ky = -1; ky <= 1; ky++) {
          for (let kx = -1; kx <= 1; kx++) {
            const px = x + kx;
            const py = y + ky;
            const pidx = (py * width + px) * 4;
            const kernelValue = kernel[ky + 1][kx + 1];
            
            r += data[pidx] * kernelValue;
            g += data[pidx + 1] * kernelValue;
            b += data[pidx + 2] * kernelValue;
          }
        }
        
        result[idx] = Math.max(0, Math.min(255, 
          data[idx] * (1 - amount) + r * amount
        ));
        result[idx + 1] = Math.max(0, Math.min(255, 
          data[idx + 1] * (1 - amount) + g * amount
        ));
        result[idx + 2] = Math.max(0, Math.min(255, 
          data[idx + 2] * (1 - amount) + b * amount
        ));
      }
    }
    
    return result;
  }

  /**
   * Preserve details in important areas
   * @private
   */
  _preserveDetails(original, processed, edgeMap, width, height) {
    const result = new Uint8ClampedArray(processed);
    
    for (let y = 0; y < height; y++) {
      for (let x = 0; x < width; x++) {
        const idx = (y * width + x) * 4;
        const edgeStrength = edgeMap[y * width + x];
        
        // In high-detail areas, blend more with original
        const blendFactor = Math.min(edgeStrength * 0.3, 0.3);
        
        result[idx] = processed[idx] * (1 - blendFactor) + original[idx] * blendFactor;
        result[idx + 1] = processed[idx + 1] * (1 - blendFactor) + original[idx + 1] * blendFactor;
        result[idx + 2] = processed[idx + 2] * (1 - blendFactor) + original[idx + 2] * blendFactor;
      }
    }
    
    return result;
  }
}

// Export for use
window.AISharpener = AISharpener;
