#include "game/ui/service_menu.hpp"

#include <utility>

#include "core/audio/audio.hpp"           // Para Audio
#include "core/input/define_buttons.hpp"  // Para DefineButtons
#include "core/input/input.hpp"           // Para Input
#include "core/input/input_types.hpp"     // Para InputAction
#include "core/locale/lang.hpp"           // Para getText, getCodeFromName, getNameFromCode
#include "core/rendering/screen.hpp"      // Para Screen
#include "core/resources/resource.hpp"    // Para Resource
#include "core/system/section.hpp"        // Para Name, name, Options, options
#include "game/entities/player.hpp"       // Para Player
#include "game/gameplay/difficulty.hpp"   // Para getCodeFromName, getNameFromCode
#include "game/options.hpp"               // Para GamepadManager, gamepad_manager, PendingChanges, Video, pending_changes, video, Audio, Gamepad, Settings, audio, checkPendingChanges, settings, Window, getPlayerWhoUsesKeyboard, playerIdToString, stringToPlayerId, window, Keyboard, Music, Sound, keyboard
#include "game/ui/menu_option.hpp"        // Para MenuOption, ActionOption, BoolOption, ListOption, FolderOption, IntOption, ActionListOption
#include "game/ui/menu_renderer.hpp"      // Para MenuRenderer
#include "game/ui/ui_message.hpp"         // Para UIMessage
#include "utils/param.hpp"                // Para Param, param, ParamGame, ParamServiceMenu
#include "utils/utils.hpp"                // Para Zone

// Singleton
ServiceMenu* ServiceMenu::instance = nullptr;
void ServiceMenu::init() { ServiceMenu::instance = new ServiceMenu(); }
void ServiceMenu::destroy() { delete ServiceMenu::instance; }
auto ServiceMenu::get() -> ServiceMenu* { return ServiceMenu::instance; }

// Constructor
ServiceMenu::ServiceMenu()
    : current_settings_group_(SettingsGroup::MAIN),
      previous_settings_group_(current_settings_group_) {
    auto element_text = Resource::get()->getText("04b_25_flat");
    auto title_text = Resource::get()->getText("04b_25_flat_2x");

    // El renderer ahora se inicializa con su configuración
    renderer_ = std::make_unique<MenuRenderer>(this, element_text, title_text);

    restart_message_ui_ = std::make_unique<UIMessage>(element_text, Lang::getText("[SERVICE_MENU] NEED_RESTART_MESSAGE"), param.service_menu.title_color);
    define_buttons_ = std::make_unique<DefineButtons>();

    reset();
}

void ServiceMenu::toggle() {
    if (define_buttons_ && define_buttons_->isEnabled()) {
        return;
    }
    if (isAnimating() && !define_buttons_->isEnabled()) {
        return;
    }

    if (!enabled_) {  // Si está cerrado, abrir
        reset();
        Options::gamepad_manager.assignAndLinkGamepads();
        renderer_->show(this);
        setEnabledInternal(true);
        playSelectSound();
    } else {  // Si está abierto, cerrar
        renderer_->hide();
        setEnabledInternal(false);
        playBackSound();
    }
}

void ServiceMenu::render() {
    // Condición corregida: renderiza si está habilitado O si se está animando
    if (enabled_ || isAnimating()) {
        renderer_->render(this);
    } else {
        return;  // Si no está ni habilitado ni animándose, no dibujes nada.
    }

    // El mensaje de reinicio y otros elementos solo deben aparecer si está completamente visible,
    // no durante la animación.
    if (enabled_ && !isAnimating()) {
        const float MSG_X = param.game.game_area.center_x;
        const float MSG_Y = renderer_->getRect().y + 39.0F;
        restart_message_ui_->setPosition(MSG_X, MSG_Y);
        restart_message_ui_->render();

        if (define_buttons_ && define_buttons_->isEnabled()) {
            define_buttons_->render();
        }
    }
}

