EasyGUI.psm1

Write-Verbose "Checking PowerShell version Requirement For EasyGUI . . ."

# Requires PS 5.1 or 7+ (but not 6.x)
$PSMajor = $PSVersionTable.PSVersion.Major
$OS = [System.Environment]::OSVersion.Platform

# Block unsupported OS first
if ($OS -ne 'Win32NT') {
    Write-Error "EasyGUI only supports Windows. Your current OS: $OS"
    return
}

# Block PowerShell older than 5.1
if ($PSMajor -lt 5 -or ($PSMajor -eq 5 -and $PSVersionTable.PSVersion.Minor -lt 1)) {
    Write-Error "EasyGUI requires at least PowerShell 5.1."
    return
}

# Block PowerShell 6.x
if ($PSMajor -eq 6) {
    Write-Error "EasyGUI does NOT support PowerShell 6.x. Please install PowerShell 7 or use Windows PowerShell 5.1."
    return
}

# Passed
Write-Verbose "EasyGUI System and PowerShell Requirement Passed."

Write-Verbose "Preparing Variables . . ."
if (-not $script:Options) { $script:Options = @{} }
if (-not $script:Window) { $script:Window = @{} }
if (-not $script:Window.Inputs) { $script:Window.Inputs = @{} }
if (-not $script:Window.InputActions) { $script:Window.InputActions = @{} }




Write-Verbose "Adding Required Assembly / Type Names . . ."
try {
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms, WindowsFormsIntegration
} catch {
    Write-Error "Oops something went wrong: $_"
}


function EasyGUI.ErrorOut {
param($ErrorMessage)
    Write-Error "a Error Have occurred: `n $ErrorMessage"
    
    [System.Windows.MessageBox]::Show(
        "Error: $ErrorMessage",
        "EasyGUI Error",
    [System.Windows.Forms.MessageBoxButtons]::OK,
    [System.Windows.Forms.MessageBoxIcon]::Error
        )

    return

}

function PrepareWindow {
    param(
        [string]$Title = "Untitled GUI",
        [int]$Width = 500,
        [int]$Height = 400
    )

$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="$Title"
        Height="$Height"
        Width="$Width"
        WindowStartupLocation="CenterScreen"
        Background="#1E1E1E"
        FontFamily="Segoe UI"
        ResizeMode="NoResize">
     
    <Grid Margin="10">
        <!-- ScrollViewer fills entire Grid -->
        <ScrollViewer VerticalScrollBarVisibility="Hidden"
                      HorizontalScrollBarVisibility="Disabled"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch"
                      Background="#1E1E1E">
            <!-- Inner StackPanel holds content -->
            <StackPanel x:Name="Stack" VerticalAlignment="Top" />
        </ScrollViewer>
    </Grid>
</Window>
"@


    try {
        $reader = New-Object System.Xml.XmlNodeReader ([xml]$xaml)
        $script:WpfWindow = [Windows.Markup.XamlReader]::Load($reader)
        $script:Stack = $script:WpfWindow.FindName("Stack")
    }
    catch {
        Write-Host "Error creating WPF window: $($_.Exception.Message)" -ForegroundColor Red
        return $false
    }
    if (-not ($script:WpfWindow | Get-Member -Name "Inputs")) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name "Inputs" -Value @{} -Force
    }
    return $true
}

