Private/Show-GridSelector.ps1

function Show-GridSelector {
    param (
        [Parameter(Mandatory)]
        [array]$Items,
        [string]$Title = "Select Item",
        [array]$Columns,
        [switch]$MultiSelect,
        [switch]$ShowPreview,
        [switch]$ShowFavorites,
        [string]$TooltipProperty
    )

    Add-Type -AssemblyName PresentationFramework
    Add-Type -AssemblyName PresentationCore
    Add-Type -AssemblyName WindowsBase

    # Get theme colors
    $t = Get-IRODThemeColors

    # If favorites enabled, add IsFavorite property and sort
    if ($ShowFavorites) {
        $favorites = Get-IRODFavorites
        $Items = $Items | ForEach-Object {
            $isFav = $_.id -in $favorites
            $_ | Add-Member -NotePropertyName "IsFavorite" -NotePropertyValue $isFav -PassThru -Force |
                 Add-Member -NotePropertyName "FavoriteDisplay" -NotePropertyValue $(if ($isFav) { "★" } else { "☆" }) -PassThru -Force
        }
        # Sort: favorites first, then alphabetically
        $Items = $Items | Sort-Object @{Expression={-[int]$_.IsFavorite}}, displayName
    }

    $script:selectorItems = [System.Collections.ArrayList]::new($Items)
    $script:filteredItems = [System.Collections.ArrayList]::new($Items)
    $script:exitRequested = $false

    # Build column XAML - add favorite column if enabled
    $columnXaml = ""
    if ($ShowFavorites) {
        $columnXaml += @"
<GridViewColumn Header="★" Width="45">
    <GridViewColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding FavoriteDisplay}" FontSize="16" Foreground="$($t.AccentGreen)"
                       Cursor="Hand" ToolTip="Click to toggle favorite">
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Foreground" Value="$($t.AccentGreenHover)"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
        </DataTemplate>
    </GridViewColumn.CellTemplate>
</GridViewColumn>
"@

    }
    foreach ($col in $Columns) {
        $columnXaml += "<GridViewColumn Header=`"$($col.Header)`" Width=`"$($col.Width)`" DisplayMemberBinding=`"{Binding $($col.Property)}`"/>`n"
    }

    $selectionMode = if ($MultiSelect) { "Extended" } else { "Single" }
    
    # Build tooltip setter if property provided
    $tooltipSetter = ""
    if ($TooltipProperty) {
        $tooltipSetter = @"
                    <Setter Property="ToolTipService.ShowDuration" Value="30000"/>
                    <Setter Property="ToolTipService.Placement" Value="Right"/>
                    <Setter Property="ToolTip">
                        <Setter.Value>
                            <ToolTip MaxWidth="500" Background="$($t.TooltipBackground)" BorderBrush="$($t.TooltipBorder)" Padding="10">
                                <TextBlock Text="{Binding $TooltipProperty}" TextWrapping="Wrap" Foreground="$($t.TextPrimary)" FontSize="12"/>
                            </ToolTip>
                        </Setter.Value>
                    </Setter>
"@

    }

    $xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="$Title" Height="600" Width="1050" WindowStartupLocation="CenterScreen" Topmost="True"
        Background="$($t.WindowBackground)">
    <Window.Resources>
        <Style TargetType="Button">
            <Setter Property="Background" Value="$($t.ButtonBackground)"/>
            <Setter Property="Foreground" Value="$($t.TextPrimary)"/>
            <Setter Property="BorderBrush" Value="$($t.Border)"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="Padding" Value="12,6"/>
            <Setter Property="FontSize" Value="13"/>
            <Setter Property="Cursor" Value="Hand"/>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Background" Value="$($t.ButtonHover)"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style TargetType="TextBox">
            <Setter Property="Background" Value="$($t.ControlBackground)"/>
            <Setter Property="Foreground" Value="$($t.TextPrimary)"/>
            <Setter Property="BorderBrush" Value="$($t.Border)"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="CaretBrush" Value="$($t.TextPrimary)"/>
        </Style>
    </Window.Resources>
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
 
        <TextBox Name="SearchBox" Grid.Row="0" Margin="0,0,0,15" Padding="8" FontSize="14"/>
        <TextBlock Grid.Row="0" Margin="10,8,0,0" Foreground="$($t.TextPlaceholder)" IsHitTestVisible="False" Name="SearchPlaceholder" FontSize="14">Search...</TextBlock>
 
        <ListView Name="ItemList" Grid.Row="1" SelectionMode="$selectionMode"
                  Background="$($t.ListBackground)" Foreground="$($t.TextPrimary)" BorderBrush="$($t.Border)" BorderThickness="1">
            <ListView.Resources>
                <Style TargetType="GridViewColumnHeader">
                    <Setter Property="Background" Value="$($t.HeaderBackground)"/>
                    <Setter Property="Foreground" Value="$($t.TextPrimary)"/>
                    <Setter Property="BorderBrush" Value="$($t.Border)"/>
                    <Setter Property="BorderThickness" Value="0,0,1,1"/>
                    <Setter Property="Padding" Value="8,6"/>
                    <Setter Property="FontWeight" Value="SemiBold"/>
                </Style>
                <Style TargetType="ListViewItem">
                    <Setter Property="Background" Value="Transparent"/>
                    <Setter Property="Foreground" Value="$($t.TextPrimary)"/>
                    <Setter Property="Padding" Value="8,4"/>
