// init_hud_animator.cpp - Implementación de la animación inicial del HUD

#include "game/systems/init_hud_animator.hpp"

#include <SDL3/SDL.h>

#include <algorithm>
#include <string>

#include "core/defaults.hpp"
#include "core/defaults/hud.hpp"
#include "core/math/easing.hpp"
#include "core/rendering/line_renderer.hpp"
#include "core/rendering/shape_renderer.hpp"

namespace Systems::InitHud {

    auto computeRangeProgress(float global_progress,
        float ratio_init,
        float ratio_end) -> float {
        if (ratio_init >= ratio_end) {
            return (global_progress >= ratio_end) ? 1.0F : 0.0F;
        }
        if (global_progress < ratio_init) {
            return 0.0F;
        }
        if (global_progress > ratio_end) {
            return 1.0F;
        }
        return (global_progress - ratio_init) / (ratio_end - ratio_init);
    }

    auto computeShipPosition(float progress, const Vec2& final_position) -> Vec2 {
        const float EASED = Easing::easeOutQuad(progress);
        const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
        // Y inicial: bajo la zone de juego (sale desde fuera).
        const float Y_INI = zone.y + zone.h + Defaults::Hud::InitAnim::SHIP_SPAWN_Y_OFFSET;
        const float Y_ANIM = Y_INI + ((final_position.y - Y_INI) * EASED);
        return Vec2{.x = final_position.x, .y = Y_ANIM};
    }

    void drawBordersAnimated(Rendering::Renderer* renderer, float progress) {
        const SDL_FRect& zone = Defaults::Zones::PLAYAREA;
        const float EASED = Easing::easeOutQuad(progress);

        const int X1 = static_cast<int>(zone.x);
        const int Y1 = static_cast<int>(zone.y);
        const int X2 = static_cast<int>(zone.x + zone.w);
        const int Y2 = static_cast<int>(zone.y + zone.h);
        const int CX = (X1 + X2) / 2;

        constexpr float PHASE_1_END = Defaults::Hud::InitAnim::BORDER_PHASE_1_END;
        constexpr float PHASE_2_END = Defaults::Hud::InitAnim::BORDER_PHASE_2_END;

        // Fase 1: línea superior crece desde el centro hacia los lados.
        if (EASED > 0.0F) {
            const float P = std::min(EASED / PHASE_1_END, 1.0F);
            const int X_LEFT = static_cast<int>(CX - ((CX - X1) * P));
            const int X_RIGHT = static_cast<int>(CX + ((X2 - CX) * P));
            Rendering::linea(renderer, CX, Y1, X_LEFT, Y1);
            Rendering::linea(renderer, CX, Y1, X_RIGHT, Y1);
        }

        // Fase 2: laterales bajan.
        if (EASED > PHASE_1_END) {
            const float P = std::min((EASED - PHASE_1_END) / (PHASE_2_END - PHASE_1_END), 1.0F);
            const int Y_BOTTOM = static_cast<int>(Y1 + ((Y2 - Y1) * P));
            Rendering::linea(renderer, X1, Y1, X1, Y_BOTTOM);
            Rendering::linea(renderer, X2, Y1, X2, Y_BOTTOM);
        }

        // Fase 3: línea inferior crece desde los lados hacia el centro.
        if (EASED > PHASE_2_END) {
            const float P = (EASED - PHASE_2_END) / (1.0F - PHASE_2_END);
            const int X_LEFT = static_cast<int>(X1 + ((CX - X1) * P));
            const int X_RIGHT = static_cast<int>(X2 - ((X2 - CX) * P));
            Rendering::linea(renderer, X1, Y2, X_LEFT, Y2);
            Rendering::linea(renderer, X2, Y2, X_RIGHT, Y2);
        }
    }

    namespace {

        // Nombre de slots de vides: una nau menys que el màxim (la nau en joc
        // no es dibuixa; els slots són repuestos). Deriva de MAX_VIDES.
        constexpr int NUM_SLOTS = Defaults::Game::MAX_VIDES - 1;

