// LineEffect.cpp - Implementation of line clearing visual and audio effects
#include "LineEffect.h"
#include <algorithm>
#include <cmath>
#include "audio/Audio.h"
#include "audio/AudioManager.h"
#include "gameplay/core/Game.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace {
float randRange(float min, float max) {
    return min + (static_cast<float>(rand()) / static_cast<float>(RAND_MAX)) * (max - min);
}
}

LineEffect::Particle::Particle(float px, float py, Style particleStyle, SDL_Color tint)
    : x(px), y(py), size(0.0f), alpha(1.0f), life(0.0f), lifeSpan(0.0f),
      blockType(rand() % 7), color(tint), style(particleStyle) {
    float angle = randRange(0.0f, static_cast<float>(M_PI) * 2.0f);
    float speed = (style == Style::Shard) ? randRange(140.0f, 260.0f) : randRange(70.0f, 140.0f);
    vx = std::cos(angle) * speed;
    vy = std::sin(angle) * speed - ((style == Style::Shard) ? randRange(40.0f, 110.0f) : randRange(10.0f, 40.0f));
    size = (style == Style::Shard) ? randRange(8.0f, 16.0f) : randRange(5.0f, 10.0f);
    lifeSpan = (style == Style::Shard) ? randRange(0.70f, 1.20f) : randRange(1.00f, 1.50f);
}

void LineEffect::Particle::update(float dt) {
    life += dt;
    if (life >= lifeSpan) {
        alpha = 0.0f;
        return;
    }

    const float progress = life / lifeSpan;
    x += vx * dt;
    y += vy * dt;
    float gravity = (style == Style::Shard) ? 520.0f : 240.0f;
    vy += gravity * dt;
    vx *= (style == Style::Shard) ? 0.96f : 0.985f;
    float shrinkRate = (style == Style::Shard) ? 24.0f : 10.0f;
    size = std::max(2.0f, size - shrinkRate * dt);
    alpha = 1.0f - progress;
}

void LineEffect::Particle::render(SDL_Renderer* renderer, SDL_Texture* blocksTex) {
    if (alpha <= 0.0f) return;

    if (blocksTex) {
        Uint8 prevA = 255;
        SDL_GetTextureAlphaMod(blocksTex, &prevA);
        SDL_SetTextureAlphaMod(blocksTex, static_cast<Uint8>(alpha * 255.0f));

        const int SPRITE_SIZE = 90;
        float srcX = blockType * SPRITE_SIZE + 2;
        float srcY = 2;
        float srcW = SPRITE_SIZE - 4;
        float srcH = SPRITE_SIZE - 4;

        SDL_FRect srcRect = {srcX, srcY, srcW, srcH};
        SDL_FRect dstRect = {x - size/2.0f, y - size/2.0f, size, size};
        SDL_RenderTexture(renderer, blocksTex, &srcRect, &dstRect);
        SDL_SetTextureAlphaMod(blocksTex, prevA);
    } else {
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
        Uint8 adjustedAlpha = static_cast<Uint8>(alpha * 255.0f);
        SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, adjustedAlpha);
        float radius = size * 0.5f;
        for (int iy = -static_cast<int>(radius); iy <= static_cast<int>(radius); ++iy) {
            for (int ix = -static_cast<int>(radius); ix <= static_cast<int>(radius); ++ix) {
                float dist2 = float(ix * ix + iy * iy);
                if (dist2 <= radius * radius) {
                    SDL_RenderPoint(renderer, x + ix, y + iy);
                }
            }
        }
    }
}

LineEffect::Spark::Spark(float px, float py, SDL_Color tint)
    : x(px), y(py), vx(0.0f), vy(0.0f), life(0.0f), maxLife(randRange(0.40f, 0.80f)),
      thickness(randRange(1.0f, 2.0f)), color(tint) {
    float angle = randRange(0.0f, static_cast<float>(M_PI) * 2.0f);
    float speed = randRange(240.0f, 520.0f);
    vx = std::cos(angle) * speed;
    vy = std::sin(angle) * speed - randRange(80.0f, 150.0f);
}

void LineEffect::Spark::update(float dt) {
    life += dt;
    x += vx * dt;
    y += vy * dt;
    vy += 420.0f * dt;
    vx *= 0.99f;
}

