#include "game/spacecraft.h"
#include "engine/assets.h"
#include "engine/renderer.h"
#include "engine/particle.h"
#include "engine/audio.h"
#include "config.h"
#include <stdlib.h>
#include <math.h>

/* ═══════════════════════════════════════════════════
 *  Constants
 * ═══════════════════════════════════════════════════ */

#define SC_FLY_SPEED       180.0f  /* px/s during fly-in / fly-out     */
#define SC_LAND_DURATION   1.6f    /* seconds for the landing sequence */
#define SC_TAKEOFF_DURATION 1.2f   /* seconds for the takeoff sequence */

/* Spritesheet frame indices (each 80x48, laid out horizontally) */
#define SC_FRAME_LANDED    0
#define SC_FRAME_LAND1     1
#define SC_FRAME_LAND2     2
#define SC_FRAME_LAND3     3
#define SC_FRAME_LAND4     4
#define SC_FRAME_COUNT     5

/* How far off-screen the ship starts / exits */
#define SC_OFFSCREEN_DIST  200.0f

/* ═══════════════════════════════════════════════════
 *  Static state
 * ═══════════════════════════════════════════════════ */

static EntityManager *s_em = NULL;
static SDL_Texture   *s_texture = NULL;
static bool           s_texture_loaded = false;

/* ── Sound effects ──────────────────────────────── */
static Sound s_sfx_engine;
static Sound s_sfx_synth;
static bool  s_sfx_loaded = false;

static void load_sfx(void) {
    if (s_sfx_loaded) return;
    s_sfx_engine = audio_load_sound("assets/sounds/engine.wav");
    s_sfx_synth  = audio_load_sound("assets/sounds/synth.wav");
    s_sfx_loaded = true;
}

/* ═══════════════════════════════════════════════════
 *  Helpers
 * ═══════════════════════════════════════════════════ */

static void load_texture(void) {
    if (s_texture_loaded) return;
    s_texture = assets_get_texture("assets/sprites/spacecraft.png");
    s_texture_loaded = true;
}

/* Get the source rect for a given frame index (in PNG pixel coords) */
static SDL_Rect frame_rect(int frame_idx) {
    return (SDL_Rect){
        frame_idx * SPACECRAFT_SRC_W, 0,
        SPACECRAFT_SRC_W, SPACECRAFT_SRC_H
    };
}

/* Pick the landing animation frame based on progress (0.0 = start, 1.0 = landed) */
static int landing_frame(float progress) {
    if (progress < 0.25f) return SC_FRAME_LAND1;
    if (progress < 0.50f) return SC_FRAME_LAND2;
    if (progress < 0.75f) return SC_FRAME_LAND3;
    if (progress < 0.95f) return SC_FRAME_LAND4;
    return SC_FRAME_LANDED;
}

/* Pick the takeoff animation frame (reverse of landing) */
static int takeoff_frame(float progress) {
    return landing_frame(1.0f - progress);
}

/* Emit thruster particles behind the ship */
static void emit_thruster_particles(Vec2 ship_pos, float intensity) {
    if (intensity <= 0.0f) return;

    /* Particles emit from the left (rear) center of the ship */
    Vec2 origin = vec2(
        ship_pos.x + 16.0f,
        ship_pos.y + SPACECRAFT_HEIGHT * 0.5f - 8.0f
    );

    int count = (int)(intensity * 3.0f);
    if (count < 1) count = 1;

    ParticleBurst b = {
        .origin     = origin,
        .count      = count,
        .speed_min  = 30.0f * intensity,
        .speed_max  = 80.0f * intensity,
        .life_min   = 0.1f,
        .life_max   = 0.3f,
        .size_min   = 1.0f,
        .size_max   = 2.5f,
        .spread     = 0.6f,
        .direction  = 3.14159f,  /* leftward */
        .drag       = 2.0f,
        .gravity_scale = 0.0f,
        .color      = (SDL_Color){255, 180, 50, 200},
        .color_vary = true,
    };
    particle_emit(&b);
}

/* ═══════════════════════════════════════════════════
 *  Entity callbacks
 * ═══════════════════════════════════════════════════ */

/* Start the engine loop sound, stopping any previous one */
static void engine_start(SpacecraftData *sd) {
    if (sd->engine_channel >= 0) return; /* already playing */
    sd->engine_channel = audio_play_sound_loop(s_sfx_engine, 70);
}