        // Pas d'un dígit (amplada + tracking, escalat): és la diferència entre
        // l'ample de dos caràcters i el d'un. Marca el ritme de tot el bloc.
        auto digitPitch(float scale, float spacing) -> float {
            return Graphics::VectorText::getTextWidth("00", scale, spacing) -
                Graphics::VectorText::getTextWidth("0", scale, spacing);
        }

        // Mida d'un slot = alçada de la majúscula del dígit (mètrica del glif).
        auto slotSize(float scale) -> float {
            return Graphics::VectorText::getTextHeight(scale);
        }

        // Ample del bloc de slots: constant, independent de les vides. NUM_SLOTS
        // slots al pas del dígit (l'últim slot ocupa la seva pròpia mida).
        auto slotsBlockWidth(float scale, float spacing) -> float {
            if (NUM_SLOTS <= 0) {
                return 0.0F;
            }
            return (static_cast<float>(NUM_SLOTS - 1) * digitPitch(scale, spacing)) + slotSize(scale);
        }

        // Dibuixa els slots de vides com a naus en miniatura en posicions FIXES.
        // Slot amb vida disponible (repuesto) → color encès; slot buit → atenuat.
        // Repuestos = vides − 1 (la nau en joc no compta com a slot).
        void drawSlots(Rendering::Renderer* renderer,
            const std::shared_ptr<Graphics::Shape>& shape,
            int lives,
            SDL_Color bright,
            SDL_Color dim,
            float x_left,
            float center_y,
            float scale,
            float spacing) {
            if (NUM_SLOTS <= 0 || !shape) {
                return;
            }
            const float SIZE = slotSize(scale);
            const float PITCH = digitPitch(scale, spacing);
            // Escala que ajusta el cercle circumscrit de la shape a la mida del
            // slot (mida predictible independent del .shp).
            const float RADIUS = shape->getBoundingRadius();
            const float ICON_SCALE = (RADIUS > 0.001F) ? (SIZE / (2.0F * RADIUS)) : 1.0F;
            const int FILLED = std::clamp(lives - 1, 0, NUM_SLOTS);
            for (int i = 0; i < NUM_SLOTS; i++) {
                const SDL_Color COLOR = (i < FILLED) ? bright : dim;
                const Vec2 POS = {.x = x_left + (SIZE / 2.0F) + (static_cast<float>(i) * PITCH), .y = center_y};
                Rendering::renderShape(renderer, shape, POS, 0.0F, ICON_SCALE, 1.0F, 1.0F, COLOR);
            }
        }

        // Pinta la puntuació amb els zeros de farciment previs al primer dígit
        // significatiu en to atenuat i la resta en brillant (efecte display de 7
        // segments). El dígit menys significatiu SEMPRE va encès: puntuació 0 →
        // cinc zeros atenuats + l'últim "0" encès (el marcador no queda mai apagat).
        void drawScore(const Graphics::VectorText& text,
            const std::string& score,
            SDL_Color bright,
            SDL_Color dim,
            float x,
            float top_y,
            float scale,
            float spacing) {
            if (score.empty()) {
                return;
            }
            // Primer dígit significatiu; si són tots zeros, força l'últim a encès.
            const size_t FIRST_SIG = score.find_first_not_of('0');
            const size_t SIG = (FIRST_SIG == std::string::npos) ? (score.size() - 1) : FIRST_SIG;
            const std::string PREFIX = score.substr(0, SIG);
            const std::string REST = score.substr(SIG);
            if (!PREFIX.empty()) {
                text.render(PREFIX, {.x = x, .y = top_y}, scale, spacing, 1.0F, dim);
                // Avança l'amplada del prefix més el buit inter-caràcter que hi
                // hauria si fos un sol string (exacte per a qualsevol spacing).
                x += Graphics::VectorText::getTextWidth(PREFIX, scale, spacing) + (spacing * scale);
            }
            if (!REST.empty()) {
                text.render(REST, {.x = x, .y = top_y}, scale, spacing, 1.0F, bright);
            }
        }

