import { spawn } from "child_process";
import { randomUUID } from "crypto";
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import type { VoiceParameters } from "@shared/schema";

const TEMP_DIR = os.tmpdir();

function generateTempPath(ext: string): string {
  return path.join(TEMP_DIR, `voiceforge_${randomUUID()}${ext}`);
}

async function runFFmpeg(args: string[]): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const ffmpeg = spawn("ffmpeg", args);
    const chunks: Buffer[] = [];
    const errorChunks: Buffer[] = [];

    ffmpeg.stdout.on("data", (data) => chunks.push(data));
    ffmpeg.stderr.on("data", (data) => errorChunks.push(data));

    ffmpeg.on("close", (code) => {
      if (code === 0) {
        resolve(Buffer.concat(chunks));
      } else {
        reject(new Error(`FFmpeg exited with code ${code}: ${Buffer.concat(errorChunks).toString()}`));
      }
    });

    ffmpeg.on("error", reject);
  });
}

async function runFFprobe(args: string[]): Promise<string> {
  return new Promise((resolve, reject) => {
    const ffprobe = spawn("ffprobe", args);
    const chunks: Buffer[] = [];
    const errorChunks: Buffer[] = [];

    ffprobe.stdout.on("data", (data) => chunks.push(data));
    ffprobe.stderr.on("data", (data) => errorChunks.push(data));

    ffprobe.on("close", (code) => {
      if (code === 0) {
        resolve(Buffer.concat(chunks).toString());
      } else {
        resolve(Buffer.concat(errorChunks).toString());
      }
    });

    ffprobe.on("error", reject);
  });
}

export async function getAudioInfo(buffer: Buffer): Promise<{
  duration: number;
  sampleRate: number;
  channels: number;
}> {
  const tempPath = generateTempPath(".mp3");
  fs.writeFileSync(tempPath, buffer);

  try {
    const output = await runFFprobe([
      "-v", "quiet",
      "-print_format", "json",
      "-show_format",
      "-show_streams",
      tempPath
    ]);

    const data = JSON.parse(output);
    const audioStream = data.streams?.find((s: any) => s.codec_type === "audio") || {};
    const format = data.format || {};

    return {
      duration: parseFloat(format.duration || audioStream.duration || "0"),
      sampleRate: parseInt(audioStream.sample_rate || "44100"),
      channels: parseInt(audioStream.channels || "2"),
    };
  } finally {
    fs.unlinkSync(tempPath);
  }
}

export async function generateWaveform(buffer: Buffer, numSamples: number = 100): Promise<number[]> {
  const tempPath = generateTempPath(".mp3");
  const pcmPath = generateTempPath(".raw");
  fs.writeFileSync(tempPath, buffer);

  try {
    await new Promise<void>((resolve, reject) => {
      const ffmpeg = spawn("ffmpeg", [
        "-i", tempPath,
        "-f", "s16le",
        "-ac", "1",
        "-ar", "8000",
        "-y",
        pcmPath
      ]);

      ffmpeg.on("close", (code) => {
        if (code === 0) resolve();
        else reject(new Error(`FFmpeg waveform extraction failed with code ${code}`));
      });

      ffmpeg.on("error", reject);
    });

    const pcmData = fs.readFileSync(pcmPath);
    const samples = new Int16Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 2);

    const waveform: number[] = [];
    const chunkSize = Math.max(1, Math.floor(samples.length / numSamples));

    for (let i = 0; i < numSamples; i++) {
      const start = i * chunkSize;
      const end = Math.min(start + chunkSize, samples.length);
      let maxAbs = 0;

      for (let j = start; j < end; j++) {
        const abs = Math.abs(samples[j]);
        if (abs > maxAbs) maxAbs = abs;
      }

      waveform.push(maxAbs / 32768);
    }

    return waveform;
  } finally {
    if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
    if (fs.existsSync(pcmPath)) fs.unlinkSync(pcmPath);
  }
}

