#include "core/resources/resource.hpp"

#include <SDL3/SDL.h>  // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_SetRenderDrawColor, SDL_EventType, SDL_PollEvent, SDL_RenderFillRect, SDL_RenderRect, SDLK_ESCAPE, SDL_Event

#include <array>       // Para array
#include <cstdlib>     // Para exit
#include <exception>   // Para exception
#include <filesystem>  // Para exists, path, remove
#include <fstream>     // Para basic_ofstream, basic_ios, basic_ostream::write, ios, ofstream
#include <iostream>    // Para std::cout
#include <ranges>      // Para __find_if_fn, find_if, __find_fn, find
#include <stdexcept>   // Para runtime_error
#include <utility>     // Para move

#include "core/audio/jail_audio.hpp"           // Para JA_LoadMusic, JA_LoadSound, JA_DeleteMusic, JA_DeleteSound
#include "core/locale/lang.hpp"                // Para getText
#include "core/rendering/screen.hpp"           // Para Screen
#include "core/rendering/text.hpp"             // Para Text
#include "core/resources/asset.hpp"            // Para Asset
#include "core/resources/resource_helper.hpp"  // Para loadFile
#include "game/options.hpp"                    // Para Options::loading
#include "utils/color.hpp"                     // Para Color, NO_COLOR_MOD
#include "utils/defines.hpp"                   // Para Texts::VERSION
#include "utils/param.hpp"                     // Para Param, param, ParamPlayer, ParamResource, ParamGame
#include "utils/utils.hpp"                     // Para getFileName
#include "version.h"                           // Para APP_NAME, GIT_HASH

struct JA_Music_t;  // lines 11-11
struct JA_Sound_t;  // lines 12-12

// Helper para cargar archivos de audio desde pack o filesystem en memoria
namespace {
    struct AudioData {
        std::vector<uint8_t> data;
        std::string filepath;
    };

    auto loadAudioData(const std::string& file_path) -> AudioData {
        auto resource_data = ResourceHelper::loadFile(file_path);
        return AudioData{.data = std::move(resource_data), .filepath = file_path};
    }
}  // namespace

// Declaraciones de funciones que necesitas implementar en otros archivos

// Singleton
Resource* Resource::instance = nullptr;

// Inicializa la instancia única del singleton con modo de carga
void Resource::init(LoadingMode mode) {
    Resource::instance = new Resource(mode);
}

// Libera la instancia
void Resource::destroy() {
    delete Resource::instance;
    Resource::instance = nullptr;
}

// Obtiene la instancia
auto Resource::get() -> Resource* { return Resource::instance; }

// Constructor con modo de carga
Resource::Resource(LoadingMode mode)
    : loading_mode_(mode),
      loading_text_(nullptr) {
    Screen::get()->show();
    if (loading_mode_ == LoadingMode::PRELOAD) {
        loading_text_ = Screen::get()->getText();
        // Ya NO llamamos load() aquí: Director bombea beginLoad() + loadStep()
        // desde iterate() para mantener vivo el bucle SDL3 durante la carga.
    } else {
        // En modo lazy, cargamos lo mínimo indispensable
        initResourceLists();
        loadEssentialResources();
    }
}

// Destructor
Resource::~Resource() {
    clear();
}

// Carga los recursos esenciales que siempre se necesitan (modo lazy)
void Resource::loadEssentialResources() {
    // Cargar recursos de texto básicos que se usan para crear texturas
    loadTextFilesQuiet();     // <- VERSIÓN SILENCIOSA
    loadEssentialTextures();  // Ya es silenciosa
    createText();             // Crear objetos de texto
    createTextTextures();     // Crear texturas generadas (game_text_xxx)
    createPlayerTextures();   // Crea las texturas de jugadores con todas sus variantes de paleta
}

// Carga los ficheros de texto del juego (versión silenciosa)
void Resource::loadTextFilesQuiet() {
    auto list = Asset::get()->getListByType(Asset::Type::FONT);

    for (const auto& l : list) {
        auto name = getFileName(l);
        // Buscar en nuestra lista y cargar directamente
        auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> auto { return t.name == name; });
        if (it != text_files_.end()) {
            it->text_file = Text::loadFile(l);
        }
    }
}

