Functions/Public/Write-Question.ps1

<#
================================================================================
ORION DESIGN - POWERSHELL UI FRAMEWORK | Write-Question Function
================================================================================
Author: Sune Alexandersen Narud
Date: August 22, 2025
Module: OrionDesign v1.6.0
Category: Interactive Elements
Dependencies: OrionDesign Theme System
 
FUNCTION PURPOSE:
Creates interactive prompts for user input with validation and type safety.
Essential interactive component providing various question types including
text input, yes/no prompts, choice selection, and secure password entry.
 
HLD INTEGRATION:
┌─ INTERACTIVE ─┐ ┌─ INPUT TYPES ─┐ ┌─ OUTPUT ─┐
│ Write-Question│◄──►│ Text/YesNo │───►│ Validated│
│ • Validation │ │ Choice/Secure │ │ Input │
│ • Defaults │ │ Number Types │ │ Type Safe│
│ • Security │ │ Custom Valid │ │ Result │
└───────────────┘ └───────────────┘ └──────────┘
================================================================================
#>


<#
.SYNOPSIS
Creates styled interactive prompts for user input with validation and formatting.
 
.DESCRIPTION
The Write-Question function provides a consistent, themed approach to gathering user input. Supports various question types including Yes/No, multiple choice, secure input, and custom validation.
 
.PARAMETER Text
The question text to display to the user.
 
.PARAMETER Type
The type of question. Valid values:
- 'Text' (default) - Free text input
- 'YesNo' - Yes/No question with Y/N options
- 'Choice' - Multiple choice selection
- 'Secure' - Secure password input
- 'Number' - Numeric input with validation
 
.PARAMETER Options
Array of options for Choice type questions.
 
.PARAMETER Default
Default value if user presses Enter without input.
 
.PARAMETER Required
Makes the question mandatory (cannot be empty).
 
.PARAMETER Validation
Script block for custom validation of input.
 
.PARAMETER ValidationMessage
Custom message to display when validation fails.
 
.EXAMPLE
Write-Question "What is your name?"
 
Simple text input question.
 
.EXAMPLE
Write-Question "Continue with deployment?" -Type YesNo -Default Yes
 
Yes/No question with default value.
 
.EXAMPLE
Write-Question "Select environment" -Type Choice -Options @("Dev","Test","Prod") -Required
 
Multiple choice question that's required.
 
.EXAMPLE
Write-Question "Enter password" -Type Secure
 
Secure password input that masks characters.
 
.EXAMPLE
Write-Question "Enter port number" -Type Number -Validation { $_ -gt 0 -and $_ -lt 65536 } -ValidationMessage "Port must be between 1 and 65535"
 
Numeric input with custom validation.
#>

