private/uitool/Initialize-UiToolParameters.ps1

<#
.SYNOPSIS
    Initializes parameter controls for New-UiTool dynamically.
#>

function Initialize-UiToolParameters {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Windows.Controls.Panel]$TargetPanel,

        [Parameter(Mandatory)]
        [array]$Parameters,

        [hashtable]$Descriptions = @{},

        [hashtable]$ThemeColors,

        [switch]$ShowParamType,

        [hashtable]$InputHelpers,

        [switch]$UseWrapLayout
    )

    if (!$ThemeColors) {
        $ThemeColors = Get-ThemeColors
    }
    if (!$InputHelpers) {
        $InputHelpers = @{ FilePicker = @(); FolderPicker = @() }
    }

    $isFirstParam = $true

    foreach ($param in $Parameters) {
        # When using wrap layout, each parameter goes into its own container
        $paramContainer = $null
        $paramBorder    = $null
        $addTarget      = $TargetPanel

        if ($UseWrapLayout) {
            # Wrap each parameter in a simple container with margin for spacing
            $paramContainer = [System.Windows.Controls.StackPanel]::new()
            $paramContainer.Orientation = 'Vertical'
            $paramContainer.Margin = [System.Windows.Thickness]::new(4,4,4,8)
            $addTarget = $paramContainer
        }
        else {
            # Add styled separator between parameters (not before first)
            if (!$isFirstParam) {
                # Create fade-style separator with gradient brush
                $borderColor = [System.Windows.Media.ColorConverter]::ConvertFromString($ThemeColors.Border)
                $transparentBorder = [System.Windows.Media.Color]::FromArgb(0, $borderColor.R, $borderColor.G, $borderColor.B)
                $gradient = [System.Windows.Media.LinearGradientBrush]::new()
                $gradient.StartPoint = [System.Windows.Point]::new(0, 0.5)
                $gradient.EndPoint   = [System.Windows.Point]::new(1, 0.5)
                [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($transparentBorder, 0))
                [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($borderColor, 0.1))
                [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($borderColor, 0.9))
                [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($transparentBorder, 1))

                $sep = [System.Windows.Controls.Border]@{
                    Height              = 1
                    Margin              = [System.Windows.Thickness]::new(0, 8, 0, 8)
                    Background          = $gradient
                    HorizontalAlignment = 'Stretch'
                    Tag                 = 'Separator_Fade'
                }
                $TargetPanel.Children.Add($sep) | Out-Null
            }
        }
        $isFirstParam = $false

        $varName = "param_$($param.Name)"
        $labelText = $param.Name
        if ($param.IsMandatory) { $labelText += " *" }
        $controlAlreadyRegistered = $false

        # Build a friendly type name for display
        $typeName = ''
        if ($param.IsSwitch) {
            $typeName = 'switch'
        }
        elseif ($param.Type) {
            $typeName = $param.Type.Name
            # Simplify common types
            switch ($typeName) {
                'String'   { $typeName = 'text' }
                'String[]' { $typeName = 'text[]' }
                'Int32'    { $typeName = 'int' }
                'Int64'    { $typeName = 'long' }
                'Double'   { $typeName = 'number' }
                'Single'   { $typeName = 'number' }
                'Boolean'  { $typeName = 'bool' }
                'DateTime' { $typeName = 'date' }
                'PSCredential' { $typeName = 'credential' }
                'SecureString' { $typeName = 'password' }
                'ScriptBlock'  { $typeName = 'script' }
            }
        }

        $labelPanel = [System.Windows.Controls.StackPanel]::new()
        $labelPanel.Orientation = 'Horizontal'
        $labelPanel.Margin = [System.Windows.Thickness]::new(0,0,0,4)

        $label = [System.Windows.Controls.TextBlock]::new()
        $label.Text = $labelText
        $label.FontWeight = [System.Windows.FontWeights]::SemiBold
        if ($ThemeColors.Foreground) {
            $label.Foreground = $ThemeColors.Foreground
        }
        $labelPanel.Children.Add($label) | Out-Null

        if ($typeName -and $ShowParamType) {
            $typeLabel = [System.Windows.Controls.TextBlock]::new()
            $typeLabel.Text = " [$typeName]"
            $typeLabel.FontStyle = [System.Windows.FontStyles]::Italic
            $typeLabel.Opacity = 0.6
            if ($ThemeColors.Foreground) {
                $typeLabel.Foreground = $ThemeColors.Foreground
            }
            $labelPanel.Children.Add($typeLabel) | Out-Null
        }

        $addTarget.Children.Add($labelPanel) | Out-Null

        $session = Get-UiSession
        $control = $null

        # ValidateSet → ComboBox
        if ($param.ValidateSet -and $param.ValidateSet.Count -gt 0) {
            $control = [System.Windows.Controls.ComboBox]::new()
            if (!$param.IsMandatory) {
                $control.Items.Add('') | Out-Null
            }
            foreach ($item in $param.ValidateSet) {
                $control.Items.Add($item) | Out-Null
            }

            if ($null -ne $param.DefaultValue) {
                $defaultIndex = $control.Items.IndexOf([string]$param.DefaultValue)
                if ($defaultIndex -ge 0) {
                    $control.SelectedIndex = $defaultIndex
                }
                else {
                    $control.SelectedIndex = 0
                }
            }
            else {
                $control.SelectedIndex = 0
            }
            Set-ComboBoxStyle -ComboBox $control
        }
        # Enum type → ComboBox with enum values
        elseif ($param.Type -and $param.Type.IsEnum) {
            $control = [System.Windows.Controls.ComboBox]::new()
            if (!$param.IsMandatory) {
                $control.Items.Add('') | Out-Null
            }
            foreach ($enumVal in [Enum]::GetNames($param.Type)) {
                $control.Items.Add($enumVal) | Out-Null
            }

            if ($null -ne $param.DefaultValue) {
                $defaultIndex = $control.Items.IndexOf([string]$param.DefaultValue)
                if ($defaultIndex -ge 0) {
                    $control.SelectedIndex = $defaultIndex
                }
                else {
                    $control.SelectedIndex = 0
                }
            }
            else {
                $control.SelectedIndex = 0
            }
            Set-ComboBoxStyle -ComboBox $control
        }
        # Switch → CheckBox
        elseif ($param.IsSwitch) {
            $control = [System.Windows.Controls.CheckBox]::new()
            $control.Content = $labelText

            # Mandatory switches must be checked and disabled
            if ($param.IsMandatory) {
                $control.IsChecked = $true
                $control.IsEnabled = $false
                $control.ToolTip = "This switch is required and cannot be disabled"
            }
            else {
                $control.IsChecked = $false
            }
            Set-CheckBoxStyle -CheckBox $control
        }
        # Bool → CheckBox
        elseif ($param.Type -eq [bool]) {
            $control = [System.Windows.Controls.CheckBox]::new()
            $control.Content = $labelText

            # Mandatory bools must be set
            if ($param.IsMandatory) {
                $control.IsChecked = $true
                $control.IsEnabled = $false
                $control.ToolTip = "This option is required and cannot be disabled"
            }
            else {
                $control.IsChecked = $false
            }
            Set-CheckBoxStyle -CheckBox $control
        }
        # Int/Double with ValidateRange → Slider (if ≤10 values) or TextBox with validation
        elseif ($param.ValidateRange -and ($param.Type -eq [int] -or $param.Type -eq [int16] -or $param.Type -eq [int32] -or $param.Type -eq [double] -or $param.Type -eq [float])) {
            $rangeSize = $param.ValidateRange.MaxRange - $param.ValidateRange.MinRange

            # Use slider only if range has 10 or fewer discrete values
            if ($rangeSize -le 10) {
                # Create a container for slider + value label
                $sliderPanel = [System.Windows.Controls.Grid]::new()
                $sliderPanel.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]@{ Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star) })
                $sliderPanel.ColumnDefinitions.Add([System.Windows.Controls.ColumnDefinition]@{ Width = [System.Windows.GridLength]::Auto })

                $slider = [System.Windows.Controls.Slider]::new()
                $slider.Minimum = $param.ValidateRange.MinRange
                $slider.Maximum = $param.ValidateRange.MaxRange
                $slider.Value = $param.ValidateRange.MinRange
                $slider.TickFrequency = 1
                $slider.IsSnapToTickEnabled = $true
                $slider.VerticalAlignment = 'Center'
                [System.Windows.Controls.Grid]::SetColumn($slider, 0)
                Set-SliderStyle -Slider $slider

                # Value label showing current slider value
                $valueLabel = [System.Windows.Controls.TextBlock]::new()
                $valueLabel.Text = [string]$slider.Value
                $valueLabel.MinWidth = 40
                $valueLabel.TextAlignment = 'Right'
                $valueLabel.VerticalAlignment = 'Center'
                $valueLabel.Margin = [System.Windows.Thickness]::new(8,0,0,0)
                if ($ThemeColors.Foreground) {
                    $valueLabel.Foreground = $ThemeColors.Foreground
                }
                [System.Windows.Controls.Grid]::SetColumn($valueLabel, 1)

                # Update label when slider value changes
                $slider.Add_ValueChanged({
                    param($sender, $eventArgs)
                    $this.Tag.Text = [string][int]$eventArgs.NewValue
                }.GetNewClosure())
                $slider.Tag = $valueLabel

                [void]$sliderPanel.Children.Add($slider)
                [void]$sliderPanel.Children.Add($valueLabel)

                $control = $sliderPanel

                # Register the slider (not the panel) for value retrieval
                $session.AddControlSafe($varName, $slider)
                $controlAlreadyRegistered = $true
            }
            else {
                # Large range - use TextBox with validation hint
                $control = [System.Windows.Controls.TextBox]::new()
                Set-TextBoxStyle -TextBox $control
                $control.Height = 32
                $control.VerticalContentAlignment = 'Center'

                # Apply numeric input filter based on type
                $filterType = if ($param.Type -eq [int] -or $param.Type -eq [int16] -or $param.Type -eq [int32] -or $param.Type -eq [int64]) { 'Int' } else { 'Double' }
                Set-TextBoxInputFilter -TextBox $control -InputType $filterType

                # Store range info in Tag for potential validation
                $control.Tag = @{
                    Type     = 'NumericRange'
                    MinRange = $param.ValidateRange.MinRange
                    MaxRange = $param.ValidateRange.MaxRange
                    DataType = $param.Type
                }

                # Add placeholder/tooltip showing valid range
                $control.ToolTip = "Enter a number between $($param.ValidateRange.MinRange) and $($param.ValidateRange.MaxRange)"
            }
        }
        # DateTime → DatePicker
        elseif ($param.Type -eq [datetime]) {
            $control = [System.Windows.Controls.DatePicker]::new()
            $control.SelectedDate = if ($param.DefaultValue) { $param.DefaultValue } else { [datetime]::Today }
            Set-DatePickerStyle -DatePicker $control
        }
        # Default → TextBox
        else {
            $control = [System.Windows.Controls.TextBox]::new()
            Set-TextBoxStyle -TextBox $control
            $control.Height = 32
            $control.VerticalContentAlignment = 'Center'

            # Apply input filtering based on parameter type
            if ($param.Type -eq [int] -or $param.Type -eq [int16] -or $param.Type -eq [int32] -or $param.Type -eq [int64]) {
                Set-TextBoxInputFilter -TextBox $control -InputType 'Int'
            }
            elseif ($param.Type -eq [double] -or $param.Type -eq [float] -or $param.Type -eq [decimal]) {
                Set-TextBoxInputFilter -TextBox $control -InputType 'Double'
            }

            # Set default value if specified
            if ($null -ne $param.DefaultValue -and $param.DefaultValue -ne '') {
                $control.Text = [string]$param.DefaultValue
            }

            if ($param.Type -eq [System.Management.Automation.PSCredential]) {
                # PSCredential → Use New-UiCredential helper
                # Create the credential control directly in the target panel
                $credContainer = [System.Windows.Controls.StackPanel]::new()
                $credContainer.Orientation = 'Vertical'
                $credContainer.Margin = [System.Windows.Thickness]::new(0, 0, 0, 4)

                # Username field
                $userLabel = [System.Windows.Controls.TextBlock]::new()
                $userLabel.Text = 'Username'
                $userLabel.Margin = [System.Windows.Thickness]::new(0, 0, 0, 4)
                if ($ThemeColors.ControlFg) {
                    $userLabel.Foreground = ConvertTo-UiBrush $ThemeColors.ControlFg
                }
                [void]$credContainer.Children.Add($userLabel)

                $userBox = [System.Windows.Controls.TextBox]::new()
                Set-TextBoxStyle -TextBox $userBox
                $userBox.Height = 32
                $userBox.VerticalContentAlignment = 'Center'
                [void]$credContainer.Children.Add($userBox)

                # Password field with peek button
                $passLabel = [System.Windows.Controls.TextBlock]::new()
                $passLabel.Text = 'Password'
                $passLabel.Margin = [System.Windows.Thickness]::new(0, 8, 0, 4)
                if ($ThemeColors.ControlFg) {
                    $passLabel.Foreground = ConvertTo-UiBrush $ThemeColors.ControlFg
                }
                [void]$credContainer.Children.Add($passLabel)

                $peekResult = New-PasswordInputWithPeek
                $passBox    = $peekResult.PasswordBox
                [void]$credContainer.Children.Add($peekResult.Container)

                # Create credential wrapper for Get-UiValue
                $credWrapper = [PSCustomObject]@{
                    PSTypeName   = 'PsUi.CredentialControl'
                    UsernameBox  = $userBox
                    PasswordBox  = $passBox
                    VariableName = $varName
                }

                # Store directly in Variables (not AddControlSafe which requires FrameworkElement)
                $session.Variables[$varName] = $credWrapper
                $controlAlreadyRegistered = $true
                $control = $credContainer

                # Wire up change events for mandatory credential validation
                if ($param.IsMandatory) {
                    $userBox.Add_TextChanged({ Update-UiToolRunButtonState })
                    $passBox.Add_PasswordChanged({ Update-UiToolRunButtonState })
                }
            }
            elseif ($param.Type -eq [System.Security.SecureString]) {
                # Use password input with peek button
                $peekResult = New-PasswordInputWithPeek
                $control    = $peekResult.Container
                
                # Register the PasswordBox for value extraction, not the wrapper grid
                $session.AddControlSafe($varName, $peekResult.PasswordBox)
                $controlAlreadyRegistered = $true
            }
            elseif ($param.Type -eq [string[]]) {
                $control.AcceptsReturn = $true
                $control.TextWrapping = [System.Windows.TextWrapping]::Wrap
                $control.MinHeight = 60
                $control.VerticalScrollBarVisibility = [System.Windows.Controls.ScrollBarVisibility]::Auto
                $control.VerticalContentAlignment = 'Top'
            }
        }

        # Apply common styling and check for input helpers
        if ($control) {
            $control.Margin = [System.Windows.Thickness]::new(0,0,0,4)

            # Helper buttons - file picker, folder picker, etc.
            $needsFilePicker     = $InputHelpers.FilePicker -contains $param.Name
            $needsFolderPicker   = $InputHelpers.FolderPicker -contains $param.Name
            $needsFilterBuilder  = $InputHelpers.FilterBuilder.ContainsKey($param.Name)
            $filterMode          = if ($needsFilterBuilder) { $InputHelpers.FilterBuilder[$param.Name] } else { 'Generic' }

            # Computer picker only works on domain-joined machines
            $needsComputerPicker = $false
            if ($InputHelpers.ComputerPicker -contains $param.Name) {
                try {
                    $cs = Get-CimInstance -ClassName Win32_ComputerSystem -ErrorAction Stop
                    if ($cs.PartOfDomain) {
                        $needsComputerPicker = $true
                    }
                }
                catch { Write-Debug "Domain check failed: $_" }
            }

            # Only add helper to TextBox controls
            if (($needsFilePicker -or $needsFolderPicker -or $needsComputerPicker -or $needsFilterBuilder) -and $control -is [System.Windows.Controls.TextBox]) {
                # Create a wrapper grid: [TextBox][Button]
                $wrapperGrid = [System.Windows.Controls.Grid]::new()
                $wrapperGrid.Margin = $control.Margin
                $control.Margin = [System.Windows.Thickness]::new(0)

                # TextBox column (stretch)
                $col1 = [System.Windows.Controls.ColumnDefinition]::new()
                $col1.Width = [System.Windows.GridLength]::new(1, [System.Windows.GridUnitType]::Star)
                [void]$wrapperGrid.ColumnDefinitions.Add($col1)

                # Button column (auto)
                $col2 = [System.Windows.Controls.ColumnDefinition]::new()
                $col2.Width = [System.Windows.GridLength]::Auto
                [void]$wrapperGrid.ColumnDefinitions.Add($col2)

                # Add TextBox to first column
                [System.Windows.Controls.Grid]::SetColumn($control, 0)
                [void]$wrapperGrid.Children.Add($control)

                # Create helper button
                $helperBtn = [System.Windows.Controls.Button]::new()
                $helperBtn.Width = 32
                $helperBtn.Height = 32
                $helperBtn.Padding = [System.Windows.Thickness]::new(0)
                $helperBtn.Margin = [System.Windows.Thickness]::new(4, 0, 0, 0)
                $helperBtn.Cursor = [System.Windows.Input.Cursors]::Hand

                # Determine icon and tooltip based on helper type
                if ($needsFolderPicker) {
                    $iconCode = [PsUi.ModuleContext]::GetIcon('Folder')
                    $helperBtn.ToolTip = 'Browse for folder...'
                    $helperBtn.Tag = @{ Mode = 'Folder'; TextBox = $control }
                }
                elseif ($needsFilePicker) {
                    $iconCode = [PsUi.ModuleContext]::GetIcon('OpenFile')
                    $helperBtn.ToolTip = 'Browse for file...'
                    $helperBtn.Tag = @{ Mode = 'File'; TextBox = $control }
                }
                elseif ($needsComputerPicker) {
                    $iconCode = [PsUi.ModuleContext]::GetIcon('Desktop')
                    $helperBtn.ToolTip = 'Select computer...'
                    # Array parameters get multi-select in the picker
                    $isArray = $param.Type.IsArray -or $param.Type.Name -like '*`[`]*'
                    $helperBtn.Tag = @{ Mode = 'Computer'; TextBox = $control; ThemeColors = $ThemeColors; MultiSelect = $isArray }
                }
                elseif ($needsFilterBuilder) {
                    $iconCode = [PsUi.ModuleContext]::GetIcon('Filter')
                    $helperBtn.ToolTip = 'Build filter pattern...'
                    $capturedFilterMode = [string]$filterMode
                    $helperBtn.Tag = @{ Mode = 'Filter'; TextBox = $control; FilterMode = $capturedFilterMode; ThemeColors = $ThemeColors }
                }

                $iconBlock = [System.Windows.Controls.TextBlock]::new()
                $iconBlock.Text = $iconCode
                $iconBlock.FontFamily = [System.Windows.Media.FontFamily]::new('Segoe MDL2 Assets')
                $iconBlock.FontSize = 14
                $iconBlock.HorizontalAlignment = 'Center'
                $iconBlock.VerticalAlignment = 'Center'
                $helperBtn.Content = $iconBlock

                # Style the button
                Set-ButtonStyle -Button $helperBtn

                # Click handler based on mode
                $helperBtn.Add_Click({
                    param($sender, $eventArgs)
                    $info = $sender.Tag
                    $result = $null

                    try {
                        switch ($info.Mode) {
                            'Folder'   { $result = Show-UiPathPicker -Mode 'Folder' }
                            'File'     { $result = Show-UiPathPicker -Mode 'File' }
                            'Computer' {
                                $multi = if ($info.MultiSelect) { $true } else { $false }
                                $picked = Show-WindowsObjectPicker -ObjectType Computer -MultiSelect:$multi
                                if ($picked) {
                                    # Extract RawValue from picker result objects
                                    if ($picked -is [array]) {
                                        $result = ($picked | ForEach-Object { $_.RawValue }) -join "`r`n"
                                    }
                                    else {
                                        $result = $picked.RawValue
                                    }
                                }
                            }
                            'Filter' {
                                $fMode = if ($info.FilterMode) { $info.FilterMode } else { 'Generic' }
                                # Get fresh colors at click time so theme changes are reflected
                                $currentColors = Get-ThemeColors
                                $result = Show-UiFilterBuilder -CurrentValue $info.TextBox.Text -Mode $fMode -ThemeColors $currentColors
                            }
                        }

                        if ($result) {
                            $info.TextBox.Text = $result
                        }
                    }
                    catch {
                        Write-Warning "Helper button error: $_"
                    }
                }.GetNewClosure())

                [System.Windows.Controls.Grid]::SetColumn($helperBtn, 1)
                [void]$wrapperGrid.Children.Add($helperBtn)

                # Register the TextBox (not the wrapper) for value retrieval
                if (!$controlAlreadyRegistered) {
                    $session.AddControlSafe($varName, $control)
                    $controlAlreadyRegistered = $true
                }

                # Add wrapper to panel instead of control
                $addTarget.Children.Add($wrapperGrid) | Out-Null
            }
            else {
                # No picker - add control directly
                if (!$controlAlreadyRegistered) {
                    $session.AddControlSafe($varName, $control)
                }

                $addTarget.Children.Add($control) | Out-Null
            }

            # Wire up change events for mandatory validation
            if ($param.IsMandatory) {
                $actualControl = $control
                
                # For wrapper grids (SecureString peek), find the actual input control
                if ($control -is [System.Windows.Controls.Grid]) {
                    foreach ($child in $control.Children) {
                        if ($child -is [System.Windows.Controls.PasswordBox]) {
                            $actualControl = $child
                            break
                        }
                    }
                }

                # Add change handlers based on control type
                if ($actualControl -is [System.Windows.Controls.TextBox]) {
                    $actualControl.Add_TextChanged({ Update-UiToolRunButtonState })
                }
                elseif ($actualControl -is [System.Windows.Controls.PasswordBox]) {
                    $actualControl.Add_PasswordChanged({ Update-UiToolRunButtonState })
                }
                elseif ($actualControl -is [System.Windows.Controls.ComboBox]) {
                    $actualControl.Add_SelectionChanged({ Update-UiToolRunButtonState })
                }
            }
        }

        # Required indicator
        if ($param.IsMandatory) {
            $reqLabel = [System.Windows.Controls.TextBlock]::new()
            $reqLabel.Text = "Required"
            $reqLabel.FontSize = 11
            $reqLabel.Foreground = ConvertTo-UiBrush $ThemeColors.Error
            $reqLabel.Margin = [System.Windows.Thickness]::new(0,0,0,2)
            $addTarget.Children.Add($reqLabel) | Out-Null
        }

        # Description
        if ($Descriptions.ContainsKey($param.Name)) {
            $descLabel = [System.Windows.Controls.TextBlock]::new()
            $descLabel.Text = $Descriptions[$param.Name]
            $descLabel.FontSize = 11
            $descLabel.TextWrapping = [System.Windows.TextWrapping]::Wrap
            $descLabel.Opacity = 0.7
            $descLabel.Margin = [System.Windows.Thickness]::new(0,0,0,4)
            if ($ThemeColors.Foreground) {
                $descLabel.Foreground = $ThemeColors.Foreground
            }
            $addTarget.Children.Add($descLabel) | Out-Null
        }

        # If using wrap layout, add the container to the target panel
        if ($UseWrapLayout -and $paramContainer) {
            $TargetPanel.Children.Add($paramContainer) | Out-Null
        }
    }

    # No parameters to configure
    if ($Parameters.Count -eq 0) {
        $emptyLabel = [System.Windows.Controls.TextBlock]::new()
        $emptyLabel.Text = "This command has no configurable parameters."
        $emptyLabel.FontStyle = [System.Windows.FontStyles]::Italic
        $emptyLabel.Opacity = 0.7
        $TargetPanel.Children.Add($emptyLabel) | Out-Null
    }
}