import type { SynthesisParams, SpectralPeak, SynthesisResult } from './types'
import {
  hzToMel,
  melToHz,
  detectSpectralPeaks,
  perceptualAmplitudeWeighting,
  shouldInvertImage,
  extractSpectrum,
  applyWindow
} from './utils'

export class ImageToAudioSynthesizer {
  private params: SynthesisParams

  constructor(params: Partial<SynthesisParams> = {}) {
    this.params = {
      duration: 5,
      minFreq: 20,
      maxFreq: 20000,
      sampleRate: 44100,
      frequencyResolution: 1,
      timeResolution: 1,
      amplitudeThreshold: 0.01,
      maxPartials: 100,
      windowType: 'hann',
      ...params
    }
  }

  /**
   * Synthesize audio from image data
   */
  synthesize(imageData: ImageData): SynthesisResult {
    const { width, height, data } = imageData
    const {
      duration,
      minFreq,
      maxFreq,
      sampleRate,
      frequencyResolution,
      timeResolution,
      amplitudeThreshold,
      maxPartials,
      windowType
    } = this.params

    // Detect image type
    const invert = shouldInvertImage(imageData)

    // Calculate synthesis parameters
    const totalSamples = Math.floor(duration * sampleRate)
    const effectiveWidth = Math.floor(width / timeResolution)
    const effectiveHeight = Math.floor(height / frequencyResolution)
    const samplesPerColumn = totalSamples / effectiveWidth
    const audio = new Float32Array(totalSamples)

    // Pre-calculate mel-scale frequency mapping
    const minMel = hzToMel(minFreq)
    const maxMel = hzToMel(maxFreq)

    // Storage for temporal smoothing
    const previousAmplitudes = new Float32Array(effectiveHeight)
    const smoothingFactor = 0.3

    // Process each time slice
    for (let col = 0; col < effectiveWidth; col++) {
      const sourceCol = col * timeResolution
      const startSample = Math.floor(col * samplesPerColumn)
      const endSample = Math.floor((col + 1) * samplesPerColumn)

      // Extract spectrum for this time slice
      const spectrum = extractSpectrum(imageData, sourceCol, effectiveHeight, frequencyResolution, invert)

      // Detect spectral peaks
      const peaks = detectSpectralPeaks(spectrum, Math.min(amplitudeThreshold, 0.01))

      // Generate partials from peaks
      const partials: SpectralPeak[] = []

      for (const peakRow of peaks) {
        // Mel-scale frequency mapping (high freq at top)
        const melValue = maxMel - (peakRow / (effectiveHeight - 1)) * (maxMel - minMel)
        const frequency = melToHz(melValue)

        let amplitude = spectrum[peakRow]

        // Apply temporal smoothing
        if (col > 0) {
          amplitude = smoothingFactor * previousAmplitudes[peakRow] + (1 - smoothingFactor) * amplitude
        }
        previousAmplitudes[peakRow] = amplitude

        // Apply perceptual weighting
        amplitude = perceptualAmplitudeWeighting(frequency, amplitude)

        // Use zero phase for simplicity
        const phase = 0

        if (amplitude > Math.min(amplitudeThreshold, 0.005)) {
          partials.push({ frequency, amplitude, phase })
        }
      }

      // Sort by amplitude and limit partials
      partials.sort((a, b) => b.amplitude - a.amplitude)
      const limitedPartials = partials.slice(0, maxPartials)

      // Generate audio for this time slice
      const chunkLength = endSample - startSample
      const audioChunk = new Float32Array(chunkLength)

      for (const { frequency, amplitude, phase } of limitedPartials) {
        for (let i = 0; i < chunkLength; i++) {
          const t = (startSample + i) / sampleRate
          audioChunk[i] += amplitude * Math.sin(2 * Math.PI * frequency * t + phase)
        }
      }

      // Apply windowing to reduce artifacts
      const windowedChunk = applyWindow(audioChunk, windowType)

      // Add windowed chunk to final audio
      for (let i = 0; i < chunkLength && startSample + i < totalSamples; i++) {
        audio[startSample + i] += windowedChunk[i]
      }
    }

    // Normalize to prevent clipping
    let maxAmplitude = 0
    for (let i = 0; i < audio.length; i++) {
      const absValue = Math.abs(audio[i])
      if (absValue > maxAmplitude) {
        maxAmplitude = absValue
      }
    }

    if (maxAmplitude > 1) {
      for (let i = 0; i < audio.length; i++) {
        audio[i] /= maxAmplitude
      }
    }

    return {
      audio,
      sampleRate,
      duration
    }
  }

  /**
   * Update synthesis parameters
   */
  updateParams(newParams: Partial<SynthesisParams>): void {
    this.params = { ...this.params, ...newParams }
  }

  /**
   * Get current parameters
   */
  getParams(): SynthesisParams {
    return { ...this.params }
  }
}

/**
 * Convenience function for quick synthesis
 */
export function synthesizeFromImage(
  imageData: ImageData,
  params: Partial<SynthesisParams> = {}
): Float32Array {
  const synthesizer = new ImageToAudioSynthesizer(params)
  const result = synthesizer.synthesize(imageData)
  return result.audio
}