export async function analyzeVoice(buffer: Buffer): Promise<VoiceParameters> {
  const tempPath = generateTempPath(".mp3");
  const pcmPath = generateTempPath(".raw");
  fs.writeFileSync(tempPath, buffer);

  try {
    await new Promise<void>((resolve, reject) => {
      const ffmpeg = spawn("ffmpeg", [
        "-i", tempPath,
        "-f", "f32le",
        "-ac", "1",
        "-ar", "16000",
        "-y",
        pcmPath
      ]);

      ffmpeg.on("close", (code) => {
        if (code === 0) resolve();
        else reject(new Error(`FFmpeg analysis failed with code ${code}`));
      });

      ffmpeg.on("error", reject);
    });

    const info = await getAudioInfo(buffer);
    const pcmData = fs.readFileSync(pcmPath);
    const samples = new Float32Array(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength / 4);

    const pitchData = detectPitch(samples, 16000);
    const spectralData = computeSpectralFeatures(samples, 16000);
    const formants = estimateFormants(samples, 16000);
    const mfcc = computeMFCC(samples, 16000);
    const tempo = estimateTempo(samples, 16000);

    return {
      pitchMean: pitchData.mean,
      pitchMin: pitchData.min,
      pitchMax: pitchData.max,
      pitchStd: pitchData.std,
      formantF1: formants.f1,
      formantF2: formants.f2,
      formantF3: formants.f3,
      formantF4: formants.f4,
      spectralCentroid: spectralData.centroid,
      spectralBandwidth: spectralData.bandwidth,
      spectralRolloff: spectralData.rolloff,
      zeroCrossingRate: computeZeroCrossingRate(samples),
      rmsEnergy: computeRMSEnergy(samples),
      harmonicRatio: computeHarmonicRatio(samples, 16000),
      mfccCoefficients: mfcc,
      tempo: tempo,
      duration: info.duration,
    };
  } finally {
    if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);
    if (fs.existsSync(pcmPath)) fs.unlinkSync(pcmPath);
  }
}

function detectPitch(samples: Float32Array, sampleRate: number): {
  mean: number;
  min: number;
  max: number;
  std: number;
} {
  const frameSize = Math.floor(sampleRate * 0.03);
  const hopSize = Math.floor(frameSize / 2);
  const pitches: number[] = [];

  for (let i = 0; i + frameSize < samples.length; i += hopSize) {
    const frame = samples.slice(i, i + frameSize);
    const pitch = yinPitchDetection(frame, sampleRate);
    if (pitch > 50 && pitch < 500) {
      pitches.push(pitch);
    }
  }

  if (pitches.length === 0) {
    return { mean: 150, min: 100, max: 200, std: 20 };
  }

  const mean = pitches.reduce((a, b) => a + b, 0) / pitches.length;
  const min = Math.min(...pitches);
  const max = Math.max(...pitches);
  const variance = pitches.reduce((sum, p) => sum + Math.pow(p - mean, 2), 0) / pitches.length;
  const std = Math.sqrt(variance);

  return { mean, min, max, std };
}

function yinPitchDetection(frame: Float32Array, sampleRate: number): number {
  const bufferSize = frame.length;
  const yinBufferSize = Math.floor(bufferSize / 2);
  const yinBuffer = new Float32Array(yinBufferSize);

  let runningSum = 0;
  yinBuffer[0] = 1;

  for (let tau = 1; tau < yinBufferSize; tau++) {
    yinBuffer[tau] = 0;
    for (let i = 0; i < yinBufferSize; i++) {
      const delta = frame[i] - frame[i + tau];
      yinBuffer[tau] += delta * delta;
    }
    runningSum += yinBuffer[tau];
    yinBuffer[tau] *= tau / runningSum;
  }

  const threshold = 0.1;
  let tau = 2;
  while (tau < yinBufferSize) {
    if (yinBuffer[tau] < threshold) {
      while (tau + 1 < yinBufferSize && yinBuffer[tau + 1] < yinBuffer[tau]) {
        tau++;
      }
      break;
    }
    tau++;
  }

  if (tau === yinBufferSize) {
    return 0;
  }

  const betterTau = parabolicInterpolation(yinBuffer, tau);
  return sampleRate / betterTau;
}

function parabolicInterpolation(buffer: Float32Array, tau: number): number {
  const x0 = tau < 1 ? tau : tau - 1;
  const x2 = tau + 1 < buffer.length ? tau + 1 : tau;

  if (x0 === tau) {
    return buffer[tau] <= buffer[x2] ? tau : x2;
  }
  if (x2 === tau) {
    return buffer[tau] <= buffer[x0] ? tau : x0;
  }

  const s0 = buffer[x0];
  const s1 = buffer[tau];
  const s2 = buffer[x2];

  return tau + (s2 - s0) / (2 * (2 * s1 - s2 - s0));
}

