#include "game/enemy.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>

/* ════════════════════════════════════════════════════
 *  GRUNT - ground patrol enemy
 * ════════════════════════════════════════════════════ */

static EntityManager *s_grunt_em = NULL;

static void grunt_update(Entity *self, float dt, const Tilemap *map) {
    GruntData *gd = (GruntData *)self->data;
    if (!gd) return;

    Body *body = &self->body;

    /* Death sequence */
    if (self->flags & ENTITY_DEAD) {
        animation_set(&self->anim, &anim_grunt_death);
        animation_update(&self->anim, dt);
        gd->death_timer -= dt;
        body->vel.x = 0;
        if (gd->death_timer <= 0) {
            entity_destroy(s_grunt_em, self);
        }
        return;
    }

    /* Patrol: walk in one direction, reverse when hitting a wall
       or about to walk off a ledge */
    body->vel.x = gd->patrol_dir * GRUNT_SPEED;

    /* Set facing direction */
    if (gd->patrol_dir < 0) self->flags |= ENTITY_FACING_LEFT;
    else                     self->flags &= ~ENTITY_FACING_LEFT;

    /* Physics */
    physics_update(body, dt, map);

    /* Turn around on wall collision */
    if (body->on_wall_left || body->on_wall_right) {
        gd->patrol_dir = -gd->patrol_dir;
    }

    /* Turn around at ledge: check if there's ground ahead */
    if (body->on_ground) {
        float check_x = (gd->patrol_dir > 0) ?
            body->pos.x + body->size.x + 2.0f :
            body->pos.x - 2.0f;
        float check_y = body->pos.y + body->size.y + 4.0f;

        int tx = world_to_tile(check_x);
        int ty = world_to_tile(check_y);

        if (!tilemap_is_solid(map, tx, ty)) {
            gd->patrol_dir = -gd->patrol_dir;
        }
    }

    /* Animation */
    if (fabsf(body->vel.x) > 1.0f) {
        animation_set(&self->anim, &anim_grunt_walk);
    } else {
        animation_set(&self->anim, &anim_grunt_idle);
    }
    animation_update(&self->anim, dt);
}

static void grunt_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 - 2.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 = {200, 60, 60, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

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

void grunt_register(EntityManager *em) {
    entity_register(em, ENT_ENEMY_GRUNT, grunt_update, grunt_render, grunt_destroy);
    s_grunt_em = em;
}

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

    e->body.size = vec2(GRUNT_WIDTH, GRUNT_HEIGHT);
    e->body.gravity_scale = 1.0f;
    e->health = GRUNT_HEALTH;
    e->max_health = GRUNT_HEALTH;
    e->damage = 1;

    GruntData *gd = calloc(1, sizeof(GruntData));
    gd->patrol_dir = 1.0f;
    gd->death_timer = 0.3f;
    e->data = gd;

    return e;
}


/* ════════════════════════════════════════════════════
 *  FLYER - flying enemy
 * ════════════════════════════════════════════════════ */

static EntityManager *s_flyer_em = NULL;

/* Helper: 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;
}

static void flyer_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;  /* flyers don't collide with tiles */
    FlyerData *fd = (FlyerData *)self->data;
    if (!fd) return;

    Body *body = &self->body;

    /* Death sequence */
    if (self->flags & ENTITY_DEAD) {
        animation_set(&self->anim, &anim_flyer_death);
        animation_update(&self->anim, dt);
        /* Fall when dead */
        body->vel.y += physics_get_gravity() * dt;
        if (body->vel.y > MAX_FALL_SPEED) body->vel.y = MAX_FALL_SPEED;
        body->pos.y += body->vel.y * dt;
        fd->death_timer -= dt;
        if (fd->death_timer <= 0) {
            entity_destroy(s_flyer_em, self);
        }
        return;
    }

    /* Bob up and down */
    fd->bob_timer += dt;
    float bob_offset = sinf(fd->bob_timer * FLYER_BOB_SPD) * FLYER_BOB_AMP;
    body->pos.y = fd->base_y + bob_offset;

    /* Wind drifts flyers (they bypass physics_update).
     * Apply as gentle position offset matching first-frame physics. */
    float wind = physics_get_wind();
    if (wind != 0.0f) {
        body->pos.x += wind * dt * dt;
    }

    /* Chase player if in range */
    Entity *player = find_player(s_flyer_em);
    if (player && player->active && !(player->flags & ENTITY_DEAD)) {
        float px = player->body.pos.x + player->body.size.x * 0.5f;
        float fx = body->pos.x + body->size.x * 0.5f;
        float dist = px - fx;

        if (fabsf(dist) < FLYER_DETECT) {
            /* Move toward player */
            if (dist < -2.0f) {
                body->pos.x -= FLYER_SPEED * dt;
                self->flags |= ENTITY_FACING_LEFT;
            } else if (dist > 2.0f) {
                body->pos.x += FLYER_SPEED * dt;
                self->flags &= ~ENTITY_FACING_LEFT;
            }

            /* Shoot at player */
            fd->shoot_timer -= dt;
            if (fd->shoot_timer <= 0 && s_flyer_em) {
                fd->shoot_timer = FLYER_SHOOT_CD;
                bool facing_left = (self->flags & ENTITY_FACING_LEFT) != 0;
                float bx = facing_left ?
                    body->pos.x - 4.0f :
                    body->pos.x + body->size.x;
                float by = body->pos.y + body->size.y * 0.4f;
                projectile_spawn(s_flyer_em, vec2(bx, by), facing_left, false);
            }
        }
    }

    /* Animation */
    animation_set(&self->anim, &anim_flyer_fly);
    animation_update(&self->anim, dt);
}

