#include "game/ui/menu.h"

#include <algorithm>  // for max, min
#include <fstream>    // for char_traits, basic_ifstream, basic_istream
#include <numeric>    // for accumulate
#include <sstream>    // for basic_stringstream

#include "core/audio/audio.hpp"       // for Audio::get (playSound)
#include "core/audio/jail_audio.hpp"  // for Ja::loadSound, Ja::deleteSound (propietat local)
#include "core/input/input.h"         // for Input, REPEAT_FALSE, InputAction
#include "core/rendering/text.h"      // for Text
#include "core/resources/asset.h"     // for Asset
#include "core/resources/resource_helper.h"

// Constructor
Menu::Menu(SDL_Renderer *renderer, const std::string &file)
    : color_greyed_{128, 128, 128} {
    // Copia punteros
    this->renderer_ = renderer;

    // Inicializa punteros
    sound_move_ = nullptr;
    sound_accept_ = nullptr;
    sound_cancel_ = nullptr;

    // Inicializa variables
    selector_.index = 0;
    selector_.previous_index = 0;
    item_selected_ = MENU_NO_OPTION;
    x_ = 0;
    y_ = 0;
    w_ = 0;
    rect_bg_.rect = {.x = 0, .y = 0, .w = 0, .h = 0};
    rect_bg_.color = {0, 0, 0};
    rect_bg_.a = 0;
    background_type_ = MENU_BACKGROUND_SOLID;
    is_centered_on_x_ = false;
    is_centered_on_y_ = false;
    are_elements_centered_on_x_ = false;
    center_x_ = 0;
    center_y_ = 0;
    widest_item_ = 0;
    default_action_when_cancel_ = 0;

    // Selector
    selector_.origin_y = 0;
    selector_.target_y = 0;
    selector_.desp_y = 0;
    selector_.origin_h = 0;
    selector_.target_h = 0;
    selector_.inc_h = 0;
    selector_.y = 0.0F;
    selector_.h = 0.0F;
    selector_.num_jumps = 8;
    selector_.moving = false;
    selector_.resizing = false;
    selector_.rect = {.x = 0, .y = 0, .w = 0, .h = 0};
    selector_.color = {0, 0, 0};
    selector_.item_color = {0, 0, 0};
    selector_.a = 255;

    // Inicializa las variables desde un fichero. Si no se pasa fichero, el
    // llamante (p.ej. Resource::preloadAll) usará loadFromBytes después —
    // y ese método ya llama a setSelectorItemColors() y reset() al final.
    if (!file.empty()) {
        load(file);
        setSelectorItemColors();
        reset();
    }
}

Menu::~Menu() {
    renderer_ = nullptr;

    if (sound_move_ != nullptr) {
        Ja::deleteSound(sound_move_);
    }

    if (sound_accept_ != nullptr) {
        Ja::deleteSound(sound_accept_);
    }

    if (sound_cancel_ != nullptr) {
        Ja::deleteSound(sound_cancel_);
    }

    delete text_;
}

