#include "game/ui/menu_renderer.hpp"

#include <algorithm>
#include <utility>

#include "core/rendering/screen.hpp"
#include "core/rendering/text.hpp"
#include "game/ui/menu_option.hpp"
#include "utils/color.hpp"
#include "utils/defines.hpp"  // Para Texts::VERSION
#include "utils/param.hpp"
#include "utils/utils.hpp"
#include "version.h"  // Para Version::GIT_HASH

// --- Implementación de las estructuras de animación ---

void MenuRenderer::ResizeAnimation::start(float from_w, float from_h, float to_w, float to_h) {
    start_width = from_w;
    start_height = from_h;
    target_width = to_w;
    target_height = to_h;
    elapsed = 0.0F;
    active = true;
}
void MenuRenderer::ResizeAnimation::stop() {
    active = false;
    elapsed = 0.0F;
}

void MenuRenderer::ShowHideAnimation::startShow(float to_w, float to_h) {
    type = Type::SHOWING;
    target_width = to_w;
    target_height = to_h;
    elapsed = 0.0F;
    active = true;
}
void MenuRenderer::ShowHideAnimation::startHide() {
    type = Type::HIDING;
    elapsed = 0.0F;
    active = true;
}
void MenuRenderer::ShowHideAnimation::stop() {
    type = Type::NONE;
    active = false;
    elapsed = 0.0F;
}

MenuRenderer::MenuRenderer(const ServiceMenu* menu_state, std::shared_ptr<Text> element_text, std::shared_ptr<Text> title_text)
    : element_text_(std::move(element_text)),
      title_text_(std::move(title_text)) {
    initializeMaxSizes();
    setPosition(param.game.game_area.center_x, param.game.game_area.center_y, PositionMode::CENTERED);
}

void MenuRenderer::render(const ServiceMenu* menu_state) {
    if (!visible_) {
        return;
    }

    // Dibuja la sombra
    if (param.service_menu.drop_shadow) {
        SDL_FRect shadow_rect = {.x = rect_.x + 5, .y = rect_.y + 5, .w = rect_.w, .h = rect_.h};
        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 64);
        SDL_RenderFillRect(Screen::get()->getRenderer(), &shadow_rect);
    }

    // Dibuja el fondo
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.service_menu.bg_color.r, param.service_menu.bg_color.g, param.service_menu.bg_color.b, param.service_menu.bg_color.a);
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);

    // Dibuja el borde
    const Color BORDER_COLOR = param.service_menu.title_color.DARKEN();
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255);
    SDL_RenderRect(Screen::get()->getRenderer(), &rect_);
    SDL_RenderRect(Screen::get()->getRenderer(), &border_rect_);

    // Solo renderizar contenido si la animación lo permite
    if (shouldShowContent()) {
        // Recorta todas las operaciones de texto y líneas al rect actual del panel
        // para que durante el resize animation nada se pinte fuera del borde.
        auto* renderer = Screen::get()->getRenderer();
        const SDL_Rect CLIP_RECT = {
            .x = static_cast<int>(rect_.x),
            .y = static_cast<int>(rect_.y),
            .w = static_cast<int>(rect_.w),
            .h = static_cast<int>(rect_.h),
        };
        SDL_SetRenderClipRect(renderer, &CLIP_RECT);

        // Dibuja el título
        float y = rect_.y + title_padding_;
        title_text_->writeDX(Text::COLOR | Text::CENTER, rect_.x + (rect_.w / 2.0F), y, menu_state->getTitle(), -4, param.service_menu.title_color);

        // Dibuja la línea separadora
        y = rect_.y + upper_height_;
        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), BORDER_COLOR.r, BORDER_COLOR.g, BORDER_COLOR.b, 255);
        SDL_RenderLine(Screen::get()->getRenderer(), rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y);

        // Dibuja el subtítulo del grupo (versión + hash en SYSTEM)
        if (groupHasSubtitle(menu_state->getCurrentGroup())) {
            const std::string SUBTITLE = "ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")";
            element_text_->writeDX(
                Text::CENTER | Text::COLOR,
                rect_.x + (rect_.w / 2.0F),
                y + options_padding_,
                SUBTITLE,
                -2,
                param.service_menu.text_color);
        }

        // Dibuja las opciones
        y = options_y_;
        const auto& option_pairs = menu_state->getOptionPairs();
        const float ROW_HEIGHT = static_cast<float>(options_height_ + options_padding_);

        for (size_t i = 0; i < option_pairs.size(); ++i) {
            const bool IS_SELECTED = (i == menu_state->getSelectedIndex());
            const Color& current_color = IS_SELECTED ? param.service_menu.selected_color : param.service_menu.text_color;

            // Offset Y del valor durante la animación de swap: va desde la fila
            // origen (la otra implicada) hasta su fila natural con easeOut.
            float value_y_offset = 0.0F;
            if (swap_animation_.active && (i == swap_animation_.idx_a || i == swap_animation_.idx_b)) {
                const size_t OTHER = (i == swap_animation_.idx_a) ? swap_animation_.idx_b : swap_animation_.idx_a;
                const float PROGRESS = easeOut(swap_animation_.elapsed / swap_animation_.duration);
                const float DELTA_ROWS = static_cast<float>(OTHER) - static_cast<float>(i);
                value_y_offset = DELTA_ROWS * ROW_HEIGHT * (1.0F - PROGRESS);
            }

            if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
                const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - element_text_->length(option_pairs.at(i).first, -2) - ServiceMenu::MIN_GAP_OPTION_VALUE;
                std::string truncated_value = getTruncatedValue(option_pairs.at(i).second, AVAILABLE_WIDTH);
                element_text_->writeColored(rect_.x + ServiceMenu::OPTIONS_HORIZONTAL_PADDING, y, option_pairs.at(i).first, current_color, -2);
                const int X = rect_.x + rect_.w - ServiceMenu::OPTIONS_HORIZONTAL_PADDING - element_text_->length(truncated_value, -2);
                element_text_->writeColored(X, static_cast<int>(y + value_y_offset), truncated_value, current_color, -2);
            } else {
                const int AVAILABLE_WIDTH = rect_.w - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
                std::string truncated_caption = getTruncatedValue(option_pairs.at(i).first, AVAILABLE_WIDTH);
                element_text_->writeDX(Text::CENTER | Text::COLOR, rect_.x + (rect_.w / 2.0F), y, truncated_caption, -2, current_color);
            }
            y += ROW_HEIGHT;
        }

        SDL_SetRenderClipRect(renderer, nullptr);
    }
}

