#pragma once

#include <cmath>       // Para std::lround
#include <cstdint>     // Para int8_t, uint8_t
#include <functional>  // Para std::function
#include <memory>      // Para std::unique_ptr
#include <string>      // Para string

// Forward-declares per no incloure core/audio/jail_audio.hpp al header. Els
// tres símbols (Music/Sound para el punter que exposa la API i Engine per al
// std::unique_ptr<Engine> membre) s'usen solo per punter al header, así que
// el forward-decl basta. El ~Audio() en .cpp veu la definició completa i
// instancia correctament el dtor de l'unique_ptr.
namespace Ja {
    class Engine;
    struct Music;
    struct Sound;
}  // namespace Ja

// --- Clase Audio: gestor d'àudio (singleton) ---
// Port del subsistema d'àudio del projecte ../aee, desacoblat d'Options:
// la configuración entra per la struct Audio::Config a init()/applySettings(),
// en lloc de llegir directament ConfigYaml::*. Això deixa audio.cpp independent
// del layout d'Options i permet substituir la font de configuración.
//
// Els volums es manegen internament como a float 0.0–1.0; la capa de
// presentació (menús, notificacions) usa las helpers toPercent/fromPercent
// per mostrar 0–100 a l'usuari.
class Audio {
   public:
    // --- Configuración injectada (Options la construeix via buildAudioConfig) ---
    struct Config {
        bool enabled{true};
        float volume{1.0F};  // Master 0..1
        bool music_enabled{true};
        float music_volume{0.8F};
        bool sound_enabled{true};
        float sound_volume{1.0F};
    };

    // --- Enums ---
    enum class Group : std::int8_t {
        ALL = -1,      // Tots los grups
        GAME = 0,      // Sons del joc
        INTERFACE = 1  // Sons de la interfície
    };

    enum class MusicState : std::uint8_t {
        PLAYING,  // Reproduint música
        PAUSED,   // Música pausada
        STOPPED,  // Música aturada
    };

    // --- Constants ---
    static constexpr float MAX_VOLUME = 1.0F;  // Volum màxim (float 0..1)
    static constexpr float MIN_VOLUME = 0.0F;  // Volum mínim (float 0..1)

    // --- Singleton ---
    static void init(const Config& config);  // Inicialitza con la configuración rebuda
    static void destroy();                   // Allibera l'objecte Audio
    static auto get() -> Audio*;             // Obté el punter a l'objecte Audio
    ~Audio();                                // Destructor (públic para std::unique_ptr)
    Audio(const Audio&) = delete;            // Evitar còpia
    Audio(Audio&&) = delete;
    auto operator=(const Audio&) -> Audio& = delete;  // Evitar assignació
    auto operator=(Audio&&) -> Audio& = delete;

    static void update();  // Actualització del sistema d'àudio

    // --- Control de música ---
    void playMusic(const std::string& name, int loop = -1, int crossfade_ms = 0);  // Reproduir música per nom (amb crossfade opcional)
    void playMusic(Ja::Music* music, int loop = -1, int crossfade_ms = 0);         // Reproduir música per punter (amb crossfade opcional)
    void pauseMusic();                                                             // Pausar la reproducció de música
    void resumeMusic();                                                            // Continua la música pausada
    void stopMusic();                                                              // Aturar completament la música
    void fadeOutMusic(int milliseconds);                                           // Fosa de sortida de la música (muta globals de Ja)
    void setOnMusicEnded(std::function<void()> callback);                          // Callback disparat cuando la pista actual acaba de drenar (CONV-03)
    // Multiplicador de velocitat de la música actual. 1.0 = normal,
    // 1.5 = un 50% més ràpid (efecte "chipmunk" — también puja el to).
    // Es reseteja a 1.0 implícitament a cada `playMusic`. No-op si no
    // hay música activa.
    void setMusicSpeed(float ratio);