// Parser compartido (recibe cualquier istream)
auto Menu::parseFromStream(std::istream &file, const std::string &filename) -> bool {
    bool success = true;
    bool textAllocated = false;
    std::string line;
    (void)filename;

    // Normalitza CRLF: en Windows els .men es llegeixen en binari (resource
    // pack o ifstream sense flag de text) i std::getline només talla pel \n,
    // deixant un \r residual. Sense això, "[item]" no fa match i el vector
    // d'items queda buit → reset() accedeix a item[0] i crasheja.
    auto strip_cr = [](std::string &s) {
        if (!s.empty() && s.back() == '\r') {
            s.pop_back();
        }
    };

    while (std::getline(file, line)) {
        strip_cr(line);
        if (line == "[item]") {
            Item newItem;
            newItem.label = "";
            newItem.h_padding_down = 1;
            newItem.selectable = true;
            newItem.greyed = false;
            newItem.linked_down = false;
            newItem.visible = true;
            newItem.line = false;

            do {
                if (!std::getline(file, line)) {
                    break;
                }
                strip_cr(line);
                int pos = line.find('=');
                if (!setItem(&newItem, line.substr(0, pos), line.substr(pos + 1, line.length()))) {
                    success = false;
                }
            } while (line != "[/item]");

            addItem(newItem);
        } else {
            int pos = line.find('=');
            if (!setVars(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
                success = false;
            }

            // Crea el objeto text tan pronto como se pueda. Necesario para añadir items.
            // Carga via ResourceHelper para que funcione tanto con pack como con filesystem.
            if (!font_png_.empty() && !font_txt_.empty() && !textAllocated) {
                auto pngBytes = ResourceHelper::loadFile(Asset::get()->get(font_png_));
                auto txtBytes = ResourceHelper::loadFile(Asset::get()->get(font_txt_));
                text_ = new Text(pngBytes, txtBytes, renderer_);
                textAllocated = true;
            }
        }
    }
    return success;
}

// Carga la configuración del menu (vía ResourceHelper: pack si està inicialitzat, filesystem si no)
auto Menu::load(const std::string &file_path) -> bool {
    const std::string filename = file_path.substr(file_path.find_last_of("\\/") + 1);
    auto bytes = ResourceHelper::loadFile(file_path);
    if (bytes.empty()) {
        return false;
    }
    return loadFromBytes(bytes, filename);
}

// Carga el menu desde bytes en memoria
auto Menu::loadFromBytes(const std::vector<uint8_t> &bytes, const std::string &nameForLogs) -> bool {
    if (bytes.empty()) {
        return false;
    }
    std::string content(reinterpret_cast<const char *>(bytes.data()), bytes.size());
    std::stringstream ss(content);
    bool ok = parseFromStream(ss, nameForLogs);
    setSelectorItemColors();
    reset();
    return ok;
}

// Asigna variables a partir de dos cadenas
auto Menu::setItem(Item *item, const std::string &var, const std::string &value) -> bool {
    // Indicador de éxito en la asignación
    bool success = true;

    if (var == "text") {
        item->label = value;
    }

    else if (var == "hPaddingDown") {
        item->h_padding_down = std::stoi(value);
    }

    else if (var == "selectable") {
        item->selectable = value == "true";
    }

    else if (var == "greyed") {
        item->greyed = value == "true";
    }

    else if (var == "linkedDown") {
        item->linked_down = value == "true";
    }

    else if (var == "visible") {
        item->visible = value == "true";
    }

    else if (var == "line") {
        item->line = value == "true";
    }

    else if ((var.empty()) || (var == "[/item]")) {
    }

    else {
        success = false;
    }

    return success;
}

// Asigna variables a partir de dos cadenas
auto Menu::setVars(const std::string &var, const std::string &value) -> bool {
    // Indicador de éxito en la asignación
    bool success = true;

    if (var == "font_png") {
        font_png_ = value;
    }

    else if (var == "font_txt") {
        font_txt_ = value;
    }

    else if (var == "sound_cancel") {
        auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
        if (!bytes.empty()) {
            sound_cancel_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size());
        }
    }

    else if (var == "sound_accept") {
        auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
        if (!bytes.empty()) {
            sound_accept_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size());
        }
    }

    else if (var == "sound_move") {
        auto bytes = ResourceHelper::loadFile(Asset::get()->get(value));
        if (!bytes.empty()) {
            sound_move_ = Ja::loadSound(bytes.data(), (uint32_t)bytes.size());
        }
    }

    else if (var == "name") {
        name_ = value;
    }

    else if (var == "x") {
        x_ = std::stoi(value);
    }

    else if (var == "centerX") {
        center_x_ = std::stoi(value);
    }

    else if (var == "centerY") {
        center_y_ = std::stoi(value);
    }

    else if (var == "y") {
        y_ = std::stoi(value);
    }

    else if (var == "backgroundType") {
        background_type_ = std::stoi(value);
    }

    else if (var == "backgroundColor") {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        getline(ss, tmp, ',');
        rect_bg_.color.r = std::stoi(tmp);
        getline(ss, tmp, ',');
        rect_bg_.color.g = std::stoi(tmp);
        getline(ss, tmp, ',');
        rect_bg_.color.b = std::stoi(tmp);
        getline(ss, tmp, ',');
        rect_bg_.a = std::stoi(tmp);
    }

    else if (var == "selector_color") {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        getline(ss, tmp, ',');
        selector_.color.r = std::stoi(tmp);
        getline(ss, tmp, ',');
        selector_.color.g = std::stoi(tmp);
        getline(ss, tmp, ',');
        selector_.color.b = std::stoi(tmp);
        getline(ss, tmp, ',');
        selector_.a = std::stoi(tmp);
    }

    else if (var == "selector_text_color") {
        // Se introducen los valores separados por comas en un vector
        std::stringstream ss(value);
        std::string tmp;
        getline(ss, tmp, ',');
        selector_.item_color.r = std::stoi(tmp);
        getline(ss, tmp, ',');
        selector_.item_color.g = std::stoi(tmp);
        getline(ss, tmp, ',');
        selector_.item_color.b = std::stoi(tmp);
    }

    else if (var == "areElementsCenteredOnX") {
        are_elements_centered_on_x_ = value == "true";
    }

    else if (var == "isCenteredOnX") {
        is_centered_on_x_ = value == "true";
    }

    else if (var == "isCenteredOnY") {
        is_centered_on_y_ = value == "true";
    }

    else if (var == "defaultActionWhenCancel") {
        default_action_when_cancel_ = std::stoi(value);
    }

    else if (var.empty()) {
    }

    else {
        success = false;
    }

    return success;
}

