<#
.SYNOPSIS
NetIQ DRA Administrative GUI Tool

.DESCRIPTION
This PowerShell GUI application was developed to simplify and streamline the use of custom
PowerShell functions designed to interact with NetIQ Directory and Resource Administrator (DRA).

These functions abstract and simplify the underlying NetIQ DRA PowerShell cmdlets, which utilize
REST API calls to communicate with DRA servers.

The GUI provides an intuitive interface for administrators, reducing complexity and improving
efficiency when performing common administrative tasks.

.AUTHOR
SMSgt Michael A. Gates

.CONTACT
Commercial: 609.754.5217
DSN:        312.650.5217
Email:      michael.gates.1@us.af.mil

.NOTES
Version: 1.0
Organization: 514th SCO / USAF

This tool is intended for internal administrative use.
#>
# ==============================================================================
# 514 MSG SCO Administration Tool - Main Controller
# ==============================================================================

# 1. Determine the root directory of the application dynamically
$AppRoot = $PSScriptRoot
if (-not $AppRoot) { $AppRoot = Split-Path -Parent $MyInvocation.MyCommand.Path }
$Global:AppRoot = $AppRoot

# Define subfolder paths
$ScriptsPath = Join-Path $AppRoot "ps1"
$UiPath      = Join-Path $AppRoot "ui"
$ConfigPath  = Join-Path $AppRoot "config"

# Load assemblies used by startup, dialogs, and execute-button UI refreshes.
Add-Type -AssemblyName System.Windows.Forms

# 2. Dot-source (load) all PowerShell functions from the domain sub-folders
$functionFolders = @('Computer_Functions', 'User_Functions', 'Group_Functions', 'Utilities')
foreach ($folder in $functionFolders) {
    $folderPath = Join-Path $ScriptsPath $folder
    if (Test-Path $folderPath) {
        Get-ChildItem -Path $folderPath -Filter "*.ps1" | ForEach-Object {
            . $_.FullName
        }
    }
}

# 3. Load the Configuration JSON
$configFile = Join-Path $ConfigPath "config.json"
if (Test-Path $configFile) {
    $Global:AppConfig = Get-Content -Path $configFile | ConvertFrom-Json
} else {
    [System.Windows.MessageBox]::Show("Could not find config.json in the config folder!", "Error", "OK", "Error")
    exit
}

# Apply defaults for newer config sections if they do not yet exist
if (-not $Global:AppConfig.PSObject.Properties.Name.Contains('CiscoISE')) {
    $Global:AppConfig | Add-Member -NotePropertyName CiscoISE -NotePropertyValue ([pscustomobject]@{
        PrimaryUrl = 'https://132.18.178.115'
        BackupUrl  = 'https://132.18.178.116'
    })
}
if (-not $Global:AppConfig.PSObject.Properties.Name.Contains('Inventory')) {
    $Global:AppConfig | Add-Member -NotePropertyName Inventory -NotePropertyValue ([pscustomobject]@{
        FileName = 'Computer_Inventory_GM.xlsx'
    })
}
if (-not $Global:AppConfig.PSObject.Properties.Name.Contains('UDrive')) {
    $Global:AppConfig | Add-Member -NotePropertyName UDrive -NotePropertyValue ([pscustomobject]@{
        Roots = @(
            "\\ptfm-stm-san3v\U_Drives\76ARS",
            "\\ptfm-stm-san3v\U_Drives\78ARS",
            "\\ptfm-stm-san3v\U_Drives\514MSG",
            "\\ptfm-stm-san3v\U_Drives\514MXG",
            "\\ptfm-stm-san3v\U_Drives\514OG",
            "\\ptfm-stm-san3v\U_Drives\514WG",
            "\\ptfm-stm-san3v\U_Drives\714 AMXS",
            "\\ptfm-stm-san3v\U_Drives\732AS"
        )
        OrganizationMappings = [pscustomobject]@{
            '88 APS' = '514MSG'; '35 APS' = '514MSG'; '514 MSG' = '514MSG'; '514 FSS' = '514MSG'; '514 CES' = '514MSG'; '514 SFS' = '514MSG'; '514 LRS' = '514MSG'
            '514 AW' = '514WG'; '514 AMW' = '514WG'; '514 AMDS' = '514WG'; '514 AMOS' = '514WG'; '514 RS' = '514WG'; '514 NRS' = '514WG'
            '514 INT' = '514OG'; '514 OSS' = '514OG'; '514 OG' = '514OG'; '732 AS' = '514OG'; '514 AES' = '514OG'; '514 ASTS' = '514OG'; '78 ARS' = '514OG'; '76 ARS' = '514OG'
            '514 MXG' = '514MXG'; '514 MXS' = '514MXG'; '514 AMXS' = '514MXG'
        }
    })
}
if (-not $Global:AppConfig.PSObject.Properties.Name.Contains('UI')) {
    $Global:AppConfig | Add-Member -NotePropertyName UI -NotePropertyValue ([pscustomobject]@{
        Mode     = 'Hamburger'
        AutoSize = $true
        Width    = 1400
        Height   = 900
        Simulation = $false
        Theme     = 'Light'
        StartTab   = 'Last Active'
        LastActiveTab = 'Computer'
    })
}
if (-not $Global:AppConfig.UI.PSObject.Properties.Name.Contains('Theme')) {
    $Global:AppConfig.UI | Add-Member -NotePropertyName Theme -NotePropertyValue 'Light'
}

# 4. Load the XAML GUI (select file by UI mode in config)
$uiMode = $Global:AppConfig.UI.Mode
if ($uiMode -eq 'AppBar') {
    $xamlFile = Join-Path $UiPath "SCOAdminTool_AppBar.xaml"
} else {
    $xamlFile = Join-Path $UiPath "SCOAdminTool.xaml"
}
if (-not (Test-Path $xamlFile)) {
    [System.Windows.MessageBox]::Show("Could not find $xamlFile in the ui folder!", "Error", "OK", "Error")
    exit
}

# Use your custom function to build the window and auto-generate control variables
$guiData = New-XamlWindow -XamlFile $xamlFile
$MainWindow = $guiData[0]

# Set the Window Title from the JSON config to prove the config loaded successfully
$MainWindow.Title = $Global:AppConfig.AppTitle

# Apply window sizing from config
if ($Global:AppConfig.UI.AutoSize -eq $true) {
    $scale = 0.80
    $MainWindow.Width  = [System.Windows.SystemParameters]::PrimaryScreenWidth  * $scale
    $MainWindow.Height = [System.Windows.SystemParameters]::PrimaryScreenHeight * $scale
} else {
    $MainWindow.Width  = $Global:AppConfig.UI.Width
    $MainWindow.Height = $Global:AppConfig.UI.Height
}

function Set-ThemeResourceBrush {
    param(
        [Parameter(Mandatory=$true)][string]$Key,
        [Parameter(Mandatory=$true)][string]$Color
    )

    $themeColor = [System.Windows.Media.ColorConverter]::ConvertFromString($Color)
    $themeResources = $MainWindow.Resources.MergedDictionaries |
        Where-Object { $_.Contains($Key) } |
        Select-Object -First 1

    $brush = if ($themeResources) { $themeResources[$Key] } else { $MainWindow.Resources[$Key] }
    if ($brush -is [System.Management.Automation.PSObject]) {
        $brush = $brush.BaseObject
    }

    if ($brush -is [System.Windows.Media.SolidColorBrush] -and -not $brush.IsFrozen) {
        $brush.Color = $themeColor
        return
    }

    $themeBrush = [System.Windows.Media.SolidColorBrush]::new($themeColor)
    if ($themeResources) {
        $themeResources.Remove($Key)
        $themeResources.Add($Key, [System.Windows.Media.Brush]$themeBrush)
    } else {
        $MainWindow.Resources.Remove($Key)
        $MainWindow.Resources.Add($Key, [System.Windows.Media.Brush]$themeBrush)
    }
}

function Update-ThemeSensitiveControls {
    $backgroundBrush = $MainWindow.FindResource('ControlBackgroundBrush')
    $foregroundBrush = $MainWindow.FindResource('ControlForegroundBrush')
    $borderBrush = $MainWindow.FindResource('ControlBorderBrush')

    foreach ($control in @($CmbOrg, $TxtDesc, $TxtInvStatus)) {
        if (-not $control) { continue }
        $control.Background = $backgroundBrush
        $control.Foreground = $foregroundBrush
        $control.BorderBrush = $borderBrush
    }

    if ($CmbOrg) {
        $CmbOrg.ApplyTemplate()
        $editableTextBox = $CmbOrg.Template.FindName('PART_EditableTextBox', $CmbOrg)
        if ($editableTextBox) {
            $editableTextBox.Background = $backgroundBrush
            $editableTextBox.Foreground = $foregroundBrush
            $editableTextBox.BorderBrush = $borderBrush
        }
    }
}

function Set-AppTheme {
    param([string]$Theme)

    $normalizedTheme = if ($Theme -eq 'Dark') { 'Dark' } else { 'Light' }
    if ($normalizedTheme -eq 'Dark') {
        Set-ThemeResourceBrush -Key 'AppBackgroundBrush' -Color '#1E1E1E'
        Set-ThemeResourceBrush -Key 'PanelBackgroundBrush' -Color '#252526'
        Set-ThemeResourceBrush -Key 'AppBarBackgroundBrush' -Color '#2D2D30'
        Set-ThemeResourceBrush -Key 'ControlBackgroundBrush' -Color '#333337'
        Set-ThemeResourceBrush -Key 'ControlForegroundBrush' -Color '#F1F1F1'
        Set-ThemeResourceBrush -Key 'ControlBorderBrush' -Color '#5F5F66'
        Set-ThemeResourceBrush -Key 'MutedForegroundBrush' -Color '#B8B8B8'
        Set-ThemeResourceBrush -Key 'AccentBrush' -Color '#0E8FE8'
        Set-ThemeResourceBrush -Key 'AccentDarkBrush' -Color '#0A6FB6'
        Set-ThemeResourceBrush -Key 'ComboBoxItemHighlightBrush' -Color '#0A5A95'
        Set-ThemeResourceBrush -Key 'ComboBoxItemHoverBrush' -Color '#0D73BF'
        Set-ThemeResourceBrush -Key 'SwitchTrackOffBrush' -Color '#5C636A'
        Set-ThemeResourceBrush -Key 'SwitchTrackOffBorderBrush' -Color '#7A828A'
        Set-ThemeResourceBrush -Key 'SwitchThumbBrush' -Color '#FFFFFF'
    } else {
        Set-ThemeResourceBrush -Key 'AppBackgroundBrush' -Color '#F0F0F0'
        Set-ThemeResourceBrush -Key 'PanelBackgroundBrush' -Color '#FFFFFF'
        Set-ThemeResourceBrush -Key 'AppBarBackgroundBrush' -Color '#2D2D30'
        Set-ThemeResourceBrush -Key 'ControlBackgroundBrush' -Color '#FFFFFF'
        Set-ThemeResourceBrush -Key 'ControlForegroundBrush' -Color '#111111'
        Set-ThemeResourceBrush -Key 'ControlBorderBrush' -Color '#555555'
        Set-ThemeResourceBrush -Key 'MutedForegroundBrush' -Color '#666666'
        Set-ThemeResourceBrush -Key 'AccentBrush' -Color '#007ACC'
        Set-ThemeResourceBrush -Key 'AccentDarkBrush' -Color '#005A9C'
        Set-ThemeResourceBrush -Key 'ComboBoxItemHighlightBrush' -Color '#003E6B'
        Set-ThemeResourceBrush -Key 'ComboBoxItemHoverBrush' -Color '#005A9C'
        Set-ThemeResourceBrush -Key 'SwitchTrackOffBrush' -Color '#ADB5BD'
        Set-ThemeResourceBrush -Key 'SwitchTrackOffBorderBrush' -Color '#87919B'
        Set-ThemeResourceBrush -Key 'SwitchThumbBrush' -Color '#FFFFFF'
    }

    $Global:AppConfig.UI.Theme = $normalizedTheme
    if ($ChkDarkMode) {
        $ChkDarkMode.IsChecked = $normalizedTheme -eq 'Dark'
    }
    Update-ThemeSensitiveControls
}

Set-AppTheme -Theme $Global:AppConfig.UI.Theme

function Get-CurrentAccountTypeInfo {
    $username = [string]$env:USERNAME
    $suffix = if ($username.Length -ge 4) {
        $username.Substring($username.Length - 4).ToUpperInvariant()
    } else {
        $username.ToUpperInvariant()
    }

    $type = switch ($suffix) {
        '.ADW' { 'ADW'; break }
        '.ADF' { 'ADF'; break }
        default { 'USER' }
    }

    $displayName = ''
    $getAdUserCommand = Get-Command -Name Get-ADUser -ErrorAction SilentlyContinue
    if ($getAdUserCommand) {
        try {
            $displayName = [string](Get-ADUser -Identity $username -Properties DisplayName -ErrorAction Stop).DisplayName
        } catch {
            $displayName = ''
        }
    }

    [pscustomobject]@{
        Type        = $type
        Username    = $username
        DisplayName = $displayName
    }
}

function Set-AccountTypeTag {
    if (-not $BdrAccountTypeTag -or -not $TxtAccountTypeTag) { return }

    $accountInfo = Get-CurrentAccountTypeInfo
    $script:CurrentUserType = $accountInfo.Type
    $TxtAccountTypeTag.Text = $accountInfo.Type

    $BdrAccountTypeTag.Background = switch ($accountInfo.Type) {
        'ADW' { [System.Windows.Media.Brushes]::ForestGreen }
        'ADF' { [System.Windows.Media.Brushes]::RoyalBlue }
        default { [System.Windows.Media.Brushes]::DimGray }
    }

    $role = switch ($accountInfo.Type) {
        'ADW' { 'Workstation Admin' }
        'ADF' { 'Server Admin' }
        default { 'Normal User' }
    }

    $tooltip = "$role account"
    if (-not [string]::IsNullOrWhiteSpace($accountInfo.DisplayName)) {
        $tooltip = "$tooltip - $($accountInfo.DisplayName)"
    }
    $BdrAccountTypeTag.ToolTip = $tooltip
}

Set-AccountTypeTag

# ==============================================================================

# ==============================================================================
# UI Mode: Hamburger — wire drawer and tab buttons
# ==============================================================================
if ($uiMode -eq 'Hamburger') {
    # Pre-populate settings controls with current config values
    $TxtWindowWidth.Text  = [string]$Global:AppConfig.UI.Width
    $TxtWindowHeight.Text = [string]$Global:AppConfig.UI.Height
    $ChkAutoSize.IsChecked = $Global:AppConfig.UI.AutoSize -eq $true

    # Pre-populate simulation toggle
    $ChkSimulation.IsChecked = $Global:AppConfig.UI.Simulation -eq $true
    $script:SimulationMode = $ChkSimulation.IsChecked
    $ChkSimulation.add_Checked({ $script:SimulationMode = $true; Initialize-ToolADSimulationData; $Global:AppConfig.UI.Simulation = $true; Save-AppConfig })
    $ChkSimulation.add_Unchecked({ $script:SimulationMode = $false; $Global:AppConfig.UI.Simulation = $false; Save-AppConfig })

    $ChkDarkMode.IsChecked = $Global:AppConfig.UI.Theme -eq 'Dark'
    $ChkDarkMode.add_Checked({ Set-AppTheme -Theme 'Dark'; Save-AppConfig })
    $ChkDarkMode.add_Unchecked({ Set-AppTheme -Theme 'Light'; Save-AppConfig })

    # Set dropdowns to current config values
    foreach ($item in $CmbUIMode.Items) {
        if ($item.Content -eq $uiMode) { $CmbUIMode.SelectedItem = $item; break }
    }
    foreach ($item in $CmbStartTab.Items) {
        if ($item.Content -eq $Global:AppConfig.UI.StartTab) { $CmbStartTab.SelectedItem = $item; break }
    }

    # Start tab change — save to config
    $CmbStartTab.add_SelectionChanged({
        $Global:AppConfig.UI.StartTab = $CmbStartTab.SelectedItem.Content
        Save-AppConfig
    })

    # UI mode change — warn, save, restart
    $CmbUIMode.add_SelectionChanged({
        $selected = $CmbUIMode.SelectedItem.Content
        if ($selected -ne $Global:AppConfig.UI.Mode) {
            $result = [System.Windows.MessageBox]::Show(
                "Changing UI mode to '$selected' will restart the application. Continue?",
                "UI Mode Change", "YesNo", "Warning"
            )
            if ($result -eq 'Yes') {
                $Global:AppConfig.UI.Mode = $selected
                Save-AppConfig
                # Restart the application
                $scriptPath = Join-Path $AppRoot "main.ps1"
                Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
                $MainWindow.Close()
            } else {
                # Reset dropdown to current mode
                foreach ($item in $CmbUIMode.Items) {
                    if ($item.Content -eq $Global:AppConfig.UI.Mode) { $CmbUIMode.SelectedItem = $item; break }
                }
            }
        }
    })

    # Toggle hamburger drawer (slides from left)
    $BtnHamburger.add_Click({
        if ($DrawerPanel.Margin.Left -lt 0) {
            $DrawerPanel.Margin = '0,0,0,0'
            $DrawerOverlay.Visibility = 'Visible'
        } else {
            $DrawerPanel.Margin = '-320,0,0,0'
            $DrawerOverlay.Visibility = 'Collapsed'
        }
    })

    # Click the dark overlay to close drawer
    $DrawerOverlay.add_MouseLeftButtonDown({
        $DrawerPanel.Margin = '-320,0,0,0'
        $DrawerOverlay.Visibility = 'Collapsed'
    })

    # Tab buttons — toggle Computer/User panels (save last active)
    $BtnTabComputer.add_Click({
        $BtnTabComputer.Background = '#2D5080'; $BtnTabComputer.Foreground = 'White'
        $BtnTabUser.Background     = 'Transparent'; $BtnTabUser.Foreground = '#AABBCC'
        $BtnTabGroup.Background    = 'Transparent'; $BtnTabGroup.Foreground = '#AABBCC'
        $PanelComputer.Visibility  = 'Visible'
        $PanelUser.Visibility      = 'Collapsed'
        $PanelGroup.Visibility     = 'Collapsed'
        Set-ActiveTab -TabName 'Computer'
    })
    $BtnTabUser.add_Click({
        $BtnTabUser.Background     = '#2D5080'; $BtnTabUser.Foreground = 'White'
        $BtnTabComputer.Background = 'Transparent'; $BtnTabComputer.Foreground = '#AABBCC'
        $BtnTabGroup.Background    = 'Transparent'; $BtnTabGroup.Foreground = '#AABBCC'
        $PanelComputer.Visibility  = 'Collapsed'
        $PanelUser.Visibility      = 'Visible'
        $PanelGroup.Visibility     = 'Collapsed'
        Set-ActiveTab -TabName 'User'
    })



    $BtnTabGroup.add_Click({
        $BtnTabGroup.Background     = '#2D5080'; $BtnTabGroup.Foreground     = 'White'
        $BtnTabComputer.Background  = 'Transparent'; $BtnTabComputer.Foreground = '#AABBCC'
        $BtnTabUser.Background      = 'Transparent'; $BtnTabUser.Foreground     = '#AABBCC'
        $PanelComputer.Visibility   = 'Collapsed'
        $PanelUser.Visibility       = 'Collapsed'
        $PanelGroup.Visibility      = 'Visible'
        Set-ActiveTab -TabName 'Group'
    })
    # Save settings from drawer controls
    function Update-UIFromDrawer {
        $Global:AppConfig.UI.AutoSize = $ChkAutoSize.IsChecked
        $Global:AppConfig.UI.Width    = [int]$TxtWindowWidth.Text
        $Global:AppConfig.UI.Height   = [int]$TxtWindowHeight.Text
        $Global:AppConfig.Development = $ChkDevMode.IsChecked
        $Global:AppConfig.NamingConvention.BasePrefix        = $TxtPrefix.Text.Trim().ToUpper()
        $Global:AppConfig.NamingConvention.SerialNumberDigits = [int]$TxtSnDigits.Text
        Save-AppConfig
    }
    $ChkAutoSize.add_Checked({ Update-UIFromDrawer })
    $ChkAutoSize.add_Unchecked({ Update-UIFromDrawer })
    $TxtWindowWidth.add_LostFocus({ Update-UIFromDrawer })
    $TxtWindowHeight.add_LostFocus({ Update-UIFromDrawer })
    $BtnShortNameMap.add_Click({ Open-ShortNameMapEditor })
}