void MenuRenderer::update(const ServiceMenu* menu_state, float delta_time) {
    updateAnimations(delta_time);

    if (visible_) {
        updateColorCounter();
        param.service_menu.selected_color = getAnimatedSelectedColor();
    }
}

// --- Nuevos métodos de control ---

void MenuRenderer::show(const ServiceMenu* menu_state) {
    if (visible_) {
        return;
    }
    visible_ = true;

    // Calcula el tamaño final y lo usa para la animación
    SDL_FRect target_rect = calculateNewRect(menu_state);

    // Detener cualquier animación anterior
    resize_animation_.stop();

    // Iniciar animación de mostrar
    show_hide_animation_.startShow(target_rect.w, target_rect.h);

    // El tamaño inicial es cero para la animación
    rect_.w = 0.0F;
    rect_.h = 0.0F;
    updatePosition();
}

void MenuRenderer::hide() {
    if (!visible_ || show_hide_animation_.type == ShowHideAnimation::Type::HIDING) {
        return;
    }

    // Detener animación de resize si la hubiera
    resize_animation_.stop();

    // Guardar tamaño actual para la animación de ocultar
    show_hide_animation_.target_width = rect_.w;
    show_hide_animation_.target_height = rect_.h;
    show_hide_animation_.startHide();
}

void MenuRenderer::setPosition(float x, float y, PositionMode mode) {
    anchor_x_ = x;
    anchor_y_ = y;
    position_mode_ = mode;
    updatePosition();
}

// --- Métodos de layout ---

void MenuRenderer::onLayoutChanged(const ServiceMenu* menu_state) {
    precalculateMenuWidths(menu_state->getAllOptions(), menu_state);
    setAnchors(menu_state);
    resize(menu_state);
}

