import 'dart:developer';
import 'dart:typed_data';

import 'package:audioplayers/audioplayers.dart';
import 'package:wolf_3d_dart/src/synth/imf_renderer.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';

class WolfAudio implements EngineAudio {
  @override
  Future<void> debugSoundTest() async {
    // Play the first 50 sounds with a 2-second gap to identify them
    for (int i = 0; i < 50; i++) {
      Future.delayed(Duration(seconds: i * 2), () {
        log("[AUDIO] Testing Sound ID: $i");
        playSoundEffectId(i);
      });
    }
  }

  bool _isInitialized = false;

  // --- Music State ---
  final AudioPlayer _musicPlayer = AudioPlayer();

  // --- SFX State ---
  // A pool of players to allow overlapping sound effects.
  static const int _maxSfxChannels = 8;
  final List<AudioPlayer> _sfxPlayers = [];
  int _currentSfxIndex = 0;

  @override
  WolfensteinData? activeGame;

  /// Initializes the audio engine and pre-allocates the SFX pool.
  @override
  Future<void> init() async {
    if (_isInitialized) return;

    try {
      // Set music player mode
      await _musicPlayer.setPlayerMode(PlayerMode.mediaPlayer);

      // Initialize the SFX pool
      for (int i = 0; i < _maxSfxChannels; i++) {
        final player = AudioPlayer();
        // lowLatency mode is highly recommended for short game sounds
        await player.setPlayerMode(PlayerMode.lowLatency);
        await player.setReleaseMode(ReleaseMode.stop);
        _sfxPlayers.add(player);
      }

      _isInitialized = true;
      log(
        "[AUDIO] AudioPlayers initialized successfully with $_maxSfxChannels SFX channels.",
      );
    } catch (e) {
      log("[AUDIO] Failed to initialize AudioPlayers - $e");
    }
  }

  /// Disposes of the audio engine and frees resources.
  @override
  void dispose() {
    stopAllAudio();
    _musicPlayer.dispose();

    for (final player in _sfxPlayers) {
      player.stop();
      player.dispose();
    }
    _sfxPlayers.clear();

    _isInitialized = false;
  }

  // ==========================================
  // MUSIC MANAGEMENT
  // ==========================================

  Future<void> playMusic(ImfMusic track, {bool looping = true}) async {
    if (!_isInitialized) return;
    await stopMusic();

    try {
      final pcmSamples = ImfRenderer.render(track);
      final wavBytes = ImfRenderer.createWavFile(pcmSamples);

      await _musicPlayer.setReleaseMode(
        looping ? ReleaseMode.loop : ReleaseMode.stop,
      );
      await _musicPlayer.play(BytesSource(wavBytes));
    } catch (e) {
      log("[AUDIO] Error playing music track - $e");
    }
  }

  @override
  Future<void> stopMusic() async {
    if (!_isInitialized) return;
    await _musicPlayer.stop();
  }

  @override
  Future<void> stopAllAudio() async {
    if (!_isInitialized) return;

    await _musicPlayer.stop();
    for (final player in _sfxPlayers) {
      await player.stop();
    }
  }

  Future<void> pauseMusic() async {
    if (_isInitialized) await _musicPlayer.pause();
  }

  Future<void> resumeMusic() async {
    if (_isInitialized) await _musicPlayer.resume();
  }

  @override
  Future<void> playMenuMusic() async {
    final data = activeGame;
    final trackIndex = data == null
        ? null
        : Music.menuTheme.trackIndexFor(data.version);
    if (data == null || trackIndex == null || trackIndex >= data.music.length) {
      return;
    }
    await playMusic(data.music[trackIndex]);
  }

  @override
  Future<void> playLevelMusic(Music music) async {
    final data = activeGame;
    if (data == null || data.music.isEmpty) return;

    final index = music.trackIndexFor(data.version) ?? 0;
    if (index < data.music.length) {
      await playMusic(data.music[index]);
    } else {
      log("[AUDIO] Warning - Track index $index out of bounds.");
    }
  }

  // ==========================================
  // SFX MANAGEMENT
  // ==========================================

  @override
  Future<void> playSoundEffect(SoundEffect effect) async {
    final data = activeGame;
    if (data == null) return;
    await playSoundEffectId(effect.idFor(data.version));
  }

  @override
  Future<void> playSoundEffectId(int sfxId) async {
    log("[AUDIO] Playing sfx id $sfxId");
    // The original engine uses a specific starting chunk for digitized sounds.
    // In many loaders, the 'sounds' list is already just the digitized ones.
    // If your list contains EVERYTHING, you need to add the offset (174).
    // If it's JUST digitized sounds, sfxId should work directly.

    final data = activeGame;
    if (data == null) return;

    final soundsList = data.sounds;
    if (sfxId < 0 || sfxId >= soundsList.length) return;

    final raw8bitBytes = soundsList[sfxId].bytes;
    if (raw8bitBytes.isEmpty) return;

    // 2. Convert 8-bit Unsigned PCM -> 16-bit Signed PCM
    // Wolf3D raw sounds are biased at 128.
    final Int16List converted16bit = Int16List(raw8bitBytes.length);
    for (int i = 0; i < raw8bitBytes.length; i++) {
      // (sample - 128) shifts it to signed 8-bit range (-128 to 127)
      // Multiplying by 256 scales it to 16-bit range (-32768 to 32512)
      converted16bit[i] = (raw8bitBytes[i] - 128) * 256;
    }

    // 3. Wrap in a WAV header (Wolf3D digitized sounds are 7000Hz Mono)
    final wavBytes = ImfRenderer.createWavFile(
      converted16bit,
      sampleRate: 7000,
    );

    try {
      final player = _sfxPlayers[_currentSfxIndex];
      _currentSfxIndex = (_currentSfxIndex + 1) % _maxSfxChannels;

      // Note: We use BytesSource because createWavFile returns Uint8List (the file bytes)
      await player.play(BytesSource(wavBytes));
    } catch (e) {
      log("[AUDIO] SFX Error - $e");
    }
  }
}