function computeSpectralFeatures(samples: Float32Array, sampleRate: number): {
  centroid: number;
  bandwidth: number;
  rolloff: number;
} {
  const fftSize = 2048;
  const numFrames = Math.floor(samples.length / fftSize);
  
  let totalCentroid = 0;
  let totalBandwidth = 0;
  let totalRolloff = 0;

  for (let frame = 0; frame < numFrames; frame++) {
    const start = frame * fftSize;
    const frameData = samples.slice(start, start + fftSize);
    
    const magnitudes = computeFFTMagnitudes(frameData);
    const freqBinWidth = sampleRate / fftSize;
    
    let weightedSum = 0;
    let totalMagnitude = 0;
    
    for (let i = 0; i < magnitudes.length; i++) {
      const freq = i * freqBinWidth;
      weightedSum += freq * magnitudes[i];
      totalMagnitude += magnitudes[i];
    }
    
    const centroid = totalMagnitude > 0 ? weightedSum / totalMagnitude : 0;
    totalCentroid += centroid;
    
    let bandwidthSum = 0;
    for (let i = 0; i < magnitudes.length; i++) {
      const freq = i * freqBinWidth;
      bandwidthSum += magnitudes[i] * Math.pow(freq - centroid, 2);
    }
    totalBandwidth += totalMagnitude > 0 ? Math.sqrt(bandwidthSum / totalMagnitude) : 0;
    
    let cumulativeMagnitude = 0;
    const rolloffThreshold = 0.85 * totalMagnitude;
    let rolloffFreq = 0;
    for (let i = 0; i < magnitudes.length; i++) {
      cumulativeMagnitude += magnitudes[i];
      if (cumulativeMagnitude >= rolloffThreshold) {
        rolloffFreq = i * freqBinWidth;
        break;
      }
    }
    totalRolloff += rolloffFreq;
  }

  return {
    centroid: numFrames > 0 ? totalCentroid / numFrames : 0,
    bandwidth: numFrames > 0 ? totalBandwidth / numFrames : 0,
    rolloff: numFrames > 0 ? totalRolloff / numFrames : 0,
  };
}

function computeFFTMagnitudes(frame: Float32Array): Float32Array {
  const n = frame.length;
  const magnitudes = new Float32Array(n / 2);
  
  for (let k = 0; k < n / 2; k++) {
    let real = 0;
    let imag = 0;
    
    for (let t = 0; t < n; t++) {
      const angle = (2 * Math.PI * k * t) / n;
      real += frame[t] * Math.cos(angle);
      imag -= frame[t] * Math.sin(angle);
    }
    
    magnitudes[k] = Math.sqrt(real * real + imag * imag) / n;
  }
  
  return magnitudes;
}

function estimateFormants(samples: Float32Array, sampleRate: number): {
  f1: number;
  f2: number;
  f3: number;
  f4: number;
} {
  const lpcOrder = 10;
  const frameSize = Math.floor(sampleRate * 0.025);
  const numFrames = Math.floor(samples.length / frameSize);
  
  const formants = { f1: 0, f2: 0, f3: 0, f4: 0 };
  let validFrames = 0;

  for (let frame = 0; frame < Math.min(numFrames, 50); frame++) {
    const start = frame * frameSize;
    const frameData = samples.slice(start, start + frameSize);
    
    const lpcCoeffs = computeLPC(frameData, lpcOrder);
    const roots = findLPCRoots(lpcCoeffs, sampleRate);
    
    const frameFormants = roots.filter(f => f > 90 && f < 5000).sort((a, b) => a - b);
    
    if (frameFormants.length >= 4) {
      formants.f1 += frameFormants[0];
      formants.f2 += frameFormants[1];
      formants.f3 += frameFormants[2];
      formants.f4 += frameFormants[3];
      validFrames++;
    }
  }

  if (validFrames > 0) {
    formants.f1 /= validFrames;
    formants.f2 /= validFrames;
    formants.f3 /= validFrames;
    formants.f4 /= validFrames;
  } else {
    formants.f1 = 500;
    formants.f2 = 1500;
    formants.f3 = 2500;
    formants.f4 = 3500;
  }

  return formants;
}

function computeLPC(frame: Float32Array, order: number): Float32Array {
  const n = frame.length;
  const r = new Float32Array(order + 1);
  
  for (let i = 0; i <= order; i++) {
    let sum = 0;
    for (let j = 0; j < n - i; j++) {
      sum += frame[j] * frame[j + i];
    }
    r[i] = sum;
  }

  const a = new Float32Array(order + 1);
  const e = new Float32Array(order + 1);
  
  a[0] = 1;
  e[0] = r[0];

  for (let i = 1; i <= order; i++) {
    let lambda = 0;
    for (let j = 0; j < i; j++) {
      lambda += a[j] * r[i - j];
    }
    
    if (e[i - 1] === 0) break;
    
    const k = -lambda / e[i - 1];
    
    const aTemp = new Float32Array(order + 1);
    for (let j = 0; j <= i; j++) {
      aTemp[j] = a[j] + k * a[i - j];
    }
    
    for (let j = 0; j <= i; j++) {
      a[j] = aTemp[j];
    }
    
    e[i] = (1 - k * k) * e[i - 1];
  }

  return a;
}