void MenuRenderer::setLayout(const ServiceMenu* menu_state) {
    precalculateMenuWidths(menu_state->getAllOptions(), menu_state);
    setAnchors(menu_state);
    setSize(menu_state);
}

void MenuRenderer::initializeMaxSizes() {
    max_menu_width_ = static_cast<size_t>(param.game.game_area.rect.w * 0.9F);
    max_menu_height_ = static_cast<size_t>(param.game.game_area.rect.h * 0.9F);
}

void MenuRenderer::setAnchors(const ServiceMenu* menu_state) {
    size_t max_entries = 0;
    for (int i = 0; i < 5; ++i) {
        max_entries = std::max(max_entries, menu_state->countOptionsInGroup(static_cast<ServiceMenu::SettingsGroup>(i)));
    }

    options_height_ = element_text_->getCharacterSize();
    options_padding_ = 5;
    title_height_ = title_text_->getCharacterSize();
    title_padding_ = title_height_ / 2;
    upper_height_ = (title_padding_ * 2) + title_height_;
    lower_padding_ = (options_padding_ * 3);
    lower_height_ = ((max_entries > 0 ? max_entries - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2);

    width_ = ServiceMenu::MIN_WIDTH;
    height_ = upper_height_ + lower_height_;
}

auto MenuRenderer::calculateNewRect(const ServiceMenu* menu_state) -> SDL_FRect {
    width_ = std::min(static_cast<size_t>(getMenuWidthForGroup(menu_state->getCurrentGroup())), max_menu_width_);
    const auto& display_options = menu_state->getDisplayOptions();
    lower_height_ = ((!display_options.empty() ? display_options.size() - 1 : 0) * (options_height_ + options_padding_)) + options_height_ + (lower_padding_ * 2);
    subtitle_offset_ = getGroupSubtitleHeight(menu_state);
    height_ = std::min(upper_height_ + subtitle_offset_ + lower_height_, max_menu_height_);

    SDL_FRect new_rect = {.x = 0, .y = 0, .w = static_cast<float>(width_), .h = static_cast<float>(height_)};

    // La posición x, y se establecerá en `updatePosition`
    return new_rect;
}

// Grupos que muestran una línea de subtítulo bajo el título (antes de las opciones).
auto MenuRenderer::groupHasSubtitle(ServiceMenu::SettingsGroup group) -> bool {
    return group == ServiceMenu::SettingsGroup::SYSTEM;
}

// Altura extra reservada para el subtítulo del grupo actual.
// Reservamos el alto de una línea de element_text_ más un pequeño padding.
auto MenuRenderer::getGroupSubtitleHeight(const ServiceMenu* menu_state) const -> size_t {
    if (!groupHasSubtitle(menu_state->getCurrentGroup())) { return 0; }
    return element_text_->getCharacterSize() + options_padding_;
}

void MenuRenderer::resize(const ServiceMenu* menu_state) {
    SDL_FRect new_rect = calculateNewRect(menu_state);

    if (rect_.w != new_rect.w || rect_.h != new_rect.h) {
        // En lugar de la animación antigua, usamos la nueva
        resize_animation_.start(rect_.w, rect_.h, new_rect.w, new_rect.h);
    } else {
        // Si no hay cambio de tamaño, solo actualizamos la posición
        updatePosition();
    }
    options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
}

void MenuRenderer::setSize(const ServiceMenu* menu_state) {
    SDL_FRect new_rect = calculateNewRect(menu_state);
    rect_.w = new_rect.w;
    rect_.h = new_rect.h;

    show_hide_animation_.stop();
    resize_animation_.stop();

    updatePosition();
    options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
    border_rect_ = {.x = rect_.x - 1, .y = rect_.y + 1, .w = rect_.w + 2, .h = rect_.h - 2};
}

// --- Métodos de animación y posición ---

void MenuRenderer::startSwapAnimation(size_t idx_a, size_t idx_b) {
    if (idx_a == idx_b) { return; }
    swap_animation_.active = true;
    swap_animation_.idx_a = idx_a;
    swap_animation_.idx_b = idx_b;
    swap_animation_.elapsed = 0.0F;
}

void MenuRenderer::updateSwapAnimation(float delta_time) {
    swap_animation_.elapsed += delta_time;
    if (swap_animation_.elapsed >= swap_animation_.duration) {
        swap_animation_.active = false;
        swap_animation_.elapsed = 0.0F;
    }
}

void MenuRenderer::updateAnimations(float delta_time) {
    if (show_hide_animation_.active) {
        updateShowHideAnimation(delta_time);
    }
    if (swap_animation_.active) {
        updateSwapAnimation(delta_time);
    }
    if (resize_animation_.active) {
        updateResizeAnimation(delta_time);
    }
}

void MenuRenderer::updateShowHideAnimation(float delta_time) {
    show_hide_animation_.elapsed += delta_time;
    float duration = show_hide_animation_.duration;

    if (show_hide_animation_.elapsed >= duration) {
        if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) {
            rect_.w = show_hide_animation_.target_width;
            rect_.h = show_hide_animation_.target_height;
        } else if (show_hide_animation_.type == ShowHideAnimation::Type::HIDING) {
            rect_.w = 0.0F;
            rect_.h = 0.0F;
            visible_ = false;
        }
        show_hide_animation_.stop();
        updatePosition();
    } else {
        float progress = easeOut(show_hide_animation_.elapsed / duration);
        if (show_hide_animation_.type == ShowHideAnimation::Type::SHOWING) {
            rect_.w = show_hide_animation_.target_width * progress;
            rect_.h = show_hide_animation_.target_height * progress;
        } else if (show_hide_animation_.type == ShowHideAnimation::Type::HIDING) {
            rect_.w = show_hide_animation_.target_width * (1.0F - progress);
            rect_.h = show_hide_animation_.target_height * (1.0F - progress);
        }
        updatePosition();
    }
    options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
}

