import { describe, it, expect } from 'vitest';
import { createRng, hashTile } from '../rng';

describe('createRng (mulberry32)', () => {
    it('одинаковый seed — одинаковая последовательность', () => {
        const a = createRng(12345);
        const b = createRng(12345);
        for (let i = 0; i < 100; i++) {
            expect(a.next()).toBe(b.next());
        }
    });

    it('разные seed — разные последовательности', () => {
        const a = createRng(1);
        const b = createRng(2);
        const seqA = Array.from({ length: 10 }, () => a.next());
        const seqB = Array.from({ length: 10 }, () => b.next());
        expect(seqA).not.toEqual(seqB);
    });

    it('next в диапазоне 0..1', () => {
        const rng = createRng(42);
        for (let i = 0; i < 1000; i++) {
            const v = rng.next();
            expect(v).toBeGreaterThanOrEqual(0);
            expect(v).toBeLessThan(1);
        }
    });

    it('int внутри [min, max] включительно', () => {
        const rng = createRng(7);
        for (let i = 0; i < 500; i++) {
            const v = rng.int(2, 5);
            expect(v).toBeGreaterThanOrEqual(2);
            expect(v).toBeLessThanOrEqual(5);
            expect(Number.isInteger(v)).toBe(true);
        }
    });

    it('int достигает обоих концов диапазона', () => {
        const rng = createRng(9);
        const seen = new Set<number>();
        for (let i = 0; i < 500; i++) seen.add(rng.int(1, 3));
        expect(seen.has(1)).toBe(true);
        expect(seen.has(3)).toBe(true);
    });

    it('pick берёт элемент из массива', () => {
        const rng = createRng(3);
        const arr = ['a', 'b', 'c'] as const;
        for (let i = 0; i < 50; i++) {
            expect(arr).toContain(rng.pick(arr));
        }
    });

    it('pick из пустого массива бросает', () => {
        const rng = createRng(3);
        expect(() => rng.pick([])).toThrow();
    });

    it('chance(0) всегда false, chance(1) всегда true', () => {
        const rng = createRng(11);
        for (let i = 0; i < 100; i++) {
            expect(rng.chance(0)).toBe(false);
            expect(rng.chance(1)).toBe(true);
        }
    });

    it('state() отражает позицию в последовательности (для сейва в связке с seed)', () => {
        const a = createRng(777);
        a.next();
        const s1 = a.state();
        a.next();
        const s2 = a.state();
        expect(s2).not.toBe(s1);

        // тот же seed и те же два вызова дают то же состояние
        const b = createRng(777);
        b.next();
        b.next();
        expect(b.state()).toBe(s2);
    });
});

describe('hashTile', () => {
    it('детерминирован и не зависит от порядка вызовов', () => {
        expect(hashTile(3, 5, 7)).toBe(hashTile(3, 5, 7));
        // отсутствие состояния: значения не «плывут» между вызовами
        const first = hashTile(0, 0, 1);
        hashTile(10, 20, 2);
        hashTile(30, 40, 3);
        expect(hashTile(0, 0, 1)).toBe(first);
    });

    it('разные координаты/seed — разные хэши (в массе)', () => {
        const seen = new Set<number>();
        for (let x = 0; x < 20; x++) {
            for (let y = 0; y < 20; y++) seen.add(hashTile(x, y, 42));
        }
        // 400 тайлов — коллизий почти нет (32-битный хэш)
        expect(seen.size).toBeGreaterThan(390);
    });

    it('возвращает неотрицательное целое', () => {
        for (let i = -5; i <= 5; i++) {
            const h = hashTile(i, i * 3, 9);
            expect(Number.isInteger(h)).toBe(true);
            expect(h).toBeGreaterThanOrEqual(0);
            expect(h).toBeLessThan(2 ** 32);
        }
    });
});