// Carga solo las texturas esenciales (fuentes)
void Resource::loadEssentialTextures() {
    const std::vector<std::string> ESSENTIAL_TEXTURES = {
        "04b_25.png",
        "04b_25_2x.png",
        "04b_25_metal.png",
        "04b_25_grey.png",
        "04b_25_flat.png",
        "04b_25_reversed.png",
        "04b_25_flat_2x.png",
        "04b_25_reversed_2x.png",
        "8bithud.png",
        "aseprite.png",
        "smb2.png",
        "smb2_grad.png"};

    auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);

    for (const auto& file : texture_list) {
        auto name = getFileName(file);
        // Solo cargar texturas esenciales
        if (std::ranges::find(ESSENTIAL_TEXTURES, name) != ESSENTIAL_TEXTURES.end()) {
            // Buscar en nuestra lista y cargar
            auto it = std::ranges::find_if(textures_, [&name](const auto& t) -> auto { return t.name == name; });
            if (it != textures_.end()) {
                it->texture = std::make_shared<Texture>(Screen::get()->getRenderer(), file);
            }
        }
    }
}

// Inicializa las listas de recursos sin cargar el contenido (modo lazy)
void Resource::initResourceLists() {
    // Inicializa lista de sonidos
    auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
    sounds_.clear();
    for (const auto& file : sound_list) {
        sounds_.emplace_back(getFileName(file));
    }

    // Inicializa lista de músicas
    auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
    musics_.clear();
    for (const auto& file : music_list) {
        musics_.emplace_back(getFileName(file));
    }

    // Inicializa lista de texturas
    auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
    textures_.clear();
    for (const auto& file : texture_list) {
        textures_.emplace_back(getFileName(file));
    }

    // Inicializa lista de ficheros de texto
    auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
    text_files_.clear();
    for (const auto& file : text_file_list) {
        text_files_.emplace_back(getFileName(file));
    }

    // Inicializa lista de animaciones
    auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
    animations_.clear();
    for (const auto& file : animation_list) {
        animations_.emplace_back(getFileName(file));
    }

    // Los demos se cargan directamente sin mostrar progreso (son pocos y pequeños)
    loadDemoDataQuiet();

    // Inicializa lista de objetos de texto (sin cargar el contenido)
    const std::vector<std::string> TEXT_OBJECTS = {
        "04b_25",
        "04b_25_2x",
        "04b_25_metal",
        "04b_25_grey",
        "04b_25_flat",
        "04b_25_reversed",
        "04b_25_flat_2x",
        "04b_25_reversed_2x",
        "8bithud",
        "aseprite",
        "smb2",
        "smb2_grad"};

    texts_.clear();
    for (const auto& text_name : TEXT_OBJECTS) {
        texts_.emplace_back(text_name);  // Constructor con nullptr por defecto
    }
}

// Obtiene el sonido a partir de un nombre (con carga perezosa)
auto Resource::getSound(const std::string& name) -> JA_Sound_t* {
    auto it = std::ranges::find_if(sounds_, [&name](const auto& s) -> auto { return s.name == name; });

    if (it != sounds_.end()) {
        // Si está en modo lazy y no se ha cargado aún, lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->sound == nullptr) {
            it->sound = loadSoundLazy(name);
        }
        return it->sound;
    }

    std::cout << "Error: Sonido no encontrado " << name << '\n';
    throw std::runtime_error("Sonido no encontrado: " + name);
}

// Obtiene la música a partir de un nombre (con carga perezosa)
auto Resource::getMusic(const std::string& name) -> JA_Music_t* {
    auto it = std::ranges::find_if(musics_, [&name](const auto& m) -> auto { return m.name == name; });

    if (it != musics_.end()) {
        // Si está en modo lazy y no se ha cargado aún, lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->music == nullptr) {
            it->music = loadMusicLazy(name);
        }
        return it->music;
    }

    std::cout << "Error: Música no encontrada " << name << '\n';
    throw std::runtime_error("Música no encontrada: " + name);
}

// Obtiene la textura a partir de un nombre (con carga perezosa)
auto Resource::getTexture(const std::string& name) -> std::shared_ptr<Texture> {
    auto it = std::ranges::find_if(textures_, [&name](const auto& t) -> auto { return t.name == name; });

    if (it != textures_.end()) {
        // Si está en modo lazy y no se ha cargado aún, lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->texture == nullptr) {
            it->texture = loadTextureLazy(name);
        }
        return it->texture;
    }

    std::cout << "Error: Imagen no encontrada " << name << '\n';
    throw std::runtime_error("Imagen no encontrada: " + name);
}

