// playfield.cpp - Implementació del fons del playfield
// © 2026 JailDesigner

#include "core/graphics/playfield.hpp"

#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <limits>

#include "core/defaults.hpp"
#include "core/rendering/line_renderer.hpp"

namespace Graphics {

    namespace {

        // Easing cubic-out: t → 1 - (1-t)^3. Decelera prop del final.
        auto easeOutCubic(float t) -> float {
            const float INV = 1.0F - t;
            return 1.0F - (INV * INV * INV);
        }

        // Lerp del color base actual (oscil·lador) cap a un color destí en
        // funció de f ∈ [0, 1]. Alpha > 0 perquè line_renderer l'usi directe.
        auto lerpColor(SDL_Color target, float f) -> SDL_Color {
            const float CLAMPED = std::clamp(f, 0.0F, 1.0F);
            const SDL_Color BASE = Rendering::getLineColor();
            const auto LERP_U8 = [&](unsigned char a, unsigned char b) {
                const float OUT = (static_cast<float>(a) * (1.0F - CLAMPED)) + (static_cast<float>(b) * CLAMPED);
                return static_cast<unsigned char>(OUT);
            };
            return SDL_Color{
                .r = LERP_U8(BASE.r, target.r),
                .g = LERP_U8(BASE.g, target.g),
                .b = LERP_U8(BASE.b, target.b),
                .a = 255};
        }

    }  // namespace

    Playfield::Playfield(Rendering::Renderer* renderer)
        : renderer_(renderer) {
        buildLines();
    }

    void Playfield::update(float delta_time) {
        elapsed_s_ += delta_time;

        // Decau l'orbit i avança la fase del sin per cada línia.
        const float ORBIT_DELTA_PHASE = Defaults::Playfield::ORBIT_FREQ_HZ * 2.0F * Defaults::Math::PI * delta_time;
        const float ORBIT_DEC = Defaults::Playfield::ORBIT_DECAY_PER_S * delta_time;
        for (auto& line : lines_) {
            line.orbit_phase += ORBIT_DELTA_PHASE;
            line.orbit_amplitude = std::max(0.0F, line.orbit_amplitude - ORBIT_DEC);

            // Avança els pulses; els desactiva quan acaben de vida.
            for (auto& pulse : line.pulses) {
                if (!pulse.active) {
                    continue;
                }
                pulse.age_s += delta_time;
                if (pulse.age_s >= Defaults::Playfield::PULSE_LIFETIME_S) {
                    pulse.active = false;
                }
            }
        }
    }

    void Playfield::spawnPulseAt(Line& line, float center_t) {
        for (auto& pulse : line.pulses) {
            if (!pulse.active) {
                pulse.active = true;
                pulse.age_s = 0.0F;
                pulse.center_t = std::clamp(center_t, 0.0F, 1.0F);
                return;
            }
        }
        // Cap slot lliure: substituïm el més vell.
        Pulse* oldest = line.pulses.data();
        for (auto& pulse : line.pulses) {
            if (pulse.age_s > oldest->age_s) {
                oldest = &pulse;
            }
        }
        oldest->active = true;
        oldest->age_s = 0.0F;
        oldest->center_t = std::clamp(center_t, 0.0F, 1.0F);
    }

    void Playfield::notifyFireworkSpawn(Vec2 pos) {
        // Línia vertical més propera (per posició x) i horitzontal més propera (per y).
        Line* closest_v = nullptr;
        Line* closest_h = nullptr;
        float min_dx = std::numeric_limits<float>::max();
        float min_dy = std::numeric_limits<float>::max();
        for (auto& line : lines_) {
            if (line.is_vertical) {
                const float DX = std::abs(pos.x - line.start.x);
                if (DX < min_dx) {
                    min_dx = DX;
                    closest_v = &line;
                }
            } else {
                const float DY = std::abs(pos.y - line.start.y);
                if (DY < min_dy) {
                    min_dy = DY;
                    closest_h = &line;
                }
            }
        }
        if (closest_v != nullptr) {
            const float LINE_LEN = closest_v->end.y - closest_v->start.y;
            const float CENTER_T = (LINE_LEN > 0.0F) ? (pos.y - closest_v->start.y) / LINE_LEN : 0.5F;
            spawnPulseAt(*closest_v, CENTER_T);
        }
        if (closest_h != nullptr) {
            const float LINE_LEN = closest_h->end.x - closest_h->start.x;
            const float CENTER_T = (LINE_LEN > 0.0F) ? (pos.x - closest_h->start.x) / LINE_LEN : 0.5F;
            spawnPulseAt(*closest_h, CENTER_T);
        }
    }