void LineEffect::Spark::render(SDL_Renderer* renderer) const {
    if (life >= maxLife) return;
    float progress = life / maxLife;
    float alpha = (1.0f - progress) * 255.0f;
    if (alpha <= 0.0f) return;

    SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, static_cast<Uint8>(alpha));
    float trail = 0.018f * (1.2f - progress) * thickness;
    SDL_FPoint tail{ x - vx * trail, y - vy * trail };
    SDL_RenderLine(renderer, x, y, tail.x, tail.y);
}

LineEffect::GlowPulse::GlowPulse(float px, float py, float baseR, float maxR, SDL_Color tint)
        : x(px), y(py), baseRadius(baseR), maxRadius(maxR), life(0.0f),
            maxLife(randRange(0.45f, 0.70f)), color(tint) {
    if (color.a == 0) color.a = 200;
}

void LineEffect::GlowPulse::update(float dt) {
    life += dt;
    if (life > maxLife) life = maxLife;
}

void LineEffect::GlowPulse::render(SDL_Renderer* renderer) const {
    if (life >= maxLife) return;
    float progress = life / maxLife;
    float radius = baseRadius + (maxRadius - baseRadius) * progress;
    float baseAlpha = (1.0f - progress) * (color.a / 255.0f);
    int intRadius = static_cast<int>(std::ceil(radius));
    for (int iy = -intRadius; iy <= intRadius; ++iy) {
        float dy = static_cast<float>(iy);
        float rowWidth = std::sqrt(std::max(0.0f, radius * radius - dy * dy));
        float falloff = std::max(0.0f, 1.0f - std::abs(dy) / radius);
        Uint8 alpha = static_cast<Uint8>(baseAlpha * falloff * 255.0f);
        if (alpha == 0) continue;
        SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, alpha);
        SDL_FRect segment{
            x - rowWidth,
            y + dy,
            rowWidth * 2.0f,
            1.4f
        };
        SDL_RenderFillRect(renderer, &segment);
    }
}

LineEffect::LineEffect() : renderer(nullptr), state(AnimationState::IDLE), timer(0.0f), 
                          rng(std::random_device{}()), audioStream(nullptr) {
}

LineEffect::~LineEffect() {
    shutdown();
}

bool LineEffect::init(SDL_Renderer* r) {
    renderer = r;
    initAudio();
    return true;
}

void LineEffect::shutdown() {
    // No separate audio stream anymore; SFX go through shared Audio mixer
}

void LineEffect::initAudio() {
    // Generate simple beep sounds procedurally (fallback when voice SFX not provided)
    
    // Generate a simple line clear beep (440Hz for 0.2 seconds)
    int sampleRate = 44100;
    int duration = static_cast<int>(0.2f * sampleRate);
    lineClearSample.resize(duration * 2); // Stereo
    
    for (int i = 0; i < duration; ++i) {
        float t = static_cast<float>(i) / sampleRate;
        float wave = std::sin(2.0f * M_PI * 440.0f * t) * 0.3f; // 440Hz sine wave
        int16_t sample = static_cast<int16_t>(wave * 32767.0f);
        lineClearSample[i * 2] = sample;     // Left channel
        lineClearSample[i * 2 + 1] = sample; // Right channel
    }
    
    // Generate a higher pitched tetris sound (880Hz for 0.4 seconds)
    duration = static_cast<int>(0.4f * sampleRate);
    tetrisSample.resize(duration * 2);
    
    for (int i = 0; i < duration; ++i) {
        float t = static_cast<float>(i) / sampleRate;
        float wave = std::sin(2.0f * M_PI * 880.0f * t) * 0.4f; // 880Hz sine wave
        int16_t sample = static_cast<int16_t>(wave * 32767.0f);
        tetrisSample[i * 2] = sample;     // Left channel
        tetrisSample[i * 2 + 1] = sample; // Right channel
    }
}

void LineEffect::startLineClear(const std::vector<int>& rows, int gridX, int gridY, int blockSize, int gridCols, int gapPx, int gapAfterCol) {
    if (rows.empty()) return;
    
    clearingRows = rows;
    effectGridCols = std::max(1, gridCols);
    effectGapPx = std::max(0, gapPx);
    effectGapAfterCol = std::clamp(gapAfterCol, 0, effectGridCols);
    state = AnimationState::FLASH_WHITE;
    timer = 0.0f;
    dropProgress = 0.0f;
    dropBlockSize = blockSize;
    rowDropTargets.fill(0.0f);
    particles.clear();
    sparks.clear();
    glowPulses.clear();

    std::array<bool, Game::ROWS> rowClearing{};
    for (int row : rows) {
        if (row >= 0 && row < Game::ROWS) {
            rowClearing[row] = true;
        }
    }

    int clearedBelow = 0;
    for (int row = Game::ROWS - 1; row >= 0; --row) {
        if (rowClearing[row]) {
            ++clearedBelow;
        } else if (clearedBelow > 0) {
            rowDropTargets[row] = static_cast<float>(clearedBelow * blockSize);
        }
    }
    
    // Create particles for each clearing row
    for (int row : rows) {
        createParticles(row, gridX, gridY, blockSize);
    }
    
    // Play appropriate sound
    playLineClearSound(static_cast<int>(rows.size()));
}