void ServiceMenu::update(float delta_time) {
    // El renderer siempre se actualiza para manejar sus animaciones
    renderer_->update(this, delta_time);

    if (!enabled_) {
        return;
    }

    // Lógica de actualización del mensaje de reinicio y botones
    bool now_pending = Options::pending_changes.has_pending_changes;
    if (now_pending != last_pending_changes_) {
        now_pending ? restart_message_ui_->show() : restart_message_ui_->hide();
        last_pending_changes_ = now_pending;
    }
    restart_message_ui_->update(delta_time);

    if (define_buttons_) {
        define_buttons_->update(delta_time);
        if (define_buttons_->isEnabled() && define_buttons_->isReadyToClose()) {
            define_buttons_->disable();
        }
    }
}

void ServiceMenu::reset() {
    selected_ = 0;
    main_menu_selected_ = 0;
    current_settings_group_ = SettingsGroup::MAIN;
    previous_settings_group_ = SettingsGroup::MAIN;
    initializeOptions();
    updateMenu();
    renderer_->setLayout(this);
}

void ServiceMenu::moveBack() {
    // Si estamos en una subpantalla, no llamamos a toggle
    if (current_settings_group_ != SettingsGroup::MAIN) {
        playBackSound();
        current_settings_group_ = previous_settings_group_;
        selected_ = (current_settings_group_ == SettingsGroup::MAIN) ? main_menu_selected_ : 0;
        updateMenu();
        return;
    }

    // Si estamos en la pantalla principal, llamamos a toggle() para cerrar con animación.
    toggle();
}

// --- Lógica de Navegación ---
void ServiceMenu::setSelectorUp() {
    if (display_options_.empty()) {
        return;
    }
    selected_ = (selected_ > 0) ? selected_ - 1 : display_options_.size() - 1;
    playMoveSound();
}

void ServiceMenu::setSelectorDown() {
    if (display_options_.empty()) {
        return;
    }
    selected_ = (selected_ + 1) % display_options_.size();
    playMoveSound();
}

void ServiceMenu::adjustOption(bool adjust_up) {
    if (display_options_.empty()) {
        return;
    }
    auto& selected_option = display_options_.at(selected_);
    if (selected_option->getBehavior() == MenuOption::Behavior::ADJUST) {
        selected_option->adjustValue(adjust_up);
        applySettings();
        playAdjustSound();
    }
}

void ServiceMenu::selectOption() {
    if (display_options_.empty()) {
        return;
    }
    if (current_settings_group_ == SettingsGroup::MAIN) {
        main_menu_selected_ = selected_;
    }

    auto* selected_option = display_options_.at(selected_);
    if (selected_option == nullptr) {
        // This shouldn't happen in normal operation, but protects against null pointer
        return;
    }

    if (auto* folder = dynamic_cast<FolderOption*>(selected_option)) {
        previous_settings_group_ = current_settings_group_;
        current_settings_group_ = folder->getTargetGroup();
        selected_ = 0;
        updateMenu();
    } else if (selected_option->getBehavior() == MenuOption::Behavior::SELECT or selected_option->getBehavior() == MenuOption::Behavior::BOTH) {
        selected_option->executeAction();
    }
    playSelectSound();
}

// --- Lógica Interna ---

void ServiceMenu::updateDisplayOptions() {
    display_options_.clear();
    for (auto& option : options_) {
        if (option->getGroup() == current_settings_group_ && !option->isHidden()) {
            display_options_.push_back(option.get());
        }
    }
    updateOptionPairs();
}

void ServiceMenu::updateOptionPairs() {
    option_pairs_.clear();
    for (const auto& option : display_options_) {
        option_pairs_.emplace_back(option->getCaption(), option->getValueAsString());
    }
}

void ServiceMenu::updateMenu() {
    title_ = settingsGroupToString(current_settings_group_);
    adjustListValues();

    // Actualiza las opciones visibles
    updateDisplayOptions();

    // Notifica al renderer del cambio de layout
    renderer_->onLayoutChanged(this);
}

void ServiceMenu::applySettings() {
    if (current_settings_group_ == SettingsGroup::CONTROLS) {
        applyControlsSettings();
    }
    if (current_settings_group_ == SettingsGroup::VIDEO) {
        applyVideoSettings();
    }
    if (current_settings_group_ == SettingsGroup::AUDIO) {
        applyAudioSettings();
    }
    if (current_settings_group_ == SettingsGroup::SETTINGS) {
        applySettingsSettings();
    }

    // Actualiza los valores de las opciones
    updateOptionPairs();
}