function findLPCRoots(lpcCoeffs: Float32Array, sampleRate: number): number[] {
  const formants: number[] = [];
  const numAngles = 128;
  
  for (let i = 0; i < numAngles; i++) {
    const angle = (Math.PI * i) / numAngles;
    const freq = (angle * sampleRate) / (2 * Math.PI);
    
    let response = 0;
    for (let k = 0; k < lpcCoeffs.length; k++) {
      response += lpcCoeffs[k] * Math.cos(k * angle);
    }
    
    if (Math.abs(response) < 0.1 && freq > 90) {
      formants.push(freq);
    }
  }

  const uniqueFormants: number[] = [];
  for (const f of formants) {
    if (!uniqueFormants.some(uf => Math.abs(uf - f) < 100)) {
      uniqueFormants.push(f);
    }
  }

  return uniqueFormants;
}

function computeMFCC(samples: Float32Array, sampleRate: number): number[] {
  const numCoeffs = 13;
  const fftSize = 512;
  const numFilters = 26;
  
  const frame = samples.slice(0, Math.min(fftSize, samples.length));
  const magnitudes = computeFFTMagnitudes(frame);
  
  const melFilters = createMelFilterbank(numFilters, fftSize, sampleRate);
  const filterEnergies = new Float32Array(numFilters);
  
  for (let i = 0; i < numFilters; i++) {
    for (let j = 0; j < magnitudes.length; j++) {
      filterEnergies[i] += magnitudes[j] * melFilters[i][j];
    }
    filterEnergies[i] = Math.log(Math.max(filterEnergies[i], 1e-10));
  }

  const mfcc: number[] = [];
  for (let i = 0; i < numCoeffs; i++) {
    let sum = 0;
    for (let j = 0; j < numFilters; j++) {
      sum += filterEnergies[j] * Math.cos((Math.PI * i * (j + 0.5)) / numFilters);
    }
    mfcc.push(sum);
  }

  return mfcc;
}

function createMelFilterbank(numFilters: number, fftSize: number, sampleRate: number): Float32Array[] {
  const minMel = hzToMel(0);
  const maxMel = hzToMel(sampleRate / 2);
  const melPoints = new Float32Array(numFilters + 2);
  
  for (let i = 0; i < numFilters + 2; i++) {
    melPoints[i] = minMel + (i * (maxMel - minMel)) / (numFilters + 1);
  }
  
  const hzPoints = melPoints.map(mel => melToHz(mel));
  const binPoints = hzPoints.map(hz => Math.floor((fftSize + 1) * hz / sampleRate));
  
  const filters: Float32Array[] = [];
  for (let i = 0; i < numFilters; i++) {
    const filter = new Float32Array(fftSize / 2);
    
    for (let j = binPoints[i]; j < binPoints[i + 1]; j++) {
      filter[j] = (j - binPoints[i]) / (binPoints[i + 1] - binPoints[i]);
    }
    for (let j = binPoints[i + 1]; j < binPoints[i + 2]; j++) {
      filter[j] = (binPoints[i + 2] - j) / (binPoints[i + 2] - binPoints[i + 1]);
    }
    
    filters.push(filter);
  }
  
  return filters;
}

function hzToMel(hz: number): number {
  return 2595 * Math.log10(1 + hz / 700);
}

function melToHz(mel: number): number {
  return 700 * (Math.pow(10, mel / 2595) - 1);
}

function computeZeroCrossingRate(samples: Float32Array): number {
  let crossings = 0;
  for (let i = 1; i < samples.length; i++) {
    if ((samples[i] >= 0 && samples[i - 1] < 0) || (samples[i] < 0 && samples[i - 1] >= 0)) {
      crossings++;
    }
  }
  return crossings / samples.length;
}

function computeRMSEnergy(samples: Float32Array): number {
  let sum = 0;
  for (let i = 0; i < samples.length; i++) {
    sum += samples[i] * samples[i];
  }
  return Math.sqrt(sum / samples.length);
}

function computeHarmonicRatio(samples: Float32Array, sampleRate: number): number {
  const fftSize = 2048;
  const numFrames = Math.floor(samples.length / fftSize);
  
  let totalHarmonicRatio = 0;

  for (let frame = 0; frame < Math.min(numFrames, 20); frame++) {
    const start = frame * fftSize;
    const frameData = samples.slice(start, start + fftSize);
    const magnitudes = computeFFTMagnitudes(frameData);
    
    let peakMagnitude = 0;
    let peakIndex = 0;
    for (let i = 5; i < magnitudes.length / 4; i++) {
      if (magnitudes[i] > peakMagnitude) {
        peakMagnitude = magnitudes[i];
        peakIndex = i;
      }
    }
    
    let harmonicEnergy = 0;
    let totalEnergy = 0;
    
    for (let i = 0; i < magnitudes.length; i++) {
      const energy = magnitudes[i] * magnitudes[i];
      totalEnergy += energy;
      
      for (let h = 1; h <= 5; h++) {
        const harmonicBin = peakIndex * h;
        if (Math.abs(i - harmonicBin) <= 2) {
          harmonicEnergy += energy;
          break;
        }
      }
    }
    
    totalHarmonicRatio += totalEnergy > 0 ? (harmonicEnergy / totalEnergy) * 100 : 0;
  }

  return numFrames > 0 ? totalHarmonicRatio / Math.min(numFrames, 20) : 50;
}

