packages/sql-faker/src/Generation/Value/ValueChoices.php

1<?php
2
3declare(strict_types=1);
4
5namespace SqlFaker\Generation\Value;
6
7use Closure;
8use InvalidArgumentException;
9
10/**
11 * Per-plan decisions, memoized so repeated candidate inspection cannot consume more randomness.
12 * The caller interprets bytes; domains only receive bounded index decisions.
13 */
14final class ValueChoices
15{
16    /**
17     * @var array<string, string|null>
18     */
19    private array $values = [];
20
21    /**
22     * @param Closure(positive-int): ?int $choice Null selects representatives after decision input ends
23     */
24    public function __construct(private readonly Closure $choice)
25    {
26    }
27
28    /**
29     * Samples once per occurrence and domain; null retains the declared representatives.
30     */
31    public function value(int $index, string $definition, ?ValueDomain $domain): ?string
32    {
33        if ($domain === null) {
34            return null;
35        }
36        $key = $index . ':' . $definition;
37        if (!array_key_exists($key, $this->values)) {
38            $this->values[$key] = $this->index(2) === 0 ? null : $domain->choose($this->index(...));
39        }
40        return $this->values[$key];
41    }
42
43    /**
44     * @param positive-int $count
45     * @throws InvalidArgumentException When a caller supplies an out-of-range decision
46     */
47    public function index(int $count): int
48    {
49        $index = ($this->choice)($count) ?? 0;
50        if ($index < 0 || $index >= $count) {
51            throw new InvalidArgumentException('Value selector returned an out-of-range index.');
52        }
53        return $index;
54    }
55}
56