function Window.Text {
    param(
        [string]$Content,
        [string]$foreground = "White",
        [string]$ID = $null  # Optional unique ID for updating
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Make sure Inputs hash exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    if ($ID) {
        # Ensure the hashtable itself exists
        if (-not $script:WpfWindow.Inputs) {
            $script:WpfWindow.Inputs = @{}
        }

        # If TextBlock already exists, just update
        if ($script:WpfWindow.Inputs.ContainsKey($ID)) {
            $script:WpfWindow.Inputs[$ID].Text = $Content
            $script:WpfWindow.Inputs[$ID].Foreground = $foreground
            return
        }
    }

    # Create a new TextBlock
    $label = New-Object System.Windows.Controls.TextBlock
    $label.Text = $Content
    $label.Foreground = $foreground
    $label.Margin = "5,5,5,10"
    $label.FontSize = 16

    # Store reference if ID given
    if ($ID) {
        $script:WpfWindow.Inputs[$ID] = $label
    }

    $script:Stack.Children.Add($label) | Out-Null
}



function Window.AddButton {
    param(
        [string]$Name,
        [scriptblock]$Command
    )
    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $Name
    $btn.Height = 35
    $btn.Width = 200
    $btn.HorizontalAlignment = "Left"
    $btn.Background = "#2A2A2A"
    $btn.Foreground = "White"
    $btn.BorderBrush = "#444"
    $btn.BorderThickness = 1
    $btn.Padding = "10,6"
    $btn.Margin = "4"
    $btn.FontSize = 14
    $btn.Cursor = "Hand"
      # Hover
    $btn.Add_MouseEnter({ $_.Source.Background = "#3A3A3A" })
    $btn.Add_MouseLeave({ $_.Source.Background = "#2A2A2A" })

    # Pressed
    $btn.Add_PreviewMouseDown({ $_.Source.Background = "#3A7BFF" })
    $btn.Add_PreviewMouseUp({ if (-not $_.Source.IsMouseOver) { $_.Source.Background = "#2A2A2A" } })

    $localCommand = $Command
    $btn.Add_Click({
        if ([string]::IsNullOrWhiteSpace($localCommand)) {
            Write-Host "`"$Name`" Button was Pressed!"
            return
        }
        try {
        & $localCommand
        } catch {
            Write-Error $_.Exception.Message
        }
    }.GetNewClosure())

    $script:Stack.Children.Add($btn) | Out-Null
}

function Window.AddInputBox {
    param(
        [Parameter(Mandatory=$true)]
        [string]$ID,

        [Parameter(Mandatory=$false)]
        [scriptblock]$Action
    )

    # Ensure custom storage exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }
    if (-not ($script:WpfWindow | Get-Member -Name InputActions)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name InputActions -Value @{}
    }

    # Ensure valid Name (must start with a letter)
    $nameSafe = "Input$ID"

    # Create TextBox
    $tb = New-Object System.Windows.Controls.TextBox
    $tb.Name = $nameSafe
    
    # Easy GUI style
    $tb.Background      = "#222"
    $tb.Foreground      = "White"
    $tb.BorderBrush     = "#555"
    $tb.BorderThickness = 1
    $tb.FontSize        = 14
    $tb.Padding         = "6"
    $tb.Margin          = "4"

    # Focus border change
    $tb.Add_GotFocus({ $_.Source.BorderBrush = "#3A7BFF" })
    $tb.Add_LostFocus({ $_.Source.BorderBrush = "#555" })

    # Hover effect
    $tb.Add_MouseEnter({ $_.Source.Background = "#2E2E2E" })
    $tb.Add_MouseLeave({ $_.Source.Background = "#222" })

    # Store input reference
    $script:WpfWindow.Inputs[$ID] = $tb

    # Store optional action
    if ($Action) {
        $script:WpfWindow.InputActions[$ID] = $Action
    }

    # Add automatically to current StackPanel if exists
    if ($script:Stack) { $script:Stack.Children.Add($tb) | Out-Null }

}


function Window.InputApply {
    param(
        [string]$InputID,
        [string]$ButtonLabel = "Apply",
        [string]$Alignment = "Left"
    )

    # Ensure the stack exists
    if (-not $script:Stack) { 
        Write-Host "GUI StackPanel not initialized"; 
        return 
    }

    # Ensure Inputs dictionary exists
    if (-not $script:WpfWindow.Inputs) { $script:WpfWindow.Inputs = @{} }

    # Ensure InputActions dictionary exists
    if (-not $script:WpfWindow.InputActions) { $script:WpfWindow.InputActions = @{} }

    # Capture local references for the closure
    $localInputs = $script:WpfWindow.Inputs
    $localActions = $script:WpfWindow.InputActions
    $localID = $InputID

    # Create the button
    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $ButtonLabel
    $btn.Height = 35
    $btn.Width = 200
    $btn.Margin = "4"
    $btn.FontSize = 14
    $btn.HorizontalAlignment = $Alignment
    $btn.Background = "#2A2A2A"
    $btn.Foreground = "White"
    $btn.BorderBrush = "#444"
    $btn.BorderThickness = 1
    $btn.Cursor = "Hand"

    # Hover and pressed effects
    $btn.Add_MouseEnter({ $_.Source.Background = "#3A3A3A" })
    $btn.Add_MouseLeave({ $_.Source.Background = "#2A2A2A" })
    $btn.Add_PreviewMouseDown({ $_.Source.Background = "#3A7BFF" })
    $btn.Add_PreviewMouseUp({ if (-not $_.Source.IsMouseOver) { $_.Source.Background = "#2A2A2A" } })

    # Click action
    $btn.Add_Click({
        # Check if input exists
        if (-not $localInputs.ContainsKey($localID)) {
            [System.Windows.MessageBox]::Show("Input '$localID' not found.","EasyGUI Error","OK","Error")
            return
        }

        # Check if action exists
        if (-not $localActions.ContainsKey($localID)) {
            [System.Windows.MessageBox]::Show("No action defined for Input '$localID'.","EasyGUI Error","OK","Error")
            return
        }

        # Execute action
        & $localActions[$localID]
    }.GetNewClosure())

    # Add to StackPanel
    $script:Stack.Children.Add($btn) | Out-Null
}










function Window.AddOption {
    param(
        [string]$Label,
        [string]$ID = "default",
        [string]$Mini = "$false",
        [string]$align = "Left",
        [string]$tooltip,
        [int]$Margin = 8,
        [bool]$Default = $false,
        [scriptblock]$Action = {}
        
        
    )

    $ID = if ($ID) { [string]$ID } else { "default" }
    $Label = if ($Label) { $Label } else { "Option" }

    # Ensure group exists
    if (-not $script:Options.ContainsKey($ID)) {
        $script:Options[$ID] = @{}
    }

    # Store state and action in script:Options
    $script:Options[$ID] = @{
        Checked = $Default
        Action  = $Action
    }
    

    # Create the checkbox
    $cb = New-Object System.Windows.Controls.CheckBox
    $cb.Content = $Label
    $cb.IsChecked = $Default
    $cb.HorizontalAlignment = $align
    if ($tooltip) {
        $cb.tooltip = $tooltip
    }
    
    $cb.Cursor = "Hand"
    $cb.Foreground = "White"
    $cb.FontSize   = 14
    $cb.Margin     = $Margin

    # Box (Border) styling
    $cb.BorderBrush = "#777"
    $cb.BorderThickness = 2
    $cb.Background = "#222"

    # Hover effect
    $cb.Add_MouseEnter({ $_.Source.BorderBrush = "#999" })
    $cb.Add_MouseLeave({ $_.Source.BorderBrush = "#777" })
    
    $cb.Add_Checked({ 
        $_.Source.Background   = "#00b100"
        $_.Source.BorderBrush  = "#00b100"
    })

    $cb.Add_Unchecked({ 
        $_.Source.Background   = "#222"
        $_.Source.BorderBrush  = "#777"
    })

    $cb.Tag = $ID

    # Update global hash when checked/unchecked
    $cb.Add_Checked({
        $script:Options[$_.Source.Tag].Checked = $true
    })
    
    $cb.Add_Unchecked({
        $script:Options[$_.Source.Tag].Checked = $false
    })
    
    

    $script:Stack.Children.Add($cb) | Out-Null
}





function Window.AddTabControl {
    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    $tabControl = New-Object System.Windows.Controls.TabControl
    $tabControl.Margin = "0,10,0,0"
    $tabControl.Height = [int]($script:WpfWindow.Height / 1.2)
    $tabControl.Width = [int]($script:WpfWindow.Width / 1.2)
    $tabControl.Background = "#1E1E1E"
    $tabControl.BorderBrush = "#1E1E1E"
    $tabControl.TabStripPlacement = "Top"
    # $tabControl.Padding = "5,2,5,2"
    $tabControl.Padding = "0,0,0,0"
    $tabControl.BorderThickness = 0
    $script:Stack.Children.Add($tabControl)
    $script:Tabs = $tabControl
}

function Window.AddTab {
    param(
        [string]$Label,
        [scriptblock]$Script
    )
    if (-not $script:Tabs) { Write-Host "TabControl not created"; return }

    $tabItem = New-Object System.Windows.Controls.TabItem
    $tabItem.Header = $Label
    $tabItem.Foreground = "#AAA"
    $tabItem.Background = "#1E1E1E"
    $tabItem.FontWeight = "Bold"

       # base style
    $tabItem.Background = "#222"
    $tabItem.BorderBrush = "#333"
    $tabItem.BorderThickness = 1
    $tabItem.Margin = "2,0"
    $tabItem.Padding = "12,6"
    $tabItem.Foreground = "#CCCCCC"
    $tabItem.FontSize = 14

    # hover
    $tabItem.Add_MouseEnter({
        if (-not $_.Source.IsSelected) {
            $_.Source.Background = "#333"
        }
    })

    $tabItem.Add_MouseLeave({
        if (-not $_.Source.IsSelected) {
            $_.Source.Background = "#222"
        }
    })

    # --- NEW: Create scrollable content for the tab ---
    $containerGrid = New-Object System.Windows.Controls.Grid
    $containerGrid.HorizontalAlignment = "Stretch"
    $containerGrid.VerticalAlignment = "Stretch"
    

    $row = New-Object System.Windows.Controls.RowDefinition
    $row.Height = "*"
    $containerGrid.RowDefinitions.Add($row)
    $col = New-Object System.Windows.Controls.ColumnDefinition
    $col.Width = "*"
    $containerGrid.ColumnDefinitions.Add($col)

    $scrollViewer = New-Object System.Windows.Controls.ScrollViewer
    $scrollViewer.VerticalScrollBarVisibility = "Hidden"  # hides scrollbar
    $scrollViewer.HorizontalScrollBarVisibility = "Hidden"
    $scrollViewer.HorizontalAlignment = "Stretch"
    $scrollViewer.VerticalAlignment = "Stretch"
    $scrollViewer.Background = "#1E1E1E"

    $scrollStack = New-Object System.Windows.Controls.StackPanel
    $scrollStack.Margin = "5"
    $scrollStack.Background = "#1E1E1E"

    $scrollViewer.Content = $scrollStack
    $containerGrid.Children.Add($scrollViewer)
    [System.Windows.Controls.Grid]::SetRow($scrollViewer,0)
    [System.Windows.Controls.Grid]::SetColumn($scrollViewer,0)

    $tabItem.Content = $containerGrid
    # --- END NEW ---

    $script:Tabs.Items.Add($tabItem)

    # Temporarily redirect global stack to inner scrollable StackPanel
    $oldStack = $script:Stack
    $script:Stack = $scrollStack

    # Execute the user-provided scriptblock
    & $Script

    # Restore original stack
    $script:Stack = $oldStack
}


function Window.AddRadioButtonApply {
    param(
        [string]$Label,
        [string]$ID
    )

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $Label
    $btn.Height = 35
    $btn.Width = 200
    $btn.Margin = "5"
    $btn.FontSize = 13
    $btn.Foreground = "White"
    $btn.Background = "#2D2D30"
    $btn.BorderBrush = "#3C3C3C"
    $btn.Cursor = "Hand"

    # Capture the options hash for this group safely
    $localOptions = if ($script:Options.ContainsKey($ID)) { $script:Options[$ID] } else { @{} }
    $localID = $ID

    $btn.Add_Click({
        $optionsGroup = $localOptions

        if (-not $optionsGroup -or $optionsGroup.Count -eq 0) {
            Write-Debug "No Options Selected for group '$localID'"
            return
        }

        # Find the selected option in the hash
        $selected = $optionsGroup.GetEnumerator() |
                    Where-Object { $_.Value.Checked } |
                    Select-Object -ExpandProperty Key

        if (-not $selected) {
            Write-Debug "No Options Selected for group '$localID'"
            return
        }

        # Execute the action stored in the hash
        $action = $optionsGroup[$selected].Action
        if ($action) { & $action }
        else { Write-Host "No action defined for selected radio: $selected" }
    }.GetNewClosure())

    $script:Stack.Children.Add($btn) | Out-Null
}




function Window.AddRadioOption {
    param(
        [string]$Label,
        [string]$ID = "default",
        [string]$Value = "default",
        [bool]$Default = $false,
        [scriptblock]$Action = {}
    )

    # Ensure group exists
    if (-not $script:Options.ContainsKey($ID)) {
        $script:Options[$ID] = @{}
    }

    if ($Default) {
        foreach ($k in $script:Options[$ID].Keys) {
            $script:Options[$ID][$k].Checked = ($k -eq $Value)
        }
    }

    
    # Store the radio option in script:Options
    $script:Options[$ID][$Value] = @{
        Checked = $Default
        Action  = $Action
    }

    # Create the RadioButton control
    $rb = New-Object System.Windows.Controls.RadioButton
    $rb.Content = $Label
    $rb.GroupName = $ID
    $rb.IsChecked = $Default
    $rb.Margin = "0,5,0,5"
    $rb.Cursor = "Hand"
    $rb.Foreground = "White"
    $rb.FontSize   = 14
    $rb.Margin     = "4"

    # Outer ring
    $rb.BorderBrush = "#777"
    $rb.BorderThickness = 2
    $rb.Background = "#777"

    # Hover
    $rb.Add_MouseEnter({ $_.Source.BorderBrush = "#999" })
    $rb.Add_MouseLeave({ $_.Source.BorderBrush = "#777" })

    # Checked
    $rb.Add_Checked({
        $_.Source.BorderBrush = "#3A7BFF"
    })
    $rb.Add_Unchecked({
        $_.Source.BorderBrush = "#777"
    })

    # Tag stores the label and group reference (no Action here)
    $rb.Tag = @{
        Value = $Value
        Group = $script:Options[$ID]
    }    
    

    # When checked, update the Checked field in script:Options
    $rb.Add_Checked({
        $tag   = $_.Source.Tag
        $group = $tag.Group
        $value = $tag.Value
    
        foreach ($key in $group.Keys) {
            $group[$key].Checked = ($key -eq $value)
        }
    })
    

    $script:Stack.Children.Add($rb) | Out-Null
}

function Window.AddDropDown {
    param(
        [Parameter(Mandatory)] [string]$ID,
        [Parameter(Mandatory)] [array]$Items,
        [Parameter()] [scriptblock]$Action = $null,
        [Parameter()] [int]$Width = 200,
        [Parameter()] [int]$Height = 32,
        [Parameter()] [string]$DisplayMember = "Display"
    )

    # 1. Setup Colors (Pre-converted for Closure)
    $bc = New-Object System.Windows.Media.BrushConverter
    $colorMain   = $bc.ConvertFromString("#222")
    $colorHover  = $bc.ConvertFromString("#2E2E2E")
    $colorBorder = $bc.ConvertFromString("#555")
    # $colorFocus = $bc.ConvertFromString("#3A7BFF")
    $colorWhite  = [System.Windows.Media.Brushes]::White

    # 2. Create the "Closed Box" (A Button)
    $btn = New-Object System.Windows.Controls.Button
    # $btn.Width = $Width
    $btn.Height = $Height
    $btn.Background = $colorMain
    $btn.Foreground = $colorWhite
    $btn.BorderBrush = $colorBorder
    $btn.BorderThickness = 1
    $btn.HorizontalContentAlignment = "Left"
    $btn.Padding = "10,0,0,0"
    $btn.Margin = "5"

    # 3. Create the "Open Box" (Popup + ListBox)
    $popup = New-Object System.Windows.Controls.Primitives.Popup
    $popup.PlacementTarget = $btn
    $popup.Placement = [System.Windows.Controls.Primitives.PlacementMode]::Bottom
    $popup.StaysOpen = $false
    $popup.AllowsTransparency = $true

    $list = New-Object System.Windows.Controls.ListBox
    $list.Width = $Width
    $list.MaxHeight = 200
    $list.Background = $colorMain
    $list.Foreground = $colorWhite
    $list.BorderBrush = $colorBorder
    $list.BorderThickness = 1
    $list.ItemsSource = $Items
    if ($Items.Count -gt 0 -and $Items[0] -is [PSCustomObject]) { 
        $list.DisplayMemberPath = $DisplayMember 
    }

    # Style the ListBoxItems to be dark
    $itemStyle = New-Object System.Windows.Style -ArgumentList ([System.Windows.Controls.ListBoxItem])
    $itemStyle.Setters.Add((New-Object System.Windows.Setter -ArgumentList ([System.Windows.Controls.ListBoxItem]::BackgroundProperty, $colorMain)))
    $itemStyle.Setters.Add((New-Object System.Windows.Setter -ArgumentList ([System.Windows.Controls.ListBoxItem]::ForegroundProperty, $colorWhite)))
    $list.ItemContainerStyle = $itemStyle
    
    $popup.Child = $list

    # 4. Create the "State Object" (To mimic SelectedItem)
    # Fixed Add-Member syntax
    $fakeCombo = New-Object PSObject
    $fakeCombo | Add-Member -MemberType NoteProperty -Name "SelectedItem" -Value $null
    $fakeCombo | Add-Member -MemberType NoteProperty -Name "InternalList" -Value $list

    # 5. Event Logic (Using GetNewClosure to keep variables alive)
    $btn.Add_Click({ 
        $popup.IsOpen = $true 
    }.GetNewClosure())

    $list.Add_SelectionChanged({
        param($s, $e)
        if ($null -eq $s.SelectedItem) { return }
        
        # Update the Fake Object and Button Text
        $fakeCombo.SelectedItem = $s.SelectedItem
        $displayVal = if ($s.SelectedItem.PSObject.Properties[$DisplayMember]) { $s.SelectedItem.$DisplayMember } else { $s.SelectedItem.ToString() }
        $btn.Content = $displayVal
        
        # Close the popup
        $popup.IsOpen = $false

        # Execute User Action
        if ($Action) { & $Action.GetNewClosure() }
    }.GetNewClosure())

    # Visual Feedback
    $btn.Add_MouseEnter({ $this.Background = $colorHover }.GetNewClosure())
    $btn.Add_MouseLeave({ $this.Background = $colorMain }.GetNewClosure())

    # 6. Initialize Selection
    if ($Items.Count -gt 0) {
        $list.SelectedIndex = 0
        $fakeCombo.SelectedItem = $Items[0]
        $btn.Content = if ($Items[0].PSObject.Properties[$DisplayMember]) { $Items[0].$DisplayMember } else { $Items[0].ToString() }
    }

    # 7. Registration in EasyGUI
    $script:WpfWindow.Inputs[$ID] = $fakeCombo
    $script:Stack.Children.Add($btn)
}



function Window.AddSuperApplyButton {
    param(
        [string]$Label,
        [string[]]$Groups  # Array of Option/Radio group IDs
    )

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $Label
    $btn.Height = 35
    $btn.Width = 250
    $btn.Margin = "5"
    $btn.FontSize = 13
    $btn.Background = "#2D2D30"
    $btn.Foreground = "White"
    $btn.BorderBrush = "#3C3C3C"
    $btn.HorizontalAlignment = "Center"
    $btn.Cursor = "Hand"

    # Capture references to all option groups inside the closure
    $groupsSnapshot = @{}
    foreach ($ID in $Groups) {
        $groupsSnapshot[$ID] = if ($script:Options -and $script:Options.ContainsKey($ID)) { 
            $script:Options[$ID] 
        } else { @{} }
    }

    $btn.Add_Click({
        foreach ($ID in $groupsSnapshot.Keys) {
            $optionsGroup = $groupsSnapshot[$ID]

            if (-not $optionsGroup -or $optionsGroup.Count -eq 0) {
                Write-Debug "No Options Selected for group '$ID'"
                continue  # skip this group, don't abort all
            }

            # Apply all checked options in this group
            $selected = $optionsGroup.GetEnumerator() |
                        Where-Object { $_.Value.Checked } |
                        Select-Object -ExpandProperty Key

            if (-not $selected) {
                Write-Debug "No Options Selected for group '$ID'"
                continue
            }

            foreach ($opt in $selected) {
                $action = $optionsGroup[$opt].Action
                if ($action) { & $action }
            }

            Write-Verbose "Group '$ID' applied: $($selected -join ', ')"
        }
    }.GetNewClosure())

    $script:Stack.Children.Add($btn) | Out-Null
}



function Window.AddApplyOptionButton {
    param(
        [string]$Label = "Apply Options",
        [string]$ID = "default",
        [string]$Alignment = "Left"
    )

    $btn = New-Object System.Windows.Controls.Button
    $btn.Content = $Label
    $btn.Margin = "5"
    $btn.Height = 35
    $btn.Width = 200
    $btn.FontSize = 13
    $btn.Background = "#2D2D30"
    $btn.Foreground = "White"
    $btn.BorderBrush = "#3C3C3C"
    $btn.HorizontalAlignment = $Alignment
    $btn.Cursor = "Hand"

    # Capture a reference to the options table for this group
    $localOptions = if ($script:Options.ContainsKey($ID)) { $script:Options[$ID] } else { @{} }
    $localID = $ID

    $btn.Add_Click({
        $optionsGroup = $localOptions

        if (-not $optionsGroup -or $optionsGroup.Count -eq 0) {
            Write-Debug "No Options Selected for group '$localID'"
            return
        }

        # Get all checked options
        $selected = $optionsGroup.GetEnumerator() |
                    Where-Object { $_.Value.Checked } |
                    Select-Object -ExpandProperty Key

        if (-not $selected) {
            Write-Debug "No Options Selected for group '$localID'"
            return
        }

        # Run actions
        foreach ($opt in $selected) {
            $action = $optionsGroup[$opt].Action
            if ($action) { & $action }
        }
    }.GetNewClosure())

    $script:Stack.Children.Add($btn) | Out-Null
}



function Window.GetID {
    param(
        [Parameter(Mandatory)]
        [string]$ID,

        [string]$Value
    )

    if (-not $script:Options.ContainsKey($ID)) {
        Write-Verbose "Legacy Inputs Does not exist"
    }

    $entry = $script:Options[$ID]

    # Radio group
    if ($Value) {
        if ($entry -is [hashtable] -and $entry.ContainsKey($Value)) {
            return [bool]$entry[$Value].Checked
        }
        return $false
    }

    # Checkbox
    if ($entry -is [hashtable] -and $entry.ContainsKey('Checked')) {
        return [bool]$entry.Checked
    }

    # Modern Inputs

    if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) {
        Write-Verbose "Modern Inputs Does not exist"
        return $false
    }

    $entry = $script:WpfWindow.Inputs[$ID]

    
    # Some GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Value')) {
        return $entry.Value
    }
    
    # DropDown
    if ($entry -is [hashtable] -and $entry.ContainsKey('SelectedItem')) {
        return $entry.SelectedItem
    }
    
    # Input Box and Text Related GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Text')) {
        return $entry.Text
    }
    
    # Media and File related GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Source')) {
        return $entry.Source
    }
    # Numeric Up Down and more
    if ($entry -is [hashtable] -and $entry.ContainsKey('State')) {
        if ($entry.State -is [hashtable] -and $entry.ContainsKey('Value')) {
            return $entry.State.Value
        }
    }

    Write-Debug "All Failed in Window.GetID, Returning false"
    return $false
}


function Select-Path {
    param (
        [Parameter(Mandatory=$true)]
        [ValidateSet("OpenFile","SaveFile","PickFolder")]
        [string]$Mode,

        [string]$Title = "Select item",
        [string]$Filter = "All files (*.*)|*.*",
        [string]$DefaultFileName = "NewFile.txt",
        [string]$InitialDirectory = [Environment]::GetFolderPath("Desktop")
    )

    switch ($Mode) {
        "OpenFile" {
            $dlg = New-Object System.Windows.Forms.OpenFileDialog
            $dlg.Title = $Title
            $dlg.Filter = $Filter
            $dlg.InitialDirectory = $InitialDirectory
            if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
                return $dlg.FileName
            }
        }
        "SaveFile" {
            $dlg = New-Object System.Windows.Forms.SaveFileDialog
            $dlg.Title = $Title
            $dlg.Filter = $Filter
            $dlg.FileName = $DefaultFileName
            $dlg.InitialDirectory = $InitialDirectory
            if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
                return $dlg.FileName
            }
        }
        "PickFolder" {
            $dlg = New-Object System.Windows.Forms.FolderBrowserDialog
            $dlg.Description = $Title
            $dlg.SelectedPath = $InitialDirectory
            if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
                return $dlg.SelectedPath
            }
        }
    }
}

function ShowWindow {
    param([bool]$DoNotBlock = $false)
    if (-not $script:WpfWindow) { Write-Host "No window created."; return }
    if ($DoNotBlock) {
    Write-Debug "Loading Non-Blocking ShowWindow"
    $null = $script:WpfWindow.Show()
    } else {
    Write-Debug "Loading Blocking ShowWindow"
    $null = $script:WpfWindow.ShowDialog()
    }
}

function Get-GUIWindow {
    return $script:WpfWindow
}

function Set-GUIWindow {
    param([Parameter(Mandatory)]$NewValue)
    $script:WpfWindow = $NewValue
}
function Adjust.ControlSizes {
    [CmdletBinding()]
    param(
        [Parameter()]
        [double]$Increase,

        [Parameter()]
        [double]$Decrease,

        [switch]$SetDefault
    )
    Write-Warning "Window.ControlSizes is experimental and may break the GUI. Use at your own risk."

    # Validate that Increase and Decrease are not used together
    if ($Increase -and $Decrease) {
        Write-Error "Use either -Increase or -Decrease, not both."
        return
    }

    function AdjustRecursive {
        param([Parameter(Mandatory = $true)]$Control)

        # --- WPF and WinForms ---
        # FontSize / Font
        if ($Control.PSObject.Properties.Name -contains 'FontSize') {
            # Save default if it doesn't exist
            if (-not $Control.PSObject.Properties.Match('FontSize_Default')) {
                $Control | Add-Member -MemberType NoteProperty -Name FontSize_Default -Value $Control.FontSize
            }

            if ($SetDefault) {
                $Control.FontSize = $Control.FontSize_Default
                $Control.PSObject.Properties.Remove('FontSize_Default')
            }
            elseif ($Increase) {
                $Control.FontSize *= $Increase
            }
            elseif ($Decrease) {
                $Control.FontSize /= $Decrease
            }
        }
        elseif ($Control.PSObject.Properties.Name -contains 'Font') {
            if (-not $Control.PSObject.Properties.Match('FontSize_Default')) {
                $Control | Add-Member -MemberType NoteProperty -Name FontSize_Default -Value $Control.Font.Size
            }

            if ($SetDefault) {
                $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.FontSize_Default)
                $Control.PSObject.Properties.Remove('FontSize_Default')
            }
            elseif ($Increase) {
                $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.Font.Size * $Increase)
            }
            elseif ($Decrease) {
                $Control.Font = New-Object System.Drawing.Font($Control.Font.FontFamily, $Control.Font.Size / $Decrease)
            }
        }
        # Width
        if (($Control.PSObject.Properties.Name -contains 'Width') -and (-not ($Control.PSObject.Properties.Name -contains "WindowStartupLocation"))) {
            if (-not $Control.PSObject.Properties.Match('Width_Default')) {
                $Control | Add-Member -MemberType NoteProperty -Name Width_Default -Value $Control.Width
            }

            if ($SetDefault) {
                $Control.Width = $Control.Width_Default
                $Control.PSObject.Properties.Remove('Width_Default')
            }
            elseif ($Increase) {
                $Control.Width *= $Increase
            }
            elseif ($Decrease) {
                $Control.Width /= $Decrease
            }
        }

        
        # Height
        if (($Control.PSObject.Properties.Name -contains 'Height') -and (-not ($Control.PSObject.Properties.Name -contains "WindowStartupLocation"))) {
            if (-not $Control.PSObject.Properties.Match('Height_Default')) {
                $Control | Add-Member -MemberType NoteProperty -Name Height_Default -Value $Control.Height
            }

            if ($SetDefault) {
                $Control.Height = $Control.Height_Default
                $Control.PSObject.Properties.Remove('Height_Default')
            }
            elseif ($Increase) {
                $Control.Height *= $Increase
            }
            elseif ($Decrease) {
                $Control.Height /= $Decrease
            }
        }
        # --- Recursively process child controls ---
        if ($Control.PSObject.Properties.Name -contains 'Children') {
            foreach ($child in $Control.Children) {
                AdjustRecursive -Control $child
            }
        }
        elseif ($Control.PSObject.Properties.Name -contains 'Controls') {
            foreach ($child in $Control.Controls) {
                AdjustRecursive -Control $child
            }
        }
    }

    # Loop through all windows
    foreach ($win in $script:WpfWindow) {
        AdjustRecursive -Control $win
    }
}

function Set.WindowSize {
    param(
        [int]$Width,
        [int]$Height
    )

    # Ensure the EasyGUI form object exists
    if (-not $script:WpfWindow) {
        Write-Error "EasyGUI window is not initialized."
        return
    }

    # Set new dimensions
    $script:WpfWindow.Width = $Width
    $script:WpfWindow.Height = $Height
}

function Window.Title {
    param([string]$Title)
    if ($script:WpfWindow) { $script:WpfWindow.Text = $Title }
}

function Window.AddImage {
    param(
        [Parameter(Mandatory=$true)]
        [string]$Path,          # Path to image file
        [string]$ID = $null,    # Optional unique ID for updating
        [int]$Width = 100,      # Optional width
        [int]$Height = 100      # Optional height
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Make sure Inputs hash exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    # If ID is provided, update existing Image if it exists
    if ($ID -and $script:WpfWindow.Inputs.ContainsKey($ID)) {
        $existing = $script:WpfWindow.Inputs[$ID]
        $bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
        $bitmap.BeginInit()
        $bitmap.UriSource = [Uri]::new($Path, [UriKind]::Absolute)
        $bitmap.EndInit()
        $existing.Source = $bitmap
        return
    }

    # Create a new Image element
    $image = New-Object System.Windows.Controls.Image
    $bitmap = New-Object System.Windows.Media.Imaging.BitmapImage
    $bitmap.BeginInit()
    $bitmap.UriSource = [Uri]::new($Path, [UriKind]::Absolute)
    $bitmap.DecodePixelWidth = $Width
    $bitmap.DecodePixelHeight = $Height
    $bitmap.EndInit()
    $image.Source = $bitmap
    $image.Width = $Width
    $image.Height = $Height
    $image.Margin = "5,5,5,10"

    # Store reference if ID is given
    if ($ID) {
        $script:WpfWindow.Inputs[$ID] = $image
    }

    $script:Stack.Children.Add($image)
}

function Window.ProgressBar {
    param(
        [string]$ID = $null,
        [int]$Width = 300,
        [int]$Height = 20,
        [bool]$IsIndeterminate = $true   # Looping style by default
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Make sure Inputs hash exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    # If ID is provided and al y exists, return existing
    if ($ID -and $script:WpfWindow.Inputs.ContainsKey($ID)) {
        return $script:WpfWindow.Inputs[$ID]
    }

    # Create a new ProgressBar
    $progressBar = New-Object System.Windows.Controls.ProgressBar
    $progressBar.Width = $Width
    $progressBar.Height = $Height
    $progressBar.Margin = "5,5,5,10"
    $progressBar.Minimum = 0
    $progressBar.Maximum = 100
    $progressBar.Value = 0
    $progressBar.IsIndeterminate = $IsIndeterminate

    # Store reference if ID is given
    if ($ID) {
        $script:WpfWindow.Inputs[$ID] = $progressBar
    }

    $script:Stack.Children.Add($progressBar) | Out-Null

   # return $progressBar
}
function Window.GroupBox {
    param(
        [string]$Header = "Group",
        [string]$ID = $null,            # Optional unique ID
        [scriptblock]$Action = $null    # Script block to add child controls
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Make sure Inputs hash exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    if ($ID) {
        # If GroupBox already exists, just return it
        if ($script:WpfWindow.Inputs.ContainsKey($ID)) {
      # return $script:WpfWindow.Inputs[$ID]
        }
    }

    # Create the GroupBox
    $groupBox = New-Object System.Windows.Controls.GroupBox
    $groupBox.Header = $Header
    $groupBox.Margin = "5,5,5,5"
    $groupBox.Padding = "5"
    $GroupBox.Background = "#222222"
    $GroupBox.Foreground = "White"
    $GroupBox.BorderBrush = "#444444"

    # Internal StackPanel to hold child controls
    $stackPanel = New-Object System.Windows.Controls.StackPanel
    $groupBox.Content = $stackPanel

    # Store reference if ID is given
    if ($ID) {
        $script:WpfWindow.Inputs[$ID] = $groupBox
    }

    # Add GroupBox to current Stack
    $script:Stack.Children.Add($groupBox) | Out-Null

    # Run the action script block in the context of the internal StackPanel
    if ($Action) {
        # Temporarily set $script:Stack to the new internal panel
        $oldStack = $script:Stack
        $script:Stack = $stackPanel
        & $Action
        $script:Stack = $oldStack
    }

 # return $stackPanel
}

# --- Helpers ---
function Window.GetRawID {
    param([string]$ID)
    if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) {
        Write-Verbose "Windows.GetID Failed because the ID Does not exist"
        return $null
    }
    $entry = $script:WpfWindow.Inputs[$ID]
    return $entry
}

function Window.SetRawID {
    param(
        [string]$ID,
        $Value
    )
    
    $item = Window.GetRawID $ID
    if ($null -eq $item) {
        # This triggers your error box
        [System.Windows.MessageBox]::Show("Error: Window.SetRawID Failed, ID '$ID' not found.")
        return
    }

    try {
            $ID = $Value
        } catch {
            [System.Windows.MessageBox]::Show("Error: Window.SetRawID Failed, Cannot Set ID $ID to $Value")
        }
    
}




# --- ContextMenu Control ---
function Window.ContextMenu {
    param(
        [string]$ID = $null,
        [scriptblock]$Items  # ScriptBlock to define menu items
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Ensure Inputs hash exists
    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    if ($ID) {
        # Replace existing ContextMenu if it exists
        if ($script:WpfWindow.Inputs.ContainsKey($ID)) {
            $existingMenu = $script:WpfWindow.Inputs[$ID]
            $existingMenu.Items.Clear()
            $contextMenu = $existingMenu
        } else {
            $contextMenu = New-Object System.Windows.Controls.ContextMenu
            $script:WpfWindow.Inputs[$ID] = $contextMenu
        }
    } else {
        $contextMenu = New-Object System.Windows.Controls.ContextMenu
    }
    $contextMenu.Background = "#2b2b2b"
    $contextMenu.Foreground = "#2b2b2b"
    
    # Temporarily store $script:CurrentMenu to let Items script block add menu items
    $script:CurrentMenu = $contextMenu
    & $Items
    Remove-Variable CurrentMenu -Scope Script -ErrorAction SilentlyContinue

    # Attach to the last added control in the stack, if exists
    if ($script:Stack.Children.Count -gt 0) {
        $lastControl = $script:Stack.Children[$script:Stack.Children.Count - 1]
        $lastControl.ContextMenu = $contextMenu
    }

}

# --- Helper to add MenuItem inside ContextMenu ---
function Window.MenuItem {
    param(
        [string]$Header,
        [scriptblock]$Action = $null
    )

    if (-not $script:CurrentMenu) { Write-Host "No ContextMenu is active"; return }

    $menuItem = New-Object System.Windows.Controls.MenuItem
    $menuItem.Header = $Header
    $menuItem.Background = "#2b2b2b"
    $menuItem.Foreground = "White"

    if ($Action) {
$menuItem.Add_Click($Action)

    }

    $script:CurrentMenu.Items.Add($menuItem)
}

function Window.NumericUpDown {
    param(
        [string]$ID = $null,
        [decimal]$Value = 0,
        [decimal]$Minimum = 0,
        [decimal]$Maximum = 100,
        [decimal]$Increment = 1,
        [string]$Label = $null
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    if ($null -eq $script:WpfWindow.Inputs) { $script:WpfWindow.Inputs = @{} }

    # 1. Main Container
    $panel = New-Object System.Windows.Controls.StackPanel
    $panel.Orientation = "Horizontal"
    $panel.Margin = "5,2,5,2"

    # 2. Label styling
    if ($Label) {
        $textBlock = New-Object System.Windows.Controls.TextBlock
        $textBlock.Text = $Label
        $textBlock.Margin = "0,0,10,0"
        $textBlock.VerticalAlignment = "Center"
        $textBlock.Foreground = "White"
        $panel.Children.Add($textBlock)
    }

    # 3. Modern Dark TextBox
    $numInput = New-Object System.Windows.Controls.TextBox
    $numInput.Text = $Value.ToString()
    $numInput.Width = 60
    $numInput.Height = 26
    $numInput.Background = "#333333"
    $numInput.Foreground = "White"
    $numInput.BorderBrush = "#555555"
    $numInput.VerticalContentAlignment = "Center"
    $numInput.HorizontalContentAlignment = "Center"
    $numInput.BorderThickness = 1

    # 4. Modern Button Stack
    $btnStack = New-Object System.Windows.Controls.StackPanel
    $btnStack.Orientation = "Vertical"
    $btnStack.VerticalAlignment = "Center"

    # Up Button Styling
    $btnUp = New-Object System.Windows.Controls.Button
    $btnUp.Content = [char]0x25B2 # Up Triangle
    $btnUp.FontSize = 7
    $btnUp.Height = 13
    $btnUp.Width = 18
    $btnUp.Background = "#444444"
    $btnUp.Foreground = "White"
    $btnUp.BorderThickness = 0

    # Down Button Styling
    $btnDown = New-Object System.Windows.Controls.Button
    $btnDown.Content = [char]0x25BC # Down Triangle
    $btnDown.FontSize = 7
    $btnDown.Height = 13
    $btnDown.Width = 18
    $btnDown.Background = "#444444"
    $btnDown.Foreground = "White"
    $btnDown.BorderThickness = 0

    # 5. Logic (Scoped via closures)
    $btnUp.Add_Click({
        [decimal]$v = 0
        if ([decimal]::TryParse($numInput.Text, [ref]$v)) {
            if (($v + $Increment) -le $Maximum) { $numInput.Text = ($v + $Increment).ToString() }
        }
    }.GetNewClosure())

    $btnDown.Add_Click({
        [decimal]$v = 0
        if ([decimal]::TryParse($numInput.Text, [ref]$v)) {
            if (($v - $Increment) -ge $Minimum) { $numInput.Text = ($v - $Increment).ToString() }
        }
    }.GetNewClosure())

    # Assembly
    $btnStack.Children.Add($btnUp)
    $btnStack.Children.Add($btnDown)
    $panel.Children.Add($numInput)
    $panel.Children.Add($btnStack)

    # 6. REGISTER ID (Matched to your GetID .State.Value pattern)
    if ($ID) {
        $stateObj = New-Object PSObject
        $stateObj | Add-Member -MemberType ScriptProperty -Name "Value" -Value {
            return [decimal]$numInput.Text
        }.GetNewClosure() -SecondValue {
            $numInput.Text = $args[0].ToString()
        }.GetNewClosure()

        $entry = New-Object PSObject
        $entry | Add-Member -NotePropertyName "State" -NotePropertyValue $stateObj
        
        $script:WpfWindow.Inputs[$ID] = $entry
    }

    $script:Stack.Children.Add($panel) | Out-Null
}

# --- Helper usage with GetID / SetID ---
# Get numeric value: $val = Window.GetID "MyNumeric"
# Set numeric value: Window.SetID "MyNumeric" 50


function Window.TreeView {
    param(
        [string]$ID = $null,
        [hashtable]$Items = @{},
        [hashtable]$Content = @{},
        [int]$Width = 400,
        [int]$Height = 300
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    if (-not ($script:WpfWindow | Get-Member -Name Inputs)) {
        $script:WpfWindow | Add-Member -MemberType NoteProperty -Name Inputs -Value @{}
    }

    $script:TreeViewGrid = New-Object System.Windows.Controls.Grid
    $script:TreeViewGrid.Width = $Width
    $script:TreeViewGrid.Height = $Height
    $col1 = New-Object System.Windows.Controls.ColumnDefinition
    $col1.Width = "200"
    $col2 = New-Object System.Windows.Controls.ColumnDefinition
    $col2.Width = "*"
    $script:TreeViewGrid.ColumnDefinitions.Add($col1)
    $script:TreeViewGrid.ColumnDefinitions.Add($col2)

    $script:TreeViewControl = New-Object System.Windows.Controls.TreeView
    [System.Windows.Controls.Grid]::SetColumn($script:TreeViewControl, 0)
    $script:TreeViewGrid.Children.Add($script:TreeViewControl) | Out-Null

    $script:TreeViewContentPanel = New-Object System.Windows.Controls.StackPanel
    [System.Windows.Controls.Grid]::SetColumn($script:TreeViewContentPanel, 1)
    $script:TreeViewGrid.Children.Add($script:TreeViewContentPanel) | Out-Null

    if ($ID) {
        $script:WpfWindow.Inputs[$ID] = @{
            Tree = $script:TreeViewControl
            ContentPanel = $script:TreeViewContentPanel
            Items = $Items
            Content = $Content
        }
    }

    function Add-TreeItems {
        param($parent, $items)
        foreach ($key in $items.Keys) {
            $node = New-Object System.Windows.Controls.TreeViewItem
            $node.Header = $key
            $parent.Items.Add($node) | Out-Null

            # Use script-scoped panel and content hash
            $node.Add_Selected({
                $panel = $script:TreeViewContentPanel
                $contentHash = $Content
                if ($null -ne $panel) {
                    $panel.Dispatcher.Invoke([action]{
                        $panel.Children.Clear()
                        $header = $this.Header
                        if ($contentHash.ContainsKey($header)) {
                            $val = $contentHash[$header]
                            if ($val -is [scriptblock]) { & $val.Invoke() }
                            elseif ($val -is [System.Windows.UIElement]) { $panel.Children.Add($val) | Out-Null }
                            elseif ($val -is [string]) {
                                $lbl = New-Object System.Windows.Controls.Label
                                $lbl.Content = $val
                                $panel.Children.Add($lbl) | Out-Null
                            }
                        } else {
                            $lbl = New-Object System.Windows.Controls.Label
                            $lbl.Content = "No content for '$header'"
                            $panel.Children.Add($lbl) | Out-Null
                        }
                    })
                }
            })

            if ($items[$key] -is [hashtable] -and $items[$key].Count -gt 0) {
                Add-TreeItems -parent $node -items $items[$key]
            }
        }
    }

    Add-TreeItems -parent $script:TreeViewControl -items $Items

    $script:Stack.Children.Add($script:TreeViewGrid) | Out-Null
}

function Window.GetInputBox {
    param(
        [string]$InputID
    )
    return $script:WpfWindow.Inputs[$InputID].Text
}

function Window.ControlLockCheckBox {
    param(
        [string]$ID,
        [string]$Label,
        [bool]$Lock = $true  # $true = lock, $false = unlock
    )

    if (-not $script:Options.ContainsKey($ID)) { return }

    # Find the control in the StackPanel
    foreach ($child in $script:Stack.Children) {
        if ($child -is [System.Windows.Controls.CheckBox] -and $child.Tag.Label -eq $Label) {
            $child.IsEnabled = -not $Lock
            return
        }
    }
}

function Window.ControlLockEntireRadioID {
    param(
        [string]$ID,
        [bool]$Lock = $true
    )

    if (-not $script:Options.ContainsKey($ID)) { return }

    # Iterate through all controls in the StackPanel
    foreach ($child in $script:Stack.Children) {
        if ($child -is [System.Windows.Controls.RadioButton]) {
            $tag = $child.Tag
            if ($tag.GroupID -eq $ID) {
                $child.IsEnabled = -not $Lock
            }
        }
    }
}

function Window.Close {
    param()

    # Ensure GUI exists
    if (-not $script:WpfWindow) {
        Write-Host "GUI not initialized"
        return
    }

    try {
        # Close on the UI thread synchronously
        $script:WpfWindow.Dispatcher.Invoke({
            # Ensure the window is not already closed
            if ($script:WpfWindow -and -not $script:WpfWindow.Dispatcher.HasShutdownStarted) {
                $script:WpfWindow.Close()
            }
        })

        # Cleanup AFTER the window has fully closed
        $script:Stack = $null

        if ($script:WpfWindow -and ($script:WpfWindow | Get-Member -Name Inputs)) {
            $script:WpfWindow.Inputs.Clear()
        }

        $script:WpfWindow = $null
    }
    catch {
        Write-Host "Error closing window: $_"
    }
}


function Window.ToggleVisibility {
    param(
        [bool]$Visible
    )

    if (-not $script:WpfWindow) {
        Write-Host "GUI not initialized"
        return
    }

    # If user didn't specify -Visible, auto-toggle
    if (-not $PSBoundParameters.ContainsKey("Visible")) {
        $Visible = -not ($script:WpfWindow.Visibility -eq [System.Windows.Visibility]::Visible)
    }

    $script:WpfWindow.Dispatcher.Invoke({
        if ($Visible) {
            $script:WpfWindow.Show()
            $script:WpfWindow.Activate()
        }
        else {
            $script:WpfWindow.Hide()
        }
    })
}


function Window.Control {
    param(
        [bool]$TopMost,
        [ValidateSet("Visible","Hidden","Empty","None")]
        [string]$ControlBars = $null,

        [string]$SetTitle = $null,

        [bool]$Resize
    )

    if (-not $script:WpfWindow) {
        Write-Host "GUI not initialized"
        return
    }

    # -----------------------------------------------------------
    # Load Native API if control bars will require it
    # -----------------------------------------------------------
    if ($ControlBars -and ($ControlBars -eq "Empty") -and -not ([Type]::GetType("EasyGUI.Native", $false))) {

        Add-Type -Name "Native" -Namespace "EasyGUI" -MemberDefinition @"
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern int GetWindowLong(System.IntPtr hWnd, int nIndex);
 
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    public static extern int SetWindowLong(System.IntPtr hWnd, int nIndex, int dwNewLong);
"@

    }

    # -----------------------------------------------------------
    # Apply changes inside UI thread
    # -----------------------------------------------------------
    $script:WpfWindow.Dispatcher.Invoke({

        # -------------------------
        # Title (SetTitle)
        # -------------------------
        if ($PSBoundParameters.ContainsKey("SetTitle")) {
            $script:WpfWindow.Title = $SetTitle
        }

        # -------------------------
        # Resize control
        # -------------------------
        if ($PSBoundParameters.ContainsKey("Resize")) {

            if ($Resize) {
                # Enable resizing
                $script:WpfWindow.ResizeMode = "CanResize"

                # If ControlBars made WindowStyle None, restore border
                if ($script:WpfWindow.WindowStyle -eq "None") {
                    $script:WpfWindow.WindowStyle = "SingleBorderWindow"
                }
            }
            else {
                # Disable resizing
                $script:WpfWindow.ResizeMode = "NoResize"
            }
        }

        # -------------------------
        # TopMost control
        # -------------------------
        if ($PSBoundParameters.ContainsKey("TopMost")) {
            $script:WpfWindow.Topmost = $TopMost
        }

        # -------------------------
        # ControlBars logic
        # -------------------------
        if ($ControlBars) {

            switch ($ControlBars) {

                "Visible" {
                    $script:WpfWindow.WindowStyle = "SingleBorderWindow"
                    if (-not $PSBoundParameters.ContainsKey("Resize")) {
                        $script:WpfWindow.ResizeMode = "CanResize"
                    }
                }

                "Hidden" {
                    $script:WpfWindow.WindowStyle = "None"
                    if (-not $PSBoundParameters.ContainsKey("Resize")) {
                        $script:WpfWindow.ResizeMode = "NoResize"
                    }
                }

                "Empty" {
                    # Remove Min/Max/Close + system menu
                    $script:WpfWindow.WindowStyle = "SingleBorderWindow"

                    if (-not $PSBoundParameters.ContainsKey("Resize")) {
                        $script:WpfWindow.ResizeMode = "NoResize"
                    }

                    $hwnd = (New-Object System.Windows.Interop.WindowInteropHelper $script:WpfWindow).Handle

                    $GWL_STYLE = -16
                    $WS_SYSMENU = 0x80000
                    $WS_MINIMIZEBOX = 0x20000
                    $WS_MAXIMIZEBOX = 0x10000

                    $current = [EasyGUI.Native]::GetWindowLong($hwnd, $GWL_STYLE)
                    $newStyle = $current -band (-bnot ($WS_SYSMENU -bor $WS_MINIMIZEBOX -bor $WS_MAXIMIZEBOX))

                    [EasyGUI.Native]::SetWindowLong($hwnd, $GWL_STYLE, $newStyle)
                }

                "None" {
                    $script:WpfWindow.WindowStyle = "None"
                    if (-not $PSBoundParameters.ContainsKey("Resize")) {
                        $script:WpfWindow.ResizeMode = "NoResize"
                    }
                }
            }
        }

    })
}

function Window.AddToggleButton {
    param(
        [Parameter(Position=0)]
        [string]$ID = $null,
        [string]$Label = $null,
        [string]$Foreground = "White",
        [bool]$Checked = $false
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Ensure Inputs hashtable exists
    if (-not ($script:WpfWindow | Get-Member -Name "Inputs")) {
        $script:WpfWindow | Add-Member -NoteProperty -Name "Inputs" -Value @{} -Force
    }

    $panel = New-Object System.Windows.Controls.StackPanel
    $panel.Orientation = "Horizontal"
    $panel.Margin = "5"

    # $textBlock = New-Object System.Windows.Controls.TextBlock
    # $textBlock.Text = $Label
    # $textBlock.Foreground = "White"
    # $textBlock.VerticalAlignment = "Center"
    # $textBlock.Margin = "0,0,15,0"
    # $panel.Children.Add($textBlock)

    if ($Label) {
        $textBlock = New-Object System.Windows.Controls.TextBlock
        $textBlock.Text = $Label
        $textBlock.FontSize = 16
        $textBlock.Margin = "0,0,10,0"
        $textBlock.VerticalAlignment = "Center"
        $textBlock.Foreground = "White"
        $panel.Children.Add($textBlock)
    }
    # 1. Track Setup
    $track = New-Object System.Windows.Controls.Border
    $track.Width = 40; $track.Height = 20; $track.CornerRadius = 10; $track.Cursor = "Hand"
    
    $startHex = if ($Checked) { "#0078D7" } else { "#555555" }
    $track.Background = New-Object System.Windows.Media.SolidColorBrush ([System.Windows.Media.ColorConverter]::ConvertFromString($startHex))

    # 2. Thumb Setup
    $thumb = New-Object System.Windows.Controls.Border
    $thumb.Width = 14; $thumb.Height = 14; $thumb.CornerRadius = 7; $thumb.Background = "White"
    $thumb.VerticalAlignment = "Center"
    $thumb.Margin = if ($Checked) { "22,0,0,0" } else { "2,0,0,0" }
    $thumb.HorizontalAlignment = "Left"
    $track.Child = $thumb

    # 3. Define the Animation Logic as a ScriptBlock
    $animBlock = {
        param($tTrack, $tThumb, [bool]$state)
        
        $duration = [System.Windows.Duration][TimeSpan]::FromSeconds(0.15)
        
        # Margin Animation
        $marginTo = if ($state) { "22,0,0,0" } else { "2,0,0,0" }
        $mAnim = New-Object System.Windows.Media.Animation.ThicknessAnimation
        $mAnim.To = [System.Windows.Thickness]$marginTo
        $mAnim.Duration = $duration
        
        # Color Animation
        $colorHex = if ($state) { "#0078D7" } else { "#555555" }
        $cAnim = New-Object System.Windows.Media.Animation.ColorAnimation
        $cAnim.To = [System.Windows.Media.ColorConverter]::ConvertFromString($colorHex)
        $cAnim.Duration = $duration

        $tThumb.BeginAnimation([System.Windows.Controls.Border]::MarginProperty, $mAnim)
        $tTrack.Background.BeginAnimation([System.Windows.Media.SolidColorBrush]::ColorProperty, $cAnim)
    }

    # 4. Attach logic and state to the track so it's always accessible
    $track | Add-Member -NotePropertyName "AnimLogic" -NotePropertyValue $animBlock
    $track | Add-Member -NotePropertyName "IsChecked" -NotePropertyValue $Checked

    # Mouse Click Event
    $track.Add_MouseDown({
        # Toggle state
        $this.IsChecked = -not $this.IsChecked
        # Run the logic attached to 'this'
        & $this.AnimLogic $this $this.Child $this.IsChecked
    }.GetNewClosure())

    # 5. ID Registration
    if ($ID) {
        $stateObj = New-Object PSObject
        $stateObj | Add-Member -MemberType ScriptProperty -Name "Value" -Value {
            return [bool]$track.IsChecked
        }.GetNewClosure() -SecondValue {
            $goal = [bool]$args[0]
            if ($track.IsChecked -ne $goal) {
                $track.IsChecked = $goal
                & $track.AnimLogic $track $thumb $goal
            }
        }.GetNewClosure()

        $entry = New-Object PSObject
        $entry | Add-Member -NotePropertyName "State" -NotePropertyValue $stateObj
        $script:WpfWindow.Inputs[$ID] = $entry
    }

    $panel.Children.Add($track)
    $script:Stack.Children.Add($panel) | Out-Null
}

function Window.SetID {
param(
    [Parameter(Mandatory)]
    $ID,
    [Parameter(Mandatory)]
    $Value,
    $DoubleID

)

if (-not $script:Options.ContainsKey($ID)) {
    Write-Verbose "Legacy Inputs Does not exist"
}
$entry = $script:Options[$ID]

    # Radio group
    if ($DoubleID) {
        if ($entry -is [hashtable] -and $entry.ContainsKey($ID)) {
            if ($entry -is [hashtable] -and $entry.ContainsKey($DoubleID)) {
            $entry[$DoubleID].Checked = $Value
            return $true
            }
        }
    }
    # Checkbox
    if ($entry -is [hashtable] -and $entry.ContainsKey('Checked')) {
        $entry.Checked = $Value
        return $true
    }

    # Modern Inputs

    if (-not $script:WpfWindow.Inputs.ContainsKey($ID)) {
        Write-Verbose "Modern Inputs Does not exist"
        return $false
    }

    $entry = $script:WpfWindow.Inputs[$ID]

    
    # Some GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Value')) {
        $entry.Value = $Value
        return $true
    }
    
    # DropDown
    if ($entry -is [hashtable] -and $entry.ContainsKey('SelectedItem')) {
        $entry.SelectedItem = $Value
        return $true
    }
    
    # Input Box and Text Related GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Text')) {
        $entry.Text = $Value
        return $true
    }
    
    # Media and File related GUI Functions
    if ($entry -is [hashtable] -and $entry.ContainsKey('Source')) {
        $entry.Source = $Value
        return $true
    }
    # Numeric Up Down and more
    if ($entry -is [hashtable] -and $entry.ContainsKey('State')) {
        if ($entry.State -is [hashtable] -and $entry.ContainsKey('Value')) {
            $entry.State.Value = $Value
            return $true
        }
    }
    EasyGUI.ErrorOut "ID '$ID' is either broken or not supported."
    return $false
}

function Window.AddDatePicker {
    param(
        [Parameter(Position=0)]
        [string]$ID = $null,
        [string]$Label = "Select Date",
        [datetime]$SelectedDate = (Get-Date)
    )

    if (-not $script:Stack) { Write-Host "GUI not initialized"; return }

    # Setup Panel & Label
    $panel = New-Object System.Windows.Controls.StackPanel -Property @{ Orientation = "Horizontal"; Margin = "5" }
    $textBlock = New-Object System.Windows.Controls.TextBlock -Property @{
        Text = $Label; Foreground = "White"; VerticalAlignment = "Center"; Width = 120; Margin = "0,0,15,0"
    }
    $panel.Children.Add($textBlock)

    # DatePicker Control
    $datePicker = New-Object System.Windows.Controls.DatePicker
    $datePicker.SelectedDate = $SelectedDate
    $datePicker.Width = 150
    $panel.Children.Add($datePicker)

    # REGISTER ID - Using Hashtable to satisfy your GetID check
    if ($ID) {
        $entry = @{}
        
        $entryObject = New-Object PSObject
        $entryObject | Add-Member -MemberType ScriptProperty -Name "Value" -Value {
            return $datePicker.SelectedDate
        }.GetNewClosure() -SecondValue {
            param($NewDate)
            # 1. Update the underlying data
            $datePicker.SelectedDate = [datetime]$NewDate
            
            # 2. FORCE the GUI to refresh the specific control
            $datePicker.Dispatcher.Invoke([System.Windows.Threading.DispatcherPriority]::Render, [action]{
                $datePicker.InvalidateVisual()
            })
        }.GetNewClosure()

        $entry["Value"] = $entryObject.Value
        $entry["Control"] = $datePicker

        $script:WpfWindow.Inputs[$ID] = $entry
    }

    $script:Stack.Children.Add($panel) | Out-Null
}

Write-Verbose "All EasyGUI Functions Sucessfully loaded."