#include "game/scenes/title.h"

#include <SDL3/SDL.h>
#include <stdlib.h>  // for rand

#include <iostream>  // for basic_ostream, operator<<, basic_ostrea...
#include <string>    // for basic_string, operator+, char_traits

#include "core/audio/audio.hpp"             // for Audio
#include "core/input/global_inputs.hpp"     // for GlobalInputs::handle
#include "core/input/input.h"               // for Input, INPUT_USE_GAMECONTROLLER, INPUT_...
#include "core/locale/lang.h"               // for Lang, ba_BA, en_UK, es_ES
#include "core/rendering/animatedsprite.h"  // for AnimatedSprite
#include "core/rendering/fade.h"            // for Fade
#include "core/rendering/screen.h"          // for Screen
#include "core/rendering/smartsprite.h"     // for SmartSprite
#include "core/rendering/sprite.h"          // for Sprite
#include "core/rendering/text.h"            // for Text, TXT_CENTER, TXT_SHADOW
#include "core/rendering/texture.h"         // for Texture
#include "core/resources/asset.h"           // for Asset
#include "core/resources/resource.h"
#include "game/defaults.hpp"  // for GAMECANVAS_CENTER_X, SECTION_PROG_QUIT
#include "game/game.h"        // for Game
#include "game/options.hpp"   // for Options
#include "game/ui/menu.h"     // for Menu

// Constructor
Title::Title(SDL_Renderer *renderer, section_t *section) {
    // Copia las direcciones de los punteros
    this->renderer = renderer;
    this->section = section;

    // Reserva memoria para los punteros
    eventHandler = new SDL_Event();
    fade = new Fade(renderer);

    Resource *R = Resource::get();
    dustTexture = R->getTexture("title_dust.png");
    coffeeTexture = R->getTexture("title_coffee.png");
    crisisTexture = R->getTexture("title_crisis.png");
    gradientTexture = R->getTexture("title_gradient.png");

    coffeeBitmap = new SmartSprite(coffeeTexture, renderer);
    crisisBitmap = new SmartSprite(crisisTexture, renderer);
    dustBitmapL = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
    dustBitmapR = new AnimatedSprite(dustTexture, renderer, "", &R->getAnimationLines("title_dust.ani"));
    gradient = new Sprite({0, 0, 256, 192}, gradientTexture, renderer);

    text1 = R->getText("smb2");
    text2 = R->getText("8bithud");

#ifdef GAME_CONSOLE
    menu.title = R->getMenu("title_gc");
    menu.options = R->getMenu("options_gc");
#else
    menu.title = R->getMenu("title");
    menu.options = R->getMenu("options");
#endif
    menu.playerSelect = R->getMenu("player_select");

#ifdef __EMSCRIPTEN__
    // En la versión web no se puede cerrar el programa: ocultamos la opción QUIT del menú de título
    menu.title->setVisible(3, false);
    menu.title->setSelectable(3, false);
#endif

    // Sonidos y música (handles compartidos)
    crashSound = R->getSound("title.wav");
    titleMusic = R->getMusic("title.ogg");

    // Inicializa los valores
    init();
}

// Destructor
Title::~Title() {
    delete eventHandler;
    delete fade;

    // Las texturas, Text, Menu, sonido y música son propiedad de Resource —
    // no se liberan aquí. Solo los sprites que posee esta escena.
    delete coffeeBitmap;
    delete crisisBitmap;
    delete dustBitmapL;
    delete dustBitmapR;
    delete gradient;

    SDL_DestroyTexture(background);
}