/* Stop the engine loop sound */
static void engine_stop(SpacecraftData *sd) {
    if (sd->engine_channel < 0) return;
    audio_stop_channel(sd->engine_channel);
    sd->engine_channel = -1;
}

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

    sd->state_timer += dt;

    switch (sd->state) {

    case SC_FLYING_IN: {
        /* Move from start_pos toward target_pos */
        Vec2 dir = vec2_sub(sd->target_pos, sd->start_pos);
        float total_dist = vec2_len(dir);
        if (total_dist < 1.0f) {
            /* Already at target */
            engine_stop(sd);
            sd->state = SC_LANDING;
            sd->state_timer = 0.0f;
            self->body.pos = sd->target_pos;
            break;
        }
        Vec2 norm = vec2_scale(dir, 1.0f / total_dist);

        float traveled = sd->state_timer * sd->fly_speed;
        if (traveled >= total_dist) {
            /* Arrived at landing zone */
            engine_stop(sd);
            self->body.pos = sd->target_pos;
            sd->state = SC_LANDING;
            sd->state_timer = 0.0f;
        } else {
            self->body.pos = vec2_add(sd->start_pos,
                                      vec2_scale(norm, traveled));
            emit_thruster_particles(self->body.pos, 1.0f);
        }
        break;
    }

    case SC_LANDING: {
        float progress = sd->state_timer / SC_LAND_DURATION;
        if (progress >= 1.0f) {
            progress = 1.0f;
            self->body.pos = sd->target_pos;
            sd->state = SC_LANDED;
            sd->state_timer = 0.0f;
            /* Play synth on touchdown */
            audio_play_sound(s_sfx_synth, 80);
        } else {
            /* Ease down from slightly above target to target */
            float height_offset = (1.0f - progress) * 30.0f;
            self->body.pos = vec2(
                sd->target_pos.x,
                sd->target_pos.y - height_offset
            );
            emit_thruster_particles(self->body.pos, 1.0f - progress);
        }
        break;
    }

    case SC_LANDED: {
        self->body.pos = sd->target_pos;
        /* Stays landed until spacecraft_takeoff() is called externally
         * (e.g. by level.c after spawning the player). */
        break;
    }

    case SC_TAKEOFF: {
        float progress = sd->state_timer / SC_TAKEOFF_DURATION;
        if (progress >= 1.0f) {
            sd->state = SC_FLYING_OUT;
            sd->state_timer = 0.0f;
            engine_start(sd);
        } else {
            float height_offset = progress * 30.0f;
            self->body.pos = vec2(
                sd->target_pos.x,
                sd->target_pos.y - height_offset
            );
            emit_thruster_particles(self->body.pos, progress);
        }
        break;
    }

    case SC_FLYING_OUT: {
        /* Fly upward and to the right, off-screen */
        float speed = sd->fly_speed * 1.5f;
        self->body.pos.x += speed * 0.7f * dt;
        self->body.pos.y -= speed * dt;

        emit_thruster_particles(self->body.pos, 1.0f);

        /* Check if far enough off-screen */
        if (self->body.pos.y < -SC_OFFSCREEN_DIST ||
            self->body.pos.x > sd->target_pos.x + SC_OFFSCREEN_DIST * 2.0f) {
            engine_stop(sd);
            sd->state = SC_DONE;
            sd->state_timer = 0.0f;
        }
        break;
    }

    case SC_DONE:
        /* Remove the entity */
        entity_destroy(s_em, self);
        return;
    }
}

static void spacecraft_render(Entity *self, const Camera *cam) {
    (void)cam;
    SpacecraftData *sd = (SpacecraftData *)self->data;
    if (!sd || !s_texture) return;

    /* Determine which frame to show */
    int frame = SC_FRAME_LANDED;
    switch (sd->state) {
    case SC_FLYING_IN:
        frame = SC_FRAME_LAND1;  /* thrusters on while flying */
        break;
    case SC_LANDING: {
        float progress = sd->state_timer / SC_LAND_DURATION;
        if (progress > 1.0f) progress = 1.0f;
        frame = landing_frame(progress);
        break;
    }
    case SC_LANDED:
        frame = SC_FRAME_LANDED;
        break;
    case SC_TAKEOFF: {
        float progress = sd->state_timer / SC_TAKEOFF_DURATION;
        if (progress > 1.0f) progress = 1.0f;
        frame = takeoff_frame(progress);
        break;
    }
    case SC_FLYING_OUT:
        frame = SC_FRAME_LAND1;
        break;
    case SC_DONE:
        return;
    }

    SDL_Rect src = frame_rect(frame);

    Sprite spr = {
        .texture  = s_texture,
        .src      = src,
        .pos      = self->body.pos,
        .size     = vec2(SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT),
        .flip_x   = false,
        .flip_y   = false,
        .layer    = LAYER_ENTITIES,
        .alpha    = 255,
        .rotation = 0.0,
    };
    renderer_submit(&spr);
}

