#include "core/system/director.h"

#include <SDL3/SDL.h>

#include <cerrno>   // for errno, EEXIST, EACCES, ENAMETOO...
#include <cstdio>   // for printf, perror
#include <cstring>  // for strcmp
#ifndef __EMSCRIPTEN__
#include <sys/stat.h>  // for mkdir, stat, S_IRWXU
#include <unistd.h>    // for getuid
#endif

#include <cstdlib>  // for exit, EXIT_FAILURE, srand
#include <filesystem>
#include <iostream>  // for cout
#include <memory>
#include <string>  // for basic_string, operator+, char_t...

#include "core/audio/audio.hpp"      // for Audio::init, Audio::destroy
#include "core/input/input.h"        // for Input, InputAction, INPUT_USE_GAME...
#include "core/input/mouse.hpp"      // for Mouse::handleEvent, Mouse::upda...
#include "core/locale/lang.h"        // for Lang, Lang::Code
#include "core/rendering/screen.h"   // for Screen
#include "core/rendering/texture.h"  // for Texture
#include "core/resources/asset.h"    // for Asset, Asset::Type
#include "core/resources/resource.h"
#include "core/resources/resource_helper.h"
#include "game/defaults.hpp"    // for SECTION_PROG_LOGO, GAMECANVAS_H...
#include "game/game.h"          // for Game
#include "game/options.hpp"     // for Options::init, loadFromFile...
#include "game/scenes/intro.h"  // for Intro
#include "game/scenes/logo.h"   // for Logo
#include "game/scenes/title.h"  // for Title
#include "utils/utils.h"        // for InputDevice, boolToString

#if !defined(_WIN32) && !defined(__EMSCRIPTEN__)
#include <pwd.h>
#endif

// Constructor
Director::Director(int argc, const char *argv[]) {
    std::cout << "Game start" << '\n';
    // Inicializa variables
    section_ = new Section();
    section_->name = SECTION_PROG_LOGO;

    // Inicializa las opciones del programa (defaults + dispositivos d'entrada)
    Options::init();

    // Obtén la ruta del directori on viu l'executable (acabada amb '/').
    // SDL_GetBasePath és independent del CWD i evita el `argv[0]` poc fiable.
#ifdef __EMSCRIPTEN__
    // En Emscripten els assets viuen a l'arrel del MEMFS — no hi ha ruta real.
    executablePath = "";
#else
    const char *base_path = SDL_GetBasePath();
    executable_path_ = (base_path != nullptr) ? base_path : "";
#endif

    // Comprueba los parametros del programa (pot activar console)
    checkProgramArguments(argc, argv);

    // Crea la carpeta del sistema donde guardar datos
    createSystemFolder("jailgames");
#ifndef DEBUG
    createSystemFolder("jailgames/coffee_crisis");
#else
    createSystemFolder("jailgames/coffee_crisis_debug");
#endif

    // Estableix el fitxer de configuració i carrega les opcions (o crea el
    // YAML amb defaults si no existeix).
    Options::setConfigFile(system_folder_ + "/config.yaml");
    Options::loadFromFile();

    // Presets de shaders (creats amb defaults si no existeixen).
    Options::setPostFXFile(system_folder_ + "/postfx.yaml");
    Options::loadPostFXFromFile();
    Options::setCrtPiFile(system_folder_ + "/crtpi.yaml");
    Options::loadCrtPiFromFile();

    // Inicializa el sistema de recursos (pack + fallback).
    // En wasm siempre se usa filesystem (MEMFS) porque el propio --preload-file
    // de emscripten ya empaqueta data/ — no hay resources.pack.
    {
#if defined(__EMSCRIPTEN__)
        const bool ENABLE_FALLBACK = true;
#elif defined(RELEASE_BUILD)
        const bool ENABLE_FALLBACK = false;
#else
        const bool ENABLE_FALLBACK = true;
#endif
#ifdef MACOS_BUNDLE
        const std::string PACK_PATH = executablePath + "../Resources/resources.pack";
#else
        const std::string PACK_PATH = executable_path_ + "resources.pack";
#endif
        if (!ResourceHelper::initializeResourceSystem(PACK_PATH, ENABLE_FALLBACK)) {
            std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << '\n';
            exit(EXIT_FAILURE);
        }
    }

    // Crea el objeto que controla los ficheros de recursos
    Asset::init(executable_path_);
    Asset::get()->setVerbose(Options::settings.console);

    // Si falta algún fichero no inicia el programa
    if (!setFileList()) {
        exit(EXIT_FAILURE);
    }

    // Inicializa SDL
    initSDL();

    // Inicializa JailAudio
    initJailAudio();

    // Establece el modo de escalado de texturas
    Texture::setGlobalScaleMode(Options::video.scale_mode);

    // Crea los objetos
    Lang::init();
    Lang::get()->setLang(Options::settings.language);

#ifdef __EMSCRIPTEN__
    Input::init("/gamecontrollerdb.txt");
#else
    {
        const std::string BIN_DIR = std::filesystem::path(executable_path_).parent_path().string();
#ifdef MACOS_BUNDLE
        Input::init(BIN_DIR + "/../Resources/gamecontrollerdb.txt");
#else
        Input::init(BIN_DIR + "/gamecontrollerdb.txt");
#endif
    }
#endif
    initInput();

    // Orden importante: Screen + initShaders ANTES de Resource::init.
    // Si `Resource::init` se ejecuta primero, carga ~100 texturas vía
    // `SDL_CreateTexture` que dejan el SDL_Renderer con el swapchain en un
    // estado que hace crashear al driver Vulkan cuando después `initShaders`
    // intenta reclamar la ventana para el dispositivo SDL3 GPU.
    //
    // Por eso el constructor de Screen NO carga notificationText desde
    // Resource; se enlaza después vía `Screen::get()->initNotifications()`.
    Screen::init(window_, renderer_);

#ifndef NO_SHADERS
    if (Options::video.gpu.acceleration) {
        Screen::get()->initShaders();
    }
#endif

    // Ahora sí, precarga todos los recursos en memoria (texturas, sonidos,
    // música, ...). Vivirán durante toda la vida de la app.
    Resource::init(renderer_);

    // Completa el enlazado de Screen con recursos que necesitan Resource
    // inicializado (actualmente sólo el Text de las notificaciones).
    Screen::get()->initNotifications();

    active_section_ = ActiveSection::NONE;
}

