#include "director.h"

#include <SDL3/SDL.h>
#include <errno.h>   // for errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h>   // for printf, perror
#include <string.h>  // 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 <fstream>   // for basic_ostream, operator<<, basi...
#include <iostream>  // for cout
#include <memory>
#include <string>  // for basic_string, operator+, char_t...
#include <vector>  // for vector

#include "asset.h"         // for Asset, assetType
#include "const.h"         // for SECTION_PROG_LOGO, GAMECANVAS_H...
#include "game.h"          // for Game
#include "input.h"         // for Input, inputs_e, INPUT_USE_GAME...
#include "intro.h"         // for Intro
#include "jail_audio.hpp"  // for JA_Init
#include "lang.h"          // for Lang, MAX_LANGUAGES, ba_BA, en_UK
#include "resource.h"
#include "resource_helper.h"
#include "logo.h"          // for Logo
#include "mouse.hpp"       // for Mouse::handleEvent, Mouse::upda...
#include "screen.h"        // for FILTER_NEAREST, Screen, FILTER_...
#include "texture.h"       // for Texture
#include "title.h"         // for Title
#include "utils.h"         // for options_t, input_t, boolToString

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

// Constructor
Director::Director(int argc, const char *argv[]) {
    std::cout << "Game start" << std::endl;
    // Inicializa variables
    section = new section_t();
    section->name = SECTION_PROG_LOGO;

    // Inicializa las opciones del programa
    initOptions();

    // Comprueba los parametros del programa
    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

    // 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
        if (!ResourceHelper::initializeResourceSystem("resources.pack", enable_fallback)) {
            std::cerr << "Fatal: resource system init failed (missing resources.pack?)" << std::endl;
            exit(EXIT_FAILURE);
        }
    }

    // Crea el objeto que controla los ficheros de recursos
    asset = new Asset(executablePath);
    asset->setVerbose(options->console);

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

    // Carga el fichero de configuración
    loadConfigFile();

    // Inicializa SDL
    initSDL();

    // Inicializa JailAudio
    initJailAudio();

    // Establece el modo de escalado de texturas
    Texture::setGlobalScaleMode(options->filter == FILTER_NEAREST ? SDL_SCALEMODE_NEAREST : SDL_SCALEMODE_LINEAR);

    // Crea los objetos
    lang = new Lang(asset);
    lang->setLang(options->language);

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

    // Precarga todos los recursos en memoria (texturas, sonidos, música, ...).
    // Vivirán durante toda la vida de la app; las escenas leen handles compartidos.
    // Debe ir antes de crear Screen porque Screen usa Text (propiedad de Resource).
    Resource::init(renderer, asset, input);

    screen = new Screen(window, renderer, asset, options);

    activeSection = ActiveSection::None;
}

Director::~Director() {
    saveConfigFile();

    // 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.
    delete screen;

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

    delete asset;
    delete input;
    delete lang;
    delete options;
    delete section;

    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);

    SDL_Quit();

    ResourceHelper::shutdownResourceSystem();

    std::cout << "\nBye!" << std::endl;
}

