private/theme/Update-SingleControlTheme.ps1

function Update-SingleControlTheme {
    <#
    .SYNOPSIS
        Applies theme styling to a single control based on its type.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        $Control,
        [Parameter(Mandatory)]
        [hashtable]$Colors
    )

    # Type dispatch
    switch ($Control) {
        { $_ -is [System.Windows.Window] } {
            Write-Debug "Updating Window with accent: $($Colors.Accent)"

            # Update the title bar color via DWM (native chrome only). Skip when header color is missing; sparse hashtables from child theme resolution otherwise NullRef the converter midwalk.
            if ([PsUi.ModuleContext]::IsInitialized -and $Control.WindowStyle -ne 'None' -and ![string]::IsNullOrEmpty($Colors.HeaderBackground) -and  ![string]::IsNullOrEmpty($Colors.HeaderForeground)) {
                $headerBg = [System.Windows.Media.ColorConverter]::ConvertFromString($Colors.HeaderBackground)
                $headerFg = [System.Windows.Media.ColorConverter]::ConvertFromString($Colors.HeaderForeground)
                [PsUi.WindowManager]::SetTitleBarColor($Control, $headerBg, $headerFg)
            }

            # Skip background for transparent/borderless windows (custom chrome needs transparent background)
            if (!$Control.AllowsTransparency) {  $Control.Background = ConvertTo-UiBrush $Colors.WindowBg  }
            $Control.Foreground = ConvertTo-UiBrush $Colors.WindowFg

            # Regenerate window icon with new theme colors (unless custom logo is set)
            $session = Get-UiSession
            if ($session -and $session.CustomLogo -and (Test-Path $session.CustomLogo)) {
                # Custom logo is set - load it instead of regenerating themed icon
                $newIcon = Get-CustomLogoIcon -Path $session.CustomLogo
            }
            else { $newIcon = New-WindowIcon -Colors $Colors }

            if ($newIcon) {
                Write-Debug "New icon created, setting on window"
                $Control.Icon = $newIcon
                [PsUi.WindowManager]::SetTaskbarIcon($Control, $newIcon)

                # Update titlebar icon for custom chrome windows (Tag is a hashtable)
                $chromeInfo = $Control.Tag
                if ($chromeInfo -is [System.Collections.IDictionary] -and $chromeInfo['TitleBarIcon']) {  $chromeInfo['TitleBarIcon'].Source = $newIcon }
            }

            # Update taskbar overlay if present (e.g. TextEditor document icon)
            if ($Control.Resources.Contains('OverlayGlyph')) {
                try {
                    $glyph       = $Control.Resources['OverlayGlyph']
                    $overlayIcon = New-TaskbarOverlayIcon -GlyphChar $glyph -Color $Colors.Accent
                    if ($overlayIcon) { [PsUi.WindowManager]::SetTaskbarOverlay($Control, $overlayIcon, 'Overlay') }
                }
                catch {  Write-Verbose "Failed to update taskbar overlay: $_"  }
            }
            break
        }
        { $_ -is [System.Windows.Controls.GroupBox] } { Set-GroupBoxStyle -GroupBox $Control; break }
        { $_ -is [System.Windows.Controls.Button] } {

            # Inline button theme update logic (was Update-ButtonTheme)
            $isAccent = $false
            if ($Control.Tag -is [System.Collections.IDictionary] -and $Control.Tag['IsAccent']) {  $isAccent = $true  }

            # Apply base button style
            Set-ButtonStyle -Button $Control -Accent:$isAccent

            # Update button icon color (non-accent buttons use accent color for icon)
            if (!$isAccent -and $Control.Content -is [System.Windows.Controls.StackPanel]) {
                foreach ($child in $Control.Content.Children) {
                    if ($child -is [System.Windows.Controls.TextBlock] -and (Test-IconFont $child.FontFamily)) {
                        $child.Foreground = ConvertTo-UiBrush $Colors.Accent
                    }
                }
            }

            # Update checkmark for theme menu buttons
            if ($Control.Tag -is [System.Collections.IDictionary] -and $Control.Tag.ContainsKey('ThemeName')) {
                if ($Control.Tag.ContainsKey('Checkmark') -and $Control.Tag['Checkmark']) {
                    $isActive = $Control.Tag['ThemeName'] -eq [PsUi.ModuleContext]::ActiveTheme
                    $Control.Tag['Checkmark'].Text = if ($isActive) { [char]0x2713 } else { ' ' }
                    if ($isActive) { $Control.Tag['Checkmark'].Foreground = ConvertTo-UiBrush $Colors.Accent }
                }
            }

            # Style popup contents if button has a popup tag
            if ($Control.Tag -is [System.Windows.Controls.Primitives.Popup]) {
                $popup       = $Control.Tag
                $popupBorder = $popup.Child
                if ($popupBorder -is [System.Windows.Controls.Border]) {
                    $popupBorder.Background  = ConvertTo-UiBrush $Colors.ControlBg
                    $popupBorder.BorderBrush = ConvertTo-UiBrush $Colors.Border
                }
            }
            break
        }
        { $_ -is [System.Windows.Controls.TextBox] } { Set-TextBoxStyle -TextBox $Control; break }
        { $_ -is [System.Windows.Controls.DataGrid] } { Set-DataGridStyle -Grid $Control; break }
        { $_ -is [System.Windows.Controls.TabControl] } {
            Set-TabControlStyle -TabControl $Control
            $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg
            break
        }
        { $_ -is [System.Windows.Controls.TabItem] } { Set-TabItemStyle -TabItem $Control; break }
        { $_ -is [System.Windows.Controls.ComboBox] } { Set-ComboBoxStyle -ComboBox $Control; break }
        { $_ -is [System.Windows.Controls.RadioButton] } {
            # Wrap in try/catch - internal template RadioButtons may fail
            try { Set-RadioButtonStyle -RadioButton $Control }
            catch { Write-Debug "RadioButton style skipped: $_" }
            break
        }
        { $_ -is [System.Windows.Controls.ListBox] } { Set-ListBoxStyle -ListBox $Control; break }
        { $_ -is [System.Windows.Controls.DatePicker] } { Set-DatePickerStyle -DatePicker $Control; break }
        { $_ -is [System.Windows.Controls.TextBlock] } {

            # Null-tag fast path - the tag switch below has scriptblock conditions that die on $null input when invoked from a dispatched scriptblock.
            $tag = $Control.Tag
            if ($null -eq $tag) {
                if (!(Test-IconFont $Control.FontFamily)) { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                break
            }

            switch ($tag) {
                'AccentBrush'                 { $Control.Foreground = ConvertTo-UiBrush $Colors.Accent }
                'AccentHeaderForegroundBrush' { $Control.Foreground = ConvertTo-UiBrush $Colors.AccentHeaderFg }
                'ControlFgBrush'              { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                'SecondaryTextBrush'          { $Control.Foreground = ConvertTo-UiBrush $Colors.SecondaryText }
                'SuccessBrush'                { $Control.Foreground = ConvertTo-UiBrush $Colors.Success }
                'ErrorBrush'                  { $Control.Foreground = ConvertTo-UiBrush $Colors.Error }
                'AccentText'                  { $Control.Foreground = ConvertTo-UiBrush $Colors.Accent }
                'AccentButtonIcon'            { $Control.Foreground = ConvertTo-UiBrush $Colors.AccentHeaderFg }
                'AccentButtonText'            { $Control.Foreground = ConvertTo-UiBrush $Colors.AccentHeaderFg }
                'ThemeButtonIcon'             { $Control.Foreground = ConvertTo-UiBrush $Colors.HeaderForeground }
                'HeaderText'                  { $Control.Foreground = ConvertTo-UiBrush $Colors.HeaderForeground }
                'TimePickerLabel'             { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                'TimePickerText'              { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                'TimePickerArrowIcon'         { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                'TimePickerColon'             { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                { $_ -eq 'CardHeaderIcon' -or $_ -eq 'CardHeaderText' } {
                    # Card header text/icon - look at parent for accent info
                    $parent = [System.Windows.Media.VisualTreeHelper]::GetParent($Control)
                    while ($parent -and !($parent.Tag -is [System.Collections.IDictionary])) {
                        $parent = [System.Windows.Media.VisualTreeHelper]::GetParent($parent)
                    }
                    if ($parent -and $parent.Tag -is [System.Collections.IDictionary] -and $parent.Tag['IsAccent']) {
                        $bgColor = if ($parent.Tag['CustomColor']) { $parent.Tag['CustomColor'] } else { $Colors.Accent }
                        $Control.Foreground = ConvertTo-UiBrush (Get-ContrastColor -HexColor $bgColor)
                    }
                    else { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                }
                default {
                    # Regular text (not an icon font)
                    if (!(Test-IconFont $Control.FontFamily)) { $Control.Foreground = ConvertTo-UiBrush $Colors.ControlFg }
                }
            }
            break
        }
        { $_ -is [System.Windows.Controls.Border] } {
            # Same null-Tag bypass as the TextBlock branch - the scriptblock switch conditions below deadlock on $null when invoked from a dispatched scriptblock.
            $tag = $Control.Tag
            if ($null -eq $tag) { break }

            # Inline Border theme update logic (was Update-BorderTheme)
            switch ($tag) {
                'HeaderBorder' {
                    # Local brush from $Colors. SetResourceReference points at Application.Current.Resources, which goes stale when ApplyTheme's cross-thread Invoke aborts under the child-STA-picker dispatcher deadlock.
                    $Control.Background = ConvertTo-UiBrush $Colors.HeaderBackground
                }
                { $_ -is [hashtable] -and $_.IsStatusBar } { Update-StatusBarTheme -Bar $Control -Colors $Colors }
                'PopupBorder' {
                    $Control.Background  = ConvertTo-UiBrush $Colors.ControlBg
                    $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
                }
                'CardBorder' {
                    $Control.Background  = ConvertTo-UiBrush $Colors.ControlBg
                    $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
                }
                'CardSeparator' { $Control.Background = ConvertTo-UiBrush $Colors.Border }
                'ConsoleGutter' {
                    $Control.Background  = ConvertTo-UiBrush $Colors.WindowBg
                    $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
                }
                { $_ -in @('TimePickerBorder', 'TimePickerArrowBorder', 'TimePickerPopupBorder') } {
                    $Control.Background  = ConvertTo-UiBrush $Colors.ControlBg
                    $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
                }
                'TimePickerSeparator' { $Control.Background = ConvertTo-UiBrush $Colors.Border }
                'Separator_Solid'     { $Control.Background = ConvertTo-UiBrush $Colors.Border }
                'Separator_Fade' {
                    if ([string]::IsNullOrEmpty($Colors.Border)) { break }
                    # Recreate fade gradient with new border color
                    $borderColor       = [System.Windows.Media.ColorConverter]::ConvertFromString($Colors.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))
                    $Control.Background = $gradient
                }
                'Separator_Accent' {
                    if ([string]::IsNullOrEmpty($Colors.Accent)) { break }
                    # Recreate accent fade gradient
                    $accentColor       = [System.Windows.Media.ColorConverter]::ConvertFromString($Colors.Accent)
                    $transparentAccent = [System.Windows.Media.Color]::FromArgb(0, $accentColor.R, $accentColor.G, $accentColor.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($transparentAccent, 0))
                    [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($accentColor, 0.15))
                    [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($accentColor, 0.85))
                    [void]$gradient.GradientStops.Add([System.Windows.Media.GradientStop]::new($transparentAccent, 1))
                    $Control.Background = $gradient
                }
            }

            # Hashtable tags (card headers with accent/custom colors)
            if ($tag -is [System.Collections.IDictionary]) {
                $tagType = $tag['Type']
                if ($tagType -eq 'CardHeader') {
                    if ($tag['CustomColor']) { $Control.Background = ConvertTo-UiBrush $tag['CustomColor'] }
                    elseif ($tag['IsAccent']) { $Control.Background = ConvertTo-UiBrush $Colors.Accent }
                    else {
                        $headerBgColor = if ($Colors.GroupBoxBg) { $Colors.GroupBoxBg } else { $Colors.WindowBg }
                        $Control.Background = ConvertTo-UiBrush $headerBgColor
                    }
                }
            }
            break
        }
        { $_ -is [System.Windows.Controls.PasswordBox] } {
            $Control.Background  = ConvertTo-UiBrush $Colors.ControlBg
            $Control.Foreground  = ConvertTo-UiBrush $Colors.ControlFg
            $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
            break
        }
        { $_ -is [System.Windows.Controls.CheckBox] } { Set-CheckBoxStyle -CheckBox $Control; break }
        { $_ -is [System.Windows.Controls.RichTextBox] } {
            $Control.Background  = ConvertTo-UiBrush $Colors.ControlBg
            $Control.Foreground  = ConvertTo-UiBrush $Colors.ControlFg
            $Control.BorderBrush = ConvertTo-UiBrush $Colors.Border
            $highlightColor      = if ($Colors.TextHighlight) { $Colors.TextHighlight } else { $Colors.Selection }
            $Control.SelectionBrush = ConvertTo-UiBrush $highlightColor
            break
        }
        { $_ -is [System.Windows.Controls.ProgressBar] } { Set-ProgressBarStyle -ProgressBar $Control; break }
        { $_ -is [System.Windows.Controls.Slider] } { Set-SliderStyle -Slider $Control; break }
        { $_ -is [System.Windows.Controls.StackPanel] } {
            if ($Control.Tag -eq 'ProgressPanel') { $Control.Background = ConvertTo-UiBrush $Colors.ControlBg }
            break
        }
    }

    # Update ContextMenu if present (not in visual tree)
    if ($Control -is [System.Windows.FrameworkElement] -and $Control.ContextMenu) {  Set-ContextMenuStyle -ContextMenu $Control.ContextMenu  }
}