Director::~Director() {
    Options::saveToFile();

    // Libera las secciones primero: sus destructores tocan audio/render SDL
    // (p.ej. Intro::~Intro llama a Ja::deleteMusic) y deben ejecutarse antes
    // de SDL_Quit().
    logo_.reset();
    intro_.reset();
    title_.reset();
    game_.reset();

    // Screen puede tener referencias a Text propiedad de Resource: destruir
    // Screen antes que Resource.
    Screen::destroy();

    // Libera todos los recursos precargados antes de cerrar SDL.
    Resource::destroy();

    Asset::destroy();
    Input::destroy();
    Lang::destroy();
    delete section_;

    Audio::destroy();

    SDL_DestroyRenderer(renderer_);
    SDL_DestroyWindow(window_);

    SDL_Quit();

    ResourceHelper::shutdownResourceSystem();

    std::cout << "\nBye!" << '\n';
}

// Inicializa el objeto input
void Director::initInput() {
    // Establece si ha de mostrar mensajes
    Input::get()->setVerbose(Options::settings.console);

    // Busca si hay un mando conectado
    Input::get()->discoverGameController();

    // Teclado - Movimiento del jugador
    Input::get()->bindKey(UP, SDL_SCANCODE_UP);
    Input::get()->bindKey(DOWN, SDL_SCANCODE_DOWN);
    Input::get()->bindKey(LEFT, SDL_SCANCODE_LEFT);
    Input::get()->bindKey(RIGHT, SDL_SCANCODE_RIGHT);
    Input::get()->bindKey(FIRE_LEFT, SDL_SCANCODE_Q);
    Input::get()->bindKey(FIRE_CENTER, SDL_SCANCODE_W);
    Input::get()->bindKey(FIRE_RIGHT, SDL_SCANCODE_E);

    // Teclado - Otros
    Input::get()->bindKey(ACCEPT, SDL_SCANCODE_RETURN);
    Input::get()->bindKey(CANCEL, SDL_SCANCODE_ESCAPE);
    Input::get()->bindKey(PAUSE, SDL_SCANCODE_ESCAPE);
    Input::get()->bindKey(EXIT, SDL_SCANCODE_ESCAPE);
    Input::get()->bindKey(WINDOW_DEC_ZOOM, SDL_SCANCODE_F1);
    Input::get()->bindKey(WINDOW_INC_ZOOM, SDL_SCANCODE_F2);
    Input::get()->bindKey(WINDOW_FULLSCREEN, SDL_SCANCODE_F3);
    Input::get()->bindKey(TOGGLE_SHADER, SDL_SCANCODE_F4);
    Input::get()->bindKey(TOGGLE_SHADER_TYPE, SDL_SCANCODE_F5);
    Input::get()->bindKey(NEXT_SHADER_PRESET, SDL_SCANCODE_F6);

    // Mando - Movimiento del jugador
    Input::get()->bindGameControllerButton(UP, SDL_GAMEPAD_BUTTON_DPAD_UP);
    Input::get()->bindGameControllerButton(DOWN, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
    Input::get()->bindGameControllerButton(LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
    Input::get()->bindGameControllerButton(RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
    Input::get()->bindGameControllerButton(FIRE_LEFT, SDL_GAMEPAD_BUTTON_WEST);
    Input::get()->bindGameControllerButton(FIRE_CENTER, SDL_GAMEPAD_BUTTON_NORTH);
    Input::get()->bindGameControllerButton(FIRE_RIGHT, SDL_GAMEPAD_BUTTON_EAST);

    // Mando - Otros
    // SOUTH queda sin asignar para evitar salidas accidentales: pausa/cancel se hace con START/BACK.
    Input::get()->bindGameControllerButton(ACCEPT, SDL_GAMEPAD_BUTTON_EAST);
#ifdef GAME_CONSOLE
    Input::get()->bindGameControllerButton(input_pause, SDL_GAMEPAD_BUTTON_BACK);
    Input::get()->bindGameControllerButton(input_exit, SDL_GAMEPAD_BUTTON_START);
#else
    Input::get()->bindGameControllerButton(PAUSE, SDL_GAMEPAD_BUTTON_START);
    Input::get()->bindGameControllerButton(EXIT, SDL_GAMEPAD_BUTTON_BACK);
#endif
}

// Inicializa JailAudio
void Director::initJailAudio() {
    Audio::init();
}

// Arranca SDL y crea la ventana
auto Director::initSDL() -> bool {
    // Indicador de éxito
    bool success = true;

    // Inicializa SDL
    if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD)) {
        if (Options::settings.console) {
            std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << '\n';
        }
        success = false;
    } else {
        // Inicia el generador de numeros aleatorios
        std::srand(static_cast<unsigned int>(SDL_GetTicks()));

        // Crea la ventana
        window_ = SDL_CreateWindow(
            Options::window.caption.c_str(),
            GAMECANVAS_WIDTH * Options::window.zoom,
            GAMECANVAS_HEIGHT * Options::window.zoom,
            0);
        if (window_ == nullptr) {
            if (Options::settings.console) {
                std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << '\n';
            }
            success = false;
        } else {
            SDL_SetWindowPosition(window_, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);

            // Crea un renderizador para la ventana
            renderer_ = SDL_CreateRenderer(window_, nullptr);

            if (renderer_ == nullptr) {
                if (Options::settings.console) {
                    std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << '\n';
                }
                success = false;
            } else {
                // Modo de blending por defecto (consistente con CCAE):
                // permite alpha blending para fades y notificaciones.
                SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);

                // Activa vsync si es necesario
                if (Options::video.vsync) {
                    SDL_SetRenderVSync(renderer_, 1);
                }

                // Inicializa el color de renderizado
                SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);

                // Establece el tamaño del buffer de renderizado
                SDL_SetRenderLogicalPresentation(renderer_, GAMECANVAS_WIDTH, GAMECANVAS_HEIGHT, SDL_LOGICAL_PRESENTATION_LETTERBOX);

                // Establece el modo de mezcla
                SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
            }
        }
    }

    if (Options::settings.console) {
        std::cout << '\n';
    }
    return success;
}