// Obtiene el fichero de texto a partir de un nombre (con carga perezosa)
auto Resource::getTextFile(const std::string& name) -> std::shared_ptr<Text::File> {
    auto it = std::ranges::find_if(text_files_, [&name](const auto& t) -> auto { return t.name == name; });

    if (it != text_files_.end()) {
        // Si está en modo lazy y no se ha cargado aún, lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->text_file == nullptr) {
            it->text_file = loadTextFileLazy(name);
        }
        return it->text_file;
    }

    std::cout << "Error: TextFile no encontrado " << name << '\n';
    throw std::runtime_error("TextFile no encontrado: " + name);
}

// Obtiene el objeto de texto a partir de un nombre (con carga perezosa)
auto Resource::getText(const std::string& name) -> std::shared_ptr<Text> {
    auto it = std::ranges::find_if(texts_, [&name](const auto& t) -> auto { return t.name == name; });

    if (it != texts_.end()) {
        // Si está en modo lazy y no se ha cargado aún, lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->text == nullptr) {
            it->text = loadTextLazy(name);
        }
        return it->text;
    }

    std::cout << "Error: Text no encontrado " << name << '\n';
    throw std::runtime_error("Text no encontrado: " + name);
}

// Obtiene la animación a partir de un nombre (con carga perezosa)
auto Resource::getAnimation(const std::string& name) -> AnimationsFileBuffer& {
    auto it = std::ranges::find_if(animations_, [&name](const auto& a) -> auto { return a.name == name; });

    if (it != animations_.end()) {
        // Si está en modo lazy y no se ha cargado aún (vector vacío), lo carga ahora
        if (loading_mode_ == LoadingMode::LAZY_LOAD && it->animation.empty()) {
            it->animation = loadAnimationLazy(name);
        }
        return it->animation;
    }

    std::cout << "Error: Animación no encontrada " << name << '\n';
    throw std::runtime_error("Animación no encontrada: " + name);
}

// Obtiene el fichero con los datos para el modo demostración a partir de un índice
auto Resource::getDemoData(int index) -> DemoData& {
    if (index < 0 || std::cmp_greater_equal(index, demos_.size())) {
        std::cout << "Index " << index << " out of range for demo data (size: " << static_cast<int>(demos_.size()) << ")" << '\n';
        static DemoData empty_demo_;
        return empty_demo_;
    }
    return demos_.at(index);
}

// --- Métodos de carga perezosa ---

auto Resource::loadSoundLazy(const std::string& name) -> JA_Sound_t* {
    auto sound_list = Asset::get()->getListByType(Asset::Type::SOUND);
    for (const auto& file : sound_list) {
        if (getFileName(file) == name) {
            auto audio_data = loadAudioData(file);
            if (!audio_data.data.empty()) {
                return JA_LoadSound(audio_data.data.data(), audio_data.data.size());
            }
            // Fallback a cargar desde disco si no está en pack
            return JA_LoadSound(file.c_str());
        }
    }
    return nullptr;
}

auto Resource::loadMusicLazy(const std::string& name) -> JA_Music_t* {
    auto music_list = Asset::get()->getListByType(Asset::Type::MUSIC);
    for (const auto& file : music_list) {
        if (getFileName(file) == name) {
            auto audio_data = loadAudioData(file);
            if (!audio_data.data.empty()) {
                return JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
            }
            // Fallback a cargar desde disco si no está en pack
            return JA_LoadMusic(file.c_str());
        }
    }
    return nullptr;
}

auto Resource::loadTextureLazy(const std::string& name) -> std::shared_ptr<Texture> {
    auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
    for (const auto& file : texture_list) {
        if (getFileName(file) == name) {
            return std::make_shared<Texture>(Screen::get()->getRenderer(), file);
        }
    }
    return nullptr;
}

auto Resource::loadTextFileLazy(const std::string& name) -> std::shared_ptr<Text::File> {
    auto text_file_list = Asset::get()->getListByType(Asset::Type::FONT);
    for (const auto& file : text_file_list) {
        if (getFileName(file) == name) {
            return Text::loadFile(file);
        }
    }
    return nullptr;
}

