import { describe, expect, it } from 'vitest';
import { createRng, hashString, pick, randomInt, shuffle } from '../src/index';

describe('hashString (cyrb128)', () => {
  it('is stable across runs and platforms (pinned values)', () => {
    expect(hashString('')).toBe(41608494);
    expect(hashString('pintrush')).toBe(4017678837);
    expect(hashString('session-1')).toBe(3838391896);
  });
  it('returns unsigned 32-bit integers', () => {
    for (const s of ['a', 'b', 'longer string with ünïcode 李', '0']) {
      const h = hashString(s);
      expect(Number.isInteger(h)).toBe(true);
      expect(h).toBeGreaterThanOrEqual(0);
      expect(h).toBeLessThan(2 ** 32);
    }
  });
});

describe('createRng (sfc32)', () => {
  it('produces the pinned sequence for a seed', () => {
    const r = createRng('pintrush');
    expect([r(), r(), r()]).toEqual([0.4810765292495489, 0.6907922269310802, 0.6293179872445762]);
  });
  it('same seed → same sequence; different seeds → different sequences; numeric seeds work', () => {
    const a = createRng('x');
    const b = createRng('x');
    const c = createRng('y');
    const sa = Array.from({ length: 20 }, () => a());
    expect(Array.from({ length: 20 }, () => b())).toEqual(sa);
    expect(Array.from({ length: 20 }, () => c())).not.toEqual(sa);
    expect(createRng(42)()).toBe(createRng('42')());
  });
  it('stays in [0, 1) and is roughly uniform', () => {
    const r = createRng('uniform');
    const buckets = new Array<number>(10).fill(0);
    for (let i = 0; i < 20_000; i++) {
      const x = r();
      expect(x).toBeGreaterThanOrEqual(0);
      expect(x).toBeLessThan(1);
      buckets[Math.floor(x * 10)]!++;
    }
    for (const n of buckets) expect(Math.abs(n - 2_000)).toBeLessThan(200);
  });
});

describe('shuffle / pick / randomInt', () => {
  it('shuffle returns a permuted copy and leaves the input alone', () => {
    const input = Object.freeze([1, 2, 3, 4, 5, 6, 7, 8]);
    const out = shuffle(input, createRng('s'));
    expect(out).not.toBe(input);
    expect([...out].sort()).toEqual([...input]);
    expect(shuffle(input, createRng('s'))).toEqual(out);
    expect(shuffle([], createRng('s'))).toEqual([]);
  });
  it('shuffle visits every permutation of 3 items', () => {
    const seen = new Set<string>();
    const r = createRng('perms');
    for (let i = 0; i < 300; i++) seen.add(shuffle(['a', 'b', 'c'], r).join(''));
    expect(seen.size).toBe(6);
  });
  it('pick chooses an element and throws on an empty array', () => {
    expect(['a', 'b']).toContain(pick(['a', 'b'], createRng('p')));
    expect(() => pick([], createRng('p'))).toThrow(/empty/);
  });
  it('randomInt stays in range', () => {
    const r = createRng('ri');
    for (let i = 0; i < 1_000; i++) {
      const n = randomInt(r, 7);
      expect(n).toBeGreaterThanOrEqual(0);
      expect(n).toBeLessThan(7);
    }
  });
});
