/**
 * Stable FNV-1a over UTF-16 code units. The explicit code-unit model makes the
 * same seed reproducible in every JavaScript runtime without locale or encoder
 * dependencies.
 */
export function hashGenerationSeed(seed: string): number {
  let hash = 0x811c9dc5;
  for (let index = 0; index < seed.length; index += 1) {
    const unit = seed.charCodeAt(index);
    hash ^= unit & 0xff;
    hash = Math.imul(hash, 0x01000193);
    hash ^= unit >>> 8;
    hash = Math.imul(hash, 0x01000193);
  }
  return hash >>> 0;
}

export function generationSeedHash(seed: string): string {
  return hashGenerationSeed(seed).toString(16).padStart(8, "0");
}

export interface DeterministicRandom {
  nextUint32(): number;
  integer(maximumExclusive: number): number;
  pick<T>(values: readonly T[]): T;
}

export function createDeterministicRandom(seed: string): DeterministicRandom {
  let state = hashGenerationSeed(seed);
  return {
    nextUint32(): number {
      state = (state + 0x6d2b79f5) >>> 0;
      let value = state;
      value = Math.imul(value ^ (value >>> 15), value | 1);
      value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
      return (value ^ (value >>> 14)) >>> 0;
    },
    integer(maximumExclusive: number): number {
      if (!Number.isSafeInteger(maximumExclusive) || maximumExclusive < 1) {
        throw new RangeError("Random selection needs a positive bound.");
      }
      return this.nextUint32() % maximumExclusive;
    },
    pick<T>(values: readonly T[]): T {
      if (values.length === 0) {
        throw new RangeError("Cannot select from an empty collection.");
      }
      return values[this.integer(values.length)] as T;
    },
  };
}