auto Resource::loadTextLazy(const std::string& name) -> std::shared_ptr<Text> {
    // Mapeo de objetos de texto a sus recursos
    struct TextMapping {
        std::string key;
        std::string texture_file;
        std::string text_file;
    };

    const std::vector<TextMapping> TEXT_MAPPINGS = {
        {.key = "04b_25", .texture_file = "04b_25.png", .text_file = "04b_25.txt"},
        {.key = "04b_25_2x", .texture_file = "04b_25_2x.png", .text_file = "04b_25_2x.txt"},
        {.key = "04b_25_metal", .texture_file = "04b_25_metal.png", .text_file = "04b_25.txt"},
        {.key = "04b_25_grey", .texture_file = "04b_25_grey.png", .text_file = "04b_25.txt"},
        {.key = "04b_25_flat", .texture_file = "04b_25_flat.png", .text_file = "04b_25.txt"},
        {.key = "04b_25_reversed", .texture_file = "04b_25_reversed.png", .text_file = "04b_25.txt"},
        {.key = "04b_25_flat_2x", .texture_file = "04b_25_flat_2x.png", .text_file = "04b_25_2x.txt"},
        {.key = "04b_25_reversed_2x", .texture_file = "04b_25_reversed_2x.png", .text_file = "04b_25_2x.txt"},
        {.key = "8bithud", .texture_file = "8bithud.png", .text_file = "8bithud.txt"},
        {.key = "aseprite", .texture_file = "aseprite.png", .text_file = "aseprite.txt"},
        {.key = "smb2", .texture_file = "smb2.png", .text_file = "smb2.txt"},
        {.key = "smb2_grad", .texture_file = "smb2_grad.png", .text_file = "smb2.txt"}};

    for (const auto& mapping : TEXT_MAPPINGS) {
        if (mapping.key == name) {
            // Cargar las dependencias automáticamente
            auto texture = getTexture(mapping.texture_file);  // Esto cargará la textura si no está cargada
            auto text_file = getTextFile(mapping.text_file);  // Esto cargará el archivo de texto si no está cargado

            if (texture && text_file) {
                return std::make_shared<Text>(texture, text_file);
            }
        }
    }

    return nullptr;
}

auto Resource::loadAnimationLazy(const std::string& name) -> AnimationsFileBuffer {
    auto animation_list = Asset::get()->getListByType(Asset::Type::ANIMATION);
    for (const auto& file : animation_list) {
        if (getFileName(file) == name) {
            return loadAnimationsFromFile(file);
        }
    }
    // Si no se encuentra, retorna vector vacío
    return AnimationsFileBuffer{};
}

// Vacia todos los vectores de recursos
void Resource::clear() {
    clearSounds();
    clearMusics();
    textures_.clear();
    text_files_.clear();
    texts_.clear();
    animations_.clear();
    demos_.clear();
}

// Carga síncrona completa: usado por Resource::reload() (hot-reload en debug).
// En arranque normal la carga la bombea Director::iterate() vía loadStep().
void Resource::load() {
    beginLoad();
    while (!loadStep(INT_MAX)) {
        // Presupuesto infinito: una sola pasada carga todo
    }
    Screen::get()->setVSync(saved_vsync_);
}

// Prepara el estado del cargador incremental. No carga nada todavía.
void Resource::beginLoad() {
    calculateTotalResources();
    initProgressBar();

    saved_vsync_ = Screen::getVSync();
    Screen::get()->setVSync(false);  // Maximiza FPS durante el preload

    stage_ = LoadStage::SOUNDS;
    stage_index_ = 0;
}

auto Resource::isLoadDone() const -> bool {
    return stage_ == LoadStage::DONE;
}