void MenuRenderer::updateResizeAnimation(float delta_time) {
    resize_animation_.elapsed += delta_time;
    float duration = resize_animation_.duration;

    if (resize_animation_.elapsed >= duration) {
        rect_.w = resize_animation_.target_width;
        rect_.h = resize_animation_.target_height;
        resize_animation_.stop();
        updatePosition();
    } else {
        float progress = easeOut(resize_animation_.elapsed / duration);
        rect_.w = resize_animation_.start_width + ((resize_animation_.target_width - resize_animation_.start_width) * progress);
        rect_.h = resize_animation_.start_height + ((resize_animation_.target_height - resize_animation_.start_height) * progress);
        updatePosition();
    }
    options_y_ = rect_.y + upper_height_ + subtitle_offset_ + lower_padding_;
}

void MenuRenderer::updatePosition() {
    switch (position_mode_) {
        case PositionMode::CENTERED:
            rect_.x = anchor_x_ - (rect_.w / 2.0F);
            rect_.y = anchor_y_ - (rect_.h / 2.0F);
            break;
        case PositionMode::FIXED:
            rect_.x = anchor_x_;
            rect_.y = anchor_y_;
            break;
    }
    // Actualizar el rectángulo del borde junto con el principal
    border_rect_ = {.x = rect_.x - 1, .y = rect_.y + 1, .w = rect_.w + 2, .h = rect_.h - 2};
}

// Resto de métodos (sin cambios significativos)

void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<MenuOption>>& all_options, const ServiceMenu* menu_state) {  // NOLINT(readability-named-parameter)
    for (int& w : group_menu_widths_) {
        w = ServiceMenu::MIN_WIDTH;
    }
    for (int group = 0; group < 5; ++group) {
        auto sg = static_cast<ServiceMenu::SettingsGroup>(group);
        int max_option_width = 0;
        int max_value_width = 0;
        for (const auto& option : all_options) {
            if (option->getGroup() != sg) {
                continue;
            }
            max_option_width = std::max(max_option_width, element_text_->length(option->getCaption(), -2));
            if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
                // Usar getMaxValueWidth() para considerar TODOS los valores posibles de la opción
                int option_max_value_width = option->getMaxValueWidth(element_text_.get());
                int max_available_value_width = static_cast<int>(max_menu_width_) - max_option_width - (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2) - ServiceMenu::MIN_GAP_OPTION_VALUE;

                if (option_max_value_width <= max_available_value_width) {
                    // Si el valor más largo cabe, usar su ancho real
                    max_value_width = std::max(max_value_width, option_max_value_width);
                } else {
                    // Si no cabe, usar el ancho disponible (será truncado)
                    max_value_width = std::max(max_value_width, max_available_value_width);
                }
            }
        }
        size_t total_width = max_option_width + (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
        if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
            total_width += ServiceMenu::MIN_GAP_OPTION_VALUE + max_value_width;
        }
        // Si el grupo tiene subtítulo, ensanchamos lo necesario para que quepa
        if (groupHasSubtitle(sg)) {
            const std::string SUBTITLE = "ver. " + std::string(Texts::VERSION) + " (" + std::string(Version::GIT_HASH) + ")";
            const size_t SUBTITLE_WIDTH = static_cast<size_t>(element_text_->length(SUBTITLE, -2)) + (ServiceMenu::OPTIONS_HORIZONTAL_PADDING * 2);
            total_width = std::max(total_width, SUBTITLE_WIDTH);
        }
        group_menu_widths_[group] = std::min(std::max(static_cast<int>(ServiceMenu::MIN_WIDTH), static_cast<int>(total_width)), static_cast<int>(max_menu_width_));
    }
}

