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

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
import 'package:wolf_3d_dart/wolf_3d_audio.dart';
import 'package:wolf_3d_dart/wolf_3d_data_types.dart';
import 'package:wolf_3d_dart/wolf_3d_engine.dart';
import 'package:wolf_3d_dart/wolf_3d_synth.dart';

class Wolf3dPlatformAudio implements EngineAudio, DebugMusicPlayer {
  Wolf3dPlatformAudio()
    : _delegate =
          (!kIsWeb &&
              _isDesktopTarget() &&
              NativeSubprocessAudio.supportsCurrentPlatform)
          ? NativeSubprocessAudio()
          : _EmbeddedWolf3dPlatformAudio();

  final EngineAudio _delegate;

  @override
  WolfensteinData? get activeGame => _delegate.activeGame;

  @override
  set activeGame(WolfensteinData? value) {
    _delegate.activeGame = value;
  }

  @override
  Future<void> debugSoundTest() => _delegate.debugSoundTest();

  @override
  Future<void> init() => _delegate.init();

  @override
  void dispose() => _delegate.dispose();

  @override
  void playMenuMusic() => _delegate.playMenuMusic();

  @override
  void playLevelMusic(Music music) => _delegate.playLevelMusic(music);

  @override
  void stopMusic() => _delegate.stopMusic();

  @override
  Future<void> stopAllAudio() => _delegate.stopAllAudio();

  @override
  void playSoundEffect(SoundEffect effect) => _delegate.playSoundEffect(effect);

  @override
  void playSoundEffectId(int sfxId) => _delegate.playSoundEffectId(sfxId);

  @override
  Future<void> playMusic(ImfMusic track, {bool looping = true}) {
    if (_delegate is DebugMusicPlayer) {
      return (_delegate as DebugMusicPlayer).playMusic(
        track,
        looping: looping,
      );
    }

    return Future<void>.value();
  }

  static bool _isDesktopTarget() {
    switch (defaultTargetPlatform) {
      case TargetPlatform.macOS:
      case TargetPlatform.windows:
      case TargetPlatform.linux:
        return true;
      case TargetPlatform.android:
      case TargetPlatform.iOS:
      case TargetPlatform.fuchsia:
        return false;
    }
  }
}

class _EmbeddedWolf3dPlatformAudio implements EngineAudio, DebugMusicPlayer {
  _EmbeddedWolf3dPlatformAudio();

  @override
  Future<void> debugSoundTest() async {
    for (int i = 0; i < 50; i++) {
      Future.delayed(Duration(seconds: i * 2), () {
        log('[AUDIO] Testing Sound ID: $i');
        playSoundEffectId(i);
      });
    }
  }

  bool _isInitialized = false;
  final AudioPlayer _musicPlayer = AudioPlayer();

  static const int _maxSfxChannels = 8;
  final List<AudioPlayer> _sfxPlayers = [];
  int _currentSfxIndex = 0;

  @override
  WolfensteinData? activeGame;

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

    try {
      await _musicPlayer.setPlayerMode(PlayerMode.mediaPlayer);

      for (int i = 0; i < _maxSfxChannels; i++) {
        final player = AudioPlayer();
        await player.setPlayerMode(PlayerMode.lowLatency);
        await player.setReleaseMode(ReleaseMode.stop);
        _sfxPlayers.add(player);
      }

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

  @override
  void dispose() {
    stopAllAudio();
    _musicPlayer.dispose();

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

    _isInitialized = false;
  }

  @override
  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.');
    }
  }

  @override
  Future<void> playSoundEffect(SoundEffect effect) async {
    final data = activeGame;
    if (data == null) return;

    final resolved = data.registry.sfx.resolve(effect);
    if (resolved != null) {
      await playSoundEffectId(resolved.slotIndex);
      return;
    }

    if (data.version == GameVersion.spearOfDestinyDemo) {
      return;
    }

    await playSoundEffectId(effect.idFor(data.version));
  }

  @override
  Future<void> playSoundEffectId(int sfxId) async {
    log('[AUDIO] Playing sfx id $sfxId');

    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;

    final Int16List converted16bit = Int16List(raw8bitBytes.length);
    for (int i = 0; i < raw8bitBytes.length; i++) {
      converted16bit[i] = (raw8bitBytes[i] - 128) * 256;
    }

    final wavBytes = ImfRenderer.createWavFile(
      converted16bit,
      sampleRate: 7000,
    );

    try {
      final player = _sfxPlayers[_currentSfxIndex];
      _currentSfxIndex = (_currentSfxIndex + 1) % _maxSfxChannels;
      await player.play(BytesSource(wavBytes));
    } catch (e) {
      log('[AUDIO] SFX Error - $e');
    }
  }
}