static void flyer_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 - 2.0f
        );

        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 = {150, 50, 180, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

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

void flyer_register(EntityManager *em) {
    entity_register(em, ENT_ENEMY_FLYER, flyer_update, flyer_render, flyer_destroy);
    s_flyer_em = em;
}

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

    e->body.size = vec2(FLYER_WIDTH, FLYER_HEIGHT);
    e->body.gravity_scale = 0.0f;  /* flyers don't fall */
    e->health = FLYER_HEALTH;
    e->max_health = FLYER_HEALTH;
    e->damage = 1;

    FlyerData *fd = calloc(1, sizeof(FlyerData));
    fd->base_y = pos.y;
    fd->death_timer = 0.5f;
    fd->shoot_timer = FLYER_SHOOT_CD;
    e->data = fd;

    return e;
}


/* ════════════════════════════════════════════════════
 *  CHARGER — detects player, telegraphs, then rushes
 * ════════════════════════════════════════════════════ */

#define CHARGER_ALERT_TIME  0.5f   /* telegraph before charge */
#define CHARGER_STUN_TIME   0.8f   /* stun duration on wall   */

static EntityManager *s_charger_em = NULL;

static void charger_update(Entity *self, float dt, const Tilemap *map) {
    ChargerData *cd = (ChargerData *)self->data;
    if (!cd) return;

    Body *body = &self->body;

    /* Death sequence */
    if (self->flags & ENTITY_DEAD) {
        animation_set(&self->anim, &anim_charger_death);
        animation_update(&self->anim, dt);
        cd->death_timer -= dt;
        body->vel.x = 0;
        if (cd->death_timer <= 0) {
            particle_emit_death_puff(body->pos, (SDL_Color){220, 120, 40, 255});
            entity_destroy(s_charger_em, self);
        }
        return;
    }

    /* State machine */
    switch (cd->state) {
        case CHARGER_PATROL: {
            body->vel.x = cd->patrol_dir * CHARGER_PATROL_SPEED;

            /* Face walk direction */
            if (cd->patrol_dir < 0) self->flags |= ENTITY_FACING_LEFT;
            else                     self->flags &= ~ENTITY_FACING_LEFT;

            physics_update(body, dt, map);

            /* Reverse at walls */
            if (body->on_wall_left || body->on_wall_right) {
                cd->patrol_dir = -cd->patrol_dir;
            }

            /* Reverse at ledge */
            if (body->on_ground) {
                float cx = (cd->patrol_dir > 0) ?
                    body->pos.x + body->size.x + 2.0f :
                    body->pos.x - 2.0f;
                float cy = body->pos.y + body->size.y + 4.0f;
                if (!tilemap_is_solid(map, world_to_tile(cx), world_to_tile(cy))) {
                    cd->patrol_dir = -cd->patrol_dir;
                }
            }

            /* Detect player — horizontal line-of-sight */
            Entity *player = find_player(s_charger_em);
            if (player && player->active && !(player->flags & ENTITY_DEAD)) {
                float px = player->body.pos.x + player->body.size.x * 0.5f;
                float mx = body->pos.x + body->size.x * 0.5f;
                float dy = fabsf(player->body.pos.y - body->pos.y);
                float dx = px - mx;

                /* Must be roughly same height and within range */
                if (dy < body->size.y * 1.5f && fabsf(dx) < CHARGER_DETECT_RANGE) {
                    cd->state = CHARGER_ALERT;
                    cd->state_timer = CHARGER_ALERT_TIME;
                    /* Face the player */
                    cd->patrol_dir = (dx > 0) ? 1.0f : -1.0f;
                    if (cd->patrol_dir < 0) self->flags |= ENTITY_FACING_LEFT;
                    else                     self->flags &= ~ENTITY_FACING_LEFT;
                }
            }
            break;
        }

        case CHARGER_ALERT: {
            /* Freeze in place during telegraph */
            body->vel.x = 0;
            physics_update(body, dt, map);
            cd->state_timer -= dt;
            if (cd->state_timer <= 0) {
                cd->state = CHARGER_CHARGE;
                cd->state_timer = CHARGER_CHARGE_TIME;
            }
            break;
        }

        case CHARGER_CHARGE: {
            body->vel.x = cd->patrol_dir * CHARGER_CHARGE_SPEED;
            physics_update(body, dt, map);
            cd->state_timer -= dt;

            /* Hit a wall -> stunned */
            if (body->on_wall_left || body->on_wall_right) {
                cd->state = CHARGER_STUNNED;
                cd->state_timer = CHARGER_STUN_TIME;
                body->vel.x = 0;
                particle_emit_spark(body->pos, (SDL_Color){255, 200, 80, 255});
            }

            /* Ran off a ledge or charge timed out -> back to patrol */
            if (!body->on_ground || cd->state_timer <= 0) {
                cd->state = CHARGER_PATROL;
                body->vel.x = 0;
            }
            break;
        }

        case CHARGER_STUNNED: {
            body->vel.x = 0;
            physics_update(body, dt, map);
            cd->state_timer -= dt;
            if (cd->state_timer <= 0) {
                cd->state = CHARGER_PATROL;
                /* Reverse direction after stun */
                cd->patrol_dir = -cd->patrol_dir;
            }
            break;
        }
    }

    /* Animation */
    if (cd->state == CHARGER_CHARGE || cd->state == CHARGER_PATROL) {
        animation_set(&self->anim, &anim_charger_walk);
    } else {
        animation_set(&self->anim, &anim_charger_idle);
    }
    animation_update(&self->anim, dt);
}

