#pragma once

#include <algorithm>   // Para max, clamp
#include <cstddef>     // Para size_t
#include <functional>  // Para function
#include <string>      // Para allocator, string, basic_string, to_string, operator==, char_traits
#include <utility>     // Para move
#include <vector>      // Para vector

#include "core/locale/lang.hpp"      // Para getText
#include "core/rendering/text.hpp"   // Para Text
#include "game/ui/service_menu.hpp"  // Para ServiceMenu

// --- Clase MenuOption: interfaz base para todas las opciones del menú ---
class MenuOption {
   public:
    // --- Enums ---
    enum class Behavior {
        ADJUST,  // Solo puede ajustar valor (como IntOption, BoolOption, ListOption)
        SELECT,  // Solo puede ejecutar acción (como ActionOption, FolderOption)
        BOTH     // Puede tanto ajustar como ejecutar acción (como ActionListOption)
    };

    // --- Constructor y destructor ---
    MenuOption(std::string caption, ServiceMenu::SettingsGroup group, bool hidden = false)
        : caption_(std::move(caption)),
          group_(group),
          hidden_(hidden) {}
    virtual ~MenuOption() = default;

    // --- Getters ---
    [[nodiscard]] auto getCaption() const -> const std::string& { return caption_; }
    [[nodiscard]] auto getGroup() const -> ServiceMenu::SettingsGroup { return group_; }
    [[nodiscard]] auto isHidden() const -> bool { return hidden_; }
    void setHidden(bool hidden) { hidden_ = hidden; }

    [[nodiscard]] virtual auto getBehavior() const -> Behavior = 0;
    [[nodiscard]] virtual auto getValueAsString() const -> std::string { return ""; }
    virtual void adjustValue(bool adjust_up) {}
    [[nodiscard]] virtual auto getTargetGroup() const -> ServiceMenu::SettingsGroup { return ServiceMenu::SettingsGroup::MAIN; }
    virtual void executeAction() {}

    virtual auto getMaxValueWidth(Text* text_renderer) const -> int { return 0; }  // Método virtual para que cada opción calcule el ancho de su valor más largo

   protected:
    // --- Variables ---
    std::string caption_;
    ServiceMenu::SettingsGroup group_;
    bool hidden_;
};

// --- Clases Derivadas ---

class BoolOption : public MenuOption {
   public:
    BoolOption(const std::string& cap, ServiceMenu::SettingsGroup grp, bool* var)
        : MenuOption(cap, grp),
          linked_variable_(var) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
    [[nodiscard]] auto getValueAsString() const -> std::string override {
        return *linked_variable_ ? Lang::getText("[SERVICE_MENU] ON") : Lang::getText("[SERVICE_MENU] OFF");
    }
    void adjustValue(bool /*adjust_up*/) override {
        *linked_variable_ = !*linked_variable_;
    }
    auto getMaxValueWidth(Text* text_renderer) const -> int override {
        return std::max(
            text_renderer->length(Lang::getText("[SERVICE_MENU] ON"), -2),
            text_renderer->length(Lang::getText("[SERVICE_MENU] OFF"), -2));
    }

   private:
    bool* linked_variable_;
};

class IntOption : public MenuOption {
   public:
    IntOption(const std::string& cap, ServiceMenu::SettingsGroup grp, int* var, int min, int max, int step)
        : MenuOption(cap, grp),
          linked_variable_(var),
          min_value_(min),
          max_value_(max),
          step_value_(step) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
    [[nodiscard]] auto getValueAsString() const -> std::string override { return std::to_string(*linked_variable_); }
    void adjustValue(bool adjust_up) override {
        int new_value = *linked_variable_ + (adjust_up ? step_value_ : -step_value_);
        *linked_variable_ = std::clamp(new_value, min_value_, max_value_);
    }
    auto getMaxValueWidth(Text* text_renderer) const -> int override {
        int max_width = 0;

        // Iterar por todos los valores posibles en el rango
        for (int value = min_value_; value <= max_value_; value += step_value_) {
            int width = text_renderer->length(std::to_string(value), -2);
            max_width = std::max(max_width, width);
        }

        return max_width;
    }

   private:
    int* linked_variable_;
    int min_value_, max_value_, step_value_;
};

// VolumeOption: emmagatzema un float 0.0..1.0 però es mostra/edita com a int 0..100.
// Pensat per als sliders de volum que l'usuari veu com percentatge però que
// internament viuen en float (API unificada del motor d'àudio).
class VolumeOption : public MenuOption {
   public:
    VolumeOption(const std::string& cap, ServiceMenu::SettingsGroup grp, float* var, int step_percent = 5)
        : MenuOption(cap, grp),
          linked_variable_(var),
          step_value_(step_percent) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
    [[nodiscard]] auto getValueAsString() const -> std::string override {
        int pct = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
        return std::to_string(pct);
    }
    void adjustValue(bool adjust_up) override {
        int current = static_cast<int>(*linked_variable_ * 100.0F + 0.5F);
        int new_value = std::clamp(current + (adjust_up ? step_value_ : -step_value_), 0, 100);
        *linked_variable_ = static_cast<float>(new_value) / 100.0F;
    }
    auto getMaxValueWidth(Text* text_renderer) const -> int override {
        int max_width = 0;
        for (int value = 0; value <= 100; value += step_value_) {
            int width = text_renderer->length(std::to_string(value), -2);
            max_width = std::max(max_width, width);
        }
        return max_width;
    }