void LineEffect::createParticles(int row, int gridX, int gridY, int blockSize) {
    const float centerY = gridY + row * blockSize + blockSize * 0.5f;
    for (int col = 0; col < effectGridCols; ++col) {
        float centerX = gridX + col * blockSize + blockSize * 0.5f;
        if (effectGapPx > 0 && effectGapAfterCol > 0 && col >= effectGapAfterCol) {
            centerX += static_cast<float>(effectGapPx);
        }
        SDL_Color tint = pickFireColor();
        spawnGlowPulse(centerX, centerY, static_cast<float>(blockSize), tint);
        spawnShardBurst(centerX, centerY, tint);
        spawnSparkBurst(centerX, centerY, tint);
    }
}

void LineEffect::spawnShardBurst(float x, float y, SDL_Color tint) {
    int shardCount = 3 + rand() % 4;
    for (int i = 0; i < shardCount; ++i) {
        particles.emplace_back(x, y, Particle::Style::Shard, tint);
    }
    int emberCount = 2 + rand() % 3;
    for (int i = 0; i < emberCount; ++i) {
        particles.emplace_back(x, y, Particle::Style::Ember, tint);
    }
}

void LineEffect::spawnSparkBurst(float x, float y, SDL_Color tint) {
    int sparkCount = 4 + rand() % 5;
    for (int i = 0; i < sparkCount; ++i) {
        sparks.emplace_back(x, y, tint);
    }
}

void LineEffect::spawnGlowPulse(float x, float y, float blockSize, SDL_Color tint) {
    glowPulses.emplace_back(x, y, blockSize * 0.45f, blockSize * 2.3f, tint);
}

bool LineEffect::update(float deltaTime) {
    if (state == AnimationState::IDLE) return true;
    
    timer += deltaTime;
    
    switch (state) {
        case AnimationState::FLASH_WHITE:
            if (timer >= FLASH_DURATION) {
                state = AnimationState::EXPLODE_BLOCKS;
                timer = 0.0f;
            }
            break;
            
        case AnimationState::EXPLODE_BLOCKS:
            updateParticles(deltaTime);
            updateSparks(deltaTime);
            updateGlowPulses(deltaTime);
            if (timer >= EXPLODE_DURATION) {
                state = AnimationState::BLOCKS_DROP;
                timer = 0.0f;
            }
            break;
            
        case AnimationState::BLOCKS_DROP:
            updateParticles(deltaTime);
            updateSparks(deltaTime);
            updateGlowPulses(deltaTime);
            dropProgress = std::min(1.0f, timer / DROP_DURATION);
            if (timer >= DROP_DURATION) {
                state = AnimationState::IDLE;
                clearingRows.clear();
                particles.clear();
                sparks.clear();
                glowPulses.clear();
                rowDropTargets.fill(0.0f);
                dropProgress = 0.0f;
                dropBlockSize = 0;
                return true; // Effect complete
            }
            break;
            
        case AnimationState::IDLE:
            return true;
    }
    
    return false; // Effect still running
}

void LineEffect::updateParticles(float dt) {
    for (auto& particle : particles) {
        particle.update(dt);
    }
    particles.erase(
        std::remove_if(particles.begin(), particles.end(),
                      [](const Particle& p) { return !p.isAlive(); }),
        particles.end());
}

void LineEffect::updateSparks(float dt) {
    for (auto& spark : sparks) {
        spark.update(dt);
    }
    sparks.erase(
        std::remove_if(sparks.begin(), sparks.end(),
                      [](const Spark& s) { return !s.isAlive(); }),
        sparks.end());
}

void LineEffect::updateGlowPulses(float dt) {
    for (auto& pulse : glowPulses) {
        pulse.update(dt);
    }
    glowPulses.erase(
        std::remove_if(glowPulses.begin(), glowPulses.end(),
                      [](const GlowPulse& p) { return !p.isAlive(); }),
        glowPulses.end());
}