# ==============================================================================
# UI Mode: App Bar — wire settings panel
# ==============================================================================
if ($uiMode -eq 'AppBar') {
    $TxtWindowWidth.Text  = [string]$Global:AppConfig.UI.Width
    $TxtWindowHeight.Text = [string]$Global:AppConfig.UI.Height
    $ChkAutoSize.IsChecked = $Global:AppConfig.UI.AutoSize -eq $true
    $SettingsPanel.Visibility = 'Collapsed'

    # Pre-populate simulation toggle
    $ChkSimulation.IsChecked = $Global:AppConfig.UI.Simulation -eq $true
    $script:SimulationMode = $ChkSimulation.IsChecked
    $ChkSimulation.add_Checked({ $script:SimulationMode = $true; Initialize-ToolADSimulationData; $Global:AppConfig.UI.Simulation = $true; Save-AppConfig })
    $ChkSimulation.add_Unchecked({ $script:SimulationMode = $false; $Global:AppConfig.UI.Simulation = $false; Save-AppConfig })

    $ChkDarkMode.IsChecked = $Global:AppConfig.UI.Theme -eq 'Dark'
    $ChkDarkMode.add_Checked({ Set-AppTheme -Theme 'Dark'; Save-AppConfig })
    $ChkDarkMode.add_Unchecked({ Set-AppTheme -Theme 'Light'; Save-AppConfig })

    # Set dropdowns to current config values
    foreach ($item in $CmbUIMode.Items) {
        if ($item.Content -eq $uiMode) { $CmbUIMode.SelectedItem = $item; break }
    }
    foreach ($item in $CmbStartTab.Items) {
        if ($item.Content -eq $Global:AppConfig.UI.StartTab) { $CmbStartTab.SelectedItem = $item; break }
    }
    $CmbStartTab.add_SelectionChanged({
        $Global:AppConfig.UI.StartTab = $CmbStartTab.SelectedItem.Content
        Save-AppConfig
    })

    # Set UI mode dropdown to current value
    foreach ($item in $CmbUIMode.Items) {
        if ($item.Content -eq $uiMode) { $CmbUIMode.SelectedItem = $item; break }
    }

    # UI mode change — warn, save, restart
    $CmbUIMode.add_SelectionChanged({
        $selected = $CmbUIMode.SelectedItem.Content
        if ($selected -ne $Global:AppConfig.UI.Mode) {
            $result = [System.Windows.MessageBox]::Show(
                "Changing UI mode to '$selected' will restart the application. Continue?",
                "UI Mode Change", "YesNo", "Warning"
            )
            if ($result -eq 'Yes') {
                $Global:AppConfig.UI.Mode = $selected
                Save-AppConfig
                $scriptPath = Join-Path $AppRoot "main.ps1"
                Start-Process powershell.exe -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""
                $MainWindow.Close()
            } else {
                foreach ($item in $CmbUIMode.Items) {
                    if ($item.Content -eq $Global:AppConfig.UI.Mode) { $CmbUIMode.SelectedItem = $item; break }
                }
            }
        }
    })

    $BtnSettings.add_Click({
        if ($SettingsPanel.Visibility -eq 'Visible') {
            $SettingsPanel.Visibility = 'Collapsed'
        } else {
            $SettingsPanel.Visibility = 'Visible'
        }
    })
    $BtnCloseSettings.add_Click({
        $SettingsPanel.Visibility = 'Collapsed'
    })

    function Update-UIFromSettings {
        $Global:AppConfig.UI.AutoSize = $ChkAutoSize.IsChecked
        $Global:AppConfig.UI.Width    = [int]$TxtWindowWidth.Text
        $Global:AppConfig.UI.Height   = [int]$TxtWindowHeight.Text
        Save-AppConfig
    }
    $ChkAutoSize.add_Checked({ Update-UIFromSettings })
    $ChkAutoSize.add_Unchecked({ Update-UIFromSettings })
    $TxtWindowWidth.add_LostFocus({ Update-UIFromSettings })
    $TxtWindowHeight.add_LostFocus({ Update-UIFromSettings })
    $BtnShortNameMap.add_Click({ Open-ShortNameMapEditor })
}

# ==============================================================================
# USER MANAGEMENT TAB — event wiring
# ==============================================================================

# Helper: lookup user in DB
function Find-UserRecord {
    param([string]$SearchValue)
    if ([string]::IsNullOrWhiteSpace($SearchValue)) { return $null }

    if ($script:SimulationMode) {
        try {
            $adUser = Get-ToolADUser -Identity $SearchValue -ErrorAction Stop
            return ConvertTo-UserDisplayRecord -User $adUser
        } catch {
            return $null
        }
    }

    return $script:UserDbLookup[$SearchValue.Trim().ToUpper()]
}

function ConvertTo-UserDisplayRecord {
    param($User)

    if ($null -eq $User) { return $null }

    if ($User.PSObject.Properties.Name -contains 'DOD_ID') { return $User }

    $email = if ($User.EmailAddress) { $User.EmailAddress } else { $User.mail }
    $phone = if ($User.OfficePhone) { $User.OfficePhone } else { $User.telephoneNumber }
    $base = if ($User.l) { $User.l } elseif ($User.City) { $User.City } else { '' }
    $rank = if ($User.personalTitle) { $User.personalTitle } elseif ($User.payGrade) { $User.payGrade } else { '' }
    $displayName = if ($User.DisplayName) {
        [string]$User.DisplayName
    } elseif ($User.Surname -or $User.GivenName) {
        "$($User.Surname), $($User.GivenName)".Trim(' ', ',')
    } else {
        [string]$User.Name
    }

    [pscustomobject]@{
        FULL_NAME      = $displayName
        DISPLAY_NAME   = $displayName
        DOD_ID         = [string]$User.EmployeeID
        PIV            = [string]$User.employeeType
        GRADE_DESC     = $rank
        UNIT           = [string]$User.Organization
        BASE           = [string]$base
        EMAIL_ADDRESS  = [string]$email
        HOME_PHONE     = [string]$phone
        OFFICE         = [string]$User.Office
        MAJCOM         = [string]$User.Department
        IA_TRAINING_DATE = $User.iaTrainingDate
        HOME_DIRECTORY = [string]$User.HomeDirectory
        HOME_DRIVE     = [string]$User.HomeDrive
        ENABLED        = $User.Enabled
        LOCKED_OUT     = $User.LockedOut
        USER_PRINCIPAL_NAME = [string]$User.UserPrincipalName
        SamAccountName = [string]$User.SamAccountName
        ADRecord       = $User
    }
}

function ConvertTo-ToolBoolean {
    param($Value)

    if ($null -eq $Value) { return $null }
    if ($Value -is [bool]) { return [bool]$Value }

    switch -Regex ([string]$Value) {
        '^(?i:true|1|yes|enabled)$' { return $true }
        '^(?i:false|0|no|disabled)$' { return $false }
        default { return $null }
    }
}

function Format-ToolADDateValue {
    param($Value)

    $date = ConvertFrom-ToolADDateValue -Value $Value
    if ($date) { return $date.ToString('yyyy-MM-dd') }

    if ($null -eq $Value) { return '' }
    $text = [string]$Value
    if ([string]::IsNullOrWhiteSpace($text)) { return '' }
    return $text
}

function ConvertFrom-ToolADDateValue {
    param($Value)

    if ($null -eq $Value) { return '' }
    if ($Value -is [datetime]) { return $Value }

    $text = [string]$Value
    if ([string]::IsNullOrWhiteSpace($text)) { return '' }

    if ($text -match 'Date\((-?\d+)\)') {
        try {
            return ([DateTimeOffset]::FromUnixTimeMilliseconds([int64]$Matches[1])).LocalDateTime
        } catch {
            return $null
        }
    }

    try {
        return [datetime]::Parse($text)
    } catch {
        return $null
    }
}

function Set-TextControlValue {
    param($Control, $Value)

    if (-not $Control) { return }
    $text = if ($null -eq $Value) { '' } else { [string]$Value }
    $Control.Text = Format-OutputPath $text
}

function Format-OutputPath {
    param($Path)

    if ($null -eq $Path) { return '' }
    $text = [string]$Path
    return [regex]::Replace($text, '\\{2,}', {
        param($match)
        $count = [int][Math]::Ceiling($match.Value.Length / 2)
        if ($match.Index -eq 0) { $count = [Math]::Max(2, $count) }
        return '\' * $count
    })
}

function Set-UserStatusTags {
    param($Record)

    if ($BdrUserEnabledTag) { $BdrUserEnabledTag.Visibility = 'Collapsed' }
    if ($BdrUserLockedTag) { $BdrUserLockedTag.Visibility = 'Collapsed' }
    if ($TxtUserFullName) { $TxtUserFullName.Foreground = [System.Windows.Media.Brushes]::Black }
    if ($null -eq $Record) { return }

    $enabled = ConvertTo-ToolBoolean -Value $Record.ENABLED
    $lockedOut = ConvertTo-ToolBoolean -Value $Record.LOCKED_OUT

    if ($BdrUserEnabledTag -and $TxtUserEnabledTag) {
        $BdrUserEnabledTag.Visibility = 'Visible'
        if ($enabled -eq $false) {
            $BdrUserEnabledTag.Background = [System.Windows.Media.Brushes]::Firebrick
            $TxtUserEnabledTag.Text = 'DISABLED'
        } else {
            $BdrUserEnabledTag.Background = [System.Windows.Media.Brushes]::ForestGreen
            $TxtUserEnabledTag.Text = 'ENABLED'
        }
    }

    if ($BdrUserLockedTag -and $TxtUserLockedTag -and $lockedOut -eq $true) {
        $BdrUserLockedTag.Visibility = 'Visible'
        $BdrUserLockedTag.Background = [System.Windows.Media.Brushes]::DarkGoldenrod
        $TxtUserLockedTag.Text = 'LOCKED'
    }
}

function Set-IATrainingTag {
    param($TrainingDateValue)

    if ($BdrIATrainingTag) { $BdrIATrainingTag.Visibility = 'Collapsed' }
    if (-not $BdrIATrainingTag -or -not $TxtIATrainingTag) { return }

    $trainingDate = ConvertFrom-ToolADDateValue -Value $TrainingDateValue
    if (-not $trainingDate) { return }

    $expires = $trainingDate.Date.AddYears(1)
    $daysLeft = [int][Math]::Ceiling(($expires - (Get-Date).Date).TotalDays)
    $BdrIATrainingTag.Visibility = 'Visible'

    if ($daysLeft -gt 90) {
        $BdrIATrainingTag.Background = [System.Windows.Media.Brushes]::ForestGreen
        $TxtIATrainingTag.Text = "$daysLeft DAYS"
    } elseif ($daysLeft -gt 0) {
        $BdrIATrainingTag.Background = [System.Windows.Media.Brushes]::DarkGoldenrod
        $TxtIATrainingTag.Text = "$daysLeft DAYS"
    } elseif ($daysLeft -eq 0) {
        $BdrIATrainingTag.Background = [System.Windows.Media.Brushes]::Firebrick
        $TxtIATrainingTag.Text = 'EXPIRES TODAY'
    } else {
        $BdrIATrainingTag.Background = [System.Windows.Media.Brushes]::Firebrick
        $TxtIATrainingTag.Text = "$([Math]::Abs($daysLeft)) DAYS EXPIRED"
    }
}

function Get-GroupNameFromDistinguishedName {
    param([string]$DistinguishedName)

    if ([string]::IsNullOrWhiteSpace($DistinguishedName)) { return '' }
    if ($DistinguishedName -match '^CN=([^,]+)') { return $Matches[1] }
    return $DistinguishedName
}

function Set-UserGroupsFromRecord {
    param($Record)

    if (-not $TxtUserGroups) { return }

    $TxtUserGroups.Items.Clear()
    if ($null -eq $Record) { return }

    $samAccountName = if ($Record.SamAccountName) { [string]$Record.SamAccountName } else { "$([string]$Record.DOD_ID)$([string]$Record.PIV)" }
    $samAccountName = $samAccountName.Trim().ToUpper()
    if ([string]::IsNullOrWhiteSpace($samAccountName)) { return }

    try {
        $adRecord = Get-ToolADUser -Identity $samAccountName -Properties MemberOf -ErrorAction Stop
        $groups = @($adRecord.MemberOf) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }

        foreach ($groupDn in ($groups | Sort-Object)) {
            [void]$TxtUserGroups.Items.Add([pscustomobject]@{
                GroupName         = Get-GroupNameFromDistinguishedName -DistinguishedName $groupDn
                DistinguishedName = [string]$groupDn
                IsSelected        = $false
                IsEnabled         = $false
            })
        }
    } catch {
        Write-Verbose "Unable to load groups for '$samAccountName': $($_.Exception.Message)"
    }
}

# Helper: display user details
function Set-UserFieldsFromRecord {
    param($Record)
    if ($null -eq $Record) {
        Set-TextControlValue -Control $TxtUserStatus -Value 'No user selected.'
        Set-TextControlValue -Control $TxtUserFullName -Value ''
        Set-TextControlValue -Control $TxtUserDodId -Value ''
        Set-TextControlValue -Control $TxtUserRank -Value ''
        Set-TextControlValue -Control $TxtUserBase -Value ''
        Set-TextControlValue -Control $TxtUserEmail -Value ''
        Set-TextControlValue -Control $TxtUserPhone -Value ''
        Set-TextControlValue -Control $TxtUserUPN -Value ''
        Set-TextControlValue -Control $TxtUserIATrainingDate -Value ''
        Set-TextControlValue -Control $TxtUserHomeDirectory -Value ''
        Set-TextControlValue -Control $TxtUserHomeDrive -Value ''
        Set-TextControlValue -Control $TxtUserEnabled -Value ''
        Set-TextControlValue -Control $TxtUserLockedOut -Value ''
        Set-UserStatusTags -Record $null
        Set-IATrainingTag -TrainingDateValue $null
        Set-UserGroupsFromRecord -Record $null
        return
    }
    $displayName = if ($Record.DISPLAY_NAME) { [string]$Record.DISPLAY_NAME } else { [string]$Record.FULL_NAME }

    Set-TextControlValue -Control $TxtUserStatus -Value 'User found.'
    Set-TextControlValue -Control $TxtUserFullName -Value $displayName
    Set-TextControlValue -Control $TxtUserDodId -Value "$([string]$Record.DOD_ID)$([string]$Record.PIV)"
    Set-TextControlValue -Control $TxtUserRank -Value $Record.GRADE_DESC
    Set-TextControlValue -Control $TxtUserBase -Value $Record.BASE
    Set-TextControlValue -Control $TxtUserEmail -Value $Record.EMAIL_ADDRESS
    Set-TextControlValue -Control $TxtUserPhone -Value $Record.HOME_PHONE
    Set-TextControlValue -Control $TxtUserUPN -Value $Record.USER_PRINCIPAL_NAME
    Set-TextControlValue -Control $TxtUserIATrainingDate -Value (Format-ToolADDateValue -Value $Record.IA_TRAINING_DATE)
    Set-TextControlValue -Control $TxtUserHomeDirectory -Value $Record.HOME_DIRECTORY
    Set-TextControlValue -Control $TxtUserHomeDrive -Value $Record.HOME_DRIVE
    Set-TextControlValue -Control $TxtUserEnabled -Value $Record.ENABLED
    Set-TextControlValue -Control $TxtUserLockedOut -Value $Record.LOCKED_OUT
    Set-UserStatusTags -Record $Record
    Set-IATrainingTag -TrainingDateValue $Record.IA_TRAINING_DATE
    Set-UserGroupsFromRecord -Record $Record
}