// Carga los ficheros de audio
void Menu::loadAudioFile(const std::string &file, int sound) {
    switch (sound) {
        case SOUND_ACCEPT:
            sound_accept_ = Ja::loadSound(file.c_str());
            break;

        case SOUND_CANCEL:
            sound_cancel_ = Ja::loadSound(file.c_str());
            break;

        case SOUND_MOVE:
            sound_move_ = Ja::loadSound(file.c_str());
            break;

        default:
            break;
    }
}

// Obtiene el nombre del menu
auto Menu::getName() const -> const std::string & {
    return name_;
}

// Obtiene el valor de la variable
auto Menu::getItemSelected() -> int {
    // Al llamar a esta funcion, se obtiene el valor y se borra
    const int temp = item_selected_;
    item_selected_ = MENU_NO_OPTION;
    return temp;
}

// Actualiza la posicion y el estado del selector
void Menu::updateSelector() {
    if (selector_.moving) {
        // Calcula el desplazamiento en Y
        selector_.y += selector_.desp_y;
        if (selector_.desp_y > 0)  // Va hacia abajo
        {
            if (selector_.y > selector_.target_y)  // Ha llegado al destino
            {
                selector_.origin_y = selector_.y = selector_.target_y;
                selector_.moving = false;
            }
        }

        else if (selector_.desp_y < 0)  // Va hacia arriba
        {
            if (selector_.y < selector_.target_y)  // Ha llegado al destino
            {
                selector_.origin_y = selector_.y = selector_.target_y;
                selector_.moving = false;
            }
        }
        selector_.rect.y = int(selector_.y);

        // Actualiza el color del item
        selector_.item_color_index++;
        selector_.item_color_index = std::min(selector_.num_jumps - 1, selector_.item_color_index);
        selector_.item_color = selector_.jump_item_colors[selector_.item_color_index];
        selector_.previous_item_color = selector_.jump_item_colors[selector_.num_jumps - 1 - selector_.item_color_index];
    } else {
        selector_.rect.y = int(selector_.y);
        selector_.item_color_index = 0;
        selector_.item_color = selector_.jump_item_colors[selector_.num_jumps - 1];
        selector_.previous_item_color = selector_.jump_item_colors[0];
    }

    if (selector_.resizing) {
        // Calcula el incremento en H
        selector_.h += selector_.inc_h;
        if (selector_.inc_h > 0)  // Crece
        {
            if (selector_.h > selector_.target_h)  // Ha llegado al destino
            {
                // selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
                selector_.origin_h = selector_.h = selector_.target_h;
                selector_.resizing = false;
            }
        }

        else if (selector_.inc_h < 0)  // Decrece
        {
            if (selector_.h < selector_.target_h)  // Ha llegado al destino
            {
                // selector.originH = selector.targetH = selector.rect.h = getSelectorHeight(selector.index);
                selector_.origin_h = selector_.h = selector_.target_h;
                selector_.resizing = false;
            }
        }
        selector_.rect.h = int(selector_.h);
    } else {
        selector_.rect.h = getSelectorHeight(selector_.index);
    }
}