static void charger_render(Entity *self, const Camera *cam) {
    (void)cam;
    Body *body = &self->body;
    ChargerData *cd = (ChargerData *)self->data;

    if (g_spritesheet && self->anim.def) {
        SDL_Rect src = animation_current_rect(&self->anim);

        /* Center the 16x16 sprite over the 14x16 hitbox */
        Vec2 render_pos = vec2(
            body->pos.x - 1.0f,
            body->pos.y
        );

        /* Flash white during alert telegraph */
        uint8_t alpha = 255;
        if (cd && cd->state == CHARGER_ALERT) {
            float blink = sinf(cd->state_timer * 30.0f);
            alpha = (blink > 0) ? 180 : 255;
        }

        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   = alpha,
        };
        renderer_submit(&spr);
    } else {
        /* Colored rect fallback */
        SDL_Color color;
        if (cd && cd->state == CHARGER_ALERT) {
            float blink = sinf(cd->state_timer * 30.0f);
            uint8_t r = (blink > 0) ? 255 : 200;
            color = (SDL_Color){r, 160, 40, 255};
        } else if (cd && cd->state == CHARGER_STUNNED) {
            color = (SDL_Color){160, 160, 100, 255};
        } else if (cd && cd->state == CHARGER_CHARGE) {
            color = (SDL_Color){255, 120, 30, 255};
        } else {
            color = (SDL_Color){220, 140, 40, 255};
        }
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

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

void charger_register(EntityManager *em) {
    entity_register(em, ENT_ENEMY_CHARGER,
                    charger_update, charger_render, charger_destroy);
    s_charger_em = em;
}

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

    e->body.size = vec2(CHARGER_WIDTH, CHARGER_HEIGHT);
    e->body.gravity_scale = 1.0f;
    e->health = CHARGER_HEALTH;
    e->max_health = CHARGER_HEALTH;
    e->damage = 1;

    ChargerData *cd = calloc(1, sizeof(ChargerData));
    if (!cd) { entity_destroy(em, e); return NULL; }
    cd->state = CHARGER_PATROL;
    cd->patrol_dir = 1.0f;
    cd->state_timer = 0;
    cd->death_timer = 0.3f;
    e->data = cd;

    return e;
}


