src/Providers/Base.ps1

# Providers/Base.ps1

function Get-FakeRandomDigit {
<#.SYNOPSIS Random integer 0-9.#>
    [CmdletBinding()] param()
    return (_Rng).Next(0, 10)
}

function Get-FakeRandomDigitNotNull {
<#.SYNOPSIS Random integer 1-9.#>
    [CmdletBinding()] param()
    return (_Rng).Next(1, 10)
}

function Get-FakeRandomDigitNot {
<#.SYNOPSIS Random digit 0-9 that is NOT $Excluded.#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][ValidateRange(0,9)][int]$Excluded)
    do { $d = (_Rng).Next(0,10) } while ($d -eq $Excluded)
    return $d
}

function Get-FakeRandomNumber {
<#
.SYNOPSIS Random integer with optional digit count.
.PARAMETER NbDigits Number of digits (1-18). $null = fully random.
.PARAMETER Strict If $true, first digit is never 0.
#>

    [CmdletBinding()]
    param(
        [Parameter()] [Nullable[int]] $NbDigits = $null,
        [Parameter()] [bool]          $Strict   = $false
    )
    if ($null -eq $NbDigits) { return (_Rng).Next(0, [int]::MaxValue) }
    if ($NbDigits -lt 1 -or $NbDigits -gt 9) { $NbDigits = [Math]::Max(1, [Math]::Min($NbDigits, 9)) }
    $min = if ($Strict) { [Math]::Pow(10, $NbDigits-1) -as [int] } else { 0 }
    $max = [Math]::Pow(10, $NbDigits) -as [int]
    return (_Rng).Next($min, $max)
}

function Get-FakeRandomFloat {
<#
.SYNOPSIS Random float.
.PARAMETER NbMaxDecimals Max decimal places ($null = any).
.PARAMETER Min Lower bound (default 0).
.PARAMETER Max Upper bound ($null = unbounded small value).
#>

    [CmdletBinding()]
    param(
        [Parameter()] [Nullable[int]]    $NbMaxDecimals = $null,
        [Parameter()] [double]           $Min           = 0,
        [Parameter()] [Nullable[double]] $Max           = $null
    )
    $hi = if ($null -eq $Max) { $Min + 1000 } else { $Max }
    $raw = $Min + ((_Rng).NextDouble() * ($hi - $Min))
    if ($null -ne $NbMaxDecimals) {
        $raw = [Math]::Round($raw, $NbMaxDecimals)
    }
    return $raw
}

function Get-FakeNumberBetween {
<#.SYNOPSIS Random integer in [$Min..$Max].#>
    [CmdletBinding()]
    param(
        [Parameter()] [int] $Min = 1000,
        [Parameter()] [int] $Max = 9000
    )
    return (_Rng).Next($Min, $Max + 1)
}

function Get-FakeRandomLetter {
<#.SYNOPSIS Random lowercase ASCII letter a-z.#>
    [CmdletBinding()] param()
    return [char]((_Rng).Next([int][char]'a', [int][char]'z' + 1))
}

function Get-FakeRandomElement {
<#.SYNOPSIS Pick one random element from an array.#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][array]$Array)
    return $Array[(_Rng).Next(0, $Array.Count)]
}

function Get-FakeRandomElements {
<#.SYNOPSIS Pick $Count random elements (with replacement) from an array.#>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)][array]$Array,
        [Parameter()][int]$Count = 1
    )
    $result = @()
    for ($i = 0; $i -lt $Count; $i++) {
        $result += $Array[(_Rng).Next(0, $Array.Count)]
    }
    return $result
}

function Invoke-FakeShuffle {
<#.SYNOPSIS Shuffle a string or array (Fisher-Yates).#>
    [CmdletBinding()]
    param([Parameter(Mandatory)]$Value)
    if ($Value -is [string]) {
        $chars = $Value.ToCharArray()
        for ($i = $chars.Count - 1; $i -gt 0; $i--) {
            $j = (_Rng).Next(0, $i + 1)
            $tmp = $chars[$i]; $chars[$i] = $chars[$j]; $chars[$j] = $tmp
        }
        return -join $chars
    }
    $arr = @($Value)
    for ($i = $arr.Count - 1; $i -gt 0; $i--) {
        $j = (_Rng).Next(0, $i + 1)
        $tmp = $arr[$i]; $arr[$i] = $arr[$j]; $arr[$j] = $tmp
    }
    return $arr
}

function Format-FakeNumerify {
<#.SYNOPSIS Replace every '#' with a random digit.#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Template)
    $sb = [System.Text.StringBuilder]::new()
    foreach ($c in $Template.ToCharArray()) {
        if ($c -eq '#') { [void]$sb.Append((_Rng).Next(0,10)) }
        else            { [void]$sb.Append($c) }
    }
    return $sb.ToString()
}