    // --- Control de sons ---
    void playSound(const std::string& name, Group group = Group::GAME);  // Reproduir so puntual per nom (muta globals de Ja)
    void playSound(Ja::Sound* sound, Group group = Group::GAME);         // Reproduir so puntual per punter (muta globals de Ja)
    // Reprodueix un so con la velocitat (i to) escalats per `speed`:
    // 1.0 = normal, 0.95 ≈ -5% (més greu i lent), 1.05 ≈ +5% (més
    // agut i ràpid). Mateixa semàntica que `setMusicSpeed`. Útil per a
    // variacions subtils que eviten la fatiga d'escoltar el mismo
    // sample idèntic (p.ex. obertures de sarcòfag, picks d'ítems).
    void playSound(const std::string& name, Group group, float speed);
    // Reprodueix un so processat per un efecte definit a data/config/sounds.yaml
    // (preset_name busca a SoundEffectsConfig). Si el preset no existeix
    // o el motor está al sin de canals con efecte, fa fallback a playSound
    // sec — l'usuari sent el so igualment, sin la cua.
    void playSoundWithEcho(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
    void playSoundWithReverb(const std::string& name, const std::string& preset_name, Group group = Group::GAME);
    void stopAllSounds();  // Aturar tots los sons (muta globals de Ja)

    // --- Control de volum (API interna: float 0.0..1.0) ---
    void setSoundVolume(float volume, Group group = Group::ALL);  // Ajusta el volum dels efectes
    void setMusicVolume(float volume);                            // Ajusta el volum de la música
    void setMasterVolume(float volume);                           // Ajusta el master (re-aplica sound + music)

    // Getters dels volums actuals (lectura de la config_ cachejada). Reflexen
    // el valor que l'usuari ha triat l'última vegada, independent del gating
    // d'enabled/channel.
    [[nodiscard]] auto getMasterVolume() const -> float { return config_.volume; }
    [[nodiscard]] auto getSoundVolume() const -> float { return config_.sound_volume; }
    [[nodiscard]] auto getMusicVolume() const -> float { return config_.music_volume; }

    // --- Helpers de conversió para la capa de presentació ---
    // UI (menús, notificacions) manega enters 0..100; internament viu float 0..1.
    // No són constexpr porque std::lround no ho es en C++20; s'usen en runtime.
    static auto toPercent(float volume) -> int {
        return static_cast<int>(std::lround(volume * 100.0F));
    }
    static auto fromPercent(int percent) -> float {
        return static_cast<float>(percent) / 100.0F;
    }

    // --- Configuración general ---
    void enable(bool value);                     // Estableix l'estat general (reaplica volums)
    void toggleEnabled() { enable(!enabled_); }  // Alterna l'estat general (reaplica volums)
    void applySettings(const Config& config);    // Aplica una nueva configuración

    // --- Configuración de sons ---
    void enableSound(bool value);                         // Estableix l'estat dels sons (reaplica volum)
    void toggleSound() { enableSound(!sound_enabled_); }  // Alterna l'estat dels sons (reaplica volum)

    // --- Configuración de música ---
    void enableMusic(bool value);                         // Estableix l'estat de la música (reaplica volum)
    void toggleMusic() { enableMusic(!music_enabled_); }  // Alterna l'estat de la música (reaplica volum)

    // --- Consultes d'estat ---
    [[nodiscard]] auto isEnabled() const -> bool { return enabled_; }
    [[nodiscard]] auto isSoundEnabled() const -> bool { return sound_enabled_; }
    [[nodiscard]] auto isMusicEnabled() const -> bool { return music_enabled_; }
    [[nodiscard]] static auto getMusicState() -> MusicState;  // Estat real consultat a Ja::
    [[nodiscard]] auto getCurrentMusicName() const -> const std::string& { return music_.name; }
    // Duración de la pista resolta per nom (mil·lisegons). 0 si la pista no
    // existeix al cache de recursos o si el seu header OGG no permet
    // calcular-la. Pensat para clients que necessiten un timeline
    // determinista (p. ex. RoomFsm) sin dependre de callbacks de fi.
    [[nodiscard]] static auto getMusicDurationMs(const std::string& name) -> int;

   private:
    // --- Tipus anidats ---
    struct Music {
        std::string name;  // Última pista de música reproduïda (buida si es va passar per punter sin filename)
        bool loop{false};  // Si el play actual es en bucle
    };

    // --- Mètodes ---
    explicit Audio(const Config& config);                                                   // Constructor privat: rep la config
    void initSDLAudio();                                                                    // Inicialitza SDL Audio
    void playMusicInternal(Ja::Music* music, int loop, int crossfade_ms);                   // Camí comú dels dos overloads de playMusic
    [[nodiscard]] auto effectiveVolume(float volume, bool channel_enabled) const -> float;  // Gate master+channel: 0 si algun está off, clamp 0..1 altrament

    // --- Variables membre ---
    static std::unique_ptr<Audio> instance;  // Instància única d'Audio

    std::unique_ptr<Ja::Engine> engine_;  // Motor de baix nivell (owned); viu mentre Audio viu.
    Config config_{};                     // Configuración injectada (volums, enables)
    Music music_;                         // Estat de la música (nom + loop cachejats)
    bool enabled_{true};                  // Estat general de l'àudio
    bool sound_enabled_{true};            // Estat dels efectes de so
    bool music_enabled_{true};            // Estat de la música
};
