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

static EntityManager *s_proj_em = NULL;

/* ════════════════════════════════════════════════════
 *  Built-in weapon definitions
 * ════════════════════════════════════════════════════ */

const ProjectileDef WEAPON_PLASMA = {
    .name           = "plasma",
    .speed          = 400.0f,
    .damage         = 1,
    .lifetime       = 2.0f,
    .gravity_scale  = 0.0f,
    .pierce_count   = 0,
    .bounce_count   = 0,
    .homing_strength = 0.0f,
    .hitbox_w       = 8.0f,
    .hitbox_h       = 8.0f,
    .flags          = 0,
    .anim_fly       = NULL,  /* set in projectile_register after sprites_init_anims */
    .anim_impact    = NULL,
};

const ProjectileDef WEAPON_SPREAD = {
    .name           = "spread",
    .speed          = 350.0f,
    .damage         = 1,
    .lifetime       = 0.8f,    /* short range */
    .gravity_scale  = 0.0f,
    .pierce_count   = 0,
    .bounce_count   = 0,
    .homing_strength = 0.0f,
    .hitbox_w       = 6.0f,
    .hitbox_h       = 6.0f,
    .flags          = 0,
    .anim_fly       = NULL,
    .anim_impact    = NULL,
};

const ProjectileDef WEAPON_LASER = {
    .name           = "laser",
    .speed          = 600.0f,
    .damage         = 1,
    .lifetime       = 1.5f,
    .gravity_scale  = 0.0f,
    .pierce_count   = 3,      /* passes through 3 enemies */
    .bounce_count   = 0,
    .homing_strength = 0.0f,
    .hitbox_w       = 10.0f,
    .hitbox_h       = 4.0f,
    .flags          = PROJ_PIERCING,
    .anim_fly       = NULL,
    .anim_impact    = NULL,
};

const ProjectileDef WEAPON_ROCKET = {
    .name           = "rocket",
    .speed          = 200.0f,
    .damage         = 3,
    .lifetime       = 3.0f,
    .gravity_scale  = 0.1f,    /* slight drop */
    .pierce_count   = 0,
    .bounce_count   = 0,
    .homing_strength = 0.0f,
    .hitbox_w       = 10.0f,
    .hitbox_h       = 6.0f,
    .flags          = PROJ_GRAVITY,
    .anim_fly       = NULL,
    .anim_impact    = NULL,
};

const ProjectileDef WEAPON_BOUNCE = {
    .name           = "bounce",
    .speed          = 300.0f,
    .damage         = 1,
    .lifetime       = 4.0f,
    .gravity_scale  = 0.5f,
    .pierce_count   = 0,
    .bounce_count   = 3,       /* bounces off 3 walls */
    .homing_strength = 0.0f,
    .hitbox_w       = 6.0f,
    .hitbox_h       = 6.0f,
    .flags          = PROJ_BOUNCY | PROJ_GRAVITY,
    .anim_fly       = NULL,
    .anim_impact    = NULL,
};

const ProjectileDef WEAPON_ENEMY_FIRE = {
    .name           = "enemy_fire",
    .speed          = 180.0f,
    .damage         = 1,
    .lifetime       = 3.0f,
    .gravity_scale  = 0.0f,
    .pierce_count   = 0,
    .bounce_count   = 0,
    .homing_strength = 0.0f,
    .hitbox_w       = 8.0f,
    .hitbox_h       = 8.0f,
    .flags          = 0,
    .anim_fly       = NULL,
    .anim_impact    = NULL,
};

/* ── Mutable copies with animation pointers ────────── */
/* We need mutable copies because the AnimDef pointers   */
/* aren't available at compile time (set after init).    */

static ProjectileDef s_weapon_plasma;
static ProjectileDef s_weapon_spread;
static ProjectileDef s_weapon_laser;
static ProjectileDef s_weapon_rocket;
static ProjectileDef s_weapon_bounce;
static ProjectileDef s_weapon_enemy_fire;