// Bombea la máquina de etapas hasta agotar el presupuesto de tiempo o completar la carga.
// Devuelve true cuando ya no queda nada por cargar.
auto Resource::loadStep(int budget_ms) -> bool {
    if (stage_ == LoadStage::DONE) { return true; }

    const Uint64 start_ns = SDL_GetTicksNS();
    const Uint64 budget_ns = static_cast<Uint64>(budget_ms) * 1'000'000ULL;

    while (stage_ != LoadStage::DONE) {
        switch (stage_) {
            case LoadStage::SOUNDS: {
                auto list = Asset::get()->getListByType(Asset::Type::SOUND);
                if (stage_index_ == 0) { sounds_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::MUSICS;
                    stage_index_ = 0;
                    break;
                }
                loadOneSound(stage_index_++);
                break;
            }
            case LoadStage::MUSICS: {
                auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
                if (stage_index_ == 0) { musics_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::TEXTURES;
                    stage_index_ = 0;
                    break;
                }
                loadOneMusic(stage_index_++);
                break;
            }
            case LoadStage::TEXTURES: {
                auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
                if (stage_index_ == 0) { textures_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::TEXT_FILES;
                    stage_index_ = 0;
                    break;
                }
                loadOneTexture(stage_index_++);
                break;
            }
            case LoadStage::TEXT_FILES: {
                auto list = Asset::get()->getListByType(Asset::Type::FONT);
                if (stage_index_ == 0) { text_files_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::ANIMATIONS;
                    stage_index_ = 0;
                    break;
                }
                loadOneTextFile(stage_index_++);
                break;
            }
            case LoadStage::ANIMATIONS: {
                auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
                if (stage_index_ == 0) { animations_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::DEMO_DATA;
                    stage_index_ = 0;
                    break;
                }
                loadOneAnimation(stage_index_++);
                break;
            }
            case LoadStage::DEMO_DATA: {
                auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
                if (stage_index_ == 0) { demos_.clear(); }
                if (stage_index_ >= list.size()) {
                    stage_ = LoadStage::CREATE_TEXT;
                    stage_index_ = 0;
                    break;
                }
                loadOneDemoData(stage_index_++);
                break;
            }
            case LoadStage::CREATE_TEXT:
                createText();
                stage_ = LoadStage::CREATE_TEXT_TEXTURES;
                break;
            case LoadStage::CREATE_TEXT_TEXTURES:
                createTextTextures();
                stage_ = LoadStage::CREATE_PLAYER_TEXTURES;
                break;
            case LoadStage::CREATE_PLAYER_TEXTURES:
                createPlayerTextures();
                stage_ = LoadStage::DONE;
                break;
            case LoadStage::DONE:
                break;
        }

        if ((SDL_GetTicksNS() - start_ns) >= budget_ns) { break; }
    }

    return stage_ == LoadStage::DONE;
}

// Recarga todos los recursos (limpia y vuelve a cargar)
void Resource::reload() {
    clear();
    if (loading_mode_ == LoadingMode::PRELOAD) {
        load();
    } else {
        initResourceLists();
    }
}

// Carga un sonido concreto desde la lista de assets
void Resource::loadOneSound(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::SOUND);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    auto audio_data = loadAudioData(path);
    JA_Sound_t* sound = nullptr;
    if (!audio_data.data.empty()) {
        sound = JA_LoadSound(audio_data.data.data(), audio_data.data.size());
    } else {
        sound = JA_LoadSound(path.c_str());
    }
    if (sound == nullptr) {
        std::cout << "Sound load failed: " << name << '\n';
    }
    sounds_.emplace_back(name, sound);
}

// Carga una música concreta desde la lista de assets
void Resource::loadOneMusic(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::MUSIC);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    auto audio_data = loadAudioData(path);
    JA_Music_t* music = nullptr;
    if (!audio_data.data.empty()) {
        music = JA_LoadMusic(audio_data.data.data(), audio_data.data.size());
    } else {
        music = JA_LoadMusic(path.c_str());
    }
    if (music == nullptr) {
        std::cout << "Music load failed: " << name << '\n';
    }
    musics_.emplace_back(name, music);
}

// Carga una textura concreta desde la lista de assets
void Resource::loadOneTexture(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::BITMAP);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    textures_.emplace_back(name, std::make_shared<Texture>(Screen::get()->getRenderer(), path));
}

// Carga un fichero de texto concreto desde la lista de assets
void Resource::loadOneTextFile(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::FONT);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    text_files_.emplace_back(name, Text::loadFile(path));
}

// Carga una animación concreta desde la lista de assets
void Resource::loadOneAnimation(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::ANIMATION);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    animations_.emplace_back(name, loadAnimationsFromFile(path));
}

// Carga un fichero de datos de demo concreto desde la lista de assets
void Resource::loadOneDemoData(size_t idx) {
    auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
    const auto& path = list[idx];
    auto name = getFileName(path);
    updateLoadingProgress(name);
    demos_.emplace_back(loadDemoDataFromFile(path));
}