// Inicializa los valores
void Title::init() {
    // Inicializa variables
    section->subsection = SUBSECTION_TITLE_1;
    counter = TITLE_COUNTER;
    backgroundCounter = 0;
    backgroundMode = rand() % 2;
    menuVisible = false;
    menu.active = menu.title;
    nextSection.name = SECTION_PROG_GAME;
    postFade = 0;
    ticks = 0;
    ticksSpeed = 15;
    fade->init(0x17, 0x17, 0x26);
    demo = true;
    vibrationStep = 0;
    vibrationInitialized = false;
    instructionsActive = false;
    demoGameActive = false;
    demoThenInstructions = false;

    // Pone valores por defecto a las opciones de control
    Options::inputs.clear();

    input_t inp;
    inp.id = 0;
    inp.name = "KEYBOARD";
    inp.deviceType = INPUT_USE_KEYBOARD;
    Options::inputs.push_back(inp);

    inp.id = 0;
    inp.name = "GAME CONTROLLER";
    inp.deviceType = INPUT_USE_GAMECONTROLLER;
    Options::inputs.push_back(inp);

    // Comprueba si hay mandos conectados
    checkInputDevices();

    // Pone valores por defecto. El primer jugador el teclado. El segundo jugador el primer mando
    deviceIndex.clear();
    deviceIndex.push_back(availableInputDevices.size() - 1);  // El último dispositivo encontrado es el teclado
    deviceIndex.push_back(0);                                 // El primer mando encontrado. Si no ha encontrado ninguno es el teclado

    // Si ha encontrado un mando se lo asigna al segundo jugador
    if (Input::get()->gameControllerFound()) {
        Options::inputs[1].id = availableInputDevices[deviceIndex[1]].id;
        Options::inputs[1].name = availableInputDevices[deviceIndex[1]].name;
        Options::inputs[1].deviceType = availableInputDevices[deviceIndex[1]].deviceType;
    } else {  // Si no ha encontrado un mando, deshabilita la opción de jugar a 2 jugadores
        menu.title->setSelectable(1, false);
        menu.title->setGreyed(1, true);
    }

    // Inicializa el bitmap de Coffee
    coffeeBitmap->init();
    coffeeBitmap->setPosX(45);
    coffeeBitmap->setPosY(11 - 200);
    coffeeBitmap->setWidth(167);
    coffeeBitmap->setHeight(46);
    coffeeBitmap->setVelX(0.0f);
    coffeeBitmap->setVelY(2.5f);
    coffeeBitmap->setAccelX(0.0f);
    coffeeBitmap->setAccelY(0.1f);
    coffeeBitmap->setSpriteClip(0, 0, 167, 46);
    coffeeBitmap->setEnabled(true);
    coffeeBitmap->setEnabledCounter(0);
    coffeeBitmap->setDestX(45);
    coffeeBitmap->setDestY(11);

    // Inicializa el bitmap de Crisis
    crisisBitmap->init();
    crisisBitmap->setPosX(60);
    crisisBitmap->setPosY(57 + 200);
    crisisBitmap->setWidth(137);
    crisisBitmap->setHeight(46);
    crisisBitmap->setVelX(0.0f);
    crisisBitmap->setVelY(-2.5f);
    crisisBitmap->setAccelX(0.0f);
    crisisBitmap->setAccelY(-0.1f);
    crisisBitmap->setSpriteClip(0, 0, 137, 46);
    crisisBitmap->setEnabled(true);
    crisisBitmap->setEnabledCounter(0);
    crisisBitmap->setDestX(60);
    crisisBitmap->setDestY(57);

    // Inicializa el bitmap de DustRight
    dustBitmapR->resetAnimation();
    dustBitmapR->setPosX(218);
    dustBitmapR->setPosY(47);
    dustBitmapR->setWidth(16);
    dustBitmapR->setHeight(16);
    dustBitmapR->setFlip(SDL_FLIP_HORIZONTAL);

    // Inicializa el bitmap de DustLeft
    dustBitmapL->resetAnimation();
    dustBitmapL->setPosX(33);
    dustBitmapL->setPosY(47);
    dustBitmapL->setWidth(16);
    dustBitmapL->setHeight(16);

    // Inicializa el sprite con el degradado
    gradient->setSpriteClip(0, 96, 256, 192);

    // Crea el mosaico de fondo del titulo
    createTiledBackground();

    // Coloca la ventana que recorre el mosaico de fondo de manera que coincida con el mosaico que hay pintado en el titulo al iniciar
    backgroundWindow.x = 128;
    backgroundWindow.y = 96;
    backgroundWindow.w = GAMECANVAS_WIDTH;
    backgroundWindow.h = GAMECANVAS_HEIGHT;

    // Inicializa los valores del vector con los valores del seno
    for (int i = 0; i < 360; ++i) {
        sin[i] = SDL_sinf((float)i * 3.14f / 180.0f);
    }

    // Actualiza los textos de los menus
    updateMenuLabels();
}

