export class Player {
    // p = the p5 instance, x and y are start position
    constructor(p, x, y) {
        this.p = p;
        this.spawnX = x;
        this.spawnY = y;

        // p5play sprite - this is the physics body
        this.sprite = new p.Sprite(x, y, 28, 28);

        // p5play uses .img not .image for direct path strings
        this.sprite.img = '/assets/player.png';

        // we dont want player to tip over
        this.sprite.rotationLock = true;

        // we dont want the player to be bouncy
        this.sprite.bounciness = 0;

        // game state
        this.collectedColors = []; // hex color strings
        this.isInvincible = false;
        this.invincibleTimer = 0;

        this._canJump = true;
        this._peakReached = false;
    }

    // called every frame from the game loop
    // keysDown is an object: {left, right, jump}
    update(keysDown) {
        const SPEED = 5;

        if (keysDown.left)  this.sprite.vel.x = -SPEED;
        if (keysDown.right) this.sprite.vel.x =  SPEED;

        // if no key is pressed, slow down (friction)
        if (!keysDown.left && !keysDown.right) {
            this.sprite.vel.x *= 0.78;
        }

        const vel_y = this.sprite.vel.y;

        // Once clearly falling, mark that we've left the ground
        if (vel_y > 2) this._peakReached = true;

        // Landing: restore jump ability only after having fallen back down
        if (this._peakReached && vel_y > -0.5 && vel_y < 1.5) {
            this._canJump = true;
            this._peakReached = false;
        }

        if (keysDown.jump && this._canJump) {
            this.sprite.vel.y = -11;
            this._canJump = false;
            keysDown.jump = false;
        }

        // clamp to canvas left/right edges
        const halfW = this.sprite.w / 2;
        if (this.sprite.x < halfW) {
            this.sprite.x = halfW;
            this.sprite.vel.x = 0;
        } else if (this.sprite.x > this.p.width - halfW) {
            this.sprite.x = this.p.width - halfW;
            this.sprite.vel.x = 0;
        }

        // count down invincibility frames
        if (this.isInvincible) {
            this.invincibleTimer--;
            if (this.invincibleTimer <= 0) {
                this.isInvincible = false;
            }
        }
    }

    // call when player hits an enemy or tar puddle
    // returns true if no lives left (game over)
    loseLife(livesStore) {
        if (this.isInvincible) return false;

        // update the store directly instead of a local variable
        livesStore.update(n => n - 1);

        // grace period so player cant take damage immediately again
        this.isInvincible = true;
        this.invincibleTimer = 90; // 1.5 seconds at 60fps

        this.respawn();

        // read the current store value to check if game over
        let current;
        livesStore.subscribe(v => current = v)();
        return current <= 0; // true = game over
    }

    // send player back to level start
    respawn() {
        this.sprite.x = this.spawnX;
        this.sprite.y = this.spawnY;
        this.sprite.vel.x = 0;
        this.sprite.vel.y = 0;
    }

    // called when player walks into a fragment
    collectFragment(hexColor) {
        this.collectedColors.push(hexColor);
        // color gets added to the glow effect
    }

    // draw the glow effect around the player
    // call this BEFORE drawing the sprite so glow appears underneath
    drawGlow() {
        if (this.collectedColors.length === 0) return;

        // use the most recently collected color as the glow color
        const c = this.collectedColors[this.collectedColors.length - 1];

        this.p.push();
        this.p.noStroke();

        // draw concentric circles getting more transparent outward
        // this fakes a soft glow without any extra library
        for (let radius = 60; radius > 0; radius -= 8) {
            const alpha = (radius / 60) * 40; // max alpha is 40
            const col = this.p.color(c);
            col.setAlpha(alpha);
            this.p.fill(col);
            this.p.circle(this.sprite.x, this.sprite.y, radius * 2);
        }
        this.p.pop();
    }

    // blink effect while invincible
    // returns false every 6 frames to hide the sprite
    isVisible() {
        if (!this.isInvincible) return true;
        // show/hide every 6 frames for a blink effect
        return Math.floor(this.invincibleTimer / 6) % 2 === 0;
    }
}