# Live search — smart matching (DOD_ID, DOD_ID+PIV, or Name)
$TxtUserInput.add_TextChanged({
    $allText = $TxtUserInput.Text
    $caret = $TxtUserInput.CaretIndex

    # Search only the current line (where cursor is)
    $lineStart = if ($caret -gt 0) { $allText.LastIndexOf("`n", $caret - 1) + 1 } else { 0 }
    $lineEnd   = $allText.IndexOf("`n", $caret)
    if ($lineEnd -lt 0) { $lineEnd = $allText.Length }
    $query = $allText.Substring($lineStart, $lineEnd - $lineStart).Trim()

    if (-not $LstSearchResults) { return }
    Show-UserSearchResults
    $LstSearchResults.Items.Clear()

    if ($query.Length -lt 3) {
        $TxtUserStatus.Text = 'Type 3+ characters to search...'
        return
    }

    $results = @(Search-ToolADUser -Query $query -MaxResults 8 | ForEach-Object { ConvertTo-UserDisplayRecord -User $_ })

    if ($results.Count -eq 0) {
        $TxtUserStatus.Text = 'No matches found.'
        return
    }

    $TxtUserStatus.Text = "$($results.Count) match(es) found."
    foreach ($user in $results) {
        [void]$LstSearchResults.Items.Add([pscustomobject]@{
            Name  = $user.FULL_NAME
            DodId = $user.DOD_ID.ToString()
            Piv   = [string]$user.PIV
        })
    }
})

function Use-UserSearchResult {
    param($SearchResult)

    if (-not $SearchResult) { return }

    $dodIdPiv = $SearchResult.DodId + $SearchResult.Piv
    $record = Find-UserRecord -SearchValue $dodIdPiv
    if (-not $record) { return }

    $newName = "$($record.GRADE_DESC) $($record.FULL_NAME)"
    $text = $TxtUserInput.Text
    $caret = $TxtUserInput.CaretIndex

    # Find start/end of the current line
    $lineStart = if ($caret -gt 0) { $text.LastIndexOf("`n", $caret - 1) + 1 } else { 0 }
    $lineEnd = $text.IndexOf("`n", $caret)
    if ($lineEnd -lt 0) { $lineEnd = $text.Length }

    # Replace current line, then move to next line
    $before = $text.Substring(0, $lineStart)
    $after  = if ($lineEnd -lt $text.Length) { $text.Substring($lineEnd) } else { '' }
    $TxtUserInput.Text = $before + $newName + $after + "`r`n"
    $TxtUserInput.CaretIndex = $TxtUserInput.Text.Length

    # Add selected user directly to target list (avoids name-collision lookup)
    $existing = $LstUserTargets.Items | Where-Object { $_.DodId -eq $record.DOD_ID -and $_.Piv -eq [string]$record.PIV }
    if (-not $existing) {
        [void]$LstUserTargets.Items.Add([pscustomobject]@{
            Name=$record.FULL_NAME; DodId=$record.DOD_ID; Piv=[string]$record.PIV; IsSelected=$true
        })
    }
    Set-UserGetSingleSelection
    Update-UserSelectAllButton
    Update-SelectedGroupsForRemove
    Update-CloneUserFields
    Set-UserFieldsFromRecord -Record $record
}

# Click a search result — replace current line with rank + full name
if ($LstSearchResults) {
    $LstSearchResults.add_SelectionChanged({
        if ($LstSearchResults.SelectedItem) {
            Use-UserSearchResult -SearchResult $LstSearchResults.SelectedItem
            $LstSearchResults.SelectedItem = $null
        }
    })
}

if ($TxtUserInput) {
    $TxtUserInput.add_PreviewKeyDown({
        param($sender, $eventArgs)

        if ($eventArgs.Key -eq [System.Windows.Input.Key]::Enter -and $LstSearchResults -and $LstSearchResults.Items.Count -eq 1) {
            Use-UserSearchResult -SearchResult $LstSearchResults.Items[0]
            $eventArgs.Handled = $true
        }
    })
}

# User list selection — show details
$LstUserTargets.add_SelectionChanged({
    if ($LstUserTargets.SelectedItem) {
        $record = Find-UserRecord -SearchValue "$($LstUserTargets.SelectedItem.DodId)$($LstUserTargets.SelectedItem.Piv)"
        Set-UserFieldsFromRecord -Record $record
    }
})

# Clear All button
$BtnUserClearAll.add_Click({
    $TxtUserInput.Text = ''
    $LstUserTargets.Items.Clear()
    if ($LstSelectedGroups) { $LstSelectedGroups.Items.Clear() }
    if ($LstUserGroupResults) { $LstUserGroupResults.Items.Clear() }
    $TxtUserStatus.Text = 'No user selected.'
})

# Select All / Deselect All toggle for user checkboxes
$BtnUserSelectAll.IsEnabled = $true

function Update-UserSelectAllButton {
    if (($RadUserGet -and $RadUserGet.IsChecked) -or ($RadUserSet -and $RadUserSet.IsChecked)) {
        $BtnUserSelectAll.Content = 'Select All'
        $BtnUserSelectAll.IsEnabled = $false
        return
    }

    $BtnUserSelectAll.IsEnabled = $true

    if ($LstUserTargets.Items.Count -eq 0) {
        $BtnUserSelectAll.Content = 'Select All'
        return
    }
    $allChecked = $true
    foreach ($item in $LstUserTargets.Items) {
        if (-not $item.IsSelected) { $allChecked = $false; break }
    }
    $BtnUserSelectAll.Content = if ($allChecked) { 'Deselect All' } else { 'Select All' }
}

function Get-UserItemSamAccountName {
    param($Item)

    if ($null -eq $Item) { return '' }
    return "$([string]$Item.DodId)$([string]$Item.Piv)".Trim()
}

function Get-CheckedUserItems {
    if (-not $LstUserTargets) { return @() }
    return @($LstUserTargets.Items | Where-Object { $_.IsSelected })
}