function estimateTempo(samples: Float32Array, sampleRate: number): number {
  const frameSize = Math.floor(sampleRate * 0.01);
  const energyEnvelope: number[] = [];
  
  for (let i = 0; i + frameSize < samples.length; i += frameSize) {
    const frame = samples.slice(i, i + frameSize);
    let energy = 0;
    for (let j = 0; j < frame.length; j++) {
      energy += frame[j] * frame[j];
    }
    energyEnvelope.push(Math.sqrt(energy / frame.length));
  }

  const diffEnvelope = energyEnvelope.slice(1).map((e, i) => Math.max(0, e - energyEnvelope[i]));
  
  const minLag = Math.floor(60 / 200 * (sampleRate / frameSize));
  const maxLag = Math.floor(60 / 60 * (sampleRate / frameSize));
  
  let maxCorr = 0;
  let bestLag = minLag;
  
  for (let lag = minLag; lag <= maxLag && lag < diffEnvelope.length / 2; lag++) {
    let corr = 0;
    for (let i = 0; i < diffEnvelope.length - lag; i++) {
      corr += diffEnvelope[i] * diffEnvelope[i + lag];
    }
    
    if (corr > maxCorr) {
      maxCorr = corr;
      bestLag = lag;
    }
  }
  
  const tempo = 60 / (bestLag * frameSize / sampleRate);
  return Math.max(60, Math.min(200, tempo));
}

interface FormantShiftParams {
  f1Shift: number;
  f2Shift: number;
  f3Shift: number;
  f4Shift: number;
  spectralTilt: number;
  breathiness: number;
}

function computeFormantShiftParams(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensityFactor: number
): FormantShiftParams {
  const interpolate = (source: number, target: number): number => {
    return target + (source - target) * intensityFactor;
  };

  const targetF1 = interpolate(sourceParams.formantF1, targetParams.formantF1);
  const targetF2 = interpolate(sourceParams.formantF2, targetParams.formantF2);
  const targetF3 = interpolate(sourceParams.formantF3, targetParams.formantF3);
  const targetF4 = interpolate(sourceParams.formantF4, targetParams.formantF4);

  const f1Ratio = targetF1 / targetParams.formantF1;
  const f2Ratio = targetF2 / targetParams.formantF2;
  const f3Ratio = targetF3 / targetParams.formantF3;
  const f4Ratio = targetF4 / targetParams.formantF4;

  const targetCentroid = interpolate(sourceParams.spectralCentroid, targetParams.spectralCentroid);
  const spectralTilt = (targetCentroid - targetParams.spectralCentroid) / 1000;

  const harmonicDiff = (sourceParams.harmonicRatio - targetParams.harmonicRatio) * intensityFactor;
  const breathiness = Math.max(0, Math.min(1, harmonicDiff / 50));

  return {
    f1Shift: Math.log2(f1Ratio) * 12,
    f2Shift: Math.log2(f2Ratio) * 12,
    f3Shift: Math.log2(f3Ratio) * 12,
    f4Shift: Math.log2(f4Ratio) * 12,
    spectralTilt: Math.max(-3, Math.min(3, spectralTilt)),
    breathiness,
  };
}