// Crea las texturas de jugadores con todas sus variantes de paleta
void Resource::createPlayerTextures() {
    // Configuración de jugadores y sus paletas
    struct PlayerConfig {
        std::string base_texture;
        std::vector<std::string> palette_files;
        std::string name_prefix;
    };

    std::vector<PlayerConfig> players = {
        {.base_texture = "player1.gif", .palette_files = {"player1_coffee1.pal", "player1_coffee2.pal", "player1_invencible.pal"}, .name_prefix = "player1"},
        {.base_texture = "player2.gif", .palette_files = {"player2_coffee1.pal", "player2_coffee2.pal", "player2_invencible.pal"}, .name_prefix = "player2"}};

    // Bucle principal
    for (size_t player_idx = 0; player_idx < players.size(); ++player_idx) {
        const auto& player = players[player_idx];  // Obtenemos el jugador actual

        // Encontrar el archivo original de la textura
        std::string texture_file_path;
        auto texture_list = Asset::get()->getListByType(Asset::Type::BITMAP);
        for (const auto& file : texture_list) {
            if (getFileName(file) == player.base_texture) {
                texture_file_path = file;
                break;
            }
        }

        // Crear las 4 texturas con sus respectivas paletas
        for (int palette_idx = 0; palette_idx < 4; ++palette_idx) {
            std::shared_ptr<Texture> texture;

            if (palette_idx == 0) {
                // Textura 0 - usar la ya cargada y modificar solo paleta 0 (default_shirt)
                texture = getTexture(player.base_texture);
                texture->setPaletteColor(0, 16, param.player.default_shirt[player_idx].darkest.TO_UINT32());
                texture->setPaletteColor(0, 17, param.player.default_shirt[player_idx].dark.TO_UINT32());
                texture->setPaletteColor(0, 18, param.player.default_shirt[player_idx].base.TO_UINT32());
                texture->setPaletteColor(0, 19, param.player.default_shirt[player_idx].light.TO_UINT32());
                texture->setPaletteColor(0, 56, param.player.outline_color[player_idx].TO_UINT32());
            } else {
                // Crear textura nueva desde archivo usando ResourceHelper
                texture = std::make_shared<Texture>(Screen::get()->getRenderer(), texture_file_path);

                // Añadir todas las paletas
                texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[0]));
                texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[1]));
                texture->addPaletteFromPalFile(Asset::get()->getPath(player.palette_files[2]));

                if (palette_idx == 1) {
                    // Textura 1 - modificar solo paleta 1 (one_coffee_shirt)
                    texture->setPaletteColor(1, 16, param.player.one_coffee_shirt[player_idx].darkest.TO_UINT32());
                    texture->setPaletteColor(1, 17, param.player.one_coffee_shirt[player_idx].dark.TO_UINT32());
                    texture->setPaletteColor(1, 18, param.player.one_coffee_shirt[player_idx].base.TO_UINT32());
                    texture->setPaletteColor(1, 19, param.player.one_coffee_shirt[player_idx].light.TO_UINT32());
                    texture->setPaletteColor(1, 56, param.player.outline_color[player_idx].TO_UINT32());
                } else if (palette_idx == 2) {
                    // Textura 2 - modificar solo paleta 2 (two_coffee_shirt)
                    texture->setPaletteColor(2, 16, param.player.two_coffee_shirt[player_idx].darkest.TO_UINT32());
                    texture->setPaletteColor(2, 17, param.player.two_coffee_shirt[player_idx].dark.TO_UINT32());
                    texture->setPaletteColor(2, 18, param.player.two_coffee_shirt[player_idx].base.TO_UINT32());
                    texture->setPaletteColor(2, 19, param.player.two_coffee_shirt[player_idx].light.TO_UINT32());
                    texture->setPaletteColor(2, 56, param.player.outline_color[player_idx].TO_UINT32());
                }
                // Textura 3 (palette_idx == 3) - no modificar nada, usar colores originales
            }

            // Asignar la paleta correspondiente
            texture->setPalette(palette_idx);

            // Guardar con nombre específico
            std::string texture_name = player.name_prefix + "_pal" + std::to_string(palette_idx);
            textures_.emplace_back(texture_name, texture);
        }
    }
}

