#include "menu_renderer.h"

#include <algorithm>  // Para max
#include <utility>    // Para pair, move

#include "color.h"        // Para Color, generateMirroredCycle, ColorCycleStyle
#include "menu_option.h"  // Para MenuOption
#include "param.h"        // Para Param, param, ParamServiceMenu, ParamGame
#include "screen.h"       // Para Screen
#include "text.h"         // Para Text, TEXT_CENTER, TEXT_COLOR
#include "utils.h"        // Para Zone

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)) {}

void MenuRenderer::render(const ServiceMenu *menu_state) {
    // Dibuja la sombra
    if (param.service_menu.drop_shadow) {
        SDL_FRect shadow_rect = {rect_.x + 5, rect_.y + 5, rect_.w, 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_);

    // Dibuja el título
    float y = rect_.y + title_padding_;
    title_text_->writeDX(TEXT_COLOR | TEXT_CENTER, param.game.game_area.center_x, 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 las opciones
    y = options_y_;
    const auto &option_pairs = menu_state->getOptionPairs();
    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;

        if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
            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_->lenght(option_pairs.at(i).second, -2);
            element_text_->writeColored(X, y, option_pairs.at(i).second, current_color, -2);
        } else {
            element_text_->writeDX(TEXT_CENTER | TEXT_COLOR, rect_.x + rect_.w / 2, y, option_pairs.at(i).first, -2, current_color);
        }
        y += options_height_ + options_padding_;
    }
}

void MenuRenderer::update(const ServiceMenu *menu_state) {
    if (resizing_) {
        updateResizeAnimation();
    }
    updateColorCounter();
    param.service_menu.selected_color = getAnimatedSelectedColor();
}

void MenuRenderer::onLayoutChanged(const ServiceMenu *menu_state) {
    // Cuando la lógica del menú notifica un cambio, el renderer recalcula su layout
    precalculateMenuWidths(menu_state->getAllOptions(), menu_state);
    setAnchors(menu_state);
    resize(menu_state);
}

void MenuRenderer::setLayout(const ServiceMenu *menu_state) {
    // Cuando la lógica del menú notifica un cambio, el renderer recalcula su layout
    precalculateMenuWidths(menu_state->getAllOptions(), menu_state);
    setAnchors(menu_state);
    setSize(menu_state);
}

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

    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_ = getMenuWidthForGroup(menu_state->getCurrentGroup());
    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);
    height_ = upper_height_ + lower_height_;

    return {(param.game.width - width_) / 2.0F, (param.game.height - height_) / 2.0F, (float)width_, (float)height_};
}

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

    if (rect_.x != new_rect.x || rect_.y != new_rect.y || rect_.w != new_rect.w || rect_.h != new_rect.h) {
        rect_anim_from_ = rect_;
        rect_anim_to_ = new_rect;
        resize_anim_step_ = 0;
        resizing_ = true;
    } else {
        rect_ = setRect(new_rect);
        resizing_ = false;
    }
    options_y_ = new_rect.y + upper_height_ + lower_padding_;
}

void MenuRenderer::setSize(const ServiceMenu *menu_state) {
    rect_ = setRect(calculateNewRect(menu_state));
    resizing_ = false;
    options_y_ = rect_.y + upper_height_ + lower_padding_;
}

void MenuRenderer::updateResizeAnimation() {
    if (!resizing_) {
        return;
    }
    ++resize_anim_step_;
    float t = static_cast<float>(resize_anim_step_) / resize_anim_steps_;
    if (t >= 1.0F) {
        rect_ = setRect(rect_anim_to_);
        resizing_ = false;
        return;
    }
    float ease = 1 - (1 - t) * (1 - t);
    SDL_FRect rect =
        {rect_anim_from_.x + (rect_anim_to_.x - rect_anim_from_.x) * ease,
            rect_anim_from_.y + (rect_anim_to_.y - rect_anim_from_.y) * ease,
            rect_anim_from_.w + (rect_anim_to_.w - rect_anim_from_.w) * ease,
            rect_anim_from_.h + (rect_anim_to_.h - rect_anim_from_.h) * ease};
    rect_ = setRect(rect);
}

void MenuRenderer::precalculateMenuWidths(const std::vector<std::unique_ptr<MenuOption>> &all_options, const ServiceMenu *menu_state) {
    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_->lenght(option->getCaption(), -2));
            if (menu_state->getCurrentGroupAlignment() == ServiceMenu::GroupAlignment::LEFT) {
                max_value_width = std::max(max_value_width, option->getMaxValueWidth(element_text_.get()));
            }
        }
        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;
        }
        group_menu_widths_[group] = std::max((int)ServiceMenu::MIN_WIDTH, (int)total_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();
    Uint64 current_ticks = SDL_GetTicks();
    if (current_ticks - last_update_ >= 50) {
        color_counter_++;
        last_update_ = current_ticks;
    }
}

auto MenuRenderer::getAnimatedSelectedColor() const -> Color {
    static auto color_cycle_ = 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_ = {rect.x - 1, rect.y + 1, rect.w + 2, rect.h - 2};
    return rect;
}