$tooltipSetter
                    <Style.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Background" Value="$($t.ListItemHover)"/>
                        </Trigger>
                        <Trigger Property="IsSelected" Value="True">
                            <Setter Property="Background" Value="$($t.SelectedBackground)"/>
                            <Setter Property="Foreground" Value="$($t.SelectedText)"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    $columnXaml
                </GridView>
            </ListView.View>
        </ListView>
 
        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
            <Button Name="FavoriteBtn" Content="★ Favorite" Width="90" Margin="0,0,10,0" Visibility="Collapsed">
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Background" Value="$($t.AccentGreen)"/>
                        <Setter Property="Foreground" Value="White"/>
                        <Setter Property="BorderBrush" Value="$($t.AccentGreenHover)"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Setter Property="Padding" Value="12,6"/>
                        <Setter Property="FontSize" Value="13"/>
                        <Setter Property="Cursor" Value="Hand"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="$($t.AccentGreenHover)"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
            <Button Name="PreviewBtn" Content="Preview" Width="90" Margin="0,0,10,0" Visibility="Collapsed">
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Background" Value="#4A4A4D"/>
                        <Setter Property="Foreground" Value="White"/>
                        <Setter Property="BorderBrush" Value="#5A5A5D"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Setter Property="Padding" Value="12,6"/>
                        <Setter Property="FontSize" Value="13"/>
                        <Setter Property="Cursor" Value="Hand"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#5A5A5D"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
            <Button Name="ExitBtn" Content="Exit Tool" Width="90" Margin="0,0,10,0">
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Background" Value="$($t.WarningRed)"/>
                        <Setter Property="Foreground" Value="White"/>
                        <Setter Property="BorderBrush" Value="#A52A2A"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Setter Property="Padding" Value="12,6"/>
                        <Setter Property="FontSize" Value="13"/>
                        <Setter Property="Cursor" Value="Hand"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#D13438"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
            <Button Name="OkBtn" Content="OK" Width="90" Margin="0,0,10,0" IsDefault="True">
                <Button.Style>
                    <Style TargetType="Button">
                        <Setter Property="Background" Value="#0E639C"/>
                        <Setter Property="Foreground" Value="White"/>
                        <Setter Property="BorderBrush" Value="#0C5086"/>
                        <Setter Property="BorderThickness" Value="1"/>
                        <Setter Property="Padding" Value="12,6"/>
                        <Setter Property="FontSize" Value="13"/>
                        <Setter Property="Cursor" Value="Hand"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#1177BB"/>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </Button.Style>
            </Button>
            <Button Name="CancelBtn" Content="Cancel" Width="90" IsCancel="True"/>
        </StackPanel>
    </Grid>