function buildAdvancedFormantFilters(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  formantShift: FormantShiftParams,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const interpolate = (source: number, target: number): number => {
    return target + (source - target) * intensityFactor;
  };

  const targetF1 = interpolate(sourceParams.formantF1, targetParams.formantF1);
  const targetF2 = interpolate(sourceParams.formantF2, targetParams.formantF2);
  const targetF3 = interpolate(sourceParams.formantF3, targetParams.formantF3);
  const targetF4 = interpolate(sourceParams.formantF4, targetParams.formantF4);

  const f1Diff = targetF1 - targetParams.formantF1;
  const f2Diff = targetF2 - targetParams.formantF2;
  const f3Diff = targetF3 - targetParams.formantF3;
  const f4Diff = targetF4 - targetParams.formantF4;

  const f1BandWidth = 100 + Math.abs(f1Diff) * 0.3;
  const f2BandWidth = 150 + Math.abs(f2Diff) * 0.3;
  const f3BandWidth = 200 + Math.abs(f3Diff) * 0.3;
  const f4BandWidth = 250 + Math.abs(f4Diff) * 0.3;

  const f1Gain = Math.min(6, Math.abs(f1Diff) / 50) * intensityFactor;
  const f2Gain = Math.min(5, Math.abs(f2Diff) / 100) * intensityFactor;
  const f3Gain = Math.min(4, Math.abs(f3Diff) / 150) * intensityFactor;
  const f4Gain = Math.min(3, Math.abs(f4Diff) / 200) * intensityFactor;

  if (Math.abs(f1Diff) > 10) {
    filters.push(`equalizer=f=${targetParams.formantF1}:t=q:w=${f1BandWidth}:g=${(-f1Gain).toFixed(2)}`);
    filters.push(`equalizer=f=${targetF1}:t=q:w=${f1BandWidth}:g=${f1Gain.toFixed(2)}`);
  }

  if (Math.abs(f2Diff) > 20) {
    filters.push(`equalizer=f=${targetParams.formantF2}:t=q:w=${f2BandWidth}:g=${(-f2Gain).toFixed(2)}`);
    filters.push(`equalizer=f=${targetF2}:t=q:w=${f2BandWidth}:g=${f2Gain.toFixed(2)}`);
  }

  if (Math.abs(f3Diff) > 30) {
    filters.push(`equalizer=f=${targetParams.formantF3}:t=q:w=${f3BandWidth}:g=${(-f3Gain).toFixed(2)}`);
    filters.push(`equalizer=f=${targetF3}:t=q:w=${f3BandWidth}:g=${f3Gain.toFixed(2)}`);
  }

  if (Math.abs(f4Diff) > 40) {
    filters.push(`equalizer=f=${targetParams.formantF4}:t=q:w=${f4BandWidth}:g=${(-f4Gain).toFixed(2)}`);
    filters.push(`equalizer=f=${targetF4}:t=q:w=${f4BandWidth}:g=${f4Gain.toFixed(2)}`);
  }

  if (Math.abs(formantShift.spectralTilt) > 0.3) {
    const tiltGain = formantShift.spectralTilt * 2;
    filters.push(`treble=g=${tiltGain.toFixed(2)}:f=3000:t=s`);
    filters.push(`bass=g=${(-tiltGain * 0.5).toFixed(2)}:f=200:t=s`);
  }

  return filters;
}

function buildSpectralEnvelopeFilters(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const interpolate = (source: number, target: number): number => {
    return target + (source - target) * intensityFactor;
  };

  const targetBandwidth = interpolate(sourceParams.spectralBandwidth, targetParams.spectralBandwidth);
  const bandwidthRatio = targetBandwidth / targetParams.spectralBandwidth;

  if (Math.abs(bandwidthRatio - 1) > 0.1) {
    const spreadFreqs = [500, 1000, 2000, 4000];
    const spreadGain = (bandwidthRatio - 1) * 3 * intensityFactor;

    for (const freq of spreadFreqs) {
      const adjustedGain = spreadGain * (freq / 2000);
      if (Math.abs(adjustedGain) > 0.3) {
        filters.push(`equalizer=f=${freq}:t=h:w=${freq * 0.5}:g=${adjustedGain.toFixed(2)}`);
      }
    }
  }

  const targetRolloff = interpolate(sourceParams.spectralRolloff, targetParams.spectralRolloff);
  const rolloffDiff = targetRolloff - targetParams.spectralRolloff;

  if (Math.abs(rolloffDiff) > 200) {
    const rolloffGain = Math.sign(rolloffDiff) * Math.min(3, Math.abs(rolloffDiff) / 500) * intensityFactor;
    filters.push(`highshelf=g=${rolloffGain.toFixed(2)}:f=${Math.min(8000, targetRolloff)}`);
  }

  return filters;
}

function buildAntiAliasingFilters(sampleRate: number = 44100): string[] {
  return [
    `highpass=f=60:p=2`,
    `lowpass=f=${Math.min(16000, sampleRate / 2 - 500)}:p=2`,
  ];
}

function buildDynamicsProcessing(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const targetEnergy = targetParams.rmsEnergy + (sourceParams.rmsEnergy - targetParams.rmsEnergy) * intensityFactor;
  const energyRatio = targetEnergy / Math.max(0.001, targetParams.rmsEnergy);
  const volumeGain = Math.max(0.3, Math.min(3, energyRatio));

  const compressionRatio = 1 + (intensityFactor * 0.5);
  const attackTime = 0.05 + (1 - intensityFactor) * 0.05;
  const releaseTime = 0.2 + (1 - intensityFactor) * 0.1;

  filters.push(`compand=attacks=${attackTime.toFixed(3)}:decays=${releaseTime.toFixed(3)}:points=-80/-80|-50/-50|-30/-28|-10/-8|0/-5:gain=${(volumeGain * 2).toFixed(1)}`);

  filters.push(`loudnorm=I=-16:TP=-1.5:LRA=11`);

  return filters;
}

