import { describe, expect, it } from 'vitest';
import { renderSpec, type SoundSpec } from '../SoundSpec';

const same = (a: Float32Array, b: Float32Array): boolean =>
    a.length === b.length && a.every((v, i) => v === b[i]);

const peak = (samples: Float32Array): number => Math.max(...[...samples].map(Math.abs));

describe('renderSpec — детерминизм', () => {
    it('одинаковые спеки → одинаковые сэмплы', () => {
        const spec: SoundSpec = { kind: 'hit', dur: 0.2, seed: 42 };
        expect(same(renderSpec(spec), renderSpec(spec))).toBe(true);
    });

    it('другой сид → другой шум (но та же форма затухания)', () => {
        const a = renderSpec({ kind: 'hit', dur: 0.2, seed: 1 });
        const b = renderSpec({ kind: 'hit', dur: 0.2, seed: 2 });
        expect(same(a, b)).toBe(false);
        expect(peak(a)).toBeCloseTo(peak(b), 5); // нормализация по одному peak
    });
});

describe('renderSpec — kinds', () => {
    it('hit/chime/scrape нормализуются к peak, немые буферы исключены', () => {
        for (const kind of ['hit', 'chime', 'scrape'] as const) {
            const samples = renderSpec({ kind, dur: 0.3, peak: 0.6 });
            expect(peak(samples)).toBeCloseTo(0.6, 2);
            expect(peak(samples)).toBeGreaterThan(0);
        }
    });

    it('длительности: без лупа — ровно dur×RATE; луп сшит (короче на фейд)', () => {
        const hit = renderSpec({ kind: 'hit', dur: 0.4 });
        expect(hit.length).toBe(8820); // 0.4 с × 22050

        const hum = renderSpec({ kind: 'hum', dur: 2 }); // луп по умолчанию
        expect(hum.length).toBeLessThan(44100); // срезан кроссфейд-хвост
        expect(hum.length).toBeGreaterThanOrEqual(44100 - 11025); // фейд ≤ 0.5 с
    });

    it('hum: заданный loop=false не сшивает; hit с loop=true сшивает', () => {
        const humRaw = renderSpec({ kind: 'hum', dur: 1, loop: false });
        expect(humRaw.length).toBe(22050);
        const hitLoop = renderSpec({ kind: 'hit', dur: 1, loop: true });
        expect(hitLoop.length).toBeLessThan(22050);
    });

    it('клэмп длительности: 0.01 → мин 0.05 с, hum 100 → макс 8 с', () => {
        expect(renderSpec({ kind: 'hit', dur: 0.01 }).length).toBe(1102); // 0.05 с
        expect(renderSpec({ kind: 'hum', dur: 100, loop: false }).length).toBe(176400); // 8 с
    });

    it('параметры доезжают: freq меняет тембр звона, tone — подложку удара', () => {
        const low = renderSpec({ kind: 'chime', dur: 0.3, freq: 220, loop: false });
        const high = renderSpec({ kind: 'chime', dur: 0.3, freq: 880, loop: false });
        expect(same(low, high)).toBe(false);

        const plain = renderSpec({ kind: 'hit', dur: 0.3, seed: 5, loop: false });
        const toned = renderSpec({ kind: 'hit', dur: 0.3, seed: 5, tone: 120, loop: false });
        expect(same(plain, toned)).toBe(false);
    });
});