static void init_weapon_defs(void) {
    s_weapon_plasma     = WEAPON_PLASMA;
    s_weapon_plasma.anim_fly    = &anim_bullet;
    s_weapon_plasma.anim_impact = &anim_bullet_impact;

    s_weapon_spread     = WEAPON_SPREAD;
    s_weapon_spread.anim_fly    = &anim_bullet;
    s_weapon_spread.anim_impact = &anim_bullet_impact;

    s_weapon_laser      = WEAPON_LASER;
    s_weapon_laser.anim_fly    = &anim_bullet;
    s_weapon_laser.anim_impact = &anim_bullet_impact;

    s_weapon_rocket     = WEAPON_ROCKET;
    s_weapon_rocket.anim_fly    = &anim_bullet;
    s_weapon_rocket.anim_impact = &anim_bullet_impact;

    s_weapon_bounce     = WEAPON_BOUNCE;
    s_weapon_bounce.anim_fly    = &anim_bullet;
    s_weapon_bounce.anim_impact = &anim_bullet_impact;

    s_weapon_enemy_fire = WEAPON_ENEMY_FIRE;
    s_weapon_enemy_fire.anim_fly    = &anim_enemy_bullet;
    s_weapon_enemy_fire.anim_impact = &anim_bullet_impact;
}

/* ════════════════════════════════════════════════════
 *  Projectile entity callbacks
 * ════════════════════════════════════════════════════ */

static void resolve_wall_bounce(Body *body, const Tilemap *map, int *bounces_left) {
    /* Check if the center of the projectile is inside a solid tile */
    int cx = world_to_tile(body->pos.x + body->size.x * 0.5f);
    int cy = world_to_tile(body->pos.y + body->size.y * 0.5f);

    if (!tilemap_is_solid(map, cx, cy)) return;
    if (*bounces_left <= 0) return;

    (*bounces_left)--;

    /* Determine bounce axis by checking neighboring tiles */
    int left_tile  = world_to_tile(body->pos.x);
    int right_tile = world_to_tile(body->pos.x + body->size.x);
    int top_tile   = world_to_tile(body->pos.y);
    int bot_tile   = world_to_tile(body->pos.y + body->size.y);

    bool hit_h = tilemap_is_solid(map, cx, top_tile - 1) == false &&
                 tilemap_is_solid(map, cx, bot_tile + 1) == false;
    bool hit_v = tilemap_is_solid(map, left_tile - 1, cy) == false &&
                 tilemap_is_solid(map, right_tile + 1, cy) == false;

    if (hit_h || (!hit_h && !hit_v)) {
        body->vel.y = -body->vel.y;
    }
    if (hit_v || (!hit_h && !hit_v)) {
        body->vel.x = -body->vel.x;
    }

    /* Push out of solid tile */
    body->pos.x += body->vel.x * DT;
    body->pos.y += body->vel.y * DT;
}