void ServiceMenu::applyControlsSettings() {}

void ServiceMenu::applyVideoSettings() {
    Screen::get()->applySettings();
    setHiddenOptions();
}

void ServiceMenu::applyAudioSettings() {
    Audio::get()->applySettings();
}

void ServiceMenu::applySettingsSettings() {
    setHiddenOptions();
}

auto ServiceMenu::getOptionByCaption(const std::string& caption) const -> MenuOption* {
    for (const auto& option : options_) {
        if (option->getCaption() == caption) {
            return option.get();
        }
    }
    return nullptr;
}

// --- Getters y otros ---

auto ServiceMenu::getCurrentGroupAlignment() const -> ServiceMenu::GroupAlignment {
    switch (current_settings_group_) {
        case SettingsGroup::CONTROLS:
        case SettingsGroup::VIDEO:
        case SettingsGroup::AUDIO:
        case SettingsGroup::SETTINGS:
            return GroupAlignment::LEFT;
        default:
            return GroupAlignment::CENTERED;
    }
}

auto ServiceMenu::countOptionsInGroup(SettingsGroup group) const -> size_t {
    size_t count = 0;
    for (const auto& option : options_) {
        if (option->getGroup() == group && !option->isHidden()) {
            count++;
        }
    }
    return count;
}