function buildHarmonicEnhancement(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  formantShift: FormantShiftParams,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const harmonicDiff = sourceParams.harmonicRatio - targetParams.harmonicRatio;

  if (harmonicDiff > 5 && intensityFactor > 0.3) {
    const exciterAmount = Math.min(0.4, harmonicDiff / 50 * intensityFactor);
    filters.push(`asubboost=dry=1:wet=${exciterAmount.toFixed(2)}:decay=0.3`);
  }

  if (formantShift.breathiness > 0.2) {
    const noiseLevel = formantShift.breathiness * 0.02;
    filters.push(`anoisesrc=d=0.001:c=white:r=44100:a=${noiseLevel.toFixed(4)}`);
  }

  return filters;
}

function buildVoiceQualityEnhancement(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const smoothingAmount = 0.15 + (intensityFactor * 0.1);
  filters.push(`anlmdn=s=${smoothingAmount.toFixed(2)}:p=0.002:r=0.002:m=15`);

  const deringingBands = [2000, 4000, 6000, 8000];
  for (const freq of deringingBands) {
    const q = 8 + (10000 - freq) / 2000;
    filters.push(`anequalizer=c0 f=${freq} w=${q} g=-1 t=1`);
  }

  const voicePresenceGain = 1.5 * intensityFactor;
  if (voicePresenceGain > 0.3) {
    filters.push(`equalizer=f=3000:t=h:w=1500:g=${voicePresenceGain.toFixed(2)}`);
    filters.push(`equalizer=f=5000:t=h:w=2000:g=${(voicePresenceGain * 0.7).toFixed(2)}`);
  }

  const pitchDiff = Math.abs(sourceParams.pitchMean - targetParams.pitchMean);
  if (pitchDiff > 30 && intensityFactor > 0.5) {
    filters.push(`afftdn=nf=-25:nt=w:bn=1`);
  }

  return filters;
}

function buildNaturalnessEnhancement(
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensityFactor: number
): string[] {
  const filters: string[] = [];

  const targetZCR = targetParams.zeroCrossingRate + 
    (sourceParams.zeroCrossingRate - targetParams.zeroCrossingRate) * intensityFactor;
  const zcrDiff = Math.abs(targetZCR - targetParams.zeroCrossingRate);
  
  if (zcrDiff > 0.02) {
    const roughnessControl = zcrDiff * 50 * intensityFactor;
    if (roughnessControl > 1) {
      filters.push(`highpass=f=80:p=2`);
      filters.push(`lowpass=f=12000:p=2`);
    }
  }

  const resonanceFreqs = [800, 1200, 2400, 3600];
  for (const freq of resonanceFreqs) {
    filters.push(`anequalizer=c0 f=${freq} w=3 g=-0.8 t=1`);
  }

  return filters;
}