// Coloca el selector en una posición específica
void Menu::setSelectorPos(int index) {
    if (index < (int)items_.size()) {
        selector_.index = index;
        selector_.rect.y = selector_.y = selector_.origin_y = selector_.target_y = items_[selector_.index].rect.y;
        selector_.rect.w = rect_bg_.rect.w;
        selector_.rect.x = rect_bg_.rect.x;
        selector_.origin_h = selector_.target_h = selector_.rect.h = getSelectorHeight(selector_.index);
        selector_.moving = false;
        selector_.resizing = false;
    }
}

// Obtiene la anchura del elemento más ancho del menu
auto Menu::getWidestItem() -> int {
    return std::accumulate(items_.begin(), items_.end(), 0, [](int acc, const Item &i) { return std::max(acc, i.rect.w); });
}

// Deja el menu apuntando al primer elemento
void Menu::reset() {
    item_selected_ = MENU_NO_OPTION;
    selector_.index = 0;
    if (items_.empty()) {
        return;
    }
    selector_.origin_y = selector_.target_y = selector_.y = items_[0].rect.y;
    selector_.origin_h = selector_.target_h = items_[0].rect.h;
    selector_.moving = false;
    selector_.resizing = false;

    // Si el primer elemento no es seleccionable, incrementa el selector
    if (!items_[selector_.index].selectable) {
        increaseSelectorIndex();
        setSelectorPos(selector_.index);
    }
}

// Actualiza el menu para recolocarlo correctamente y establecer el tamaño
void Menu::reorganize() {
    setRectSize();

    if (is_centered_on_x_) {
        centerMenuOnX(center_x_);
    }

    if (is_centered_on_y_) {
        centerMenuOnY(center_y_);
    }

    if (are_elements_centered_on_x_) {
        centerMenuElementsOnX();
    }
}

// Deja el menu apuntando al siguiente elemento
void Menu::increaseSelectorIndex() {
    // Guarda el indice actual antes de modificarlo
    selector_.previous_index = selector_.index;

    // Obten las coordenadas del elemento actual
    selector_.y = selector_.origin_y = items_[selector_.index].rect.y;
    selector_.h = selector_.origin_h = getSelectorHeight(selector_.index);

    // Calcula cual es el siguiente elemento
    ++selector_.index %= items_.size();
    while (!items_[selector_.index].selectable) {
        ++selector_.index %= items_.size();
    }

    // Establece las coordenadas y altura de destino
    selector_.target_y = items_[selector_.index].rect.y;
    selector_.desp_y = (selector_.target_y - selector_.origin_y) / selector_.num_jumps;

    selector_.target_h = getSelectorHeight(selector_.index);
    selector_.inc_h = (selector_.target_h - selector_.origin_h) / selector_.num_jumps;

    selector_.moving = true;
    if (selector_.inc_h != 0) {
        selector_.resizing = true;
    }
}

// Deja el menu apuntando al elemento anterior
void Menu::decreaseSelectorIndex() {
    // Guarda el indice actual antes de modificarlo
    selector_.previous_index = selector_.index;

    // Obten las coordenadas del elemento actual
    selector_.y = selector_.origin_y = items_[selector_.index].rect.y;
    selector_.h = selector_.origin_h = getSelectorHeight(selector_.index);

    // Calcula cual es el siguiente elemento
    if (selector_.index == 0) {
        selector_.index = items_.size() - 1;
    } else {
        selector_.index--;
    }

    while (!items_[selector_.index].selectable) {
        if (selector_.index == 0) {
            selector_.index = items_.size() - 1;
        } else {
            selector_.index--;
        }
    }

    // Establece las coordenadas y altura de destino
    selector_.target_y = items_[selector_.index].rect.y;
    selector_.desp_y = (selector_.target_y - selector_.origin_y) / selector_.num_jumps;

    selector_.target_h = getSelectorHeight(selector_.index);
    selector_.inc_h = (selector_.target_h - selector_.origin_h) / selector_.num_jumps;

    selector_.moving = true;
    if (selector_.inc_h != 0) {
        selector_.resizing = true;
    }
}

// Actualiza la logica del menu
void Menu::update() {
    checkInput();
    updateSelector();
}