// Inicializa todas las opciones del menú
void ServiceMenu::initializeOptions() {
    options_.clear();

    // CONTROLS - Usando ActionListOption para mandos
    options_.push_back(std::make_unique<ActionListOption>(
        Lang::getText("[SERVICE_MENU] CONTROLLER1"),
        SettingsGroup::CONTROLS,
        Input::get()->getControllerNames(),
        []() -> std::string {
            return Options::gamepad_manager.getGamepad(Player::Id::PLAYER1).name;
        },
        [](const std::string& val) -> void {
            Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER1, Input::get()->getGamepadByName(val), val);
        },
        [this]() -> void {
            // Acción: configurar botones del mando del jugador 1
            auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER1);
            if ((gamepad != nullptr) && gamepad->instance) {
                define_buttons_->enable(gamepad);
            }
        }));

    options_.push_back(std::make_unique<ActionListOption>(
        Lang::getText("[SERVICE_MENU] CONTROLLER2"),
        SettingsGroup::CONTROLS,
        Input::get()->getControllerNames(),
        []() -> std::string {
            return Options::gamepad_manager.getGamepad(Player::Id::PLAYER2).name;
        },
        [](const std::string& val) -> void {
            Options::gamepad_manager.assignGamepadToPlayer(Player::Id::PLAYER2, Input::get()->getGamepadByName(val), val);
        },
        [this]() -> void {
            // Acción: configurar botones del mando del jugador 2
            auto* gamepad = &Options::gamepad_manager.getGamepad(Player::Id::PLAYER2);
            if ((gamepad != nullptr) && gamepad->instance) {
                define_buttons_->enable(gamepad);
            }
        }));

    // CONTROLS - Opción para teclado (solo lista, sin acción)
    options_.push_back(std::make_unique<ListOption>(
        Lang::getText("[SERVICE_MENU] KEYBOARD"),
        SettingsGroup::CONTROLS,
        std::vector<std::string>{
            Lang::getText("[SERVICE_MENU] PLAYER1"),
            Lang::getText("[SERVICE_MENU] PLAYER2")},
        []() -> std::string {
            // Devolver el jugador actual asignado al teclado
            return Options::playerIdToString(Options::getPlayerWhoUsesKeyboard());
        },
        [](const std::string& val) -> void {
            // Asignar el teclado al jugador seleccionado
            Options::keyboard.assignTo(Options::stringToPlayerId(val));
        }));

    // CONTROLS - Acción para intercambiar mandos
    options_.push_back(std::make_unique<ActionOption>(
        Lang::getText("[SERVICE_MENU] SWAP_CONTROLLERS"),
        SettingsGroup::CONTROLS,
        [this]() -> void {
            Options::gamepad_manager.swapPlayers();
            adjustListValues();   // Sincroniza el valor de las opciones de lista (como MANDO1) con los datos reales
            updateOptionPairs();  // Actualiza los pares de texto <opción, valor> que se van a dibujar

            // Feedback visual: anima el intercambio de los valores entre
            // las filas de MANDO 1 y MANDO 2, imprescindible cuando los dos
            // mandos tienen el mismo nombre (el texto no cambia al swap).
            const std::string CAPTION1 = Lang::getText("[SERVICE_MENU] CONTROLLER1");
            const std::string CAPTION2 = Lang::getText("[SERVICE_MENU] CONTROLLER2");
            size_t idx1 = display_options_.size();
            size_t idx2 = display_options_.size();
            for (size_t i = 0; i < display_options_.size(); ++i) {
                const auto& caption = display_options_[i]->getCaption();
                if (caption == CAPTION1) { idx1 = i; }
                if (caption == CAPTION2) { idx2 = i; }
            }
            if (idx1 < display_options_.size() && idx2 < display_options_.size()) {
                renderer_->startSwapAnimation(idx1, idx2);
            }
        }));

    // VIDEO
    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] FULLSCREEN"),
        SettingsGroup::VIDEO,
        &Options::video.fullscreen));

    options_.push_back(std::make_unique<IntOption>(
        Lang::getText("[SERVICE_MENU] WINDOW_SIZE"),
        SettingsGroup::VIDEO,
        &Options::window.zoom,
        1,
        Options::window.max_zoom,
        1));

    // Shader: Desactivat / PostFX / CrtPi
    {
        std::string disabled_text = Lang::getText("[SERVICE_MENU] SHADER_DISABLED");
        std::vector<std::string> shader_values = {disabled_text, "PostFX", "CrtPi"};
        auto shader_getter = [disabled_text]() -> std::string {
            // NOLINTNEXTLINE(performance-no-automatic-move) -- captura por valor en lambda const, no se puede mover
            if (!Options::video.shader.enabled) { return disabled_text; }
            return (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
        };
        auto shader_setter = [disabled_text](const std::string& val) {
            if (val == disabled_text) {
                Options::video.shader.enabled = false;
            } else {
                Options::video.shader.enabled = true;
                const auto TYPE = (val == "CrtPi") ? Rendering::ShaderType::CRTPI : Rendering::ShaderType::POSTFX;
                Options::video.shader.current_shader = TYPE;
                auto* screen = Screen::get();
                if (screen != nullptr) {
                    screen->applySettings();
                }
            }
            Screen::initShaders();
        };
        options_.push_back(std::make_unique<ListOption>(
            Lang::getText("[SERVICE_MENU] SHADER"),
            SettingsGroup::VIDEO,
            shader_values,
            shader_getter,
            shader_setter));
    }

    // Preset: muestra nombre, cicla circularmente entre presets del shader activo
    {
        auto preset_getter = []() -> std::string {
            if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
                if (Options::crtpi_presets.empty()) { return ""; }
                return Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
            }
            if (Options::postfx_presets.empty()) { return ""; }
            return Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
        };
        auto preset_adjuster = [](bool up) {
            if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
                if (Options::crtpi_presets.empty()) { return; }
                const int SIZE = static_cast<int>(Options::crtpi_presets.size());
                Options::video.shader.current_crtpi_preset = up
                    ? (Options::video.shader.current_crtpi_preset + 1) % SIZE
                    : (Options::video.shader.current_crtpi_preset + SIZE - 1) % SIZE;
            } else {
                if (Options::postfx_presets.empty()) { return; }
                const int SIZE = static_cast<int>(Options::postfx_presets.size());
                Options::video.shader.current_postfx_preset = up
                    ? (Options::video.shader.current_postfx_preset + 1) % SIZE
                    : (Options::video.shader.current_postfx_preset + SIZE - 1) % SIZE;
            }
            Screen::initShaders();
        };
        auto preset_max_width = [](Text* text) -> int {
            int max_w = 0;
            for (const auto& p : Options::postfx_presets) { max_w = std::max(max_w, text->length(p.name, -2)); }
            for (const auto& p : Options::crtpi_presets) { max_w = std::max(max_w, text->length(p.name, -2)); }
            return max_w;
        };

        options_.push_back(std::make_unique<CallbackOption>(
            Lang::getText("[SERVICE_MENU] SHADER_PRESET"),
            SettingsGroup::VIDEO,
            preset_getter,
            preset_adjuster,
            preset_max_width));
    }

    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] SUPERSAMPLING"),
        SettingsGroup::VIDEO,
        &Options::video.supersampling.enabled));

    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] VSYNC"),
        SettingsGroup::VIDEO,
        &Options::video.vsync));

    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] INTEGER_SCALE"),
        SettingsGroup::VIDEO,
        &Options::video.integer_scale));

    // FILTER: Nearest / Linear (solo visible en el fallback SDL, sin GPU acelerada)
    {
        std::vector<std::string> filter_values = {"Nearest", "Linear"};
        auto filter_getter = []() -> std::string {
            return (Options::video.scale_mode == SDL_SCALEMODE_LINEAR) ? "Linear" : "Nearest";
        };
        auto filter_setter = [](const std::string& val) {
            Options::video.scale_mode = (val == "Linear") ? SDL_SCALEMODE_LINEAR : SDL_SCALEMODE_NEAREST;
            if (Screen::get() != nullptr) { Screen::get()->applyFilter(); }
        };
        options_.push_back(std::make_unique<ListOption>(
            Lang::getText("[SERVICE_MENU] FILTER"),
            SettingsGroup::VIDEO,
            filter_values,
            filter_getter,
            filter_setter));
    }

    // AUDIO
    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] AUDIO"),
        SettingsGroup::AUDIO,
        &Options::audio.enabled));

    options_.push_back(std::make_unique<VolumeOption>(
        Lang::getText("[SERVICE_MENU] MAIN_VOLUME"),
        SettingsGroup::AUDIO,
        &Options::audio.volume,
        5));

    options_.push_back(std::make_unique<VolumeOption>(
        Lang::getText("[SERVICE_MENU] MUSIC_VOLUME"),
        SettingsGroup::AUDIO,
        &Options::audio.music.volume,
        5));

    options_.push_back(std::make_unique<VolumeOption>(
        Lang::getText("[SERVICE_MENU] SFX_VOLUME"),
        SettingsGroup::AUDIO,
        &Options::audio.sound.volume,
        5));

    // SETTINGS
    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] AUTOFIRE"),
        SettingsGroup::SETTINGS,
        &Options::settings.autofire));

    options_.push_back(std::make_unique<ListOption>(
        Lang::getText("[SERVICE_MENU] LANGUAGE"),
        SettingsGroup::SETTINGS,
        std::vector<std::string>{
            Lang::getText("[SERVICE_MENU] LANG_ES"),
            Lang::getText("[SERVICE_MENU] LANG_BA"),
            Lang::getText("[SERVICE_MENU] LANG_EN")},
        []() -> std::string {
            return Lang::getNameFromCode(Options::pending_changes.new_language);
        },
        [](const std::string& val) -> void {
            Options::pending_changes.new_language = Lang::getCodeFromName(val);
            Options::checkPendingChanges();
        }));

    options_.push_back(std::make_unique<ListOption>(
        Lang::getText("[SERVICE_MENU] DIFFICULTY"),
        SettingsGroup::SETTINGS,
        std::vector<std::string>{
            Lang::getText("[SERVICE_MENU] EASY"),
            Lang::getText("[SERVICE_MENU] NORMAL"),
            Lang::getText("[SERVICE_MENU] HARD")},
        []() -> std::string {
            return Difficulty::getNameFromCode(Options::pending_changes.new_difficulty);
        },
        [](const std::string& val) -> void {
            Options::pending_changes.new_difficulty = Difficulty::getCodeFromName(val);
            Options::checkPendingChanges();
        }));

    options_.push_back(std::make_unique<BoolOption>(
        Lang::getText("[SERVICE_MENU] ENABLE_SHUTDOWN"),
        SettingsGroup::SETTINGS,
        &Options::settings.shutdown_enabled));

    // SYSTEM
    options_.push_back(std::make_unique<ActionOption>(
        Lang::getText("[SERVICE_MENU] RESET"),
        SettingsGroup::SYSTEM,
        [this]() -> void {
            Section::name = Section::Name::RESET;
            toggle();
        }));

    options_.push_back(std::make_unique<ActionOption>(
        Lang::getText("[SERVICE_MENU] QUIT"),
        SettingsGroup::SYSTEM,
        []() -> void {
            Section::name = Section::Name::QUIT;
            Section::options = Section::Options::NONE;
        }));

    options_.push_back(std::make_unique<ActionOption>(
        Lang::getText("[SERVICE_MENU] SHUTDOWN"),
        SettingsGroup::SYSTEM,
        []() -> void {
            Section::name = Section::Name::QUIT;
            Section::options = Section::Options::SHUTDOWN;
        },
        !Options::settings.shutdown_enabled));

    // MAIN MENU
    options_.push_back(std::make_unique<FolderOption>(
        Lang::getText("[SERVICE_MENU] CONTROLS"),
        SettingsGroup::MAIN,
        SettingsGroup::CONTROLS));

    options_.push_back(std::make_unique<FolderOption>(
        Lang::getText("[SERVICE_MENU] VIDEO"),
        SettingsGroup::MAIN,
        SettingsGroup::VIDEO));

    options_.push_back(std::make_unique<FolderOption>(
        Lang::getText("[SERVICE_MENU] AUDIO"),
        SettingsGroup::MAIN,
        SettingsGroup::AUDIO));

    options_.push_back(std::make_unique<FolderOption>(
        Lang::getText("[SERVICE_MENU] SETTINGS"),
        SettingsGroup::MAIN,
        SettingsGroup::SETTINGS));

    options_.push_back(std::make_unique<FolderOption>(
        Lang::getText("[SERVICE_MENU] SYSTEM"),
        SettingsGroup::MAIN,
        SettingsGroup::SYSTEM));

    // Oculta opciones según configuración
    setHiddenOptions();
}

