#pragma once

#include <SDL3/SDL.h>

#include <atomic>
#include <cstdint>
#include <memory>

#include "game/scenes/scene.hpp"

// El Director és l'únic thread del runtime. Cada iterate() fa input →
// tick de l'escena actual → Jd8::flip → overlay → present → sleep al frame
// target. Totes les escenes (`Scenes::Scene` i `ModuleGame`) són
// tick-based i no bloquegen — no hi ha fibers, mutex ni condition_variable.
// Compatible amb SDL_AppIterate i amb el futur port a emscripten.
class Director {
   public:
    static void init();
    static void destroy();
    static auto get() -> Director*;

    // Bucle principal clàssic (build natiu sense SDL_MAIN_USE_CALLBACKS).
    // Internament crida setup() + bucle d'iterate() + teardown(). Crida des de main().
    void run();

    // Punts d'entrada compatibles amb SDL_AppInit / SDL_AppIterate /
    // SDL_AppEvent / SDL_AppQuit. Permeten que el Director siga driven
    // per l'event loop de SDL3 en lloc d'un bucle propi — imprescindible
    // per al port a emscripten, on el runtime posseïx el main loop.
    void setup();
    auto iterate() -> bool;  // torna false quan el joc vol eixir
    void teardown();
    void handleEvent(const SDL_Event& event);

    // Demana l'eixida (ex: segona pulsació d'ESC o SDL_QUIT)
    void requestQuit();
    [[nodiscard]] auto isQuitRequested() const -> bool { return quit_requested_; }

    // Demana un reinici "suau": para música i sons, reseteja Info::ctx i
    // torna a l'intro (state 255). Es processa al començament del pròxim
    // iterate() per evitar manipular l'escena des d'una lambda del menú.
    void requestRestart();

    // Consumeix el flag de "tecla polsada" (com l'antic Ji::anyKey)
    auto consumeKeyPressed() -> bool;

    // Indica si ESC està bloquejada (el joc no l'ha de veure)
    [[nodiscard]] auto isEscBlocked() const -> bool { return esc_blocked_ || esc_swallow_until_release_; }

    // Pausa: mentre està activa, iterate() no avança l'escena — es
    // continua presentant el darrer frame amb overlay fresc.
    void togglePause();
    [[nodiscard]] auto isPaused() const -> bool { return paused_; }

    ~Director();

   private:
    Director() = default;

    static std::unique_ptr<Director> instance;

    void pollAllEvents();  // drenatge amb SDL_PollEvent, només per al bucle natiu

    // Inicialitza Info::ctx a partir de Options::game.* i comprova trick.ini.
    // Es crida una sola vegada des d'iterate() a la primera invocació.
    static void initGameContext();
    // Construeix l'escena apropiada segons game_state_ i Info::ctx.
    // Retorna nullptr si l'state actual no té escena registrada (bug).
    [[nodiscard]] auto createNextScene() const -> std::unique_ptr<Scenes::Scene>;
    // Helpers d'iterate() — extrets per reduir complexitat cognitiva.
    void applyRestart();
    static void maybeStartTitleCredits();
    auto tickActiveScene() -> bool;  // true = continuar; false = sortir del loop

    // Helpers d'handleEvent() — cada un retorna true si l'event s'ha consumit.
    auto handleMenuEvent(const SDL_Event& event) -> bool;
    auto handleEscapeEvent(const SDL_Event& event) -> bool;

    // Buffers persistents entre iteracions. Abans eren locals a run(),
    // ara són membres perquè iterate() els pot reutilitzar sense tornar-los
    // a reservar en cada crida del callback.
    Uint32 game_frame_[320 * 200]{};
    Uint32 presentation_buffer_[320 * 200]{};
    bool has_frame_{false};

    // Estat de l'escena actual. Abans vivia al stack del GameFiber; des
    // de la Phase B.2 de la migració viu directament al Director.
    std::unique_ptr<Scenes::Scene> current_scene_;
    int game_state_{1};  // 0 = gameplay (ModuleGame), 1 = via SceneRegistry, -1 = quit
    Uint32 last_tick_ms_{0};
    bool context_initialized_{false};

    std::atomic<bool> quit_requested_{false};
    std::atomic<bool> restart_requested_{false};
    std::atomic<bool> key_pressed_{false};
    std::atomic<bool> esc_blocked_{false};
    std::atomic<bool> paused_{false};
    // Quan el menú tanca amb ESC, empassem-nos l'ESC fins que l'usuari la deixe anar,
    // per no fer eixir el joc al proper poll de Ji::keyPressed.
    std::atomic<bool> esc_swallow_until_release_{false};
    // Tecles consumides pel menú (KEY_DOWN): el KEY_UP associat cal empassar-lo
    // per evitar que el joc (Ji::anyKey / Ji::moveCheats) les veja quan el menú tanca.
    bool menu_keys_held_[SDL_SCANCODE_COUNT]{};
};