// Pinta el menu en pantalla
void Menu::render() {
    // Rendereritza el fondo del menu
    if (background_type_ == MENU_BACKGROUND_SOLID) {
        SDL_FRect fBG = {(float)rect_bg_.rect.x, (float)rect_bg_.rect.y, (float)rect_bg_.rect.w, (float)rect_bg_.rect.h};
        SDL_SetRenderDrawColor(renderer_, rect_bg_.color.r, rect_bg_.color.g, rect_bg_.color.b, rect_bg_.a);
        SDL_RenderFillRect(renderer_, &fBG);
    }

    // Renderiza el rectangulo del selector
    const SDL_FRect fTemp = {(float)selector_.rect.x, (float)(selector_.rect.y - 1), (float)selector_.rect.w, (float)(selector_.rect.h + 1)};
    SDL_SetRenderDrawColor(renderer_, selector_.color.r, selector_.color.g, selector_.color.b, selector_.a);
    SDL_RenderFillRect(renderer_, &fTemp);

    // Renderiza el borde del fondo
    if (background_type_ == MENU_BACKGROUND_SOLID) {
        SDL_FRect fBGBorder = {(float)rect_bg_.rect.x, (float)rect_bg_.rect.y, (float)rect_bg_.rect.w, (float)rect_bg_.rect.h};
        SDL_SetRenderDrawColor(renderer_, rect_bg_.color.r, rect_bg_.color.g, rect_bg_.color.b, 255);
        SDL_RenderRect(renderer_, &fBGBorder);
    }

    // Crea una linea por si hay que dibujarla entre los items
    HorizontalLine line;
    line.x1 = selector_.rect.x + (selector_.rect.w / 6);
    line.x2 = line.x1 + ((selector_.rect.w / 6) * 4);

    // Renderiza el texto
    for (int i = 0; i < (int)items_.size(); ++i) {
        if (items_[i].visible) {
            // Comprueba si ha de dibujar una linea en el elemento del menu
            if (items_[i].line) {
                line.y = items_[i].rect.y + items_[i].rect.h + (items_[i].h_padding_down / 2) - 1;
                SDL_SetRenderDrawColor(renderer_, 255, 255, 255, 64);
                SDL_RenderLine(renderer_, line.x1, line.y, line.x2, line.y);
            }

            // Dibuja el elemento
            if (items_[i].greyed) {  // Tiene prioridad si el elemento es gris
                text_->writeColored(items_[i].rect.x, items_[i].rect.y, items_[i].label, color_greyed_);
            }

            else if (i == selector_.index) {  // A continuación si tiene el indice
                const Color color = {selector_.item_color.r, selector_.item_color.g, selector_.item_color.b};
                text_->writeColored(items_[i].rect.x, items_[i].rect.y, items_[i].label, color);
            }

            else if (i == selector_.previous_index) {  // O si lo ha tenido
                const Color color = {selector_.previous_item_color.r, selector_.previous_item_color.g, selector_.previous_item_color.b};
                text_->writeColored(items_[i].rect.x, items_[i].rect.y, items_[i].label, color);
            }

            else if (items_[i].selectable) {  // O si simplemente es un elemento normal
                text_->write(items_[i].rect.x, items_[i].rect.y, items_[i].label);
            }

            else {                                                      // Si no es seleccionable
                if ((items_[i].linked_up) && (i == selector_.index + 1)) {  // Si el elemento está enlazado con el elemento superior se pinta del color del selector
                    const Color color = {selector_.item_color.r, selector_.item_color.g, selector_.item_color.b};
                    text_->writeColored(items_[i].rect.x, items_[i].rect.y, items_[i].label, color);
                } else {  // Si no está enlazado con el elemento superior se pinta con el color normal
                    text_->write(items_[i].rect.x, items_[i].rect.y, items_[i].label);
                }
            }
        }
    }
}

