#include "audio.hpp"

#include <SDL3/SDL.h>  // Para SDL_LogInfo, SDL_LogCategory, SDL_G...

#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
#include "external/stb_vorbis.h"
// clang-format on

#include "core/audio/jail_audio.hpp"          // Para JA_FadeOutMusic, JA_Init, JA_PauseM...
#include "core/resources/resource_cache.hpp"  // Para Resource
#include "game/options.hpp"                   // Para AudioOptions, audio, MusicOptions

// 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; }

// 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 && instance->music_.state == MusicState::PLAYING && JA_GetMusicState() != JA_MUSIC_PLAYING) {
        instance->music_.state = MusicState::STOPPED;
    }
}

// Reproduce la música (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;
    }

    // Intentar obtener recurso; si falla, no tocar estado
    auto* resource = Resource::Cache::get()->getMusic(name);
    if (resource == nullptr) return;

    if (crossfade_ms > 0 && music_.state == MusicState::PLAYING) {
        // Crossfade: fade-out de la pista actual + fade-in de la nueva
        JA_CrossfadeMusic(resource, crossfade_ms, loop);
    } else {
        // Cambio inmediato
        if (music_.state == MusicState::PLAYING) {
            JA_StopMusic();
        }
        JA_PlayMusic(resource, loop);
    }

    music_.name = name;
    music_.loop = new_loop;
    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() {  // NOLINT(readability-make-member-function-const)
    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(Resource::Cache::get()->getSound(name), 0, static_cast<int>(group));
    }
}

// Reproduce un sonido por puntero directo
void Audio::playSound(JA_Sound_t* sound, Group group) const {
    if (sound_enabled_) {
        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_Music_state ja_state = JA_GetMusicState();
    switch (ja_state) {
        case JA_MUSIC_PLAYING:
            return MusicState::PLAYING;
        case JA_MUSIC_PAUSED:
            return MusicState::PAUSED;
        case JA_MUSIC_STOPPED:
        case JA_MUSIC_INVALID:
        case JA_MUSIC_DISABLED:
        default:
            return MusicState::STOPPED;
    }
}

// Establece el volumen de los sonidos
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
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)) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_AUDIO could not initialize! SDL Error: %s", SDL_GetError());
    } else {
        JA_Init(FREQUENCY, SDL_AUDIO_S16LE, 2);
        enable(Options::audio.enabled);

        // Aplicar estado de música y sonido guardado en las opciones.
        // enable() ya aplica los volúmenes, pero no toca music_enabled_/sound_enabled_.
        // Si alguno está desactivado, hay que forzar el volumen a 0 en el backend.
        if (!Options::audio.music.enabled) {
            setMusicVolume(0.0F);  // music_enabled_=true aún → llega a JA
            enableMusic(false);
        }
        if (!Options::audio.sound.enabled) {
            setSoundVolume(0.0F);  // sound_enabled_=true aún → llega a JA
            enableSound(false);
        }

        std::cout << "\n** AUDIO SYSTEM **\n";
        std::cout << "Audio system initialized successfully\n";
    }
}