// Sincroniza los valores de las opciones tipo lista
void ServiceMenu::adjustListValues() {
    for (auto& option : options_) {
        if (auto* list_option = dynamic_cast<ListOption*>(option.get())) {
            list_option->sync();
        }
    }
}

// Reproduce el sonido de navegación del menú
void ServiceMenu::playAdjustSound() { Audio::get()->playSound("service_menu_adjust.wav", Audio::Group::INTERFACE); }
void ServiceMenu::playMoveSound() { Audio::get()->playSound("service_menu_move.wav", Audio::Group::INTERFACE); }
void ServiceMenu::playSelectSound() { Audio::get()->playSound("service_menu_select.wav", Audio::Group::INTERFACE); }
void ServiceMenu::playBackSound() { Audio::get()->playSound("service_menu_back.wav", Audio::Group::INTERFACE); }

// Devuelve el nombre del grupo como string para el título
auto ServiceMenu::settingsGroupToString(SettingsGroup group) -> std::string {
    switch (group) {
        case SettingsGroup::MAIN:
            return Lang::getText("[SERVICE_MENU] TITLE");
        case SettingsGroup::CONTROLS:
            return Lang::getText("[SERVICE_MENU] CONTROLS");
        case SettingsGroup::VIDEO:
            return Lang::getText("[SERVICE_MENU] VIDEO");
        case SettingsGroup::AUDIO:
            return Lang::getText("[SERVICE_MENU] AUDIO");
        case SettingsGroup::SETTINGS:
            return Lang::getText("[SERVICE_MENU] SETTINGS");
        case SettingsGroup::SYSTEM:
            return Lang::getText("[SERVICE_MENU] SYSTEM");
        default:
            return Lang::getText("[SERVICE_MENU] TITLE");
    }
}