// Inicializa el objeto input
void Director::initInput() {
    // Establece si ha de mostrar mensajes
    input->setVerbose(options->console);

    // Busca si hay un mando conectado
    input->discoverGameController();

    // Teclado - Movimiento del jugador
    input->bindKey(input_up, SDL_SCANCODE_UP);
    input->bindKey(input_down, SDL_SCANCODE_DOWN);
    input->bindKey(input_left, SDL_SCANCODE_LEFT);
    input->bindKey(input_right, SDL_SCANCODE_RIGHT);
    input->bindKey(input_fire_left, SDL_SCANCODE_Q);
    input->bindKey(input_fire_center, SDL_SCANCODE_W);
    input->bindKey(input_fire_right, SDL_SCANCODE_E);

    // Teclado - Otros
    input->bindKey(input_accept, SDL_SCANCODE_RETURN);
    input->bindKey(input_cancel, SDL_SCANCODE_ESCAPE);
    input->bindKey(input_pause, SDL_SCANCODE_ESCAPE);
    input->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
    input->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
    input->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
    input->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);

    // Mando - Movimiento del jugador
    input->bindGameControllerButton(input_up, SDL_GAMEPAD_BUTTON_DPAD_UP);
    input->bindGameControllerButton(input_down, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
    input->bindGameControllerButton(input_left, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
    input->bindGameControllerButton(input_right, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);
    input->bindGameControllerButton(input_fire_left, SDL_GAMEPAD_BUTTON_WEST);
    input->bindGameControllerButton(input_fire_center, SDL_GAMEPAD_BUTTON_NORTH);
    input->bindGameControllerButton(input_fire_right, SDL_GAMEPAD_BUTTON_EAST);

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

// Inicializa JailAudio
void Director::initJailAudio() {
    JA_Init(48000, SDL_AUDIO_S16, 2);
}

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

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

        // Crea la ventana
        int incW = 0;
        int incH = 0;
        if (options->borderEnabled) {
            incW = options->borderWidth * 2;
            incH = options->borderHeight * 2;
        }
        window = SDL_CreateWindow(WINDOW_CAPTION, (options->gameWidth + incW) * options->windowSize, (options->gameHeight + incH) * options->windowSize, 0);
        if (window == nullptr) {
            if (options->console) {
                std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
            }
            success = false;
        } else {
            SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);

            // Crea un renderizador para la ventana
            renderer = SDL_CreateRenderer(window, NULL);

            if (renderer == nullptr) {
                if (options->console) {
                    std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
                }
                success = false;
            } else {
                // Activa vsync si es necesario
                if (options->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, options->gameWidth, options->gameHeight, SDL_LOGICAL_PRESENTATION_LETTERBOX);

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

    if (options->console) {
        std::cout << std::endl;
    }
    return success;
}

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

    // Ficheros de configuración
    asset->add(systemFolder + "/config.txt", t_data, false, true);
    asset->add(systemFolder + "/score.bin", t_data, false, true);
    asset->add(prefix + "/data/demo/demo.bin", t_data);

    // Musicas
    asset->add(prefix + "/data/music/intro.ogg", t_music);
    asset->add(prefix + "/data/music/playing.ogg", t_music);
    asset->add(prefix + "/data/music/title.ogg", t_music);

    // Sonidos
    asset->add(prefix + "/data/sound/balloon.wav", t_sound);
    asset->add(prefix + "/data/sound/bubble1.wav", t_sound);
    asset->add(prefix + "/data/sound/bubble2.wav", t_sound);
    asset->add(prefix + "/data/sound/bubble3.wav", t_sound);
    asset->add(prefix + "/data/sound/bubble4.wav", t_sound);
    asset->add(prefix + "/data/sound/bullet.wav", t_sound);
    asset->add(prefix + "/data/sound/coffeeout.wav", t_sound);
    asset->add(prefix + "/data/sound/hiscore.wav", t_sound);
    asset->add(prefix + "/data/sound/itemdrop.wav", t_sound);
    asset->add(prefix + "/data/sound/itempickup.wav", t_sound);
    asset->add(prefix + "/data/sound/menu_move.wav", t_sound);
    asset->add(prefix + "/data/sound/menu_select.wav", t_sound);
    asset->add(prefix + "/data/sound/player_collision.wav", t_sound);
    asset->add(prefix + "/data/sound/stage_change.wav", t_sound);
    asset->add(prefix + "/data/sound/title.wav", t_sound);
    asset->add(prefix + "/data/sound/clock.wav", t_sound);
    asset->add(prefix + "/data/sound/powerball.wav", t_sound);

    // Texturas
    asset->add(prefix + "/data/gfx/balloon1.png", t_bitmap);
    asset->add(prefix + "/data/gfx/balloon1.ani", t_data);
    asset->add(prefix + "/data/gfx/balloon2.png", t_bitmap);
    asset->add(prefix + "/data/gfx/balloon2.ani", t_data);
    asset->add(prefix + "/data/gfx/balloon3.png", t_bitmap);
    asset->add(prefix + "/data/gfx/balloon3.ani", t_data);
    asset->add(prefix + "/data/gfx/balloon4.png", t_bitmap);
    asset->add(prefix + "/data/gfx/balloon4.ani", t_data);
    asset->add(prefix + "/data/gfx/bullet.png", t_bitmap);

    asset->add(prefix + "/data/gfx/game_buildings.png", t_bitmap);
    asset->add(prefix + "/data/gfx/game_clouds.png", t_bitmap);
    asset->add(prefix + "/data/gfx/game_grass.png", t_bitmap);
    asset->add(prefix + "/data/gfx/game_power_meter.png", t_bitmap);
    asset->add(prefix + "/data/gfx/game_sky_colors.png", t_bitmap);
    asset->add(prefix + "/data/gfx/game_text.png", t_bitmap);

    asset->add(prefix + "/data/gfx/intro.png", t_bitmap);
    asset->add(prefix + "/data/gfx/logo.png", t_bitmap);
    asset->add(prefix + "/data/gfx/menu_game_over.png", t_bitmap);
    asset->add(prefix + "/data/gfx/menu_game_over_end.png", t_bitmap);

    asset->add(prefix + "/data/gfx/item_points1_disk.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_points1_disk.ani", t_data);
    asset->add(prefix + "/data/gfx/item_points2_gavina.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_points2_gavina.ani", t_data);
    asset->add(prefix + "/data/gfx/item_points3_pacmar.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_points3_pacmar.ani", t_data);
    asset->add(prefix + "/data/gfx/item_clock.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_clock.ani", t_data);
    asset->add(prefix + "/data/gfx/item_coffee.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_coffee.ani", t_data);
    asset->add(prefix + "/data/gfx/item_coffee_machine.png", t_bitmap);
    asset->add(prefix + "/data/gfx/item_coffee_machine.ani", t_data);

    asset->add(prefix + "/data/gfx/title_bg_tile.png", t_bitmap);
    asset->add(prefix + "/data/gfx/title_coffee.png", t_bitmap);
    asset->add(prefix + "/data/gfx/title_crisis.png", t_bitmap);
    asset->add(prefix + "/data/gfx/title_dust.png", t_bitmap);
    asset->add(prefix + "/data/gfx/title_dust.ani", t_data);
    asset->add(prefix + "/data/gfx/title_gradient.png", t_bitmap);

    asset->add(prefix + "/data/gfx/player_head.ani", t_data);
    asset->add(prefix + "/data/gfx/player_body.ani", t_data);
    asset->add(prefix + "/data/gfx/player_legs.ani", t_data);
    asset->add(prefix + "/data/gfx/player_death.ani", t_data);
    asset->add(prefix + "/data/gfx/player_fire.ani", t_data);

    asset->add(prefix + "/data/gfx/player_bal1_head.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_bal1_body.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_bal1_legs.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_bal1_death.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_bal1_fire.png", t_bitmap);

    asset->add(prefix + "/data/gfx/player_arounder_head.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_arounder_body.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_arounder_legs.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_arounder_death.png", t_bitmap);
    asset->add(prefix + "/data/gfx/player_arounder_fire.png", t_bitmap);

    // Fuentes
    asset->add(prefix + "/data/font/8bithud.png", t_font);
    asset->add(prefix + "/data/font/8bithud.txt", t_font);
    asset->add(prefix + "/data/font/nokia.png", t_font);
    asset->add(prefix + "/data/font/nokia_big2.png", t_font);
    asset->add(prefix + "/data/font/nokia.txt", t_font);
    asset->add(prefix + "/data/font/nokia2.png", t_font);
    asset->add(prefix + "/data/font/nokia2.txt", t_font);
    asset->add(prefix + "/data/font/nokia_big2.txt", t_font);
    asset->add(prefix + "/data/font/smb2_big.png", t_font);
    asset->add(prefix + "/data/font/smb2_big.txt", t_font);
    asset->add(prefix + "/data/font/smb2.png", t_font);
    asset->add(prefix + "/data/font/smb2.txt", t_font);

    // Textos
    asset->add(prefix + "/data/lang/es_ES.txt", t_lang);
    asset->add(prefix + "/data/lang/en_UK.txt", t_lang);
    asset->add(prefix + "/data/lang/ba_BA.txt", t_lang);

    // Menus
    asset->add(prefix + "/data/menu/title.men", t_data);
    asset->add(prefix + "/data/menu/title_gc.men", t_data);
    asset->add(prefix + "/data/menu/options.men", t_data);
    asset->add(prefix + "/data/menu/options_gc.men", t_data);
    asset->add(prefix + "/data/menu/pause.men", t_data);
    asset->add(prefix + "/data/menu/gameover.men", t_data);
    asset->add(prefix + "/data/menu/player_select.men", t_data);

    return asset->check();
}

// Inicializa las opciones del programa
void Director::initOptions() {
    // Crea el puntero a la estructura de opciones
    options = new options_t;

    // Pone unos valores por defecto para las opciones de control
    options->input.clear();

    input_t inp;
    inp.id = 0;
    inp.name = "KEYBOARD";
    inp.deviceType = INPUT_USE_KEYBOARD;
    options->input.push_back(inp);

    inp.id = 0;
    inp.name = "GAME CONTROLLER";
    inp.deviceType = INPUT_USE_GAMECONTROLLER;
    options->input.push_back(inp);

    // Opciones de video
    options->gameWidth = GAMECANVAS_WIDTH;
    options->gameHeight = GAMECANVAS_HEIGHT;
    options->videoMode = 0;
    options->windowSize = 3;
    options->filter = FILTER_NEAREST;
    options->vSync = true;
    options->integerScale = true;
    options->keepAspect = true;
    options->borderWidth = 0;
    options->borderHeight = 0;
    options->borderEnabled = false;

    // Opciones varios
    options->playerSelected = 0;
    options->difficulty = DIFFICULTY_NORMAL;
    options->language = ba_BA;
    options->console = false;

#ifdef __EMSCRIPTEN__
    // En Emscripten la ventana la gestiona el navegador
    options->windowSize = 4;
    options->videoMode = 0;
    options->integerScale = true;
#endif
}

// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, const char *argv[]) {
    // Establece la ruta del programa
    executablePath = argv[0];

    // Comprueba el resto de parametros
    for (int i = 1; i < argc; ++i) {
        if (strcmp(argv[i], "--console") == 0) {
            options->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;
    systemFolder = 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 = {0};
    if (stat(systemFolder.c_str(), &st) == -1) {
        errno = 0;
#ifdef _WIN32
        int ret = mkdir(systemFolder.c_str());
#else
        int ret = mkdir(systemFolder.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
}

// Carga el fichero de configuración
bool Director::loadConfigFile() {
    // Indicador de éxito en la carga
    bool success = true;

    // Variables para manejar el fichero
    const std::string filePath = "config.txt";
    std::string line;
    std::ifstream file(asset->get(filePath));

    // Si el fichero se puede abrir
    if (file.good()) {
        // Procesa el fichero linea a linea
        if (options->console) {
            std::cout << "Reading file " << filePath << std::endl;
        }
        while (std::getline(file, line)) {
            // Comprueba que la linea no sea un comentario
            if (line.substr(0, 1) != "#") {
                // Encuentra la posición del caracter '='
                int pos = line.find("=");
                // Procesa las dos subcadenas
                if (!setOptions(options, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
                    if (options->console) {
                        std::cout << "Warning: file " << filePath << std::endl;
                        std::cout << "Unknown parameter " << line.substr(0, pos).c_str() << std::endl;
                    }
                    success = false;
                }
            }
        }

        // Cierra el fichero
        if (options->console) {
            std::cout << "Closing file " << filePath << std::endl;
        }
        file.close();
    }

    // El fichero no existe
    else {  // Crea el fichero con los valores por defecto
        saveConfigFile();
    }

    // Normaliza los valores
    if (options->videoMode != 0 && options->videoMode != SDL_WINDOW_FULLSCREEN) {
        options->videoMode = 0;
    }

    if (options->windowSize < 1 || options->windowSize > 4) {
        options->windowSize = 3;
    }

    if (options->language < 0 || options->language > MAX_LANGUAGES) {
        options->language = en_UK;
    }

    return success;
}

// Guarda el fichero de configuración
bool Director::saveConfigFile() {
    bool success = true;

    // Crea y abre el fichero de texto
    std::ofstream file(asset->get("config.txt"));

    if (file.good()) {
        if (options->console) {
            std::cout << asset->get("config.txt") << " open for writing" << std::endl;
        }
    } else {
        if (options->console) {
            std::cout << asset->get("config.txt") << " can't be opened" << std::endl;
        }
    }

    // Opciones g´raficas
    file << "## VISUAL OPTIONS\n";
    if (options->videoMode == 0) {
        file << "videoMode=0\n";
    }

    else if (options->videoMode == SDL_WINDOW_FULLSCREEN) {
        file << "videoMode=SDL_WINDOW_FULLSCREEN\n";
    }

    file << "windowSize=" + std::to_string(options->windowSize) + "\n";

    if (options->filter == FILTER_NEAREST) {
        file << "filter=FILTER_NEAREST\n";
    } else {
        file << "filter=FILTER_LINEAL\n";
    }

    file << "vSync=" + boolToString(options->vSync) + "\n";
    file << "integerScale=" + boolToString(options->integerScale) + "\n";
    file << "keepAspect=" + boolToString(options->keepAspect) + "\n";
    file << "borderEnabled=" + boolToString(options->borderEnabled) + "\n";
    file << "borderWidth=" + std::to_string(options->borderWidth) + "\n";
    file << "borderHeight=" + std::to_string(options->borderHeight) + "\n";

    // Otras opciones del programa
    file << "\n## OTHER OPTIONS\n";
    file << "language=" + std::to_string(options->language) + "\n";
    file << "difficulty=" + std::to_string(options->difficulty) + "\n";
    file << "input0=" + std::to_string(options->input[0].deviceType) + "\n";
    file << "input1=" + std::to_string(options->input[1].deviceType) + "\n";

    // Cierra el fichero
    file.close();

    return success;
}

// Gestiona las transiciones entre secciones
void Director::handleSectionTransition() {
    // Determina qué sección debería estar activa
    ActiveSection targetSection = ActiveSection::None;
    switch (section->name) {
        case SECTION_PROG_LOGO:
            targetSection = ActiveSection::Logo;
            break;
        case SECTION_PROG_INTRO:
            targetSection = ActiveSection::Intro;
            break;
        case SECTION_PROG_TITLE:
            targetSection = ActiveSection::Title;
            break;
        case SECTION_PROG_GAME:
            targetSection = ActiveSection::Game;
            break;
    }

    // Si no ha cambiado, no hay nada que hacer
    if (targetSection == activeSection) return;

    // Destruye la sección anterior
    logo.reset();
    intro.reset();
    title.reset();
    game.reset();

    // Crea la nueva sección
    activeSection = targetSection;
    switch (activeSection) {
        case ActiveSection::Logo:
            logo = std::make_unique<Logo>(renderer, screen, asset, input, section);
            break;
        case ActiveSection::Intro:
            intro = std::make_unique<Intro>(renderer, screen, asset, input, lang, section);
            break;
        case ActiveSection::Title:
            title = std::make_unique<Title>(renderer, screen, input, asset, options, lang, section);
            break;
        case ActiveSection::Game: {
            const int numPlayers = section->subsection == SUBSECTION_GAME_PLAY_1P ? 1 : 2;
            game = std::make_unique<Game>(numPlayers, 0, renderer, screen, asset, lang, input, false, options, section);
            break;
        }
        case ActiveSection::None:
            break;
    }
}

// Ejecuta un frame del juego
SDL_AppResult Director::iterate() {
#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->videoMode != 0);

    // Gestiona las transiciones entre secciones
    handleSectionTransition();

    // Ejecuta un frame de la sección activa
    switch (activeSection) {
        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
SDL_AppResult Director::handleEvent(SDL_Event *event) {
#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->handleGamepadAdded(event->gdevice.which, name)) {
            screen->notify(name + " " + lang->getText(94),
                color_t{0x40, 0xFF, 0x40},
                color_t{0, 0, 0},
                2500);
        }
    } else if (event->type == SDL_EVENT_GAMEPAD_REMOVED) {
        std::string name;
        if (input->handleGamepadRemoved(event->gdevice.which, name)) {
            screen->notify(name + " " + lang->getText(95),
                color_t{0xFF, 0x50, 0x50},
                color_t{0, 0, 0},
                2500);
        }
    }

    // Gestiona la visibilidad del cursor según el movimiento del ratón
    Mouse::handleEvent(*event, options->videoMode != 0);

    // Reenvía el evento a la sección activa
    switch (activeSection) {
        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;
}

// Asigna variables a partir de dos cadenas
bool Director::setOptions(options_t *options, std::string var, std::string value) {
    // Indicador de éxito en la asignación
    bool success = true;

    // Opciones de video
    if (var == "videoMode") {
        if (value == "SDL_WINDOW_FULLSCREEN" || value == "SDL_WINDOW_FULLSCREEN_DESKTOP") {
            options->videoMode = SDL_WINDOW_FULLSCREEN;
        } else {
            options->videoMode = 0;
        }
    }

    else if (var == "windowSize") {
        options->windowSize = std::stoi(value);
        if ((options->windowSize < 1) || (options->windowSize > 4)) {
            options->windowSize = 3;
        }
    }

    else if (var == "filter") {
        if (value == "FILTER_LINEAL") {
            options->filter = FILTER_LINEAL;
        } else {
            options->filter = FILTER_NEAREST;
        }
    }

    else if (var == "vSync") {
        options->vSync = stringToBool(value);
    }

    else if (var == "integerScale") {
        options->integerScale = stringToBool(value);
    }

    else if (var == "keepAspect") {
        options->keepAspect = stringToBool(value);
    }

    else if (var == "borderEnabled") {
        options->borderEnabled = stringToBool(value);
    }

    else if (var == "borderWidth") {
        options->borderWidth = std::stoi(value);
    }

    else if (var == "borderHeight") {
        options->borderHeight = std::stoi(value);
    }

    // Opciones varias
    else if (var == "language") {
        options->language = std::stoi(value);
    }

    else if (var == "difficulty") {
        options->difficulty = std::stoi(value);
    }

    else if (var == "input0") {
        options->input[0].deviceType = std::stoi(value);
    }

    else if (var == "input1") {
        options->input[1].deviceType = std::stoi(value);
    }

    // Lineas vacias o que empiezan por comentario
    else if (var == "" || var.substr(0, 1) == "#") {
    }

    else {
        success = false;
    }

    return success;
}