function Format-FakeLexify {
<#.SYNOPSIS Replace every '?' with a random lowercase letter.#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Template)
    $sb = [System.Text.StringBuilder]::new()
    foreach ($c in $Template.ToCharArray()) {
        if ($c -eq '?') { [void]$sb.Append([char]((_Rng).Next([int][char]'a', [int][char]'z'+1))) }
        else            { [void]$sb.Append($c) }
    }
    return $sb.ToString()
}

function Format-FakeBothify {
<#.SYNOPSIS Replace '#' with digit and '?' with letter.#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Template)
    return Format-FakeLexify (Format-FakeNumerify $Template)
}

function Format-FakeAsciify {
<#.SYNOPSIS Replace every '*' with a random printable ASCII character (33-126).#>
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Template)
    $sb = [System.Text.StringBuilder]::new()
    foreach ($c in $Template.ToCharArray()) {
        if ($c -eq '*') { [void]$sb.Append([char]((_Rng).Next(33, 127))) }
        else            { [void]$sb.Append($c) }
    }
    return $sb.ToString()
}

function Format-FakeRegexify {
<#
.SYNOPSIS Generate a string matching a simplified regex pattern.
.DESCRIPTION Supports: literal chars, [abc] classes, [a-z] ranges,
              (a|b|c) alternation, ?, *, + quantifiers, {n} {n,m} quantifiers.
#>

    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Pattern)

    function _ExpandClass([string]$cls) {
        $chars = New-Object System.Collections.Generic.List[char]
        $i = 0
        while ($i -lt $cls.Length) {
            if ($i+2 -lt $cls.Length -and $cls[$i+1] -eq '-') {
                $from=[int][char]$cls[$i]; $to=[int][char]$cls[$i+2]
                for($k=$from;$k -le $to;$k++){$chars.Add([char]$k)}
                $i+=3
            } else { $chars.Add($cls[$i]); $i++ }
        }
        return $chars
    }

    # Simple recursive-descent generator
    $posRef = @(0)  # Use array to allow reference modification across scopes
    function _Parse {
        $result = ''
        while ($posRef[0] -lt $Pattern.Length) {
            $c = $Pattern[$posRef[0]]
            switch ($c) {
                '(' {
                    $posRef[0]++
                    $alts = [System.Collections.Generic.List[string]]::new()
                    $cur  = ''
                    $depth = 1
                    while ($posRef[0] -lt $Pattern.Length -and $depth -gt 0) {
                        $ch = $Pattern[$posRef[0]]
                        if     ($ch -eq '(') { $depth++; $cur += $ch; $posRef[0]++ }
                        elseif ($ch -eq ')') { $depth--; if($depth -gt 0){$cur+=$ch} else{$posRef[0]++} }
                        elseif ($ch -eq '|' -and $depth -eq 1) { $alts.Add($cur); $cur=''; $posRef[0]++ }
                        else   { $cur += $ch; $posRef[0]++ }
                    }
                    $alts.Add($cur)
                    $chosen = $alts[((_Rng).Next(0,$alts.Count))]
                    $result += $chosen  # flat alternation
                }
                '[' {
                    $posRef[0]++
                    $cls = ''
                    while ($posRef[0] -lt $Pattern.Length -and $Pattern[$posRef[0]] -ne ']') {
                        $cls += $Pattern[$posRef[0]]; $posRef[0]++
                    }
                    $posRef[0]++ # consume ]
                    $chars = _ExpandClass $cls
                    $picked = [string]$chars[((_Rng).Next(0,$chars.Count))]
                    # check quantifier
                    $picked = _Quantify $picked
                    $result += $picked
                    continue
                }
                '{' {
                    # handled by _Quantify, skip stray {
                    $posRef[0]++; continue
                }
                default {
                    $atom = [string]$c
                    $posRef[0]++
                    if ($posRef[0] -lt $Pattern.Length -and $Pattern[$posRef[0]] -match '[?*+{]') {
                        $atom = _Quantify $atom
                    }
                    if ($atom -ne '') { $result += $atom }
                    continue
                }
            }
        }
        return $result
    }

    function _Quantify([string]$atom) {
        if ($posRef[0] -ge $Pattern.Length) { return $atom }
        $q = $Pattern[$posRef[0]]
        switch ($q) {
            '?' { $posRef[0]++; if((_Rng).Next(0,2) -eq 0){return ''} else{return $atom} }
            '*' { $posRef[0]++; $n=(_Rng).Next(0,5); return $atom*$n }
            '+' { $posRef[0]++; $n=(_Rng).Next(1,5); return $atom*$n }
            '{' {
                $posRef[0]++
                $nums=''; while($posRef[0] -lt $Pattern.Length -and $Pattern[$posRef[0]] -ne '}'){$nums+=$Pattern[$posRef[0]];$posRef[0]++}
                $posRef[0]++ # consume }
                if ($nums -match '^(\d+),(\d+)$') {
                    $n = (_Rng).Next([int]$Matches[1],[int]$Matches[2]+1)
                } else { $n = [int]$nums }
                return $atom*$n
            }
            default { return $atom }
        }
    }

    return _Parse
}