import type { AudioProcessor } from "../processors/AudioProcessor";

const CROSSFADE_SAMPLES = 256;

/**
 * Apply equal-power crossfade between two regions
 */
function applyCrossfade(
  samples: Float32Array,
  fadeOutStart: number,
  fadeOutEnd: number,
  fadeInStart: number,
  fadeInEnd: number
): void {
  const fadeLength = Math.min(fadeOutEnd - fadeOutStart, fadeInEnd - fadeInStart);

  for (let i = 0; i < fadeLength; i++) {
    const t = i / fadeLength;
    const fadeOutGain = Math.cos(t * Math.PI * 0.5);
    const fadeInGain = Math.sin(t * Math.PI * 0.5);

    const fadeOutIdx = fadeOutStart + i;
    const fadeInIdx = fadeInStart + i;

    if (fadeOutIdx < samples.length && fadeInIdx < samples.length) {
      const fadeOutSample = samples[fadeOutIdx] * fadeOutGain;
      const fadeInSample = samples[fadeInIdx] * fadeInGain;
      samples[fadeOutIdx] = fadeOutSample + fadeInSample;
    }
  }
}

/**
 * Extract a portion of the audio buffer (crop to selection)
 */
export function cropAudio(
  buffer: AudioBuffer,
  startSample: number,
  endSample: number,
  sampleRate: number
): [Float32Array, Float32Array] {
  const start = Math.max(0, Math.floor(startSample));
  const end = Math.min(buffer.length, Math.ceil(endSample));
  const length = end - start;

  const leftChannel = buffer.getChannelData(0);
  const rightChannel = buffer.getChannelData(1);

  const newLeft = new Float32Array(length);
  const newRight = new Float32Array(length);

  for (let i = 0; i < length; i++) {
    newLeft[i] = leftChannel[start + i];
    newRight[i] = rightChannel[start + i];
  }

  return [newLeft, newRight];
}

/**
 * Remove a portion of the audio buffer (cut with crossfade)
 */
export function cutAudio(
  buffer: AudioBuffer,
  startSample: number,
  endSample: number,
  sampleRate: number
): [Float32Array, Float32Array] {
  const start = Math.max(0, Math.floor(startSample));
  const end = Math.min(buffer.length, Math.ceil(endSample));

  const leftChannel = buffer.getChannelData(0);
  const rightChannel = buffer.getChannelData(1);

  const beforeLength = start;
  const afterLength = buffer.length - end;
  const newLength = beforeLength + afterLength;

  const newLeft = new Float32Array(newLength);
  const newRight = new Float32Array(newLength);

  for (let i = 0; i < beforeLength; i++) {
    newLeft[i] = leftChannel[i];
    newRight[i] = rightChannel[i];
  }

  for (let i = 0; i < afterLength; i++) {
    newLeft[beforeLength + i] = leftChannel[end + i];
    newRight[beforeLength + i] = rightChannel[end + i];
  }

  const crossfadeLength = Math.min(CROSSFADE_SAMPLES, beforeLength, afterLength);
  if (crossfadeLength > 0 && beforeLength > 0 && afterLength > 0) {
    const crossfadeStart = beforeLength - crossfadeLength;

    for (let i = 0; i < crossfadeLength; i++) {
      const t = i / crossfadeLength;
      const fadeOutGain = Math.cos(t * Math.PI * 0.5);
      const fadeInGain = Math.sin(t * Math.PI * 0.5);

      const idx = crossfadeStart + i;
      const beforeSample = newLeft[idx];
      const afterSample = newLeft[beforeLength + i];
      newLeft[idx] = beforeSample * fadeOutGain + afterSample * fadeInGain;

      const beforeSampleR = newRight[idx];
      const afterSampleR = newRight[beforeLength + i];
      newRight[idx] = beforeSampleR * fadeOutGain + afterSampleR * fadeInGain;
    }

    for (let i = crossfadeLength; i < afterLength; i++) {
      newLeft[beforeLength - crossfadeLength + i] = newLeft[beforeLength + i];
      newRight[beforeLength - crossfadeLength + i] = newRight[beforeLength + i];
    }

    const finalLength = beforeLength - crossfadeLength + afterLength;
    return [
      newLeft.slice(0, finalLength),
      newRight.slice(0, finalLength)
    ];
  }

  return [newLeft, newRight];
}

/**
 * Process only a selected region of the audio with crossfades at boundaries
 */
export async function processSelection(
  buffer: AudioBuffer,
  startSample: number,
  endSample: number,
  processor: AudioProcessor,
  sampleRate: number
): Promise<[Float32Array, Float32Array]> {
  const start = Math.max(0, Math.floor(startSample));
  const end = Math.min(buffer.length, Math.ceil(endSample));
  const selectionLength = end - start;

  const leftChannel = buffer.getChannelData(0);
  const rightChannel = buffer.getChannelData(1);

  const selectedLeft = new Float32Array(selectionLength);
  const selectedRight = new Float32Array(selectionLength);

  for (let i = 0; i < selectionLength; i++) {
    selectedLeft[i] = leftChannel[start + i];
    selectedRight[i] = rightChannel[start + i];
  }

  const [processedLeft, processedRight] = await processor.process(selectedLeft, selectedRight);

  const newLeft = new Float32Array(buffer.length);
  const newRight = new Float32Array(buffer.length);

  for (let i = 0; i < buffer.length; i++) {
    newLeft[i] = leftChannel[i];
    newRight[i] = rightChannel[i];
  }

  for (let i = 0; i < processedLeft.length; i++) {
    newLeft[start + i] = processedLeft[i];
    newRight[start + i] = processedRight[i];
  }

  const maxCrossfade = Math.floor(selectionLength / 4);
  const crossfadeLength = Math.min(CROSSFADE_SAMPLES, start, buffer.length - end, maxCrossfade);

  if (crossfadeLength > 0) {
    for (let i = 0; i < crossfadeLength; i++) {
      const t = i / crossfadeLength;
      const unprocessedGain = Math.cos(t * Math.PI * 0.5);
      const processedGain = Math.sin(t * Math.PI * 0.5);

      const startIdx = start + i;
      newLeft[startIdx] = leftChannel[startIdx] * unprocessedGain + processedLeft[i] * processedGain;
      newRight[startIdx] = rightChannel[startIdx] * unprocessedGain + processedRight[i] * processedGain;
    }

    for (let i = 0; i < crossfadeLength; i++) {
      const t = i / crossfadeLength;
      const processedGain = Math.cos(t * Math.PI * 0.5);
      const unprocessedGain = Math.sin(t * Math.PI * 0.5);

      const endIdx = end - crossfadeLength + i;
      const processedIdx = processedLeft.length - crossfadeLength + i;
      newLeft[endIdx] = processedLeft[processedIdx] * processedGain + leftChannel[endIdx] * unprocessedGain;
      newRight[endIdx] = processedRight[processedIdx] * processedGain + rightChannel[endIdx] * unprocessedGain;
    }
  }

  return [newLeft, newRight];
}