export async function transformVoice(
  sourceBuffer: Buffer,
  targetBuffer: Buffer,
  sourceParams: VoiceParameters,
  targetParams: VoiceParameters,
  intensity: number = 100,
  onProgress?: (progress: number, stage: string) => void
): Promise<{ transformedBuffer: Buffer; resultParams: VoiceParameters; matchScore: number }> {
  const sourcePath = generateTempPath(".mp3");
  const targetPath = generateTempPath(".mp3");
  const intermediatePath = generateTempPath(".wav");
  const outputPath = generateTempPath(".mp3");
  
  fs.writeFileSync(sourcePath, sourceBuffer);
  fs.writeFileSync(targetPath, targetBuffer);

  try {
    onProgress?.(10, "extracting_features");

    const intensityFactor = intensity / 100;
    
    const interpolate = (source: number, target: number): number => {
      return target + (source - target) * intensityFactor;
    };

    const formantShiftParams = computeFormantShiftParams(sourceParams, targetParams, intensityFactor);

    const targetPitch = interpolate(sourceParams.pitchMean, targetParams.pitchMean);

    const pitchRatio = targetPitch / targetParams.pitchMean;

    onProgress?.(25, "transforming");

    const pitchShiftSemitones = 12 * Math.log2(pitchRatio);
    const clampedPitchShift = Math.max(-6, Math.min(6, pitchShiftSemitones));
    
    const stage1Filters: string[] = [];
    
    if (Math.abs(clampedPitchShift) > 0.1) {
      const pitchMultiplier = Math.pow(2, clampedPitchShift / 12);
      const newSampleRate = Math.round(44100 * pitchMultiplier);
      const tempoCompensation = 1 / pitchMultiplier;
      const clampedTempo = Math.max(0.5, Math.min(2.0, tempoCompensation));
      
      stage1Filters.push(`asetrate=${newSampleRate}`);
      stage1Filters.push(`aresample=44100`);
      stage1Filters.push(`atempo=${clampedTempo.toFixed(4)}`);
    }
    
    if (stage1Filters.length === 0) {
      stage1Filters.push("anull");
    }

    await new Promise<void>((resolve, reject) => {
      const ffmpeg = spawn("ffmpeg", [
        "-i", targetPath,
        "-af", stage1Filters.join(","),
        "-ar", "44100",
        "-ac", "1",
        "-y",
        intermediatePath
      ]);

      const stderrChunks: Buffer[] = [];
      ffmpeg.stderr.on("data", (data) => stderrChunks.push(data));

      ffmpeg.on("close", (code) => {
        if (code === 0) resolve();
        else {
          const stderr = Buffer.concat(stderrChunks).toString();
          console.error("FFmpeg stage 1 stderr:", stderr);
          reject(new Error(`FFmpeg pitch/tempo transform failed with code ${code}: ${stderr.slice(-500)}`));
        }
      });

      ffmpeg.on("error", reject);
    });

    onProgress?.(45, "matching");

    const formantFilters = buildAdvancedFormantFilters(sourceParams, targetParams, formantShiftParams, intensityFactor);
    const spectralFilters = buildSpectralEnvelopeFilters(sourceParams, targetParams, intensityFactor);
    const antiAliasFilters = buildAntiAliasingFilters(44100);
    const qualityFilters = buildVoiceQualityEnhancement(sourceParams, targetParams, intensityFactor);
    const naturalnessFilters = buildNaturalnessEnhancement(sourceParams, targetParams, intensityFactor);
    const dynamicsFilters = buildDynamicsProcessing(sourceParams, targetParams, intensityFactor);

    const stage2Filters: string[] = [
      ...antiAliasFilters.slice(0, 1),
      ...formantFilters,
      ...spectralFilters,
      ...qualityFilters,
      ...antiAliasFilters.slice(1),
      ...naturalnessFilters,
      ...dynamicsFilters,
    ];

    if (stage2Filters.length === 0) {
      stage2Filters.push("anull");
    }

    onProgress?.(65, "matching");

    await new Promise<void>((resolve, reject) => {
      const ffmpeg = spawn("ffmpeg", [
        "-i", intermediatePath,
        "-af", stage2Filters.join(","),
        "-ar", "44100",
        "-ac", "2",
        "-b:a", "192k",
        "-y",
        outputPath
      ]);

      const stderrChunks: Buffer[] = [];
      ffmpeg.stderr.on("data", (data) => stderrChunks.push(data));

      ffmpeg.on("close", (code) => {
        if (code === 0) resolve();
        else {
          const stderr = Buffer.concat(stderrChunks).toString();
          console.error("FFmpeg stage 2 stderr:", stderr);
          reject(new Error(`FFmpeg formant transform failed with code ${code}: ${stderr.slice(-500)}`));
        }
      });

      ffmpeg.on("error", reject);
    });

    onProgress?.(85, "finalizing");

    const transformedBuffer = fs.readFileSync(outputPath);
    
    const resultParams = await analyzeVoice(transformedBuffer);

    const matchScore = calculateMatchScore(sourceParams, resultParams);

    onProgress?.(100, "completed");

    return { transformedBuffer, resultParams, matchScore };
  } finally {
    if (fs.existsSync(sourcePath)) fs.unlinkSync(sourcePath);
    if (fs.existsSync(targetPath)) fs.unlinkSync(targetPath);
    if (fs.existsSync(intermediatePath)) fs.unlinkSync(intermediatePath);
    if (fs.existsSync(outputPath)) fs.unlinkSync(outputPath);
  }
}

function calculateMatchScore(source: VoiceParameters, result: VoiceParameters): number {
  const weights = {
    pitch: 0.25,
    formants: 0.25,
    spectral: 0.20,
    energy: 0.15,
    harmonic: 0.15,
  };

  const pitchScore = 100 - Math.min(100, Math.abs(source.pitchMean - result.pitchMean) / source.pitchMean * 100);
  
  const formantScore = 100 - Math.min(100, (
    Math.abs(source.formantF1 - result.formantF1) / source.formantF1 +
    Math.abs(source.formantF2 - result.formantF2) / source.formantF2
  ) * 50);
  
  const spectralScore = 100 - Math.min(100, Math.abs(source.spectralCentroid - result.spectralCentroid) / source.spectralCentroid * 100);
  
  const energyScore = 100 - Math.min(100, Math.abs(source.rmsEnergy - result.rmsEnergy) / Math.max(0.001, source.rmsEnergy) * 100);
  
  const harmonicScore = 100 - Math.min(100, Math.abs(source.harmonicRatio - result.harmonicRatio));

  const totalScore = 
    weights.pitch * pitchScore +
    weights.formants * formantScore +
    weights.spectral * spectralScore +
    weights.energy * energyScore +
    weights.harmonic * harmonicScore;

  return Math.max(0, Math.min(100, totalScore));
}