// Actualiza las variables del objeto
void Title::update() {
    // Actualiza el audio
    Audio::update();

    // Comprueba las entradas
    checkInput();

    // Calcula la lógica de los objetos
    if (SDL_GetTicks() - ticks > ticksSpeed) {
        // Actualiza el contador de ticks
        ticks = SDL_GetTicks();

        switch (section->subsection) {
            // Sección 1 - Titulo desplazandose
            case SUBSECTION_TITLE_1: {
                // Actualiza los objetos
                coffeeBitmap->update();
                crisisBitmap->update();

                // Si los objetos han llegado a su destino, cambiamos de Sección
                if (coffeeBitmap->hasFinished() && crisisBitmap->hasFinished()) {
                    section->subsection = SUBSECTION_TITLE_2;

                    // Pantallazo blanco: pintar sobre el gameCanvas y dejar
                    // que Screen::blit() presente por la ruta activa (GPU o
                    // SDL_Renderer). Un `SDL_RenderPresent(renderer)` directe
                    // crasheja quan el SDL3 GPU ha reclamat la ventana.
                    Screen::get()->start();
                    SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF);
                    SDL_RenderClear(renderer);
                    Screen::get()->blit();

                    // Reproduce el efecto sonoro
                    Audio::get()->playSound(crashSound);
                }
            } break;

            // Sección 2 - Titulo vibrando
            case SUBSECTION_TITLE_2: {
                // Captura las posiciones base y reproduce el sonido la primera vez
                if (!vibrationInitialized) {
                    vibrationCoffeeBaseX = coffeeBitmap->getPosX();
                    vibrationCrisisBaseX = crisisBitmap->getPosX();
                    vibrationInitialized = true;
                }

                // Agita la pantalla
                const int v[] = {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, 0};

                coffeeBitmap->setPosX(vibrationCoffeeBaseX + v[vibrationStep / 3]);
                crisisBitmap->setPosX(vibrationCrisisBaseX + v[vibrationStep / 3]);
                dustBitmapR->update();
                dustBitmapL->update();

                vibrationStep++;

                if (vibrationStep >= 33) {
                    section->subsection = SUBSECTION_TITLE_3;
                    vibrationStep = 0;
                    vibrationInitialized = false;
                }
            } break;

            // Sección 3 - La pantalla de titulo con el menú y la música
            case SUBSECTION_TITLE_3: {
                if (counter > 0) {  // Reproduce la música
                    if (Audio::getRealMusicState() == Audio::MusicState::STOPPED) {
                        Audio::get()->playMusic(titleMusic);
                    }

                    dustBitmapR->update();
                    dustBitmapL->update();

                    // Actualiza la lógica del titulo
                    fade->update();

                    if (fade->hasEnded()) {
                        switch (postFade) {
                            case 0:  // 1 PLAYER
                                section->name = SECTION_PROG_GAME;
                                section->subsection = SUBSECTION_GAME_PLAY_1P;
                                Audio::get()->stopMusic();
                                break;

                            case 1:  // 2 PLAYERS
                                section->name = SECTION_PROG_GAME;
                                section->subsection = SUBSECTION_GAME_PLAY_2P;
                                Audio::get()->stopMusic();
                                break;

                            case 2:  // QUIT
#ifndef __EMSCRIPTEN__
                                section->name = SECTION_PROG_QUIT;
                                Audio::get()->stopMusic();
#endif
                                break;

                            case 3:  // TIME OUT
                                counter = TITLE_COUNTER;
                                menu.active->reset();
                                if (demo) {
                                    demoThenInstructions = true;
                                    runDemoGame();
                                } else
                                    section->name = SECTION_PROG_LOGO;
                                break;

                            default:
                                break;
                        }
                    }

                    // Actualiza el tileado de fondo
                    updateBG();

                    // Comprueba las entradas para el menu
                    if (menuVisible == true && !fade->isEnabled()) {
                        menu.active->update();
                    }

                    // Comprueba si se ha seleccionado algún item del menú de titulo
                    if (menu.active->getName() == "TITLE") {
                        switch (menu.active->getItemSelected()) {
                            case 0:  // 1 PLAYER -> Cambia al manu de selección de jugador
                                menu.active = menu.playerSelect;
                                break;

                            case 1:  // 2 PLAYERS
                                postFade = 1;
                                fade->activateFade();
                                break;

                            case 2:  // OPTIONS
                                menu.active = menu.options;
                                prevVideo = Options::video;
                                prevWindow = Options::window;
                                prevSettings = Options::settings;
                                prevInputs = Options::inputs;
                                break;

                            case 3:  // QUIT
                                postFade = 2;
                                fade->activateFade();
                                break;

                            default:
                                break;
                        }
                    }

                    // Comprueba si se ha seleccionado algún item del menú de selección de jugador
                    if (menu.active->getName() == "PLAYER_SELECT") {
                        switch (menu.active->getItemSelected()) {
                            case 0:
                                // Este item no se puede seleccionar y actua de titulo
                                break;

                            case 1:  // BAL1
                                postFade = 0;
                                Options::settings.player_selected = 0;
                                fade->activateFade();
                                break;

                            case 2:  // AROUNDER
                                postFade = 0;
                                Options::settings.player_selected = 1;
                                fade->activateFade();
                                break;

                            case 3:  // BACK
                                menu.active = menu.title;
                                menu.playerSelect->reset();
                                break;

                            default:
                                break;
                        }
                    }

                    // Comprueba si se ha seleccionado algún item de opciones
                    if (menu.active->getName() == "OPTIONS") {
                        switch (menu.active->getItemSelected()) {
                            case 0:  // Difficulty
                                if (Options::settings.difficulty == DIFFICULTY_EASY)
                                    Options::settings.difficulty = DIFFICULTY_NORMAL;
                                else if (Options::settings.difficulty == DIFFICULTY_NORMAL)
                                    Options::settings.difficulty = DIFFICULTY_HARD;
                                else
                                    Options::settings.difficulty = DIFFICULTY_EASY;
                                updateMenuLabels();
                                break;

                            case 1:  // PLAYER 1 CONTROLS
                                updatePlayerInputs(0);
                                updateMenuLabels();
                                break;

                            case 3:  // PLAYER 2 CONTROLS
                                updatePlayerInputs(1);
                                updateMenuLabels();
                                break;

                            case 5:  // Language
                                Options::settings.language++;
                                if (Options::settings.language == 3)
                                    Options::settings.language = 0;
                                updateMenuLabels();
                                break;

                            case 6:  // Display mode
                                switchFullScreenModeVar();
                                if (Options::video.fullscreen) {
                                    menu.options->setSelectable(8, false);
                                    menu.options->setGreyed(8, true);
                                } else {
                                    menu.options->setSelectable(8, true);
                                    menu.options->setGreyed(8, false);
                                }
                                updateMenuLabels();
                                break;

                            case 8:  // Windows size
                                Options::window.zoom++;
                                if (Options::window.zoom > Options::window.max_zoom)
                                    Options::window.zoom = 1;
                                updateMenuLabels();
                                break;

                            case 9:  // Scale mode
                                Options::video.scale_mode = (Options::video.scale_mode == SDL_SCALEMODE_NEAREST)
                                    ? SDL_SCALEMODE_LINEAR
                                    : SDL_SCALEMODE_NEAREST;
                                Texture::setGlobalScaleMode(Options::video.scale_mode);
                                reLoadTextures();
                                updateMenuLabels();
                                break;

                            case 10:  // VSYNC
                                Options::video.vsync = !Options::video.vsync;
                                updateMenuLabels();
                                break;

                            case 11:  // HOW TO PLAY
                                runInstructions(m_manual);
                                break;

                            case 12:  // ACCEPT
                                applyOptions();
                                menu.active->reset();
                                menu.active = menu.title;
                                break;

                            case 13:  // CANCEL
                                Options::video = prevVideo;
                                Options::window = prevWindow;
                                Options::settings = prevSettings;
                                Options::inputs = prevInputs;
                                updateMenuLabels();
                                menu.active->reset();
                                menu.active = menu.title;
                                break;

                            default:
                                break;
                        }
                    }

                    if (menu.active->getName() == "TITLE") {
                        counter--;
                    }
                } else if (counter == 0) {
                    if (demo) {
                        demoThenInstructions = true;
                        runDemoGame();
                    } else {
                        section->name = SECTION_PROG_LOGO;
                    }
                }

                // Sección Instrucciones
                if (section->subsection == SUBSECTION_TITLE_INSTRUCTIONS) {
                    runInstructions(m_auto);
                }
            }

            break;

            default:
                break;
        }
    }
}

