/**
 * Convert frequency from Hz to Mel scale
 */
export function hzToMel(freq: number): number {
  return 2595 * Math.log10(1 + freq / 700)
}

/**
 * Convert frequency from Mel scale to Hz
 */
export function melToHz(mel: number): number {
  return 700 * (Math.pow(10, mel / 2595) - 1)
}

/**
 * Detect spectral peaks in amplitude spectrum
 */
export function detectSpectralPeaks(spectrum: number[], threshold: number = 0.01): number[] {
  const peaks: number[] = []

  // Find significant components above threshold
  for (let i = 0; i < spectrum.length; i++) {
    if (spectrum[i] > threshold) {
      peaks.push(i)
    }
  }

  // Fallback: use local maxima with lower threshold if no peaks found
  if (peaks.length === 0) {
    for (let i = 1; i < spectrum.length - 1; i++) {
      if (spectrum[i] > spectrum[i - 1] &&
          spectrum[i] > spectrum[i + 1] &&
          spectrum[i] > 0.001) {
        peaks.push(i)
      }
    }
  }

  return peaks
}

/**
 * Apply perceptual amplitude weighting
 */
export function perceptualAmplitudeWeighting(freq: number, amplitude: number): number {
  // Gentle boost around 1kHz for perceptual accuracy
  const normalizedFreq = Math.log10(freq / 1000)
  const weight = Math.exp(-normalizedFreq * normalizedFreq * 0.5) * 0.5 + 0.5
  return amplitude * weight
}

/**
 * Auto-detect if image colors should be inverted
 */
export function shouldInvertImage(imageData: ImageData): boolean {
  const { width, height, data } = imageData
  let totalBrightness = 0

  for (let i = 0; i < data.length; i += 4) {
    const gray = 0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]
    totalBrightness += gray / 255
  }

  const meanBrightness = totalBrightness / (width * height)
  return meanBrightness > 0.5 // Bright background = diagram
}

/**
 * Generate windowing function
 */
export function generateWindow(length: number, windowType: string): Float32Array {
  const window = new Float32Array(length)

  switch (windowType) {
    case 'hann':
      for (let i = 0; i < length; i++) {
        window[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / (length - 1)))
      }
      break

    case 'hamming':
      for (let i = 0; i < length; i++) {
        window[i] = 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1))
      }
      break

    case 'blackman':
      for (let i = 0; i < length; i++) {
        const factor = 2 * Math.PI * i / (length - 1)
        window[i] = 0.42 - 0.5 * Math.cos(factor) + 0.08 * Math.cos(2 * factor)
      }
      break

    case 'rectangular':
    default:
      window.fill(1.0)
      break
  }

  return window
}

/**
 * Apply windowing function to audio chunk
 */
export function applyWindow(audioChunk: Float32Array, windowType: string): Float32Array {
  const window = generateWindow(audioChunk.length, windowType)
  const windowed = new Float32Array(audioChunk.length)

  for (let i = 0; i < audioChunk.length; i++) {
    windowed[i] = audioChunk[i] * window[i]
  }

  return windowed
}

/**
 * Extract grayscale spectrum from image column
 */
export function extractSpectrum(
  imageData: ImageData,
  col: number,
  height: number,
  frequencyResolution: number,
  invert: boolean
): number[] {
  const { width, data } = imageData
  const spectrum: number[] = []

  for (let row = 0; row < height; row++) {
    const sourceRow = row * frequencyResolution
    const idx = (sourceRow * width + col) * 4
    const r = data[idx]
    const g = data[idx + 1]
    const b = data[idx + 2]

    let amplitude = (0.299 * r + 0.587 * g + 0.114 * b) / 255
    if (invert) amplitude = 1 - amplitude
    spectrum.push(amplitude)
  }

  return spectrum
}