    void Playfield::notifyShipPass(Vec2 pos, float speed_px_s) {
        if (speed_px_s < Defaults::Playfield::ORBIT_SHIP_SPEED_THRESHOLD) {
            return;
        }
        const float MAX_DIST = Defaults::Playfield::ORBIT_PROXIMITY_PX;
        for (auto& line : lines_) {
            // Distància perpendicular del punt a la línia (que és horitzontal o vertical).
            const float DIST = line.is_vertical
                ? std::abs(pos.x - line.start.x)
                : std::abs(pos.y - line.start.y);
            if (DIST < MAX_DIST) {
                line.orbit_amplitude = Defaults::Playfield::ORBIT_AMPLITUDE_MAX_PX;
            }
        }
    }

    void Playfield::buildLines() {
        const SDL_FRect& zona = Defaults::Zones::PLAYAREA;
        const float CELL_W = zona.w / static_cast<float>(Defaults::Playfield::COLUMNS);
        const float CELL_H = zona.h / static_cast<float>(Defaults::Playfield::ROWS);
        const float SUB_W = CELL_W / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
        const float SUB_H = CELL_H / static_cast<float>(Defaults::Playfield::SUBDIVISIONS);
        const int SUB_VERTS = Defaults::Playfield::COLUMNS * Defaults::Playfield::SUBDIVISIONS;
        const int SUB_HORIZ = Defaults::Playfield::ROWS * Defaults::Playfield::SUBDIVISIONS;

        std::vector<Line> verticals;
        std::vector<Line> horizontals;

        // Verticals: posicions i ∈ [1, SUB_VERTS-1].
        for (int i = 1; i < SUB_VERTS; i++) {
            const float X = zona.x + (static_cast<float>(i) * SUB_W);
            const bool IS_MAIN = (i % Defaults::Playfield::SUBDIVISIONS) == 0;
            const float BRIGHTNESS = IS_MAIN
                ? Defaults::Playfield::GRID_BRIGHTNESS
                : Defaults::Playfield::SUBGRID_BRIGHTNESS;
            verticals.push_back(Line{
                .start = {.x = X, .y = zona.y},
                .end = {.x = X, .y = zona.y + zona.h},
                .brightness = BRIGHTNESS,
                .spawn_time_s = 0.0F,
                .is_vertical = true,
                .orbit_amplitude = 0.0F,
                .orbit_phase = 0.0F,
                .pulses = {}});
        }

        // Horitzontals: posicions j ∈ [1, SUB_HORIZ-1].
        for (int j = 1; j < SUB_HORIZ; j++) {
            const float Y = zona.y + (static_cast<float>(j) * SUB_H);
            const bool IS_MAIN = (j % Defaults::Playfield::SUBDIVISIONS) == 0;
            const float BRIGHTNESS = IS_MAIN
                ? Defaults::Playfield::GRID_BRIGHTNESS
                : Defaults::Playfield::SUBGRID_BRIGHTNESS;
            horizontals.push_back(Line{
                .start = {.x = zona.x, .y = Y},
                .end = {.x = zona.x + zona.w, .y = Y},
                .brightness = BRIGHTNESS,
                .spawn_time_s = 0.0F,
                .is_vertical = false,
                .orbit_amplitude = 0.0F,
                .orbit_phase = 0.0F,
                .pulses = {}});
        }

        // Ona diagonal: la línia esquerra/superior naix a t=0 i les següents
        // propaguen cap a la dreta/inferior, en paral·lel. Verticals i
        // horitzontals comparteixen la finestra temporal així el front arriba
        // a la cantonada inferior-dreta alhora.
        const float SPAWN_WINDOW =
            Defaults::Playfield::TOTAL_ANIMATION_DURATION_S - Defaults::Playfield::LINE_GROWTH_DURATION_S;
        const int NUM_V = static_cast<int>(verticals.size());
        const int NUM_H = static_cast<int>(horizontals.size());
        const float INTERVAL_V = (NUM_V > 1) ? SPAWN_WINDOW / static_cast<float>(NUM_V - 1) : 0.0F;
        const float INTERVAL_H = (NUM_H > 1) ? SPAWN_WINDOW / static_cast<float>(NUM_H - 1) : 0.0F;

        lines_.clear();
        lines_.reserve(verticals.size() + horizontals.size());

        for (int i = 0; i < NUM_V; i++) {
            verticals[i].spawn_time_s = static_cast<float>(i) * INTERVAL_V;
            lines_.push_back(verticals[i]);
        }
        for (int i = 0; i < NUM_H; i++) {
            horizontals[i].spawn_time_s = static_cast<float>(i) * INTERVAL_H;
            lines_.push_back(horizontals[i]);
        }
    }