// Dibuja el objeto en pantalla
void Title::render() {
    switch (section->subsection) {
        // Sección 1 - Titulo desplazandose
        case SUBSECTION_TITLE_1: {
            // Prepara para empezar a dibujar en la textura de juego
            Screen::get()->start();

            // Limpia la pantalla
            Screen::get()->clean(bgColor);

            // Dibuja el tileado de fondo
            {
                SDL_FRect fSrc = {(float)backgroundWindow.x, (float)backgroundWindow.y, (float)backgroundWindow.w, (float)backgroundWindow.h};
                SDL_RenderTexture(renderer, background, &fSrc, nullptr);
            };

            // Dibuja el degradado
            gradient->render();

            // Dibuja los objetos
            coffeeBitmap->render();
            crisisBitmap->render();

            // Vuelca el contenido del renderizador en pantalla
            Screen::get()->blit();
        } break;

        // Sección 2 - Titulo vibrando
        case SUBSECTION_TITLE_2: {
            // Prepara para empezar a dibujar en la textura de juego
            Screen::get()->start();

            // Limpia la pantalla
            Screen::get()->clean(bgColor);

            // Dibuja el tileado de fondo
            {
                SDL_FRect fSrc = {(float)backgroundWindow.x, (float)backgroundWindow.y, (float)backgroundWindow.w, (float)backgroundWindow.h};
                SDL_RenderTexture(renderer, background, &fSrc, nullptr);
            };

            // Dibuja el degradado
            gradient->render();

            // Dibuja los objetos (posiciones ya actualizadas por update)
            coffeeBitmap->render();
            crisisBitmap->render();

            dustBitmapR->render();
            dustBitmapL->render();

            // Vuelca el contenido del renderizador en pantalla
            Screen::get()->blit();
        } break;

        // Sección 3 - La pantalla de titulo con el menú y la música
        case SUBSECTION_TITLE_3: {  // Prepara para empezar a dibujar en la textura de juego
            Screen::get()->start();

            // Limpia la pantalla
            Screen::get()->clean(bgColor);

            // Dibuja el tileado de fondo
            {
                SDL_FRect fSrc = {(float)backgroundWindow.x, (float)backgroundWindow.y, (float)backgroundWindow.w, (float)backgroundWindow.h};
                SDL_RenderTexture(renderer, background, &fSrc, nullptr);
            };

            // Dibuja el degradado
            gradient->render();

            // Dibuja los objetos
            if (menu.active->getName() != "OPTIONS") {
                // Bitmaps con el logo/titulo del juego
                coffeeBitmap->render();
                crisisBitmap->render();

                // Texto con el copyright y versión
                text2->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, GAMECANVAS_HEIGHT - (BLOCK * 2), TEXT_COPYRIGHT, 1, noColor, 1, shdwTxtColor);
            }

            if (menuVisible == true) {
                menu.active->render();
            }

            dustBitmapR->render();
            dustBitmapL->render();

            // PRESS ANY KEY!
            if ((counter % 50 > 14) && (menuVisible == false)) {
                text1->writeDX(TXT_CENTER | TXT_SHADOW, GAMECANVAS_CENTER_X, PLAY_AREA_THIRD_QUARTER_Y + BLOCK, Lang::get()->getText(23), 1, noColor, 1, shdwTxtColor);
            }

            // Fade
            fade->render();

            // Vuelca el contenido del renderizador en pantalla
            Screen::get()->blit();
        } break;

        default:
            break;
    }
}