        // Pinta el bloc d'un jugador "punts vides" amb el seu color (punts amb
        // zeros atenuats, vides com a icones de nau en brillant). Si right_align,
        // el bloc acaba a anchor_x (ancorat a la dreta); si no, comença a
        // anchor_x (esquerra).
        void drawPlayerBlock(Rendering::Renderer* renderer,
            const Graphics::VectorText& text,
            const std::shared_ptr<Graphics::Shape>& shape,
            const std::string& score,
            int lives,
            SDL_Color bright,
            SDL_Color dim,
            float anchor_x,
            float center_y,
            float scale,
            float spacing,
            bool right_align) {
            const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F);
            const float W_SCORE = Graphics::VectorText::getTextWidth(score, scale, spacing);
            const float GAP = digitPitch(scale, spacing);  // separació punts↔slots = un pas de dígit
            const float W_LIVES = slotsBlockWidth(scale, spacing);
            const float BLOCK_W = W_SCORE + GAP + W_LIVES;

            float x = right_align ? (anchor_x - BLOCK_W) : anchor_x;
            drawScore(text, score, bright, dim, x, TOP_Y, scale, spacing);
            x += W_SCORE + GAP;
            drawSlots(renderer, shape, lives, bright, dim, x, center_y, scale, spacing);
        }

        // Pinta el nivell centrat: etiqueta "NIVELL" en verd atenuat i el número
        // en verd brillant.
        void drawLevel(const Graphics::VectorText& text,
            const std::string& label,
            const std::string& value,
            float top_y,
            float scale,
            float spacing) {
            const float W_LABEL = Graphics::VectorText::getTextWidth(label, scale, spacing);
            const float W_VALUE = Graphics::VectorText::getTextWidth(value, scale, spacing);
            const float CX = Defaults::Game::WIDTH / 2.0F;
            float x = CX - ((W_LABEL + W_VALUE) / 2.0F);
            text.render(label, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_DIM);
            x += W_LABEL;
            text.render(value, {.x = x, .y = top_y}, scale, spacing, 1.0F, Defaults::Hud::Colors::LEVEL_BRIGHT);
        }

    }  // namespace

    void drawScoreboardAt(Rendering::Renderer* renderer,
        const Graphics::VectorText& text,
        const ScoreboardData& data,
        float center_y,
        float scale,
        float spacing) {
        // Els blocs s'ancoren a les verticals del PLAYAREA (sota el marc).
        const SDL_FRect& play = Defaults::Zones::PLAYAREA;
        const float LEFT = play.x;
        const float RIGHT = play.x + play.w;
        const float TOP_Y = center_y - (Graphics::VectorText::getTextHeight(scale) / 2.0F);

        drawPlayerBlock(renderer, text, data.shape_p1, data.score_p1, data.lives_p1, Defaults::Hud::Colors::P1_BRIGHT, Defaults::Hud::Colors::P1_DIM, LEFT, center_y, scale, spacing, false);
        drawPlayerBlock(renderer, text, data.shape_p2, data.score_p2, data.lives_p2, Defaults::Hud::Colors::P2_BRIGHT, Defaults::Hud::Colors::P2_DIM, RIGHT, center_y, scale, spacing, true);
        drawLevel(text, data.level_label, data.level_value, TOP_Y, scale, spacing);
    }

    void drawScoreboardAnimated(Rendering::Renderer* renderer,
        const Graphics::VectorText& text,
        const ScoreboardData& data,
        float progress) {
        const float EASED = Easing::easeOutQuad(progress);

        constexpr float SCALE = Defaults::Hud::SCOREBOARD_TEXT_SCALE;
        constexpr float SPACING = Defaults::Hud::SCOREBOARD_TEXT_SPACING;
        const SDL_FRect& scoreboard_zone = Defaults::Zones::SCOREBOARD;
        const float Y_FINAL = scoreboard_zone.y + (scoreboard_zone.h / 2.0F);
        // Posición inicial: fuera de la pantalla por debajo.
        const auto Y_INI = static_cast<float>(Defaults::Game::HEIGHT);
        const float Y_ANIM = Y_INI + ((Y_FINAL - Y_INI) * EASED);

        drawScoreboardAt(renderer, text, data, Y_ANIM, SCALE, SPACING);
    }

}  // namespace Systems::InitHud