// Crea texturas a partir de textos para mostrar puntuaciones y mensajes
void Resource::createTextTextures() {
    struct NameAndText {
        std::string name;
        std::string text;

        NameAndText(std::string name_init, std::string text_init)
            : name(std::move(name_init)),
              text(std::move(text_init)) {}
    };

    // Texturas de tamaño normal con outline
    std::vector<NameAndText> strings1 = {
        {"game_text_1000_points", "1.000"},
        {"game_text_2500_points", "2.500"},
        {"game_text_5000_points", "5.000"},
        {"game_text_powerup", Lang::getText("[GAME_TEXT] 4")},
        {"game_text_one_hit", Lang::getText("[GAME_TEXT] 5")},
        {"game_text_stop", Lang::getText("[GAME_TEXT] 6")},
        {"game_text_1000000_points", Lang::getText("[GAME_TEXT] 8")}};

    auto text1 = getText("04b_25_enhanced");
    for (const auto& s : strings1) {
        textures_.emplace_back(s.name, text1->writeDXToTexture(Text::STROKE, s.text, -2, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
    }

    // Texturas de tamaño doble
    std::vector<NameAndText> strings2 = {
        {"game_text_100000_points", "100.000"},
        {"game_text_get_ready", Lang::getText("[GAME_TEXT] 7")},
        {"game_text_last_stage", Lang::getText("[GAME_TEXT] 3")},
        {"game_text_congratulations", Lang::getText("[GAME_TEXT] 1")},
        {"game_text_new_record", Lang::getText("[GAME_TEXT] NEW_RECORD")},
        {"game_text_game_over", "Game Over"}};

    auto text2 = getText("04b_25_2x_enhanced");
    for (const auto& s : strings2) {
        textures_.emplace_back(s.name, text2->writeDXToTexture(Text::STROKE, s.text, -4, Colors::NO_COLOR_MOD, 1, param.game.item_text_outline_color));
    }
}

// Crea los objetos de texto a partir de los archivos de textura y texto
void Resource::createText() {
    struct ResourceInfo {
        std::string key;
        std::string texture_file;
        std::string text_file;
        std::string white_texture_file;  // Textura blanca opcional

        ResourceInfo(std::string k, std::string t_file, std::string txt_file, std::string w_file = "")
            : key(std::move(k)),
              texture_file(std::move(t_file)),
              text_file(std::move(txt_file)),
              white_texture_file(std::move(w_file)) {}
    };

    std::vector<ResourceInfo> resources = {
        {"04b_25", "04b_25.png", "04b_25.txt"},
        {"04b_25_enhanced", "04b_25.png", "04b_25.txt", "04b_25_white.png"},  // Nueva fuente con textura blanca
        {"04b_25_white", "04b_25_white.png", "04b_25.txt"},
        {"04b_25_2x", "04b_25_2x.png", "04b_25_2x.txt"},
        {"04b_25_2x_enhanced", "04b_25_2x.png", "04b_25_2x.txt", "04b_25_2x_white.png"},  // Nueva fuente con textura blanca
        {"04b_25_metal", "04b_25_metal.png", "04b_25.txt"},
        {"04b_25_grey", "04b_25_grey.png", "04b_25.txt"},
        {"04b_25_flat", "04b_25_flat.png", "04b_25.txt"},
        {"04b_25_reversed", "04b_25_reversed.png", "04b_25.txt"},
        {"04b_25_flat_2x", "04b_25_flat_2x.png", "04b_25_2x.txt"},
        {"04b_25_reversed_2x", "04b_25_reversed_2x.png", "04b_25_2x.txt"},
        {"8bithud", "8bithud.png", "8bithud.txt"},
        {"aseprite", "aseprite.png", "aseprite.txt"},
        {"smb2", "smb2.png", "smb2.txt"},
        {"smb2_grad", "smb2_grad.png", "smb2.txt"}};

    for (const auto& resource : resources) {
        if (!resource.white_texture_file.empty()) {
            // Crear texto con textura blanca
            texts_.emplace_back(resource.key, std::make_shared<Text>(getTexture(resource.texture_file), getTexture(resource.white_texture_file), getTextFile(resource.text_file)));
        } else {
            // Crear texto normal
            texts_.emplace_back(resource.key, std::make_shared<Text>(getTexture(resource.texture_file), getTextFile(resource.text_file)));
        }
    }
}

// Vacía el vector de sonidos y libera la memoria asociada
void Resource::clearSounds() {
    for (auto& sound : sounds_) {
        if (sound.sound != nullptr) {
            JA_DeleteSound(sound.sound);
            sound.sound = nullptr;
        }
    }
    sounds_.clear();
}

// Vacía el vector de músicas y libera la memoria asociada
void Resource::clearMusics() {
    for (auto& music : musics_) {
        if (music.music != nullptr) {
            JA_DeleteMusic(music.music);
            music.music = nullptr;
        }
    }
    musics_.clear();
}

// Calcula el número total de recursos a cargar y reinicia el contador de carga
void Resource::calculateTotalResources() {
    const std::array<Asset::Type, 6> ASSET_TYPES = {
        Asset::Type::SOUND,
        Asset::Type::MUSIC,
        Asset::Type::BITMAP,
        Asset::Type::FONT,
        Asset::Type::ANIMATION,
        Asset::Type::DEMODATA};

    size_t total = 0;
    for (const auto& asset_type : ASSET_TYPES) {
        auto list = Asset::get()->getListByType(asset_type);
        total += list.size();
    }

    loading_count_ = ResourceCount(total);
}

// Muestra el progreso de carga en pantalla (barra y texto)
void Resource::renderProgress() {
    // Obtiene la pantalla y el renderer
    auto* screen = Screen::get();
    auto* renderer = screen->getRenderer();

    // Actualiza la lógica principal de la pantalla (input, etc.)
    screen->coreUpdate();

    // Inicia el frame y limpia la pantalla
    screen->start();
    screen->clean();

    // Si la pantalla de carga está desactivada, dejamos todo en negro.
    // wait_for_input solo tiene efecto cuando la pantalla está visible.
    if (!Options::loading.show) {
        screen->coreRender();
        return;
    }

    // Estamos en la fase de espera explícita al usuario tras terminar la carga
    const bool WAITING_FOR_INPUT = isLoadDone() && Options::loading.wait_for_input;

    auto text_color = param.resource.color;
    auto bar_color = param.resource.color.DARKEN(100);
    const auto TEXT_HEIGHT = loading_text_->getCharacterSize();

    // Dibuja el interior de la barra de progreso
    SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a);
    SDL_RenderFillRect(renderer, &loading_full_rect_);

    // Dibuja el marco de la barra de progreso
    SDL_SetRenderDrawColor(renderer, bar_color.r, bar_color.g, bar_color.b, bar_color.a);
    SDL_RenderRect(renderer, &loading_wired_rect_);

    // Texto centrado sobre la barra: mientras carga, el nombre del recurso;
    // al terminar en modo wait_for_input, el prompt traducido.
    const std::string OVER_BAR_TEXT = WAITING_FOR_INPUT
        ? Lang::getText("[RESOURCE] PRESS_TO_CONTINUE")
        : loading_resource_name_;
    if ((Options::loading.show_resource_name || WAITING_FOR_INPUT) && !OVER_BAR_TEXT.empty()) {
        loading_text_->writeDX(
            Text::CENTER | Text::COLOR,
            loading_wired_rect_.x + (loading_wired_rect_.w / 2),
            loading_wired_rect_.y - TEXT_HEIGHT - 2,
            OVER_BAR_TEXT,
            1,
            text_color);
    }

    // Muestra nombre de la aplicación
    loading_text_->writeDX(
        Text::CENTER | Text::COLOR,
        param.game.game_area.center_x,
        param.game.game_area.center_y - TEXT_HEIGHT,
        spaceBetweenLetters(std::string(Version::APP_NAME)),
        1,
        text_color);

    // Muestra la versión y el hash del commit
    loading_text_->writeDX(
        Text::CENTER | Text::COLOR,
        param.game.game_area.center_x,
        param.game.game_area.center_y + TEXT_HEIGHT,
        "ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")",
        1,
        text_color);

    // Renderiza el frame en pantalla
    screen->coreRender();
}