// Comprueba las entradas
void Title::checkInput() {
#ifndef __EMSCRIPTEN__
    if (Input::get()->checkInput(input_exit, REPEAT_FALSE)) {
        section->name = SECTION_PROG_QUIT;
        return;
    }
#endif
    GlobalInputs::handle();
}

// Actualiza el tileado de fondo
void Title::updateBG() {
    if (backgroundMode == 0) {  // El tileado de fondo se desplaza en diagonal
        ++backgroundWindow.x %= 64;
        ++backgroundWindow.y %= 64;
    } else {  // El tileado de fondo se desplaza en circulo
        ++backgroundCounter %= 360;
        backgroundWindow.x = 128 + (int(sin[(backgroundCounter + 270) % 360] * 128));
        backgroundWindow.y = 96 + (int(sin[(360 - backgroundCounter) % 360] * 96));
    }
}

// Cambia el valor de la variable de modo de pantalla completa
void Title::switchFullScreenModeVar() {
    Options::video.fullscreen = !Options::video.fullscreen;
}

// Actualiza los elementos de los menus
void Title::updateMenuLabels() {
    int i = 0;
    // DIFFICULTY
    switch (Options::settings.difficulty) {
        case DIFFICULTY_EASY:
            menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(66));  // EASY
            break;

        case DIFFICULTY_NORMAL:
            menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67));  // NORMAL
            break;

        case DIFFICULTY_HARD:
            menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(68));  // HARD
            break;

        default:
            menu.options->setItemCaption(i, Lang::get()->getText(59) + ": " + Lang::get()->getText(67));  // NORMAL
            break;
    }

    i++;
    // PLAYER 1 CONTROLS
    menu.options->setItemCaption(i, Lang::get()->getText(62));

    i++;
    // PLAYER 1 CONTROLS - OPTIONS
    switch (Options::inputs[0].deviceType) {
        case INPUT_USE_KEYBOARD:
            menu.options->setItemCaption(i, Lang::get()->getText(69));  // KEYBOARD
            menu.options->setGreyed(i, false);
            break;

        case INPUT_USE_GAMECONTROLLER:
            menu.options->setItemCaption(i, Lang::get()->getText(70));  // GAME CONTROLLER
            if (!Input::get()->gameControllerFound())
                menu.options->setGreyed(i, true);
            else {
                menu.options->setGreyed(i, false);
                menu.options->setItemCaption(i, Options::inputs[0].name);
            }
            break;

        default:
            menu.options->setItemCaption(i, Lang::get()->getText(69));  // KEYBOARD
            break;
    }

    i++;
    // PLAYER 2 CONTROLS
    menu.options->setItemCaption(i, Lang::get()->getText(63));

    i++;
    // PLAYER 2 CONTROLS - OPTIONS
    switch (Options::inputs[1].deviceType) {
        case INPUT_USE_KEYBOARD:
            menu.options->setItemCaption(i, Lang::get()->getText(69));  // KEYBOARD
            menu.options->setGreyed(i, false);
            break;

        case INPUT_USE_GAMECONTROLLER:
            menu.options->setItemCaption(i, Lang::get()->getText(70));  // GAME CONTROLLER
            if (!Input::get()->gameControllerFound())
                menu.options->setGreyed(i, true);
            else {
                menu.options->setGreyed(i, false);
                menu.options->setItemCaption(i, Options::inputs[1].name);
            }
            break;

        default:
            menu.options->setItemCaption(i, Lang::get()->getText(69));  // KEYBOARD
            break;
    }

    i++;
    // LANGUAGE
    switch (Options::settings.language) {
        case es_ES:
            menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(24));  // SPANISH
            break;

        case ba_BA:
            menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(25));  // VALENCIAN
            break;

        case en_UK:
            menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(26));  // ENGLISH
            break;

        default:
            menu.options->setItemCaption(i, Lang::get()->getText(8) + ": " + Lang::get()->getText(26));  // ENGLISH
            break;
    }

    i++;
    // DISPLAY MODE
    menu.options->setItemCaption(i, Lang::get()->getText(58));

    i++;
    // DISPLAY MODE - OPTIONS
    menu.options->setItemCaption(i, Options::video.fullscreen ? Lang::get()->getText(5) : Lang::get()->getText(4));

    i++;
    // WINDOW SIZE
    menu.options->setItemCaption(i, Lang::get()->getText(7) + " x" + std::to_string(Options::window.zoom));  // WINDOW SIZE

    i++;
    // SCALE MODE
    if (Options::video.scale_mode == SDL_SCALEMODE_LINEAR)
        menu.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(71));  // BILINEAL
    else
        menu.options->setItemCaption(i, Lang::get()->getText(60) + ": " + Lang::get()->getText(72));  // LINEAL

    i++;
    // VSYNC
    if (Options::video.vsync)
        menu.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(73));  // ON
    else
        menu.options->setItemCaption(i, Lang::get()->getText(61) + ": " + Lang::get()->getText(74));  // OFF

    i++;
    // HOW TO PLAY
    menu.options->setItemCaption(i, Lang::get()->getText(2));

    i++;
    // ACCEPT
    menu.options->setItemCaption(i, Lang::get()->getText(9));  // ACCEPT

    i++;
    // CANCEL
    menu.options->setItemCaption(i, Lang::get()->getText(10));  // CANCEL

    // Recoloca el menu de opciones
    menu.options->centerMenuOnX(GAMECANVAS_CENTER_X);
    menu.options->centerMenuOnY(GAMECANVAS_CENTER_Y);
    menu.options->centerMenuElementsOnX();