   private:
    float* linked_variable_;
    int step_value_;
};

class ListOption : public MenuOption {
   public:
    ListOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::vector<std::string> values, std::function<std::string()> current_value_getter, std::function<void(const std::string&)> new_value_setter)
        : MenuOption(cap, grp),
          value_list_(std::move(values)),
          getter_(std::move(current_value_getter)),
          setter_(std::move(new_value_setter)) {
        sync();
    }

    void sync() {
        std::string current_value = getter_();
        for (size_t i = 0; i < value_list_.size(); ++i) {
            if (value_list_[i] == current_value) {
                list_index_ = i;
                return;
            }
        }
    }

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
    [[nodiscard]] auto getValueAsString() const -> std::string override {
        return value_list_.empty() ? "" : value_list_[list_index_];
    }
    void adjustValue(bool adjust_up) override {
        if (value_list_.empty()) {
            return;
        }
        size_t size = value_list_.size();
        list_index_ = adjust_up ? (list_index_ + 1) % size
                                : (list_index_ + size - 1) % size;
        setter_(value_list_[list_index_]);
    }
    auto getMaxValueWidth(Text* text_renderer) const -> int override {
        int max_w = 0;
        for (const auto& val : value_list_) {
            max_w = std::max(max_w, text_renderer->length(val, -2));
        }
        return max_w;
    }

   private:
    std::vector<std::string> value_list_;
    std::function<std::string()> getter_;
    std::function<void(const std::string&)> setter_;
    size_t list_index_{0};
};

class FolderOption : public MenuOption {
   public:
    FolderOption(const std::string& cap, ServiceMenu::SettingsGroup grp, ServiceMenu::SettingsGroup target)
        : MenuOption(cap, grp),
          target_group_(target) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::SELECT; }
    [[nodiscard]] auto getTargetGroup() const -> ServiceMenu::SettingsGroup override { return target_group_; }

   private:
    ServiceMenu::SettingsGroup target_group_;
};

class ActionOption : public MenuOption {
   public:
    ActionOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::function<void()> action, bool hidden = false)
        : MenuOption(cap, grp, hidden),
          action_(std::move(action)) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::SELECT; }
    void executeAction() override {
        if (action_) {
            action_();
        }
    }

   private:
    std::function<void()> action_;
};

// Opción de lista con acción
class ActionListOption : public MenuOption {
   public:
    using ValueGetter = std::function<std::string()>;
    using ValueSetter = std::function<void(const std::string&)>;
    using ActionExecutor = std::function<void()>;

    ActionListOption(const std::string& caption, ServiceMenu::SettingsGroup group, std::vector<std::string> options, ValueGetter getter, ValueSetter setter, ActionExecutor action_executor, bool hidden = false)
        : MenuOption(caption, group, hidden),
          options_(std::move(options)),
          value_getter_(std::move(getter)),
          value_setter_(std::move(setter)),
          action_executor_(std::move(action_executor)) {
        updateCurrentIndex();
    }

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::BOTH; }
    [[nodiscard]] auto getValueAsString() const -> std::string override;
    [[nodiscard]] auto getMaxValueWidth(Text* text) const -> int override;
    void adjustValue(bool up) override;
    void executeAction() override;
    void sync();  // Sincroniza con el valor actual

   private:
    std::vector<std::string> options_;
    ValueGetter value_getter_;
    ValueSetter value_setter_;
    ActionExecutor action_executor_;
    size_t current_index_{0};

    void updateCurrentIndex();
    [[nodiscard]] auto findCurrentIndex() const -> size_t;
};

// Opción genérica con callbacks: getter para mostrar, adjuster(bool up) para cambiar valor
class CallbackOption : public MenuOption {
   public:
    CallbackOption(const std::string& cap, ServiceMenu::SettingsGroup grp, std::function<std::string()> getter, std::function<void(bool)> adjuster, std::function<int(Text*)> max_width_fn = nullptr)
        : MenuOption(cap, grp),
          getter_(std::move(getter)),
          adjuster_(std::move(adjuster)),
          max_width_fn_(std::move(max_width_fn)) {}

    [[nodiscard]] auto getBehavior() const -> Behavior override { return Behavior::ADJUST; }
    [[nodiscard]] auto getValueAsString() const -> std::string override { return getter_(); }
    void adjustValue(bool adjust_up) override { adjuster_(adjust_up); }
    auto getMaxValueWidth(Text* text_renderer) const -> int override {
        return max_width_fn_ ? max_width_fn_(text_renderer) : 0;
    }

   private:
    std::function<std::string()> getter_;
    std::function<void(bool)> adjuster_;
    std::function<int(Text*)> max_width_fn_;
};