static void projectile_update(Entity *self, float dt, const Tilemap *map) {
    ProjectileData *pd = (ProjectileData *)self->data;
    if (!pd || !pd->def) return;

    const ProjectileDef *def = pd->def;
    Body *body = &self->body;

    /* ── Impact animation phase ──────────────── */
    if (pd->proj_flags & PROJ_IMPACT) {
        animation_update(&self->anim, dt);
        if (self->anim.finished) {
            entity_destroy(s_proj_em, self);
        }
        return;
    }

    /* ── Apply gravity if flagged ────────────── */
    if (def->flags & PROJ_GRAVITY) {
        body->vel.y += physics_get_gravity() * def->gravity_scale * dt;
        if (body->vel.y > MAX_FALL_SPEED) body->vel.y = MAX_FALL_SPEED;
    }

    /* ── Apply wind ─────────────────────────── */
    float wind = physics_get_wind();
    if (wind != 0.0f) {
        body->vel.x += wind * dt;
    }

    /* ── Move ────────────────────────────────── */
    body->pos.x += body->vel.x * dt;
    body->pos.y += body->vel.y * dt;

    /* ── Tilemap collision ───────────────────── */
    int cx = world_to_tile(body->pos.x + body->size.x * 0.5f);
    int cy = world_to_tile(body->pos.y + body->size.y * 0.5f);
    if (tilemap_is_solid(map, cx, cy)) {
        if ((def->flags & PROJ_BOUNCY) && pd->bounces_left > 0) {
            resolve_wall_bounce(body, map, &pd->bounces_left);
        } else {
            projectile_hit(self);
            return;
        }
    }

    /* ── Lifetime ────────────────────────────── */
    pd->lifetime -= dt;
    if (pd->lifetime <= 0) {
        entity_destroy(s_proj_em, self);
        return;
    }

    /* ── Homing ──────────────────────────────── */
    if ((def->flags & PROJ_HOMING) && def->homing_strength > 0 && s_proj_em) {
        /* Find nearest valid target */
        bool is_player_proj = (pd->proj_flags & PROJ_FROM_PLAYER) != 0;
        Entity *best = NULL;
        float best_dist = 99999.0f;

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

        for (int i = 0; i < s_proj_em->count; i++) {
            Entity *e = &s_proj_em->entities[i];
            if (!e->active || (e->flags & ENTITY_DEAD)) continue;

            /* Player bullets target enemies, enemy bullets target player */
            if (is_player_proj) {
                if (e->type != ENT_ENEMY_GRUNT && e->type != ENT_ENEMY_FLYER) continue;
            } else {
                if (e->type != ENT_PLAYER) continue;
            }

            Vec2 target_center = vec2(
                e->body.pos.x + e->body.size.x * 0.5f,
                e->body.pos.y + e->body.size.y * 0.5f
            );
            float d = vec2_dist(proj_center, target_center);
            if (d < best_dist) {
                best_dist = d;
                best = e;
            }
        }

        if (best) {
            Vec2 target_center = vec2(
                best->body.pos.x + best->body.size.x * 0.5f,
                best->body.pos.y + best->body.size.y * 0.5f
            );
            Vec2 to_target = vec2_norm(vec2_sub(target_center, proj_center));
            Vec2 cur_dir = vec2_norm(body->vel);
            float spd = vec2_len(body->vel);

            /* Steer toward target */
            Vec2 new_dir = vec2_norm(vec2_lerp(cur_dir, to_target,
                                               def->homing_strength * dt));
            body->vel = vec2_scale(new_dir, spd);
        }
    }

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

static void projectile_render(Entity *self, const Camera *cam) {
    (void)cam;
    Body *body = &self->body;
    ProjectileData *pd = (ProjectileData *)self->data;

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

        /* Center the 16x16 sprite on the smaller hitbox */
        Vec2 render_pos = vec2(
            body->pos.x + body->size.x * 0.5f - SPRITE_CELL * 0.5f,
            body->pos.y + body->size.y * 0.5f - SPRITE_CELL * 0.5f
        );

        bool flip_x = false;
        bool flip_y = false;
        if (pd && !(pd->proj_flags & PROJ_IMPACT)) {
            flip_x = (body->vel.x < 0);
            /* Flip vertically for downward-only projectiles */
            flip_y = (body->vel.y > 0 && fabsf(body->vel.x) < 1.0f);
        }

        Sprite spr = {
            .texture = g_spritesheet,
            .src     = src,
            .pos     = render_pos,
            .size    = vec2(SPRITE_CELL, SPRITE_CELL),
            .flip_x  = flip_x,
            .flip_y  = flip_y,
            .layer   = LAYER_ENTITIES,
            .alpha   = 255,
        };
        renderer_submit(&spr);
    } else {
        /* Fallback colored rectangle */
        SDL_Color color = (pd && (pd->proj_flags & PROJ_FROM_PLAYER)) ?
            (SDL_Color){100, 220, 255, 255} :
            (SDL_Color){255, 180, 50, 255};
        renderer_draw_rect(body->pos, body->size, color, LAYER_ENTITIES, cam);
    }
}

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