// Establece las etiquetas del menu de titulo
#ifdef GAME_CONSOLE
    menu.title->setItemCaption(0, Lang::get()->getText(0));  // PLAY
#else
    menu.title->setItemCaption(0, Lang::get()->getText(51));  // 1 PLAYER
#endif
    menu.title->setItemCaption(1, Lang::get()->getText(52));  // 2 PLAYERS
    menu.title->setItemCaption(2, Lang::get()->getText(1));   // OPTIONS
    menu.title->setItemCaption(3, Lang::get()->getText(3));   // QUIT
#ifdef __EMSCRIPTEN__
    menu.title->setVisible(3, false);
    menu.title->setSelectable(3, false);
#endif

    // Recoloca el menu de titulo
    menu.title->centerMenuOnX(GAMECANVAS_CENTER_X);
    menu.title->centerMenuElementsOnX();

    // Establece las etiquetas del menu de seleccion de jugador
    menu.playerSelect->setItemCaption(0, Lang::get()->getText(39));  // SELECT PLAYER
    menu.playerSelect->setItemCaption(3, Lang::get()->getText(40));  // BACK

    // Recoloca el menu de selección de jugador
    menu.playerSelect->centerMenuOnX(GAMECANVAS_CENTER_X);
    menu.playerSelect->centerMenuElementsOnX();

