#include "core/rendering/render_info.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para transform
#include <cmath>      // Para round, floor
#include <iomanip>    // Para setprecision
#include <sstream>    // Para ostringstream
#include <string>     // Para string

#include "core/rendering/screen.hpp"   // Para Screen
#include "core/rendering/surface.hpp"  // Para Surface
#include "core/rendering/text.hpp"     // Para Text
#include "game/options.hpp"            // Para Options
#include "game/ui/console.hpp"         // Para Console
#include "game/ui/notifier.hpp"        // Para Notifier
#include "utils/utils.hpp"             // Para prettyName

// [SINGLETON]
RenderInfo* RenderInfo::render_info = nullptr;

// [SINGLETON] Crearemos el objeto con esta función estática
void RenderInfo::init() {
    RenderInfo::render_info = new RenderInfo();
}

// [SINGLETON] Destruiremos el objeto con esta función estática
void RenderInfo::destroy() {
    delete RenderInfo::render_info;
    RenderInfo::render_info = nullptr;
}

// [SINGLETON] Con este método obtenemos el objeto y podemos trabajar con él
auto RenderInfo::get() -> RenderInfo* {
    return RenderInfo::render_info;
}

// Constructor: en DEBUG se activa inmediatamente (notifica a Notifier del offset)
RenderInfo::RenderInfo() {
#ifdef _DEBUG
    toggle();
#endif
}

// Actualiza la animación de entrada/salida del overlay
void RenderInfo::update(float delta_time) {
    switch (status_) {
        case Status::RISING:
            y_ += SLIDE_SPEED * delta_time;
            if (y_ >= 0.0F) {
                y_ = 0.0F;
                status_ = Status::ACTIVE;
            }
            break;
        case Status::VANISHING:
            y_ -= SLIDE_SPEED * delta_time;
            if (y_ <= static_cast<float>(-HEIGHT)) {
                y_ = static_cast<float>(-HEIGHT);
                status_ = Status::HIDDEN;
            }
            break;
        default:
            break;
    }
}

// Renderiza el overlay de información por pantalla
void RenderInfo::render() const {
    if (status_ == Status::HIDDEN) { return; }

    // FPS
    std::string line = std::to_string(Screen::get()->getLastFPS()) + " fps";

    // Driver GPU
    const auto& driver = Screen::get()->getGPUDriver();
    line += " | " + (driver.empty() ? std::string("sdl") : driver);

    // Zoom calculado (alto físico / alto lógico), con coma decimal y sin ceros innecesarios
    const float ROUNDED = std::round(Screen::get()->getZoomFactor() * 100.0F) / 100.0F;
    std::string zoom_str;
    if (ROUNDED == std::floor(ROUNDED)) {
        zoom_str = std::to_string(static_cast<int>(ROUNDED));
    } else {
        std::ostringstream oss;
        oss << std::fixed << std::setprecision(2) << ROUNDED;
        zoom_str = oss.str();
        if (zoom_str.back() == '0') { zoom_str.pop_back(); }
        std::ranges::replace(zoom_str, '.', ',');
    }
    line += " | " + zoom_str + "x";

    // PostFX: muestra shader + preset, o nada si está desactivado
    if (Options::video.shader.enabled) {
        const bool IS_CRTPI = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI);
        const std::string SHADER_NAME = IS_CRTPI ? "crtpi" : "postfx";
        std::string preset_name = "-";
        if (IS_CRTPI) {
            if (!Options::crtpi_presets.empty()) {
                preset_name = prettyName(Options::crtpi_presets[static_cast<size_t>(Options::video.shader.current_crtpi_preset)].name);
            }
        } else {
            if (!Options::postfx_presets.empty()) {
                preset_name = prettyName(Options::postfx_presets[static_cast<size_t>(Options::video.shader.current_postfx_preset)].name);
            }
        }
        line += " | " + SHADER_NAME + " " + preset_name;
    }

    // Todo en lowercase
    std::ranges::transform(line, line.begin(), [](unsigned char c) { return std::tolower(c); });

    // Constantes visuales (igual que Console)
    static constexpr Uint8 BG_COLOR = 0;   // PaletteColor::BLACK
    static constexpr Uint8 MSG_COLOR = 9;  // PaletteColor::BRIGHT_GREEN
    static constexpr int TEXT_SIZE = 6;
    static constexpr int PADDING_V = (TEXT_SIZE / 2) - 1;

    // Fuente: preferir la de la consola si está disponible
    auto text_obj = (Console::get() != nullptr) ? Console::get()->getText() : Screen::get()->getText();

    // Posición Y: debajo de la consola + altura animada de la pila de notificaciones + offset animado propio
    const int CONSOLE_Y = (Console::get() != nullptr) ? Console::get()->getVisibleHeight() : 0;
    const int NOTIFIER_Y = (Notifier::get() != nullptr) ? Notifier::get()->getVisibleHeight() : 0;
    const int Y = CONSOLE_Y + NOTIFIER_Y + static_cast<int>(y_);

    // Rectángulo de fondo: ancho completo, alto ajustado al texto
    const SDL_FRect RECT = {
        .x = 0.0F,
        .y = static_cast<float>(Y),
        .w = Options::game.width,
        .h = static_cast<float>(TEXT_SIZE + (PADDING_V * 2))};

    auto game_surface = Screen::get()->getGameSurface();
    game_surface->fillRect(&RECT, BG_COLOR);
    // game_surface->drawRectBorder(&RECT, BORDER_COLOR);
    text_obj->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG,
        static_cast<int>(Options::game.width / 2),
        Y + PADDING_V,
        line,
        1,
        MSG_COLOR);
}

// Activa o desactiva el overlay (la posición Y se calcula pull-side en render())
void RenderInfo::toggle() {
    switch (status_) {
        case Status::HIDDEN:
            status_ = Status::RISING;
            Screen::get()->updateZoomFactor();
            break;
        case Status::ACTIVE:
            status_ = Status::VANISHING;
            break;
        default:
            break;
    }
}