// Establece el estado de oculto de ciertas opciones
void ServiceMenu::setHiddenOptions() {
    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] WINDOW_SIZE"));
        if (option != nullptr) {
            option->setHidden(Options::video.fullscreen);
        }
    }

    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHUTDOWN"));
        if (option != nullptr) {
            option->setHidden(!Options::settings.shutdown_enabled);
        }
    }

    // Las opciones de shader solo tienen sentido cuando SDL3GPU está activo.
    // Las opciones de filtro SDL solo tienen sentido en el fallback sin GPU.
    // Ambos checks son runtime para cubrir tanto builds sin shaders como fallos de inicialización.
    const bool HW_ACCEL = Screen::isHardwareAccelerated();

    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] FILTER"));
        if (option != nullptr) {
            option->setHidden(HW_ACCEL);
        }
    }

    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER"));
        if (option != nullptr) {
            option->setHidden(!HW_ACCEL);
        }
    }

    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SHADER_PRESET"));
        if (option != nullptr) {
            option->setHidden(!HW_ACCEL || !Options::video.shader.enabled);
        }
    }

    {
        auto* option = getOptionByCaption(Lang::getText("[SERVICE_MENU] SUPERSAMPLING"));
        if (option != nullptr) {
            option->setHidden(!HW_ACCEL || !Options::video.shader.enabled || Options::video.shader.current_shader != Rendering::ShaderType::POSTFX);
        }
    }

