import type { AudioProcessor, ProcessorCategory } from './AudioProcessor';

export class SegmentShuffler implements AudioProcessor {
  getName(): string {
    return 'Segment Shuffler';
  }

  getDescription(): string {
    return 'Randomly reorganizes and swaps parts of the sound';
  }

  getCategory(): ProcessorCategory {
    return 'Time';
  }

  process(
    leftChannel: Float32Array,
    rightChannel: Float32Array
  ): [Float32Array, Float32Array] {
    const length = leftChannel.length;
    const numSegments = Math.floor(Math.random() * 13) + 4;
    const segmentSize = Math.floor(length / numSegments);

    // Crossfade length: 441 samples (10ms at 44100Hz) - aggressive smoothing
    // Using longer fades to ensure no clicks even with very abrupt transitions
    const crossfadeLength = 441;

    const segments: Array<{ left: Float32Array; right: Float32Array }> = [];
    for (let i = 0; i < numSegments; i++) {
      const start = i * segmentSize;
      const end = i === numSegments - 1 ? length : (i + 1) * segmentSize;
      segments.push({
        left: leftChannel.slice(start, end),
        right: rightChannel.slice(start, end)
      });
    }

    this.shuffleArray(segments);

    const outputLeft = new Float32Array(length);
    const outputRight = new Float32Array(length);

    let writePos = 0;
    for (let i = 0; i < segments.length; i++) {
      const segment = segments[i];
      const segLen = segment.left.length;
      const isFirstSegment = i === 0;
      const isLastSegment = i === segments.length - 1;

      // Calculate actual fade length - limit to segment length to prevent overlap issues
      // If segment is very short, use a single Hann window over entire segment
      let actualFadeIn = isFirstSegment ? 0 : Math.min(crossfadeLength, Math.floor(segLen / 2));
      let actualFadeOut = isLastSegment ? 0 : Math.min(crossfadeLength, Math.floor(segLen / 2));

      // Check if fades would overlap - if so, reduce them proportionally
      if (actualFadeIn + actualFadeOut > segLen) {
        const scale = segLen / (actualFadeIn + actualFadeOut);
        actualFadeIn = Math.floor(actualFadeIn * scale);
        actualFadeOut = Math.floor(actualFadeOut * scale);
      }

      for (let j = 0; j < segLen && writePos < length; j++, writePos++) {
        let leftSample = segment.left[j];
        let rightSample = segment.right[j];
        let gain = 1.0;

        // Raised cosine (Hann) fade-in at segment start
        // Formula: 0.5 * (1 - cos(π * t)) where t goes from 0 to 1
        // This has continuous first and second derivatives (C² continuity)
        if (actualFadeIn > 0 && j < actualFadeIn) {
          const t = j / actualFadeIn;
          gain *= 0.5 * (1.0 - Math.cos(Math.PI * t));
        }

        // Raised cosine (Hann) fade-out at segment end
        // Formula: 0.5 * (1 + cos(π * t)) where t goes from 0 to 1
        // This mirrors the fade-in curve for symmetry
        if (actualFadeOut > 0 && j >= segLen - actualFadeOut) {
          const samplesIntoFadeOut = j - (segLen - actualFadeOut);
          const t = samplesIntoFadeOut / actualFadeOut;
          gain *= 0.5 * (1.0 + Math.cos(Math.PI * t));
        }

        outputLeft[writePos] = leftSample * gain;
        outputRight[writePos] = rightSample * gain;
      }
    }

    // Master fade in/out using raised cosine for buffer boundaries
    // This ensures the entire buffer starts and ends at zero
    const masterFadeLength = 441;

    // Raised cosine fade-in at buffer start
    for (let i = 0; i < Math.min(masterFadeLength, length); i++) {
      const t = i / masterFadeLength;
      const gain = 0.5 * (1.0 - Math.cos(Math.PI * t));
      outputLeft[i] *= gain;
      outputRight[i] *= gain;
    }

    // Raised cosine fade-out at buffer end
    for (let i = 0; i < Math.min(masterFadeLength, length); i++) {
      const pos = length - 1 - i;
      const samplesIntoFadeOut = i;
      const t = samplesIntoFadeOut / masterFadeLength;
      const gain = 0.5 * (1.0 + Math.cos(Math.PI * t));
      outputLeft[pos] *= gain;
      outputRight[pos] *= gain;
    }

    return [outputLeft, outputRight];
  }

  private shuffleArray<T>(array: T[]): void {
    for (let i = array.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [array[i], array[j]] = [array[j], array[i]];
    }
  }
}