/* ════════════════════════════════════════════════════
 *  Public API
 * ════════════════════════════════════════════════════ */

void projectile_register(EntityManager *em) {
    entity_register(em, ENT_PROJECTILE, projectile_update,
                    projectile_render, projectile_destroy_fn);
    s_proj_em = em;
    init_weapon_defs();
}

Entity *projectile_spawn_def(EntityManager *em, const ProjectileDef *def,
                             Vec2 pos, Vec2 dir, bool from_player) {
    Entity *e = entity_spawn(em, ENT_PROJECTILE, pos);
    if (!e) return NULL;

    e->body.size = vec2(def->hitbox_w, def->hitbox_h);
    e->body.gravity_scale = 0.0f;  /* gravity handled manually via def */

    /* Normalize direction and scale to projectile speed */
    Vec2 norm_dir = vec2_norm(dir);
    e->body.vel = vec2_scale(norm_dir, def->speed);

    e->health = 1;
    e->damage = def->damage;

    ProjectileData *pd = calloc(1, sizeof(ProjectileData));
    pd->def = def;
    pd->lifetime = def->lifetime;
    pd->pierces_left = def->pierce_count;
    pd->bounces_left = def->bounce_count;

    if (from_player) {
        pd->proj_flags |= PROJ_FROM_PLAYER;
    }

    if (def->anim_fly) {
        animation_set(&e->anim, def->anim_fly);
    }

    e->data = pd;
    return e;
}

Entity *projectile_spawn_dir(EntityManager *em, Vec2 pos, Vec2 dir, bool from_player) {
    const ProjectileDef *def = from_player ? &s_weapon_plasma : &s_weapon_enemy_fire;
    return projectile_spawn_def(em, def, pos, dir, from_player);
}

Entity *projectile_spawn(EntityManager *em, Vec2 pos, bool facing_left, bool from_player) {
    Vec2 dir = facing_left ? vec2(-1, 0) : vec2(1, 0);
    return projectile_spawn_dir(em, pos, dir, from_player);
}

void projectile_hit(Entity *proj) {
    if (!proj || !proj->data) return;
    ProjectileData *pd = (ProjectileData *)proj->data;

    /* If piercing and has pierces left, don't destroy */
    if ((pd->def->flags & PROJ_PIERCING) && pd->pierces_left > 0) {
        pd->pierces_left--;
        return;
    }

    /* Emit impact sparks */
    Vec2 hit_center = vec2(
        proj->body.pos.x + proj->body.size.x * 0.5f,
        proj->body.pos.y + proj->body.size.y * 0.5f
    );
    SDL_Color spark_color = (pd->proj_flags & PROJ_FROM_PLAYER) ?
        (SDL_Color){100, 220, 255, 255} :   /* cyan for player bullets  */
        (SDL_Color){255, 180, 50, 255};      /* orange for enemy bullets */
    particle_emit_spark(hit_center, spark_color);

    /* Switch to impact animation */
    pd->proj_flags |= PROJ_IMPACT;
    proj->body.vel = vec2(0, 0);

    if (pd->def->anim_impact) {
        animation_set(&proj->anim, pd->def->anim_impact);
        proj->anim.current_frame = 0;
        proj->anim.timer = 0;
        proj->anim.finished = false;
    } else {
        /* No impact animation, destroy immediately */
        entity_destroy(s_proj_em, proj);
    }
}

bool projectile_is_impacting(const Entity *proj) {
    if (!proj || !proj->data) return true;
    const ProjectileData *pd = (const ProjectileData *)proj->data;
    return (pd->proj_flags & PROJ_IMPACT) != 0;
}

bool projectile_is_from_player(const Entity *proj) {
    if (!proj || !proj->data) return false;
    const ProjectileData *pd = (const ProjectileData *)proj->data;
    return (pd->proj_flags & PROJ_FROM_PLAYER) != 0;
}