function Get-CheckedUserNames {
    return @(Get-CheckedUserItems | ForEach-Object { Get-UserItemSamAccountName -Item $_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
}

function Format-UserSelectionDisplay {
    param($Item)

    if ($null -eq $Item) { return '' }
    $samAccountName = Get-UserItemSamAccountName -Item $Item
    return "$($Item.Name) ($samAccountName)"
}

function Get-CloneUserSelection {
    $checked = @(Get-CheckedUserItems)
    $primaryItem = if ($checked.Count -gt 0) { $checked[0] } else { $null }
    $cloneItems = if ($checked.Count -gt 1) { @($checked | Select-Object -Skip 1) } else { @() }

    [pscustomobject]@{
        PrimaryItem = $primaryItem
        CloneItems  = $cloneItems
        PrimaryName = Get-UserItemSamAccountName -Item $primaryItem
        CloneNames  = @($cloneItems | ForEach-Object { Get-UserItemSamAccountName -Item $_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
    }
}

function Update-CloneUserFields {
    if (-not $RadUserClone -or -not $RadUserClone.IsChecked) { return }

    $selection = Get-CloneUserSelection
    if ($TxtUserPrimary) {
        $TxtUserPrimary.Text = if ($selection.PrimaryItem) { Format-UserSelectionDisplay -Item $selection.PrimaryItem } else { '' }
    }
    if ($TxtUserClone) {
        $TxtUserClone.Text = @($selection.CloneItems | ForEach-Object { Format-UserSelectionDisplay -Item $_ }) -join "`r`n"
    }
}

function Set-UserGetSingleSelection {
    param($KeepItem)

    $isSingleUserMode = ($RadUserGet -and $RadUserGet.IsChecked) -or ($RadUserSet -and $RadUserSet.IsChecked)
    if (-not $isSingleUserMode) { return }

    $checked = @(Get-CheckedUserItems)
    if ($checked.Count -le 1) { return }

    $keep = if ($KeepItem -and $KeepItem.IsSelected) {
        $KeepItem
    } elseif ($LstUserTargets.SelectedItem -and $LstUserTargets.SelectedItem.IsSelected) {
        $LstUserTargets.SelectedItem
    } else {
        $checked[-1]
    }

    foreach ($item in $LstUserTargets.Items) {
        $item.IsSelected = [object]::ReferenceEquals($item, $keep)
    }
    $LstUserTargets.Items.Refresh()
}

function Get-SetUserFieldConfig {
    return @(
        [pscustomobject]@{ CheckBox = $ChkSetUserRank;          TextBox = $TxtUserRank;          Property = 'personalTitle';      Label = 'Rank' }
        [pscustomobject]@{ CheckBox = $ChkSetUserEmail;         TextBox = $TxtUserEmail;         Property = 'EmailAddress';       Label = 'Email' }
        [pscustomobject]@{ CheckBox = $ChkSetUserPhone;         TextBox = $TxtUserPhone;         Property = 'OfficePhone';        Label = 'Phone' }
        [pscustomobject]@{ CheckBox = $ChkSetUserUPN;           TextBox = $TxtUserUPN;           Property = 'UserPrincipalName';  Label = 'User Principal Name' }
        [pscustomobject]@{ CheckBox = $ChkSetUserHomeDrive;     TextBox = $TxtUserHomeDrive;     Property = 'HomeDrive';          Label = 'Home Drive' }
        [pscustomobject]@{ CheckBox = $ChkSetUserHomeDirectory; TextBox = $TxtUserHomeDirectory; Property = 'HomeDirectory';      Label = 'Home Directory' }
    )
}

function Set-UserDetailsEditMode {
    param(
        [bool]$Enabled,
        [bool]$AllowGroupChecks = $false
    )

    foreach ($field in (Get-SetUserFieldConfig)) {
        if ($field.CheckBox) {
            $field.CheckBox.Visibility = if ($Enabled) { 'Visible' } else { 'Collapsed' }
            if (-not $Enabled) { $field.CheckBox.IsChecked = $false }
        }
        if ($field.TextBox) {
            $field.TextBox.IsReadOnly = -not $Enabled
            $field.TextBox.Background = [System.Windows.Media.Brushes]::Transparent
        }
    }

    if ($TxtUserGroups) {
        foreach ($group in $TxtUserGroups.Items) {
            if ($group.PSObject.Properties.Name -contains 'IsEnabled') {
                $group.IsEnabled = $AllowGroupChecks
            }
        }
        $TxtUserGroups.Items.Refresh()
    }
}

function Get-SelectedSetUserProperties {
    $changes = @()
    foreach ($field in (Get-SetUserFieldConfig)) {
        if ($field.CheckBox -and $field.CheckBox.IsChecked) {
            $changes += [pscustomobject]@{
                Label    = $field.Label
                Property = $field.Property
                Value    = if ($field.TextBox) { [string]$field.TextBox.Text } else { '' }
            }
        }
    }
    return @($changes)
}

function ConvertTo-UserGroupDisplayRecord {
    param($Group)

    if ($null -eq $Group) { return $null }
    $name = if ($Group.Name) { [string]$Group.Name } elseif ($Group.SamAccountName) { [string]$Group.SamAccountName } else { [string]$Group.DistinguishedName }
    $email = if ($Group.mail) { [string]$Group.mail } elseif ($Group.EmailAddress) { [string]$Group.EmailAddress } else { '' }

    [pscustomobject]@{
        Name              = $name
        Email             = $email
        SamAccountName    = [string]$Group.SamAccountName
        DistinguishedName = [string]$Group.DistinguishedName
        IsSelected        = $false
    }
}

function Show-UserGroupMode {
    param(
        [bool]$Enabled,
        [bool]$AllowRemoveButtons = $true
    )

    if ($GrpUserSearchResults) { $GrpUserSearchResults.Header = 'Search Results' }
    if ($GrpUserDetailsRight) { $GrpUserDetailsRight.Header = if ($Enabled) { 'Selected Groups' } else { 'User Details' } }
    if ($LstSearchResults) { $LstSearchResults.Visibility = 'Visible' }
    if ($LstUserGroupResults) { $LstUserGroupResults.Visibility = 'Collapsed' }
    if ($ScrUserDetails) { $ScrUserDetails.Visibility = if ($Enabled) { 'Collapsed' } else { 'Visible' } }
    if ($PanelSelectedGroups) { $PanelSelectedGroups.Visibility = if ($Enabled) { 'Visible' } else { 'Collapsed' } }

    if ($LstSelectedGroups -and $LstSelectedGroups.View -and $LstSelectedGroups.View.Columns.Count -ge 3) {
        $removeColumn = $LstSelectedGroups.View.Columns[2]
        $removeColumn.Header = if ($AllowRemoveButtons) { 'Remove' } else { '' }
        $removeColumn.Width = if ($AllowRemoveButtons) { 70 } else { 0 }
    }

    if ($GrpUserSearchResults -and $GrpUserSearchResults.Parent -and $GrpUserSearchResults.Parent.RowDefinitions.Count -ge 2) {
        $GrpUserSearchResults.Visibility = 'Visible'
        $GrpUserSearchResults.Parent.RowDefinitions[0].Height = [System.Windows.GridLength]::new(200)
        $GrpUserSearchResults.Parent.RowDefinitions[1].Height = [System.Windows.GridLength]::new(8)
    }
}

function Show-UserSearchResults {
    if ($GrpUserSearchResults) { $GrpUserSearchResults.Header = 'Search Results' }
    if ($LstSearchResults) { $LstSearchResults.Visibility = 'Visible' }
    if ($LstUserGroupResults) { $LstUserGroupResults.Visibility = 'Collapsed' }
}

function Show-GroupSearchResults {
    if ($GrpUserSearchResults) { $GrpUserSearchResults.Header = 'Group Search Results' }
    if ($LstSearchResults) { $LstSearchResults.Visibility = 'Collapsed' }
    if ($LstUserGroupResults) { $LstUserGroupResults.Visibility = 'Visible' }
}

function Add-SelectedUserGroup {
    param($Group)

    if (-not $LstSelectedGroups -or $null -eq $Group) { return }
    $record = ConvertTo-UserGroupDisplayRecord -Group $Group
    if ($null -eq $record -or [string]::IsNullOrWhiteSpace($record.Name)) { return }

    $existing = @($LstSelectedGroups.Items | Where-Object {
        $_.Name -eq $record.Name -or
        (-not [string]::IsNullOrWhiteSpace($record.DistinguishedName) -and $_.DistinguishedName -eq $record.DistinguishedName)
    })
    if ($existing.Count -eq 0) {
        $record.IsSelected = $true
        [void]$LstSelectedGroups.Items.Add($record)
    }
    if ($TxtSelectedGroupsStatus) { $TxtSelectedGroupsStatus.Text = "$($LstSelectedGroups.Items.Count) group(s) selected." }
}

function Search-UserGroups {
    if (-not $LstUserGroupResults -or -not $TxtUserGroup) { return }

    $query = $TxtUserGroup.Text.Trim()
    $LstUserGroupResults.Items.Clear()
    if ($query.Length -lt 3) { return }

    Show-GroupSearchResults
    $groups = @(Search-ToolADGroup -Query $query -MaxResults 20 | ForEach-Object { ConvertTo-UserGroupDisplayRecord -Group $_ })
    foreach ($group in $groups) {
        if ($group) { [void]$LstUserGroupResults.Items.Add($group) }
    }
}

function Get-SharedUserGroupRecords {
    $users = @(Get-CheckedUserNames)
    if ($users.Count -eq 0) { return @() }

    $shared = $null
    foreach ($userName in $users) {
        try {
            $adUser = Get-ToolADUser -Identity $userName -Properties MemberOf -ErrorAction Stop
            $groups = @($adUser.MemberOf) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
            if ($null -eq $shared) {
                $shared = @($groups)
            } else {
                $shared = @($shared | Where-Object { $groups -contains $_ })
            }
        } catch {
            Write-Verbose "Unable to load shared groups for '$userName': $($_.Exception.Message)"
            return @()
        }
    }

    return @($shared | Sort-Object | ForEach-Object {
        $groupDn = [string]$_
        $groupObj = Get-ToolADGroup -Identity $groupDn -ErrorAction SilentlyContinue
        $groupName = if ($groupObj -and $groupObj.Name) { [string]$groupObj.Name } else { Get-GroupNameFromDistinguishedName -DistinguishedName $groupDn }
        $samAccountName = if ($groupObj -and $groupObj.SamAccountName) { [string]$groupObj.SamAccountName } else { $groupName }

        [pscustomobject]@{
            Name              = $groupName
            Email             = if ($groupObj -and $groupObj.mail) { [string]$groupObj.mail } else { '' }
            SamAccountName    = $samAccountName
            DistinguishedName = $groupDn
            IsSelected        = $false
        }
    })
}

function Update-SelectedGroupsForRemove {
    if (-not $RadUserRemoveGroup -or -not $RadUserRemoveGroup.IsChecked -or -not $LstSelectedGroups) { return }

    $LstSelectedGroups.Items.Clear()
    foreach ($group in (Get-SharedUserGroupRecords)) {
        [void]$LstSelectedGroups.Items.Add($group)
    }
    if ($TxtSelectedGroupsStatus) {
        $TxtSelectedGroupsStatus.Text = if ($LstSelectedGroups.Items.Count -gt 0) {
            "$($LstSelectedGroups.Items.Count) shared group(s) available to remove."
        } else {
            'No shared groups found for the selected users.'
        }
    }
}

$BtnUserSelectAll.add_Click({
    if ($BtnUserSelectAll.Content -eq 'Select All') {
        foreach ($item in $LstUserTargets.Items) { $item.IsSelected = $true }
    } else {
        foreach ($item in $LstUserTargets.Items) { $item.IsSelected = $false }
    }
    Set-UserGetSingleSelection
    $LstUserTargets.Items.Refresh()
    Update-UserSelectAllButton
    Update-SelectedGroupsForRemove
    Update-CloneUserFields
})

$LstUserTargets.add_MouseLeftButtonUp({
    Start-Sleep -Milliseconds 50
    Set-UserGetSingleSelection
    Update-UserSelectAllButton
    Update-SelectedGroupsForRemove
    Update-CloneUserFields
})

$LstUserTargets.AddHandler(
    [System.Windows.Controls.Primitives.ToggleButton]::CheckedEvent,
    [System.Windows.RoutedEventHandler]{
        param($sender, $eventArgs)

        $clickedItem = $null
        if ($eventArgs.OriginalSource -is [System.Windows.FrameworkElement]) {
            $clickedItem = $eventArgs.OriginalSource.DataContext
        }

        Set-UserGetSingleSelection -KeepItem $clickedItem
        if ($clickedItem) {
            $LstUserTargets.SelectedItem = $clickedItem
            $record = Find-UserRecord -SearchValue "$(Get-UserItemSamAccountName -Item $clickedItem)"
            Set-UserFieldsFromRecord -Record $record
        }
        Update-UserSelectAllButton
        Update-SelectedGroupsForRemove
        Update-CloneUserFields
    }
)

# Action radio buttons — toggle action-specific inputs
function Update-UserActionUI {
    $GrpUserDetails.Visibility = 'Visible'
    $LblUserProperty.Visibility = 'Collapsed'; $TxtUserProperty.Visibility = 'Collapsed'
    $LblUserValue.Visibility    = 'Collapsed'; $TxtUserValue.Visibility    = 'Collapsed'
    $LblUserGroup.Visibility    = 'Collapsed'
    if ($PanelUserGroupSearch) { $PanelUserGroupSearch.Visibility = 'Collapsed' }
    $LblUserPrimary.Visibility  = 'Collapsed'; $TxtUserPrimary.Visibility  = 'Collapsed'
    $LblUserClone.Visibility    = 'Collapsed'; $TxtUserClone.Visibility    = 'Collapsed'
    Show-UserGroupMode -Enabled $false
    Set-UserDetailsEditMode -Enabled $false
    Set-UserGetSingleSelection
    Update-UserSelectAllButton

    if ($RadUserSet.IsChecked) {
        $GrpUserDetails.Visibility = 'Collapsed'
        Set-UserDetailsEditMode -Enabled $true
    } elseif ($RadUserAddGroup.IsChecked) {
        $LblUserGroup.Visibility = 'Visible'
        if ($PanelUserGroupSearch) { $PanelUserGroupSearch.Visibility = 'Visible' }
        Show-UserGroupMode -Enabled $true -AllowRemoveButtons $true
        if ($LstSelectedGroups) { $LstSelectedGroups.Items.Clear() }
        if ($LstUserGroupResults) { $LstUserGroupResults.Items.Clear() }
        if ($TxtSelectedGroupsStatus) { $TxtSelectedGroupsStatus.Text = 'Search for a group and click a result to add it.' }
    } elseif ($RadUserRemoveGroup.IsChecked) {
        $GrpUserDetails.Visibility = 'Collapsed'
        Show-UserGroupMode -Enabled $true -AllowRemoveButtons $false
        if ($TxtUserGroup) { $TxtUserGroup.Text = '' }
        if ($LstUserGroupResults) { $LstUserGroupResults.Items.Clear() }
        Update-SelectedGroupsForRemove
    } elseif ($RadUserCreateUDrive -and $RadUserCreateUDrive.IsChecked) {
        $GrpUserDetails.Visibility = 'Collapsed'
    } elseif ($RadUserTestGroup.IsChecked) {
        $LblUserGroup.Visibility = 'Visible'
        if ($PanelUserGroupSearch) { $PanelUserGroupSearch.Visibility = 'Visible' }
        Show-UserGroupMode -Enabled $true -AllowRemoveButtons $true
        if ($LstSelectedGroups) { $LstSelectedGroups.Items.Clear() }
        if ($LstUserGroupResults) { $LstUserGroupResults.Items.Clear() }
        if ($TxtSelectedGroupsStatus) { $TxtSelectedGroupsStatus.Text = 'Search for groups and click results to add them for membership testing.' }
    } elseif ($RadUserClone.IsChecked) {
        $LblUserPrimary.Visibility = 'Visible'; $TxtUserPrimary.Visibility = 'Visible'
        $LblUserClone.Visibility   = 'Visible'; $TxtUserClone.Visibility   = 'Visible'
        Update-CloneUserFields
    } else {
        $GrpUserDetails.Visibility = 'Collapsed'
    }
}
$RadUserGet.add_Checked({ Update-UserActionUI })
$RadUserEnable.add_Checked({ Update-UserActionUI })
$RadUserDisable.add_Checked({ Update-UserActionUI })
$RadUserUnlock.add_Checked({ Update-UserActionUI })
if ($RadUserCreateUDrive) { $RadUserCreateUDrive.add_Checked({ Update-UserActionUI }) }
$RadUserSet.add_Checked({ Update-UserActionUI })
$RadUserAddGroup.add_Checked({ Update-UserActionUI })
$RadUserRemoveGroup.add_Checked({ Update-UserActionUI })
$RadUserClone.add_Checked({ Update-UserActionUI })
$RadUserTestGroup.add_Checked({ Update-UserActionUI })

if ($TxtUserGroup) {
    $TxtUserGroup.add_TextChanged({ Search-UserGroups })
    $TxtUserGroup.add_KeyDown({
        param($sender, $eventArgs)

        if ($eventArgs.Key -eq [System.Windows.Input.Key]::Enter -and $LstUserGroupResults -and $LstUserGroupResults.Items.Count -eq 1) {
            Add-SelectedUserGroup -Group $LstUserGroupResults.Items[0]
            $LstUserGroupResults.SelectedItem = $null
            $eventArgs.Handled = $true
        }
    })
}

if ($BtnUserGroupSearch) {
    $BtnUserGroupSearch.add_Click({ Search-UserGroups })
}

if ($LstUserGroupResults) {
    $LstUserGroupResults.add_SelectionChanged({
        if ($LstUserGroupResults.SelectedItem -and ($RadUserAddGroup.IsChecked -or $RadUserTestGroup.IsChecked)) {
            Add-SelectedUserGroup -Group $LstUserGroupResults.SelectedItem
            $LstUserGroupResults.SelectedItem = $null
        }
    })
}

if ($LstSelectedGroups) {
    $LstSelectedGroups.AddHandler(
        [System.Windows.Controls.Button]::ClickEvent,
        [System.Windows.RoutedEventHandler]{
            param($sender, $eventArgs)
            $button = $eventArgs.OriginalSource
            if ($button -is [System.Windows.Controls.Button] -and $button.Tag) {
                $LstSelectedGroups.Items.Remove($button.Tag)
                if ($TxtSelectedGroupsStatus) { $TxtSelectedGroupsStatus.Text = "$($LstSelectedGroups.Items.Count) group(s) selected." }
            }
        }
    )
}

# Track last active tab for App Bar TabControl
if ($MainTabControl) {
    $MainTabControl.add_SelectionChanged({
        if ($TabComputer.IsSelected) { Set-ActiveTab -TabName 'Computer' }
        elseif ($TabUser.IsSelected) { Set-ActiveTab -TabName 'User' }
        elseif ($TabGroup -and $TabGroup.IsSelected) { Set-ActiveTab -TabName 'Group' }
    })
}

# ==============================================================================

# ==============================================================================
# GROUP MANAGEMENT TAB — event wiring
# ==============================================================================

if ($PanelGroup -and $BtnGroupClearAll -and $BtnGroupSelectAll) {
    # Clear All button
    $BtnGroupClearAll.add_Click({
        $TxtGroupUserInput.Text = ''
        $LstGroupUsers.Items.Clear()
    })

    # Select All toggle
    $BtnGroupSelectAll.add_Click({
        if ($BtnGroupSelectAll.Content -eq 'Select All') {
            foreach ($item in $LstGroupUsers.Items) { $item.IsSelected = $true }
            $BtnGroupSelectAll.Content = 'Deselect All'
        } else {
            foreach ($item in $LstGroupUsers.Items) { $item.IsSelected = $false }
            $BtnGroupSelectAll.Content = 'Select All'
        }
        $LstGroupUsers.Items.Refresh()
    })

    # Clone action — show/hide clone fields
    if ($RadGroupClone -and $GrpGroupClone) { $RadGroupClone.add_Checked({ $GrpGroupClone.Visibility = 'Visible' }) }
    if ($RadGroupAdd -and $GrpGroupClone) { $RadGroupAdd.add_Checked({ $GrpGroupClone.Visibility = 'Collapsed' }) }
    if ($RadGroupRemove -and $GrpGroupClone) { $RadGroupRemove.add_Checked({ $GrpGroupClone.Visibility = 'Collapsed' }) }
    if ($RadGroupTest -and $GrpGroupClone) { $RadGroupTest.add_Checked({ $GrpGroupClone.Visibility = 'Collapsed' }) }
}

# STORE GLOBAL OUTPUT
$global:TxtOutput = $TxtOutput

# Inventory file / cache
$script:InventoryFile = Join-Path (Join-Path $AppRoot 'xlsx') $Global:AppConfig.Inventory.FileName
$script:InventoryData = @()
$script:TechRefreshPackageRows = @()
$script:IsTechRefreshPackageLoaded = $false
$script:LastImportedFile = $null
$script:TechRefreshLookup = @{}
$script:SimulationMode = $Global:AppConfig.UI.Simulation -eq $true

# Load User Database for simulation/offline use
$script:UserDbFile = Join-Path (Join-Path $AppRoot 'xlsx') 'AFUsersDB.xlsx'
$script:UserDbData = @()
$script:UserDbLookup = @{}
if ($script:SimulationMode) {
    Initialize-ToolADSimulationData
}
if (-not $script:SimulationMode -and (Test-Path $script:UserDbFile)) {
    try {
        $script:UserDbData = @(Import-Excel -Path $script:UserDbFile)
        foreach ($user in $script:UserDbData) {
            $key0 = "$($user.DOD_ID)$($user.PIV)".Trim().ToUpper()
            if ($key0) { $script:UserDbLookup[$key0] = $user }
            $key = $user.DOD_ID.ToString().Trim()
            if ($key) { $script:UserDbLookup[$key] = $user }
            $key2 = $user.EMAIL_ADDRESS.ToString().Trim().ToUpper()
            if ($key2) { $script:UserDbLookup[$key2] = $user }
            $key3 = $user.FULL_NAME.ToString().Trim().ToUpper()
            if ($key3) { $script:UserDbLookup[$key3] = $user }
        }
    } catch {
        Write-Warning "Could not load user database: $($_.Exception.Message)"
    }
}

# Load ShortName mappings
$script:ShortNameMapFile = Join-Path $ConfigPath "shortname_map.json"
if (Test-Path $script:ShortNameMapFile) {
    try {
        $script:ShortNameMappings = (Get-Content $script:ShortNameMapFile | ConvertFrom-Json).Mappings
    } catch {
        $script:ShortNameMappings = @()
    }
} else {
    $script:ShortNameMappings = @()
}

function Save-AppConfig {
    try {
        $Global:AppConfig | ConvertTo-Json -Depth 10 | Set-Content -Path $configFile -Encoding UTF8
    } catch {
        Write-Warning "Unable to save config.json: $($_.Exception.Message)"
    }
}

# Apply Start Tab from config
# ==============================================================================
$startTab = $Global:AppConfig.UI.StartTab
if ($startTab -eq 'Last Active') { $startTab = $Global:AppConfig.UI.LastActiveTab }
if ($startTab -eq 'Group') { $startTab = 'Computer' }

function Set-ActiveTab {
    param([string]$TabName)
    $Global:AppConfig.UI.LastActiveTab = $TabName
    Save-AppConfig
}

if ($startTab -eq 'User') {
    if ($uiMode -eq 'Hamburger') {
        $BtnTabUser.Background = '#2D5080'; $BtnTabUser.Foreground = 'White'
        $BtnTabComputer.Background = 'Transparent'; $BtnTabComputer.Foreground = '#AABBCC'
        $BtnTabGroup.Background = 'Transparent'; $BtnTabGroup.Foreground = '#AABBCC'
        $PanelComputer.Visibility = 'Collapsed'; $PanelUser.Visibility = 'Visible'; $PanelGroup.Visibility = 'Collapsed'
    } elseif ($MainTabControl) {
        $TabUser.IsSelected = $true
    }
    Set-ActiveTab -TabName 'User'
} elseif ($startTab -eq 'Group') {
    if ($uiMode -eq 'Hamburger') {
        $BtnTabGroup.Background = '#2D5080'; $BtnTabGroup.Foreground = 'White'
        $BtnTabComputer.Background = 'Transparent'; $BtnTabComputer.Foreground = '#AABBCC'
        $BtnTabUser.Background = 'Transparent'; $BtnTabUser.Foreground = '#AABBCC'
        $PanelComputer.Visibility = 'Collapsed'; $PanelUser.Visibility = 'Collapsed'; $PanelGroup.Visibility = 'Visible'
    } elseif ($MainTabControl -and $TabGroup) {
        $TabGroup.IsSelected = $true
    }
    Set-ActiveTab -TabName 'Group'
} else {
    if ($uiMode -eq 'Hamburger') {
        $BtnTabComputer.Background = '#2D5080'; $BtnTabComputer.Foreground = 'White'
        $BtnTabUser.Background = 'Transparent'; $BtnTabUser.Foreground = '#AABBCC'
        $BtnTabGroup.Background = 'Transparent'; $BtnTabGroup.Foreground = '#AABBCC'
        $PanelComputer.Visibility = 'Visible'; $PanelUser.Visibility = 'Collapsed'; $PanelGroup.Visibility = 'Collapsed'
    }
    Set-ActiveTab -TabName 'Computer'
}


# Reusable Tech Refresh package loader — called from Import and checkbox
function Load-TechRefreshPackage {
    param([string]$FilePath)

    $script:TechRefreshPackageRows = @()
    $script:IsTechRefreshPackageLoaded = $false

    if (-not $FilePath -or -not (Test-Path $FilePath)) {
        $TxtStatusFile.Text = if ($script:LastImportedFile) { "File: $([System.IO.Path]::GetFileName($script:LastImportedFile))" } else { "" }
        return $false
    }

    $ext = [System.IO.Path]::GetExtension($FilePath).ToLower()
    if ($ext -notin @('.xlsx', '.csv')) {
        $TxtStatusFile.Text = "File: $([System.IO.Path]::GetFileName($FilePath)) — [BAD FORMAT] Not a supported format"
        return $false
    }

    try {
        if ($ext -eq '.xlsx') { $rows = Import-Excel $FilePath }
        else { $rows = Import-Csv $FilePath }
    } catch {
        $TxtStatusFile.Text = "File: $([System.IO.Path]::GetFileName($FilePath)) — [ERROR] Error reading file"
        return $false
    }

    if (-not $rows -or $rows.Count -eq 0) {
        $TxtStatusFile.Text = "File: $([System.IO.Path]::GetFileName($FilePath)) — [EMPTY] No data found"
        return $false
    }

    $columnMap = @{
        'ComputerName' = @('ComputerName', 'Computer Name', 'Name')
        'SerialNumber' = @('New Serial', 'Serial Number', 'Serial')
        'Organization' = @('Organization', 'Unit', 'Org')
        'BuildingNum'  = @('BuildingNum', 'Building Number', 'Building', 'Bldg')
        'RoomNum'      = @('RoomNum', 'Room Number', 'Room')
        'Model'        = @('Model', 'Replacement Model', 'Short Name', 'Type')
        'OS'           = @('OS', 'Operating System')
        'Extension'    = @('Extension', 'Duty Phone', 'Phone', 'Ext')
        'POC'          = @('POC', 'Custodian', 'Equipment Custodian')
        'OldSerial'    = @('Old Serial', 'Replaced')
        'MAC'          = @('MAC Address', 'MAC', 'MacAddress')
        'Series'       = @('Series')
    }

    $actualColumns = $rows[0].PSObject.Properties.Name
    $missing = @()
    $sourceColumns = @{}

    foreach ($key in $columnMap.Keys) {
        $match = $columnMap[$key] | Where-Object { $_ -in $actualColumns } | Select-Object -First 1
        if ($match) {
            $sourceColumns[$key] = $match
        } elseif ($key -notin @('Model', 'OS', 'Extension', 'POC', 'OldSerial', 'MAC', 'Series', 'SerialNumber')) {
            $missing += $key
        }
    }

    if ($missing.Count -gt 0) {
        $fileName = [System.IO.Path]::GetFileName($FilePath)
        $TxtStatusFile.Text = "File: $fileName - ? Missing: $($missing -join ', ')"
        Write-Line "Tech Refresh: missing required columns: $($missing -join ', ')" -Color "Red"
        return $false
    }

    # Remap columns to standard keys
    $remapped = @()
    foreach ($row in $rows) {
        $newRow = [pscustomobject]@{}
        foreach ($key in $columnMap.Keys) {
            $sourceCol = $sourceColumns[$key]
            $value = if ($sourceCol) { $row.$sourceCol } else { '' }
            $newRow | Add-Member -NotePropertyName $key -NotePropertyValue $value
        }
        $remapped += $newRow
    }

    $script:TechRefreshPackageRows = @(
        $remapped | Where-Object {
            $_.ComputerName -and $_.Organization -and $_.BuildingNum -and $_.RoomNum
        }
    )
    $script:IsTechRefreshPackageLoaded = ($script:TechRefreshPackageRows.Count -gt 0)

    # Build hashtable for O(1) lookup by ComputerName and SerialNumber
    $script:TechRefreshLookup = @{}
    foreach ($row in $script:TechRefreshPackageRows) {
        $key = $row.ComputerName.ToString().Trim().ToUpper()
        if ($key) { $script:TechRefreshLookup[$key] = $row }
        $key2 = if ($row.SerialNumber) { $row.SerialNumber.ToString().Trim().ToUpper() } else { '' }
        if ($key2 -and $key2 -ne $key) { $script:TechRefreshLookup[$key2] = $row }
    }

    $fileName = [System.IO.Path]::GetFileName($FilePath)
    if ($script:IsTechRefreshPackageLoaded) {
        $TxtStatusFile.Text = "File: $fileName — [OK] Tech Refresh compatible ($($script:TechRefreshPackageRows.Count) rows)"
    } else {
        $TxtStatusFile.Text = "File: $fileName — [NO VALID ROWS] Required columns present but no valid rows found"
    }

    return $script:IsTechRefreshPackageLoaded
}

# Open the ShortName Mappings editor window
function Open-ShortNameMapEditor {
    $mapXamlFile = Join-Path $UiPath "ShortNameMap.xaml"
    if (-not (Test-Path $mapXamlFile)) {
        [System.Windows.MessageBox]::Show("Could not find ShortNameMap.xaml!", "Error", "OK", "Error")
        return
    }

    $mapGuiData = New-XamlWindow -XamlFile $mapXamlFile
    $MapWindow   = $mapGuiData[0]
    $MapWindow.Owner = $MainWindow

    # Refresh the list from current data
    function Refresh-MapList {
        $LstMappings.Items.Clear()
        foreach ($mapping in $script:ShortNameMappings) {
            [void]$LstMappings.Items.Add([pscustomobject]@{
                Model     = $mapping.Model
                ShortName = $mapping.ShortName
            })
        }
    }
    Refresh-MapList

    # Click on list item populates edit boxes
    $LstMappings.add_SelectionChanged({
        if ($LstMappings.SelectedItem) {
            $TxtEditModel.Text     = $LstMappings.SelectedItem.Model
            $TxtEditShortName.Text = $LstMappings.SelectedItem.ShortName
        }
    })

    # Add or Update
    $script:_mapEditIndex = -1
    $BtnAddUpdate.add_Click({
        $model = $TxtEditModel.Text.Trim()
        $short = $TxtEditShortName.Text.Trim()
        if (-not $model) {
            [System.Windows.MessageBox]::Show("Model name is required.", "Warning", "OK", "Warning")
            return
        }

        # Find existing model to update
        $found = $false
        for ($i = 0; $i -lt $script:ShortNameMappings.Count; $i++) {
            if ($script:ShortNameMappings[$i].Model -eq $model) {
                $script:ShortNameMappings[$i].ShortName = $short
                $found = $true
                break
            }
        }
        if (-not $found) {
            $script:ShortNameMappings += [pscustomobject]@{ Model = $model; ShortName = $short }
        }

        Refresh-MapList
        $TxtEditModel.Text = ''; $TxtEditShortName.Text = ''
    })

    # Remove selected
    $BtnRemove.add_Click({
        if (-not $LstMappings.SelectedItem) {
            [System.Windows.MessageBox]::Show("Select a mapping to remove.", "Warning", "OK", "Warning")
            return
        }
        $model = $LstMappings.SelectedItem.Model
        $script:ShortNameMappings = @($script:ShortNameMappings | Where-Object { $_.Model -ne $model })
        Refresh-MapList
        $TxtEditModel.Text = ''; $TxtEditShortName.Text = ''
    })

    # Save to JSON file
    $BtnSaveMap.add_Click({
        $mapData = @{ Mappings = @($script:ShortNameMappings) }
        $mapData | ConvertTo-Json -Depth 5 | Set-Content $script:ShortNameMapFile -Encoding UTF8
        [System.Windows.MessageBox]::Show("ShortName mappings saved.", "Saved", "OK", "Information")
    })

    # Import from another JSON file
    $BtnImportMap.add_Click({
        Add-Type -AssemblyName System.Windows.Forms
        $dlg = New-Object System.Windows.Forms.OpenFileDialog
        $dlg.Filter = "JSON Files (*.json)|*.json"
        $dlg.Title = "Import ShortName Mappings"
        if ($dlg.ShowDialog() -eq 'OK') {
            try {
                $imported = (Get-Content $dlg.FileName | ConvertFrom-Json).Mappings
                $count = if ($imported) { $imported.Count } else { 0 }
                if ($count -gt 0) {
                    $result = [System.Windows.MessageBox]::Show(
                        "Import $count mapping(s)? Existing entries with the same Model will be overwritten.",
                        "Confirm Import", "YesNo", "Question")
                    if ($result -eq 'Yes') {
                        foreach ($m in $imported) {
                            $existing = $script:ShortNameMappings | Where-Object { $_.Model -eq $m.Model }
                            if ($existing) { $existing.ShortName = $m.ShortName }
                            else { $script:ShortNameMappings += [pscustomobject]@{ Model = $m.Model; ShortName = $m.ShortName } }
                        }
                        Refresh-MapList
                        [System.Windows.MessageBox]::Show("$count mapping(s) imported.", "Import Complete", "OK", "Information")
                    }
                } else {
                    [System.Windows.MessageBox]::Show("No valid mappings found in file.", "Import Error", "OK", "Warning")
                }
            } catch {
                [System.Windows.MessageBox]::Show("Error reading file: $($_.Exception.Message)", "Import Error", "OK", "Error")
            }
        }
    })

    # Export to a chosen location
    $BtnExportMap.add_Click({
        Add-Type -AssemblyName System.Windows.Forms
        $dlg = New-Object System.Windows.Forms.SaveFileDialog
        $dlg.Filter = "JSON Files (*.json)|*.json"
        $dlg.FileName = "shortname_map_export.json"
        $dlg.Title = "Export ShortName Mappings"
        if ($dlg.ShowDialog() -eq 'OK') {
            try {
                $mapData = @{ Mappings = @($script:ShortNameMappings) }
                $mapData | ConvertTo-Json -Depth 5 | Set-Content $dlg.FileName -Encoding UTF8
                [System.Windows.MessageBox]::Show("Mappings exported to $($dlg.FileName)", "Export Complete", "OK", "Information")
            } catch {
                [System.Windows.MessageBox]::Show("Error exporting: $($_.Exception.Message)", "Export Error", "OK", "Error")
            }
        }
    })

    # Close
    $BtnCloseMap.add_Click({ $MapWindow.Close() })

    $MapWindow.ShowDialog() | Out-Null
}

function Get-InventoryData {
    if (-not (Test-Path $script:InventoryFile)) {
        return @()
    }

    try {
        $data = Import-Excel -Path $script:InventoryFile
        if ($null -eq $data) { return @() }
        return @($data)
    } catch {
        Write-Warning "Unable to read inventory file '$script:InventoryFile': $($_.Exception.Message)"
        return @()
    }
}

# Computer List Item Class for Selection
class ComputerListItem {
    [string]$Name
    [bool]$IsSelected
    
    ComputerListItem([string]$name) {
        $this.Name = $name
        $this.IsSelected = $true  # Default to selected
    }
}

function Get-OrganizationList {
    param([object[]]$InventoryRows)

    $units = @()
    if ($InventoryRows) {
        $units = $InventoryRows |
            Where-Object { $_.Unit } |
            ForEach-Object { $_.Unit.ToString().Trim().ToUpper() } |
            Where-Object { $_ -ne ' '} |
            Sort-Object -Unique
    }

    return $units
}

function Load-Organizations {
    $CmbOrg.Items.Clear()
    foreach ($org in (Get-OrganizationList -InventoryRows $script:InventoryData)) {
        [void]$CmbOrg.Items.Add($org)
    }
}

function Sync-TargetList {
    # Preserve selection states for existing computers
    $selectionStates = @{}
    foreach ($item in $LstTargets.Items) {
        $selectionStates[$item.Name] = $item.IsSelected
    }
    
    $LstTargets.Items.Clear()
    
    $targets = $TxtComputerNames.Text -split "`r`n|`n" | Where-Object { $_.Trim() -ne ' '} | ForEach-Object { $_.Trim() } | Select-Object -Unique
    
    foreach ($target in $targets) {
        $listItem = [ComputerListItem]::new($target)
        # Restore previous selection state if it existed
        if ($selectionStates.ContainsKey($target)) {
            $listItem.IsSelected = $selectionStates[$target]
        }
        [void]$LstTargets.Items.Add($listItem)
    }
    
    Update-SelectAllButtonState
    
    if ($LstTargets.Items.Count -gt 0 -and $LstTargets.SelectedItem) {
        $selectedComputer = $LstTargets.SelectedItem.Name
        $record = Find-InventoryRecord -SearchValue $selectedComputer
        Set-InventoryFieldsFromRecord -Record $record -SearchValue $selectedComputer
    } elseif ($LstTargets.Items.Count -gt 0) {
        # Auto-select first item for inventory display
        $LstTargets.SelectedIndex = 0
        if ($LstTargets.SelectedItem) {
            $selectedComputer = $LstTargets.SelectedItem.Name
            $record = Find-InventoryRecord -SearchValue $selectedComputer
            Set-InventoryFieldsFromRecord -Record $record -SearchValue $selectedComputer
        }
    } else {
        Clear-InventoryDisplay
    }
}
function Update-SelectAllButtonState {
    if ($LstTargets.Items.Count -eq 0) {
        $BtnSelectAll.Content = "Select All"
        $BtnSelectAll.IsEnabled = $false
        return
    }
    $BtnSelectAll.IsEnabled = $true
    $allSelected = $true
    foreach ($item in $LstTargets.Items) {
        if (-not $item.IsSelected) {
            $allSelected = $false
            break
        }
    }
    if ($allSelected) {
        $BtnSelectAll.Content = "Deselect All"
    } else {
        $BtnSelectAll.Content = "Select All"
    }
}

function Set-AllSelectionState {
    param([bool]$Selected)
    foreach ($item in $LstTargets.Items) {
        $item.IsSelected = $Selected
    }
    $LstTargets.Items.Refresh()
    Update-SelectAllButtonState
}


function Find-InventoryRecord {
    param([string]$SearchValue)

    if ([string]::IsNullOrWhiteSpace($SearchValue)) { return $null }

    $needle = $SearchValue.Trim().ToUpper()
    return $script:InventoryData | Where-Object {
        ($_.('Serial Number') -and $_.('Serial Number').ToString().Trim().ToUpper() -eq $needle) -or
        ($_.('Computer Name') -and $_.('Computer Name').ToString().Trim().ToUpper() -eq $needle)
    } | Select-Object -First 1
}

function Clear-InventoryDisplay {
    $TxtInvStatus.Text = 'No inventory record selected.'
    $TxtInvCustodian.Text = ''
    $TxtInvUnit.Text = ''
    $TxtInvDutyPhone.Text = ''
    $TxtInvBuilding.Text = ''
    $TxtInvRoom.Text = ''
    $TxtInvSerial.Text = ''
    $TxtInvComputerName.Text = ''
    $TxtInvReplaced.Text = ''
    $TxtInvType.Text = ''
    $TxtInvMAC.Text = ''
    $TxtInvSeries.Text = ''
    $TxtInvShortName.Text = ''
}

function Set-InventoryFieldsFromRecord {
    param(
        [object]$Record,
        [string]$SearchValue
    )

    if ($null -eq $Record) {
        Clear-InventoryDisplay
        if (-not [string]::IsNullOrWhiteSpace($SearchValue)) {
            $TxtInvStatus.Text = "No inventory match found for '$SearchValue'."
            if ($SearchValue -match '-') {
                $TxtInvComputerName.Text = $SearchValue.Trim().ToUpper()
            } else {
                $TxtInvSerial.Text = $SearchValue.Trim().ToUpper()
            }
        }
        return
    }

    $TxtInvStatus.Text        = 'Inventory match found.'
    $TxtInvCustodian.Text     = [string]$Record.POC
    $TxtInvUnit.Text          = [string]$Record.Unit
    $TxtInvDutyPhone.Text     = [string]$Record.'Duty Phone'
    $TxtInvBuilding.Text      = [string]$Record.Building
    $TxtInvRoom.Text          = [string]$Record.Room
    $TxtInvSerial.Text        = [string]$Record.'Serial Number'
    $TxtInvComputerName.Text  = [string]$Record.'Computer Name'
    $TxtInvReplaced.Text      = [string]$Record.Replaced
    $TxtInvType.Text          = [string]$Record.Type
    $TxtInvMAC.Text           = [string]$Record.'MAC Address'
    $TxtInvSeries.Text        = [string]$Record.Series
    $TxtInvShortName.Text     = [string]$Record.'Short Name'
}

function Refresh-InventoryCache {
    $script:InventoryData = Get-InventoryData
    Load-Organizations
}

function Add-InventoryRecord {
    param(
        [string]$Custodian,
        [string]$Unit,
        [string]$DutyPhone,
        [string]$Building,
        [string]$Room,
        [string]$SerialNumber,
        [string]$ComputerName,
        [string]$Replaced,
        [string]$Type,
        [string]$MacAddress,
        [string]$Series,
        [string]$ShortName
    )

    $targetFolder = Split-Path -Parent $script:InventoryFile
    if (-not (Test-Path $targetFolder)) {
        New-Item -ItemType Directory -Path $targetFolder -Force | Out-Null
    }

    $excel = $null
    try {
        if (Test-Path $script:InventoryFile) {
            $excel = Open-ExcelPackage -Path $script:InventoryFile
            $worksheet = $excel.Workbook.Worksheets[1]
        } else {
            $seed = [pscustomobject]@{
                Date          = Get-Date
                POC           = $Custodian
                Unit          = $Unit
                'Duty Phone'  = $DutyPhone
                Building      = $Building
                Room          = $Room
                'Serial Number' = $SerialNumber
                'Computer Name' = $ComputerName
                Replaced      = $Replaced
                Type          = $Type
                'MAC Address' = $MacAddress
                Series        = $Series
                'Short Name'  = $ShortName
            }
            $null = $seed | Export-Excel -Path $script:InventoryFile -WorksheetName 'Computer_Inventory_GM' -TableName 'ComputerInventory' -AutoSize
            $excel = Open-ExcelPackage -Path $script:InventoryFile
            $worksheet = $excel.Workbook.Worksheets[1]
        }

        # Find the last column index dynamically
        $lastColumn = 13  

        $nextRow = if ($worksheet.Dimension) { $worksheet.Dimension.End.Row + 1 } else { 2 }
        $worksheet.Cells[$nextRow, 1].Value  = Get-Date
        $worksheet.Cells[$nextRow, 2].Value  = $Custodian
        $worksheet.Cells[$nextRow, 3].Value  = $Unit
        $worksheet.Cells[$nextRow, 4].Value  = $DutyPhone
        $worksheet.Cells[$nextRow, 5].Value  = $Building
        $worksheet.Cells[$nextRow, 6].Value  = $Room
        $worksheet.Cells[$nextRow, 7].Value  = $SerialNumber
        $worksheet.Cells[$nextRow, 8].Value  = $ComputerName
        $worksheet.Cells[$nextRow, 9].Value  = $Replaced
        $worksheet.Cells[$nextRow, 10].Value = $Type
        $worksheet.Cells[$nextRow, 11].Value = $MacAddress
        $worksheet.Cells[$nextRow, 12].Value = $Series
        $worksheet.Cells[$nextRow, 13].Value = $ShortName

        Close-ExcelPackage $excel
        $excel = $null
        return $true
    } catch {
        if ($excel) {
            try { Close-ExcelPackage $excel -NoSave } catch {}
        }
        throw
    }
}

# Populate New Computer Details from selected inventory record
function Populate-NewComputerDetails {
    param([object]$InventoryRecord)
    
    if ($null -eq $InventoryRecord) {
        return
    }
    
    # Store original values to show what changed
    $oldModel = $TxtModel.Text
    $oldOrg = $CmbOrg.Text
    $oldBldg = $TxtBldg.Text
    $oldRoom = $TxtRoom.Text
    $oldExt = $TxtExt.Text
    
    # Map inventory fields to New Computer Details
    # Computer Model ← Short Name
    if ($InventoryRecord.'Short Name') {
        $TxtModel.Text = [string]$InventoryRecord.'Short Name'
    } elseif ($InventoryRecord.ShortName) {
        # Handle if column is named "ShortName" without space
        $TxtModel.Text = [string]$InventoryRecord.ShortName
    }
    
    # Organization ← Unit
    if ($InventoryRecord.Unit) {
        $CmbOrg.Text = [string]$InventoryRecord.Unit
    }
    
    # Building Number ← Building
    if ($InventoryRecord.Building) {
        $TxtBldg.Text = [string]$InventoryRecord.Building
    }
    
    # Room Number ← Room
    if ($InventoryRecord.Room) {
        $TxtRoom.Text = [string]$InventoryRecord.Room
    }
    
    # Extension ← Duty Phone
    if ($InventoryRecord.'Duty Phone') {
        $TxtExt.Text = [string]$InventoryRecord.'Duty Phone'
    } elseif ($InventoryRecord.'DutyPhone') {
        # Handle alternative naming
        $TxtExt.Text = [string]$InventoryRecord.DutyPhone
    }
    
    # OS remains untouched - not populated from inventory
    
    # Show what was populated (only show changed fields)
    $changes = @()
    if ($oldModel -ne $TxtModel.Text -and $TxtModel.Text) { $changes += "Model: $($TxtModel.Text)" }
    if ($oldOrg -ne $CmbOrg.Text -and $CmbOrg.Text) { $changes += "Organization: $($CmbOrg.Text)" }
    if ($oldBldg -ne $TxtBldg.Text -and $TxtBldg.Text) { $changes += "Building: $($TxtBldg.Text)" }
    if ($oldRoom -ne $TxtRoom.Text -and $TxtRoom.Text) { $changes += "Room: $($TxtRoom.Text)" }
    if ($oldExt -ne $TxtExt.Text -and $TxtExt.Text) { $changes += "Extension: $($TxtExt.Text)" }
    
    if ($changes.Count -gt 0) {
        Write-Line "Populated New Computer Details from selected inventory record:" -Color "Cyan"
        foreach ($change in $changes) {
            Write-Line "  $change" -Color "Green"
        }
    }
}

# ==============================================================================
# Initialize UI Defaults from Config
# ==============================================================================
$TxtPrefix.Text = $Global:AppConfig.NamingConvention.BasePrefix
$TxtSnDigits.Text = $Global:AppConfig.NamingConvention.SerialNumberDigits
$TxtOS.Text = $Global:AppConfig.NamingConvention.DefaultOS

# Set default Device Type ComboBox
foreach ($item in $CmbDeviceType.Items) {
    if ($item.Content -match "^$($Global:AppConfig.NamingConvention.DefaultDeviceType)\s*-") {
        $CmbDeviceType.SelectedItem = $item
        break
    }
}

# Initialize DEV mode toggle from config
$ChkDevMode.IsChecked = $Global:AppConfig.Development -eq $true

# Load inventory cache / organizations from xlsx\Computer_Inventory_GM.xlsx
Refresh-InventoryCache
if (-not (Test-Path $script:InventoryFile)) {
    $TxtInvStatus.Text = "Inventory file not found: $script:InventoryFile"
} else {
    $TxtInvStatus.Text = "Inventory loaded from: $script:InventoryFile"
}

# Initialize Cisco ISE button labels/tooltips
$BtnISEPrimary.ToolTip = $Global:AppConfig.CiscoISE.PrimaryUrl
$BtnISEBackup.ToolTip  = $Global:AppConfig.CiscoISE.BackupUrl

# ==============================================================================
# Auto-Calculated Description Logic
# ==============================================================================
$UpdateDescription = {
    $os    = $TxtOS.Text.Trim()
    $model = $TxtModel.Text.Replace(" ", "")
    $org   = $CmbOrg.Text.Replace(" ", "")
    
    $bldg  = $TxtBldg.Text.Replace(" ", "")
    if ($bldg) { $bldg = "B-$bldg" }
    
    $room  = $TxtRoom.Text.Replace(" ", "")
    if ($room) { $room = "R-$room" }
    
    $ext   = $TxtExt.Text.Replace(" ", "")
    if ($ext) { $ext = "X-$ext" }

    # Array of parts, filter out empty ones, join with comma + space
    $parts = @($os, $model, $org, $bldg, $room, $ext) | Where-Object { $_ -ne "" }
    $TxtDesc.Text = $parts -join ", "
}

# Attach the event to all text boxes so it updates live as you type
$TxtOS.add_TextChanged($UpdateDescription)
$TxtModel.add_TextChanged($UpdateDescription)
$CmbOrg.add_SelectionChanged($UpdateDescription)
$CmbOrg.add_LostFocus($UpdateDescription)
$TxtBldg.add_TextChanged($UpdateDescription)
$TxtRoom.add_TextChanged($UpdateDescription)
$TxtExt.add_TextChanged($UpdateDescription)

# ==============================================================================
# Auto-Formatting Logic (Air Force Standard)
# ==============================================================================

# Use KeyUp instead of PreviewKeyUp so standard typing is not interrupted
$TxtComputerNames.add_KeyUp({
    param($sender, $e)
    if ($e.Key -eq 'Return' -or $e.Key -eq 'Enter') {
        $lines = $TxtComputerNames.Text -split "`r`n|`n"
        $formattedLines = @()
        $changedLines = @()
        
        foreach ($line in $lines) {
            if ($line.Trim()) {
                $oldName = $line.Trim()
                $newName = ConvertTo-AFComputerName -RawName $oldName
                $formattedLines += $newName
                
                if ($oldName -ne $newName) {
                    $changedLines += "'$oldName' -> '$newName'"
                }
            }
        }
        
        $filteredLines = $formattedLines | Where-Object { $_ -ne "" }
        $newText = ($filteredLines -join "`r`n") + "`r`n"
        
        if ($TxtComputerNames.Text -ne $newText) {
            $TxtComputerNames.Text = $newText
            $TxtComputerNames.CaretIndex = $TxtComputerNames.Text.Length
            
            # Show what changed
            if ($changedLines.Count -gt 0) {
                Write-Line "Formatted computer names:" -Color "Cyan"
                foreach ($change in $changedLines) {
                    Write-Line "  $change" -Color "Green"
                }
                Write-Line ""
            }
        }
    }
})

# ==============================================================================
# Import File Logic
# ==============================================================================
$BtnImport.add_Click({
    Add-Type -AssemblyName System.Windows.Forms
    $openFileDialog = New-Object System.Windows.Forms.OpenFileDialog
    $openFileDialog.Filter = "Supported Files (*.txt;*.csv;*.xlsx)|*.txt;*.csv;*.xlsx"
    $openFileDialog.Title = "Select a file containing computer names/serial numbers"
    
    if ($openFileDialog.ShowDialog() -eq 'OK') {
        $file = $openFileDialog.FileName
        $script:LastImportedFile = $file
        $TxtStatusFile.Text = "File: $([System.IO.Path]::GetFileName($file))"
        $ext = [System.IO.Path]::GetExtension($file).ToLower()
        $importedNames = @()
        
        Write-Line "Importing from $file ..." -Color "Cyan"
        [System.Windows.Forms.Application]::DoEvents()
        
        try {
            # Define expected column names in order of preference
            $preferredColumns = @("computer name", "name", "serial number", "sn")
            
            if ($ext -eq '.txt') {
                # For text files, read all non-empty lines
                $allLines = Get-Content $file | Where-Object { $_.Trim() -ne ' '}
                
                # Check if first line looks like a header (contains common column names)
                $firstLine = $allLines[0].ToLower()
                $isHeader = $false
                foreach ($col in $preferredColumns) {
                    if ($firstLine -match $col) {
                        $isHeader = $true
                        break
                    }
                }
                
                # If header exists, skip it; otherwise use all lines
                if ($isHeader -and $allLines.Count -gt 1) {
                    $importedNames = $allLines[1..$($allLines.Count-1)]
                    Write-Line "Detected header row in text file, skipping first line." -Color "Yellow"
                } else {
                    $importedNames = $allLines
                }
            } 
            elseif ($ext -eq '.csv') {
                $csv = Import-Csv $file
                
                # Find which column to use based on preferred columns
                $columnNames = $csv[0].PSObject.Properties.Name
                $selectedColumn = $null
                
                foreach ($pref in $preferredColumns) {
                    $selectedColumn = $columnNames | Where-Object { $_ -like "*$pref*" } | Select-Object -First 1
                    if ($selectedColumn) {
                        Write-Line "Found column '$selectedColumn' matching preferred name '$pref'" -Color "Green"
                        break
                    }
                }
                
                if ($selectedColumn) {
                    $importedNames = $csv.$selectedColumn | Where-Object { $_ -and $_.ToString().Trim() -ne ' '}
                } else {
                    Write-Line "ERROR: Could not find expected column names." -Color "Red" -Bold $true
                    Write-Line "Expected columns (case-insensitive, partial match): $($preferredColumns -join ', ')" -Color "Yellow"
                    Write-Line "Found columns: $($columnNames -join ', ')" -Color "Yellow"
                    Write-Line "Please ensure your file has one of these column headers." -Color "Yellow"
                    $BtnExecute.IsEnabled = $true
                    return
                }
            } 
            elseif ($ext -eq '.xlsx') {
                $xlsx = Import-Excel $file
                
                # Find which column to use based on preferred columns
                $columnNames = $xlsx[0].PSObject.Properties.Name
                $selectedColumn = $null
                
                foreach ($pref in $preferredColumns) {
                    $selectedColumn = $columnNames | Where-Object { $_ -like "*$pref*" } | Select-Object -First 1
                    if ($selectedColumn) {
                        Write-Line "Found column '$selectedColumn' matching preferred name '$pref'" -Color "Green"
                        break
                    }
                }
                
                if ($selectedColumn) {
                    $importedNames = $xlsx.$selectedColumn | Where-Object { $_ -and $_.ToString().Trim() -ne ' '}
                } else {
                    Write-Line "ERROR: Could not find expected column names." -Color "Red" -Bold $true
                    Write-Line "Expected columns (case-insensitive, partial match): $($preferredColumns -join ', ')" -Color "Yellow"
                    Write-Line "Found columns: $($columnNames -join ', ')" -Color "Yellow"
                    Write-Line "Please ensure your file has one of these column headers." -Color "Yellow"
                    $BtnExecute.IsEnabled = $true
                    return
                }
            }

            # Validate we actually got data
            if ($importedNames.Count -eq 0) {
                Write-Line "WARNING: No data found in the imported file." -Color "Yellow" -Bold $true
                return
            }

            # Get current lines that are not empty
            $existingLines = @()
            if ($TxtComputerNames.Text.Trim()) {
                $existingLines = $TxtComputerNames.Text -split "`r`n|`n" | Where-Object { $_.Trim() -ne ' '}
            }
            
            # Format each imported name and add to collection
            $formattedNames = @()
            $skippedNames = @()
            
            foreach ($name in $importedNames) {
                if (-not [string]::IsNullOrWhiteSpace($name)) {
                    $formattedName = ConvertTo-AFComputerName -RawName $name.ToString()
                    if ($formattedName) {
                        $formattedNames += $formattedName
                    } else {
                        $skippedNames += $name
                    }
                }
            }
            
            # Report any skipped names
            if ($skippedNames.Count -gt 0) {
                Write-Line "WARNING: Skipped $($skippedNames.Count) invalid entries: $($skippedNames -join ', ')" -Color "Yellow"
            }
            
            # Combine existing and new names, remove duplicates
            $allNames = $existingLines + $formattedNames
            $uniqueNames = $allNames | Select-Object -Unique
            
            # Build the new text with each name on its own line
            $newText = ""
            for ($i = 0; $i -lt $uniqueNames.Count; $i++) {
                $newText += $uniqueNames[$i]
                if ($i -lt $uniqueNames.Count - 1) {
                    $newText += "`r`n"
                }
            }
            
            # Add a final newline if there are any names
            if ($uniqueNames.Count -gt 0) {
                $newText += "`r`n"
            }
            
            $TxtComputerNames.Text = $newText
            
            Write-Line "Successfully imported and formatted $($formattedNames.Count) computer names." -Color "Green"
            if ($skippedNames.Count -gt 0) {
                Write-Line "Skipped $($skippedNames.Count) invalid entries." -Color "Yellow"
            }
            Write-Line "Total unique computer names: $($uniqueNames.Count)" -Color "Cyan"
            
        } catch {
            Write-Line "ERROR importing file: $($_.Exception.Message)" -Color "Red" -Bold $true
            Write-Line "Please check the file format and try again." -Color "Yellow"
        }
    }
    if ($RadNew.IsChecked -and $ChkTechRefreshPackage.IsChecked) {
        Load-TechRefreshPackage -FilePath $file
        if ($script:IsTechRefreshPackageLoaded) {
            $TxtComputerNames.Text = (
                $script:TechRefreshPackageRows |
                ForEach-Object { $_.ComputerName.ToString().Trim().ToUpper() } |
                Sort-Object -Unique
            ) -join "`r`n"
            if ($TxtComputerNames.Text) { $TxtComputerNames.Text += "`r`n" }
            Sync-TargetList

            # Auto-populate inventory from Tech Refresh rows
            $invAdded = 0
            foreach ($row in $script:TechRefreshPackageRows) {
                $sn   = if ($row.SerialNumber) { $row.SerialNumber.ToString().Trim().ToUpper() } else { '' }
                $cnam = if ($row.ComputerName) { $row.ComputerName.ToString().Trim().ToUpper() } else { '' }
                if ([string]::IsNullOrWhiteSpace($sn) -and [string]::IsNullOrWhiteSpace($cnam)) { continue }

                # Look up ShortName from mapping
                $modelName = if ($row.Model) { $row.Model.ToString().Trim() } else { '' }
                $shortName = ''
                if ($modelName) {
                    $mapMatch = $script:ShortNameMappings | Where-Object { $_.Model -eq $modelName } | Select-Object -First 1
                    if ($mapMatch) { $shortName = $mapMatch.ShortName }
                }

                try {
                    Add-InventoryRecord `
                        -Custodian    (if ($row.POC) { $row.POC.ToString().Trim().ToUpper() } else { '' }) `
                        -Unit         (if ($row.Organization) { $row.Organization.ToString().Trim().ToUpper() } else { '' }) `
                        -DutyPhone    (if ($row.Extension) { $row.Extension.ToString().Trim() } else { '' }) `
                        -Building     (if ($row.BuildingNum) { $row.BuildingNum.ToString().Trim().ToUpper() } else { '' }) `
                        -Room         (if ($row.RoomNum) { $row.RoomNum.ToString().Trim().ToUpper() } else { '' }) `
                        -SerialNumber $sn `
                        -ComputerName $cnam `
                        -Replaced     (if ($row.OldSerial) { $row.OldSerial.ToString().Trim().ToUpper() } else { '' }) `
                        -Type         $modelName `
                        -MacAddress   (if ($row.MAC) { $row.MAC.ToString().Trim().ToUpper() } else { '' }) `
                        -Series       (if ($row.Series) { $row.Series.ToString().Trim().ToUpper() } else { '' }) `
                        -ShortName    $shortName
                    $invAdded++
                } catch {
                    Write-Line "  Skipped inventory for $sn — already exists or error" -Color "Yellow"
                }
            }
            if ($invAdded -gt 0) {
                Refresh-InventoryCache
                Write-Line "Added $invAdded row(s) to inventory from Tech Refresh file." -Color "Green"
            }
        }
    }
})
# ==============================================================================
# 5. UI Event Wiring (Interactive Logic)
# ==============================================================================
# Logic to show/hide the "New Computer Details" group box
$RadNew.add_Checked({
    if ($ChkTechRefreshPackage.IsChecked) {
        $GrpNewDetails.Visibility = 'Collapsed'
    } else {
        $GrpNewDetails.Visibility = 'Visible'
    }
    $ChkTechRefreshPackage.IsEnabled = $true

    if ($LstTargets.SelectedItem) {
        $selected = [string]$LstTargets.SelectedItem.Name
        $record = Find-InventoryRecord -SearchValue $selected
        Populate-NewComputerDetails -InventoryRecord $record
    }
})

$RadNew.add_Unchecked({
    $GrpNewDetails.Visibility = 'Collapsed'
    $ChkTechRefreshPackage.IsEnabled = $false
    $ChkTechRefreshPackage.IsChecked = $false
})

# Toggle New Computer Details when Tech Refresh Package checkbox changes
$ChkTechRefreshPackage.add_Checked({
    $GrpNewDetails.Visibility = 'Collapsed'
    if ($script:LastImportedFile) {
        Load-TechRefreshPackage -FilePath $script:LastImportedFile
    }
})
$ChkTechRefreshPackage.add_Unchecked({
    if ($RadNew.IsChecked) {
        $GrpNewDetails.Visibility = 'Visible'
    }
    if ($script:LastImportedFile) {
        $TxtStatusFile.Text = "File: $([System.IO.Path]::GetFileName($script:LastImportedFile))"
    }
})

# Logic to Enable/Disable the "Recycle Bin" checkbox for Remove Computer
$RadRemove.add_Checked({
    $ChkRecycleBin.IsEnabled = $true
})
$RadRemove.add_Unchecked({
    $ChkRecycleBin.IsEnabled = $false
    $ChkRecycleBin.IsChecked = $false
})


# Sync target list with textbox and show inventory details for selected computer
$TxtComputerNames.add_TextChanged({ Sync-TargetList })

#LstTargets SelectionChanged event handler
$LstTargets.add_SelectionChanged({
    if ($LstTargets.SelectedItem) {
        $selected = [string]$LstTargets.SelectedItem.Name
        $record = Find-InventoryRecord -SearchValue $selected
        Set-InventoryFieldsFromRecord -Record $record -SearchValue $selected

        # If no inventory record found but Tech Refresh package is loaded,
        # populate inventory fields from the package row for verification
        if ($null -eq $record -and $script:IsTechRefreshPackageLoaded) {
            $trRow = $script:TechRefreshLookup[$selected.ToUpper()]
            if ($trRow) {
                $TxtInvStatus.Text       = 'Tech Refresh row — verify below'
                $TxtInvCustodian.Text    = if ($trRow.POC) { [string]$trRow.POC } else { '' }
                $TxtInvUnit.Text         = if ($trRow.Organization) { [string]$trRow.Organization } else { '' }
                $TxtInvDutyPhone.Text    = if ($trRow.Extension) { [string]$trRow.Extension } else { '' }
                $TxtInvBuilding.Text     = if ($trRow.BuildingNum) { [string]$trRow.BuildingNum } else { '' }
                $TxtInvRoom.Text         = if ($trRow.RoomNum) { [string]$trRow.RoomNum } else { '' }
                $TxtInvSerial.Text       = if ($trRow.SerialNumber) { [string]$trRow.SerialNumber } else { '' }
                $TxtInvComputerName.Text = if ($trRow.ComputerName) { [string]$trRow.ComputerName } else { '' }
                $TxtInvReplaced.Text     = if ($trRow.OldSerial) { [string]$trRow.OldSerial } else { '' }
                $TxtInvType.Text         = if ($trRow.Model) { [string]$trRow.Model } else { '' }
                $TxtInvMAC.Text          = if ($trRow.MAC) { [string]$trRow.MAC } else { '' }
                $TxtInvSeries.Text       = if ($trRow.Series) { [string]$trRow.Series } else { '' }

                # ShortName from mapping
                $modelName = if ($trRow.Model) { $trRow.Model.ToString().Trim() } else { '' }
                $shortName = ''
                if ($modelName) {
                    $mapMatch = $script:ShortNameMappings | Where-Object { $_.Model -eq $modelName } | Select-Object -First 1
                    if ($mapMatch) { $shortName = $mapMatch.ShortName }
                }
                $TxtInvShortName.Text = $shortName
            }
        }
        
        # Populate New Computer Details when a computer is selected
        # Only auto-populate if we're in New Computer mode
        if ($RadNew.IsChecked) {
            Populate-NewComputerDetails -InventoryRecord $record
        }
    }
})

# CheckBox click handler for the ListView items
$LstTargets.add_MouseLeftButtonUp({
    Start-Sleep -Milliseconds 50
    Update-SelectAllButtonState
})

# Clear All button — clears the target computer textbox
$BtnClearAll.add_Click({
    $TxtComputerNames.Text = ''
    Sync-TargetList
})

# Select All / Deselect All toggle button
$BtnSelectAll.add_Click({
    if ($BtnSelectAll.Content -eq "Select All") {
        Set-AllSelectionState -Selected $true
    } else {
        Set-AllSelectionState -Selected $false
    }
})

$BtnISEPrimary.add_Click({
    Start-Process $Global:AppConfig.CiscoISE.PrimaryUrl
})

$BtnISEBackup.add_Click({
    Start-Process $Global:AppConfig.CiscoISE.BackupUrl
})

$ChkDevMode.add_Checked({
    $Global:AppConfig.Development = $true
    Save-AppConfig
})

$ChkDevMode.add_Unchecked({
    $Global:AppConfig.Development = $false
    Save-AppConfig
})

$BtnRefreshInventory.add_Click({
    Refresh-InventoryCache
    Sync-TargetList
    Write-Line "Inventory reloaded from $script:InventoryFile" -Color "Cyan"
})

$BtnAddInventory.add_Click({
    $serialNumber = $TxtInvSerial.Text.Trim().ToUpper()
    $computerName = $TxtInvComputerName.Text.Trim().ToUpper()
    $unit         = $TxtInvUnit.Text.Trim().ToUpper()
    $macAddress   = $TxtInvMAC.Text.Trim().ToUpper()

    if ([string]::IsNullOrWhiteSpace($serialNumber) -or
        [string]::IsNullOrWhiteSpace($computerName) -or
        [string]::IsNullOrWhiteSpace($unit)) {
        Write-Line "ERROR: Serial Number, Computer Name, and Unit are required to add inventory." -Color "Red" -Bold $true
        return
    }

    try {
        Add-InventoryRecord `
            -Custodian   $TxtInvCustodian.Text.Trim().ToUpper() `
            -Unit        $unit `
            -DutyPhone   $TxtInvDutyPhone.Text.Trim() `
            -Building    $TxtInvBuilding.Text.Trim().ToUpper() `
            -Room        $TxtInvRoom.Text.Trim().ToUpper() `
            -SerialNumber $serialNumber `
            -ComputerName $computerName `
            -Replaced    $TxtInvReplaced.Text.Trim().ToUpper() `
            -Type        $TxtInvType.Text.Trim().ToUpper() `
            -MacAddress  $macAddress `
            -Series      $TxtInvSeries.Text.Trim().ToUpper() `
            -ShortName   $TxtInvShortName.Text.Trim().ToUpper() | Out-Null

        Refresh-InventoryCache
        Sync-TargetList
        Write-Line "Inventory entry added for $computerName ($serialNumber)." -Color "Green" -Bold $true
        $record = Find-InventoryRecord -SearchValue $serialNumber
        Set-InventoryFieldsFromRecord -Record $record -SearchValue $serialNumber
    } catch {
        Write-Line "ERROR updating inventory: $($_.Exception.Message)" -Color "Red" -Bold $true
    }
})

# Populate New Computer Details
$BtnPopulateFromInventory.add_Click({
    if ($LstTargets.SelectedItem) {
        $selected = [string]$LstTargets.SelectedItem.Name
        $record = Find-InventoryRecord -SearchValue $selected
        if ($record) {
            Populate-NewComputerDetails -InventoryRecord $record
            Write-Line "Manually populated New Computer Details from $selected" -Color "Green"
        } else {
            Write-Line "No inventory record found for $selected" -Color "Yellow"
        }
    } else {
        Write-Line "Please select a computer from the target list first" -Color "Yellow"
    }
})

# ==============================================================================
# MAC Address Click-to-Copy Functionality
# ==============================================================================
$TxtInvMAC.add_MouseLeftButtonUp({
    if (-not [string]::IsNullOrWhiteSpace($TxtInvMAC.Text)) {
        try {
            # Copy to clipboard
            [System.Windows.Clipboard]::SetText($TxtInvMAC.Text.Trim())
            
            # Provide visual feedback in the output console
            Write-Line "✓ MAC Address copied to clipboard: $($TxtInvMAC.Text.Trim())" -Color "Green"
            
            # Flash the background briefly for visual confirmation
            $originalBg = $TxtInvMAC.Background
            $TxtInvMAC.Background = [System.Windows.Media.Brushes]::LightGreen
            $timer = New-Object System.Windows.Threading.DispatcherTimer
            $timer.Interval = [TimeSpan]::FromMilliseconds(300)
            $timer.Add_Tick({
                $TxtInvMAC.Background = $originalBg
                $timer.Stop()
                $timer.Dispose()
            })
            $timer.Start()
        } catch {
            Write-Line "Failed to copy MAC address: $($_.Exception.Message)" -Color "Red"
        }
    } else {
        Write-Line "No MAC address to copy" -Color "Yellow"
    }
})

# Add a tooltip to indicate clickability
$TxtInvMAC.ToolTip = "Click to copy MAC address to clipboard"

# Close the window cleanly if needed (optional hook)
$MainWindow.add_Closed({
    # Add any cleanup tasks here if needed when the app closes
})

# ==============================================================================
# 6. Execute Button Logic
# ==============================================================================
$BtnExecute.add_Click({
    $BtnExecute.IsEnabled = $false
    
    $isDevMode = $ChkDevMode.IsChecked -eq $true
    Clear-Output
    if ($isDevMode) {
        Write-Line "=== RUNNING IN DEVELOPMENT MODE (Commands will NOT execute) ===" -Color "Yellow" -Bold $true
        Write-Line ""
    } else {
        Write-Line "Executing action... Please wait." -Color "Cyan"
        Write-Line ""
    }
    
    [System.Windows.Forms.Application]::DoEvents()

    # ======================================================================
    # USER MANAGEMENT tab actions
    # ======================================================================
    if ($PanelUser.Visibility -eq 'Visible' -or ($MainTabControl -and $TabUser.IsSelected)) {
        $selectedUserItems = @(Get-CheckedUserItems)
        $userNames = @(Get-CheckedUserNames)

        if ($userNames.Count -eq 0) {
            Write-Line "ERROR: Please search for and select at least one user." -Color "Red" -Bold $true
            $BtnExecute.IsEnabled = $true
            return
        }

        $draServer = $Global:AppConfig.DRA.ServerFQDN
        Write-Line "Selected user account(s) ($($userNames.Count)): $($userNames -join ', ')" -Color "Cyan"
        Write-Line ""

        if ($RadUserGet.IsChecked) {
            if ($userNames.Count -ne 1) {
                Write-Line "ERROR: Get User Info supports one checked user at a time." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }

            $userName = $userNames[0]
            Write-Result "Action: Get user info for '$userName'"
            if ($isDevMode) {
                Write-Command "Get-User -UserName '$userName' -DraServer '$draServer'"
            } elseif ($script:SimulationMode) {
                $user = Get-ToolADUser -Identity $userName -Properties MemberOf -ErrorAction Stop
                Set-UserFieldsFromRecord -Record (ConvertTo-UserDisplayRecord -User $user)
                Write-Line "Simulation: loaded user details for '$userName'." -Color "Green"
            } else {
                Get-User -UserName $userName -DraServer $draServer | Out-String | ForEach-Object { Write-Line $_ }
                Write-Line "Retrieved user info for '$userName'." -Color "Green"
            }
        }
        elseif ($RadUserEnable.IsChecked) {
            Write-Result "Action: Enable user account(s)"
            if ($isDevMode) { Write-Command "Enable-User -UserName '$($userNames -join ',')' -DraServer '$draServer'" }
            else {
                Enable-User -UserName $userNames -DraServer $draServer -ErrorAction Stop
                Write-Line "Enable submitted for $($userNames.Count) user(s)." -Color "Green"
            }
        }
        elseif ($RadUserDisable.IsChecked) {
            Write-Result "Action: Disable user account(s)"
            if ($isDevMode) { Write-Command "Disable-User -UserName '$($userNames -join ',')' -DraServer '$draServer'" }
            else {
                Disable-User -UserName $userNames -DraServer $draServer -ErrorAction Stop
                Write-Line "Disable submitted for $($userNames.Count) user(s)." -Color "Green"
            }
        }
        elseif ($RadUserUnlock.IsChecked) {
            Write-Result "Action: Unlock user account(s)"
            if ($isDevMode) { Write-Command "Unlock-User -UserName '$($userNames -join ',')' -DraServer '$draServer'" }
            else {
                Unlock-User -UserName $userNames -DraServer $draServer -ErrorAction Stop
                Write-Line "Unlock submitted for $($userNames.Count) user(s)." -Color "Green"
            }
        }
        elseif ($RadUserCreateUDrive -and $RadUserCreateUDrive.IsChecked) {
            Write-Result "Action: Create U-Drive(s)"

            foreach ($userName in $userNames) {
                try {
                    $adUser = Get-ToolADUser -Identity $userName -Properties HomeDirectory,HomeDrive,Organization -ErrorAction Stop
                } catch {
                    Write-Line "ERROR: Could not resolve user '$userName'. $($_.Exception.Message)" -Color "Red" -Bold $true
                    continue
                }

                if (-not [string]::IsNullOrWhiteSpace([string]$adUser.HomeDirectory)) {
                    Write-Line "$userName already has HomeDirectory configured as '$(Format-OutputPath $adUser.HomeDirectory)'. Use Set User Property to change an existing U-Drive." -Color "Yellow"
                    continue
                }

                $existingDrive = if ($isDevMode) { $null } else { Find-UDrive -UserName $userName | Select-Object -First 1 }
                if ($existingDrive) {
                    $existingDriveDisplay = Format-OutputPath $existingDrive
                    Write-Line "Found existing U-Drive folder for '$userName': $existingDriveDisplay" -Color "Cyan"
                    if ($isDevMode) {
                        Write-Command "Set-User -UserName '$userName' -Property 'HomeDrive','HomeDirectory' -Value 'U:','$existingDriveDisplay' -DraServer '$draServer'"
                    } else {
                        Set-User -UserName $userName -Property @('HomeDrive','HomeDirectory') -Value @('U:', $existingDrive) -DraServer $draServer -ErrorAction Stop
                        Write-Line "Configured '$userName' to use existing U-Drive path." -Color "Green"
                    }
                    continue
                }

                $organizationGroup = Get-ToolUDriveOrganizationGroup -Organization $adUser.Organization
                if ([string]::IsNullOrWhiteSpace($organizationGroup)) {
                    Write-Line "ERROR: '$userName' does not have an Organization value to map for U-Drive creation." -Color "Red" -Bold $true
                    continue
                }

                $uDriveRoot = Get-ToolUDriveRootForOrganization -OrganizationGroup $organizationGroup
                if ([string]::IsNullOrWhiteSpace($uDriveRoot)) {
                    Write-Line "ERROR: No U-Drive root is configured for organization group '$organizationGroup' from user organization '$($adUser.Organization)'." -Color "Red" -Bold $true
                    continue
                }

                if ($isDevMode) {
                    Write-Line "Development mode: simulating no existing U-Drive folder found for '$userName'." -Color "Yellow"
                    Write-Command "Create-UDrive -UserName '$userName' -Organization '$organizationGroup'"
                } elseif ($script:SimulationMode) {
                    $newFolder = Join-Path $uDriveRoot $adUser.Name
                    Set-User -UserName $userName -Property @('HomeDrive','HomeDirectory') -Value @('U:', $newFolder) -DraServer $draServer -ErrorAction Stop
                    Write-Line "Simulation: configured '$userName' U-Drive as '$(Format-OutputPath $newFolder)'." -Color "Green"
                } else {
                    Create-UDrive -UserName $userName -Organization $organizationGroup -ErrorAction Stop |
                        ForEach-Object { Write-Line (Format-OutputPath $_) -Color "Green" }
                    Write-Line "Create U-Drive submitted for '$userName' using organization group '$organizationGroup'." -Color "Green"
                }
            }
        }
        elseif ($RadUserAddGroup.IsChecked -or $RadUserRemoveGroup.IsChecked -or $RadUserTestGroup.IsChecked) {
            $selectedGroups = @($LstSelectedGroups.Items | Where-Object { $_.IsSelected } | ForEach-Object {
                if ($_.SamAccountName) { [string]$_.SamAccountName }
                else { [string]$_.Name }
            } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })

            if ($selectedGroups.Count -eq 0) {
                Write-Line "ERROR: Please select at least one group." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }

            if ($RadUserAddGroup.IsChecked) {
                Write-Result "Action: Add selected user(s) to selected group(s)"
                if ($isDevMode) { Write-Command "Add-MemberToGroup -UserName '$($userNames -join ',')' -GroupName '$($selectedGroups -join ',')' -DraServer '$draServer'" }
                else {
                    Add-MemberToGroup -UserName $userNames -GroupName $selectedGroups -DraServer $draServer -ErrorAction Stop
                    Write-Line "Add to group submitted for $($userNames.Count) user(s) and $($selectedGroups.Count) group(s)." -Color "Green"
                }
            } elseif ($RadUserRemoveGroup.IsChecked) {
                Write-Result "Action: Remove selected user(s) from selected group(s)"
                if ($isDevMode) { Write-Command "Remove-MemberFromGroup -UserName '$($userNames -join ',')' -GroupName '$($selectedGroups -join ',')' -DraServer '$draServer'" }
                else {
                    Remove-MemberFromGroup -UserName $userNames -GroupName $selectedGroups -DraServer $draServer -ErrorAction Stop
                    Write-Line "Remove from group submitted for $($userNames.Count) user(s) and $($selectedGroups.Count) group(s)." -Color "Green"
                }
            } else {
                Write-Result "Action: Test selected user(s) against selected group(s)"
                if ($isDevMode) {
                    Write-Command "Test-GroupMembership -UserName '$($userNames -join ',')' -GroupName '$($selectedGroups -join ',')'"
                } else {
                    $results = @(Test-GroupMembership -UserName $userNames -GroupName $selectedGroups -ErrorAction Stop)
                    foreach ($result in $results) {
                        $color = if ($result.MembershipType -eq 'Direct') { 'Green' } elseif ($result.MembershipType -eq 'Recursive') { 'Cyan' } else { 'Yellow' }
                        Write-Line $result.Message -Color $color
                    }
                    Write-Line "Membership test completed for $($userNames.Count) user(s) and $($selectedGroups.Count) group(s)." -Color "Green"
                }
            }
        }
        elseif ($RadUserSet.IsChecked) {
            if ($userNames.Count -ne 1) {
                Write-Line "ERROR: Set User Property supports one checked user at a time." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }

            $changes = @(Get-SelectedSetUserProperties)
            if ($changes.Count -eq 0) {
                Write-Line "ERROR: Check at least one User Details field to update." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }

            $userName = $userNames[0]
            Write-Result "Action: Set user properties for '$userName'"
            foreach ($change in $changes) {
                if ($isDevMode) {
                    Write-Command "Set-User -UserName '$userName' -Property '$($change.Property)' -Value '$($change.Value)' -DraServer '$draServer'"
                } else {
                    Set-User -UserName $userName -Property $change.Property -Value $change.Value -DraServer $draServer -ErrorAction Stop
                    Write-Line "Set $($change.Label) to '$($change.Value)'." -Color "Green"
                }
            }

            if (-not $isDevMode) {
                $updatedUser = Get-ToolADUser -Identity $userName -Properties MemberOf -ErrorAction SilentlyContinue
                if ($updatedUser) { Set-UserFieldsFromRecord -Record (ConvertTo-UserDisplayRecord -User $updatedUser) }
            }
        }
        elseif ($RadUserClone.IsChecked) {
            $selection = Get-CloneUserSelection
            if (-not $selection.PrimaryName) {
                Write-Line "ERROR: Select one Primary User and at least one Clone User." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }
            if ($selection.CloneNames.Count -eq 0) {
                Write-Line "ERROR: Select at least one Clone User after the Primary User." -Color "Red" -Bold $true
                $BtnExecute.IsEnabled = $true
                return
            }

            $cloneList = $selection.CloneNames -join ','
            Write-Result "Action: Clone groups from '$($selection.PrimaryName)' to $($selection.CloneNames.Count) user(s)"
            if ($isDevMode) {
                Write-Command "Clone-UserGroups -PrimaryUser '$($selection.PrimaryName)' -CloneUser '$cloneList'"
            } else {
                Clone-UserGroups -PrimaryUser $selection.PrimaryName -CloneUser $cloneList -ErrorAction Stop |
                    ForEach-Object { Write-Line ([string]$_) -Color "Green" }
                Write-Line "Clone groups submitted for $($selection.CloneNames.Count) user(s)." -Color "Green"
            }
        }
        Write-Line "--- Operation Complete ---" -Color "Gray" -Bold $true
        $BtnExecute.IsEnabled = $true
        return
    }

    # ======================================================================
    # GROUP MANAGEMENT tab actions
    # ======================================================================
    $isGroupContext = $false
    if ($MainTabControl -and $TabGroup) {
        $isGroupContext = $TabGroup.IsSelected
    } elseif ($PanelGroup) {
        $isGroupContext = $PanelGroup.Visibility -eq 'Visible'
    }

    if ($isGroupContext) {
        $groupName = $TxtGroupName.Text.Trim()
        if (-not $groupName) {
            Write-Line "ERROR: Group name is required." -Color "Red" -Bold $true
            $BtnExecute.IsEnabled = $true; return
        }
        $groupUsers = $TxtGroupUserInput.Text -split "`r`n|`n" | Where-Object { $_.Trim() -ne '' } | ForEach-Object { $_.Trim() }
        if ($groupUsers.Count -eq 0 -and -not $RadGroupTest.IsChecked) {
            Write-Line "ERROR: Please select at least one user." -Color "Red" -Bold $true
            $BtnExecute.IsEnabled = $true; return
        }
        $userNames = @()
        foreach ($u in $groupUsers) {
            $rec = Find-UserRecord -SearchValue $u
            if (-not $rec -and $u -match '^\S+\s+(.+)$') { $rec = Find-UserRecord -SearchValue $Matches[1] }
            if ($rec) { $userNames += "$($rec.DOD_ID)$($rec.PIV)" }
        }
        $draServer = $Global:AppConfig.DRA.ServerFQDN
        if ($RadGroupAdd.IsChecked) {
            Write-Result "Action: Add users to group '$groupName'"
            if ($isDevMode) { Write-Command "Add-MemberToGroup -UserName @($($userNames -join ',')) -GroupName '$groupName' -DraServer '$draServer'" }
            elseif ($script:SimulationMode) {
                Add-MemberToGroup -UserName $userNames -GroupName $groupName -DraServer $draServer -ErrorAction Stop
                Write-Line "Simulation: added $($userNames.Count) user(s) to '$groupName'." -Color "Green"
            }
            else { Write-Line "Add to group (simulated) for $($userNames.Count) user(s)." -Color "Green" }
        }
        elseif ($RadGroupRemove.IsChecked) {
            Write-Result "Action: Remove users from group '$groupName'"
            if ($isDevMode) { Write-Command "Remove-DRAGroupMembers -Identifier '$groupName' -User @($($userNames -join ','))" }
            elseif ($script:SimulationMode) {
                Remove-MemberFromGroup -UserName $userNames -GroupName $groupName -DraServer $draServer -ErrorAction Stop
                Write-Line "Simulation: removed $($userNames.Count) user(s) from '$groupName'." -Color "Green"
            }
            else { Write-Line "Remove from group (simulated) for $($userNames.Count) user(s)." -Color "Green" }
        }
        elseif ($RadGroupClone.IsChecked) {
            $primary = $TxtGroupPrimary.Text.Trim(); $clone = $TxtGroupClone.Text.Trim()
            if (-not $primary -or -not $clone) { Write-Line "ERROR: Both Primary and Clone users required." -Color "Red" -Bold $true; $BtnExecute.IsEnabled = $true; return }
            Write-Result "Action: Clone groups from '$primary' to '$clone'"
            if ($isDevMode) { Write-Command "Clone-UserGroups -PrimaryUser '$primary' -CloneUser '$clone'" }
            elseif ($script:SimulationMode) {
                Clone-UserGroups -PrimaryUser $primary -CloneUser $clone -ErrorAction Stop | Out-Null
                Write-Line "Simulation: cloned groups from '$primary' to '$clone'." -Color "Green"
            }
            else { Write-Line "Clone groups (simulated)." -Color "Green" }
        }
        elseif ($RadGroupTest.IsChecked) {
            Write-Result "Action: Test membership in '$groupName'"
            if ($groupUsers.Count -gt 0) {
                $testUserNames = @()
                foreach ($groupUser in $groupUsers) {
                    $uRec = Find-UserRecord -SearchValue $groupUser
                    if (-not $uRec -and $groupUser -match '^\S+\s+(.+)$') { $uRec = Find-UserRecord -SearchValue $Matches[1] }
                    if ($uRec) { $testUserNames += "$($uRec.DOD_ID)$($uRec.PIV)" }
                }

                if ($testUserNames.Count -gt 0) {
                    if ($isDevMode) { Write-Command "Test-GroupMembership -GroupName '$groupName' -UserName '$($testUserNames -join ',')'" }
                    else {
                        $results = @(Test-GroupMembership -GroupName $groupName -UserName $testUserNames)
                        foreach ($result in $results) {
                            $color = if ($result.MembershipType -eq 'Direct') { 'Green' } elseif ($result.MembershipType -eq 'Recursive') { 'Cyan' } else { 'Yellow' }
                            Write-Line $result.Message -Color $color
                        }
                    }
                }
            } else {
                if ($isDevMode) { Write-Command "Get-DRAGroupMembers -Identifier '$groupName'" }
                elseif ($script:SimulationMode) {
                    $members = Get-ToolADGroupMember -Identity $groupName
                    Write-Line "Simulation: $($members.Count) member(s) found in '$groupName'." -Color "Green"
                }
                else { Write-Line "Listing group members (simulated) for '$groupName'." -Color "Green" }
            }
        }
        Write-Line "--- Operation Complete ---" -Color "Gray" -Bold $true
        $BtnExecute.IsEnabled = $true; return
    }

    # ======================================================================
    # COMPUTER MANAGEMENT tab actions
    # ======================================================================
    # Get only SELECTED computers from the list
    $selectedComputers = @()
    foreach ($item in $LstTargets.Items) {
        if ($item.IsSelected) {
            $selectedComputers += $item.Name
        }
    }
    
    $compString = $selectedComputers -join ', '
    
    if ($selectedComputers.Count -eq 0) {
        Write-Line "ERROR: Please select at least one computer from the target list." -Color "Red" -Bold $true
        $BtnExecute.IsEnabled = $true
        return
    }
    
    Write-Line "Selected computers ($($selectedComputers.Count)): $compString" -Color "Cyan"
    Write-Line ""

    $draServer = $Global:AppConfig.DRA.ServerFQDN

    try {
        if ($RadGet.IsChecked) {
            Write-Result "Action: Get Computer Info for selected computers"
            if ($isDevMode) {
                Write-Command "Get-Computer -ComputerName $compString -DraServer $draServer"
            } else {
                $results = Get-Computer -ComputerName $selectedComputers -DraServer $draServer -ErrorAction Stop | 
                    Select-Object IsDisabled, DNSHostName, Class, Description, DistinguishedName, FriendlyName, FriendlyPath, IsManaged, ObjectClass, AdditionalAttributes | 
                    Format-List | 
                    Out-String
                Write-Result $results
            }
        }
        elseif ($RadEnable.IsChecked) {
            Write-Result "Action: Enable Computer for selected computers"
            if ($isDevMode) {
                Write-Command "Enable-Computer -ComputerName $compString -DraServer $draServer"
            } else {
                Enable-Computer -ComputerName $selectedComputers -DraServer $draServer -ErrorAction Stop
                Write-Line "Enable command sent successfully." -Color "Green"
            }
        }
        elseif ($RadDisable.IsChecked) {
            Write-Result "Action: Disable Computer for selected computers"
            if ($isDevMode) {
                Write-Command "Disable-Computer -ComputerName $compString -DraServer $draServer"
            } else {
                Disable-Computer -ComputerName $selectedComputers -DraServer $draServer -ErrorAction Stop
                Write-Line "Disable command sent successfully." -Color "Green"
            }
        }
        elseif ($RadReset.IsChecked) {
            Write-Result "Action: Reset Computer for selected computers"
            if ($isDevMode) {
                Write-Command "Reset-Computer -ComputerName $compString -DraServer $draServer"
            } else {
                Reset-Computer -ComputerName $selectedComputers -DraServer $draServer -ErrorAction Stop
                Write-Line "Reset command sent successfully." -Color "Green"
            }
        }
        elseif ($RadNew.IsChecked) {
            if ($ChkTechRefreshPackage.IsChecked) {
                # DEBUG: uncomment to see variable state
                # Write-Line "DEBUG: IsLoaded=$script:IsTechRefreshPackageLoaded, RowCount=$($script:TechRefreshPackageRows.Count)" -Color "Yellow"
                if (-not $script:IsTechRefreshPackageLoaded -or $script:TechRefreshPackageRows.Count -eq 0) {
                    Write-Line "ERROR: Please import a Tech Refresh Package first." -Color "Red" -Bold $true
                    Write-Line "  Required columns: ComputerName, Organization, BuildingNum, RoomNum" -Color "Yellow"
                }
                else {
                    $sg = $Global:AppConfig.ActiveDirectory.DefaultSecurityGroup
                    $defaultOS = $Global:AppConfig.NamingConvention.DefaultOS

                    $selectedSet = @{}

                    foreach ($computer in $selectedComputers) {
                        $selectedSet[$computer.ToString().Trim().ToUpper()] = $true
                    }

                    $packageRows = $script:TechRefreshPackageRows | Where-Object {
                        $selectedSet.ContainsKey($_.ComputerName.ToString().Trim().ToUpper())
                    }

                    Write-Result "Action: Create New Computers from Tech Refresh Package"
                    Write-Line "Package rows selected: $($packageRows.Count)" -Color "Cyan"

                    foreach ($row in $packageRows) {
                        $computerName = $row.ComputerName.ToString().Trim().ToUpper()
                        $org = $row.Organization.ToString().Trim()
                        $bldg = $row.BuildingNum.ToString().Trim().ToUpper()
                        $room = $row.RoomNum.ToString().Trim().ToUpper()

                        # Build description from row data (same pattern as GUI auto-calc)
                        $descOs    = if ($row.OS) { $row.OS.ToString().Trim().ToUpper() } else { $defaultOS }
                        if ($row.Model) {
                            $modelName = $row.Model.ToString().Trim()
                            $mapMatch  = $script:ShortNameMappings | Where-Object { $_.Model -eq $modelName } | Select-Object -First 1
                            $descModel = if ($mapMatch) { $mapMatch.ShortName } else { $modelName.Replace(' ', '').ToUpper() }
                        } else { $descModel = '' }
                        $descOrg   = $org.Replace(' ', '')
                        $descBldg  = if ($bldg) { "B-$bldg" } else { '' }
                        $descRoom  = if ($room) { "R-$room" } else { '' }
                        $descExt   = if ($row.Extension) { "X-$($row.Extension.ToString().Replace(' ', ''))" } else { '' }
                        $descParts = @($descOs, $descModel, $descOrg, $descBldg, $descRoom, $descExt) | Where-Object { $_ -ne '' }
                        $desc = $descParts -join ', '

                        if ($isDevMode) {
                            Write-Command "New-Computer -ComputerName '$computerName' -Organization '$org' -BuildingNum '$bldg' -RoomNum '$room' -Description '$desc' -DraServer '$draServer' -SecurityGroup '$sg'"
                        }
                        else {
                            New-Computer `
                                -ComputerName $computerName `
                                -Organization $org `
                                -BuildingNum $bldg `
                                -RoomNum $room `
                                -Description $desc `
                                -DraServer $draServer `
                                -SecurityGroup $sg `
                                -ErrorAction Stop
                        }
                    }

                    Write-Line "Tech Refresh Package processing complete." -Color "Green"
                }
            }
            else {
                Write-Result "Action: Create New Computer for selected computers"
                $org = $CmbOrg.Text
                $bldg = $TxtBldg.Text
                $room = $TxtRoom.Text
                $desc = $TxtDesc.Text
                $sg = $Global:AppConfig.ActiveDirectory.DefaultSecurityGroup

                if ([string]::IsNullOrWhiteSpace($org) -or [string]::IsNullOrWhiteSpace($bldg) -or [string]::IsNullOrWhiteSpace($room)) {
                    Write-Line "ERROR: Organization, Building, and Room are required for new computers." -Color "Red" -Bold $true
                } else {
                    if ($isDevMode) {
                        # Format computer names with spaces after commas for better display
                        $compStringDisplay = $selectedComputers -join ', '
                        # Build the command with proper spacing
                        $commandString = "New-Computer -ComputerName $compStringDisplay -Organization '$org' -BuildingNum '$bldg' -RoomNum '$room' -Description '$desc' -DraServer '$draServer' -SecurityGroup '$sg'"
                        Write-Command $commandString
                    } else {
                        New-Computer -ComputerName $selectedComputers -Organization $org -BuildingNum $bldg -RoomNum $room -Description $desc -DraServer $draServer -SecurityGroup $sg -ErrorAction Stop
                        Write-Line "Create New Computer command sent successfully." -Color "Green"
                    }
                }
            }
        }
        elseif ($RadRemove.IsChecked) {
            Write-Result "Action: Remove Computer for selected computers"
            
            if ($isDevMode) {
                if ($ChkRecycleBin.IsChecked) {
                    Write-Command "Remove-Computer -ComputerName $compString -RecycleBin -DraServer $draServer"
                } else {
                    Write-Command "Remove-Computer -ComputerName $compString -DraServer $draServer"
                }
            } else {
                if ($ChkRecycleBin.IsChecked) { 
                    Remove-Computer -ComputerName $selectedComputers -RecycleBin -DraServer $draServer -ErrorAction Stop
                    Write-Line "Remove command (including Recycle Bin) sent successfully." -Color "Green"
                } else {
                    Remove-Computer -ComputerName $selectedComputers -DraServer $draServer -ErrorAction Stop
                    Write-Line "Remove command sent successfully." -Color "Green"
                }
            }
        }
    } catch {
        Write-Line "AN ERROR OCCURRED:" -Color "Red" -Bold $true
        Write-Line $_.Exception.Message -Color "Red"
    }

    Write-Line "--- Operation Complete ---" -Color "Gray" -Bold $true
    $BtnExecute.IsEnabled = $true
})

# ==============================================================================
# 7. Helper Functions for Colored Output
# ==============================================================================

Function Write-Line {
    param(
        [string]$Text,
        [string]$Color = "White",
        [bool]$Bold = $false
    )
    
    $paragraph = New-Object System.Windows.Documents.Paragraph
    $run = New-Object System.Windows.Documents.Run($Text + "`r`n")
    $run.Foreground = Get-BrushColor -Color $Color
    if ($Bold) { $run.FontWeight = "Bold" }
    $paragraph.Inlines.Add($run)
    $global:TxtOutput.Document.Blocks.Add($paragraph)
}

Function Write-Command {
    param([string]$CommandText)

    $paragraph = New-Object System.Windows.Documents.Paragraph

    # Normalize spacing
    $text = $CommandText -replace ',([^\s])', ', $1'
    $text = $text -replace '\s+', ' '
    $text = $text.Trim()

    # Match the cmdlet/function name at the beginning
    if ($text -match '^(?<cmdlet>[A-Za-z][A-Za-z0-9]*-[A-Za-z][A-Za-z0-9-]*)') {
        $cmdletText = $matches.cmdlet

        $cmdletRun = New-Object System.Windows.Documents.Run($cmdletText)
        $cmdletRun.Foreground = Get-BrushColor -Color "Cyan"
        $cmdletRun.FontWeight = "Bold"
        $paragraph.Inlines.Add($cmdletRun)

        $text = $text.Substring($cmdletText.Length).TrimStart()
    }

    # Find all parameter names that begin at start or after whitespace
    $paramPattern = '(?<!\S)-(?<param>[A-Za-z][A-Za-z0-9]*)'
    $paramMatches = [regex]::Matches($text, $paramPattern)

    for ($i = 0; $i -lt $paramMatches.Count; $i++) {
        $currentMatch = $paramMatches[$i]
        $paramName = $currentMatch.Groups["param"].Value

        $valueStart = $currentMatch.Index + $currentMatch.Length

        if ($i -lt ($paramMatches.Count - 1)) {
            $nextMatch = $paramMatches[$i + 1]
            $valueLength = $nextMatch.Index - $valueStart
        }
        else {
            $valueLength = $text.Length - $valueStart
        }

        $valueText = $text.Substring($valueStart, $valueLength).Trim()

        # Space before each parameter
        $paragraph.Inlines.Add((New-Object System.Windows.Documents.Run(" ")))

        # Parameter name
        $paramRun = New-Object System.Windows.Documents.Run("-" + $paramName)
        $paramRun.Foreground = Get-BrushColor -Color "Magenta"
        $paragraph.Inlines.Add($paramRun)

        # Optional value
        if (-not [string]::IsNullOrWhiteSpace($valueText)) {
            $paragraph.Inlines.Add((New-Object System.Windows.Documents.Run(" ")))

            $parts = [regex]::Matches($valueText, '[^,]+|,')
            foreach ($part in $parts) {
                $run = New-Object System.Windows.Documents.Run($part.Value)

                if ($part.Value -eq ',') {
                    $run.Foreground = Get-BrushColor -Color "Red"
                    $run.FontWeight = "Bold"
                }
                else {
                    $run.Foreground = Get-BrushColor -Color "White"
                }

                $paragraph.Inlines.Add($run)
            }
        }
    }

    $global:TxtOutput.Document.Blocks.Add($paragraph)
}

Function Write-Result {
    param([string]$ResultText)
    
    $lines = $ResultText -split "`r`n"
    $paragraph = New-Object System.Windows.Documents.Paragraph
    
    foreach ($line in $lines) {
        if ([string]::IsNullOrWhiteSpace($line)) { 
            $paragraph.Inlines.Add((New-Object System.Windows.Documents.Run("`r`n")))
            continue 
        }
        
        $run = New-Object System.Windows.Documents.Run($line + "`r`n")
        
        if ($line -match "^Action:") {
            $run.Foreground = Get-BrushColor -Color "Green"
            $run.FontWeight = "Bold"
        }
        elseif ($line -match "^(IsDisabled|DNSHostName|Class|Description|DistinguishedName|FriendlyName|FriendlyPath|ObjectClass)") {
            $run.Foreground = Get-BrushColor -Color "Cyan"
        }
        elseif ($line -match "^ERROR") {
            $run.Foreground = Get-BrushColor -Color "Red"
            $run.FontWeight = "Bold"
        }
        elseif ($line -match "^WARNING") {
            $run.Foreground = Get-BrushColor -Color "Yellow"
            $run.FontWeight = "Bold"
        }
        elseif ($line -match "successfully") {
            $run.Foreground = Get-BrushColor -Color "Green"
        }
        elseif ($line -match "--- Operation Complete ---") {
            $run.Foreground = Get-BrushColor -Color "Gray"
            $run.FontWeight = "Bold"
        }
        else {
            $run.Foreground = Get-BrushColor -Color "White"
        }
        
        $paragraph.Inlines.Add($run)
    }
    
    $global:TxtOutput.Document.Blocks.Add($paragraph)
}

Function Clear-Output {
    $global:TxtOutput.Document.Blocks.Clear()
}

Function Get-BrushColor {
    param([string]$Color)
    
    switch ($Color) {
        "Red" { return [System.Windows.Media.Brushes]::Red }
        "Green" { return [System.Windows.Media.Brushes]::LimeGreen }
        "Blue" { return [System.Windows.Media.Brushes]::DodgerBlue }
        "Yellow" { return [System.Windows.Media.Brushes]::Yellow }
        "Cyan" { return [System.Windows.Media.Brushes]::Cyan }
        "Magenta" { return [System.Windows.Media.Brushes]::Magenta }
        "DarkCyan" { return [System.Windows.Media.Brushes]::DarkCyan }
        "DarkOrange" { return [System.Windows.Media.Brushes]::DarkOrange }
        "Gray" { return [System.Windows.Media.Brushes]::Gray }
        "White" { return [System.Windows.Media.Brushes]::White }
        default { return [System.Windows.Media.Brushes]::White }
    }
}

Function Test-AFComputerName {
    param([string]$ComputerName)
    
    $prefix = $TxtPrefix.Text.Trim().ToUpper()
    $typeStr = $CmbDeviceType.SelectedItem.Content
    $type = "W"
    if ($typeStr -match "^(W|L|T|V)\s*-") { $type = $matches[1] }
    
    $expectedPattern = "^$prefix$type-"
    
    if ($ComputerName -match $expectedPattern) {
        return $true
    } else {
        return $false
    }
}

Function ConvertTo-AFComputerName {
    param([string]$RawName)
    
    $RawName = $RawName.Trim().ToUpper()
    if ([string]::IsNullOrWhiteSpace($RawName)) { return "" }
    
    $prefix = $TxtPrefix.Text.Trim().ToUpper()
    $typeStr = $CmbDeviceType.SelectedItem.Content
    $type = "W"
    if ($typeStr -match "^(W|L|T|V)\s*-") { $type = $matches[1] }
    $snDigits = [int]$TxtSnDigits.Text
    
    # Extract the serial number from whatever format was entered
    if ($RawName -match "-(\d+[A-Z0-9]*)$") {
        $serial = $matches[1]
    } elseif ($RawName -match "^([A-Z]+[WLT]-?)?(\d+[A-Z0-9]*)$") {
        $serial = $matches[2]
    } else {
        $serial = $RawName -replace '[^A-Z0-9]', ''
    }
    
    # Apply length limit
    if ($serial.Length -gt $snDigits) {
        $serial = $serial.Substring($serial.Length - $snDigits)
    }
    
    $finalName = "$prefix$type-$serial"
    
    # Final length check
    if ($finalName.Length -gt 15) {
        $finalName = $finalName.Substring(0, 15)
    }
    
    return $finalName
}

# ==============================================================================
# 8. region Application Header / Startup Banner
# ==============================================================================
Write-Line "=============================================" -Color "DarkGray"
Write-Line " NetIQ DRA Administrative GUI Tool" -Color "Cyan" -Bold $true
Write-Line "---------------------------------------------" -Color "DarkGray"
Write-Line " Author : SMSgt Michael A. Gates" -Color "Gray"
Write-Line " Comm   : 609.754.5217" -Color "Gray"
Write-Line " DSN    : 312.650.5217" -Color "Gray"
Write-Line " Email  : michael.gates.1@us.af.mil" -Color "Gray"
Write-Line "---------------------------------------------" -Color "DarkGray"
Write-Line " Simplifies NetIQ DRA PowerShell operations" -Color "DarkGray"
Write-Line " using REST-based cmdlets and custom functions" -Color "DarkGray"
Write-Line "=============================================" -Color "DarkGray"
Write-Line ""

# ==============================================================================
# 9. Launch the Application
# ==============================================================================
$MainWindow.ShowDialog() | Out-Null