function Write-Question {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param(
        [Parameter(Mandatory, ParameterSetName = 'Default', Position = 0)][string]$Text,
        [ValidateSet('Text', 'YesNo', 'Choice', 'Secure', 'Number')] [string]$Type = 'Text',
        [array]$Options = @(),
        [string]$Default = "",
        [switch]$Required,
        [scriptblock]$Validation,
        [string]$ValidationMessage = "Invalid input. Please try again.",

        [Parameter(Mandatory, ParameterSetName = 'Demo')]
        [switch]$Demo
    )

    if ($Demo) {
        $renderCodeBlock = {
            param([string[]]$Lines)
            $innerWidth = ($Lines | Measure-Object -Property Length -Maximum).Maximum + 4
            $bar = '─' * $innerWidth
            Write-Host ' # Code' -ForegroundColor DarkGray
            Write-Host " ┌$bar┐" -ForegroundColor DarkGray
            foreach ($line in $Lines) {
                $padded = (" $line").PadRight($innerWidth)
                Write-Host " │" -ForegroundColor DarkGray -NoNewline
                Write-Host $padded -ForegroundColor Green -NoNewline
                Write-Host '│' -ForegroundColor DarkGray
            }
            Write-Host " └$bar┘" -ForegroundColor DarkGray
            Write-Host ''
        }

        Write-Host ''
        Write-Host ' Write-Question Demo' -ForegroundColor Cyan
        Write-Host ' ===================' -ForegroundColor DarkGray
        Write-Host ' (Static preview - actual function waits for user input)' -ForegroundColor DarkGray
        Write-Host ''

        $theme = if ($script:Theme) { $script:Theme } else { @{ Question='Yellow'; Muted='DarkGray'; Accent='Cyan' } }
        $qColor = if ($theme.Question) { $theme.Question } else { 'Yellow' }
        $mColor = if ($theme.Muted)    { $theme.Muted    } else { 'DarkGray' }

        # Text type
        Write-Host ' [Type: Text]' -ForegroundColor Yellow
        Write-Host ''
        & $renderCodeBlock @("Write-Question 'What is your name?' -Type Text")
        Write-Host ' ► ' -ForegroundColor $qColor -NoNewline
        Write-Host 'What is your name? ' -NoNewline
        Write-Host '(text input)' -ForegroundColor $mColor
        Write-Host ''

        # YesNo type
        Write-Host ' [Type: YesNo]' -ForegroundColor Yellow
        Write-Host ''
        & $renderCodeBlock @("Write-Question 'Continue with deployment?' -Type YesNo -Default Yes")
        Write-Host ' ► ' -ForegroundColor $qColor -NoNewline
        Write-Host 'Continue with deployment? ' -NoNewline
        Write-Host '[Y/N] (default: Y) ' -ForegroundColor $mColor -NoNewline
        Write-Host ''
        Write-Host ''

        # Choice type
        Write-Host ' [Type: Choice]' -ForegroundColor Yellow
        Write-Host ''
        & $renderCodeBlock @("Write-Question 'Select environment' -Type Choice -Options @('Dev','Test','Prod') -Required")
        Write-Host ' ► ' -ForegroundColor $qColor -NoNewline
        Write-Host 'Select environment:'
        Write-Host " 1. Dev" ; Write-Host " 2. Test" ; Write-Host " 3. Prod"
        Write-Host ' Enter choice (1-3): ' -NoNewline -ForegroundColor $mColor
        Write-Host ''
        Write-Host ''

        # Secure type
        Write-Host ' [Type: Secure]' -ForegroundColor Yellow
        Write-Host ''
        & $renderCodeBlock @("Write-Question 'Enter password' -Type Secure")
        Write-Host ' ► ' -ForegroundColor $qColor -NoNewline
        Write-Host 'Enter password: ' -NoNewline
        Write-Host '(masked input)' -ForegroundColor $mColor
        Write-Host ''

        return
    }

    # Default theme
    if (-not $script:Theme) {
        $script:Theme = @{
            Accent   = 'Cyan'
            Success  = 'Green'
            Warning  = 'Yellow'
            Error    = 'Red'
            Text     = 'White'
            Muted    = 'DarkGray'
            Divider  = '─'
            UseAnsi  = $true
        }
        if ($psISE) { $script:Theme.UseAnsi = $false }
    }

    do {
        $validInput = $true
        $userInput = ""

        # Display the question
        Write-Host
        Write-Host $Text -ForegroundColor $script:Theme.Text -NoNewline

        # Add type-specific prompts
        switch ($Type) {
            'YesNo' {
                $promptText = if ($Default) { " [Y/n]" } else { " [y/N]" }
                Write-Host $promptText -ForegroundColor $script:Theme.Muted -NoNewline
            }
            'Choice' {
                Write-Host "`n" -NoNewline
                for ($i = 0; $i -lt $Options.Count; $i++) {
                    $prefix = " $($i + 1). "
                    Write-Host $prefix -ForegroundColor $script:Theme.Accent -NoNewline
                    Write-Host $Options[$i] -ForegroundColor $script:Theme.Text
                }
                Write-Host "Select (1-$($Options.Count))" -ForegroundColor $script:Theme.Muted -NoNewline
                if ($Default) {
                    Write-Host " [default: $Default]" -ForegroundColor $script:Theme.Muted -NoNewline
                }
            }
            'Number' {
                Write-Host " (number)" -ForegroundColor $script:Theme.Muted -NoNewline
                if ($Default) {
                    Write-Host " [default: $Default]" -ForegroundColor $script:Theme.Muted -NoNewline
                }
            }
            'Secure' {
                Write-Host " (password will be hidden)" -ForegroundColor $script:Theme.Muted -NoNewline
            }
            default {
                if ($Default) {
                    Write-Host " [default: $Default]" -ForegroundColor $script:Theme.Muted -NoNewline
                }
            }
        }

        Write-Host ": " -ForegroundColor $script:Theme.Text -NoNewline

        # Get user input based on type
        switch ($Type) {
            'Secure' {
                $secureInput = Read-Host -AsSecureString
                $userInput = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureInput))
            }
            default {
                $userInput = Read-Host
            }
        }

        # Apply default if input is empty
        if ([string]::IsNullOrWhiteSpace($userInput) -and $Default) {
            $userInput = $Default
        }

        # Validate input based on type
        switch ($Type) {
            'YesNo' {
                if ([string]::IsNullOrWhiteSpace($userInput)) {
                    $userInput = if ($Default -eq 'Yes') { 'Y' } else { 'N' }
                }
                if ($userInput -match '^[Yy]') { 
                    return $true 
                } elseif ($userInput -match '^[Nn]') { 
                    return $false 
                } else {
                    Write-Host " ❌ Please enter Y for Yes or N for No" -ForegroundColor $script:Theme.Error
                    $validInput = $false
                }
            }
            'Choice' {
                if ([string]::IsNullOrWhiteSpace($userInput)) {
                    if ($Required) {
                        Write-Host " ❌ Selection is required" -ForegroundColor $script:Theme.Error
                        $validInput = $false
                    } elseif ($Default) {
                        return $Default
                    }
                } else {
                    $selection = 0
                    if ([int]::TryParse($userInput, [ref]$selection) -and $selection -ge 1 -and $selection -le $Options.Count) {
                        return $Options[$selection - 1]
                    } else {
                        Write-Host " ❌ Please enter a number between 1 and $($Options.Count)" -ForegroundColor $script:Theme.Error
                        $validInput = $false
                    }
                }
            }
            'Number' {
                if ([string]::IsNullOrWhiteSpace($userInput)) {
                    if ($Required) {
                        Write-Host " ❌ Number is required" -ForegroundColor $script:Theme.Error
                        $validInput = $false
                    } elseif ($Default) {
                        $userInput = $Default
                    } else {
                        # No input and no default, but not required - allow empty
                        return $null
                    }
                }
                
                # Validate the number (whether from input or default)
                if (-not [string]::IsNullOrWhiteSpace($userInput)) {
                    $number = 0
                    if (-not [double]::TryParse($userInput, [ref]$number)) {
                        Write-Host " ❌ Please enter a valid number" -ForegroundColor $script:Theme.Error
                        $validInput = $false
                    } else {
                        $userInput = $number
                    }
                }
            }
            default {
                if ([string]::IsNullOrWhiteSpace($userInput) -and $Required) {
                    Write-Host " ❌ Input is required" -ForegroundColor $script:Theme.Error
                    $validInput = $false
                }
            }
        }

        # Apply custom validation if provided and input is valid so far
        if ($validInput -and $Validation -and $userInput -ne $null -and $userInput -ne "") {
            try {
                # Debug output (remove after fixing)
                Write-Host " [DEBUG] Validating value: '$userInput' (Type: $($userInput.GetType().Name))" -ForegroundColor DarkGray
                $validationResult = & $Validation $userInput
                Write-Host " [DEBUG] Validation result: $validationResult" -ForegroundColor DarkGray
                if (-not $validationResult) {
                    Write-Host " ❌ $ValidationMessage" -ForegroundColor $script:Theme.Error
                    $validInput = $false
                }
            } catch {
                Write-Host " [DEBUG] Validation error: $($_.Exception.Message)" -ForegroundColor DarkRed
                Write-Host " ❌ $ValidationMessage" -ForegroundColor $script:Theme.Error
                $validInput = $false
            }
        }

    } while (-not $validInput)

    return $userInput
}