import { describe, expect, it } from 'vitest';
import { VoxelWorld } from '../../world/world';
import { mannequin } from '../../models/mannequin';
import { walkClip } from '../../animation/locomotion';
import { Character } from '../character';

/** Плоский мир с землёй y=0..1 и стеной x=6 (как в тестах body). */
function testWorld(): VoxelWorld {
    const w = new VoxelWorld(16);
    for (let x = 0; x < 10; x++)
        for (let z = 0; z < 8; z++) {
            w.set(x, 0, z, 1);
            w.set(x, 1, z, 1);
        }
    for (let y = 2; y <= 4; y++)
        for (let z = 0; z < 8; z++) w.set(6, y, z, 1);
    return w;
}

function makeChar(spawn: readonly [number, number] = [3, 5]): Character {
    const { model, rig } = mannequin();
    const clip = walkClip({ duration: 0.8, legSwing: 0.55, armSwing: 0.4, bob: 0.25 });
    return new Character(rig, model, clip, spawn, { size: [3, 10, 3], speed: 3, strideLength: 3 });
}

describe('Character (тело + ходьба от скорости)', () => {
    it('стоит на земле, в покое фаза не растёт', () => {
        const w = testWorld();
        const c = makeChar();
        for (let i = 0; i < 60; i++) c.step(w, 1 / 60);
        expect(c.body.pos[1]).toBeCloseTo(2);
        expect(c.body.grounded).toBe(true);
        expect(c.phase).toBe(0);
    });

    it('движение: скорость из setMove, фаза растёт пропорционально пути', () => {
        const w = testWorld();
        const c = makeChar();
        c.setMove([1, 0]); // скорость 3 вокс/с вправо мира
        for (let i = 0; i < 10; i++) c.step(w, 1 / 60); // 0.167 с свободного хода
        expect(c.body.vel[0]).toBeCloseTo(3);
        expect(c.body.pos[0]).toBeCloseTo(3.5, 3);
        expect(c.yaw).toBeCloseTo(Math.PI / 2, 5); // смотрит по +x
        for (let i = 0; i < 590; i++) c.step(w, 1 / 60); // упёрся в стену x=6
        expect(c.body.pos[0]).toBeCloseTo(4.5, 3); // путь 1.5 вокселя до грани
        expect(c.phase).toBeCloseTo(0.5, 3); // 1.5 вокселя / stride 3 = полцикла
    });

    it('у стены: зажат по грани, фаза замирает, yaw помнит последнее движение', () => {
        const w = testWorld();
        const c = makeChar([3, 5]);
        c.setMove([1, 0]);
        for (let i = 0; i < 600; i++) c.step(w, 1 / 60); // упёрся в стену x=6
        const stopped = c.body.pos[0];
        expect(stopped).toBeCloseTo(6 - 1.5 - 1e-4, 3);
        const phaseAtWall = c.phase;
        for (let i = 0; i < 60; i++) c.step(w, 1 / 60); // ввод держится, но движения нет
        expect(c.body.pos[0]).toBeCloseTo(stopped, 6);
        expect(c.phase).toBeCloseTo(phaseAtWall, 9); // фаза не ползёт у стены
        expect(c.yaw).toBeCloseTo(Math.PI / 2, 5);
    });

    it('visual(): воксели вокруг тела, поворот по движению, поза от фазы', () => {
        const w = testWorld();
        const c = makeChar();
        c.setMove([1, 0]);
        for (let i = 0; i < 30; i++) c.step(w, 1 / 60);
        const voxels = c.visual();
        expect(voxels.length).toBe(32); // все воксели манекена
        for (const v of voxels) {
            expect(Math.abs(v.pos[0] - c.body.pos[0])).toBeLessThanOrEqual(2.5); // модель 5 шириной
            expect(v.pos[1]).toBeGreaterThanOrEqual(c.body.pos[1]);
        }
        const idle = makeChar();
        expect(idle.visual().length).toBe(32);
    });
});