Private/UI/Show-PIMActivationDialog.ps1

function Show-PIMActivationDialog {
    <#
    .SYNOPSIS
        Displays a dialog for PIM role activation requirements.
     
    .DESCRIPTION
        Shows a Windows Forms dialog to collect scheduling, justification, ticket information,
        and optional Azure reduced scope required for PIM role activation. Returns user input or
        cancellation status.
     
    .PARAMETER RequiresJustification
        Specifies that justification text is mandatory for activation.
     
    .PARAMETER RequiresTicket
        Specifies that a ticket number is mandatory for activation.
     
    .PARAMETER OptionalJustification
        Displays justification field as optional with recommended usage note.
 
    .PARAMETER ShowAzureReducedScope
        Displays an optional Azure reduced-scope picker for Azure Resource role activations.
 
    .PARAMETER ProfileRoleItems
        Selected role items used for profile save/update actions and activation schedule validation.
 
    .PARAMETER ProfileDefaultDuration
        Requested duration used for profile save/update actions and activation schedule validation.
     
    .EXAMPLE
        Show-PIMActivationDialog -RequiresJustification
        Shows dialog with required justification field.
     
    .EXAMPLE
        Show-PIMActivationDialog -RequiresTicket -OptionalJustification
        Shows dialog with required ticket field and optional justification.
     
    .OUTPUTS
        PSCustomObject
        Returns object with Justification, TicketNumber, AzureReducedScope, ScheduleForLater,
        ScheduledStartTime, ScheduledStartTimeUtc, and Cancelled properties.
     
    .NOTES
        Requires System.Windows.Forms assembly for GUI display.
    #>

    [CmdletBinding()]
    param(
        [switch]$RequiresJustification,
        [switch]$RequiresTicket,
        [switch]$OptionalJustification,
        [switch]$ShowAzureReducedScope,
        [object[]]$AzureRoleItems = @(),
        [object[]]$ProfileRoleItems = @(),
        [hashtable]$ProfileDefaultDuration,
        [string]$ActivationProfileName = '',
        [switch]$AllowSaveAsProfile,
        [switch]$AllowProfileManagement
    )
    
    # Initialize result object
    $result = [PSCustomObject]@{
        Justification = ""
        TicketNumber  = ""
        TicketSystem  = "ServiceNow"
        AzureReducedScope = ""
        ScheduleForLater = $false
        ScheduledStartTime = $null
        ScheduledStartTimeUtc = $null
        ProfileSaved  = $false
        ProfileDeleted = $false
        Cancelled     = $true
    }
    
    # Create main form
    $form = New-Object System.Windows.Forms.Form -Property @{
        Text            = "Role Activation Requirements"
        Size            = [System.Drawing.Size]::new(500, 350)
        StartPosition   = 'CenterScreen'
        FormBorderStyle = 'FixedDialog'
        MaximizeBox     = $false
        MinimizeBox     = $false
        BackColor       = [System.Drawing.Color]::White
        TopMost         = $true
        ShowInTaskbar   = $true
    }
    
    $y = 10
    $justificationControl = $null
    $txtTicket = $null
    $cmbTicketSystem = $null
    $chkUseReducedScope = $null
    $reducedScopePanel = $null
    $lblReducedOriginalScopeValue = $null
    $lblReducedSelectedScopeValue = $null
    $lblReducedScopeStatus = $null
    $btnReducedScopeReset = $null
    $chkScheduleActivation = $null
    $dtpScheduleDate = $null
    $dtpScheduleTime = $null
    $lblScheduleStatus = $null
    $reducedScopeState = [ordered]@{
        OriginalScope        = ''
        OriginalDisplayName  = ''
        CurrentParentScope   = ''
        SelectedScope        = ''
        SelectedDisplayName  = ''
        SuppressChildChange  = $false
    }

    $saveActivationProfile = {
        param(
            [string]$ProfileName,
            [string]$SuccessMessage
        )

        if ([string]::IsNullOrWhiteSpace($ProfileName)) { return $null }
        if (-not $ProfileRoleItems -or $ProfileRoleItems.Count -eq 0) {
            Show-TopMostMessageBox -Message 'No roles are available to save in this activation profile.' -Title 'Activation Profile' -Icon Warning
            return $null
        }

        $duration = if ($ProfileDefaultDuration) { $ProfileDefaultDuration } else { @{ Hours = 8; Minutes = 0; TotalMinutes = 480 } }
        $savedProfile = Save-PIMActivationProfile -ProfileName $ProfileName -SelectedRoles @($ProfileRoleItems) -DefaultDuration $duration
        $result.ProfileSaved = $true
        Show-TopMostMessageBox -Message ($SuccessMessage -f $savedProfile.Name, $savedProfile.Roles.Count) -Title 'Activation Profile' -Icon Information
        return $savedProfile
    }

    if (-not [string]::IsNullOrWhiteSpace($ActivationProfileName)) {
        $form.Text = "Activate Profile - $ActivationProfileName"

        $lblProfile = New-Object System.Windows.Forms.Label -Property @{
            Text      = "Activation Profile: $ActivationProfileName"
            Location  = [System.Drawing.Point]::new(10, $y)
            Size      = [System.Drawing.Size]::new(460, 22)
            Font      = [System.Drawing.Font]::new('Segoe UI', 9, [System.Drawing.FontStyle]::Bold)
            ForeColor = [System.Drawing.Color]::FromArgb(32, 31, 30)
        }
        $form.Controls.Add($lblProfile)
        $y += 28
    }

    $scheduleWindow = Resolve-PIMActivationSchedule -RoleItems $ProfileRoleItems -RequestedDuration $ProfileDefaultDuration
    $defaultScheduleStart = (Get-Date).AddMinutes(30)
    $defaultScheduleStart = $defaultScheduleStart.AddSeconds(-1 * $defaultScheduleStart.Second).AddMilliseconds(-1 * $defaultScheduleStart.Millisecond)
    if ($scheduleWindow.MaxStartLocal -and $defaultScheduleStart -gt $scheduleWindow.MaxStartLocal) {
        $defaultScheduleStart = $scheduleWindow.MaxStartLocal
    }
    if ($defaultScheduleStart -lt (Get-Date)) {
        $defaultScheduleStart = Get-Date
    }

    $scheduleGroup = New-Object System.Windows.Forms.GroupBox -Property @{
        Text      = 'Activation Schedule'
        Location  = [System.Drawing.Point]::new(10, $y)
        Size      = [System.Drawing.Size]::new(460, 108)
        Font      = [System.Drawing.Font]::new('Segoe UI', 9)
        ForeColor = [System.Drawing.Color]::FromArgb(32, 31, 30)
    }

    $chkScheduleActivation = New-Object System.Windows.Forms.CheckBox -Property @{
        Name      = 'chkScheduleActivation'
        Text      = 'Schedule for later'
        Location  = [System.Drawing.Point]::new(12, 22)
        Size      = [System.Drawing.Size]::new(150, 22)
        Font      = [System.Drawing.Font]::new('Segoe UI', 9)
        Cursor    = [System.Windows.Forms.Cursors]::Hand
    }
    $scheduleGroup.Controls.Add($chkScheduleActivation)

    $lblScheduleDate = New-Object System.Windows.Forms.Label -Property @{
        Text     = 'Date'
        Location = [System.Drawing.Point]::new(20, 52)
        Size     = [System.Drawing.Size]::new(38, 22)
        Font     = [System.Drawing.Font]::new('Segoe UI', 9)
    }
    $scheduleGroup.Controls.Add($lblScheduleDate)

    $dtpScheduleDate = New-Object System.Windows.Forms.DateTimePicker -Property @{
        Name     = 'dtpScheduleDate'
        Location = [System.Drawing.Point]::new(60, 49)
        Size     = [System.Drawing.Size]::new(150, 23)
        Format   = [System.Windows.Forms.DateTimePickerFormat]::Short
        Enabled  = $false
        Font     = [System.Drawing.Font]::new('Segoe UI', 9)
    }
    try { $dtpScheduleDate.MinDate = (Get-Date).Date } catch { }
    if ($scheduleWindow.MaxStartLocal -and $scheduleWindow.MaxStartLocal.Date -ge $dtpScheduleDate.MinDate) {
        try { $dtpScheduleDate.MaxDate = $scheduleWindow.MaxStartLocal.Date } catch { }
    }
    try { $dtpScheduleDate.Value = $defaultScheduleStart.Date } catch { }
    $scheduleGroup.Controls.Add($dtpScheduleDate)

    $lblScheduleTime = New-Object System.Windows.Forms.Label -Property @{
        Text     = 'Time'
        Location = [System.Drawing.Point]::new(225, 52)
        Size     = [System.Drawing.Size]::new(40, 22)
        Font     = [System.Drawing.Font]::new('Segoe UI', 9)
    }
    $scheduleGroup.Controls.Add($lblScheduleTime)

    $dtpScheduleTime = New-Object System.Windows.Forms.DateTimePicker -Property @{
        Name         = 'dtpScheduleTime'
        Location     = [System.Drawing.Point]::new(265, 49)
        Size         = [System.Drawing.Size]::new(80, 23)
        Format       = [System.Windows.Forms.DateTimePickerFormat]::Custom
        CustomFormat = 'HH:mm'
        ShowUpDown   = $true
        Enabled      = $false
        Font         = [System.Drawing.Font]::new('Segoe UI', 9)
    }
    try { $dtpScheduleTime.Value = [datetime]::Today.AddHours($defaultScheduleStart.Hour).AddMinutes($defaultScheduleStart.Minute) } catch { }
    $scheduleGroup.Controls.Add($dtpScheduleTime)

    $scheduleStatusText = if ($scheduleWindow.MaxStartLocal) {
        "Latest start: $($scheduleWindow.MaxStartLocal.ToString('yyyy-MM-dd HH:mm'))"
    }
    else {
        'Future start uses your local time.'
    }
    $lblScheduleStatus = New-Object System.Windows.Forms.Label -Property @{
        Text      = $scheduleStatusText
        Location  = [System.Drawing.Point]::new(20, 76)
        Size      = [System.Drawing.Size]::new(420, 24)
        ForeColor = [System.Drawing.Color]::FromArgb(96, 94, 92)
        Font      = [System.Drawing.Font]::new('Segoe UI', 8)
    }
    $scheduleGroup.Controls.Add($lblScheduleStatus)

    $chkScheduleActivation.Add_CheckedChanged({
        $enabled = $chkScheduleActivation.Checked
        $dtpScheduleDate.Enabled = $enabled
        $dtpScheduleTime.Enabled = $enabled
    })

    $form.Controls.Add($scheduleGroup)
    $y += 118

    $getSelectedScheduleStart = {
        $dateValue = $dtpScheduleDate.Value
        $timeValue = $dtpScheduleTime.Value
        $combined = [datetime]::new(
            $dateValue.Year,
            $dateValue.Month,
            $dateValue.Day,
            $timeValue.Hour,
            $timeValue.Minute,
            0
        )
        return [datetime]::SpecifyKind($combined, [System.DateTimeKind]::Local)
    }

    $getSelectedReducedScope = {
        if ($reducedScopeState['SelectedScope']) { return [string]$reducedScopeState['SelectedScope'] }
        return ''
    }

    $getRoleDataFromItem = {
        param([object]$RoleItem)

        if ($RoleItem -is [System.Windows.Forms.ListViewItem]) { return $RoleItem.Tag }
        return $RoleItem
    }

    $getObjectPropertyValue = {
        param(
            [object]$InputObject,
            [string[]]$PropertyNames
        )

        if (-not $InputObject) { return $null }
        foreach ($propertyName in $PropertyNames) {
            if ($InputObject.PSObject.Properties[$propertyName] -and -not [string]::IsNullOrWhiteSpace([string]$InputObject.$propertyName)) {
                return [string]$InputObject.$propertyName
            }
        }

        return $null
    }

    $getRoleScope = {
        param([object]$RoleData)

        return & $getObjectPropertyValue $RoleData @('FullScope', 'Scope', 'DirectoryScopeId')
    }

    $getOriginalScopeDisplayName = {
        param(
            [object]$RoleData,
            [string]$Scope
        )

        $displayName = & $getObjectPropertyValue $RoleData @('ScopeDisplayName', 'ResourceDisplayName', 'SubscriptionName')
        if ([string]::IsNullOrWhiteSpace($displayName) -or $displayName -eq 'Subscription') {
            if ($Scope -match '^/subscriptions/[^/]+/resourceGroups/([^/]+)') {
                $displayName = [System.Uri]::UnescapeDataString($matches[1])
            }
            elseif ($Scope -match '^/subscriptions/([^/]+)$') {
                $displayName = & $getObjectPropertyValue $RoleData @('SubscriptionName')
                if ([string]::IsNullOrWhiteSpace($displayName) -or $displayName -eq 'Subscription') { $displayName = $matches[1] }
            }
            elseif ($Scope -match '^/providers/Microsoft\.Management/managementGroups/([^/]+)') {
                $displayName = $matches[1]
            }
        }

        if ([string]::IsNullOrWhiteSpace($displayName)) { $displayName = $Scope }

        $scopeType = & $getObjectPropertyValue $RoleData @('ScopeType')
        if (-not [string]::IsNullOrWhiteSpace($scopeType) -and $displayName -notlike "*($scopeType)") {
            return "$displayName ($scopeType)"
        }

        return $displayName
    }

    $getOriginalScopeOptionsFromSelectedRoles = {
        $scopeOptionsByScope = [ordered]@{}

        foreach ($roleItem in @($AzureRoleItems)) {
            $roleData = & $getRoleDataFromItem $roleItem
            if (-not $roleData) { continue }

            $scope = & $getRoleScope $roleData
            if ([string]::IsNullOrWhiteSpace($scope)) { continue }
            if (-not $scope.StartsWith('/')) { $scope = "/$scope" }
            while ($scope.Length -gt 1 -and $scope.EndsWith('/')) {
                $scope = $scope.Substring(0, $scope.Length - 1)
            }

            if ($scopeOptionsByScope.Contains($scope)) { continue }
            $displayName = & $getOriginalScopeDisplayName $roleData $scope

            $scopeOptionsByScope[$scope] = [PSCustomObject]@{
                DisplayName    = $displayName
                Name           = $displayName
                SubscriptionId = if ($scope -match '^/subscriptions/([^/]+)') { $matches[1] } else { $null }
                ResourceGroup  = $null
                ResourceId     = $null
                Scope          = $scope
                Type           = 'OriginalScope'
            }
        }

        return @($scopeOptionsByScope.Values | Sort-Object DisplayName)
    }

    $updateReducedScopeLabels = {
        if ($lblReducedOriginalScopeValue) {
            $originalText = if ($reducedScopeState['OriginalDisplayName']) { $reducedScopeState['OriginalDisplayName'] } else { 'Not loaded' }
            $lblReducedOriginalScopeValue.Text = $originalText
        }

        if ($lblReducedSelectedScopeValue) {
            $selectedText = if ($reducedScopeState['SelectedDisplayName']) { $reducedScopeState['SelectedDisplayName'] } else { 'No reduced scope selected' }
            $lblReducedSelectedScopeValue.Text = $selectedText
        }

        if ($btnReducedScopeReset) {
            $btnReducedScopeReset.Enabled = -not [string]::IsNullOrWhiteSpace([string]$reducedScopeState['SelectedScope'])
        }
    }

    $loadEligibleChildScopes = {
        param([string]$ParentScope)

        try {
            $cmbReducedResourceGroup.Items.Clear()
            $cmbReducedResourceGroup.Enabled = $false
            $cmbReducedResource.Items.Clear()
            $cmbReducedResource.Enabled = $false

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

            $reducedScopeState['CurrentParentScope'] = $ParentScope
            $lblReducedScopeStatus.Text = 'Loading eligible child scopes...'
            [System.Windows.Forms.Application]::DoEvents()

            Write-Verbose "Azure reduced scope: loading eligible child resources for scope '$ParentScope'."
            $childScopes = @(Get-AzureReducedScopeOptions -OriginalScope $ParentScope)

            $reducedScopeState['SuppressChildChange'] = $true
            try {
                foreach ($childScope in $childScopes) {
                    [void]$cmbReducedResourceGroup.Items.Add($childScope)
                }
                $cmbReducedResourceGroup.SelectedIndex = -1
            }
            finally {
                $reducedScopeState['SuppressChildChange'] = $false
            }

            $cmbReducedResourceGroup.Enabled = $childScopes.Count -gt 0
            if ($childScopes.Count -gt 0) {
                $lblReducedScopeStatus.Text = if ($reducedScopeState['SelectedScope']) {
                    'Choose a deeper child scope, or click OK to use the selected scope.'
                }
                else {
                    'Choose an eligible child scope.'
                }
            }
            else {
                $lblReducedScopeStatus.Text = if ($reducedScopeState['SelectedScope']) {
                    'No deeper eligible child scopes were returned. Click OK to use the selected scope.'
                }
                else {
                    'No eligible child scopes were returned for the original scope.'
                }
            }
        }
        catch {
            $lblReducedScopeStatus.Text = 'Unable to load eligible child scopes.'
            Show-TopMostMessageBox -Message "Unable to load eligible child scopes: $($_.Exception.Message)" -Title 'Reduced Scope' -Icon Warning
        }
    }

    $loadReducedScopeSubscriptions = {
        try {
            $lblReducedScopeStatus.Text = 'Loading original scopes...'
            $cmbReducedResourceGroup.Enabled = $false
            $cmbReducedResource.Enabled = $false
            $cmbReducedResourceGroup.Items.Clear()
            $cmbReducedResource.Items.Clear()
            [System.Windows.Forms.Application]::DoEvents()

            Write-Verbose 'Azure reduced scope: loading original scope options from selected Azure role(s).'
            $originalScopes = @(& $getOriginalScopeOptionsFromSelectedRoles)
            Write-Verbose "Azure reduced scope: selected Azure role(s) provided $($originalScopes.Count) original scope option(s)."

            if ($originalScopes.Count -eq 0) {
                $reducedScopeState['OriginalScope'] = ''
                $reducedScopeState['OriginalDisplayName'] = ''
                $reducedScopeState['CurrentParentScope'] = ''
                $reducedScopeState['SelectedScope'] = ''
                $reducedScopeState['SelectedDisplayName'] = ''
                & $updateReducedScopeLabels
                $lblReducedScopeStatus.Text = 'No original Azure scopes were available for reduced scope.'
                return
            }

            if ($originalScopes.Count -gt 1) {
                $reducedScopeState['OriginalScope'] = ''
                $reducedScopeState['OriginalDisplayName'] = 'Multiple original scopes selected'
                $reducedScopeState['CurrentParentScope'] = ''
                $reducedScopeState['SelectedScope'] = ''
                $reducedScopeState['SelectedDisplayName'] = ''
                & $updateReducedScopeLabels
                $lblReducedScopeStatus.Text = 'Reduced scope requires selected Azure roles to share one original scope.'
                return
            }

            $originalScope = $originalScopes[0]
            $reducedScopeState['OriginalScope'] = [string]$originalScope.Scope
            $reducedScopeState['OriginalDisplayName'] = [string]$originalScope.DisplayName
            $reducedScopeState['CurrentParentScope'] = [string]$originalScope.Scope
            $reducedScopeState['SelectedScope'] = ''
            $reducedScopeState['SelectedDisplayName'] = ''
            & $updateReducedScopeLabels
            & $loadEligibleChildScopes ([string]$originalScope.Scope)
        }
        catch {
            $lblReducedScopeStatus.Text = 'Unable to load reduced-scope options.'
            $chkUseReducedScope.Checked = $false
            Show-TopMostMessageBox -Message "Unable to load Azure reduced-scope options: $($_.Exception.Message)" -Title 'Reduced Scope' -Icon Warning
        }
    }

    $loadReducedScopeResourceGroups = {
        if ($reducedScopeState['SuppressChildChange']) { return }
        if (-not $cmbReducedResourceGroup.SelectedItem) { return }

        $selectedChildScope = $cmbReducedResourceGroup.SelectedItem
        if (-not $selectedChildScope.PSObject.Properties['Scope'] -or [string]::IsNullOrWhiteSpace([string]$selectedChildScope.Scope)) { return }

        $reducedScopeState['SelectedScope'] = [string]$selectedChildScope.Scope
        $reducedScopeState['SelectedDisplayName'] = [string]$selectedChildScope.DisplayName
        & $updateReducedScopeLabels
        & $loadEligibleChildScopes ([string]$selectedChildScope.Scope)
    }

    $loadReducedScopeResources = {
        if ([string]::IsNullOrWhiteSpace([string]$reducedScopeState['OriginalScope'])) { return }

        $reducedScopeState['SelectedScope'] = ''
        $reducedScopeState['SelectedDisplayName'] = ''
        & $updateReducedScopeLabels
        & $loadEligibleChildScopes ([string]$reducedScopeState['OriginalScope'])
    }
    
    # Add justification field if required or optional
    if ($RequiresJustification -or $OptionalJustification) {
        $labelText = if ($RequiresJustification) { "Justification (required):" } else { "Justification (optional - recommended):" }
        
        $lblJust = New-Object System.Windows.Forms.Label -Property @{
            Text      = $labelText
            Location  = [System.Drawing.Point]::new(10, $y)
            Size      = [System.Drawing.Size]::new(460, 20)
            Font      = [System.Drawing.Font]::new("Segoe UI", 9)
            ForeColor = [System.Drawing.Color]::FromArgb(32, 31, 30)
        }
        $y += 25
        
        $txtJust = New-Object System.Windows.Forms.TextBox -Property @{
            Name          = "txtJustification"
            Location      = [System.Drawing.Point]::new(10, $y)
            Size          = [System.Drawing.Size]::new(460, 80)
            Multiline     = $true
            AcceptsReturn = $true
            ScrollBars    = 'Vertical'
            Text          = "PowerShell activation"
        }
        $y += 90
        
        $form.Controls.AddRange(@($lblJust, $txtJust))
        # Store the justification textbox in a variable for later use
        $justificationControl = $txtJust
    }
    
    # Add ticket field if required
    if ($RequiresTicket) {
        $lblTicket = New-Object System.Windows.Forms.Label -Property @{
            Text     = "Ticket Number *"
            Location = [System.Drawing.Point]::new(10, $y)
            Size     = [System.Drawing.Size]::new(120, 23)
            Font     = [System.Drawing.Font]::new("Segoe UI", 9)
        }
        $form.Controls.Add($lblTicket)
        
        $txtTicket = New-Object System.Windows.Forms.TextBox -Property @{
            Name     = "txtTicket"
            Location = [System.Drawing.Point]::new(130, $y)
            Size     = [System.Drawing.Size]::new(280, 23)
            Font     = [System.Drawing.Font]::new("Segoe UI", 9)
        }
        $form.Controls.Add($txtTicket)
        
        $y += 30
        
        # Add ticket system dropdown
        $lblTicketSystem = New-Object System.Windows.Forms.Label -Property @{
            Text     = "Ticket System"
            Location = [System.Drawing.Point]::new(10, $y)
            Size     = [System.Drawing.Size]::new(120, 23)
            Font     = [System.Drawing.Font]::new("Segoe UI", 9)
        }
        $form.Controls.Add($lblTicketSystem)
        
        $cmbTicketSystem = New-Object System.Windows.Forms.ComboBox -Property @{
            Name          = "cmbTicketSystem"
            Location      = [System.Drawing.Point]::new(130, $y)
            Size          = [System.Drawing.Size]::new(280, 23)
            Font          = [System.Drawing.Font]::new("Segoe UI", 9)
            DropDownStyle = 'DropDownList'
        }
        
        # Add common ticket systems
        $ticketSystems = @('ServiceNow', 'Jira', 'Azure DevOps', 'ServiceDesk Plus', 'BMC Remedy', 'Cherwell', 'Other')
        $cmbTicketSystem.Items.AddRange($ticketSystems)
        
        # Try to use saved preference or default to ServiceNow
        $savedSystem = Get-SavedTicketSystem
        if ($savedSystem -and $ticketSystems -contains $savedSystem) {
            $cmbTicketSystem.SelectedItem = $savedSystem
        }
        else {
            $cmbTicketSystem.SelectedIndex = 0  # Default to ServiceNow
        }
        
        $form.Controls.Add($cmbTicketSystem)
        
        $y += 35
    }

    if ($ShowAzureReducedScope) {
        $chkUseReducedScope = New-Object System.Windows.Forms.CheckBox -Property @{
            Name      = 'chkUseAzureReducedScope'
            Text      = 'Use Reduced Scope'
            Location  = [System.Drawing.Point]::new(10, $y)
            Size      = [System.Drawing.Size]::new(180, 23)
            Font      = [System.Drawing.Font]::new('Segoe UI', 9)
            Cursor    = [System.Windows.Forms.Cursors]::Hand
        }
        $form.Controls.Add($chkUseReducedScope)
        $y += 28

        $reducedScopePanel = New-Object System.Windows.Forms.Panel -Property @{
            Name        = 'pnlAzureReducedScope'
            Location    = [System.Drawing.Point]::new(10, $y)
            Size        = [System.Drawing.Size]::new(460, 158)
            BackColor   = [System.Drawing.Color]::White
            BorderStyle = [System.Windows.Forms.BorderStyle]::None
            Visible     = $false
        }

        $lblSubscription = New-Object System.Windows.Forms.Label -Property @{
            Text     = 'Original Scope'
            Location = [System.Drawing.Point]::new(0, 5)
            Size     = [System.Drawing.Size]::new(115, 23)
            Font     = [System.Drawing.Font]::new('Segoe UI', 9)
        }
        $reducedScopePanel.Controls.Add($lblSubscription)

        $lblReducedOriginalScopeValue = New-Object System.Windows.Forms.Label -Property @{
            Name         = 'lblAzureReducedOriginalScope'
            Text         = 'Not loaded'
            Location     = [System.Drawing.Point]::new(120, 5)
            Size         = [System.Drawing.Size]::new(330, 23)
            Font         = [System.Drawing.Font]::new('Segoe UI', 9)
            AutoEllipsis = $true
        }
        $reducedScopePanel.Controls.Add($lblReducedOriginalScopeValue)

        $lblResourceGroup = New-Object System.Windows.Forms.Label -Property @{
            Text     = 'Selected Scope'
            Location = [System.Drawing.Point]::new(0, 35)
            Size     = [System.Drawing.Size]::new(115, 23)
            Font     = [System.Drawing.Font]::new('Segoe UI', 9)
        }
        $reducedScopePanel.Controls.Add($lblResourceGroup)

        $lblReducedSelectedScopeValue = New-Object System.Windows.Forms.Label -Property @{
            Name         = 'lblAzureReducedSelectedScope'
            Text         = 'No reduced scope selected'
            Location     = [System.Drawing.Point]::new(120, 35)
            Size         = [System.Drawing.Size]::new(250, 23)
            Font         = [System.Drawing.Font]::new('Segoe UI', 9)
            AutoEllipsis = $true
        }
        $reducedScopePanel.Controls.Add($lblReducedSelectedScopeValue)

        $btnReducedScopeReset = New-Object System.Windows.Forms.Button -Property @{
            Name      = 'btnAzureReducedScopeReset'
            Text      = 'Reset'
            Location  = [System.Drawing.Point]::new(380, 33)
            Size      = [System.Drawing.Size]::new(70, 25)
            Font      = [System.Drawing.Font]::new('Segoe UI', 8)
            Enabled   = $false
            Cursor    = [System.Windows.Forms.Cursors]::Hand
        }
        $reducedScopePanel.Controls.Add($btnReducedScopeReset)

        $lblEligibleChild = New-Object System.Windows.Forms.Label -Property @{
            Text     = 'Eligible Child'
            Location = [System.Drawing.Point]::new(0, 67)
            Size     = [System.Drawing.Size]::new(115, 23)
            Font     = [System.Drawing.Font]::new('Segoe UI', 9)
        }
        $reducedScopePanel.Controls.Add($lblEligibleChild)

        $cmbReducedResourceGroup = New-Object System.Windows.Forms.ComboBox -Property @{
            Name          = 'cmbAzureReducedChildScope'
            Location      = [System.Drawing.Point]::new(120, 65)
            Size          = [System.Drawing.Size]::new(330, 23)
            Font          = [System.Drawing.Font]::new('Segoe UI', 9)
            DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
            DisplayMember = 'DisplayName'
            Enabled       = $false
        }
        $reducedScopePanel.Controls.Add($cmbReducedResourceGroup)

        $lblResource = New-Object System.Windows.Forms.Label -Property @{
            Text     = 'Resource'
            Location = [System.Drawing.Point]::new(0, 95)
            Size     = [System.Drawing.Size]::new(115, 23)
            Font     = [System.Drawing.Font]::new('Segoe UI', 9)
            Visible  = $false
        }
        $reducedScopePanel.Controls.Add($lblResource)

        $cmbReducedResource = New-Object System.Windows.Forms.ComboBox -Property @{
            Name          = 'cmbAzureReducedResource'
            Location      = [System.Drawing.Point]::new(120, 93)
            Size          = [System.Drawing.Size]::new(330, 23)
            Font          = [System.Drawing.Font]::new('Segoe UI', 9)
            DropDownStyle = [System.Windows.Forms.ComboBoxStyle]::DropDownList
            DisplayMember = 'DisplayName'
            Enabled       = $false
            Visible       = $false
        }
        $reducedScopePanel.Controls.Add($cmbReducedResource)

        $lblReducedScopeStatus = New-Object System.Windows.Forms.Label -Property @{
            Text      = 'Choose an eligible child scope.'
            Location  = [System.Drawing.Point]::new(120, 95)
            Size      = [System.Drawing.Size]::new(330, 20)
            ForeColor = [System.Drawing.Color]::FromArgb(96, 94, 92)
            Font      = [System.Drawing.Font]::new('Segoe UI', 8)
        }
        $reducedScopePanel.Controls.Add($lblReducedScopeStatus)

        $lblReducedScopeNote = New-Object System.Windows.Forms.Label -Property @{
            Text      = "After choosing a child scope, the next eligible child level loads automatically. Click OK at the level you want to activate."
            Location  = [System.Drawing.Point]::new(120, 115)
            Size      = [System.Drawing.Size]::new(330, 34)
            ForeColor = [System.Drawing.Color]::FromArgb(96, 94, 92)
            Font      = [System.Drawing.Font]::new('Segoe UI', 8)
        }
        $reducedScopePanel.Controls.Add($lblReducedScopeNote)
        $form.Controls.Add($reducedScopePanel)

        $chkUseReducedScope.Add_CheckedChanged({
            $reducedScopePanel.Visible = $chkUseReducedScope.Checked
            if ($chkUseReducedScope.Checked -and [string]::IsNullOrWhiteSpace([string]$reducedScopeState['OriginalScope'])) {
                & $loadReducedScopeSubscriptions
            }
        })

        $cmbReducedResourceGroup.Add_SelectedIndexChanged({ & $loadReducedScopeResourceGroups })
        $btnReducedScopeReset.Add_Click({ & $loadReducedScopeResources })

        $y += 163
    }
    
    # Add optional justification note
    if ($OptionalJustification -and -not $RequiresJustification) {
        $lblNote = New-Object System.Windows.Forms.Label -Property @{
            Text      = "Note: While justification is optional, providing a clear reason helps with audit trails."
            Location  = [System.Drawing.Point]::new(10, $y)
            Size      = [System.Drawing.Size]::new(460, 40)
            ForeColor = [System.Drawing.Color]::FromArgb(96, 94, 92)
            Font      = [System.Drawing.Font]::new("Segoe UI", 8)
        }
        $y += 45
        $form.Controls.Add($lblNote)
    }
    
    $showProfileButtons = $AllowSaveAsProfile -or ($AllowProfileManagement -and -not [string]::IsNullOrWhiteSpace($ActivationProfileName))
    $clientWidth = if ($showProfileButtons) { 604 } else { 484 }
    $form.ClientSize = [System.Drawing.Size]::new($clientWidth, [Math]::Max(311, $y + 75))
    $buttonY = $form.ClientSize.Height - 45
    $cancelButtonX = $form.ClientSize.Width - 95
    $okButtonX = $cancelButtonX - 100

    # Create OK button with styling and validation
    $okButton = New-Object System.Windows.Forms.Button -Property @{
        Text         = "OK"
        DialogResult = [System.Windows.Forms.DialogResult]::None
        Location     = [System.Drawing.Point]::new($okButtonX, $buttonY)
        Size         = [System.Drawing.Size]::new(80, 30)
        BackColor    = [System.Drawing.Color]::FromArgb(0, 103, 184)
        ForeColor    = [System.Drawing.Color]::White
        FlatStyle    = [System.Windows.Forms.FlatStyle]::Flat
        Font         = [System.Drawing.Font]::new("Segoe UI", 9)
        Cursor       = [System.Windows.Forms.Cursors]::Hand
        Anchor       = ([System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right)
    }
    $okButton.FlatAppearance.BorderSize = 0
    
    # Add button hover effects
    $okButton.Add_MouseEnter({ $this.BackColor = [System.Drawing.Color]::FromArgb(0, 90, 158) })
    $okButton.Add_MouseLeave({ $this.BackColor = [System.Drawing.Color]::FromArgb(0, 103, 184) })
    
    # Validate required fields on OK click
    $okButton.Add_Click({
            $isValid = $true
            $validationMessage = ""
        
            # Validate ticket if required
            if ($RequiresTicket) {
                $ticketText = $form.Controls["txtTicket"].Text.Trim()
                if ([string]::IsNullOrWhiteSpace($ticketText)) {
                    $isValid = $false
                    $validationMessage += "Ticket number is required.{0}" -f [Environment]::NewLine
                }
            }
        
            # Validate justification if required
            if ($RequiresJustification -and $justificationControl -and [string]::IsNullOrWhiteSpace($justificationControl.Text)) {
                Show-TopMostMessageBox -Message "Justification is required for these roles." -Title "Validation Error" -Icon Warning
                return
            }
        
            if ($RequiresTicket -and $txtTicket -and [string]::IsNullOrWhiteSpace($txtTicket.Text)) {
                Show-TopMostMessageBox -Message "Ticket number is required for these roles." -Title "Validation Error" -Icon Warning
                return
            }
        
            if ($isValid) {
                # Set result values
                $result.Justification = if ($justificationControl) { $justificationControl.Text.Trim() } else { "" }
            
                if ($RequiresTicket) {
                    $result.TicketNumber = $form.Controls["txtTicket"].Text.Trim()
                    $result.TicketSystem = $cmbTicketSystem.SelectedItem.ToString()
                
                    # Save ticket system preference
                    Save-TicketSystemPreference -System $result.TicketSystem
                }

                if ($ShowAzureReducedScope -and $chkUseReducedScope -and $chkUseReducedScope.Checked) {
                    $selectedScope = & $getSelectedReducedScope
                    if ([string]::IsNullOrWhiteSpace($selectedScope)) {
                        Show-TopMostMessageBox -Message 'Choose an eligible child scope for reduced scope.' -Title 'Validation Error' -Icon Warning
                        return
                    }
                    $result.AzureReducedScope = $selectedScope
                }
                elseif ($ShowAzureReducedScope) {
                    $result.AzureReducedScope = ''
                }

                if ($chkScheduleActivation -and $chkScheduleActivation.Checked) {
                    $selectedScheduleStart = & $getSelectedScheduleStart
                    $scheduleResolution = Resolve-PIMActivationSchedule -RoleItems $ProfileRoleItems -RequestedDuration $ProfileDefaultDuration -ScheduleStartTime $selectedScheduleStart -Scheduled
                    if (-not $scheduleResolution.IsValid) {
                        Show-TopMostMessageBox -Message $scheduleResolution.ErrorMessage -Title 'Schedule Validation' -Icon Warning
                        return
                    }

                    $result.ScheduleForLater = $true
                    $result.ScheduledStartTime = $scheduleResolution.StartLocal
                    $result.ScheduledStartTimeUtc = $scheduleResolution.StartUtcString
                }
                else {
                    $result.ScheduleForLater = $false
                    $result.ScheduledStartTime = $null
                    $result.ScheduledStartTimeUtc = $null
                }
            
                $result.Cancelled = $false
                $form.DialogResult = [System.Windows.Forms.DialogResult]::OK
                $form.Close()
            }
            else {
                Show-TopMostMessageBox -Message $validationMessage.TrimEnd() -Title "Validation Error" -Icon Warning
            }
        })
    
    # Create Cancel button with styling
    $cancelButton = New-Object System.Windows.Forms.Button -Property @{
        Text         = "Cancel"
        DialogResult = [System.Windows.Forms.DialogResult]::Cancel
        Location     = [System.Drawing.Point]::new($cancelButtonX, $buttonY)
        Size         = [System.Drawing.Size]::new(80, 30)
        BackColor    = [System.Drawing.Color]::White
        ForeColor    = [System.Drawing.Color]::FromArgb(32, 31, 30)
        FlatStyle    = [System.Windows.Forms.FlatStyle]::Flat
        Font         = [System.Drawing.Font]::new("Segoe UI", 9)
        Cursor       = [System.Windows.Forms.Cursors]::Hand
        Anchor       = ([System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right)
    }
    $cancelButton.FlatAppearance.BorderSize = 1
    $cancelButton.FlatAppearance.BorderColor = [System.Drawing.Color]::FromArgb(200, 198, 196)
    
    $cancelButton.Add_MouseEnter({ $this.BackColor = [System.Drawing.Color]::FromArgb(245, 245, 245) })
    $cancelButton.Add_MouseLeave({ $this.BackColor = [System.Drawing.Color]::White })

    $profileButtons = @()
    $profileButtonX = 10

    if ($AllowSaveAsProfile -and [string]::IsNullOrWhiteSpace($ActivationProfileName)) {
        $saveProfileButton = New-Object System.Windows.Forms.Button -Property @{
            Text      = 'Save as Profile'
            Location  = [System.Drawing.Point]::new($profileButtonX, $buttonY)
            Size      = [System.Drawing.Size]::new(115, 30)
            BackColor = [System.Drawing.Color]::White
            ForeColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
            FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
            Font      = [System.Drawing.Font]::new('Segoe UI', 9)
            Cursor    = [System.Windows.Forms.Cursors]::Hand
            Anchor    = ([System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left)
        }
        $saveProfileButton.FlatAppearance.BorderSize = 1
        $saveProfileButton.FlatAppearance.BorderColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
        $saveProfileButton.Add_MouseEnter({
            $this.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
            $this.ForeColor = [System.Drawing.Color]::White
        })
        $saveProfileButton.Add_MouseLeave({
            $this.BackColor = [System.Drawing.Color]::White
            $this.ForeColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
        })
        $saveProfileButton.Add_Click({
            try {
                $nameResult = Show-PIMProfileNameDialog -Title 'Save Activation Profile'
                if ($nameResult.Cancelled) { return }
                & $saveActivationProfile $nameResult.ProfileName "Saved activation profile '{0}' with {1} role(s)."
            }
            catch {
                Show-TopMostMessageBox -Message "Failed to save activation profile: $($_.Exception.Message)" -Title 'Activation Profile' -Icon Error
            }
        })
        $profileButtons += $saveProfileButton
        $profileButtonX += 125
    }

    if ($AllowProfileManagement -and -not [string]::IsNullOrWhiteSpace($ActivationProfileName)) {
        $updateProfileButton = New-Object System.Windows.Forms.Button -Property @{
            Text      = 'Update Profile'
            Location  = [System.Drawing.Point]::new($profileButtonX, $buttonY)
            Size      = [System.Drawing.Size]::new(110, 30)
            BackColor = [System.Drawing.Color]::White
            ForeColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
            FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
            Font      = [System.Drawing.Font]::new('Segoe UI', 9)
            Cursor    = [System.Windows.Forms.Cursors]::Hand
            Anchor    = ([System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left)
        }
        $updateProfileButton.FlatAppearance.BorderSize = 1
        $updateProfileButton.FlatAppearance.BorderColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
        $updateProfileButton.Add_MouseEnter({
            $this.BackColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
            $this.ForeColor = [System.Drawing.Color]::White
        })
        $updateProfileButton.Add_MouseLeave({
            $this.BackColor = [System.Drawing.Color]::White
            $this.ForeColor = [System.Drawing.Color]::FromArgb(0, 120, 212)
        })
        $updateProfileButton.Add_Click({
            try {
                $confirmResult = [System.Windows.Forms.MessageBox]::Show(
                    $form,
                    "Update activation profile '$ActivationProfileName' with the current role set and duration?",
                    'Update Activation Profile',
                    [System.Windows.Forms.MessageBoxButtons]::YesNo,
                    [System.Windows.Forms.MessageBoxIcon]::Question
                )
                if ($confirmResult -ne [System.Windows.Forms.DialogResult]::Yes) { return }
                & $saveActivationProfile $ActivationProfileName "Updated activation profile '{0}' with {1} role(s)."
            }
            catch {
                Show-TopMostMessageBox -Message "Failed to update activation profile: $($_.Exception.Message)" -Title 'Activation Profile' -Icon Error
            }
        })
        $profileButtons += $updateProfileButton
        $profileButtonX += 120

        $deleteProfileButton = New-Object System.Windows.Forms.Button -Property @{
            Text      = 'Delete'
            Location  = [System.Drawing.Point]::new($profileButtonX, $buttonY)
            Size      = [System.Drawing.Size]::new(80, 30)
            BackColor = [System.Drawing.Color]::White
            ForeColor = [System.Drawing.Color]::FromArgb(164, 38, 44)
            FlatStyle = [System.Windows.Forms.FlatStyle]::Flat
            Font      = [System.Drawing.Font]::new('Segoe UI', 9)
            Cursor    = [System.Windows.Forms.Cursors]::Hand
            Anchor    = ([System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Left)
        }
        $deleteProfileButton.FlatAppearance.BorderSize = 1
        $deleteProfileButton.FlatAppearance.BorderColor = [System.Drawing.Color]::FromArgb(164, 38, 44)
        $deleteProfileButton.Add_MouseEnter({
            $this.BackColor = [System.Drawing.Color]::FromArgb(164, 38, 44)
            $this.ForeColor = [System.Drawing.Color]::White
        })
        $deleteProfileButton.Add_MouseLeave({
            $this.BackColor = [System.Drawing.Color]::White
            $this.ForeColor = [System.Drawing.Color]::FromArgb(164, 38, 44)
        })
        $deleteProfileButton.Add_Click({
            try {
                $confirmResult = [System.Windows.Forms.MessageBox]::Show(
                    $form,
                    "Delete activation profile '$ActivationProfileName'?",
                    'Delete Activation Profile',
                    [System.Windows.Forms.MessageBoxButtons]::YesNo,
                    [System.Windows.Forms.MessageBoxIcon]::Warning
                )
                if ($confirmResult -ne [System.Windows.Forms.DialogResult]::Yes) { return }

                $deleted = Manage-PIMProfiles -Action Delete -ProfileName $ActivationProfileName
                if ($deleted) {
                    $result.ProfileDeleted = $true
                    $result.Cancelled = $true
                    Show-TopMostMessageBox -Message "Deleted activation profile '$ActivationProfileName'." -Title 'Activation Profile' -Icon Information
                    $form.DialogResult = [System.Windows.Forms.DialogResult]::Cancel
                    $form.Close()
                }
                else {
                    Show-TopMostMessageBox -Message "Activation profile '$ActivationProfileName' was not found." -Title 'Activation Profile' -Icon Warning
                }
            }
            catch {
                Show-TopMostMessageBox -Message "Failed to delete activation profile: $($_.Exception.Message)" -Title 'Activation Profile' -Icon Error
            }
        })
        $profileButtons += $deleteProfileButton
    }
    
    # Add controls and set form properties
    $form.Controls.AddRange(@($okButton, $cancelButton) + $profileButtons)
    $form.AcceptButton = $okButton
    $form.CancelButton = $cancelButton
    
    # Ensure the form is brought to front and activated
    $form.Add_Shown({
            $this.Activate()
            $this.BringToFront()
            $this.TopMost = $true
            $this.Focus()
        })

    # Show dialog and process result
    $dialogResult = $form.ShowDialog()
    
    if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) {
        if (($RequiresJustification -or $OptionalJustification) -and $justificationControl) {
            $result.Justification = if ([string]::IsNullOrWhiteSpace($justificationControl.Text)) { "PowerShell activation" } else { $justificationControl.Text }
        }
        
        if ($RequiresTicket -and $txtTicket -and -not [string]::IsNullOrWhiteSpace($txtTicket.Text)) {
            $result.TicketNumber = $txtTicket.Text
        }

        if ($ShowAzureReducedScope -and $chkUseReducedScope -and $chkUseReducedScope.Checked) {
            $result.AzureReducedScope = & $getSelectedReducedScope
        }
    }
    else {
        $result.Cancelled = $true
    }
    
    $form.Dispose()
    return $result
}