Private/UI/Read-Choice.ps1

function Read-Choice {
    <#
        .SYNOPSIS
        Prompts the user to select from a list of options.
 
        .DESCRIPTION
        Displays a question with a set of options and waits for user input.
        Validates the response against the provided options and returns the selected choice.
        Supports a default option that is used if the user presses Enter without input.
        The default option is displayed in uppercase to indicate it will be used if no input is provided.
         
        Input is case-insensitive and the function will loop until a valid option is selected.
 
        .PARAMETER Question
        The question or prompt to display to the user.
 
        .PARAMETER Options
        An array of valid option strings that the user can choose from.
        Default is @('y', 'n') for yes/no questions.
 
        .PARAMETER Default
        The default option to use if the user provides no input (presses Enter).
        Must be one of the options in the Options array.
        If not specified, defaults to the first option in the Options array.
        The default option is displayed in uppercase in the prompt.
 
        .INPUTS
        None. This function does not accept pipeline input.
 
        .OUTPUTS
        System.String
        Returns the selected option as a string.
 
        .EXAMPLE
        Read-Choice -Question "Continue?"
        Prompts "Continue? [Y/n]" and accepts 'y' or 'n'. Default is 'y'.
 
        .EXAMPLE
        Read-Choice -Question "Select action" -Options @('run', 'skip', 'exit') -Default 'skip'
        Prompts "Select action [run/SKIP/exit]" with 'skip' as the default.
 
        .EXAMPLE
        $choice = Read-Choice -Question "Overwrite file?" -Options @('yes', 'no', 'all') -Default 'no'
        if ($choice -eq 'yes') {
            # Overwrite the file
        }
        Stores the user's choice and uses it in conditional logic.
 
        .EXAMPLE
        $action = Read-Choice -Question "What would you like to do?" -Options @('audit', 'fix', 'export', 'quit')
        switch ($action) {
            'audit' { Invoke-Audit }
            'fix' { Invoke-Fix }
            'export' { Export-Results }
            'quit' { exit }
        }
        Uses the returned choice in a switch statement.
 
        .NOTES
        The function loops until a valid option is entered.
        User input is case-insensitive (e.g., 'Y', 'y', and 'yes' will all match 'yes').
        Pressing Enter without input uses the default option.
        Invalid input displays a yellow warning message with valid options.
    #>

    
    #requires -Version 5.1
    
    [CmdletBinding()]
    [OutputType([string])]
    param(
        [Parameter(Mandatory)]
        [string]$Question,
        
        [string[]]$Options = @('y', 'n'),
        
        [ValidateScript({
                if ($Options -notcontains $_) {
                    throw "-Default option '$_' must be one of the available options: $($Options -join ', ')"
                }
                $true
            })]
        [string]$Default = $null
    )
    
    # Set default to first option if not specified
    if (-not $Default) {
        $Default = $Options[0]
    }
    
    # Format options display with default highlighted
    $optionsDisplay = ($Options | ForEach-Object {
            if ($_ -eq $Default) { $_.ToUpper() } else { $_ }
        }) -join '/'
    
    while ($true) {
        try {
            $response = Read-Host "$Question [$optionsDisplay]"
            
            # Use default if no input provided
            if ([string]::IsNullOrWhiteSpace($response)) {
                return $Default
            }
            
            # Check if response matches an option (case-insensitive)
            $match = $Options | Where-Object { $_ -eq $response }
            if ($match) {
                return $match
            }
            
            Write-Warning "Invalid option. Please choose from: $($Options -join ', ')"
        } catch {
            $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                $_.Exception,
                'PromptFailed',
                [System.Management.Automation.ErrorCategory]::NotSpecified,
                $Question
            )
            $PSCmdlet.WriteError($errorRecord)
            return $Default
        }
    }
}