#ifdef GAME_CONSOLE
    menu.options->setGreyed(1, true);
    menu.options->setSelectable(1, false);
    menu.options->setGreyed(2, true);
    menu.options->setSelectable(2, false);
    menu.options->setGreyed(3, true);
    menu.options->setSelectable(3, false);
    menu.options->setGreyed(4, true);
    menu.options->setSelectable(4, false);
#endif
}

// Aplica las opciones de menu seleccionadas
void Title::applyOptions() {
    Screen::get()->setVideoMode(Options::video.fullscreen);
    Screen::get()->setWindowZoom(Options::window.zoom);
    Screen::get()->setVSync(Options::video.vsync);

    Lang::get()->setLang(Options::settings.language);

    updateMenuLabels();
    createTiledBackground();
}

// Ejecuta un frame
void Title::iterate() {
    // Si las instrucciones están activas, delega el frame
    if (instructionsActive) {
        instructions->update();
        instructions->render();

        if (instructions->hasFinished()) {
            bool wasQuit = instructions->isQuitRequested();
            delete instructions;
            instructions = nullptr;
            instructionsActive = false;

            if (wasQuit) {
                section->name = SECTION_PROG_QUIT;
            } else if (instructionsMode == m_auto) {
                section->name = SECTION_PROG_TITLE;
                init();
                demo = true;
            } else {
                section->name = SECTION_PROG_TITLE;
                section->subsection = SUBSECTION_TITLE_3;
            }
        }
        return;
    }

    // Si el juego demo está activo, delega el frame
    if (demoGameActive) {
        // El demo Game necesita section->name == SECTION_PROG_GAME para funcionar
        section->name = SECTION_PROG_GAME;
        demoGame->iterate();

        if (demoGame->hasFinished()) {
            // cppcheck-suppress knownConditionTrueFalse
            const bool wasQuit = (section->name == SECTION_PROG_QUIT);
            delete demoGame;
            demoGame = nullptr;
            demoGameActive = false;

            // cppcheck-suppress knownConditionTrueFalse
            if (wasQuit) {
                section->name = SECTION_PROG_QUIT;
            } else if (demoThenInstructions) {
                section->name = SECTION_PROG_TITLE;
                section->subsection = SUBSECTION_TITLE_3;
                demoThenInstructions = false;
                runInstructions(m_auto);
            } else {
                section->name = SECTION_PROG_TITLE;
                section->subsection = SUBSECTION_TITLE_1;
            }
        } else {
            // Restaura section para que Director no transicione fuera de Title
            section->name = SECTION_PROG_TITLE;
        }
        return;
    }

    // Ejecución normal del título
    update();
    render();
}

// Procesa un evento individual
void Title::handleEvent(const SDL_Event *event) {
    // Si hay un sub-estado activo, delega el evento
    if (instructionsActive && instructions) {
        // SDL_EVENT_QUIT ya lo maneja Director
        return;
    }

    if (demoGameActive && demoGame) {
        demoGame->handleEvent(event);
        return;
    }

    // SDL_EVENT_QUIT ya lo maneja Director

    if (event->type == SDL_EVENT_RENDER_DEVICE_RESET || event->type == SDL_EVENT_RENDER_TARGETS_RESET) {
        reLoadTextures();
    }

    if (section->subsection == SUBSECTION_TITLE_3) {
        if ((event->type == SDL_EVENT_KEY_UP) || (event->type == SDL_EVENT_JOYSTICK_BUTTON_UP)) {
            menuVisible = true;
            counter = TITLE_COUNTER;
        }
    }
}

// Bucle para el titulo del juego (compatibilidad)
void Title::run() {
    while (section->name == SECTION_PROG_TITLE || instructionsActive || demoGameActive) {
        iterate();
    }
}

// Inicia la parte donde se muestran las instrucciones
void Title::runInstructions(mode_e mode) {
    instructions = new Instructions(renderer, section);
    instructions->start(mode);
    instructionsActive = true;
    instructionsMode = mode;
}

// Inicia el juego en modo demo
void Title::runDemoGame() {
    // Temporalmente ponemos section para que el constructor de Game funcione
    section->name = SECTION_PROG_GAME;
    section->subsection = SUBSECTION_GAME_PLAY_1P;
    demoGame = new Game(1, 0, renderer, true, section);
    demoGameActive = true;
    // Restauramos section para que Director no transicione fuera de Title
    section->name = SECTION_PROG_TITLE;
}