// Establece el rectangulo de fondo del menu y el selector
void Menu::setRectSize(int w, int h) {
    // Establece el ancho
    if (w == 0) {            // Si no se pasa un valor, se busca si hay uno prefijado
        if (this->w_ == 0) {  // Si no hay prefijado, coge el item mas ancho
            rect_bg_.rect.w = findWidth() + text_->getCharacterSize();
        } else {  // Si hay prefijado, coge ese
            rect_bg_.rect.w = this->w_;
        }
    } else {  // Si se pasa un valor, se usa y se prefija
        rect_bg_.rect.w = w;
        this->w_ = w;
    }

    // Establece el alto
    if (h == 0) {  // Si no se pasa un valor, se busca de manera automatica
        rect_bg_.rect.h = findHeight() + text_->getCharacterSize();
    } else {  // Si se pasa un valor, se aplica
        rect_bg_.rect.h = h;
    }

    // La posición X es la del menú menos medio caracter
    if (this->w_ != 0) {  // Si el ancho esta prefijado, la x coinccide
        rect_bg_.rect.x = x_;
    } else {  // Si el ancho es automatico, se le da un poco de margen
        rect_bg_.rect.x = x_ - (text_->getCharacterSize() / 2);
    }

    // La posición Y es la del menu menos la altura de medio caracter
    rect_bg_.rect.y = y_ - (text_->getCharacterSize() / 2);

    // Establecemos los valores del rectangulo del selector a partir de los valores del rectangulo de fondo
    setSelectorPos(selector_.index);
}

// Establece el color del rectangulo de fondo
void Menu::setBackgroundColor(Color color, int alpha) {
    rect_bg_.color = color;
    rect_bg_.a = alpha;
}

// Establece el color del rectangulo del selector
void Menu::setSelectorColor(Color color, int alpha) {
    selector_.color = color;
    selector_.a = alpha;
}

// Establece el color del texto del selector
void Menu::setSelectorTextColor(Color color) {
    selector_.item_color = color;
}

// Centra el menu respecto un punto en el eje X
void Menu::centerMenuOnX(int value) {
    is_centered_on_x_ = true;
    if (value != 0) {
        center_x_ = value;
    } else if (center_x_ == 0) {
        return;
    }

    // Establece la nueva posición centrada en funcion del elemento más ancho o del ancho fijo del menu
    if (w_ != 0) {  // Si se ha definido un ancho fijo
        x_ = (center_x_) - (w_ / 2);
    } else {  // Si se actua en función del elemento más ancho
        x_ = (center_x_) - (findWidth() / 2);
    }

    // Actualiza el rectangulo de fondo y del selector
    rect_bg_.rect.x = x_;
    selector_.rect.x = x_;

    // Reposiciona los elementos del menu
    for (auto &i : items_) {
        i.rect.x = x_;
    }

    // Recalcula el rectangulo de fondo
    setRectSize();

    // Vuelve a centrar los elementos si fuera el caso
    if (are_elements_centered_on_x_) {
        centerMenuElementsOnX();
    }
}

// Centra el menu respecto un punto en el eje Y
void Menu::centerMenuOnY(int value) {
    is_centered_on_y_ = true;
    center_y_ = value;

    // Establece la nueva posición centrada en funcion del elemento más ancho
    y_ = (value) - (findHeight() / 2);

    // Reposiciona los elementos del menu
    replaceElementsOnY();

    // Recalcula el rectangulo de fondo
    setRectSize();
}

// Centra los elementos del menu en el eje X
void Menu::centerMenuElementsOnX() {
    are_elements_centered_on_x_ = true;

    for (auto &i : items_) {
        i.rect.x = (center_x_ - (i.rect.w / 2));
    }
}

// Añade un item al menu
void Menu::addItem(Item new_item) {
    if (items_.empty()) {  // Si es el primer item coge la posición en el eje Y del propio menu
        new_item.rect.y = y_;
    } else {  // En caso contrario, coge la posición en el eje Y a partir del último elemento
        new_item.rect.y = items_.back().rect.y + items_.back().rect.h + items_.back().h_padding_down;
    }

    new_item.rect.x = x_;

    items_.push_back(new_item);

    setItemCaption(items_.size() - 1, new_item.label);

    if (items_.size() > 1) {
        if (items_[items_.size() - 2].linked_down) {
            items_.back().linked_up = true;
        }
    }

    center_x_ = x_ + (findWidth() / 2);
    reorganize();
}

