#include "engine/camera.h"
#include <stdlib.h>
#include <math.h>

void camera_init(Camera *c, float vp_w, float vp_h) {
    c->pos = vec2_zero();
    c->viewport = vec2(vp_w, vp_h);
    c->bounds_min = vec2_zero();
    c->bounds_max = vec2(vp_w, vp_h); /* default: one screen */
    c->smoothing = 5.0f;
    c->deadzone = vec2(30.0f, 20.0f);
    c->look_ahead = vec2(40.0f, 30.0f);
    c->zoom = 1.0f;
}

void camera_set_bounds(Camera *c, float world_w, float world_h) {
    c->bounds_min = vec2_zero();
    c->bounds_max = vec2(world_w, world_h);
}

void camera_follow(Camera *c, Vec2 target, Vec2 velocity, float dt) {
    /* Target is the center point we want to follow */
    Vec2 desired;
    desired.x = target.x - c->viewport.x * 0.5f;
    desired.y = target.y - c->viewport.y * 0.5f;

    /* Look-ahead: shift camera in the direction of movement */
    if (velocity.x > 10.0f)       desired.x += c->look_ahead.x;
    else if (velocity.x < -10.0f) desired.x -= c->look_ahead.x;

    /* Vertical look-ahead: lead camera when falling or rising */
    if (velocity.y > 50.0f)       desired.y += c->look_ahead.y;
    else if (velocity.y < -50.0f) desired.y -= c->look_ahead.y;

    /* Smooth follow using exponential decay */
    float t = 1.0f - expf(-c->smoothing * dt);
    c->pos = vec2_lerp(c->pos, desired, t);

    /* Clamp to world bounds */
    c->pos.x = clampf(c->pos.x, c->bounds_min.x,
                       c->bounds_max.x - c->viewport.x);
    c->pos.y = clampf(c->pos.y, c->bounds_min.y,
                       c->bounds_max.y - c->viewport.y);
}

Vec2 camera_world_to_screen(const Camera *c, Vec2 world_pos) {
    Vec2 screen = vec2_sub(world_pos, c->pos);
    /* Apply zoom */
    if (c->zoom != 1.0f && c->zoom > 0.0f) {
        screen = vec2_scale(screen, c->zoom);
    }
    /* Apply shake offset */
    screen.x += c->shake_offset.x;
    screen.y += c->shake_offset.y;
    return screen;
}

Vec2 camera_screen_to_world(const Camera *c, Vec2 screen_pos) {
    Vec2 world = screen_pos;
    /* Remove shake */
    world.x -= c->shake_offset.x;
    world.y -= c->shake_offset.y;
    /* Reverse zoom */
    if (c->zoom != 1.0f && c->zoom > 0.0f) {
        world = vec2_scale(world, 1.0f / c->zoom);
    }
    return vec2_add(world, c->pos);
}

/* ── Screen shake ────────────────────────────────── */

static float randf_sym(void) {
    /* Random float in [-1, 1] */
    return ((float)rand() / (float)RAND_MAX) * 2.0f - 1.0f;
}

void camera_shake(Camera *c, float intensity, float duration) {
    /* Take the stronger shake if one is already active */
    if (intensity > c->shake_intensity || c->shake_timer <= 0) {
        c->shake_intensity = intensity;
        c->shake_timer = duration;
    }
}

void camera_update_shake(Camera *c, float dt) {
    if (c->shake_timer <= 0) {
        c->shake_offset = vec2_zero();
        return;
    }

    c->shake_timer -= dt;

    /* Decay intensity as shake expires */
    float t = c->shake_timer > 0 ? c->shake_timer / 0.3f : 0;  /* ~0.3s reference */
    if (t > 1.0f) t = 1.0f;
    float current = c->shake_intensity * t;

    /* Random offset, rounded to whole pixels for crisp pixel art */
    c->shake_offset.x = roundf(randf_sym() * current);
    c->shake_offset.y = roundf(randf_sym() * current);

    if (c->shake_timer <= 0) {
        c->shake_timer = 0;
        c->shake_intensity = 0;
        c->shake_offset = vec2_zero();
    }
}
