"""Deterministic PRNG using SplitMix64 for reproducible randomness.

Fast, non-cryptographic PRNG suitable for game shuffles and deterministic
random number generation. Based on splitmix64 by Sebastiano Vigna.
"""

from __future__ import annotations

import hashlib

MASK64 = (1 << 64) - 1


class SplitMix64:
    """Fast deterministic PRNG based on splitmix64.

    Provides reproducible random number generation from a seed.
    NOT cryptographically secure - use only for game logic.

    Example:
        >>> prng = SplitMix64(b"game-seed-123")
        >>> prng.next_u64()  # Returns deterministic 64-bit int
        >>> prng.randbelow(52)  # Returns int in [0, 52)
    """

    def __init__(self, seed_bytes: bytes) -> None:
        """Initialize PRNG from seed bytes.

        Args:
            seed_bytes: Arbitrary-length seed (hashed to 64 bits internally)
        """
        h = hashlib.sha256(seed_bytes).digest()
        self.state = int.from_bytes(h[:8], "big")

    def next_u64(self) -> int:
        """Generate next 64-bit random integer.

        Returns:
            Random integer in [0, 2^64)
        """
        self.state = (self.state + 0x9E3779B97F4A7C15) & MASK64
        z = self.state
        z = (z ^ (z >> 30)) * 0xBF58476D1CE4E5B9 & MASK64
        z = (z ^ (z >> 27)) * 0x94D049BB133111EB & MASK64
        z ^= z >> 31
        return z & MASK64

    def randbelow(self, n: int) -> int:
        """Generate random integer in [0, n).

        Args:
            n: Upper bound (exclusive)

        Returns:
            Random integer in [0, n)

        Raises:
            ValueError: If n <= 0
        """
        if n <= 0:
            raise ValueError("n must be > 0")
        x = self.next_u64() >> 1  # 63-bit to avoid sign issues
        return x % n


def fisher_yates_shuffle(items: list[int], prng: SplitMix64) -> list[int]:
    """Perform Fisher-Yates shuffle using provided PRNG.

    Args:
        items: List to shuffle (copied, not modified in-place)
        prng: PRNG instance to use for randomness

    Returns:
        Shuffled copy of items
    """
    result = items.copy()
    for i in range(len(result) - 1, 0, -1):
        j = prng.randbelow(i + 1)
        result[i], result[j] = result[j], result[i]
    return result