// Modifica las opciones para los controles de los jugadores
bool Title::updatePlayerInputs(int numPlayer) {
    const int numDevices = availableInputDevices.size();

    if (!Input::get()->gameControllerFound()) {  // Si no hay mandos se deja todo de manera prefijada
        deviceIndex[0] = 0;
        deviceIndex[1] = 0;

        Options::inputs[0].id = -1;
        Options::inputs[0].name = "KEYBOARD";
        Options::inputs[0].deviceType = INPUT_USE_KEYBOARD;

        Options::inputs[1].id = 0;
        Options::inputs[1].name = "GAME CONTROLLER";
        Options::inputs[1].deviceType = INPUT_USE_GAMECONTROLLER;

        return true;
    } else {  // Si hay mas de un dispositivo, se recorre el vector
        if (Options::settings.console) {
            std::cout << "numplayer:" << numPlayer << std::endl;
            std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl;
        }

        // Incrementa el indice
        if (deviceIndex[numPlayer] < numDevices - 1) {
            deviceIndex[numPlayer]++;
        } else {
            deviceIndex[numPlayer] = 0;
        }
        if (Options::settings.console) {
            std::cout << "deviceindex:" << deviceIndex[numPlayer] << std::endl;
        }

        // Si coincide con el del otro jugador, se lo intercambian
        if (deviceIndex[0] == deviceIndex[1]) {
            const int theOtherPlayer = (numPlayer + 1) % 2;
            deviceIndex[theOtherPlayer]--;
            if (deviceIndex[theOtherPlayer] < 0) {
                deviceIndex[theOtherPlayer] = numDevices - 1;
            }
        }

        // Copia el dispositivo marcado por el indice a la variable de opciones de cada jugador
        Options::inputs[0] = availableInputDevices[deviceIndex[0]];
        Options::inputs[1] = availableInputDevices[deviceIndex[1]];

        return true;
    }
}

// Crea el mosaico de fondo del titulo
void Title::createTiledBackground() {
    // Crea la textura para el mosaico de fondo
    background = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, GAMECANVAS_WIDTH * 2, GAMECANVAS_HEIGHT * 2);
    if (background != nullptr) {
        SDL_SetTextureScaleMode(background, Texture::currentScaleMode);
    }
    if (background == nullptr) {
        if (Options::settings.console) {
            std::cout << "TitleSurface could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
        }
    }

    // Crea los objetos para pintar en la textura de fondo
    Texture *bgTileTexture = new Texture(renderer, Asset::get()->get("title_bg_tile.png"));
    Sprite *tile = new Sprite({0, 0, 64, 64}, bgTileTexture, renderer);

    // Prepara para dibujar sobre la textura
    SDL_SetRenderTarget(renderer, background);
    SDL_SetRenderDrawColor(renderer, 0x43, 0x43, 0x4F, 0xFF);
    SDL_RenderClear(renderer);

    // Rellena la textura con el tile
    tile->setSpriteClip(0, 0, 64, 64);
    for (int i = 0; i < 8; ++i) {
        for (int j = 0; j < 6; ++j) {
            tile->setPosX(i * 64);
            tile->setPosY(j * 64);
            tile->render();
        }
    }

    // Vuelve a colocar el renderizador apuntando a la pantalla
    SDL_SetRenderTarget(renderer, nullptr);

    // Libera la memoria utilizada por los objetos
    bgTileTexture->unload();
    delete bgTileTexture;
    delete tile;
}

// Comprueba cuantos mandos hay conectados para gestionar el menu de opciones.
// El estado de Input lo mantiene al día Director via eventos SDL_EVENT_GAMEPAD_ADDED/REMOVED,
// así que aquí solo leemos la lista actual sin reescanear.
void Title::checkInputDevices() {
    if (Options::settings.console) {
        std::cout << "Filling devices for options menu..." << std::endl;
    }
    const int numControllers = Input::get()->getNumControllers();
    availableInputDevices.clear();
    input_t temp;

    // Añade todos los mandos
    if (numControllers > 0)
        for (int i = 0; i < numControllers; ++i) {
            temp.id = i;
            temp.name = Input::get()->getControllerName(i);
            temp.deviceType = INPUT_USE_GAMECONTROLLER;
            availableInputDevices.push_back(temp);
            if (Options::settings.console) {
                std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl;
            }
        }

    // Añade el teclado al final
    temp.id = -1;
    temp.name = "KEYBOARD";
    temp.deviceType = INPUT_USE_KEYBOARD;
    availableInputDevices.push_back(temp);
    if (Options::settings.console) {
        std::cout << "Device " << (int)availableInputDevices.size() << " - " << temp.name.c_str() << std::endl;
        std::cout << std::endl;
    }
}

// Recarga las texturas
void Title::reLoadTextures() {
    dustTexture->reLoad();
    coffeeTexture->reLoad();
    crisisTexture->reLoad();
    gradientTexture->reLoad();
    createTiledBackground();
}