#Requires -Version 7.4

function Set-vSEALRegistryKey {
    <#
    .SYNOPSIS
        Creates or updates a registry key value.

    .DESCRIPTION
        Creates or updates registry key values with support for various value types,
        WOW6432Node redirection, and user-specific registry access via SID.
        Creates parent keys if they don't exist. Uses Convert-vSEALRegistryPath
        internally for path normalization.

    .PARAMETER LiteralPath
        The literal registry key path (e.g., HKLM:\SOFTWARE\Microsoft).
        Wildcards are not processed.

    .PARAMETER Name
        The name of the registry value to create or update.

    .PARAMETER Value
        The value to set.

    .PARAMETER Type
        The registry value type as a RegistryValueKind enum or string.
        Valid values: String, ExpandString, Binary, DWord, QWord, MultiString.
        Defaults to String.

    .PARAMETER Wow6432Node
        Access the 32-bit registry view on 64-bit systems.

    .PARAMETER SID
        User SID for accessing user-specific registry (converts HKCU to HKU\SID).

    .PARAMETER MultiStringValueMode
        For MultiString values, specifies how to handle the value:
        - Replace: Replace the entire value (default)
        - Add: Add the value(s) to the existing MultiString array
        - Remove: Remove the value(s) from the existing MultiString array

    .PARAMETER RegistryOptions
        Registry options for creating keys. Use 'Volatile' for volatile keys
        that are not preserved when the system restarts.

    .OUTPUTS
        [void]

    .EXAMPLE
        Set-vSEALRegistryKey -LiteralPath 'HKLM:\SOFTWARE\Test' -Name 'Setting' -Value 'Enabled'
        Creates or updates a string value.

    .EXAMPLE
        Set-vSEALRegistryKey -LiteralPath 'HKLM:\SOFTWARE\Test' -Name 'Count' -Value 42 -Type DWord
        Creates or updates a DWORD value.

    .EXAMPLE
        Set-vSEALRegistryKey -LiteralPath 'HKCU:\Software\Test' -Name 'Setting' -Value 'Data' -SID 'S-1-5-21-123456789'
        Sets a value in another user's registry hive.

    .EXAMPLE
        Set-vSEALRegistryKey -LiteralPath 'HKLM:\SOFTWARE\Test' -Name 'Paths' -Value 'C:\NewPath' -Type MultiString -MultiStringValueMode Add
        Adds a path to an existing MultiString value.
    #>
    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType([void])]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('Key')]
        [string]$LiteralPath,

        [Parameter(Mandatory, Position = 1)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory, Position = 2)]
        [AllowNull()]
        [AllowEmptyString()]
        $Value,

        [Parameter()]
        [Alias('PropertyType')]
        [Microsoft.Win32.RegistryValueKind]$Type = [Microsoft.Win32.RegistryValueKind]::String,

        [Parameter()]
        [switch]$Wow6432Node,

        [Parameter()]
        [string]$SID,

        [Parameter()]
        [ValidateSet('Replace', 'Add', 'Remove')]
        [string]$MultiStringValueMode = 'Replace',

        [Parameter()]
        [Microsoft.Win32.RegistryOptions]$RegistryOptions = [Microsoft.Win32.RegistryOptions]::None
    )

    # Convert the registry path using the helper function
    $convertParams = @{
        Key = $LiteralPath
    }
    if ($Wow6432Node) {
        $convertParams['Wow6432Node'] = $true
    }
    if ($SID) {
        $convertParams['SID'] = $SID
    }

    try {
        $convertedPath = Convert-vSEALRegistryPath @convertParams
    }
    catch {
        Write-vSEALLog -Message "Failed to convert registry path '$LiteralPath': $($_.Exception.Message)" -Level 'ERROR'
        throw
    }

    # Convert RegistryValueKind to PowerShell property type string
    $propertyType = switch ($Type) {
        ([Microsoft.Win32.RegistryValueKind]::String) { 'String' }
        ([Microsoft.Win32.RegistryValueKind]::ExpandString) { 'ExpandString' }
        ([Microsoft.Win32.RegistryValueKind]::Binary) { 'Binary' }
        ([Microsoft.Win32.RegistryValueKind]::DWord) { 'DWord' }
        ([Microsoft.Win32.RegistryValueKind]::QWord) { 'QWord' }
        ([Microsoft.Win32.RegistryValueKind]::MultiString) { 'MultiString' }
        default { 'String' }
    }

    Write-vSEALLog -Message "Setting registry value: $convertedPath\$Name = $Value (Type: $propertyType)" -Level 'VERBOSE'

    # Handle MultiString value modes
    $valueToSet = $Value
    if ($Type -eq [Microsoft.Win32.RegistryValueKind]::MultiString -and $MultiStringValueMode -ne 'Replace') {
        try {
            $existingValue = Get-ItemProperty -LiteralPath $convertedPath -Name $Name -ErrorAction SilentlyContinue
            if ($existingValue) {
                $existingArray = @($existingValue.$Name)
                $newValues = @($Value)

                if ($MultiStringValueMode -eq 'Add') {
                    # Add values that don't already exist
                    $valueToSet = @($existingArray) + @($newValues | Where-Object { $_ -notin $existingArray })
                    Write-vSEALLog -Message "Adding values to MultiString: $($newValues -join ', ')" -Level 'VERBOSE'
                }
                elseif ($MultiStringValueMode -eq 'Remove') {
                    # Remove specified values
                    $valueToSet = @($existingArray | Where-Object { $_ -notin $newValues })
                    Write-vSEALLog -Message "Removing values from MultiString: $($newValues -join ', ')" -Level 'VERBOSE'
                }
            }
            else {
                # No existing value - Add mode creates new, Remove mode does nothing meaningful
                if ($MultiStringValueMode -eq 'Remove') {
                    Write-vSEALLog -Message "Registry value '$Name' does not exist, nothing to remove" -Level 'VERBOSE'
                    return
                }
                $valueToSet = @($Value)
            }
        }
        catch {
            Write-vSEALLog -Message "Failed to read existing MultiString value, using Replace mode: $($_.Exception.Message)" -Level 'VERBOSE'
            $valueToSet = @($Value)
        }
    }

    # Check if the key exists, create if not
    if (-not (Test-Path -LiteralPath $convertedPath)) {
        Write-vSEALLog -Message "Registry key does not exist, creating: $convertedPath" -Level 'VERBOSE'

        if ($PSCmdlet.ShouldProcess($convertedPath, 'Create registry key')) {
            try {
                if ($RegistryOptions -eq [Microsoft.Win32.RegistryOptions]::Volatile) {
                    # Create volatile key using .NET API
                    $hive = $null
                    $subKeyPath = $null

                    if ($convertedPath -match 'Registry::HKEY_LOCAL_MACHINE\\(.+)$') {
                        $hive = [Microsoft.Win32.Registry]::LocalMachine
                        $subKeyPath = $Matches[1]
                    }
                    elseif ($convertedPath -match 'Registry::HKEY_CURRENT_USER\\(.+)$') {
                        $hive = [Microsoft.Win32.Registry]::CurrentUser
                        $subKeyPath = $Matches[1]
                    }
                    elseif ($convertedPath -match 'Registry::HKEY_USERS\\(.+)$') {
                        $hive = [Microsoft.Win32.Registry]::Users
                        $subKeyPath = $Matches[1]
                    }
                    elseif ($convertedPath -match 'Registry::HKEY_CLASSES_ROOT\\(.+)$') {
                        $hive = [Microsoft.Win32.Registry]::ClassesRoot
                        $subKeyPath = $Matches[1]
                    }
                    elseif ($convertedPath -match 'Registry::HKEY_CURRENT_CONFIG\\(.+)$') {
                        $hive = [Microsoft.Win32.Registry]::CurrentConfig
                        $subKeyPath = $Matches[1]
                    }

                    if ($hive -and $subKeyPath) {
                        $createdKey = $hive.CreateSubKey($subKeyPath, $true, $RegistryOptions)
                        if ($createdKey) {
                            $createdKey.Close()
                            Write-vSEALLog -Message "Created volatile registry key: $convertedPath" -Level 'VERBOSE'
                        }
                    }
                }
                else {
                    $null = New-Item -Path $convertedPath -Force -ErrorAction Stop
                    Write-vSEALLog -Message "Created registry key: $convertedPath" -Level 'VERBOSE'
                }
            }
            catch {
                Write-vSEALLog -Message "Failed to create registry key '$convertedPath': $($_.Exception.Message)" -Level 'ERROR'
                throw
            }
        }
        else {
            return
        }
    }

    # Check if the value already exists
    $valueExists = $false
    try {
        $existingValue = Get-ItemProperty -LiteralPath $convertedPath -Name $Name -ErrorAction Stop
        $valueExists = $true
    }
    catch [System.Management.Automation.ItemNotFoundException] {
        $valueExists = $false
    }
    catch [System.Management.Automation.PSArgumentException] {
        # Property does not exist
        $valueExists = $false
    }

    if ($PSCmdlet.ShouldProcess("$convertedPath\$Name", "Set registry value to '$valueToSet'")) {
        try {
            if ($valueExists) {
                # Update existing value
                Set-ItemProperty -LiteralPath $convertedPath -Name $Name -Value $valueToSet -Type $propertyType -ErrorAction Stop
                Write-vSEALLog -Message "Updated registry value: $convertedPath\$Name" -Level 'VERBOSE'
            }
            else {
                # Create new value
                $null = New-ItemProperty -LiteralPath $convertedPath -Name $Name -Value $valueToSet -PropertyType $propertyType -Force -ErrorAction Stop
                Write-vSEALLog -Message "Created registry value: $convertedPath\$Name" -Level 'VERBOSE'
            }
        }
        catch {
            Write-vSEALLog -Message "Failed to set registry value '$convertedPath\$Name': $($_.Exception.Message)" -Level 'ERROR'
            throw
        }
    }
}