#ifdef __EMSCRIPTEN__
    // En la versión web no tiene sentido exponer: permitir apagar el sistema
    // (no aplica al navegador) ni salir del juego (lo gestiona el navegador).
    // SHADER ya queda oculto por el check de HW_ACCEL de arriba.
    for (const auto& caption : {
             Lang::getText("[SERVICE_MENU] ENABLE_SHUTDOWN"),
             Lang::getText("[SERVICE_MENU] QUIT")}) {
        auto* option = getOptionByCaption(caption);
        if (option != nullptr) {
            option->setHidden(true);
        }
    }
#endif

    updateMenu();  // El menú debe refrescarse si algo se oculta
}

void ServiceMenu::handleEvent(const SDL_Event& event) {
    if (!enabled_) {
        return;
    }

    // Si DefineButtons está activo, que maneje todos los eventos
    if (define_buttons_ && define_buttons_->isEnabled()) {
        define_buttons_->handleEvents(event);
    }
}

auto ServiceMenu::checkInput() -> bool {
    // --- Guardas ---
    // No procesar input si el menú no está habilitado, si se está animando o si se definen botones
    if (!enabled_ || isAnimating() || (define_buttons_ && define_buttons_->isEnabled())) {
        return false;
    }

    static auto* input_ = Input::get();
    using Action = Input::Action;

    const std::vector<std::pair<Action, std::function<void()>>> ACTIONS = {
        {Action::UP, [this]() -> void { setSelectorUp(); }},
        {Action::DOWN, [this]() -> void { setSelectorDown(); }},
        {Action::RIGHT, [this]() -> void { adjustOption(true); }},
        {Action::LEFT, [this]() -> void { adjustOption(false); }},
        {Action::SM_SELECT, [this]() -> void { selectOption(); }},
        {Action::SM_BACK, [this]() -> void { moveBack(); }},
    };

    // Teclado
    for (const auto& [action, func] : ACTIONS) {
        if (input_->checkAction(action, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            func();
            return true;
        }
    }

    // Mandos
    for (const auto& gamepad : input_->getGamepads()) {
        for (const auto& [action, func] : ACTIONS) {
            if (input_->checkAction(action, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad)) {
                func();
                return true;
            }
        }
    }

    return false;
}

// --- Nuevo Getter ---
auto ServiceMenu::isAnimating() const -> bool {
    return renderer_ && renderer_->isAnimating();
}

auto ServiceMenu::isDefiningButtons() const -> bool {
    return define_buttons_ && define_buttons_->isEnabled();
}

void ServiceMenu::refresh() {
    // Este método está diseñado para ser llamado desde fuera, por ejemplo,
    // cuando un mando se conecta o desconecta mientras el menú está abierto.

    // La función updateMenu() es la forma más completa de refrescar, ya que
    // sincroniza los valores, actualiza la lista de opciones visibles y notifica
    // al renderer de cualquier cambio de layout que pueda haber ocurrido.
    updateMenu();
}

// Método para registrar callback
void ServiceMenu::setStateChangeCallback(StateChangeCallback callback) {
    state_change_callback_ = std::move(callback);
}

// Método interno que cambia estado y notifica
void ServiceMenu::setEnabledInternal(bool enabled) {
    if (enabled_ != enabled) {  // Solo si realmente cambia
        enabled_ = enabled;

        // Notifica el cambio si hay callback registrado
        if (state_change_callback_) {
            state_change_callback_(enabled_);
        }
    }
}