// Crea el indice de ficheros
auto Director::setFileList() -> bool {
#ifdef MACOS_BUNDLE
    const std::string PREFIX = "/../Resources";
#else
    const std::string PREFIX;
#endif

    // Ficheros de configuración
    Asset::get()->add(system_folder_ + "/score.bin", Asset::Type::DATA, false, true);
    Asset::get()->add(PREFIX + "/data/demo/demo.bin", Asset::Type::DATA);

    // Musicas
    Asset::get()->add(PREFIX + "/data/music/intro.ogg", Asset::Type::MUSIC);
    Asset::get()->add(PREFIX + "/data/music/playing.ogg", Asset::Type::MUSIC);
    Asset::get()->add(PREFIX + "/data/music/title.ogg", Asset::Type::MUSIC);

    // Sonidos
    Asset::get()->add(PREFIX + "/data/sound/balloon.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/bubble1.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/bubble2.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/bubble3.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/bubble4.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/bullet.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/coffeeout.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/hiscore.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/itemdrop.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/itempickup.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/menu_move.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/menu_select.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/player_collision.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/stage_change.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/title.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/clock.wav", Asset::Type::SOUND);
    Asset::get()->add(PREFIX + "/data/sound/powerball.wav", Asset::Type::SOUND);

    // Texturas
    Asset::get()->add(PREFIX + "/data/gfx/balloon1.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/balloon1.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/balloon2.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/balloon2.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/balloon3.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/balloon3.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/balloon4.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/balloon4.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/bullet.png", Asset::Type::BITMAP);

    Asset::get()->add(PREFIX + "/data/gfx/game_buildings.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/game_clouds.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/game_grass.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/game_power_meter.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/game_sky_colors.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/game_text.png", Asset::Type::BITMAP);

    Asset::get()->add(PREFIX + "/data/gfx/intro.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/logo.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/menu_game_over.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/menu_game_over_end.png", Asset::Type::BITMAP);

    Asset::get()->add(PREFIX + "/data/gfx/item_points1_disk.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_points1_disk.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/item_points2_gavina.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_points2_gavina.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/item_points3_pacmar.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_points3_pacmar.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/item_clock.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_clock.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/item_coffee.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_coffee.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/item_coffee_machine.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/item_coffee_machine.ani", Asset::Type::DATA);

    Asset::get()->add(PREFIX + "/data/gfx/title_bg_tile.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/title_coffee.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/title_crisis.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/title_dust.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/title_dust.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/title_gradient.png", Asset::Type::BITMAP);

    Asset::get()->add(PREFIX + "/data/gfx/player_head.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/player_body.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/player_legs.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/player_death.ani", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/gfx/player_fire.ani", Asset::Type::DATA);

    Asset::get()->add(PREFIX + "/data/gfx/player_bal1_head.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_bal1_body.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_bal1_legs.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_bal1_death.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_bal1_fire.png", Asset::Type::BITMAP);

    Asset::get()->add(PREFIX + "/data/gfx/player_arounder_head.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_arounder_body.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_arounder_legs.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_arounder_death.png", Asset::Type::BITMAP);
    Asset::get()->add(PREFIX + "/data/gfx/player_arounder_fire.png", Asset::Type::BITMAP);

    // Fuentes
    Asset::get()->add(PREFIX + "/data/font/8bithud.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/8bithud.txt", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia_big2.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia.txt", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia2.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia2.txt", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/nokia_big2.txt", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/smb2_big.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/smb2_big.txt", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/smb2.png", Asset::Type::FONT);
    Asset::get()->add(PREFIX + "/data/font/smb2.txt", Asset::Type::FONT);

    // Textos
    Asset::get()->add(PREFIX + "/data/lang/es_ES.txt", Asset::Type::LANG);
    Asset::get()->add(PREFIX + "/data/lang/en_UK.txt", Asset::Type::LANG);
    Asset::get()->add(PREFIX + "/data/lang/ba_BA.txt", Asset::Type::LANG);

    // Menus
    Asset::get()->add(PREFIX + "/data/menu/title.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/title_gc.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/options.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/options_gc.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/pause.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/gameover.men", Asset::Type::DATA);
    Asset::get()->add(PREFIX + "/data/menu/player_select.men", Asset::Type::DATA);

    return Asset::get()->check();
}

// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, const char *argv[]) {
    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--console") == 0) {
            Options::settings.console = true;
        }
    }
}

// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string &folder) {
#ifdef __EMSCRIPTEN__
    // En Emscripten usamos una carpeta en MEMFS (no persistente)
    systemFolder = "/config/" + folder;
#elif _WIN32
    systemFolder = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
    struct passwd *pw = getpwuid(getuid());
    const char *homedir = pw->pw_dir;
    systemFolder = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
    struct passwd *pw = getpwuid(getuid());
    const char *homedir = pw->pw_dir;
    system_folder_ = std::string(homedir) + "/.config/" + folder;

    {
        // Intenta crear ".config", per si no existeix
        std::string config_base_folder = std::string(homedir) + "/.config";
        int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
        if (ret == -1 && errno != EEXIST) {
            printf("ERROR CREATING CONFIG BASE FOLDER.");
            exit(EXIT_FAILURE);
        }
    }
#endif

#ifdef __EMSCRIPTEN__
    // En Emscripten no necesitamos crear carpetas (MEMFS las crea automáticamente)
    (void)folder;
#else
    struct stat st = {.st_dev = 0};
    if (stat(system_folder_.c_str(), &st) == -1) {
        errno = 0;
#ifdef _WIN32
        int ret = mkdir(systemFolder.c_str());
#else
        int ret = mkdir(system_folder_.c_str(), S_IRWXU);
#endif

        if (ret == -1) {
            switch (errno) {
                case EACCES:
                    printf("the parent directory does not allow write");
                    exit(EXIT_FAILURE);

                case EEXIST:
                    printf("pathname already exists");
                    exit(EXIT_FAILURE);

                case ENAMETOOLONG:
                    printf("pathname is too long");
                    exit(EXIT_FAILURE);

                default:
                    perror("mkdir");
                    exit(EXIT_FAILURE);
            }
        }
    }
