/**
 * Парити-тест спек-синтеза: renderSpec(SPEC_SFX[key]) должен совпадать с
 * формулой бывшего файлового генератора (tools/audio/gen.mjs) — миграция
 * на playSpec не изменила звучание. Агент без слуха, поэтому сверяем
 * численно: bell_hit/whoosh бит-в-бит (формулы совпали дословно), остальные
 * — с допуском 1e-5 (перегруппировка умножений даёт расхождение ~1e-15,
 * допуск ловит любую реальную ошибку формулы: частоту, вес, огибающую).
 */
import { describe, expect, it } from 'vitest';
import { renderSpec } from '@rpg/engine';
import { bandNoise, decay, normalize, synth } from '@rpg/engine/tools/synth.mjs';
import {
    ashDie,
    ashHiss,
    bellHit,
    bellLow,
    chestOpen,
    chime,
    deerStartle,
    hurt,
    motePickup,
    note,
    pickup,
    spit,
    uiClick,
    whoosh
} from '../../../tools/audio/gen.mjs';
import { SPEC_SFX } from '../sfxSpecs';

/** Максимум модуля разности двух сэмпл-массивов. */
function maxDiff(a: Float32Array, b: Float32Array): number {
    expect(a.length).toBe(b.length);
    let max = 0;
    for (let i = 0; i < a.length; i++) max = Math.max(max, Math.abs(a[i]! - b[i]!));
    return max;
}

describe('паритет SPEC_SFX с формулами gen.mjs', () => {
    it('sfx/bell_hit — бит-в-бит равен bellHit()', () => {
        const spec = renderSpec(SPEC_SFX['sfx/bell_hit']!);
        const wav = bellHit();
        expect(spec.length).toBe(wav.length);
        for (let i = 0; i < wav.length; i++) expect(spec[i]).toBe(wav[i]);
    });

    it('sfx/whoosh — бит-в-бит равен whoosh()', () => {
        const spec = renderSpec(SPEC_SFX['sfx/whoosh']!);
        const wav = whoosh();
        expect(spec.length).toBe(wav.length);
        for (let i = 0; i < wav.length; i++) expect(spec[i]).toBe(wav[i]);
    });

    const EPS = 1e-5;
    const CASES: [string, () => Float32Array][] = [
        ['sfx/bell_low', bellLow],
        ['sfx/ash_hiss', ashHiss],
        ['sfx/ash_die', ashDie],
        ['sfx/spit', spit],
        ['sfx/hurt', hurt],
        ['sfx/mote_pickup', motePickup],
        ['sfx/pickup', pickup],
        ['sfx/chest_open', chestOpen],
        ['sfx/note', note],
        ['sfx/deer_startle', deerStartle],
        ['sfx/ui_click', uiClick],
        ['sfx/chime', chime]
    ];
    for (const [key, gen] of CASES) {
        it(`${key} — совпадает с формулой gen.mjs (eps ${EPS})`, () => {
            expect(maxDiff(renderSpec(SPEC_SFX[key]!), gen())).toBeLessThan(EPS);
        });
    }

    it('sfx/step_water — совпадает с формулой всплеска (eps)', () => {
        // Формула бывшего sfx/step_water.wav (seed 255 в gen.mjs); у спека
        // сид 255+... — нет, базовый спек шагов взял сиды StepVariants
        // (253-256), поэтому здесь сверяем саму формулу с сидом 255.
        const gen = (): Float32Array => {
            const dur = 0.18;
            const noise = bandNoise(155, 600, 3200, dur);
            const s = synth(dur, (t) => (noise() * 0.6 + Math.sin(2 * Math.PI * 300 * t) * 0.2) * decay(t, dur, 2));
            return normalize(s, 0.3);
        };
        const spec: typeof SPEC_SFX[string] = { ...SPEC_SFX['sfx/step_water']!, seed: 155 };
        expect(maxDiff(renderSpec(spec), gen())).toBeLessThan(EPS);
    });
});