Private/Invoke-Replacements.ps1

function Invoke-Replacements {
    <#
    .SYNOPSIS
        Applies a replacement map to a string (case-insensitive matching, casing-aware output).
    .DESCRIPTION
        Internal engine shared by Protect-SensitiveData and Unprotect-SensitiveData. For each key in
        the map, every case-insensitive occurrence in the text is replaced with the mapped value,
        re-cased to match the found occurrence via Convert-MatchCasing.
    .PARAMETER Text
        The text to transform.
    .PARAMETER Map
        Hashtable of replacements (key = term to find, value = replacement).
    #>

    param(
        [string]$Text,
        [hashtable]$Map
    )

    foreach ($key in $Map.Keys) {
        $value  = $Map[$key]
        $result = [System.Text.StringBuilder]::new()
        $index  = 0

        while ($index -lt $Text.Length) {
            $found = [System.Globalization.CultureInfo]::CurrentCulture.CompareInfo.IndexOf(
                $Text, $key, $index,
                [System.Globalization.CompareOptions]::IgnoreCase
            )

            if ($found -lt 0) {
                # No more matches - append the rest
                [void]$result.Append($Text.Substring($index))
                break
            }

            # Append text before match
            [void]$result.Append($Text.Substring($index, $found - $index))

            # Get the actual matched text and adapt replacement casing
            $matchedText = $Text.Substring($found, $key.Length)
            $casedValue  = Convert-MatchCasing -Match $matchedText -Replacement $value
            [void]$result.Append($casedValue)

            $index = $found + $key.Length
        }

        $Text = $result.ToString()
    }

    return $Text
}