#endif
}

// Gestiona las transiciones entre secciones
void Director::handleSectionTransition() {
    // Determina qué sección debería estar activa
    ActiveSection target_section = ActiveSection::NONE;
    switch (section_->name) {
        case SECTION_PROG_LOGO:
            target_section = ActiveSection::LOGO;
            break;
        case SECTION_PROG_INTRO:
            target_section = ActiveSection::INTRO;
            break;
        case SECTION_PROG_TITLE:
            target_section = ActiveSection::TITLE;
            break;
        case SECTION_PROG_GAME:
            target_section = ActiveSection::GAME;
            break;
        default:
            break;
    }

    // Si no ha cambiado, no hay nada que hacer
    if (target_section == active_section_) {
        return;
    }

    // Destruye la sección anterior
    logo_.reset();
    intro_.reset();
    title_.reset();
    game_.reset();

    // Crea la nueva sección
    active_section_ = target_section;
    switch (active_section_) {
        case ActiveSection::LOGO:
            logo_ = std::make_unique<Logo>(renderer_, section_);
            break;
        case ActiveSection::INTRO:
            intro_ = std::make_unique<Intro>(renderer_, section_);
            break;
        case ActiveSection::TITLE:
            title_ = std::make_unique<Title>(renderer_, section_);
            break;
        case ActiveSection::GAME: {
            const int NUM_PLAYERS = section_->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
            game_ = std::make_unique<Game>(NUM_PLAYERS, 0, renderer_, false, section_);
            break;
        }
        case ActiveSection::NONE:
            break;
    }
}

// Ejecuta un frame del juego
auto Director::iterate() -> SDL_AppResult {
#ifdef __EMSCRIPTEN__
    // En WASM no se puede salir: reinicia al logo
    if (section->name == SECTION_PROG_QUIT) {
        section->name = SECTION_PROG_LOGO;
    }
#else
    if (section_->name == SECTION_PROG_QUIT) {
        return SDL_APP_SUCCESS;
    }
#endif

    // Actualiza la visibilidad del cursor del ratón
    Mouse::updateCursorVisibility(Options::video.fullscreen);

    // Gestiona las transiciones entre secciones
    handleSectionTransition();

    // Ejecuta un frame de la sección activa
    switch (active_section_) {
        case ActiveSection::LOGO:
            logo_->iterate();
            break;
        case ActiveSection::INTRO:
            intro_->iterate();
            break;
        case ActiveSection::TITLE:
            title_->iterate();
            break;
        case ActiveSection::GAME:
            game_->iterate();
            break;
        case ActiveSection::NONE:
            break;
    }

    return SDL_APP_CONTINUE;
}

// Procesa un evento
auto Director::handleEvent(SDL_Event *event) -> SDL_AppResult {
#ifndef __EMSCRIPTEN__
    // Evento de salida de la aplicación
    if (event->type == SDL_EVENT_QUIT) {
        section_->name = SECTION_PROG_QUIT;
        return SDL_APP_SUCCESS;
    }
#endif

    // Hot-plug de mandos
    if (event->type == SDL_EVENT_GAMEPAD_ADDED) {
        std::string name;
        if (Input::get()->handleGamepadAdded(event->gdevice.which, name)) {
            Screen::get()->notify(name + " " + Lang::get()->getText(94),
                Color{0x40, 0xFF, 0x40},
                Color{0, 0, 0},
                2500);
        }
    } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
        std::string name;
        if (Input::get()->handleGamepadRemoved(event->gdevice.which, name)) {
            Screen::get()->notify(name + " " + Lang::get()->getText(95),
                Color{0xFF, 0x50, 0x50},
                Color{0, 0, 0},
                2500);
        }
    }

    // Gestiona la visibilidad del cursor según el movimiento del ratón
    Mouse::handleEvent(*event, Options::video.fullscreen);

    // Reenvía el evento a la sección activa
    switch (active_section_) {
        case ActiveSection::LOGO:
            logo_->handleEvent(event);
            break;
        case ActiveSection::INTRO:
            intro_->handleEvent(event);
            break;
        case ActiveSection::TITLE:
            title_->handleEvent(event);
            break;
        case ActiveSection::GAME:
            game_->handleEvent(event);
            break;
        case ActiveSection::NONE:
            break;
    }

    return SDL_APP_CONTINUE;
}
