#include "game/hazards.h"
#include "game/player.h"
#include "game/sprites.h"
#include "game/projectile.h"
#include "engine/physics.h"
#include "engine/renderer.h"
#include "engine/particle.h"
#include "engine/audio.h"
#include <stdlib.h>
#include <math.h>

/* ════════════════════════════════════════════════════
 *  Shared helpers
 * ════════════════════════════════════════════════════ */

/* Find the player entity in the manager */
static Entity *find_player(EntityManager *em) {
    for (int i = 0; i < em->count; i++) {
        Entity *e = &em->entities[i];
        if (e->active && e->type == ENT_PLAYER) return e;
    }
    return NULL;
}

/* ════════════════════════════════════════════════════
 *  TURRET — floor/wall-mounted gun, shoots at player
 * ════════════════════════════════════════════════════ */

static EntityManager *s_turret_em = NULL;

static void turret_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;
    TurretData *td = (TurretData *)self->data;
    if (!td) return;

    /* Death: turrets can be destroyed */
    if (self->flags & ENTITY_DEAD) {
        self->timer -= dt;
        if (self->timer <= 0) {
            entity_destroy(s_turret_em, self);
        }
        return;
    }

    /* Flash timer for muzzle flash visual */
    if (td->fire_flash > 0) {
        td->fire_flash -= dt;
    }

    /* Look for player */
    Entity *player = find_player(s_turret_em);
    if (!player || !player->active || (player->flags & ENTITY_DEAD)) {
        td->shoot_timer = TURRET_SHOOT_CD;
        return;
    }

    /* Distance check */
    float px = player->body.pos.x + player->body.size.x * 0.5f;
    float py = player->body.pos.y + player->body.size.y * 0.5f;
    float tx = self->body.pos.x + self->body.size.x * 0.5f;
    float ty = self->body.pos.y + self->body.size.y * 0.5f;

    float dx = px - tx;
    float dy = py - ty;
    float dist = sqrtf(dx * dx + dy * dy);

    /* Face toward player */
    if (dx < 0) self->flags |= ENTITY_FACING_LEFT;
    else        self->flags &= ~ENTITY_FACING_LEFT;

    if (dist < TURRET_DETECT_RANGE) {
        td->shoot_timer -= dt;
        if (td->shoot_timer <= 0 && s_turret_em) {
            td->shoot_timer = TURRET_SHOOT_CD;
            td->fire_flash = 0.15f;

            /* Aim direction: normalize vector to player */
            Vec2 dir;
            if (dist > 0.01f) {
                dir = vec2(dx / dist, dy / dist);
            } else {
                dir = vec2(1.0f, 0.0f);
            }

            /* Spawn projectile from turret barrel */
            bool facing_left = (self->flags & ENTITY_FACING_LEFT) != 0;
            float bx = facing_left ?
                self->body.pos.x - 4.0f :
                self->body.pos.x + self->body.size.x;
            float by = self->body.pos.y + self->body.size.y * 0.3f;

            projectile_spawn_dir(s_turret_em, vec2(bx, by), dir, false);
        }
    } else {
        /* Reset cooldown when player leaves range */
        if (td->shoot_timer < TURRET_SHOOT_CD * 0.5f)
            td->shoot_timer = TURRET_SHOOT_CD * 0.5f;
    }

    /* Animation */
    if (td->fire_flash > 0) {
        animation_set(&self->anim, &anim_turret_fire);
    } else {
        animation_set(&self->anim, &anim_turret_idle);
    }
    animation_update(&self->anim, dt);
}