void LineEffect::render(SDL_Renderer* renderer, SDL_Texture* blocksTex, int gridX, int gridY, int blockSize, int gapPx, int gapAfterCol) {
    if (state == AnimationState::IDLE) return;

    // Allow caller to override gap mapping (useful for Coop renderer that inserts a mid-gap).
    effectGapPx = std::max(0, gapPx);
    effectGapAfterCol = std::clamp(gapAfterCol, 0, effectGridCols);
    
    switch (state) {
        case AnimationState::FLASH_WHITE:
            renderFlash(gridX, gridY, blockSize);
            break;
            
        case AnimationState::EXPLODE_BLOCKS:
            renderExplosion(blocksTex);
            break;
            
        case AnimationState::BLOCKS_DROP:
            renderExplosion(blocksTex);
            break;
            
        case AnimationState::IDLE:
            break;
    }
}

float LineEffect::getRowDropOffset(int row) const {
    if (state != AnimationState::BLOCKS_DROP) {
        return 0.0f;
    }
    if (row < 0 || row >= Game::ROWS) {
        return 0.0f;
    }
    float target = rowDropTargets[row];
    if (target <= 0.0f) {
        return 0.0f;
    }
    float eased = dropProgress * dropProgress * dropProgress;
    return target * eased;
}

void LineEffect::renderFlash(int gridX, int gridY, int blockSize) {
    // Create a flashing white effect with varying opacity
    float progress = timer / FLASH_DURATION;
    float flashIntensity = std::sin(progress * M_PI * 6.0f) * 0.5f + 0.5f;
    
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
    Uint8 alpha = static_cast<Uint8>(flashIntensity * 180.0f);
    
    for (int row : clearingRows) {
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, alpha);
        const int gapW = (effectGapPx > 0 && effectGapAfterCol > 0 && effectGapAfterCol < effectGridCols) ? effectGapPx : 0;
        SDL_FRect flashRect = {
            static_cast<float>(gridX - 4),
            static_cast<float>(gridY + row * blockSize - 4),
            static_cast<float>(effectGridCols * blockSize + gapW + 8),
            static_cast<float>(blockSize + 8)
        };
        SDL_RenderFillRect(renderer, &flashRect);
        
        SDL_SetRenderDrawColor(renderer, 100, 150, 255, alpha / 2);
        for (int i = 1; i <= 3; ++i) {
            SDL_FRect glowRect = {
                flashRect.x - i,
                flashRect.y - i,
                flashRect.w + 2*i,
                flashRect.h + 2*i
            };
            SDL_RenderRect(renderer, &glowRect);
        }
    }
}

void LineEffect::renderExplosion(SDL_Texture* blocksTex) {
    renderGlowPulses();
    renderSparks();
    renderParticleGlows();
    for (auto& particle : particles) {
        particle.render(renderer, blocksTex);
    }
}

void LineEffect::renderGlowPulses() {
    if (glowPulses.empty()) return;
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
    for (const auto& pulse : glowPulses) {
        pulse.render(renderer);
    }
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
}

void LineEffect::renderSparks() {
    if (sparks.empty()) return;
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
    for (const auto& spark : sparks) {
        spark.render(renderer);
    }
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
}

void LineEffect::renderParticleGlows() {
    if (particles.empty()) return;
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_ADD);
    for (const auto& particle : particles) {
        if (particle.alpha <= 0.0f) continue;
        float radius = particle.size * 0.6f;
        SDL_SetRenderDrawColor(renderer, particle.color.r, particle.color.g, particle.color.b,
                               static_cast<Uint8>(particle.alpha * 150.0f));
        SDL_FRect glowRect{ particle.x - radius, particle.y - radius, radius * 2.0f, radius * 2.0f };
        SDL_RenderFillRect(renderer, &glowRect);
    }
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
}

void LineEffect::playLineClearSound(int lineCount) {
    // Choose appropriate sound based on line count
    const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
    if (sample && !sample->empty()) {
        // Mix via shared Audio device so it layers with music
        if (auto sys = AudioManager::get()) sys->playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
    }
}

SDL_Color LineEffect::pickFireColor() const {
    static const SDL_Color palette[] = {
        {255, 200, 120, 210},
        {255, 150, 90, 220},
        {255, 100, 160, 200},
        {120, 200, 255, 200},
        {255, 255, 200, 210}
    };
    const size_t count = sizeof(palette) / sizeof(palette[0]);
    return palette[rand() % count];
}