auto MenuRenderer::getMenuWidthForGroup(ServiceMenu::SettingsGroup group) const -> int { return group_menu_widths_[static_cast<int>(group)]; }
void MenuRenderer::updateColorCounter() {
    static Uint64 last_update_ = SDL_GetTicks();
    if (SDL_GetTicks() - last_update_ >= 50) {
        color_counter_++;
        last_update_ = SDL_GetTicks();
    }
}
auto MenuRenderer::getAnimatedSelectedColor() const -> Color {
    static auto color_cycle_ = Colors::generateMirroredCycle(param.service_menu.selected_color, ColorCycleStyle::HUE_WAVE);
    return color_cycle_.at(color_counter_ % color_cycle_.size());
}
auto MenuRenderer::setRect(SDL_FRect rect) -> SDL_FRect {
    border_rect_ = {.x = rect.x - 1, .y = rect.y + 1, .w = rect.w + 2, .h = rect.h - 2};
    return rect;
}
auto MenuRenderer::getTruncatedValueWidth(const std::string& value, int available_width) const -> int {
    int value_width = element_text_->length(value, -2);
    if (value_width <= available_width) {
        return value_width;
    }

    // Calculamos cuántos caracteres podemos mostrar más los puntos suspensivos
    // Estimamos el ancho de los puntos suspensivos como 3 caracteres promedio
    int ellipsis_width = element_text_->length("...", -2);
    int available_for_text = available_width - ellipsis_width;

    if (available_for_text <= 0) {
        return ellipsis_width;  // Solo mostramos los puntos suspensivos
    }

    // Calculamos aproximadamente cuántos caracteres caben
    float char_width = static_cast<float>(value_width) / value.length();
    auto max_chars = static_cast<size_t>(available_for_text / char_width);

    // Verificamos el ancho real del texto truncado
    std::string truncated = truncateWithEllipsis(value, max_chars);
    return element_text_->length(truncated, -2);
}

auto MenuRenderer::getTruncatedValue(const std::string& value, int available_width) const -> std::string {
    int value_width = element_text_->length(value, -2);
    if (value_width <= available_width) {
        return value;
    }

    // Calculamos cuántos caracteres podemos mostrar
    int ellipsis_width = element_text_->length("...", -2);
    int available_for_text = available_width - ellipsis_width;

    if (available_for_text <= 0) {
        return "...";  // Solo mostramos los puntos suspensivos
    }

    // Calculamos aproximadamente cuántos caracteres caben
    float char_width = static_cast<float>(value_width) / value.length();
    auto max_chars = static_cast<size_t>(available_for_text / char_width);

    // Ajustamos iterativamente hasta que el texto quepa
    std::string truncated = truncateWithEllipsis(value, max_chars);
    while (element_text_->length(truncated, -2) > available_width && max_chars > 1) {
        max_chars--;
        truncated = truncateWithEllipsis(value, max_chars);
    }

    return truncated;
}

auto MenuRenderer::easeOut(float t) -> float { return 1.0F - ((1.0F - t) * (1.0F - t)); }
auto MenuRenderer::shouldShowContent() const -> bool { return !show_hide_animation_.active; }