static void spacecraft_destroy(Entity *self) {
    if (self->data) {
        SpacecraftData *sd = (SpacecraftData *)self->data;
        engine_stop(sd);
        free(self->data);
        self->data = NULL;
    }
}

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

void spacecraft_register(EntityManager *em) {
    entity_register(em, ENT_SPACECRAFT,
                    spacecraft_update, spacecraft_render, spacecraft_destroy);
    s_em = em;
    load_texture();
    load_sfx();
}

Entity *spacecraft_spawn(EntityManager *em, Vec2 land_pos) {
    load_texture();

    /* land_pos is the top-left of where the ship should rest on the
     * ground (as provided by the ENTITY directive in .lvl files). */
    Vec2 target = land_pos;

    /* Start off-screen: above and to the right */
    Vec2 start = vec2(
        target.x + SC_OFFSCREEN_DIST,
        target.y - SC_OFFSCREEN_DIST
    );

    Entity *e = entity_spawn(em, ENT_SPACECRAFT, start);
    if (!e) return NULL;

    e->body.size = vec2(SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT);
    e->body.gravity_scale = 0.0f;  /* manual movement, no physics */
    e->health = 999;
    e->max_health = 999;
    e->damage = 0;
    e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;

    SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
    if (!sd) {
        entity_destroy(em, e);
        return NULL;
    }
    sd->state = SC_FLYING_IN;
    sd->state_timer = 0.0f;
    sd->target_pos = target;
    sd->start_pos = start;
    sd->fly_speed = SC_FLY_SPEED;
    sd->is_exit_ship = false;
    sd->engine_channel = -1;
    e->data = sd;

    /* Start engine sound for fly-in */
    engine_start(sd);

    printf("Spacecraft spawned (fly-in to %.0f, %.0f)\n",
           target.x, target.y);
    return e;
}

Entity *spacecraft_spawn_exit(EntityManager *em, Vec2 land_pos) {
    load_texture();

    Vec2 target = land_pos;

    /* Start off-screen: above and to the right (same approach as intro) */
    Vec2 start = vec2(
        target.x + SC_OFFSCREEN_DIST,
        target.y - SC_OFFSCREEN_DIST
    );

    Entity *e = entity_spawn(em, ENT_SPACECRAFT, start);
    if (!e) return NULL;

    e->body.size = vec2(SPACECRAFT_WIDTH, SPACECRAFT_HEIGHT);
    e->body.gravity_scale = 0.0f;
    e->health = 999;
    e->max_health = 999;
    e->damage = 0;
    e->flags |= ENTITY_INVINCIBLE | ENTITY_ALWAYS_UPDATE;

    SpacecraftData *sd = calloc(1, sizeof(SpacecraftData));
    if (!sd) {
        entity_destroy(em, e);
        return NULL;
    }
    sd->state = SC_FLYING_IN;
    sd->state_timer = 0.0f;
    sd->target_pos = target;
    sd->start_pos = start;
    sd->fly_speed = SC_FLY_SPEED;
    sd->is_exit_ship = true;
    sd->engine_channel = -1;
    e->data = sd;

    engine_start(sd);

    printf("Exit spacecraft spawned (fly-in to %.0f, %.0f)\n",
           target.x, target.y);
    return e;
}

void spacecraft_takeoff(Entity *ship) {
    if (!ship || !ship->data) return;
    SpacecraftData *sd = (SpacecraftData *)ship->data;
    if (sd->state == SC_LANDED) {
        sd->state = SC_TAKEOFF;
        sd->state_timer = 0.0f;
    }
}

bool spacecraft_is_done(const Entity *ship) {
    if (!ship || !ship->data) return true;
    const SpacecraftData *sd = (const SpacecraftData *)ship->data;
    return sd->state == SC_DONE;
}

bool spacecraft_is_landed(const Entity *ship) {
    if (!ship || !ship->data) return false;
    const SpacecraftData *sd = (const SpacecraftData *)ship->data;
    return sd->state == SC_LANDED;
}

bool spacecraft_is_exit_ship(const Entity *ship) {
    if (!ship || !ship->data) return false;
    const SpacecraftData *sd = (const SpacecraftData *)ship->data;
    return sd->is_exit_ship;
}
