#include "SyncLineRenderer.h"

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

SyncLineRenderer::SyncLineRenderer()
    : m_state(SyncState::Idle),
      m_flashTimer(0.0f),
            m_time(0.0f) {
        m_particles.reserve(MAX_PARTICLES);
}

static float syncWobbleX(float t) {
    // Small, smooth horizontal motion to make the conduit feel fluid.
    // Kept subtle so it doesn't distract from gameplay.
    return std::sinf(t * 2.1f) * 1.25f + std::sinf(t * 5.2f + 1.3f) * 0.55f;
}

void SyncLineRenderer::SpawnParticle() {
    if (m_particles.size() >= MAX_PARTICLES) {
        return;
    }

    SyncParticle p;
    const float centerX = (m_rect.x + (m_rect.w * 0.5f)) + syncWobbleX(m_time);
    // Spawn around the beam center so it reads like a conduit.
    const float jitter = -8.0f + static_cast<float>(std::rand() % 17);

    p.x = centerX + jitter;
    p.y = m_rect.y + m_rect.h + static_cast<float>(std::rand() % 10);

    // Two styles: tiny sparkle dots + short streaks.
    const bool dot = (std::rand() % 100) < 35;
    if (dot) {
        p.vx = (-18.0f + static_cast<float>(std::rand() % 37));
        p.vy = 180.0f + static_cast<float>(std::rand() % 180);
        p.w = 1.0f + static_cast<float>(std::rand() % 2);
        p.h = 1.0f + static_cast<float>(std::rand() % 2);
        p.alpha = 240.0f;
    } else {
        p.vx = (-14.0f + static_cast<float>(std::rand() % 29));
        p.vy = 160.0f + static_cast<float>(std::rand() % 200);
        p.w = 1.0f + static_cast<float>(std::rand() % 3);
        p.h = 3.0f + static_cast<float>(std::rand() % 10);
        p.alpha = 220.0f;
    }

    // Slight color variance (cyan/green/white) to keep it energetic.
    const int roll = std::rand() % 100;
    if (roll < 55) {
        p.color = SDL_Color{110, 255, 210, 255};
    } else if (roll < 90) {
        p.color = SDL_Color{120, 210, 255, 255};
    } else {
        p.color = SDL_Color{255, 255, 255, 255};
    }

    m_particles.push_back(p);
}

void SyncLineRenderer::SpawnBurst(int count) {
    for (int i = 0; i < count; ++i) {
        SpawnParticle();
    }
}

void SyncLineRenderer::SetRect(const SDL_FRect& rect) {
    m_rect = rect;
}

void SyncLineRenderer::SetState(SyncState state) {
    if (state != SyncState::ClearFlash) {
        m_state = state;
    }
}

void SyncLineRenderer::TriggerClearFlash() {
    m_state = SyncState::ClearFlash;
    m_flashTimer = FLASH_DURATION;

    // Reward burst: strong visual feedback on cooperative clear.
    SpawnBurst(56);
}

void SyncLineRenderer::Update(float deltaTime) {
    m_time += deltaTime;
    m_pulseTime += deltaTime;

    // State-driven particle spawning
    float spawnRatePerSec = 0.0f;
    int particlesPerSpawn = 1;
    switch (m_state) {
        case SyncState::LeftReady:
        case SyncState::RightReady:
            spawnRatePerSec = 24.0f;  // steady
            break;
        case SyncState::Synced:
            spawnRatePerSec = 78.0f;  // very heavy stream
            particlesPerSpawn = 2;
            break;
        default:
            spawnRatePerSec = 18.0f;  // always-on sparkle stream
            break;
    }

    if (spawnRatePerSec <= 0.0f) {
        m_spawnAcc = 0.0f;
    } else {
        m_spawnAcc += deltaTime * spawnRatePerSec;
        while (m_spawnAcc >= 1.0f) {
            m_spawnAcc -= 1.0f;
            for (int i = 0; i < particlesPerSpawn; ++i) {
                SpawnParticle();
            }
        }
    }

    // Update particles
    for (auto& p : m_particles) {
        p.x += p.vx * deltaTime;
        p.y -= p.vy * deltaTime;
        // Slow drift & fade.
        p.vx *= (1.0f - 0.35f * deltaTime);
        p.alpha -= 115.0f * deltaTime;
    }
    std::erase_if(m_particles, [&](const SyncParticle& p) {
        // Cull when out of view or too far from the beam.
        const float centerX = (m_rect.x + (m_rect.w * 0.5f)) + syncWobbleX(m_time);
        const float maxDx = 18.0f;
        return (p.y < (m_rect.y - 16.0f)) || p.alpha <= 0.0f || std::fabs(p.x - centerX) > maxDx;
    });

    if (m_state == SyncState::ClearFlash) {
        m_flashTimer -= deltaTime;
        if (m_flashTimer <= 0.0f) {
            m_state = SyncState::Idle;
            m_flashTimer = 0.0f;
        }
    }
}

