#include "core/audio/audio.hpp"

#include <SDL3/SDL.h>  // Para SDL_GetError, SDL_Init

#include <algorithm>  // Para clamp
#include <iostream>   // Para std::cout

// Implementación de stb_vorbis (debe estar ANTES de incluir jail_audio.hpp).
// clang-format off
#undef STB_VORBIS_HEADER_ONLY
// NOLINTNEXTLINE(bugprone-suspicious-include) — stb_vorbis és single-file: el TU principal inclou el .c per portar la implementació.
#include "external/stb_vorbis.c"
// stb_vorbis.c filtra les macros L, C i R (i PLAYBACK_*) al TU. Les netegem
// perquè xocarien amb noms de paràmetres de plantilla en altres headers.
#undef L
#undef C
#undef R
#undef PLAYBACK_MONO
#undef PLAYBACK_LEFT
#undef PLAYBACK_RIGHT
// clang-format on

#include "core/audio/audio_adapter.hpp"  // Para AudioResource::getMusic/getSound
#include "core/audio/jail_audio.hpp"     // Para JA_*
#include "game/options.hpp"              // Para Options::audio

// Singleton
Audio* Audio::instance = nullptr;

// Inicializa la instancia única del singleton
void Audio::init() { Audio::instance = new Audio(); }

// Libera la instancia
void Audio::destroy() {
    delete Audio::instance;
    Audio::instance = nullptr;
}

// Obtiene la instancia
auto Audio::get() -> Audio* { return Audio::instance; }

// Constructor
Audio::Audio() { initSDLAudio(); }

// Destructor
Audio::~Audio() {
    Ja::quit();
}

// Método principal
void Audio::update() {
    Ja::update();

    // Sincronizar estado: detectar cuando la música se para (ej. fade-out completado)
    if (instance != nullptr && instance->music_.state == MusicState::PLAYING && Ja::getMusicState() != Ja::MusicState::PLAYING) {
        instance->music_.state = MusicState::STOPPED;
    }
}

// Reproduce la música por nombre (con crossfade opcional)
void Audio::playMusic(const std::string& name, const int loop, const int crossfade_ms) {
    bool new_loop = (loop != 0);

    // Si ya está sonando exactamente la misma pista y mismo modo loop, no hacemos nada
    if (music_.state == MusicState::PLAYING && music_.name == name && music_.loop == new_loop) {
        return;
    }

    if (!music_enabled_) { return; }

    auto* resource = AudioResource::getMusic(name);
    if (resource == nullptr) { return; }

    if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
        Ja::crossfadeMusic(resource, crossfade_ms, loop);
    } else {
        if (music_.state == MusicState::PLAYING) {
            Ja::stopMusic();
        }
        Ja::playMusic(resource, loop);
    }

    music_.name = name;
    music_.loop = new_loop;
    music_.state = MusicState::PLAYING;
}

// Reproduce la música por puntero (con crossfade opcional)
void Audio::playMusic(Ja::Music* music, const int loop, const int crossfade_ms) {
    if (!music_enabled_ || music == nullptr) { return; }

    if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
        Ja::crossfadeMusic(music, crossfade_ms, loop);
    } else {
        if (music_.state == MusicState::PLAYING) {
            Ja::stopMusic();
        }
        Ja::playMusic(music, loop);
    }

    music_.name.clear();  // nom desconegut quan es passa per punter
    music_.loop = (loop != 0);
    music_.state = MusicState::PLAYING;
}

// Pausa la música
void Audio::pauseMusic() {
    if (music_enabled_ && music_.state == MusicState::PLAYING) {
        Ja::pauseMusic();
        music_.state = MusicState::PAUSED;
    }
}

// Continua la música pausada
void Audio::resumeMusic() {
    if (music_enabled_ && music_.state == MusicState::PAUSED) {
        Ja::resumeMusic();
        music_.state = MusicState::PLAYING;
    }
}

// Detiene la música
void Audio::stopMusic() {
    if (music_enabled_) {
        Ja::stopMusic();
        music_.state = MusicState::STOPPED;
    }
}

// Reproduce un sonido por nombre
void Audio::playSound(const std::string& name, Group group) const {
    if (sound_enabled_) {
        Ja::playSound(AudioResource::getSound(name), 0, static_cast<int>(group));
    }
}

// Reproduce un sonido por puntero directo
void Audio::playSound(Ja::Sound* sound, Group group) const {
    if (sound_enabled_ && sound != nullptr) {
        Ja::playSound(sound, 0, static_cast<int>(group));
    }
}

// Detiene todos los sonidos
void Audio::stopAllSounds() const {
    if (sound_enabled_) {
        Ja::stopChannel(-1);
    }
}

// Realiza un fundido de salida de la música
void Audio::fadeOutMusic(int milliseconds) const {
    if (music_enabled_ && getRealMusicState() == MusicState::PLAYING) {
        Ja::fadeOutMusic(milliseconds);
    }
}

// Consulta directamente el estado real de la música en jailaudio
auto Audio::getRealMusicState() -> MusicState {
    Ja::MusicState ja_state = Ja::getMusicState();
    switch (ja_state) {
        case Ja::MusicState::PLAYING:
            return MusicState::PLAYING;
        case Ja::MusicState::PAUSED:
            return MusicState::PAUSED;
        case Ja::MusicState::STOPPED:
        case Ja::MusicState::INVALID:
        case Ja::MusicState::DISABLED:
        default:
            return MusicState::STOPPED;
    }
}

// Establece el volumen de los sonidos (float 0.0..1.0)
void Audio::setSoundVolume(float sound_volume, Group group) const {
    if (sound_enabled_) {
        sound_volume = std::clamp(sound_volume, MIN_VOLUME, MAX_VOLUME);
        const float CONVERTED_VOLUME = sound_volume * Options::audio.volume;
        Ja::setSoundVolume(CONVERTED_VOLUME, static_cast<int>(group));
    }
}

// Establece el volumen de la música (float 0.0..1.0)
void Audio::setMusicVolume(float music_volume) const {
    if (music_enabled_) {
        music_volume = std::clamp(music_volume, MIN_VOLUME, MAX_VOLUME);
        const float CONVERTED_VOLUME = music_volume * Options::audio.volume;
        Ja::setMusicVolume(CONVERTED_VOLUME);
    }
}

// Aplica la configuración
void Audio::applySettings() {
    enable(Options::audio.enabled);
}

// Establecer estado general
void Audio::enable(bool value) {
    enabled_ = value;

    setSoundVolume(enabled_ ? Options::audio.sound.volume : MIN_VOLUME);
    setMusicVolume(enabled_ ? Options::audio.music.volume : MIN_VOLUME);
}

// Inicializa SDL Audio
void Audio::initSDLAudio() {
    if (!SDL_Init(SDL_INIT_AUDIO)) {
        std::cout << "SDL_AUDIO could not initialize! SDL Error: " << SDL_GetError() << '\n';
    } else {
        Ja::init(FREQUENCY, SDL_AUDIO_S16LE, 2);
        enable(Options::audio.enabled);
    }
}
