import type { RandomState } from "./types";

const DEFAULT_NON_ZERO_SEED = 0x9e3779b9;

function hashString(value: string): number {
  let hash = 0x811c9dc5;
  for (let index = 0; index < value.length; index += 1) {
    hash ^= value.charCodeAt(index);
    hash = Math.imul(hash, 0x01000193);
  }
  return hash >>> 0;
}

export function normalizeSeed(seed: number | string): number {
  const normalized = typeof seed === "string"
    ? hashString(seed)
    : Number.isFinite(seed)
      ? Math.trunc(seed) >>> 0
      : DEFAULT_NON_ZERO_SEED;
  return normalized === 0 ? DEFAULT_NON_ZERO_SEED : normalized;
}

export function createRandomState(seed: number | string): RandomState {
  const normalized = normalizeSeed(seed);
  return { seed: normalized, state: normalized, draws: 0 };
}

export function nextRandom(source: RandomState): { value: number; random: RandomState } {
  const state = (source.state + 0x6d2b79f5) >>> 0;
  let value = state;
  value = Math.imul(value ^ (value >>> 15), value | 1);
  value ^= value + Math.imul(value ^ (value >>> 7), value | 61);
  const result = ((value ^ (value >>> 14)) >>> 0) / 4294967296;
  return {
    value: result,
    random: { seed: source.seed, state, draws: source.draws + 1 },
  };
}

export function randomInt(source: RandomState, maximumExclusive: number): { value: number; random: RandomState } {
  if (!Number.isInteger(maximumExclusive) || maximumExclusive <= 0) {
    throw new Error("maximumExclusive must be a positive integer.");
  }
  const next = nextRandom(source);
  return { value: Math.floor(next.value * maximumExclusive), random: next.random };
}

export function shuffleWithRandom<T>(source: RandomState, values: readonly T[]): { values: T[]; random: RandomState } {
  const shuffled = [...values];
  let random = source;
  for (let index = shuffled.length - 1; index > 0; index -= 1) {
    const sampled = randomInt(random, index + 1);
    random = sampled.random;
    [shuffled[index], shuffled[sampled.value]] = [shuffled[sampled.value], shuffled[index]];
  }
  return { values: shuffled, random };
}

export function weightedIndex(source: RandomState, weights: readonly number[]): { value: number; random: RandomState } {
  if (!weights.length || weights.some((weight) => !Number.isFinite(weight) || weight < 0)) {
    throw new Error("weights must contain non-negative finite values.");
  }
  const total = weights.reduce((sum, weight) => sum + weight, 0);
  if (total <= 0) throw new Error("weights must contain at least one positive value.");
  const next = nextRandom(source);
  let cursor = next.value * total;
  for (let index = 0; index < weights.length; index += 1) {
    cursor -= weights[index];
    if (cursor < 0) return { value: index, random: next.random };
  }
  return { value: weights.length - 1, random: next.random };
}