// Carga los datos para el modo demostración (sin mostrar progreso)
void Resource::loadDemoDataQuiet() {
    auto list = Asset::get()->getListByType(Asset::Type::DEMODATA);
    demos_.clear();

    for (const auto& l : list) {
        demos_.emplace_back(loadDemoDataFromFile(l));
    }
}

// Inicializa los rectangulos que definen la barra de progreso
void Resource::initProgressBar() {
    const float WIRED_BAR_WIDTH = param.game.width * BAR_WIDTH_RATIO;
    const float BAR_X_POSITION = (param.game.width - WIRED_BAR_WIDTH) / 2.0F;
    const float BAR_Y_POSITION = (param.game.height * BAR_Y_RATIO) - (BAR_HEIGHT / 2.0F);

    loading_wired_rect_ = {.x = BAR_X_POSITION, .y = BAR_Y_POSITION, .w = WIRED_BAR_WIDTH, .h = BAR_HEIGHT};

    const float FULL_BAR_WIDTH = WIRED_BAR_WIDTH * loading_count_.getPercentage();
    loading_full_rect_ = {.x = BAR_X_POSITION, .y = BAR_Y_POSITION, .w = FULL_BAR_WIDTH, .h = BAR_HEIGHT};
}

// Actualiza el estado del progreso. No renderiza: el repintado lo hace
// Preload::iterate una vez por frame llamando a renderProgress().
void Resource::updateLoadingProgress(std::string name) {
    loading_resource_name_ = std::move(name);
    loading_count_.increase();
    updateProgressBar();
}

// Actualiza la barra de estado
void Resource::updateProgressBar() {
    loading_full_rect_.w = loading_wired_rect_.w * loading_count_.getPercentage();
}