static void turret_render(Entity *self, const Camera *cam) {
    (void)cam;
    Body *body = &self->body;

    if (g_spritesheet && self->anim.def) {
        SDL_Rect src = animation_current_rect(&self->anim);
        Vec2 render_pos = vec2(
            body->pos.x - 1.0f,
            body->pos.y
        );
        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = render_pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = (self->flags & ENTITY_FACING_LEFT) != 0,
            .flip_y  = false,
            .layer   = LAYER_ENTITIES,
            .alpha   = 255,
        };
        renderer_submit(&spr);
    } else {
        SDL_Color color = {130, 130, 130, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

static void turret_destroy(Entity *self) {
    free(self->data);
    self->data = NULL;
}

void turret_register(EntityManager *em) {
    entity_register(em, ENT_TURRET, turret_update, turret_render, turret_destroy);
    s_turret_em = em;
}

Entity *turret_spawn(EntityManager *em, Vec2 pos) {
    Entity *e = entity_spawn(em, ENT_TURRET, pos);
    if (!e) return NULL;

    e->body.size = vec2(TURRET_WIDTH, TURRET_HEIGHT);
    e->body.gravity_scale = 0.0f;  /* turrets are stationary */
    e->health = TURRET_HEALTH;
    e->max_health = TURRET_HEALTH;
    e->damage = 1;  /* light contact damage — turret is a hostile machine */

    TurretData *td = calloc(1, sizeof(TurretData));
    td->shoot_timer = TURRET_SHOOT_CD;
    td->fire_flash = 0.0f;
    e->data = td;

    /* Start with death timer if needed */
    e->timer = 0.3f;

    animation_set(&e->anim, &anim_turret_idle);

    return e;
}


/* ════════════════════════════════════════════════════
 *  MOVING PLATFORM — travels back and forth,
 *  player rides on top
 * ════════════════════════════════════════════════════ */

static EntityManager *s_mplat_em = NULL;

static void mplat_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;
    MovingPlatData *md = (MovingPlatData *)self->data;
    if (!md) return;

    /* Advance phase (sine oscillation) */
    float angular_speed = (MPLAT_SPEED / md->range) * 1.0f;
    md->phase += angular_speed * dt;
    if (md->phase > 2.0f * 3.14159265f)
        md->phase -= 2.0f * 3.14159265f;

    /* Calculate new position */
    float offset = sinf(md->phase) * md->range;
    Vec2 new_pos = vec2(
        md->origin.x + md->direction.x * offset,
        md->origin.y + md->direction.y * offset
    );

    /* Store velocity for player riding (carry velocity) */
    self->body.vel.x = (new_pos.x - self->body.pos.x) / dt;
    self->body.vel.y = (new_pos.y - self->body.pos.y) / dt;

    self->body.pos = new_pos;

    /* Check if player is standing on the platform */
    Entity *player = find_player(s_mplat_em);
    if (player && player->active && !(player->flags & ENTITY_DEAD)) {
        Body *pb = &player->body;
        Body *mb = &self->body;

        /* Player's feet must be at the platform top (within 3px) */
        float player_bottom = pb->pos.y + pb->size.y;
        float plat_top = mb->pos.y;

        bool horizontally_on = (pb->pos.x + pb->size.x > mb->pos.x + 1.0f) &&
                               (pb->pos.x < mb->pos.x + mb->size.x - 1.0f);
        bool vertically_close = (player_bottom >= plat_top - 3.0f) &&
                                (player_bottom <= plat_top + 4.0f);
        bool falling_or_standing = pb->vel.y >= -1.0f;

        if (horizontally_on && vertically_close && falling_or_standing) {
            /* Carry the player */
            pb->pos.x += self->body.vel.x * dt;
            pb->pos.y = plat_top - pb->size.y;
            pb->on_ground = true;
            if (pb->vel.y > 0) pb->vel.y = 0;
        }
    }
}

static void mplat_render(Entity *self, const Camera *cam) {
    (void)cam;
    Body *body = &self->body;

    if (g_spritesheet) {
        SDL_Rect src = animation_current_rect(&self->anim);
        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = body->pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = false,
            .flip_y  = false,
            .layer   = LAYER_ENTITIES,
            .alpha   = 255,
        };
        renderer_submit(&spr);
    } else {
        SDL_Color color = {60, 110, 165, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

static void mplat_destroy(Entity *self) {
    free(self->data);
    self->data = NULL;
}

void mplat_register(EntityManager *em) {
    entity_register(em, ENT_MOVING_PLATFORM, mplat_update, mplat_render, mplat_destroy);
    s_mplat_em = em;
}

Entity *mplat_spawn(EntityManager *em, Vec2 pos) {
    return mplat_spawn_dir(em, pos, vec2(1.0f, 0.0f));  /* default horizontal */
}

Entity *mplat_spawn_dir(EntityManager *em, Vec2 pos, Vec2 dir) {
    Entity *e = entity_spawn(em, ENT_MOVING_PLATFORM, pos);
    if (!e) return NULL;

    e->body.size = vec2(MPLAT_WIDTH, MPLAT_HEIGHT);
    e->body.gravity_scale = 0.0f;
    e->health = 9999;    /* indestructible */
    e->max_health = 9999;
    e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;
    e->damage = 0;

    MovingPlatData *md = calloc(1, sizeof(MovingPlatData));
    md->origin = pos;
    md->direction = dir;
    md->range = MPLAT_RANGE;
    md->phase = 0.0f;
    e->data = md;

    animation_set(&e->anim, &anim_platform);

    return e;
}


/* ════════════════════════════════════════════════════
 *  FLAME VENT — floor grate with timed blue flames
 * ════════════════════════════════════════════════════ */

static EntityManager *s_flame_em = NULL;

/* Damage cooldown tracking: use entity timer field */
#define FLAME_DAMAGE_CD 0.5f

static void flame_vent_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;
    FlameVentData *fd = (FlameVentData *)self->data;
    if (!fd) return;

    /* Tick the phase timer */
    fd->timer -= dt;
    if (fd->timer <= 0) {
        fd->active = !fd->active;
        fd->timer = fd->active ? FLAME_ON_TIME : FLAME_OFF_TIME;
    }

    /* Warning flicker: 0.3s before flames ignite */
    fd->warn_time = 0.0f;
    if (!fd->active && fd->timer < 0.3f) {
        fd->warn_time = fd->timer;
    }

    /* Animation */
    if (fd->active) {
        animation_set(&self->anim, &anim_flame_vent_active);
    } else {
        animation_set(&self->anim, &anim_flame_vent_idle);
    }
    animation_update(&self->anim, dt);

    /* Damage player when flames are active */
    if (fd->active) {
        /* Damage cooldown */
        if (self->timer > 0) {
            self->timer -= dt;
        }

        Entity *player = find_player(s_flame_em);
        if (player && player->active &&
            !(player->flags & ENTITY_DEAD) &&
            !(player->flags & ENTITY_INVINCIBLE) &&
            self->timer <= 0) {
            /* Check overlap with flame area (the full sprite, not just base) */
            if (physics_overlap(&self->body, &player->body)) {
                player->health -= FLAME_DAMAGE;
                if (player->health <= 0)
                    player->flags |= ENTITY_DEAD;
                self->timer = FLAME_DAMAGE_CD;

                /* Blue flame hit particles */
                Vec2 hit_center = vec2(
                    player->body.pos.x + player->body.size.x * 0.5f,
                    player->body.pos.y + player->body.size.y * 0.5f
                );
                SDL_Color flame_col = {68, 136, 255, 255};
                particle_emit_spark(hit_center, flame_col);
            }
        }

        /* Emit ambient flame particles */
        if ((int)(fd->timer * 10.0f) % 3 == 0) {
            Vec2 origin = vec2(
                self->body.pos.x + self->body.size.x * 0.5f,
                self->body.pos.y + 2.0f
            );
            ParticleBurst burst = {
                .origin      = origin,
                .count       = 2,
                .speed_min   = 20.0f,
                .speed_max   = 50.0f,
                .life_min    = 0.1f,
                .life_max    = 0.3f,
                .size_min    = 1.0f,
                .size_max    = 2.5f,
                .spread      = 0.4f,
                .direction   = -1.5708f,  /* upward */
                .drag        = 0.0f,
                .gravity_scale = -0.3f,   /* float upward */
                .color       = {100, 150, 255, 255},
                .color_vary  = true,
            };
            particle_emit(&burst);
        }
    }
}

static void flame_vent_render(Entity *self, const Camera *cam) {
    (void)cam;
    FlameVentData *fd = (FlameVentData *)self->data;
    Body *body = &self->body;

    if (g_spritesheet && self->anim.def) {
        SDL_Rect src = animation_current_rect(&self->anim);
        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = body->pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = false,
            .flip_y  = false,
            .layer   = LAYER_ENTITIES,
            .alpha   = 255,
        };

        /* Warning flicker: blink the sprite when about to ignite */
        if (fd && fd->warn_time > 0) {
            int blink = (int)(fd->warn_time * 20.0f) % 2;
            spr.alpha = blink ? 180 : 255;
        }

        renderer_submit(&spr);
    } else {
        SDL_Color color = fd && fd->active ?
            (SDL_Color){68, 136, 255, 255} :
            (SDL_Color){80, 80, 80, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

static void flame_vent_destroy(Entity *self) {
    free(self->data);
    self->data = NULL;
}

void flame_vent_register(EntityManager *em) {
    entity_register(em, ENT_FLAME_VENT, flame_vent_update, flame_vent_render, flame_vent_destroy);
    s_flame_em = em;
}

Entity *flame_vent_spawn(EntityManager *em, Vec2 pos) {
    Entity *e = entity_spawn(em, ENT_FLAME_VENT, pos);
    if (!e) return NULL;

    e->body.size = vec2(FLAME_WIDTH, FLAME_HEIGHT);
    e->body.gravity_scale = 0.0f;
    e->health = 9999;
    e->max_health = 9999;
    e->flags |= ENTITY_INVINCIBLE;
    e->damage = FLAME_DAMAGE;

    FlameVentData *fd = calloc(1, sizeof(FlameVentData));
    fd->active = false;
    fd->timer = FLAME_OFF_TIME;
    fd->warn_time = 0.0f;
    e->data = fd;
    e->timer = 0.0f;  /* damage cooldown */

    animation_set(&e->anim, &anim_flame_vent_idle);

    return e;
}


/* ════════════════════════════════════════════════════
 *  FORCE FIELD — energy barrier that toggles on/off
 * ════════════════════════════════════════════════════ */

static EntityManager *s_ffield_em = NULL;

static void force_field_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;
    ForceFieldData *fd = (ForceFieldData *)self->data;
    if (!fd) return;

    /* Tick the phase timer */
    fd->timer -= dt;
    if (fd->timer <= 0) {
        fd->active = !fd->active;
        fd->timer = fd->active ? FFIELD_ON_TIME : FFIELD_OFF_TIME;
    }

    /* Animation */
    if (fd->active) {
        animation_set(&self->anim, &anim_force_field_on);
    } else {
        animation_set(&self->anim, &anim_force_field_off);
    }
    animation_update(&self->anim, dt);

    /* When active: block player and deal damage */
    if (fd->active) {
        Entity *player = find_player(s_ffield_em);
        if (player && player->active && !(player->flags & ENTITY_DEAD)) {
            Body *pb = &player->body;
            Body *fb = &self->body;

            if (physics_overlap(pb, fb)) {
                /* Push player out of the force field */
                float player_cx = pb->pos.x + pb->size.x * 0.5f;
                float field_cx  = fb->pos.x + fb->size.x * 0.5f;

                if (player_cx < field_cx) {
                    /* Push left */
                    pb->pos.x = fb->pos.x - pb->size.x;
                    if (pb->vel.x > 0) pb->vel.x = 0;
                } else {
                    /* Push right */
                    pb->pos.x = fb->pos.x + fb->size.x;
                    if (pb->vel.x < 0) pb->vel.x = 0;
                }

                /* Damage with cooldown */
                if (!(player->flags & ENTITY_INVINCIBLE) && self->timer <= 0) {
                    player->health -= FFIELD_DAMAGE;
                    if (player->health <= 0)
                        player->flags |= ENTITY_DEAD;
                    self->timer = 0.8f;  /* damage cooldown */

                    /* Electric zap particles */
                    Vec2 zap_pos = vec2(
                        fb->pos.x + fb->size.x * 0.5f,
                        player->body.pos.y + player->body.size.y * 0.5f
                    );
                    SDL_Color zap_col = {136, 170, 255, 255};
                    particle_emit_spark(zap_pos, zap_col);
                }
            }
        }

        /* Emit shimmer particles */
        if ((int)(fd->timer * 8.0f) % 2 == 0) {
            float ry = self->body.pos.y + (float)(rand() % (int)self->body.size.y);
            Vec2 origin = vec2(
                self->body.pos.x + self->body.size.x * 0.5f,
                ry
            );
            ParticleBurst burst = {
                .origin      = origin,
                .count       = 1,
                .speed_min   = 5.0f,
                .speed_max   = 15.0f,
                .life_min    = 0.1f,
                .life_max    = 0.25f,
                .size_min    = 0.5f,
                .size_max    = 1.5f,
                .spread      = 3.14159f,
                .direction   = 0.0f,
                .drag        = 0.5f,
                .gravity_scale = 0.0f,
                .color       = {136, 170, 255, 200},
                .color_vary  = true,
            };
            particle_emit(&burst);
        }
    }

    /* Damage cooldown */
    if (self->timer > 0) {
        self->timer -= dt;
    }
}

static void force_field_render(Entity *self, const Camera *cam) {
    (void)cam;
    ForceFieldData *fd = (ForceFieldData *)self->data;
    Body *body = &self->body;

    if (g_spritesheet && self->anim.def) {
        SDL_Rect src = animation_current_rect(&self->anim);
        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = body->pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = false,
            .flip_y  = false,
            .layer   = LAYER_ENTITIES,
            .alpha   = fd && fd->active ? 220 : 100,
        };
        renderer_submit(&spr);
    } else {
        SDL_Color color = fd && fd->active ?
            (SDL_Color){100, 130, 255, 220} :
            (SDL_Color){40, 50, 80, 100};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

static void force_field_destroy(Entity *self) {
    free(self->data);
    self->data = NULL;
}

void force_field_register(EntityManager *em) {
    entity_register(em, ENT_FORCE_FIELD, force_field_update, force_field_render, force_field_destroy);
    s_ffield_em = em;
}

Entity *force_field_spawn(EntityManager *em, Vec2 pos) {
    Entity *e = entity_spawn(em, ENT_FORCE_FIELD, pos);
    if (!e) return NULL;

    e->body.size = vec2(FFIELD_WIDTH, FFIELD_HEIGHT);
    e->body.gravity_scale = 0.0f;
    e->health = 9999;
    e->max_health = 9999;
    e->flags |= ENTITY_INVINCIBLE;
    e->damage = FFIELD_DAMAGE;

    ForceFieldData *fd = calloc(1, sizeof(ForceFieldData));
    fd->active = true;
    fd->timer = FFIELD_ON_TIME;
    e->data = fd;
    e->timer = 0.0f;  /* damage cooldown */

    animation_set(&e->anim, &anim_force_field_on);

    return e;
}

/* ════════════════════════════════════════════════════
 *  ASTEROID — Falling space rock
 * ════════════════════════════════════════════════════ */

static EntityManager *s_asteroid_em = NULL;
static Sound s_sfx_asteroid_impact;
static bool  s_asteroid_sfx_loaded = false;

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

static void asteroid_update(Entity *self, float dt, const Tilemap *map) {
    AsteroidData *ad = (AsteroidData *)self->data;
    if (!ad) return;

    /* Initial delay before first fall */
    if (ad->start_delay > 0) {
        ad->start_delay -= dt;
        self->body.pos.y = -20.0f;  /* hide off-screen */
        return;
    }

    if (!ad->falling) {
        /* Waiting to respawn */
        ad->respawn_timer -= dt;
        if (ad->respawn_timer <= 0) {
            /* Reset to spawn position and start falling */
            self->body.pos = ad->spawn_pos;
            ad->falling = true;
            ad->trail_timer = 0;
            ad->fall_speed = ASTEROID_FALL_SPEED;
            /* Randomize drift direction each fall */
            ad->drift_x = ((float)(rand() % 201) - 100.0f) / 100.0f * ASTEROID_DRIFT_MAX;
        }
        return;
    }

    /* Accelerate while falling (gravity-like) */
    ad->fall_speed += ASTEROID_ACCEL * dt;
    self->body.pos.y += ad->fall_speed * dt;

    /* Apply horizontal drift */
    self->body.pos.x += ad->drift_x * dt;

    /* Tumble animation */
    animation_update(&self->anim, dt);

    /* Debris trail — cold grey dust kicked off the tumbling rock */
    ad->trail_timer -= dt;
    if (ad->trail_timer <= 0) {
        ad->trail_timer = 0.06f;
        Vec2 center = vec2(
            self->body.pos.x + self->body.size.x * 0.5f,
            self->body.pos.y
        );
        ParticleBurst trail = {
            .origin      = center,
            .count       = 3,
            .speed_min   = 8.0f,
            .speed_max   = 30.0f,
            .life_min    = 0.15f,
            .life_max    = 0.4f,
            .size_min    = 1.0f,
            .size_max    = 2.5f,
            .spread      = 0.8f,
            .direction   = -(float)M_PI / 2.0f,  /* upward */
            .drag        = 2.5f,
            .gravity_scale = 0.0f,
            .color       = {160, 160, 165, 150},  /* cold grey dust */
            .color_vary  = true,
        };
        particle_emit(&trail);
    }

    /* Check player collision */
    Entity *player = find_player(s_asteroid_em);
    if (player && !(player->flags & ENTITY_INVINCIBLE) &&
        !(player->flags & ENTITY_DEAD)) {
        if (physics_overlap(&self->body, &player->body)) {
            player->health -= ASTEROID_DAMAGE;
            if (player->health <= 0) {
                player->health = 0;
                player->flags |= ENTITY_DEAD;
            } else {
                /* Grant invincibility frames */
                PlayerData *ppd = (PlayerData *)player->data;
                if (ppd) {
                    ppd->inv_timer = PLAYER_INV_TIME;
                    player->flags |= ENTITY_INVINCIBLE;
                }

                /* Knockback downward and away */
                float knock_dir = (player->body.pos.x < self->body.pos.x)
                    ? -1.0f : 1.0f;
                player->body.vel.x = knock_dir * 120.0f;
                player->body.vel.y = 100.0f;
            }

            Vec2 hit_pos = vec2(
                player->body.pos.x + player->body.size.x * 0.5f,
                player->body.pos.y + player->body.size.y * 0.5f
            );

            /* Rock shrapnel — fast grey chunks */
            ParticleBurst shrapnel = {
                .origin      = hit_pos,
                .count       = 10,
                .speed_min   = 60.0f,
                .speed_max   = 180.0f,
                .life_min    = 0.2f,
                .life_max    = 0.5f,
                .size_min    = 1.5f,
                .size_max    = 3.0f,
                .spread      = (float)M_PI,
                .direction   = 0,
                .drag        = 2.0f,
                .gravity_scale = 0.4f,
                .color       = {150, 150, 155, 255},
                .color_vary  = true,
            };
            particle_emit(&shrapnel);

            /* Dust cloud */
            ParticleBurst dust = {
                .origin      = hit_pos,
                .count       = 8,
                .speed_min   = 20.0f,
                .speed_max   = 60.0f,
                .life_min    = 0.3f,
                .life_max    = 0.6f,
                .size_min    = 2.0f,
                .size_max    = 4.0f,
                .spread      = (float)M_PI,
                .direction   = 0,
                .drag        = 4.0f,
                .gravity_scale = 0.0f,
                .color       = {130, 130, 135, 180},
                .color_vary  = true,
            };
            particle_emit(&dust);

            if (s_asteroid_sfx_loaded) {
                audio_play_sound_at(s_sfx_asteroid_impact, 80, hit_pos, 0);
            }

            /* Explode on contact — hide and start respawn */
            ad->falling = false;
            ad->respawn_timer = ASTEROID_RESPAWN;
            self->body.pos.y = -100.0f;
            return;
        }
    }

    /* Check ground collision */
    int tx = world_to_tile(self->body.pos.x + self->body.size.x * 0.5f);
    int ty = world_to_tile(self->body.pos.y + self->body.size.y);
    bool hit_ground = tilemap_is_solid(map, tx, ty);

    /* Also despawn if far below level */
    float level_bottom = (float)(map->height * TILE_SIZE) + 32.0f;
    if (hit_ground || self->body.pos.y > level_bottom) {
        Vec2 impact_pos = vec2(
            self->body.pos.x + self->body.size.x * 0.5f,
            self->body.pos.y + self->body.size.y
        );

        /* Rock shrapnel — fast chunks flying outward */
        ParticleBurst shrapnel = {
            .origin      = impact_pos,
            .count       = 16,
            .speed_min   = 80.0f,
            .speed_max   = 220.0f,
            .life_min    = 0.3f,
            .life_max    = 0.7f,
            .size_min    = 1.5f,
            .size_max    = 3.5f,
            .spread      = (float)M_PI,
            .direction   = 0,
            .drag        = 1.5f,
            .gravity_scale = 0.5f,
            .color       = {140, 140, 145, 255},
            .color_vary  = true,
        };
        particle_emit(&shrapnel);

        /* Dust plume — big, slow, billowing upward */
        ParticleBurst dust_plume = {
            .origin      = impact_pos,
            .count       = 14,
            .speed_min   = 30.0f,
            .speed_max   = 80.0f,
            .life_min    = 0.4f,
            .life_max    = 0.9f,
            .size_min    = 2.5f,
            .size_max    = 5.0f,
            .spread      = 1.2f,
            .direction   = -(float)M_PI / 2.0f,  /* upward */
            .drag        = 3.0f,
            .gravity_scale = -0.1f,
            .color       = {120, 120, 125, 160},
            .color_vary  = true,
        };
        particle_emit(&dust_plume);

        /* Ground-level dust spread — hugs the surface */
        ParticleBurst ground_dust = {
            .origin      = impact_pos,
            .count       = 10,
            .speed_min   = 40.0f,
            .speed_max   = 120.0f,
            .life_min    = 0.3f,
            .life_max    = 0.6f,
            .size_min    = 2.0f,
            .size_max    = 3.5f,
            .spread      = 0.3f,
            .direction   = (float)M_PI,  /* leftward half */
            .drag        = 3.5f,
            .gravity_scale = 0.0f,
            .color       = {150, 150, 155, 140},
            .color_vary  = true,
        };
        particle_emit(&ground_dust);
        ground_dust.direction = 0;  /* rightward half */
        particle_emit(&ground_dust);

        /* Bright flash — brief white pop at impact point */
        ParticleBurst flash = {
            .origin      = impact_pos,
            .count       = 4,
            .speed_min   = 5.0f,
            .speed_max   = 15.0f,
            .life_min    = 0.04f,
            .life_max    = 0.08f,
            .size_min    = 3.0f,
            .size_max    = 5.0f,
            .spread      = (float)M_PI,
            .direction   = 0,
            .drag        = 8.0f,
            .gravity_scale = 0.0f,
            .color       = {220, 220, 230, 255},
            .color_vary  = false,
        };
        particle_emit(&flash);

        if (s_asteroid_sfx_loaded) {
            audio_play_sound_at(s_sfx_asteroid_impact, 60, impact_pos, 0);
        }

        /* Hide and start respawn timer */
        ad->falling = false;
        ad->respawn_timer = ASTEROID_RESPAWN;
        self->body.pos.y = -100.0f;  /* hide off-screen */
    }
}

static void asteroid_render(Entity *self, const Camera *cam) {
    (void)cam;
    AsteroidData *ad = (AsteroidData *)self->data;
    if (!ad || !ad->falling) return;
    if (!g_spritesheet || !self->anim.def) return;

    SDL_Rect src = animation_current_rect(&self->anim);
    Body *body = &self->body;

    float draw_x = body->pos.x + body->size.x * 0.5f - SPRITE_CELL * 0.5f;
    float draw_y = body->pos.y + body->size.y * 0.5f - SPRITE_CELL * 0.5f;

    Sprite spr = {
        .texture = g_spritesheet,
        .src     = src,
        .pos     = vec2(draw_x, draw_y),
        .size    = vec2(SPRITE_CELL, SPRITE_CELL),
        .flip_x  = false,
        .flip_y  = false,
        .layer   = LAYER_ENTITIES,
        .alpha   = 255,
    };
    renderer_submit(&spr);
}

static void asteroid_destroy(Entity *self) {
    free(self->data);
    self->data = NULL;
}

void asteroid_register(EntityManager *em) {
    entity_register(em, ENT_ASTEROID, asteroid_update, asteroid_render, asteroid_destroy);
    s_asteroid_em = em;

    if (!s_asteroid_sfx_loaded) {
        s_sfx_asteroid_impact = audio_load_sound("assets/sounds/asteroidCollision.wav");
        s_asteroid_sfx_loaded = true;
    }
}

Entity *asteroid_spawn(EntityManager *em, Vec2 pos) {
    Entity *e = entity_spawn(em, ENT_ASTEROID, pos);
    if (!e) return NULL;

    e->body.size = vec2(ASTEROID_WIDTH, ASTEROID_HEIGHT);
    e->body.gravity_scale = 0.0f;  /* we handle movement manually */
    e->health = 9999;
    e->max_health = 9999;
    e->flags |= ENTITY_INVINCIBLE;
    e->damage = ASTEROID_DAMAGE;

    AsteroidData *ad = calloc(1, sizeof(AsteroidData));
    ad->spawn_pos = pos;
    ad->falling = true;
    ad->fall_speed = ASTEROID_FALL_SPEED;
    ad->drift_x = ((float)(rand() % 201) - 100.0f) / 100.0f * ASTEROID_DRIFT_MAX;
    ad->trail_timer = 0;
    ad->respawn_timer = 0;
    /* Stagger start times based on spawn position to avoid all falling at once */
    ad->start_delay = (pos.x * 0.013f + pos.y * 0.007f);
    ad->start_delay = ad->start_delay - (float)(int)ad->start_delay;  /* frac part */
    ad->start_delay *= 8.0f;  /* 0-8s stagger */
    e->data = ad;

    animation_set(&e->anim, &anim_asteroid);

    return e;
}
