import { describe, expect, it } from 'vitest';
import { walkClip } from '../locomotion';
import { samplePose, validateClip, footMinY } from '../clip';
import { makeRig } from '../rig';
import type { VoxelModel } from '../../models/format';

const PARAMS = { duration: 0.8, legSwing: 0.5, armSwing: 0.3, bob: 0.2, sway: 0.1 };

describe('walkClip (процедурная локомоция)', () => {
    it('детерминирован', () => {
        expect(JSON.stringify(walkClip(PARAMS))).toBe(JSON.stringify(walkClip(PARAMS)));
    });

    it('проходит числовую валидацию (цикл замкнут, ключи монотонны)', () => {
        expect(validateClip(walkClip(PARAMS))).toEqual([]);
    });

    it('ноги в противофазе, рука против одноимённой ноги', () => {
        const clip = walkClip(PARAMS);
        const q = samplePose(clip, PARAMS.duration * 0.25); // левая нога в махе
        expect(q.legL?.rot?.[0]).toBeCloseTo(PARAMS.legSwing);
        expect(q.legR?.rot?.[0]).toBeCloseTo(-PARAMS.legSwing);
        expect(q.armL?.rot?.[0]).toBeCloseTo(-PARAMS.armSwing);
        expect(q.armR?.rot?.[0]).toBeCloseTo(PARAMS.armSwing);
    });

    it('корень: в контакте y=0, в полном шаге приседает, качается вбок', () => {
        const clip = walkClip(PARAMS);
        const contact = samplePose(clip, 0);
        expect(contact.root?.pos?.[1]).toBeCloseTo(0);
        const stride = samplePose(clip, PARAMS.duration * 0.25);
        expect(stride.root?.pos?.[1]).toBeCloseTo(-PARAMS.bob);
        expect(stride.root?.pos?.[0]).toBeCloseTo(PARAMS.sway);
        const strideR = samplePose(clip, PARAMS.duration * 0.75);
        expect(strideR.root?.pos?.[0]).toBeCloseTo(-PARAMS.sway);
    });

    it('контакт стопы: в опоре стопа на земле, в махе поднята', () => {
        // манекен-заглушка: нога 5 вокселей вниз от pivot на y=5
        const model: VoxelModel = {
            size: [1, 6, 1],
            palette: { 6: '#5b5b66' },
            data: btoa(String.fromCharCode(6, 6, 6, 6, 6, 6)),
        };
        const rig = makeRig(
            [
                { name: 'root', parent: -1, pivot: [0.5, 0, 0.5] },
                { name: 'legL', parent: 0, pivot: [0.5, 5, 0.5] },
            ],
            model,
            [1, 1, 1, 1, 1, 0],
        );
        const clip = walkClip({ ...PARAMS, legSwing: 0.5 });
        const ground = footMinY(rig, model, clip, 'legL', 0);
        const swing = footMinY(rig, model, clip, 'legL', PARAMS.duration * 0.25);
        expect(ground).toBeCloseTo(0.5, 2); // стопа на земле (центр нижнего вокселя 0.5)
        // мах поднимает стопу (с поправкой на приседание корня −bob в этой же фазе)
        expect(swing).toBeGreaterThan(ground + PARAMS.legSwing * 0.5);
    });
});