SDL_Color SyncLineRenderer::GetBaseColor() const {
    switch (m_state) {
        case SyncState::LeftReady:
        case SyncState::RightReady:
            return SDL_Color{255, 220, 100, 235};

        case SyncState::Synced:
            return SDL_Color{100, 255, 120, 240};

        case SyncState::ClearFlash:
            return SDL_Color{255, 255, 255, 255};

        default:
            return SDL_Color{80, 180, 255, 235};
    }
}

void SyncLineRenderer::Render(SDL_Renderer* renderer) {
    if (!renderer) {
        return;
    }

    // We render the conduit with lots of translucent layers. Using additive blending
    // for glow/pulse makes it read like a blurred beam without shaders.
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    const float wobbleX = syncWobbleX(m_time);
    const float centerX = (m_rect.x + (m_rect.w * 0.5f)) + wobbleX;
    const float h = m_rect.h;
    const float hotspotH = std::clamp(h * 0.12f, 18.0f, 44.0f);

    // Flash factor (0..1)
    const float flashT = (m_state == SyncState::ClearFlash && FLASH_DURATION > 0.0f)
                             ? std::clamp(m_flashTimer / FLASH_DURATION, 0.0f, 1.0f)
                             : 0.0f;

    SDL_Color color = GetBaseColor();

    // Synced pulse drives aura + core intensity.
    float pulse01 = 0.0f;
    if (m_state == SyncState::Synced) {
        pulse01 = 0.5f + 0.5f * std::sinf(m_time * 6.0f);
    }

    // 1) Outer aura layers (bloom-like using rectangles)
    auto drawGlow = [&](float extraW, Uint8 a, SDL_Color c) {
        SDL_FRect fr{
            centerX - (m_rect.w + extraW) * 0.5f,
            m_rect.y,
            m_rect.w + extraW,
            m_rect.h
        };
        SDL_SetRenderDrawColor(renderer, c.r, c.g, c.b, a);
        SDL_RenderFillRect(renderer, &fr);
    };

    SDL_Color aura = color;
    // Slightly bias aura towards cyan so it reads “energy conduit”.
    aura.r = static_cast<Uint8>(std::min(255, static_cast<int>(aura.r) + 10));
    aura.g = static_cast<Uint8>(std::min(255, static_cast<int>(aura.g) + 10));
    aura.b = static_cast<Uint8>(std::min(255, static_cast<int>(aura.b) + 35));

    const float auraBoost = (m_state == SyncState::Synced) ? (0.70f + 0.80f * pulse01) : 0.70f;
    const float flashBoost = 1.0f + flashT * 1.45f;

    SDL_BlendMode oldBlend = SDL_BLENDMODE_BLEND;
    SDL_GetRenderDrawBlendMode(renderer, &oldBlend);
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);

    SDL_Color auraOuter = aura;
    auraOuter.r = static_cast<Uint8>(std::min(255, static_cast<int>(auraOuter.r) + 10));
    auraOuter.g = static_cast<Uint8>(std::min(255, static_cast<int>(auraOuter.g) + 5));
    auraOuter.b = static_cast<Uint8>(std::min(255, static_cast<int>(auraOuter.b) + 55));

    SDL_Color auraInner = aura;
    auraInner.r = static_cast<Uint8>(std::min(255, static_cast<int>(auraInner.r) + 40));
    auraInner.g = static_cast<Uint8>(std::min(255, static_cast<int>(auraInner.g) + 40));
    auraInner.b = static_cast<Uint8>(std::min(255, static_cast<int>(auraInner.b) + 70));

    // Wider + softer outer halo, then tighter inner glow.
    drawGlow(62.0f, static_cast<Uint8>(std::clamp(12.0f * auraBoost * flashBoost, 0.0f, 255.0f)), auraOuter);
    drawGlow(44.0f, static_cast<Uint8>(std::clamp(20.0f * auraBoost * flashBoost, 0.0f, 255.0f)), auraOuter);
    drawGlow(30.0f, static_cast<Uint8>(std::clamp(34.0f * auraBoost * flashBoost, 0.0f, 255.0f)), auraOuter);
    drawGlow(18.0f, static_cast<Uint8>(std::clamp(54.0f * auraBoost * flashBoost, 0.0f, 255.0f)), auraInner);
    drawGlow(10.0f, static_cast<Uint8>(std::clamp(78.0f * auraBoost * flashBoost, 0.0f, 255.0f)), auraInner);

    // 2) Hotspots near top/bottom (adds that “powered endpoints” vibe)
    SDL_Color hot = auraInner;
    hot.r = static_cast<Uint8>(std::min(255, static_cast<int>(hot.r) + 35));
    hot.g = static_cast<Uint8>(std::min(255, static_cast<int>(hot.g) + 35));
    hot.b = static_cast<Uint8>(std::min(255, static_cast<int>(hot.b) + 35));
    {
        const float hotW1 = 34.0f;
        const float hotW2 = 18.0f;
        SDL_FRect topHot1{ centerX - (m_rect.w + hotW1) * 0.5f, m_rect.y, m_rect.w + hotW1, hotspotH };
        SDL_FRect botHot1{ centerX - (m_rect.w + hotW1) * 0.5f, m_rect.y + m_rect.h - hotspotH, m_rect.w + hotW1, hotspotH };
        SDL_FRect topHot2{ centerX - (m_rect.w + hotW2) * 0.5f, m_rect.y + hotspotH * 0.12f, m_rect.w + hotW2, hotspotH * 0.78f };
        SDL_FRect botHot2{ centerX - (m_rect.w + hotW2) * 0.5f, m_rect.y + m_rect.h - hotspotH * 0.90f, m_rect.w + hotW2, hotspotH * 0.78f };

        Uint8 ha1 = static_cast<Uint8>(std::clamp((m_state == SyncState::Synced ? 85.0f : 55.0f) * flashBoost, 0.0f, 255.0f));
        Uint8 ha2 = static_cast<Uint8>(std::clamp((m_state == SyncState::Synced ? 130.0f : 90.0f) * flashBoost, 0.0f, 255.0f));
        SDL_SetRenderDrawColor(renderer, hot.r, hot.g, hot.b, ha1);
        SDL_RenderFillRect(renderer, &topHot1);
        SDL_RenderFillRect(renderer, &botHot1);
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, ha2);
        SDL_RenderFillRect(renderer, &topHot2);
        SDL_RenderFillRect(renderer, &botHot2);
    }

    // 3) Synced pulse wave (a travelling “breath” around the beam)
    if (m_state == SyncState::Synced) {
        float wave = std::fmod(m_pulseTime * 2.4f, 1.0f);
        float width = 10.0f + wave * 26.0f;
        Uint8 alpha = static_cast<Uint8>(std::clamp(150.0f * (1.0f - wave) * flashBoost, 0.0f, 255.0f));

        SDL_FRect waveRect{
            centerX - (m_rect.w + width) * 0.5f,
            m_rect.y,
            m_rect.w + width,
            m_rect.h
        };

        SDL_SetRenderDrawColor(renderer, 140, 255, 220, alpha);
        SDL_RenderFillRect(renderer, &waveRect);
    }

    // 4) Shimmer bands (stylish motion inside the conduit)
    {
        const int bands = 7;
        const float speed = (m_state == SyncState::Synced) ? 160.0f : 95.0f;
        const float bandW = m_rect.w + 12.0f;
        for (int i = 0; i < bands; ++i) {
            const float phase = (static_cast<float>(i) / static_cast<float>(bands));
            const float y = m_rect.y + std::fmod(m_time * speed + phase * h, h);
            const float fade = 0.35f + 0.65f * std::sinf((m_time * 2.1f) + phase * 6.28318f);
            const float bandH = 2.0f + (phase * 2.0f);
            Uint8 a = static_cast<Uint8>(std::clamp((26.0f + 36.0f * pulse01) * std::fabs(fade) * flashBoost, 0.0f, 255.0f));
            SDL_FRect fr{ centerX - bandW * 0.5f, y, bandW, bandH };
            SDL_SetRenderDrawColor(renderer, 200, 255, 255, a);
            SDL_RenderFillRect(renderer, &fr);
        }
    }

    // 5) Core beam (thin bright core + thicker body with horizontal gradient)
    Uint8 bodyA = color.a;
    if (m_state == SyncState::Synced) {
        bodyA = static_cast<Uint8>(std::clamp(175.0f + pulse01 * 75.0f, 0.0f, 255.0f));
    }
    // Keep the center more translucent; let glow carry intensity.
    bodyA = static_cast<Uint8>(std::clamp(bodyA * (0.72f + flashT * 0.35f), 0.0f, 255.0f));

    // Render a smooth-looking body by stacking a few vertical strips.
    // This approximates a gradient (bright center -> soft edges) without shaders.
    {
        // Allow thinner beam while keeping gradient readable.
        const float bodyW = std::max(4.0f, m_rect.w);
        const float x0 = centerX - bodyW * 0.5f;

        SDL_FRect left{ x0, m_rect.y, bodyW * 0.34f, m_rect.h };
        SDL_FRect mid{ x0 + bodyW * 0.34f, m_rect.y, bodyW * 0.32f, m_rect.h };
        SDL_FRect right{ x0 + bodyW * 0.66f, m_rect.y, bodyW * 0.34f, m_rect.h };

        SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, static_cast<Uint8>(std::clamp(bodyA * 0.60f, 0.0f, 255.0f)));
        SDL_RenderFillRect(renderer, &left);
        SDL_RenderFillRect(renderer, &right);

        SDL_SetRenderDrawColor(renderer,
                               static_cast<Uint8>(std::min(255, static_cast<int>(color.r) + 35)),
                               static_cast<Uint8>(std::min(255, static_cast<int>(color.g) + 35)),
                               static_cast<Uint8>(std::min(255, static_cast<int>(color.b) + 55)),
                               static_cast<Uint8>(std::clamp(bodyA * 0.88f, 0.0f, 255.0f)));
        SDL_RenderFillRect(renderer, &mid);
    }

    SDL_FRect coreRect{ centerX - 1.1f, m_rect.y, 2.2f, m_rect.h };
    Uint8 coreA = static_cast<Uint8>(std::clamp(210.0f + pulse01 * 70.0f + flashT * 95.0f, 0.0f, 255.0f));
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, coreA);
    SDL_RenderFillRect(renderer, &coreRect);

    // Switch back to normal alpha blend for particles so they stay readable.
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    // 6) Energy particles (sparks/streaks traveling upward)
    for (const auto& p : m_particles) {
        Uint8 a = static_cast<Uint8>(std::clamp(p.alpha, 0.0f, 255.0f));

        // Add a tiny sinusoidal sway so the stream feels alive.
        const float sway = std::sinf((p.y * 0.045f) + (m_time * 6.2f)) * 0.9f;
        SDL_FRect spark{ (p.x + sway) - (p.w * 0.5f), p.y, p.w, p.h };
        SDL_SetRenderDrawColor(renderer, p.color.r, p.color.g, p.color.b, a);
        SDL_RenderFillRect(renderer, &spark);

        // A little aura around each spark helps it read at speed.
        if (a > 40) {
            SDL_FRect sparkGlow{ spark.x - 1.0f, spark.y - 1.0f, spark.w + 2.0f, spark.h + 2.0f };
            SDL_SetRenderDrawColor(renderer, p.color.r, p.color.g, p.color.b, static_cast<Uint8>(a * 0.35f));
            SDL_RenderFillRect(renderer, &sparkGlow);
        }
    }

    // 7) Flash/glow overlay (adds “clear burst” punch)
    if (m_state == SyncState::ClearFlash) {
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);

        const float extra = 74.0f;
        SDL_FRect glow{ centerX - (m_rect.w + extra) * 0.5f, m_rect.y, m_rect.w + extra, m_rect.h };
        Uint8 ga = static_cast<Uint8>(std::clamp(90.0f + 140.0f * flashT, 0.0f, 255.0f));
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, ga);
        SDL_RenderFillRect(renderer, &glow);

        SDL_SetRenderDrawBlendMode(renderer, oldBlend);
    }

    // Restore whatever blend mode the caller had.
    SDL_SetRenderDrawBlendMode(renderer, oldBlend);
}
