import { PlayerMotionModel, IPlatform, DEFAULT_GRAVITY } from '@logic/PlayerMotionModel';
import { MOVE_SPEED, PlayerColorState } from '@common/Constants';

function makeGroundPlatform(): IPlatform {
    return { topY: 0, leftX: -1000, rightX: 1000 };
}

function makeModel(color: PlayerColorState = PlayerColorState.Red) {
    return new PlayerMotionModel({
        aabb: { x: 0, y: 16, w: 16, h: 32 }, // character 16x32 resting on y=0 ground
        platforms: [makeGroundPlatform()],
        initialColorState: color,
    });
}

describe('PlayerMotionModel — horizontal movement (req 2.1, 5.1-5.2)', () => {
    it('stands still on initialisation', () => {
        const m = makeModel();
        m.update(0.016);
        expect(m.vx).toBe(0);
        expect(m.isGrounded).toBe(true);
    });

    it('moves at 100 px/s in red state', () => {
        const m = makeModel(PlayerColorState.Red);
        m.update(0.016); // settle
        m.setHorizontalInput(1);
        m.update(1);
        expect(m.vx).toBe(MOVE_SPEED[PlayerColorState.Red]);
        expect(m.aabb.x).toBeCloseTo(100, 1);
    });

    it('moves at 150 px/s in yellow state', () => {
        const m = makeModel(PlayerColorState.Yellow);
        m.update(0.016);
        m.setHorizontalInput(-1);
        m.update(1);
        expect(m.vx).toBe(-MOVE_SPEED[PlayerColorState.Yellow]);
    });

    it('reflects speed immediately when setColorState is called mid-run', () => {
        const m = makeModel(PlayerColorState.Red);
        m.update(0.016);
        m.setHorizontalInput(1);
        m.setColorState(PlayerColorState.Yellow);
        m.update(1);
        expect(m.vx).toBe(150);
    });
});

describe('PlayerMotionModel — jump / gravity (req 2.4, 13.4)', () => {
    it('applyJumpImpulse is rejected when in the air (req 2.4)', () => {
        const m = makeModel();
        m.update(0.016);
        expect(m.applyJumpImpulse(600)).toBe(true); // first jump succeeds
        expect(m.isGrounded).toBe(false);
        expect(m.applyJumpImpulse(600)).toBe(false); // second jump in air denied
    });

    it('gravity pulls the player back to the ground', () => {
        const m = makeModel();
        m.update(0.016);
        m.applyJumpImpulse(600);
        // Simulate ~1 second of flight — gravity reclaims the player.
        for (let i = 0; i < 120; i++) m.update(1 / 60);
        expect(m.isGrounded).toBe(true);
        expect(m.vy).toBe(0);
    });

    it('preserves mid-air vx (起跳定型 — req 13.4)', () => {
        const m = makeModel();
        m.update(0.016);
        // Build horizontal speed + jump.
        m.setHorizontalInput(1);
        m.update(0.016);
        m.applyJumpImpulse(500);
        const airVx = m.vx;
        // Even if the player now tries to flip direction while airborne, vx stays put.
        m.setHorizontalInput(-1);
        m.update(0.05);
        expect(m.vx).toBe(airVx);
    });

    it('applyHorizontalImpulse overrides vx (for parabolic jumps, req 2.5)', () => {
        const m = makeModel();
        m.update(0.016);
        m.applyJumpImpulse(600);
        m.applyHorizontalImpulse(120);
        m.update(0.016);
        expect(m.vx).toBe(120);
    });
});

describe('PlayerMotionModel — platform switching', () => {
    it('setPlatforms clears grounded and re-settles on next update', () => {
        const m = makeModel();
        m.update(0.016);
        expect(m.isGrounded).toBe(true);
        m.setPlatforms([{ topY: -500, leftX: -10, rightX: 10 }]);
        expect(m.isGrounded).toBe(false);
    });

    it('gravity constant matches the documented default', () => {
        expect(DEFAULT_GRAVITY).toBe(2500);
    });
});