// Cambia el texto de un item
void Menu::setItemCaption(int index, const std::string &text) {
    items_[index].label = text;
    items_[index].rect.w = this->text_->lenght(items_[index].label);
    items_[index].rect.h = this->text_->getCharacterSize();
    reorganize();
}

// Establece el indice del itemm que se usará por defecto al cancelar el menu
void Menu::setDefaultActionWhenCancel(int item) {
    default_action_when_cancel_ = item;
}

// Gestiona la entrada de teclado y mando durante el menu
void Menu::checkInput() {
    if (Input::get()->checkInput(UP, REPEAT_FALSE)) {
        decreaseSelectorIndex();
        if (sound_move_ != nullptr) {
            Audio::get()->playSound(sound_move_);
        }
    }

    if (Input::get()->checkInput(DOWN, REPEAT_FALSE)) {
        increaseSelectorIndex();
        if (sound_move_ != nullptr) {
            Audio::get()->playSound(sound_move_);
        }
    }

    if (Input::get()->checkInput(ACCEPT, REPEAT_FALSE)) {
        item_selected_ = selector_.index;
        if (sound_accept_ != nullptr) {
            Audio::get()->playSound(sound_accept_);
        }
    }

    if (Input::get()->checkInput(CANCEL, REPEAT_FALSE)) {
        item_selected_ = default_action_when_cancel_;
        if (sound_cancel_ != nullptr) {
            Audio::get()->playSound(sound_cancel_);
        }
    }
}

// Calcula el ancho del menu
auto Menu::findWidth() -> int {
    return getWidestItem();
}

// Calcula el alto del menu
auto Menu::findHeight() -> int {
    const int height = std::accumulate(items_.begin(), items_.end(), 0, [](int acc, const Item &i) { return acc + i.rect.h + i.h_padding_down; });

    return height - items_.back().h_padding_down;
}

// Recoloca los elementos del menu en el eje Y
void Menu::replaceElementsOnY() {
    items_[0].rect.y = y_;

    for (int i = 1; i < (int)items_.size(); i++) {
        items_[i].rect.y = items_[i - 1].rect.y + items_[i - 1].rect.h + items_[i - 1].h_padding_down;
    }
}

// Establece el estado seleccionable de un item
void Menu::setSelectable(int index, bool value) {
    items_[index].selectable = value;
}

// Establece el estado agrisado de un item
void Menu::setGreyed(int index, bool value) {
    items_[index].greyed = value;
}

// Establece el estado de enlace de un item
void Menu::setLinkedDown(int index, bool value) {
    items_[index].linked_down = value;
}

// Establece el estado de visibilidad de un item
void Menu::setVisible(int index, bool value) {
    items_[index].visible = value;
}

// Calcula la altura del selector
auto Menu::getSelectorHeight(int value) -> int {
    if (items_[value].linked_down) {
        return items_[value].rect.h + items_[value].h_padding_down + items_[value + 1].rect.h;
    }
    return items_[value].rect.h;
}

// Establece el nombre del menu
void Menu::setName(const std::string &name) {
    this->name_ = name;
}

// Establece la posición del menu
void Menu::setPos(int x, int y) {
    this->x_ = x;
    this->y_ = y;
}

// Establece el tipo de fondo del menu
void Menu::setBackgroundType(int value) {
    background_type_ = value;
}

// Establece la fuente de texto que se utilizará
void Menu::setText(const std::string &font_png, const std::string &font_txt) {
    if (text_ == nullptr) {
        text_ = new Text(Asset::get()->get(font_png), Asset::get()->get(font_txt), renderer_);
    }
}

// Calcula los colores del selector para el degradado
void Menu::setSelectorItemColors() {
    const Color colorFrom = {255, 255, 255};
    const Color colorTo = selector_.item_color;

    for (int i = 0; i < selector_.num_jumps; ++i) {
        const float step = ((float)i / (selector_.num_jumps - 1));
        const int r = colorFrom.r + ((colorTo.r - colorFrom.r) * step);
        const int g = colorFrom.g + ((colorTo.g - colorFrom.g) * step);
        const int b = colorFrom.b + ((colorTo.b - colorFrom.b) * step);
        selector_.jump_item_colors[i].r = r;
        selector_.jump_item_colors[i].g = g;
        selector_.jump_item_colors[i].b = b;
    }

    selector_.item_color_index = 0;
}