/* ════════════════════════════════════════════════════
 *  SPAWNER — stationary, periodically spawns grunts
 * ════════════════════════════════════════════════════ */

#define SPAWNER_PULSE_TIME  1.0f  /* pulse warning before spawn */

static EntityManager *s_spawner_em = NULL;

/* Count how many grunts are currently alive. */
static int count_alive_grunts(EntityManager *em) {
    int count = 0;
    for (int i = 0; i < em->count; i++) {
        Entity *e = &em->entities[i];
        if (e->active && e->type == ENT_ENEMY_GRUNT &&
            !(e->flags & ENTITY_DEAD)) {
            count++;
        }
    }
    return count;
}

static void spawner_update(Entity *self, float dt, const Tilemap *map) {
    (void)map;
    SpawnerData *sd = (SpawnerData *)self->data;
    if (!sd) return;

    /* Death sequence */
    if (self->flags & ENTITY_DEAD) {
        animation_set(&self->anim, &anim_spawner_death);
        animation_update(&self->anim, dt);
        sd->death_timer -= dt;
        if (sd->death_timer <= 0) {
            particle_emit_death_puff(self->body.pos, (SDL_Color){180, 60, 180, 255});
            entity_destroy(s_spawner_em, self);
        }
        return;
    }

    sd->spawn_timer -= dt;

    /* Pulse warning when about to spawn */
    if (sd->spawn_timer < SPAWNER_PULSE_TIME && sd->spawn_timer > 0) {
        sd->pulse_timer += dt;
    } else {
        sd->pulse_timer = 0;
    }

    /* Animation */
    animation_set(&self->anim, &anim_spawner_idle);
    animation_update(&self->anim, dt);

    /* Spawn a grunt when timer expires */
    if (sd->spawn_timer <= 0) {
        sd->spawn_timer = SPAWNER_INTERVAL;

        if (count_alive_grunts(s_spawner_em) < SPAWNER_MAX_ALIVE) {
            /* Spawn grunt just below the spawner */
            Vec2 spawn_pos = vec2(
                self->body.pos.x,
                self->body.pos.y + self->body.size.y + 2.0f
            );
            Entity *grunt = grunt_spawn(s_spawner_em, spawn_pos);
            if (grunt) {
                particle_emit_spark(spawn_pos, (SDL_Color){180, 100, 220, 255});
            }
        }
    }
}

static void spawner_render(Entity *self, const Camera *cam) {
    (void)cam;
    Body *body = &self->body;
    SpawnerData *sd = (SpawnerData *)self->data;

    if (g_spritesheet && self->anim.def) {
        SDL_Rect src = animation_current_rect(&self->anim);

        Vec2 render_pos = vec2(body->pos.x, body->pos.y);

        /* Pulse brightness when about to spawn */
        uint8_t alpha = 255;
        if (sd && sd->pulse_timer > 0) {
            float pulse = sinf(sd->pulse_timer * 12.0f);
            alpha = (pulse > 0) ? 200 : 255;
        }

        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = render_pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = false,
            .flip_y  = false,
            .layer   = LAYER_ENTITIES,
            .alpha   = alpha,
        };
        renderer_submit(&spr);
    } else {
        /* Colored rect fallback */
        SDL_Color color;
        if (sd && sd->pulse_timer > 0) {
            float pulse = sinf(sd->pulse_timer * 12.0f) * 0.5f + 0.5f;
            uint8_t r = (uint8_t)(140 + 80 * pulse);
            uint8_t b = (uint8_t)(180 + 60 * pulse);
            color = (SDL_Color){r, 50, b, 255};
        } else {
            color = (SDL_Color){140, 50, 160, 255};
        }
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

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

void spawner_register(EntityManager *em) {
    entity_register(em, ENT_SPAWNER,
                    spawner_update, spawner_render, spawner_destroy);
    s_spawner_em = em;
}

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

    e->body.size = vec2(SPAWNER_WIDTH, SPAWNER_HEIGHT);
    e->body.gravity_scale = 0.0f; /* stationary */
    e->health = SPAWNER_HEALTH;
    e->max_health = SPAWNER_HEALTH;
    e->damage = 0; /* no contact damage, not invincible */

    SpawnerData *sd = calloc(1, sizeof(SpawnerData));
    if (!sd) { entity_destroy(em, e); return NULL; }
    sd->spawn_timer = SPAWNER_INTERVAL;
    sd->death_timer = 0.4f;
    sd->pulse_timer = 0;
    e->data = sd;

    return e;
}