</Window>
"@


    $reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
    $window = [System.Windows.Markup.XamlReader]::Load($reader)

    $searchBox = $window.FindName("SearchBox")
    $searchPlaceholder = $window.FindName("SearchPlaceholder")
    $listView = $window.FindName("ItemList")
    $favoriteBtn = $window.FindName("FavoriteBtn")
    $previewBtn = $window.FindName("PreviewBtn")
    $exitBtn = $window.FindName("ExitBtn")
    $okBtn = $window.FindName("OkBtn")
    $cancelBtn = $window.FindName("CancelBtn")

    # Show preview button if requested
    if ($ShowPreview) {
        $previewBtn.Visibility = "Visible"
    }

    # Show favorite button if requested
    if ($ShowFavorites) {
        $favoriteBtn.Visibility = "Visible"
    }

    $listView.ItemsSource = $script:filteredItems

    # Handle star clicks for favorites
    if ($ShowFavorites) {
        $listView.Add_PreviewMouseLeftButtonDown({
            param($sender, $e)
            $source = $e.OriginalSource
            
            # Check if clicked on a TextBlock that's a star
            if ($source -is [System.Windows.Controls.TextBlock]) {
                $text = $source.Text
                if ($text -eq "★" -or $text -eq "☆") {
                    # Find the data item
                    $listViewItem = [System.Windows.Media.VisualTreeHelper]::GetParent($source)
                    while ($listViewItem -and $listViewItem -isnot [System.Windows.Controls.ListViewItem]) {
                        $listViewItem = [System.Windows.Media.VisualTreeHelper]::GetParent($listViewItem)
                    }
                    
                    if ($listViewItem) {
                        $item = $listViewItem.DataContext
                        if ($item -and $item.id) {
                            $scriptId = $item.id
                            
                            if ($item.IsFavorite) {
                                Remove-IRODFavorite -ScriptId $scriptId
                                $item.IsFavorite = $false
                                $item.FavoriteDisplay = "☆"
                            }
                            else {
                                Add-IRODFavorite -ScriptId $scriptId
                                $item.IsFavorite = $true
                                $item.FavoriteDisplay = "★"
                            }
                            
                            # Re-sort items
                            $searchText = $searchBox.Text.ToLower()
                            $script:filteredItems.Clear()
                            
                            $sortedItems = $script:selectorItems | Sort-Object @{Expression={-[int]$_.IsFavorite}}, displayName
                            $script:selectorItems.Clear()
                            foreach ($sortedItem in $sortedItems) {
                                $null = $script:selectorItems.Add($sortedItem)
                            }
                            
                            foreach ($sItem in $script:selectorItems) {
                                $match = [string]::IsNullOrEmpty($searchText)
                                if (-not $match) {
                                    foreach ($col in $Columns) {
                                        $val = $sItem.($col.Property)
                                        if ($val -and $val.ToString().ToLower().Contains($searchText)) {
                                            $match = $true
                                            break
                                        }
                                    }
                                }
                                if ($match) {
                                    $null = $script:filteredItems.Add($sItem)
                                }
                            }
                            $listView.Items.Refresh()
                            
                            $e.Handled = $true
                        }
                    }
                }
            }
        })
    }

    # Allow space key in search box
    $window.Add_PreviewKeyDown({
        param($src, $e)
        if ($e.Key -eq [System.Windows.Input.Key]::Space) {
            $focused = [System.Windows.Input.Keyboard]::FocusedElement
            if ($focused -eq $searchBox) {
                $caretIndex = $searchBox.CaretIndex
                $searchBox.Text = $searchBox.Text.Insert($caretIndex, " ")
                $searchBox.CaretIndex = $caretIndex + 1
                $e.Handled = $true
            }
        }
    })

    # Search filter
    $searchBox.Add_TextChanged({
        $searchText = $searchBox.Text.ToLower()
        $searchPlaceholder.Visibility = if ($searchText) { "Collapsed" } else { "Visible" }

        $script:filteredItems.Clear()
        foreach ($item in $script:selectorItems) {
            $match = $false
            foreach ($col in $Columns) {
                $val = $item.($col.Property)
                if ($val -and $val.ToString().ToLower().Contains($searchText)) {
                    $match = $true
                    break
                }
            }
            if ($match) {
                $null = $script:filteredItems.Add($item)
            }
        }
        $listView.Items.Refresh()
    })

    $script:selectorResult = $null
    $exitBtn.Add_Click({
        $script:selectorResult = $null
        $script:exitRequested = $true
        $window.Close()
    })

    $okBtn.Add_Click({
        if ($MultiSelect) {
            $script:selectorResult = @($listView.SelectedItems)
        } else {
            $script:selectorResult = $listView.SelectedItem
        }
        $window.Close()
    })

    $cancelBtn.Add_Click({
        $script:selectorResult = $null
        $window.Close()
    })

    # Favorite button click handler
    $favoriteBtn.Add_Click({
        $selectedItem = $listView.SelectedItem
        if (-not $selectedItem) {
            [System.Windows.MessageBox]::Show("Please select a script to toggle favorite.", "No Selection", "OK", "Warning")
            return
        }

        $scriptId = $selectedItem.id
        if ($selectedItem.IsFavorite) {
            # Remove from favorites
            Remove-IRODFavorite -ScriptId $scriptId
            $selectedItem.IsFavorite = $false
            $selectedItem.FavoriteDisplay = "☆"
        }
        else {
            # Add to favorites
            Add-IRODFavorite -ScriptId $scriptId
            $selectedItem.IsFavorite = $true
            $selectedItem.FavoriteDisplay = "★"
        }

        # Re-sort and refresh the list
        $searchText = $searchBox.Text.ToLower()
        $script:filteredItems.Clear()
        
        # Get all items sorted with favorites first
        $sortedItems = $script:selectorItems | Sort-Object @{Expression={-[int]$_.IsFavorite}}, displayName
        $script:selectorItems.Clear()
        foreach ($item in $sortedItems) {
            $null = $script:selectorItems.Add($item)
        }
        
        foreach ($item in $script:selectorItems) {
            $match = [string]::IsNullOrEmpty($searchText)
            if (-not $match) {
                foreach ($col in $Columns) {
                    $val = $item.($col.Property)
                    if ($val -and $val.ToString().ToLower().Contains($searchText)) {
                        $match = $true
                        break
                    }
                }
            }
            if ($match) {
                $null = $script:filteredItems.Add($item)
            }
        }
        $listView.Items.Refresh()
        
        # Re-select the item
        $listView.SelectedItem = $selectedItem
    })

    # Preview button click handler
    $previewBtn.Add_Click({
        $selectedItem = $listView.SelectedItem
        if (-not $selectedItem) {
            [System.Windows.MessageBox]::Show("Please select a script to preview.", "No Selection", "OK", "Warning")
            return
        }

        try {
            # Fetch full script details
            $scriptId = $selectedItem.id
            $uri = "$script:GraphBaseUrl/deviceManagement/deviceHealthScripts/$scriptId"
            $scriptDetails = Invoke-Graph -Uri $uri

            # Decode base64 content
            $detectionScript = if ($scriptDetails.detectionScriptContent) {
                [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($scriptDetails.detectionScriptContent))
            } else { "(No detection script)" }

            $remediationScript = if ($scriptDetails.remediationScriptContent) {
                [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($scriptDetails.remediationScriptContent))
            } else { "(No remediation script)" }

            # Create preview window
            $previewXaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Script Preview" Height="700" Width="1000"
        WindowStartupLocation="CenterScreen" Background="#1E1E1E">
    <Grid Margin="15">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
 
        <TextBlock Grid.Row="0" Name="ScriptTitle" FontSize="16" FontWeight="Bold" Foreground="White" Margin="0,0,0,15"/>
        <TextBlock Grid.Row="1" Text="Detection Script" FontSize="14" FontWeight="Bold" Foreground="#569CD6" Margin="0,0,0,8"/>
        <TextBox Grid.Row="2" Name="DetectionBox" IsReadOnly="True" TextWrapping="NoWrap" AcceptsReturn="True"
                 VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
                 FontFamily="Consolas" FontSize="12" Background="#252526" Foreground="#D4D4D4"
                 BorderBrush="#3F3F46" Padding="10"/>
 
        <TextBlock Grid.Row="3" Text="Remediation Script" FontSize="14" FontWeight="Bold" Foreground="#4EC9B0" Margin="0,15,0,8"/>
        <TextBox Grid.Row="4" Name="RemediationBox" IsReadOnly="True" TextWrapping="NoWrap" AcceptsReturn="True"
                 VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
                 FontFamily="Consolas" FontSize="12" Background="#252526" Foreground="#D4D4D4"
                 BorderBrush="#3F3F46" Padding="10"/>
 
        <Button Grid.Row="5" Content="Close" Width="100" Height="30" HorizontalAlignment="Right" Margin="0,15,0,0"
                Background="#2D2D30" Foreground="White" BorderBrush="#3F3F46" IsCancel="True"/>
    </Grid>
</Window>
"@


            $previewReader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($previewXaml))
            $previewWindow = [System.Windows.Markup.XamlReader]::Load($previewReader)
            
            # Set content programmatically to avoid XML escaping issues
            $previewWindow.Title = "Script Preview - $($selectedItem.displayName)"
            $previewWindow.FindName("ScriptTitle").Text = $selectedItem.displayName
            $previewWindow.FindName("DetectionBox").Text = $detectionScript
            $previewWindow.FindName("RemediationBox").Text = $remediationScript
            
            $previewWindow.Owner = $window
            $null = $previewWindow.ShowDialog()
        }
        catch {
            [System.Windows.MessageBox]::Show("Failed to load script preview: $_", "Error", "OK", "Error")
        }
    })

    # Double-click to select (single mode)
    if (-not $MultiSelect) {
        $listView.Add_MouseDoubleClick({
            if ($listView.SelectedItem) {
                $script:selectorResult = $listView.SelectedItem
                $window.Close()
            }
        })
    }

    $null = $window.ShowDialog()
    return $script:selectorResult
}