import 'dart:typed_data';

import 'package:wolf_3d_dart/wolf_3d_data_types.dart';

import 'opl2_emulator.dart';

class ImfRenderer {
  /// Renders music in Stereo with a 10ms Haas widening effect.
  static Int16List render(ImfMusic music) {
    final emulator = Opl2Emulator();
    const double samplesPerTick = Opl2Emulator.sampleRate / 700.0;

    int totalTicks = 0;
    for (final inst in music.instructions) {
      totalTicks += inst.delay;
    }

    // Allocate buffer for Stereo (2 channels per sample)
    int totalMonoSamples = (totalTicks * samplesPerTick).round();
    final pcmBuffer = Int16List(totalMonoSamples * 2);
    int sampleIndex = 0;

    // --- Stereo Widening (Haas Effect) ---
    // 10ms at 44.1kHz = 441 samples.
    // This is tight enough to sound like 'width' rather than 'echo'.
    const int delaySamples = 441;
    final delayLine = Float32List(delaySamples);
    int delayPtr = 0;

    for (final instruction in music.instructions) {
      emulator.writeRegister(instruction.register, instruction.data);
      int samplesToGenerate = (instruction.delay * samplesPerTick).round();

      for (int i = 0; i < samplesToGenerate; i++) {
        if (sampleIndex + 1 >= pcmBuffer.length) break;

        double monoSample = emulator.generateSample();

        // Left channel: Immediate
        double left = monoSample;
        // Right channel: Delayed
        double right = delayLine[delayPtr];

        delayLine[delayPtr] = monoSample;
        delayPtr = (delayPtr + 1) % delaySamples;

        // Interleave L and R
        pcmBuffer[sampleIndex++] = (left * 32767).toInt().clamp(-32768, 32767);
        pcmBuffer[sampleIndex++] = (right * 32767).toInt().clamp(-32768, 32767);
      }
    }
    return pcmBuffer;
  }

  /// Wraps raw PCM data into a standard Stereo WAV file header.
  static Uint8List createWavFile(
    Int16List pcmData, {
    int sampleRate = Opl2Emulator.sampleRate,
  }) {
    const int numChannels = 2;
    const int bytesPerSample = 2;
    const int blockAlign = numChannels * bytesPerSample;

    final int dataSize = pcmData.length * bytesPerSample;
    final int byteRate = sampleRate * blockAlign;
    final int fileSize = 36 + dataSize;

    final bytes = BytesBuilder();

    // RIFF header
    bytes.add('RIFF'.codeUnits);
    bytes.add(_int32ToBytes(fileSize));
    bytes.add('WAVE'.codeUnits);

    // Format chunk
    bytes.add('fmt '.codeUnits);
    bytes.add(_int32ToBytes(16));
    bytes.add(_int16ToBytes(1));
    bytes.add(_int16ToBytes(numChannels));
    bytes.add(_int32ToBytes(sampleRate));
    bytes.add(_int32ToBytes(byteRate));
    bytes.add(_int16ToBytes(blockAlign));
    bytes.add(_int16ToBytes(16));

    // Data chunk
    bytes.add('data'.codeUnits);
    bytes.add(_int32ToBytes(dataSize));

    final byteData = ByteData(dataSize);
    for (int i = 0; i < pcmData.length; i++) {
      byteData.setInt16(i * 2, pcmData[i], Endian.little);
    }
    bytes.add(byteData.buffer.asUint8List());

    return bytes.toBytes();
  }

  static List<int> _int16ToBytes(int value) => [
    value & 0xff,
    (value >> 8) & 0xff,
  ];
  static List<int> _int32ToBytes(int value) => [
    value & 0xff,
    (value >> 8) & 0xff,
    (value >> 16) & 0xff,
    (value >> 24) & 0xff,
  ];
}