    auto Playfield::computeLineProgress(const Line& line) const -> float {
        const float LINE_ELAPSED = elapsed_s_ - line.spawn_time_s;
        return std::clamp(LINE_ELAPSED / Defaults::Playfield::LINE_GROWTH_DURATION_S, 0.0F, 1.0F);
    }

    void Playfield::draw() const {
        for (const auto& line : lines_) {
            const float RAW_P = computeLineProgress(line);
            if (RAW_P <= 0.0F) {
                continue;
            }
            const float P = easeOutCubic(RAW_P);

            // Desplaçament perpendicular per orbit (verticals → x, horitzontals → y).
            const float ORBIT_OFFSET = line.orbit_amplitude * std::sin(line.orbit_phase);
            const float ORBIT_DX = line.is_vertical ? ORBIT_OFFSET : 0.0F;
            const float ORBIT_DY = line.is_vertical ? 0.0F : ORBIT_OFFSET;

            const float START_X = line.start.x + ORBIT_DX;
            const float START_Y = line.start.y + ORBIT_DY;
            const float DX = line.end.x - line.start.x;
            const float DY = line.end.y - line.start.y;
            const float CURRENT_X = START_X + (DX * P);
            const float CURRENT_Y = START_Y + (DY * P);

            // Tram base (brillo de la línia).
            Rendering::linea(
                renderer_,
                static_cast<int>(START_X),
                static_cast<int>(START_Y),
                static_cast<int>(CURRENT_X),
                static_cast<int>(CURRENT_Y),
                line.brightness);

            // Cap brillant mentre creix: l'últim tram de la línia es repinta més brillant.
            if (P < 1.0F) {
                const float LENGTH = std::sqrt((DX * DX) + (DY * DY));
                if (LENGTH > 0.0F) {
                    const float HEAD_T = std::max(0.0F, P - (Defaults::Playfield::HEAD_LENGTH_PX / LENGTH));
                    const float HEAD_X = START_X + (DX * HEAD_T);
                    const float HEAD_Y = START_Y + (DY * HEAD_T);
                    Rendering::linea(
                        renderer_,
                        static_cast<int>(HEAD_X),
                        static_cast<int>(HEAD_Y),
                        static_cast<int>(CURRENT_X),
                        static_cast<int>(CURRENT_Y),
                        Defaults::Playfield::HEAD_BRIGHTNESS);
                }
            }

            // Pulses: cada un és un segment brillant centrat a center_t que
            // s'expandeix amb el temps i s'apaga.
            const float LINE_LENGTH = std::sqrt((DX * DX) + (DY * DY));
            if (LINE_LENGTH <= 0.0F) {
                continue;
            }
            const SDL_Color PULSE_TARGET = {
                .r = Defaults::Playfield::PULSE_COLOR_R,
                .g = Defaults::Playfield::PULSE_COLOR_G,
                .b = Defaults::Playfield::PULSE_COLOR_B,
                .a = 255};
            for (const auto& pulse : line.pulses) {
                if (!pulse.active) {
                    continue;
                }
                const float HALF_WIDTH_T = (pulse.age_s * Defaults::Playfield::PULSE_SPREAD_PER_S) / LINE_LENGTH;
                const float INTENSITY = std::max(
                    0.0F,
                    1.0F - (pulse.age_s / Defaults::Playfield::PULSE_LIFETIME_S));
                const float T1 = std::clamp(pulse.center_t - HALF_WIDTH_T, 0.0F, 1.0F);
                const float T2 = std::clamp(pulse.center_t + HALF_WIDTH_T, 0.0F, 1.0F);
                if (T2 <= T1) {
                    continue;
                }
                const float P1_X = START_X + (DX * T1);
                const float P1_Y = START_Y + (DY * T1);
                const float P2_X = START_X + (DX * T2);
                const float P2_Y = START_Y + (DY * T2);
                const SDL_Color SEG_COLOR = lerpColor(PULSE_TARGET, INTENSITY);
                Rendering::linea(
                    renderer_,
                    static_cast<int>(P1_X),
                    static_cast<int>(P1_Y),
                    static_cast<int>(P2_X),
                    static_cast<int>(P2_Y),
                    1.0F,
                    0.0F,
                    SEG_COLOR);
            }
        }
    }

}  // namespace Graphics
