<?php

namespace App\Services\Ai;

/**
 * Tiny deterministic PRNG (mulberry32) so AI-generated designs are reproducible
 * from a single integer seed. The same seed always produces the same sequence,
 * which is what lets `/ai/shuffle/{project}` re-roll the look (new seed → new
 * geometry/placement) without another model call.
 *
 * Not cryptographic — purely for layout variety.
 */
class SeededRandom
{
    private int $state;

    public function __construct(int $seed)
    {
        // Keep the state inside 32 bits; avoid the 0 fixed-point.
        $this->state = ($seed & 0xFFFFFFFF);
        if ($this->state === 0) {
            $this->state = 0x9E3779B9;
        }
    }

    /** Next float in [0, 1). */
    public function next(): float
    {
        // mulberry32
        $this->state = ($this->state + 0x6D2B79F5) & 0xFFFFFFFF;
        $t = $this->state;
        $t = (self::imul($t ^ ($t >> 15), $t | 1)) & 0xFFFFFFFF;
        $t ^= ($t + self::imul($t ^ ($t >> 7), $t | 61)) & 0xFFFFFFFF;
        $t = ($t ^ ($t >> 14)) & 0xFFFFFFFF;

        return $t / 4294967296.0;
    }

    /** Float in [$min, $max). */
    public function range(float $min, float $max): float
    {
        return $min + ($max - $min) * $this->next();
    }

    /** Float in [$min, $max), rounded to $places. */
    public function round(float $min, float $max, int $places = 2): float
    {
        return round($this->range($min, $max), $places);
    }

    /** Integer in [$min, $max] (inclusive). */
    public function int(int $min, int $max): int
    {
        if ($max <= $min) {
            return $min;
        }

        return $min + (int) floor($this->next() * ($max - $min + 1));
    }

    /** True with probability $p. */
    public function bool(float $p = 0.5): bool
    {
        return $this->next() < $p;
    }

    /**
     * Pick one element from a non-empty list.
     *
     * @template T
     * @param  array<int, T>  $items
     * @return T
     */
    public function pick(array $items): mixed
    {
        $items = array_values($items);

        return $items[$this->int(0, count($items) - 1)];
    }

    /** ±$amount jitter around 0. */
    public function jitter(float $amount): float
    {
        return $this->range(-$amount, $amount);
    }

    /** 32-bit integer multiply (emulates JS Math.imul) for mulberry32. */
    private static function imul(int $a, int $b): int
    {
        $a &= 0xFFFFFFFF;
        $b &= 0xFFFFFFFF;
        $aHi = ($a >> 16) & 0xFFFF;
        $aLo = $a & 0xFFFF;
        $bHi = ($b >> 16) & 0xFFFF;
        $bLo = $b & 0xFFFF;

        // (aLo*bLo) + (((aHi*bLo + aLo*bHi) << 16)), all mod 2^32
        $lo   = $aLo * $bLo;
        $mid  = (($aHi * $bLo) + ($aLo * $bHi)) & 0xFFFFFFFF;

        return ($lo + (($mid << 16) & 0xFFFFFFFF)) & 0xFFFFFFFF;
    }
}
