Generate-IntuneVisualizationReport.ps1


<#PSScriptInfo
 
.VERSION 0.1
 
.GUID 66a23821-6af0-4989-bd26-5851e247e7ac
 
.AUTHOR Roy
 
.COMPANYNAME
 
.COPYRIGHT
 
.TAGS
 
.LICENSEURI
 
.PROJECTURI
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
.PRIVATEDATA
 
#>


<#
 
.DESCRIPTION
 Visualizing the Intune Enrollment
 
#>
 

param(  
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive")]
    [Parameter(Mandatory = $false, ParameterSetName = "ClientSecret")]
    [Parameter(Mandatory = $false, ParameterSetName = "Certificate")]
    [Parameter(Mandatory = $false, ParameterSetName = "Identity")]
    [Parameter(Mandatory = $false, ParameterSetName = "AccessToken")]
    [string[]]$RequiredScopes = @("User.Read.All", "Group.Read.All", "DeviceManagementConfiguration.Read.All", "DeviceManagementApps.Read.All", "DeviceManagementManagedDevices.Read.All", "Device.Read.All", "Mail.Send"),
    
    [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
    [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
    [Parameter(Mandatory = $false, ParameterSetName = "Identity")]
    [Parameter(Mandatory = $true, ParameterSetName = "AccessToken")]
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive")]
    [string]$TenantId,
    
    [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
    [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
    [Parameter(Mandatory = $false, ParameterSetName = "Interactive")]
    [string]$ClientId,
    
    [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
    [string]$ClientSecret,
    
    [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
    [string]$CertificateThumbprint,

    [Parameter(Mandatory = $true, ParameterSetName = "Identity")]
    [switch]$Identity,

    [Parameter(Mandatory = $true, ParameterSetName = "AccessToken")]
    [string]$AccessToken,

    [Parameter(Mandatory = $false)]
    [switch]$IncludeFilterDetails,

    [Parameter(Mandatory = $false)]
    [switch]$ExportToCsv,

    [Parameter(Mandatory = $false)]
    [string]$ExportPath,

    [Parameter(Mandatory = $false)]
    [switch]$GenerateHtmlReport,

    [Parameter(Mandatory = $false)]
    [string]$HtmlReportPath,

    [Parameter(Mandatory = $false)]
    [switch]$DebugMode
)

function Test-PSVersion {
    [CmdletBinding()]
    param()
    
    $minimumVersion = [Version]"7.0.0"
    $currentVersion = $PSVersionTable.PSVersion
    
    if ($currentVersion -lt $minimumVersion) {
        Write-Host "Error: This script requires PowerShell 7.0 or higher." -ForegroundColor Red
        Write-Host "Current PowerShell version: $($currentVersion)" -ForegroundColor Red
        Write-Host "Please install PowerShell 7 from https://github.com/PowerShell/PowerShell/releases" -ForegroundColor Yellow
        
        # Check if pwsh is installed but script was run in older version
        if (Get-Command pwsh -ErrorAction SilentlyContinue) {
            Write-Host "`nPowerShell 7 appears to be installed. You can run this script with:" -ForegroundColor Cyan
            Write-Host "pwsh -File `"$($MyInvocation.PSCommandPath)`"" -ForegroundColor Cyan

            # Ask if they want to relaunch with PowerShell 7
            do {
                $response = Read-Host "Would you like to run this script using PowerShell 7 now? (Y/N)"
            } while ($response -notmatch '^(Y|y|N|n)$')

            if ($response -match '^(Y|y)$') {
                Start-Process -FilePath "pwsh" -ArgumentList "-File `"$($MyInvocation.PSCommandPath)`"" -NoNewWindow
            }
            else {
                Write-Host "Script execution stopped." -ForegroundColor Yellow
            }
        }
        
        # Stop script execution
        exit
    }
    
    Write-Host "PowerShell version check passed: Running PowerShell $currentVersion" -ForegroundColor Green
}

# Check PowerShell version before proceeding
Test-PSVersion

function Connect-ToMgGraph {
    [CmdletBinding(DefaultParameterSetName = "Interactive")]
    param (
        [Parameter(Mandatory = $false, ParameterSetName = "Interactive")]
        [Parameter(Mandatory = $false, ParameterSetName = "ClientSecret")]
        [Parameter(Mandatory = $false, ParameterSetName = "Certificate")]
        [Parameter(Mandatory = $false, ParameterSetName = "Identity")]
        [Parameter(Mandatory = $false, ParameterSetName = "AccessToken")]
        [string[]]$RequiredScopes = @("User.Read"),
        
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [Parameter(Mandatory = $false, ParameterSetName = "Identity")]
        [Parameter(Mandatory = $true, ParameterSetName = "AccessToken")]
        [string]$TenantId,
        
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [string]$ClientId,
        
        [Parameter(Mandatory = $true, ParameterSetName = "ClientSecret")]
        [string]$ClientSecret,
        
        [Parameter(Mandatory = $true, ParameterSetName = "Certificate")]
        [string]$CertificateThumbprint,

        [Parameter(Mandatory = $true, ParameterSetName = "Identity")]
        [switch]$Identity,

        [Parameter(Mandatory = $true, ParameterSetName = "AccessToken")]
        [string]$AccessToken
    )
    
    # Check if Microsoft Graph module is installed and import it
    Install-Requirements
    
    # Determine authentication method based on parameters
    $AuthMethod = $PSCmdlet.ParameterSetName
    Write-Verbose "Using authentication method: $AuthMethod"
    
    # Check if already connected
    $contextInfo = Get-MgContext -ErrorAction SilentlyContinue
    $reconnect = $false
    
    if ($contextInfo) {
        # Check if we have all the required permissions for interactive auth
        if ($AuthMethod -eq "Interactive") {
            $currentScopes = $contextInfo.Scopes
            $missingScopes = $RequiredScopes | Where-Object { $_ -notin $currentScopes }
            
            if ($missingScopes) {
                Write-Verbose "Missing required scopes: $($missingScopes -join ', '). Reconnecting..."
                Disconnect-MgGraph -ErrorAction SilentlyContinue
                $reconnect = $true
            }
            else {
                Write-Verbose "Already connected with required scopes."
            }
        }
        else {
            # For other auth methods, disconnect and reconnect with the new credentials
            Write-Verbose "Switching to $AuthMethod authentication method..."
            Disconnect-MgGraph -ErrorAction SilentlyContinue
            $reconnect = $true
        }
    }
    else {
        $reconnect = $true
    }
    
    if ($reconnect) {
        try {
            # Define connection parameters based on authentication method
            switch ($AuthMethod) {
                "Interactive" {
                    $connectParams = @{
                        Scopes    = $RequiredScopes
                        NoWelcome = $true
                    }
                    if ($TenantId) { $connectParams.TenantId = $TenantId }
                    if ($ClientId) { $connectParams.ClientId = $ClientId }
                    Connect-MgGraph @connectParams
                }
                "ClientSecret" {
                    $secureSecret = ConvertTo-SecureString $ClientSecret -AsPlainText -Force
                    $credential = New-Object System.Management.Automation.PSCredential($ClientId, $secureSecret)
                    Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $credential -NoWelcome
                }
                "Certificate" {
                    Connect-MgGraph -TenantId $TenantId -ClientId $ClientId -CertificateThumbprint $CertificateThumbprint -NoWelcome
                }
                "Identity" {
                    $connectParams = @{
                        Identity  = $true
                        NoWelcome = $true
                    }
                    if ($TenantId) { $connectParams.TenantId = $TenantId }
                    Connect-MgGraph @connectParams
                }
                "AccessToken" {
                    $secureToken = ConvertTo-SecureString $AccessToken -AsPlainText -Force
                    Connect-MgGraph -AccessToken $secureToken -NoWelcome
                }
            }
            
            # Verify connection
            $newContext = Get-MgContext
            if ($newContext) {
                Write-Verbose "Successfully connected to Microsoft Graph as $($newContext.Account)"
                return $newContext
            }
            else {
                throw "Connection attempt completed but unable to confirm connection"
            }
        }
        catch {
            Write-Error "Error connecting to Microsoft Graph: $_"
            return $null
        }
    }
    
    return $contextInfo
}

function Install-Requirements {
    
    $PsVersion = $PSVersionTable.PSVersion.Major

    $requiredModules = @(
        "Microsoft.Graph.Authentication"
    )

    foreach ($module in $requiredModules) {
        if ($PsVersion -ge 7) {
            # For PowerShell 7+, install the most recent version
            $moduleInstalled = Get-Module -ListAvailable -Name $module
            
            if (-not $moduleInstalled) {
                Write-Host "Installing latest version of module: $module" -ForegroundColor Cyan
                Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck
            }
            else {
                Write-Host "Module $module is already installed." -ForegroundColor Green
            }
        }
        else {
            # For PowerShell 5.1, use version 2.25.0
            $moduleVersion = "2.25.0"
            $moduleInstalled = Get-Module -ListAvailable -Name $module | Where-Object { $_.Version -eq $moduleVersion }
        
            if (-not $moduleInstalled) {
                Write-Host "Installing module: $module version $moduleVersion" -ForegroundColor Cyan
                Install-Module -Name $module -Scope CurrentUser -Force -AllowClobber -RequiredVersion $moduleVersion -SkipPublisherCheck
            }
            else {
                Write-Host "Module $module version $moduleVersion is already installed." -ForegroundColor Green
            }
        }
    }

    # Import the required modules
    foreach ($module in $requiredModules) {
        if ($PsVersion -ge 7) {
            # For PowerShell 7+, import the latest available version
            $importedModule = Get-Module -Name $module
        
            if (-not $importedModule) {
                try {
                    Import-Module -Name $module -Force -ErrorAction Stop
                    Write-Host "Latest version of module $module imported successfully." -ForegroundColor Green
                }
                catch {
                    Write-Host "Failed to import module $module. Error: $_" -ForegroundColor Red
                    throw
                }
            }
            else {
                Write-Host "Module $module was already imported." -ForegroundColor Green
            }
        }
        else {
            # For PowerShell 5.1, import version 2.25.0
            $moduleVersion = "2.25.0"
            $importedModule = Get-Module -Name $module | Where-Object { $_.Version -eq $moduleVersion }
        
            if (-not $importedModule) {
                try {
                    Import-Module -Name $module -RequiredVersion $moduleVersion -Force -ErrorAction Stop
                    Write-Host "Module $module version $moduleVersion imported successfully." -ForegroundColor Green
                }
                catch {
                    Write-Host "Failed to import module $module version $moduleVersion. Error: $_" -ForegroundColor Red
                    throw
                }
            }
            else {
                Write-Host "Module $module version $moduleVersion was already imported." -ForegroundColor Green
            }
        }
    }
}

function Invoke-GraphRequestWithPaging {
    param (
        [string]$Uri,
        [string]$Method = "GET"
    )
    
    $results = @()
    $currentUri = $Uri
    
    do {
        try {
            $response = Invoke-MgGraphRequest -Uri $currentUri -Method $Method -OutputType PSObject -ErrorAction Stop
            
            # Add the current page of results
            if ($response.value) {
                $results += $response.value
            }
            
            # Get the next page URL if it exists
            $currentUri = $response.'@odata.nextLink'
        }
        catch {
            #write-warning "Error in GraphRequestWithPaging for URI $currentUri : $_"
            return $null
        }
    } while ($currentUri)
    
    return $results
}

function Get-IntuneEntities {
    param (
        [Parameter(Mandatory = $true)]
        [string]$EntityType,
        
        [Parameter(Mandatory = $false)]
        [string]$Filter = "",
        
        [Parameter(Mandatory = $false)]
        [string]$Select = "",
        
        [Parameter(Mandatory = $false)]
        [string]$Expand = ""
    )

    # Handle special cases for app management and specific deviceManagement endpoints
    if ($EntityType -like "deviceAppManagement/*" -or $EntityType -eq "deviceManagement/templates" -or $EntityType -eq "deviceManagement/intents") {
        $baseUri = "https://graph.microsoft.com/beta"
        $actualEntityType = $EntityType
    }
    else {
        $baseUri = "https://graph.microsoft.com/beta/deviceManagement"
        $actualEntityType = "$EntityType"
    }
    
    $currentUri = "$baseUri/$actualEntityType"
    if ($Filter) { $currentUri += "?`$filter=$Filter" }
    if ($Select) { $currentUri += $(if ($Filter) { "&" }else { "?" }) + "`$select=$Select" }
    if ($Expand) { $currentUri += $(if ($Filter -or $Select) { "&" }else { "?" }) + "`$expand=$Expand" }

    # Use the custom paging function instead of manual pagination
    $entities = Invoke-GraphRequestWithPaging -Uri $currentUri -Method "GET"
    
    if ($entities) {
        return $entities
    }
    else {
        return @()
    }
}

function Get-GroupInfo {
    param (
        [Parameter(Mandatory = $true)]
        [string]$GroupId
    )

    # Use a script-level cache for group info to avoid repeated API calls
    if (-not $script:GroupCache) {
        $script:GroupCache = @{}
    }

    # Check if we already have this group cached
    if ($script:GroupCache.ContainsKey($GroupId)) {
        return $script:GroupCache[$GroupId]
    }

    try {
        $groupUri = "https://graph.microsoft.com/v1.0/groups/$GroupId"
        $group = Invoke-MgGraphRequest -Uri $groupUri -Method Get
        $groupInfo = @{
            Id          = $group.id
            DisplayName = $group.displayName
            Success     = $true
        }
        
        # Cache the result
        $script:GroupCache[$GroupId] = $groupInfo
        return $groupInfo
    }
    catch {
        $groupInfo = @{
            Id          = $GroupId
            DisplayName = "Unknown Group"
            Success     = $false
        }
        
        # Cache the error result too to avoid repeated failed lookups
        $script:GroupCache[$GroupId] = $groupInfo
        return $groupInfo
    }
}

function Get-IntuneDeviceData {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$DeviceName
    )
    
    # Get device from Microsoft Intune
    $deviceUri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=deviceName eq '$DeviceName'"
    $deviceResponse = Invoke-GraphRequestWithPaging -Uri $deviceUri -Method GET
    
    if ($deviceResponse -and $deviceResponse.Count -gt 0) {
        $device = $deviceResponse[0]
        
        # Create a custom object with all the properties needed for filtering
        $deviceData = [PSCustomObject]@{
            Id                        = $device.id
            DeviceName                = $device.deviceName
            UserPrincipalName         = $device.userPrincipalName
            AzureAdDeviceId           = $device.azureAdDeviceId
            OperatingSystem           = $device.operatingSystem
            OSVersion                 = $device.osVersion
            DeviceType                = $device.deviceType
            ComplianceState           = $device.complianceState
            JoinType                  = $device.joinType
            ManagementAgent           = $device.managementAgent
            OwnerType                 = $device.ownerType
            EnrollmentProfileName     = $device.enrollmentProfileName
            AutopilotEnrolled         = $device.autopilotEnrolled
            Manufacturer              = $device.manufacturer
            Model                     = $device.model
            SerialNumber              = $device.serialNumber
            ProcessorArchitecture     = $device.processorArchitecture
            EthernetMacAddress        = $device.ethernetMacAddress
            WiFiMacAddress            = $device.wiFiMacAddress
            TotalStorageSpaceInBytes  = $device.totalStorageSpaceInBytes
            FreeStorageSpaceInBytes   = $device.freeStorageSpaceInBytes
            PhysicalMemoryInBytes     = $device.physicalMemoryInBytes
            IsEncrypted               = $device.isEncrypted
            IsSupervised              = $device.isSupervised
            JailBroken                = $device.jailBroken
            AzureAdRegistered         = $device.azureAdRegistered
            DeviceEnrollmentType      = $device.deviceEnrollmentType
            ChassisType               = $device.chassisType
            EnrolledDateTime          = $device.enrolledDateTime
            LastSyncDateTime          = $device.lastSyncDateTime
            ManagementState           = $device.managementState
            DeviceRegistrationState   = $device.deviceRegistrationState
            DeviceCategory            = $device.deviceCategory
            DeviceCategoryDisplayName = $device.deviceCategoryDisplayName
            EmailAddress              = $device.emailAddress
            UserDisplayName           = $device.userDisplayName
            UserId                    = $device.userId
        }
        
        return $deviceData
    }
    else {
        #write-warning "Device '$DeviceName' not found in Intune"
        return $null
    }
}

function Test-IntuneFilter {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$FilterRule,
        
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$DeviceProperties
    )

    # Helper function to evaluate a single condition
    function Test-Condition {
        param (
            [string]$PropertyPath,
            [string]$Operator,
            [string]$Value
        )

        # Get the property from the device object
        $property = $PropertyPath.Trim()
        
        # Handle nested properties (e.g., device.enrollmentProfileName)
        if ($property.StartsWith("device.")) {
            $property = $property.Substring(7) # Remove "device." prefix
        }
        
        # Get the actual property value
        $actualValue = $null
        if ($DeviceProperties.PSObject.Properties.Name -contains $property) {
            $actualValue = $DeviceProperties.$property
        }
        else {
            Write-Verbose "Property '$property' not found in device properties"
            return $false
        }
        
        # Handle null values
        if ($null -eq $actualValue) {
            Write-Verbose "Property '$property' is null"
            return $false
        }
        
        # Evaluate based on operator
        switch -Regex ($Operator.Trim()) {
            "-eq" { return $actualValue -eq $Value }
            "-ne" { return $actualValue -ne $Value }
            "-contains" { return $actualValue -contains $Value }
            "-notContains" { return $actualValue -notcontains $Value }
            "-startsWith" { return $actualValue.StartsWith($Value) }
            "-notStartsWith" { return -not $actualValue.StartsWith($Value) }
            "-endsWith" { return $actualValue.EndsWith($Value) }
            "-notEndsWith" { return -not $actualValue.EndsWith($Value) }
            "-match" { return $actualValue -match $Value }
            "-notMatch" { return $actualValue -notmatch $Value }
            "-in" { 
                $valueArray = $Value -split ','
                return $valueArray -contains $actualValue 
            }
            "-notIn" { 
                $valueArray = $Value -split ','
                return $valueArray -notcontains $actualValue 
            }
            default {
                #write-warning "Unsupported operator: $Operator"
                return $false
            }
        }
    }

    # Split the rule into individual conditions
    $filterRule = $FilterRule -replace '\s+', ' ' # Normalize whitespace
    
    # Initial parsing to separate by "or" and "and" operators
    $orConditions = @()
    $sections = $filterRule -split ' or '
    
    foreach ($section in $sections) {
        $andConditions = @()
        $andSections = $section -split ' and '
        
        foreach ($andSection in $andSections) {
            # Extract the condition components using a simpler approach to avoid regex issues
            # Remove outer parentheses if they exist
            $cleanSection = $andSection.Trim()
            if ($cleanSection.StartsWith("(") -and $cleanSection.EndsWith(")")) {
                $cleanSection = $cleanSection.Substring(1, $cleanSection.Length - 2).Trim()
            }
            
            # Split by spaces to get the parts
            $parts = $cleanSection -split '\s+', 3
            
            if ($parts.Count -ge 3) {
                $propertyPath = $parts[0]
                $operator = $parts[1]
                # Remove any surrounding quotes from the value
                $value = $parts[2] -replace '^["\'']|["\'']$', ''
                
                # Store the condition for evaluation
                $andConditions += @{
                    PropertyPath = $propertyPath
                    Operator     = $operator
                    Value        = $value
                }
            }
            else {
                #write-warning "Could not parse condition: $andSection"
            }
        }
        
        $orConditions += @{ AndConditions = $andConditions }
    }
    
    # Evaluate the conditions
    foreach ($orCondition in $orConditions) {
        $allAndConditionsTrue = $true
        
        foreach ($andCondition in $orCondition.AndConditions) {
            $result = Test-Condition -PropertyPath $andCondition.PropertyPath -Operator $andCondition.Operator -Value $andCondition.Value
            Write-Verbose "Evaluated: $($andCondition.PropertyPath) $($andCondition.Operator) '$($andCondition.Value)' = $result"
            
            if (-not $result) {
                $allAndConditionsTrue = $false
                break
            }
        }
        
        if ($allAndConditionsTrue) {
            return $true
        }
    }
    
    return $false
}

function Test-DevicePolicyAssignment {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$PolicyId,
        
        [Parameter(Mandatory = $true)]
        [string]$DeviceName,
        
        [Parameter(Mandatory = $false)]
        [switch]$ShowVerbose
    )
    
    # Get device information using Invoke-GraphRequestWithPaging
    if ($ShowVerbose) {
        Write-Host "Retrieving device information for $DeviceName..." -ForegroundColor Cyan
    }
    
    $deviceUri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices?`$filter=deviceName eq '$DeviceName'"
    $deviceResponse = Invoke-GraphRequestWithPaging -Uri $deviceUri -Method GET
    
    if (-not $deviceResponse -or $deviceResponse.Count -eq 0) {
        Write-Host "Device not found in Intune." -ForegroundColor Red
        return $false
    }
    
    $device = $deviceResponse[0]
    $deviceId = $device.id
    
    if ($ShowVerbose) {
        Write-Host "Found device with ID: $deviceId" -ForegroundColor Green
    }
    
    # Get basic device properties needed for filter evaluation
    $deviceProperties = Get-IntuneDeviceData -DeviceName $device.deviceName
    
    # Get user's group memberships if needed for group-based assignments
    $userPrincipalName = $deviceProperties.UserPrincipalName
    $userGroupIds = @()
    
    if ($userPrincipalName) {
        if ($ShowVerbose) {
            Write-Host "Retrieving group memberships for user: $userPrincipalName..." -ForegroundColor Cyan
        }
            
        try {
            $userUri = "https://graph.microsoft.com/v1.0/users/$userPrincipalName"
            $userResponse = Invoke-MgGraphRequest -Method GET -Uri $userUri
            
            if ($userResponse) {
                $userGroupsUri = "https://graph.microsoft.com/v1.0/users/$($userResponse.id)/transitiveMemberOf?`$select=id"
                $userGroupsData = Invoke-GraphRequestWithPaging -Uri $userGroupsUri -Method "GET"
                $userGroupIds = $userGroupsData.id
                            
                if ($ShowVerbose) {
                    Write-Host "User is a member of $($userGroupIds.Count) groups" -ForegroundColor Green
                }
            }
        }
        catch {
            Write-Host "Error retrieving user group memberships: $_" -ForegroundColor Yellow
        }
    }
    
    # Get Azure AD device group memberships
    $deviceGroupIds = @()
    
    if ($deviceProperties.AzureAdDeviceId) {
        if ($ShowVerbose) {
            Write-Host "Retrieving group memberships for Azure AD device..." -ForegroundColor Cyan
        }
            
        try {
            # Get the Azure AD device
            $azureAdDeviceUri = "https://graph.microsoft.com/v1.0/devices?`$filter=deviceId eq '$($deviceProperties.AzureAdDeviceId)'"
            $azureAdDeviceResponse = Invoke-GraphRequestWithPaging -Uri $azureAdDeviceUri -Method GET
                    
            if ($azureAdDeviceResponse -and $azureAdDeviceResponse.Count -gt 0) {
                # Get device group memberships
                $deviceGroupsUri = "https://graph.microsoft.com/v1.0/devices/$($azureAdDeviceResponse[0].id)/transitiveMemberOf?`$select=id"
                $deviceGroupsResponse = Invoke-GraphRequestWithPaging -Uri $deviceGroupsUri -Method GET
                $deviceGroupIds = $deviceGroupsResponse.id
                            
                if ($ShowVerbose) {
                    Write-Host "Device is a member of $($deviceGroupIds.Count) groups" -ForegroundColor Green
                }
            }
            else {
                Write-Host "Azure AD device not found for device ID: $($deviceProperties.AzureAdDeviceId)" -ForegroundColor Yellow
            }
        }
        catch {
            Write-Host "Error retrieving device group memberships: $_" -ForegroundColor Yellow
        }
    }
    
    # Get all Intune filters
    if ($ShowVerbose) {
        Write-Host "Retrieving all Intune filters..." -ForegroundColor Cyan
    }
    
    $filtersUri = "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters?`$filter=platform eq 'windows10AndLater'"
    $intuneFilters = Invoke-GraphRequestWithPaging -Uri $filtersUri -Method GET
    
    # Get policy assignments
    if ($ShowVerbose) {
        Write-Host "Getting assignments for policy ID: $PolicyId" -ForegroundColor Blue
    }
    
    $assignmentsUri = "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies('$PolicyId')/assignments"
    $assignmentResponse = Invoke-GraphRequestWithPaging -Uri $assignmentsUri -Method GET
    
    $isAssigned = $false        # Check each assignment to see if it applies to this device
        foreach ($assignment in $assignmentResponse) {
        $targetType = $assignment.target.'@odata.type'
        $filterId = $assignment.target.deviceAndAppManagementAssignmentFilterId
        $filterType = $assignment.target.deviceAndAppManagementAssignmentFilterType
        
        # Check if base assignment applies (without filter)
        $baseApplicable = $false
        
        switch ($targetType) {
            "#microsoft.graph.allDevicesAssignmentTarget" {
                $baseApplicable = $true
                if ($ShowVerbose) {
                    Write-Host " Assignment targets all devices" -ForegroundColor Gray
                }
            }
            "#microsoft.graph.allLicensedUsersAssignmentTarget" {
                $baseApplicable = ($null -ne $userPrincipalName)
                if ($ShowVerbose) {
                    Write-Host " Assignment targets all licensed users" -ForegroundColor Gray
                }
            }
            "#microsoft.graph.groupAssignmentTarget" {
                $groupId = $assignment.target.groupId
                # Check if device or user is a member of this group
                $baseApplicable = ($userGroupIds -contains $groupId) -or ($deviceGroupIds -contains $groupId)
                if ($ShowVerbose) {
                    Write-Host " Assignment targets group ID: $groupId (Device/User in group: $baseApplicable)" -ForegroundColor Gray
                }
            }
        }
        
        # Final assignment result after applying filter (if any)
        $assignmentApplies = $baseApplicable
        
        # Handle filter logic if present
        if ($filterId) {
            $filter = $intuneFilters | Where-Object id -EQ $filterId
                    
            if ($filter) {
                if ($ShowVerbose) {
                    Write-Host " Evaluating filter: $($filter.displayName)" -ForegroundColor Gray
                }
                            
                $filterMatched = Test-IntuneFilter -FilterRule $filter.rule -DeviceProperties $deviceProperties
                
                if ($ShowVerbose) {
                    Write-Host " Filter match result: $filterMatched" -ForegroundColor $(if ($filterMatched) { "Green" } else { "Yellow" })
                }
                            
                # Apply filter logic based on include/exclude type
                if ($filterType -eq "include") {
                    $assignmentApplies = $baseApplicable -and $filterMatched
                }
                elseif ($filterType -eq "exclude") {
                    $assignmentApplies = $baseApplicable -and (-not $filterMatched)
                }
            }
            else {
                # Filter not found - consider as not matching
                $assignmentApplies = $false
                if ($ShowVerbose) {
                    Write-Host " Filter not found with ID: $filterId" -ForegroundColor Red
                }
            }
        }
        
        if ($ShowVerbose) {
            Write-Host " Assignment applies: $assignmentApplies" -ForegroundColor $(if ($assignmentApplies) { "Green" } else { "Yellow" })
        }
        
        # If any assignment applies, the policy is assigned to this device
        if ($assignmentApplies) {
            $isAssigned = $true
            # Can break here since we only need to know if at least one assignment applies
            break
        }
    }
    
    if ($ShowVerbose) {
        Write-Host "Policy $(if ($isAssigned) { "IS" } else { "IS NOT" }) assigned to device $DeviceName" -ForegroundColor $(if ($isAssigned) { "Green" } else { "Yellow" })
    }
    
    return $isAssigned
}

function Get-DetailedPolicyAssignments {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [string]$EntityType,
        
        [Parameter(Mandatory = $true)]
        [string]$EntityId,
        
        [Parameter(Mandatory = $true)]
        [string]$PolicyName
    )
    
    # Determine the correct assignments URI based on EntityType
    $assignmentsUri = $null
    
    if ($EntityType -eq "deviceAppManagement/managedAppPolicies") {
        # For generic App Protection Policies, determine the specific policy type first
        $policyDetailsUri = "https://graph.microsoft.com/beta/deviceAppManagement/managedAppPolicies/$EntityId"
        try {
            $policyDetailsResponse = Invoke-MgGraphRequest -Uri $policyDetailsUri -Method Get
            $policyODataType = $policyDetailsResponse.'@odata.type'
            $specificPolicyTypePath = switch ($policyODataType) {
                "#microsoft.graph.androidManagedAppProtection" { "androidManagedAppProtections" }
                "#microsoft.graph.iosManagedAppProtection" { "iosManagedAppProtections" }
                "#microsoft.graph.windowsManagedAppProtection" { "windowsManagedAppProtections" }
                default { $null }
            }
            if ($specificPolicyTypePath) {
                $assignmentsUri = "https://graph.microsoft.com/beta/deviceAppManagement/$specificPolicyTypePath('$EntityId')/assignments"
            }
        }
        catch {
            #write-warning "Error fetching details for App Protection Policy '$EntityId': $($_.Exception.Message)"
            return @()
        }
    }
    elseif ($EntityType -eq "mobileAppConfigurations") {
        $assignmentsUri = "https://graph.microsoft.com/beta/deviceAppManagement/mobileAppConfigurations('$EntityId')/assignments"
    }
    elseif ($EntityType -like "deviceAppManagement/*ManagedAppProtections") {
        $assignmentsUri = "https://graph.microsoft.com/beta/$EntityType('$EntityId')/assignments"
    }
    elseif ($EntityType -eq "deviceManagement/intents") {
        $assignmentsUri = "https://graph.microsoft.com/beta/deviceManagement/intents('$EntityId')/assignments"
    }
    else {
        # General device management entities
        $assignmentsUri = "https://graph.microsoft.com/beta/deviceManagement/$EntityType('$EntityId')/assignments"
    }
    
    if (-not $assignmentsUri) {
        #write-warning "Could not determine assignments URI for EntityType '$EntityType' and EntityId '$EntityId'"
        return @()
    }
    
    try {
        # Get all assignments for the policy
        $assignments = Invoke-GraphRequestWithPaging -Uri $assignmentsUri -Method "GET"
        
        # Get all filters for reference
        $filtersUri = "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters"
        $allFilters = Invoke-GraphRequestWithPaging -Uri $filtersUri -Method "GET"
        
        # Process each assignment
        $detailedAssignments = foreach ($assignment in $assignments) {
            $targetType = $assignment.target.'@odata.type'
            $filterId = $assignment.target.deviceAndAppManagementAssignmentFilterId
            $filterType = $assignment.target.deviceAndAppManagementAssignmentFilterType
            
            # Get basic assignment info
            $assignmentInfo = switch ($targetType) {
                "#microsoft.graph.allDevicesAssignmentTarget" { 
                    @{
                        AssignmentType = "All Devices"
                        TargetName     = "All Devices"
                        TargetId       = $null
                        GroupId        = $null
                    }
                }
                "#microsoft.graph.allLicensedUsersAssignmentTarget" { 
                    @{
                        AssignmentType = "All Users"
                        TargetName     = "All Users"
                        TargetId       = $null
                        GroupId        = $null
                    }
                }
                "#microsoft.graph.groupAssignmentTarget" { 
                    $groupId = $assignment.target.groupId
                    $groupInfo = Get-GroupInfo -GroupId $groupId
                    @{
                        AssignmentType = "Group (Include)"
                        TargetName     = $groupInfo.DisplayName
                        TargetId       = $groupId
                        GroupId        = $groupId
                    }
                }
                "#microsoft.graph.exclusionGroupAssignmentTarget" { 
                    $groupId = $assignment.target.groupId
                    $groupInfo = Get-GroupInfo -GroupId $groupId
                    @{
                        AssignmentType = "Group (Exclude)"
                        TargetName     = $groupInfo.DisplayName
                        TargetId       = $groupId
                        GroupId        = $groupId
                    }
                }
                default { 
                    @{
                        AssignmentType = "Unknown"
                        TargetName     = "Unknown"
                        TargetId       = $null
                        GroupId        = $null
                    }
                }
            }
            
            # Get filter information if present
            $filterInfo = if ($filterId) {
                $filter = $allFilters | Where-Object { $_.id -eq $filterId }
                if ($filter) {
                    @{
                        FilterId       = $filter.id
                        FilterName     = $filter.displayName
                        FilterType     = $filterType
                        FilterRule     = $filter.rule
                        FilterPlatform = $filter.platform
                    }
                }
                else {
                    @{
                        FilterId       = $filterId
                        FilterName     = "Filter Not Found"
                        FilterType     = $filterType
                        FilterRule     = "Unknown"
                        FilterPlatform = "Unknown"
                    }
                }
            }
            else {
                @{
                    FilterId       = $null
                    FilterName     = "No Filter"
                    FilterType     = "None"
                    FilterRule     = $null
                    FilterPlatform = $null
                }
            }
            
            # Create combined assignment object
            [PSCustomObject]@{
                PolicyName     = $PolicyName
                PolicyId       = $EntityId
                PolicyType     = $EntityType
                AssignmentType = $assignmentInfo.AssignmentType
                TargetName     = $assignmentInfo.TargetName
                TargetId       = $assignmentInfo.TargetId
                GroupId        = $assignmentInfo.GroupId
                FilterId       = $filterInfo.FilterId
                FilterName     = $filterInfo.FilterName
                FilterType     = $filterInfo.FilterType
                FilterRule     = $filterInfo.FilterRule
                FilterPlatform = $filterInfo.FilterPlatform
                AssignmentId   = $assignment.id
            }
        }
        
        return $detailedAssignments
    }
    catch {
        #write-warning "Error getting detailed assignments for policy '$PolicyName': $($_.Exception.Message)"
        return @()
    }
}

function Get-DetailedPolicyAssignmentsFromExpanded {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [object]$Policy,
        
        [Parameter(Mandatory = $true)]
        [string]$PolicyName,
        
        [Parameter(Mandatory = $true)]
        [object]$PolicyType
    )
    
    try {
        $detailedAssignments = @()
        
        if ($Policy.assignments -and $Policy.assignments.Count -gt 0) {
            foreach ($assignment in $Policy.assignments) {
                $targetType = "Unknown"
                $targetName = "Unknown"
                $targetId = $null
                $groupId = $null
                $filterInfo = @{
                    FilterId       = $null
                    FilterName     = "No Filter"
                    FilterType     = "None"
                    FilterRule     = $null
                    FilterPlatform = $null
                }
                
                # Process target information
                if ($assignment.target) {
                    $targetType = switch ($assignment.target.'@odata.type') {
                        "#microsoft.graph.allLicensedUsersAssignmentTarget" { "All Users" }
                        "#microsoft.graph.allDevicesAssignmentTarget" { "All Devices" }
                        "#microsoft.graph.groupAssignmentTarget" { "Group" }
                        "#microsoft.graph.exclusionGroupAssignmentTarget" { "Group (Exclude)" }
                        default { $assignment.target.'@odata.type' }
                    }
                    
                    if ($assignment.target.groupId) {
                        $targetId = $assignment.target.groupId
                        $groupId = $assignment.target.groupId
                        
                        # Get group information
                        try {
                            $group = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/groups/$($assignment.target.groupId)" -Method GET
                            $targetName = $group.displayName
                        }
                        catch {
                            $targetName = "Group (ID: $($assignment.target.groupId))"
                        }
                    }
                    else {
                        $targetName = $targetType
                    }
                    
                    # Handle assignment filters
                    if ($assignment.target.deviceAndAppManagementAssignmentFilterId) {
                        $filterId = $assignment.target.deviceAndAppManagementAssignmentFilterId
                        $filterType = switch ($assignment.target.deviceAndAppManagementAssignmentFilterType) {
                            "include" { "include" }
                            "exclude" { "exclude" }
                            default { "include" }
                        }
                        
                        # Get filter information
                        try {
                            $filter = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/deviceManagement/assignmentFilters/$filterId" -Method GET
                            $filterInfo = @{
                                FilterId       = $filterId
                                FilterName     = $filter.displayName
                                FilterType     = $filterType
                                FilterRule     = $filter.rule
                                FilterPlatform = $filter.platform
                            }
                        }
                        catch {
                            $filterInfo = @{
                                FilterId       = $filterId
                                FilterName     = "Filter Not Found"
                                FilterType     = $filterType
                                FilterRule     = $null
                                FilterPlatform = $null
                            }
                        }
                    }
                }
                
                # Get assignment intent (Required/Available/Uninstall) for applications
                $assignmentIntent = "Unknown"
                if ($assignment.intent) {
                    $assignmentIntent = switch ($assignment.intent) {
                        "required" { "Required" }
                        "available" { "Available" }
                        "uninstall" { "Uninstall" }
                        "availableWithoutEnrollment" { "Available (No Enrollment)" }
                        default { $assignment.intent }
                    }
                }
                
                $detailedAssignments += [PSCustomObject]@{
                    PolicyName       = $PolicyName
                    PolicyId         = $Policy.id
                    PolicyType       = $PolicyType.EntityType
                    AssignmentType   = $targetType
                    AssignmentIntent = $assignmentIntent
                    TargetName       = $targetName
                    TargetId         = $targetId
                    GroupId          = $groupId
                    FilterId         = $filterInfo.FilterId
                    FilterName       = $filterInfo.FilterName
                    FilterType       = $filterInfo.FilterType
                    FilterRule       = $filterInfo.FilterRule
                    FilterPlatform   = $filterInfo.FilterPlatform
                    AssignmentId     = $assignment.id
                }
            }
        }
        
        return $detailedAssignments
    }
    catch {
        #write-warning "Error processing expanded assignments for policy '$PolicyName': $($_.Exception.Message)"
        return @()
    }
}

function Get-AllIntunePoliciesWithAssignments {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [switch]$IncludeFilterDetails,
        
        [Parameter(Mandatory = $false)]
        [switch]$ExportToCsv,
        
        [Parameter(Mandatory = $false)]
        [string]$ExportPath
    )
    
    Write-Host "Starting comprehensive Intune policy and assignment retrieval..." -ForegroundColor Green
    
    # Initialize collections
    $allPolicyAssignments = [System.Collections.ArrayList]::new()
    $GraphEndpoint = "https://graph.microsoft.com"
    
    # Define policy types to retrieve
    $policyTypes = @(
        @{ Name = "Device Configuration"; EntityType = "deviceConfigurations"; DisplayName = "Device Configuration Profiles" },
        @{ Name = "Settings Catalog"; EntityType = "configurationPolicies"; DisplayName = "Settings Catalog Policies" },
        @{ Name = "Administrative Templates"; EntityType = "groupPolicyConfigurations"; DisplayName = "Administrative Templates" },
        @{ Name = "Compliance Policies"; EntityType = "deviceCompliancePolicies"; DisplayName = "Compliance Policies" },
        @{ Name = "App Protection"; EntityType = "deviceAppManagement/managedAppPolicies"; DisplayName = "App Protection Policies" },
        @{ Name = "App Configuration"; EntityType = "mobileAppConfigurations"; DisplayName = "App Configuration Policies" },
        @{ Name = "Applications"; EntityType = "deviceAppManagement/mobileApps"; DisplayName = "Applications" },
        @{ Name = "Platform Scripts"; EntityType = "deviceManagementScripts"; DisplayName = "Platform Scripts" },
        @{ Name = "Remediation Scripts"; EntityType = "deviceHealthScripts"; DisplayName = "Proactive Remediation Scripts" },
        @{ Name = "Autopilot Profiles"; EntityType = "windowsAutopilotDeploymentProfiles"; DisplayName = "Autopilot Deployment Profiles" },
        @{ Name = "ESP Profiles"; EntityType = "deviceEnrollmentConfigurations"; DisplayName = "Enrollment Status Page Profiles" },
        @{ Name = "Endpoint Security"; EntityType = "deviceManagement/intents"; DisplayName = "Endpoint Security Policies" }
    )
    
    # Get all Intune filters upfront for reference
    Write-Host "Retrieving all Intune assignment filters..." -ForegroundColor Yellow
    $filtersUri = "$GraphEndpoint/beta/deviceManagement/assignmentFilters"
    $allFilters = Invoke-GraphRequestWithPaging -Uri $filtersUri -Method "GET"
    Write-Host "Retrieved $($allFilters.Count) assignment filters" -ForegroundColor Green
    
    # Process each policy type - using sequential processing for Graph API calls to avoid module/auth issues
    # We'll use parallel processing for data processing tasks later
    foreach ($policyType in $policyTypes) {
        Write-Host "`nProcessing: $($policyType.DisplayName)" -ForegroundColor Cyan
        
        try {
            # Get all policies of this type
            # Use expand for mobile apps to get assignments in a single call
            if ($policyType.Name -eq "Applications") {
                $policies = Get-IntuneEntities -EntityType $policyType.EntityType -Expand "assignments"
            }
            else {
                $policies = Get-IntuneEntities -EntityType $policyType.EntityType
            }
            Write-Host "Found $($policies.Count) policies of type: $($policyType.Name)" -ForegroundColor Yellow
            
            # Process policies with optimized sequential processing for Graph API calls
            if ($policies.Count -gt 0) {
                Write-Host " Processing $($policies.Count) policies with optimized sequential processing..." -ForegroundColor Gray
                
                foreach ($policy in $policies) {
                    $policyName = if ($policy.displayName) { $policy.displayName } elseif ($policy.name) { $policy.name } else { "Unnamed Policy" }
                    
                    # Get detailed assignments for this policy
                    try {
                        # Handle mobile apps with expanded assignments differently
                        if ($policyType.Name -eq "Applications" -and $policy.assignments) {
                            $detailedAssignments = Get-DetailedPolicyAssignmentsFromExpanded -Policy $policy -PolicyName $policyName -PolicyType $policyType
                        }
                        else {
                            $detailedAssignments = Get-DetailedPolicyAssignments -EntityType $policyType.EntityType -EntityId $policy.id -PolicyName $policyName
                        }
                        
                        if ($detailedAssignments.Count -eq 0) {
                            # Add entry for unassigned policy
                            $unassignedPolicy = [PSCustomObject]@{
                                PolicyCategory       = $policyType.Name
                                PolicyName           = $policyName
                                PolicyId             = $policy.id
                                PolicyType           = $policyType.EntityType
                                AssignmentType       = "Not Assigned"
                                TargetName           = "Not Assigned"
                                TargetId             = $null
                                GroupId              = $null
                                FilterId             = $null
                                FilterName           = "No Filter"
                                FilterType           = "None"
                                FilterRule           = $null
                                FilterPlatform       = $null
                                AssignmentId         = $null
                                CreatedDateTime      = $policy.createdDateTime
                                LastModifiedDateTime = $policy.lastModifiedDateTime
                            }
                            $null = $allPolicyAssignments.Add($unassignedPolicy)
                        }
                        else {
                            foreach ($assignment in $detailedAssignments) {
                                $enrichedAssignment = [PSCustomObject]@{
                                    PolicyCategory       = $policyType.Name
                                    PolicyName           = $assignment.PolicyName
                                    PolicyId             = $assignment.PolicyId
                                    PolicyType           = $assignment.PolicyType
                                    AssignmentType       = $assignment.AssignmentType
                                    AssignmentIntent     = if ($assignment.AssignmentIntent) { $assignment.AssignmentIntent } else { $null }
                                    TargetName           = $assignment.TargetName
                                    TargetId             = $assignment.TargetId
                                    GroupId              = $assignment.GroupId
                                    FilterId             = $assignment.FilterId
                                    FilterName           = $assignment.FilterName
                                    FilterType           = $assignment.FilterType
                                    FilterRule           = $assignment.FilterRule
                                    FilterPlatform       = $assignment.FilterPlatform
                                    AssignmentId         = $assignment.AssignmentId
                                    CreatedDateTime      = $policy.createdDateTime
                                    LastModifiedDateTime = $policy.lastModifiedDateTime
                                }
                                $null = $allPolicyAssignments.Add($enrichedAssignment)
                            }
                        }
                    }
                    catch {
                        #write-warning "Error processing policy '$policyName': $($_.Exception.Message)"
                        # Add unassigned entry on error
                        $unassignedPolicy = [PSCustomObject]@{
                            PolicyCategory       = $policyType.Name
                            PolicyName           = $policyName
                            PolicyId             = $policy.id
                            PolicyType           = $policyType.EntityType
                            AssignmentType       = "Not Assigned"
                            TargetName           = "Not Assigned"
                            TargetId             = $null
                            GroupId              = $null
                            FilterId             = $null
                            FilterName           = "No Filter"
                            FilterType           = "None"
                            FilterRule           = $null
                            FilterPlatform       = $null
                            AssignmentId         = $null
                            CreatedDateTime      = $policy.createdDateTime
                            LastModifiedDateTime = $policy.lastModifiedDateTime
                        }
                        $null = $allPolicyAssignments.Add($unassignedPolicy)
                    }
                }
            }
        }
        catch {
            #write-warning "Error processing policy type $($policyType.Name): $($_.Exception.Message)"
        }
    }
    
    # Display summary
    Write-Host "`n=== INTUNE POLICIES AND ASSIGNMENTS SUMMARY ===" -ForegroundColor Green
    Write-Host "Total policy assignments found: $($allPolicyAssignments.Count)" -ForegroundColor White
    
    # Group by policy category
    $categoryGroups = $allPolicyAssignments | Group-Object -Property PolicyCategory
    foreach ($group in $categoryGroups) {
        Write-Host "`n$($group.Name):" -ForegroundColor Cyan
        Write-Host " Total assignments: $($group.Count)" -ForegroundColor White
        
        # Sub-group by assignment type
        $assignmentGroups = $group.Group | Group-Object -Property AssignmentType
        foreach ($assignmentGroup in $assignmentGroups) {
            Write-Host " $($assignmentGroup.Name): $($assignmentGroup.Count)" -ForegroundColor Gray
        }
    }
    
    # Display detailed assignments
    Write-Host "`n=== DETAILED POLICY ASSIGNMENTS ===" -ForegroundColor Green
    
    foreach ($categoryGroup in $categoryGroups) {
        Write-Host "`n--- $($categoryGroup.Name) ---" -ForegroundColor Cyan
        
        foreach ($assignment in $categoryGroup.Group) {
            Write-Host "Policy: $($assignment.PolicyName)" -ForegroundColor White
            $assignmentText = " Assignment: $($assignment.AssignmentType) -> $($assignment.TargetName)"
            if ($assignment.AssignmentIntent) {
                $assignmentText += " (Intent: $($assignment.AssignmentIntent))"
            }
            Write-Host $assignmentText -ForegroundColor Gray
            
            if ($assignment.FilterName -ne "No Filter" -and $assignment.FilterName -ne "Filter Not Found" -and $assignment.FilterName) {
                Write-Host " Filter: $($assignment.FilterName) ($($assignment.FilterType))" -ForegroundColor Yellow
                if ($IncludeFilterDetails -and $assignment.FilterRule) {
                    Write-Host " Rule: $($assignment.FilterRule)" -ForegroundColor DarkGray
                }
            }
            Write-Host ""
        }
    }
    
    # Export if requested
    if ($ExportToCsv) {
        $exportPath = if ($ExportPath) { $ExportPath } else { ".\IntuneAllPoliciesWithAssignments_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" }
        $allPolicyAssignments | Export-Csv -Path $exportPath -NoTypeInformation
        Write-Host "Results exported to: $exportPath" -ForegroundColor Green
    }
    
    return $allPolicyAssignments
}

function Get-DynamicGroupsWithMembershipRules {
    [CmdletBinding()]
    param ()
    
    Write-Host "Retrieving Groups and their membership rules..." -ForegroundColor Cyan
    
    try {
        # Get all groups (both dynamic and static)
        $allGroupsUri = "https://graph.microsoft.com/v1.0/groups?`$select=id,displayName,groupTypes,membershipRule,membershipRuleProcessingState"
        $allGroupsData = Invoke-GraphRequestWithPaging -Uri $allGroupsUri -Method "GET"
        
        $groups = foreach ($group in $allGroupsData) {
            # Check if it's a dynamic group
            $isDynamic = $group.groupTypes -and ($group.groupTypes -contains "DynamicMembership")
            
            [PSCustomObject]@{
                Id              = $group.id
                DisplayName     = $group.displayName
                MembershipRule  = if ($isDynamic) { $group.membershipRule } else { $null }
                ProcessingState = if ($isDynamic) { $group.membershipRuleProcessingState } else { "Static" }
                GroupTag        = $null  # Will be extracted from membership rule for dynamic groups
                IsDynamic       = $isDynamic
            }
        }
        
        # Extract group tags from membership rules for dynamic groups only
        foreach ($group in $groups | Where-Object { $_.IsDynamic }) {
            if ($group.MembershipRule) {
                # Look for device.devicePhysicalIds patterns that indicate group tags
                if ($group.MembershipRule -match '\[OrderID\]:([^"]+)') {
                    $group.GroupTag = $matches[1]
                }
                elseif ($group.MembershipRule -match 'device\.devicePhysicalIds\s+-any\s+_\.contains\s*\(\s*"([^"]+)"\s*\)') {
                    $group.GroupTag = $matches[1]
                }
            }
        }
        
        Write-Host "Found $($groups.Count) Groups" -ForegroundColor Green
        return $groups
    }
    catch {
        #write-warning "Error retrieving Groups: $($_.Exception.Message)"
        return @()
    }
}

function Get-ESPConfigurations {
    [CmdletBinding()]
    param()
    
    Write-Host "Retrieving Enrollment Status Page configurations..." -ForegroundColor Cyan
    
    try {
        # Get all ESP configurations with assignments
        $espConfigs = @()
        $enrollmentConfigs = Invoke-GraphRequestWithPaging -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations" -Method "GET"
        
        foreach ($config in $enrollmentConfigs) {
            if ($config.deviceEnrollmentConfigurationType -eq "windows10EnrollmentCompletionPageConfiguration") {
                Write-Host " Processing ESP: $($config.displayName)" -ForegroundColor Gray
                
                # Get detailed ESP configuration with assignments
                $detailedConfig = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/deviceManagement/deviceEnrollmentConfigurations/$($config.id)?`$expand=assignments" -Method GET
                
                # Get app details for selectedMobileAppIds if they exist
                $blockedApps = @()
                if ($detailedConfig.selectedMobileAppIds -and $detailedConfig.selectedMobileAppIds.Count -gt 0) {
                    foreach ($appId in $detailedConfig.selectedMobileAppIds) {
                        try {
                            $app = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/deviceAppManagement/mobileApps/$appId" -Method GET
                            $blockedApps += @{
                                Id          = $app.id
                                DisplayName = $app.displayName
                                Publisher   = $app.publisher
                            }
                        }
                        catch {
                            #write-warning "Could not retrieve app details for ID: $appId"
                            $blockedApps += @{
                                Id          = $appId
                                DisplayName = "Unknown App"
                                Publisher   = "Unknown"
                            }
                        }
                    }
                }
                
                # Process assignments
                $assignments = @()
                foreach ($assignment in $detailedConfig.assignments) {
                    $assignmentInfo = @{
                        Id         = $assignment.id
                        Target     = $assignment.target
                        FilterId   = $assignment.target.deviceAndAppManagementAssignmentFilterId
                        FilterType = $assignment.target.deviceAndAppManagementAssignmentFilterType
                    }
                    
                    # Determine assignment type and target details
                    switch ($assignment.target.'@odata.type') {
                        "#microsoft.graph.allDevicesAssignmentTarget" {
                            $assignmentInfo.Type = "All Devices"
                            $assignmentInfo.TargetName = "All Devices"
                        }
                        "#microsoft.graph.allLicensedUsersAssignmentTarget" {
                            $assignmentInfo.Type = "All Users"
                            $assignmentInfo.TargetName = "All Users"
                        }
                        "#microsoft.graph.groupAssignmentTarget" {
                            $assignmentInfo.Type = "Group"
                            $assignmentInfo.GroupId = $assignment.target.groupId
                            # Get group name
                            try {
                                $group = Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/groups/$($assignment.target.groupId)" -Method GET
                                $assignmentInfo.TargetName = $group.displayName
                            }
                            catch {
                                $assignmentInfo.TargetName = "Unknown Group"
                            }
                        }
                        default {
                            $assignmentInfo.Type = "Unknown"
                            $assignmentInfo.TargetName = "Unknown Target"
                        }
                    }
                    
                    $assignments += $assignmentInfo
                }
                
                $espConfig = @{
                    Id                                   = $detailedConfig.id
                    DisplayName                          = $detailedConfig.displayName
                    Description                          = $detailedConfig.description
                    InstallProgressTimeoutInMinutes      = $detailedConfig.installProgressTimeoutInMinutes
                    InstallQualityUpdates                = $detailedConfig.installQualityUpdates
                    ShowInstallationProgress             = $detailedConfig.showInstallationProgress
                    TrackInstallProgressForAutopilotOnly = $detailedConfig.trackInstallProgressForAutopilotOnly
                    AllowDeviceResetOnInstallFailure     = $detailedConfig.allowDeviceResetOnInstallFailure
                    AllowDeviceUseOnInstallFailure       = $detailedConfig.allowDeviceUseOnInstallFailure
                    AllowLogCollectionOnInstallFailure   = $detailedConfig.allowLogCollectionOnInstallFailure
                    AllowNonBlockingAppInstallation      = $detailedConfig.allowNonBlockingAppInstallation
                    BlockedApps                          = $blockedApps
                    Assignments                          = $assignments
                    CreatedDateTime                      = $detailedConfig.createdDateTime
                    LastModifiedDateTime                 = $detailedConfig.lastModifiedDateTime
                }
                
                $espConfigs += $espConfig
            }
        }
        
        Write-Host "Retrieved $($espConfigs.Count) ESP configurations" -ForegroundColor Green
        return $espConfigs
    }
    catch {
        Write-Error "Error retrieving ESP configurations: $($_.Exception.Message)"
        return @()
    }
}

function Get-EnrollmentFlowData {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [System.Collections.Generic.List[object]]$PolicyAssignments,
        
        [Parameter(Mandatory = $false)]
        [array]$ESPConfigurations = @()
    )
    
    Write-Host "Building enrollment flow data..." -ForegroundColor Green
    
    # Get Groups (both dynamic and static)
    $allGroups = Get-DynamicGroupsWithMembershipRules
    
    # Get autopilot profiles and their assignments
    Write-Host "Retrieving Autopilot profiles..." -ForegroundColor Cyan
    $autopilotProfiles = Get-IntuneEntities -EntityType "windowsAutopilotDeploymentProfiles"
    
    # Use the passed policy assignments data
    Write-Host "Using previously retrieved policy assignments..." -ForegroundColor Cyan
    
    # Build enrollment flows - one flow per Autopilot profile
    $enrollmentFlows = @()
    
    foreach ($autopilotProfile in $autopilotProfiles) {
        Write-Host "Processing Autopilot profile: $($autopilotProfile.displayName)" -ForegroundColor Gray
        
        # Get assignments for this autopilot profile
        $profileAssignments = Get-DetailedPolicyAssignments -EntityType "windowsAutopilotDeploymentProfiles" -EntityId $autopilotProfile.id -PolicyName $autopilotProfile.displayName
        
        # Get all groups that this autopilot profile is assigned to
        $assignedGroups = @()
        foreach ($assignment in $profileAssignments) {
            if ($assignment.AssignmentType -eq "Group (Include)" -and $assignment.GroupId) {
                # Find the Group (both dynamic and static)
                $group = $allGroups | Where-Object { $_.Id -eq $assignment.GroupId }
                
                if ($group) {
                    Write-Host " Found Group assignment: $($group.DisplayName)" -ForegroundColor Gray
                    $assignedGroups += $group
                }
            }
        }
        
        # Only create a flow if there are group assignments
        if ($assignedGroups.Count -gt 0) {
            Write-Host " Creating single flow for Autopilot profile with $($assignedGroups.Count) assigned groups" -ForegroundColor Gray
            
            # Collect all policies and applications that would apply to devices covered by this Autopilot profile
            # by combining all policies from all assigned groups
            $allApplicablePolicies = @()
            $allApplicableApplications = @()
            
            # Find the best applicable ESP (priority: group-specific > All Devices > All Users)
            $applicableESP = $null
            
            foreach ($group in $assignedGroups) {
                Write-Host " Processing group: $($group.DisplayName)" -ForegroundColor Gray
                
                # Get all policies that would apply to devices in this Group
                $directGroupPolicies = $PolicyAssignments | Where-Object { 
                    $_.GroupId -eq $group.Id -and 
                    $_.AssignmentType -eq "Group (Include)" -and
                    $_.PolicyCategory -ne "Autopilot Profiles" -and
                    $_.PolicyCategory -ne "ESP Profiles" -and
                    $_.PolicyCategory -ne "Applications"
                }
                
                # Get excluded policies for this group
                $excludedPolicies = $PolicyAssignments | Where-Object { 
                    $_.GroupId -eq $group.Id -and 
                    $_.AssignmentType -eq "Group (Exclude)" -and
                    $_.PolicyCategory -ne "Autopilot Profiles" -and
                    $_.PolicyCategory -ne "ESP Profiles" -and
                    $_.PolicyCategory -ne "Applications"
                }
                
                # Add all group-specific policies
                $allApplicablePolicies += $directGroupPolicies
                $allApplicablePolicies += $excludedPolicies
                
                # Get applications for this group
                $directGroupApplications = $PolicyAssignments | Where-Object { 
                    $_.GroupId -eq $group.Id -and 
                    ($_.AssignmentType -eq "Group (Include)" -or $_.AssignmentType -eq "Group") -and
                    $_.PolicyCategory -eq "Applications"
                }
                
                $excludedApplications = $PolicyAssignments | Where-Object { 
                    $_.GroupId -eq $group.Id -and 
                    $_.AssignmentType -eq "Group (Exclude)" -and
                    $_.PolicyCategory -eq "Applications"
                }
                
                $allApplicableApplications += $directGroupApplications
                $allApplicableApplications += $excludedApplications
                
                # Find ESP for this group (only if we haven't found a group-specific one yet)
                if (-not $applicableESP -and $ESPConfigurations.Count -gt 0) {
                    foreach ($esp in $ESPConfigurations) {
                        $espAssignedToGroup = $esp.Assignments | Where-Object { 
                            $_.Type -eq "Group" -and $_.GroupId -eq $group.Id
                        }
                        if ($espAssignedToGroup) {
                            $applicableESP = $esp
                            Write-Host " Found group-specific ESP: $($esp.DisplayName)" -ForegroundColor DarkGray
                            break
                        }
                    }
                }
                
                Write-Host " Group policies: $($directGroupPolicies.Count), Excluded: $($excludedPolicies.Count)" -ForegroundColor DarkGray
                Write-Host " Group applications: $($directGroupApplications.Count), Excluded: $($excludedApplications.Count)" -ForegroundColor DarkGray
            }
            
            # Add global policies (All Devices and All Users)
            $allDevicesPolicies = $PolicyAssignments | Where-Object { 
                $_.AssignmentType -eq "All Devices" -and
                $_.PolicyCategory -ne "Autopilot Profiles" -and
                $_.PolicyCategory -ne "ESP Profiles" -and
                $_.PolicyCategory -ne "Applications"
            }
            
            $allUsersPolicies = $PolicyAssignments | Where-Object { 
                $_.AssignmentType -eq "All Users" -and
                $_.PolicyCategory -ne "Autopilot Profiles" -and
                $_.PolicyCategory -ne "ESP Profiles" -and
                $_.PolicyCategory -ne "Applications"
            }
            
            $allDevicesApplications = $PolicyAssignments | Where-Object { 
                $_.AssignmentType -eq "All Devices" -and
                $_.PolicyCategory -eq "Applications"
            }
            
            $allUsersApplications = $PolicyAssignments | Where-Object { 
                $_.AssignmentType -eq "All Users" -and
                $_.PolicyCategory -eq "Applications"
            }
            
            # Combine all policies
            $allApplicablePolicies += $allDevicesPolicies
            $allApplicablePolicies += $allUsersPolicies
            $allApplicableApplications += $allDevicesApplications
            $allApplicableApplications += $allUsersApplications
            
            # If no group-specific ESP found, look for All Devices or All Users ESP
            if (-not $applicableESP -and $ESPConfigurations.Count -gt 0) {
                foreach ($esp in $ESPConfigurations) {
                    $espAssignedToAllDevices = $esp.Assignments | Where-Object { 
                        $_.Type -eq "All Devices"
                    }
                    if ($espAssignedToAllDevices) {
                        $applicableESP = $esp
                        Write-Host " Found All Devices ESP: $($esp.DisplayName)" -ForegroundColor DarkGray
                        break
                    }
                }
                
                if (-not $applicableESP) {
                    foreach ($esp in $ESPConfigurations) {
                        $espAssignedToAllUsers = $esp.Assignments | Where-Object { 
                            $_.Type -eq "All Users"
                        }
                        if ($espAssignedToAllUsers) {
                            $applicableESP = $esp
                            Write-Host " Found All Users ESP: $($esp.DisplayName)" -ForegroundColor DarkGray
                            break
                        }
                    }
                }
            }
            
            # Get all application IDs that affect any of the groups
            $affectedAppIds = $allApplicableApplications | Select-Object -ExpandProperty PolicyId -Unique
            
            # Get ALL assignments for these applications to show complete picture
            $finalApplications = $PolicyAssignments | Where-Object { 
                $_.PolicyCategory -eq "Applications" -and 
                $_.PolicyId -in $affectedAppIds
            }
            
            Write-Host " Total applicable policies: $($allApplicablePolicies.Count)" -ForegroundColor DarkGray
            Write-Host " Total applicable applications: $($finalApplications.Count)" -ForegroundColor DarkGray
            
            # Create enrollment flow object - one per Autopilot profile
            $enrollmentFlow = [PSCustomObject]@{
                FlowName             = $autopilotProfile.displayName  # Just the Autopilot profile name
                AutopilotProfile     = [PSCustomObject]@{
                    Name                       = $autopilotProfile.displayName
                    Id                         = $autopilotProfile.id
                    OutOfBoxExperienceSettings = $autopilotProfile.outOfBoxExperienceSettings
                    DeviceNameTemplate         = $autopilotProfile.deviceNameTemplate
                    Language                   = $autopilotProfile.language
                    Locale                     = $autopilotProfile.locale
                    Assignments                = $profileAssignments
                }
                AssignedGroups       = $assignedGroups | ForEach-Object {
                    [PSCustomObject]@{
                        Name            = $_.DisplayName
                        Id              = $_.Id
                        MembershipRule  = $_.MembershipRule
                        GroupTag        = $_.GroupTag
                        ProcessingState = $_.ProcessingState
                        IsDynamic       = $_.IsDynamic
                    }
                }
                ESPConfiguration     = if ($applicableESP) {
                    [PSCustomObject]@{
                        Name                                 = $applicableESP.DisplayName
                        Id                                   = $applicableESP.Id
                        Description                          = $applicableESP.Description
                        InstallProgressTimeoutInMinutes      = $applicableESP.InstallProgressTimeoutInMinutes
                        InstallQualityUpdates                = $applicableESP.InstallQualityUpdates
                        ShowInstallationProgress             = $applicableESP.ShowInstallationProgress
                        TrackInstallProgressForAutopilotOnly = $applicableESP.TrackInstallProgressForAutopilotOnly
                        AllowDeviceResetOnInstallFailure     = $applicableESP.AllowDeviceResetOnInstallFailure
                        AllowDeviceUseOnInstallFailure       = $applicableESP.AllowDeviceUseOnInstallFailure
                        AllowLogCollectionOnInstallFailure   = $applicableESP.AllowLogCollectionOnInstallFailure
                        AllowNonBlockingAppInstallation      = $applicableESP.AllowNonBlockingAppInstallation
                        BlockedApps                          = $applicableESP.BlockedApps
                        Assignments                          = $applicableESP.Assignments
                    }
                }
                else { $null }
                AssignedPolicies     = $allApplicablePolicies | Group-Object -Property PolicyCategory | ForEach-Object {
                    [PSCustomObject]@{
                        Category = $_.Name
                        Count    = $_.Count
                        Policies = $_.Group | ForEach-Object {
                            [PSCustomObject]@{
                                Name           = $_.PolicyName
                                Id             = $_.PolicyId
                                AssignmentType = $_.AssignmentType
                                TargetName     = $_.TargetName
                                FilterName     = if ($_.FilterName -ne "No Filter" -and $_.FilterName -ne "Filter Not Found") { $_.FilterName } else { $null }
                                FilterType     = if ($_.FilterType -ne "None") { $_.FilterType } else { $null }
                            }
                        }
                    }
                }
                AssignedApplications = $finalApplications | Group-Object -Property PolicyName | ForEach-Object {
                    [PSCustomObject]@{
                        Name            = $_.Name
                        Id              = $_.Group[0].PolicyId
                        Category        = $_.Group[0].PolicyCategory
                        Assignments     = $_.Group | ForEach-Object {
                            [PSCustomObject]@{
                                AssignmentType   = $_.AssignmentType
                                AssignmentIntent = if ($_.AssignmentIntent) { $_.AssignmentIntent } else { "Unknown" }
                                TargetName       = $_.TargetName
                                FilterName       = if ($_.FilterName -ne "No Filter" -and $_.FilterName -ne "Filter Not Found") { $_.FilterName } else { $null }
                                FilterType       = if ($_.FilterType -ne "None") { $_.FilterType } else { $null }
                            }
                        }
                        AssignmentCount = $_.Group.Count
                    }
                }
                TotalPolicies        = $allApplicablePolicies.Count
                PolicyBreakdown      = [PSCustomObject]@{
                    DirectGroup = ($allApplicablePolicies | Where-Object { $_.AssignmentType -eq "Group (Include)" }).Count
                    AllDevices  = ($allApplicablePolicies | Where-Object { $_.AssignmentType -eq "All Devices" }).Count
                    AllUsers    = ($allApplicablePolicies | Where-Object { $_.AssignmentType -eq "All Users" }).Count
                    Excluded    = ($allApplicablePolicies | Where-Object { $_.AssignmentType -eq "Group (Exclude)" }).Count
                }
            }
            
            $enrollmentFlows += $enrollmentFlow
        }
    }
    
    Write-Host "Built $($enrollmentFlows.Count) enrollment flows" -ForegroundColor Green
    return $enrollmentFlows
}

function New-EnrollmentFlowHtmlReport {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [AllowEmptyCollection()]
        [array]$EnrollmentFlows,
        
        [Parameter(Mandatory = $false)]
        [string]$OutputPath = ".\IntuneEnrollmentFlow_$(Get-Date -Format 'yyyyMMdd_HHmmss').html",
        
        [Parameter(Mandatory = $false)]
        [string]$TenantName = "Unknown Tenant",
        
        [Parameter(Mandatory = $false)]
        [array]$AllPolicyAssignments = @()
    )
    
    Write-Host "Generating modern Bootstrap HTML report..." -ForegroundColor Green
    Write-Host "Enrollment flows count: $($EnrollmentFlows.Count)" -ForegroundColor Gray
    Write-Host "Output path: $OutputPath" -ForegroundColor Gray
    

    
    # Create a local copy of global assignments to avoid parameter modification issues
    $globalPolicyAssignments = if ($AllPolicyAssignments -and $AllPolicyAssignments.Count -gt 0) { 
        $AllPolicyAssignments | ForEach-Object { $_ }  # Create a copy
    } else { 
        @() 
    }
    
    # Validate and fix the output path
    if ([string]::IsNullOrWhiteSpace($OutputPath)) {
        $OutputPath = ".\IntuneEnrollmentFlow_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
        Write-Host "Empty output path provided, using default: $OutputPath" -ForegroundColor Yellow
    }
    
    # Check if the path ends with a directory separator (which would be invalid for a file)
    if ($OutputPath.EndsWith('\') -or $OutputPath.EndsWith('/')) {
        $OutputPath = $OutputPath.TrimEnd('\', '/') + "\IntuneEnrollmentFlow_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
        Write-Host "Directory path provided, appending filename: $OutputPath" -ForegroundColor Yellow
    }
    
    # Ensure the path has a file extension
    if (-not [System.IO.Path]::HasExtension($OutputPath)) {
        $OutputPath = $OutputPath + ".html"
        Write-Host "No file extension provided, adding .html: $OutputPath" -ForegroundColor Yellow
    }

    # Calculate summary statistics
    if ($EnrollmentFlows.Count -gt 0) {
        $uniqueGroups = ($EnrollmentFlows | ForEach-Object { $_.AssignedGroups } | Select-Object -Unique -Property Id).Count
        $uniqueAutopilotProfiles = ($EnrollmentFlows | Select-Object -ExpandProperty AutopilotProfile | Select-Object -Unique -Property Id).Count
        $totalPolicies = ($EnrollmentFlows | Measure-Object -Property TotalPolicies -Sum).Sum
    }
    else {
        $uniqueGroups = 0
        $uniqueAutopilotProfiles = 0
        $totalPolicies = 0
    }
    
    # Initialize StringBuilder for better performance
    $htmlBuilder = New-Object System.Text.StringBuilder
    
    # Add initial HTML content
    $null = $htmlBuilder.AppendLine(@"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>$TenantName Intune Enrollment Flow Visualization</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css">
    <link rel="stylesheet" href="https://cdn.datatables.net/1.13.6/css/dataTables.bootstrap5.min.css">
    <link rel="stylesheet" href="https://cdn.datatables.net/buttons/2.4.1/css/buttons.bootstrap5.min.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <script src="https://code.jquery.com/jquery-3.7.0.js"></script>
    <script src="https://cdn.datatables.net/1.13.6/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/1.13.6/js/dataTables.bootstrap5.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.1/js/dataTables.buttons.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.bootstrap5.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/pdfmake.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.53/vfs_fonts.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.html5.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.print.min.js"></script>
    <script src="https://cdn.datatables.net/buttons/2.4.1/js/buttons.colVis.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <style>
        :root {
            /* Light mode variables (default) */
            --primary-color: #0078d4;
            --secondary-color: #2b88d8;
            --permanent-color: #d83b01;
            --eligible-color: #107c10;
            --group-color: #5c2d91;
            --disabled-color: #d9534f;
            --enabled-color: #5cb85c;
            --service-principal-color: #0078d4;
            --na-color: #6c757d;
            --bg-color: #f8f9fa;
            --card-bg: #ffffff;
            --text-color: #333333;
            --table-header-bg: #f5f5f5;
            --table-header-color: #333333;
            --table-stripe-bg: rgba(0,0,0,0.02);
            --table-hover-bg: rgba(0,0,0,0.04);
            --table-border-color: #dee2e6;
            --filter-tag-bg: #e9ecef;
            --filter-tag-color: #495057;
            --filter-bg: white;
            --btn-outline-color: #6c757d;
            --border-color: #dee2e6;
            --toggle-bg: #ccc;
            --button-bg: #f8f9fa;
            --button-color: #333;
            --button-border: #ddd;
            --button-hover-bg: #e9ecef;
            --footer-text: white;
            --input-bg: #fff;
            --input-color: #333;
            --input-border: #ced4da;
            --input-focus-border: #86b7fe;
            --input-focus-shadow: rgba(13, 110, 253, 0.25);
            --datatable-even-row-bg: #fff;
            --datatable-odd-row-bg: #f9f9f9;
            --tab-active-bg: #0078d4;
            --tab-active-color: #fff;
            --accent-color: #107c10;
            --warning-color: #f59e0b;
            --danger-color: #d83b01;
            --info-color: #0078d4;
            --bg-primary: #ffffff;
            --bg-secondary: #f8f9fa;
            --bg-tertiary: #f1f5f9;
            --text-primary: #333333;
            --text-secondary: #6c757d;
            --text-muted: #94a3b8;
            --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
            --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
            --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
            --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
            --gradient-primary: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
            --gradient-secondary: linear-gradient(135deg, var(--secondary-color) 0%, var(--accent-color) 100%);
            --nav-bg: #f8f9fa;
            --nav-border: #dee2e6;
            --nav-link-color: #6c757d;
            --nav-link-active-color: #0078d4;
            --nav-link-active-bg: #ffffff;
        }
         
        [data-theme="dark"] {
            /* Dark mode variables */
            --primary-color: #0078d4;
            --secondary-color: #2b88d8;
            --permanent-color: #d83b01;
            --eligible-color: #107c10;
            --group-color: #5c2d91;
            --disabled-color: #6c757d;
            --enabled-color: #0078d4;
            --service-principal-color: #0078d4;
            --bg-color: #121212;
            --card-bg: #1e1e1e;
            --text-color: #e0e0e0;
            --table-header-bg: #333333;
            --table-header-color: #e0e0e0;
            --table-stripe-bg: rgba(255,255,255,0.03);
            --table-hover-bg: rgba(255,255,255,0.05);
            --table-border-color: #444444;
            --filter-bg: #252525;
            --btn-outline-color: #adb5bd;
            --border-color: #444444;
            --toggle-bg: #555555;
            --button-bg: #2a2a2a;
            --button-color: #e0e0e0;
            --button-border: #444;
            --button-hover-bg: #3a3a3a;
            --footer-text: white;
            --input-bg: #2a2a2a;
            --input-color: #e0e0e0;
            --input-border: #444444;
            --input-focus-border: #0078d4;
            --input-focus-shadow: rgba(0, 120, 212, 0.25);
            --datatable-even-row-bg: #1e1e1e;
            --datatable-odd-row-bg: #252525;
            --tab-active-bg: #0078d4;
            --tab-active-color: #fff;
            --accent-color: #107c10;
            --warning-color: #f59e0b;
            --danger-color: #d83b01;
            --info-color: #0078d4;
            --bg-primary: #121212;
            --bg-secondary: #1e1e1e;
            --bg-tertiary: #2a2a2a;
            --text-primary: #e0e0e0;
            --text-secondary: #adb5bd;
            --text-muted: #6c757d;
            --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.4);
            --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.4);
            --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.4);
            --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.4);
            --gradient-primary: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
            --gradient-secondary: linear-gradient(135deg, var(--secondary-color) 0%, var(--accent-color) 100%);
            --nav-bg: #1e1e1e;
            --nav-border: #444444;
            --nav-link-color: #adb5bd;
            --nav-link-active-color: #ffffff;
            --nav-link-active-bg: #0078d4;
        }
 
        * {
            box-sizing: border-box;
        }
 
        body {
            font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            margin: 0;
            padding: 0;
            background: var(--bg-color);
            min-height: 100vh;
            color: var(--text-color);
            line-height: 1.4;
            position: relative;
            transition: all 0.3s ease;
            font-size: 12px;
        }
 
        [data-theme="dark"] body {
            background: var(--bg-color);
        }
 
        body::before {
            display: none;
        }
 
        .app-container {
            min-height: 100vh;
            padding: 0;
            display: flex;
            flex-direction: column;
        }
         
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 0 20px;
            background: var(--card-bg);
            border-radius: 0;
            box-shadow: none;
            overflow: hidden;
            border: none;
            position: relative;
            transition: all 0.3s ease;
            flex: 1;
            display: flex;
            flex-direction: column;
        }
 
        /* Responsive container widths */
        @media (max-width: 576px) {
            .container {
                max-width: 100%;
                padding: 0 15px;
            }
        }
 
        @media (min-width: 577px) and (max-width: 768px) {
            .container {
                max-width: 95%;
                padding: 0 15px;
            }
        }
 
        @media (min-width: 769px) and (max-width: 992px) {
            .container {
                max-width: 90%;
                padding: 0 20px;
            }
        }
 
        @media (min-width: 993px) and (max-width: 1200px) {
            .container {
                max-width: 85%;
                padding: 0 20px;
            }
        }
 
        [data-theme="dark"] .container {
            background: var(--card-bg);
        }
 
        .container::before {
            display: none;
        }
         
        .dashboard-header {
            background: var(--gradient-primary);
            color: white;
            padding: 1.2rem 0.8rem;
            text-align: center;
            position: relative;
            overflow: hidden;
            margin: 0;
            border-radius: 0;
            z-index: 1;
        }
 
        .dashboard-header::before {
            display: none;
        }
 
        .dashboard-header::after {
            display: none;
        }
         
        .dashboard-title {
            position: relative;
            z-index: 1;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            margin-bottom: 0.3rem;
        }
         
        .dashboard-title h1 {
            margin: 0;
            font-size: 1rem;
            font-weight: 700;
            color: white;
            text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
         
        .logo {
            height: 20px;
            width: 20px;
            filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
        }
         
        .report-date {
            font-size: 0.65rem;
            color: rgba(255, 255, 255, 0.9);
            font-weight: 400;
            position: relative;
            z-index: 1;
        }
         
        .stats-section {
            padding: 0.6rem;
            background: var(--bg-secondary);
            transition: background-color 0.3s ease;
        }
 
        [data-theme="dark"] .stats-section {
            background: var(--bg-secondary);
        }
         
        .stats-container {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 1rem;
            padding: 1rem;
            margin-top: 1rem;
            margin-bottom: 1rem;
        }
         
        @media (max-width: 768px) {
            .stats-container {
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 0.8rem;
                padding: 0.8rem;
            }
        }
         
        @media (max-width: 480px) {
            .stats-container {
                grid-template-columns: 1fr 1fr;
                gap: 0.6rem;
                padding: 0.6rem;
            }
        }
         
        .stats-card {
            height: 100%;
            text-align: center;
            padding: 0.6rem 0.4rem;
            border-radius: 6px;
            color: white;
            position: relative;
            overflow: hidden;
            min-height: 60px;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: var(--shadow-md);
        }
 
        .stats-card:hover {
            transform: translateY(-2px);
            box-shadow: var(--shadow-lg);
        }
         
        .stats-card::before {
            content: '';
            position: absolute;
            top: -6px;
            right: -6px;
            width: 32px;
            height: 32px;
            border-radius: 50%;
            background-color: rgba(255,255,255,0.1);
            z-index: 0;
        }
         
        .stats-card i {
            font-size: 1rem;
            margin-bottom: 0.2rem;
            position: relative;
            z-index: 1;
        }
         
        .stats-card h3 {
            font-size: 0.6rem;
            font-weight: 600;
            margin-bottom: 0.1rem;
            position: relative;
            z-index: 1;
        }
         
        .stats-card .number {
            font-size: 1.1rem;
            font-weight: 700;
            position: relative;
            z-index: 1;
        }
         
        .primary-bg {
            background: var(--primary-color);
        }
         
        .success-bg {
            background: var(--eligible-color);
        }
         
        .warning-bg {
            background: var(--permanent-color);
        }
         
        .info-bg {
            background: var(--secondary-color);
        }
         
        .flows-section {
            padding: 0.6rem;
            background: var(--bg-primary);
            transition: background-color 0.3s ease;
            flex: 1;
            display: flex;
            flex-direction: column;
        }
 
        [data-theme="dark"] .flows-section {
            background: var(--bg-primary);
        }
         
        /* Navigation Tabs */
        .nav-tabs {
            border-bottom: 2px solid var(--nav-border);
            background-color: var(--nav-bg);
            border-radius: 6px 6px 0 0;
            padding: 3px 3px 0 3px;
            margin-bottom: 0;
            display: flex;
            justify-content: space-between;
        }
         
        .nav-tabs .nav-item {
            flex: 1;
            margin-right: 0;
        }
         
        .nav-tabs .nav-link {
            border: none;
            color: var(--nav-link-color);
            padding: 6px 8px;
            margin-right: 0;
            border-radius: 6px 6px 0 0;
            background-color: transparent;
            transition: all 0.3s ease;
            font-weight: 500;
            font-size: 0.7rem;
            text-align: center;
            width: 100%;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
         
        /* Add small gaps between tabs */
        .nav-tabs .nav-item:not(:last-child) {
            margin-right: 3px;
        }
         
        /* Responsive tabs for smaller screens */
        @media (max-width: 768px) {
            .nav-tabs .nav-link {
                font-size: 0.6rem;
                padding: 4px 6px;
            }
        }
         
        @media (max-width: 480px) {
            .nav-tabs {
                flex-wrap: wrap;
            }
             
            .nav-tabs .nav-item {
                flex: 1 1 50%;
                margin-bottom: 3px;
            }
             
            .nav-tabs .nav-link {
                font-size: 0.55rem;
                padding: 3px 4px;
            }
        }
         
        .nav-tabs .nav-link:hover {
            background-color: rgba(99, 102, 241, 0.1);
            color: var(--primary-color);
            transform: translateY(-1px);
        }
         
        .nav-tabs .nav-link.active {
            background-color: var(--nav-link-active-bg);
            color: var(--nav-link-active-color);
            border-bottom: 2px solid var(--primary-color);
            font-weight: 600;
            box-shadow: var(--shadow-md);
        }
         
        .tab-content {
            background-color: var(--card-bg);
            border: 1px solid var(--nav-border);
            border-top: none;
            border-radius: 0 0 6px 6px;
            padding: 0;
            box-shadow: var(--shadow-lg);
            flex: 1;
            overflow-y: auto;
        }
         
        /* Flow Steps - Vertical Layout */
        .flow-container {
            padding: 0.6rem;
            position: relative;
            overflow: visible;
        }
 
        .flow-title {
            text-align: center;
            margin-bottom: 0.8rem;
        }
 
        .flow-title h2 {
            font-size: 0.9rem;
            font-weight: 700;
            color: var(--text-primary);
            margin-bottom: 0.2rem;
        }
 
        .flow-title p {
            font-size: 0.65rem;
            color: var(--text-secondary);
        }
         
        .flow-step {
            background-color: var(--card-bg);
            border: 1px solid var(--border-color);
            border-radius: 6px;
            margin-bottom: 1.2rem;
            overflow: visible;
            box-shadow: var(--shadow-md);
            transition: all 0.3s ease;
            position: relative;
        }
         
        .flow-step:hover {
            transform: translateY(-1px);
            box-shadow: var(--shadow-lg);
        }
         
        .flow-step:not(:last-child)::after {
            content: '';
            position: absolute;
            bottom: -15px;
            left: 50%;
            transform: translateX(-50%);
            width: 0;
            height: 0;
            border-left: 10px solid transparent;
            border-right: 10px solid transparent;
            border-top: 15px solid var(--primary-color);
            z-index: 1000;
            filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
        }
         
        .flow-step:not(:last-child)::before {
            content: '';
            position: absolute;
            bottom: -5px;
            left: 50%;
            transform: translateX(-50%);
            width: 3px;
            height: 10px;
            background: var(--primary-color);
            z-index: 999;
            border-radius: 2px;
        }
         
        /* Add padding to flow steps that have arrows */
        .flow-step:not(:last-child) {
            padding-bottom: 20px;
        }
         
        .step-header {
            padding: 0.5rem 0.6rem;
            display: flex;
            align-items: center;
            gap: 0.3rem;
            font-size: 0.75rem;
            font-weight: 600;
            color: white;
        }
         
        .step-header i {
            font-size: 0.8rem;
        }
         
        .step-dynamic-group .step-header {
            background: var(--primary-color);
        }
         
        .step-autopilot .step-header {
            background: var(--secondary-color);
        }
         
        .step-esp .step-header {
            background: var(--group-color);
        }
         
        .blocked-apps-list {
            list-style: none;
            padding: 0;
            margin: 0.25rem 0;
            background: var(--bg-color);
            border: 1px solid var(--border-color);
            border-radius: 4px;
            max-height: 120px;
            overflow-y: auto;
        }
         
        .blocked-apps-list li {
            padding: 0.375rem 0.5rem;
            border-bottom: 1px solid var(--border-color);
            font-size: 0.7rem;
        }
         
        .blocked-apps-list li:last-child {
            border-bottom: none;
        }
         
        .blocked-apps-list li strong {
            color: var(--text-color);
        }
         
        .step-policies .step-header {
            background: var(--eligible-color);
        }
         
        /* Configuration Policies vertical stacking support */
        .step-policies .policy-assignment {
            display: block;
            line-height: 1.4;
            white-space: normal;
            word-wrap: break-word;
            overflow-wrap: break-word;
        }
         
        .step-policies .policy-filter {
            display: block;
            line-height: 1.4;
            white-space: normal;
            word-wrap: break-word;
            overflow-wrap: break-word;
        }
         
        /* Ensure configuration policies can show multiple lines */
        .step-policies .policy-item {
            align-items: flex-start;
            min-height: auto;
        }
         
        .step-applications .step-header {
            background: var(--warning-color);
        }
         
        .step-content {
            padding: 0.6rem;
        }
         
        .step-name {
            font-weight: 600;
            font-size: 0.75rem;
            margin-bottom: 0.3rem;
            color: var(--text-primary);
        }
         
        .group-tag {
            background: var(--primary-color);
            color: white;
            padding: 3px 6px;
            border-radius: 8px;
            font-size: 0.6rem;
            display: inline-block;
            margin-bottom: 0.3rem;
            font-weight: 500;
        }
         
        .membership-rule {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.2);
            border-left: 2px solid var(--primary-color);
            padding: 0.5rem;
            border-radius: 4px;
            font-family: 'Fira Code', 'Courier New', monospace;
            font-size: 0.6rem;
            margin-top: 0.3rem;
            word-break: break-all;
            line-height: 1.3;
            color: var(--text-primary);
        }
         
        .autopilot-settings {
            background: rgba(139, 92, 246, 0.1);
            border: 1px solid rgba(139, 92, 246, 0.2);
            border-left: 2px solid #8b5cf6;
            padding: 0.5rem;
            border-radius: 4px;
            margin-top: 0.3rem;
        }
         
        .autopilot-settings dt {
            font-weight: 600;
            color: var(--text-primary);
            margin-bottom: 0.1rem;
            font-size: 0.65rem;
        }
         
        .autopilot-settings dd {
            margin-bottom: 0.3rem;
            color: var(--text-secondary);
            font-size: 0.6rem;
        }
         
        .esp-config-section {
            margin-top: 1rem;
        }
         
        .dynamic-group-config-section {
            margin-top: 1rem;
        }
         
        .autopilot-config-section {
            margin-top: 1rem;
        }
         
        .config-toggle {
            border-color: var(--border-color);
            color: var(--text-primary);
            font-size: 0.75rem;
            padding: 0.4rem 0.8rem;
            transition: all 0.2s ease;
        }
         
        .config-toggle:hover {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
            color: white;
        }
         
        .config-toggle i {
            transition: transform 0.2s ease;
        }
         
        .config-toggle[aria-expanded="true"] i {
            transform: rotate(180deg);
        }
         
        .config-toggle[aria-expanded="true"] {
            background-color: var(--primary-color);
            border-color: var(--primary-color);
            color: white;
        }
         
        /* Step-level collapse functionality */
        .step-toggle {
            background-color: var(--card-bg);
            border: 1px solid var(--primary-color);
            color: var(--primary-color);
            padding: 0.5rem 1rem;
            border-radius: 6px;
            font-weight: 500;
            transition: all 0.3s ease;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 0.5rem;
            margin-bottom: 1rem;
        }
         
        .step-toggle:hover {
            background-color: var(--primary-color);
            color: white;
            transform: translateY(-2px);
            box-shadow: var(--shadow-md);
        }
         
        .step-toggle i {
            transition: transform 0.3s ease;
        }
         
        .step-toggle[aria-expanded="true"] i {
            transform: rotate(180deg);
        }
         
        .step-toggle[aria-expanded="true"] {
            background-color: var(--primary-color);
            color: white;
            border-color: var(--primary-color);
        }
         
        .policy-category {
            background: rgba(16, 185, 129, 0.1);
            border: 1px solid rgba(16, 185, 129, 0.2);
            border-left: 3px solid var(--accent-color);
            padding: 0.8rem;
            border-radius: 8px;
            margin-bottom: 0.8rem;
        }
         
        .policy-category h5 {
            color: var(--accent-color);
            margin-bottom: 0.6rem;
            font-size: 0.9rem;
            font-weight: 600;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
         
        .policy-count {
            background: var(--accent-color);
            color: white;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 0.65rem;
            font-weight: 500;
        }
         
        .policy-table-header {
            display: grid;
            grid-template-columns: 3fr 1.2fr 1.5fr 2.3fr;
            gap: 0.5rem;
            padding: 0.5rem 0.8rem;
            background: rgba(16, 185, 129, 0.1);
            border-radius: 4px;
            margin-bottom: 0.5rem;
            font-size: 0.65rem;
            font-weight: 600;
            color: var(--accent-color);
            text-transform: uppercase;
            letter-spacing: 0.3px;
            border: 1px solid rgba(16, 185, 129, 0.2);
        }
         
        /* Applications section header styling */
        .step-applications .policy-table-header {
            background: rgba(245, 158, 11, 0.1);
            color: var(--warning-color);
            border: 1px solid rgba(245, 158, 11, 0.2);
        }
         
        /* 5-column layout for applications */
        .step-applications .policy-table-header {
            grid-template-columns: 3fr 1.5fr 1fr 1.2fr 2fr;
            gap: 0.5rem;
        }
         
        .step-applications .policy-item {
            display: grid;
            grid-template-columns: 3fr 1.5fr 1fr 1.2fr 2fr;
            gap: 0.5rem;
            align-items: flex-start;
            padding: 0.4rem 0.8rem;
            margin-bottom: 0.2rem;
            background: var(--card-bg);
            border-radius: 4px;
            border-left: 3px solid var(--warning-color);
            font-size: 0.7rem;
            border: 1px solid var(--border-color);
            transition: all 0.2s ease;
            min-height: 2.5rem;
        }
         
        .step-applications .policy-item:hover {
            background: rgba(245, 158, 11, 0.05);
            transform: translateX(3px);
            box-shadow: var(--shadow-md);
            border-left-color: var(--warning-color);
        }
         
        .step-applications .policy-item:nth-child(odd) {
            background: rgba(245, 158, 11, 0.02);
        }
         
        .step-applications .policy-item:nth-child(even) {
            background: var(--card-bg);
        }
         
        .step-applications .policy-name {
            font-weight: 600;
            color: var(--text-primary);
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
         
        .step-applications .policy-assignment {
            display: block;
            padding-top: 0.1rem;
            line-height: 1.4;
        }
         
        .step-applications .policy-filter {
            display: block;
            padding-top: 0.1rem;
            word-wrap: break-word;
            overflow-wrap: break-word;
            hyphens: auto;
            line-height: 1.4;
        }
         
        .step-applications .policy-filter:last-child {
            word-wrap: break-word;
            overflow-wrap: break-word;
            hyphens: auto;
            line-height: 1.4;
        }
         
        .step-applications .filter-badge {
            display: inline-block;
            max-width: 100%;
            word-wrap: break-word;
            overflow-wrap: break-word;
            hyphens: auto;
            line-height: 1.3;
            white-space: normal;
            padding: 3px 6px;
            font-size: 0.55rem;
            border-radius: 8px;
        }
         
        .step-applications .assignment-badge {
            display: inline-block;
            max-width: 100%;
            word-wrap: break-word;
            overflow-wrap: break-word;
            hyphens: auto;
            line-height: 1.2;
            white-space: normal;
        }
         
        .step-applications .intent-badge {
            display: inline-block;
            max-width: 100%;
            word-wrap: break-word;
            overflow-wrap: break-word;
            hyphens: auto;
            line-height: 1.2;
            white-space: normal;
        }
         
        /* Continuation rows for additional assignments */
        .app-item-continuation {
            border-top: none !important;
            margin-top: 0 !important;
            padding-top: 0.2rem !important;
            border-left-color: transparent !important;
        }
         
        .app-item-continuation .policy-name {
            border-left: 3px solid var(--warning-color);
            margin-left: -0.8rem;
            padding-left: 0.8rem;
            height: 100%;
            min-height: 1.5rem;
        }
         
        .header-policy-name {
            text-align: left;
        }
         
        .header-assignment {
            text-align: left;
        }
         
        .header-filter {
            text-align: left;
        }
         
        /* Right-align specific filter columns */
        .step-dynamic-group .header-filter:last-child,
        .step-autopilot .header-filter:last-child,
        .step-esp .header-filter:last-child {
            text-align: right;
        }
         
        /* Applications specific header alignment */
        .step-applications .header-assignment {
            text-align: left;
        }
         
        .step-applications .header-filter:first-of-type {
            text-align: left;
        }
         
        .step-applications .header-filter:last-of-type {
            text-align: left;
        }
         
        @media (max-width: 768px) {
            .policy-table-header {
                display: none;
            }
        }
         
        .policy-item {
            background: rgba(255, 255, 255, 0.8);
            border: 1px solid var(--border-color);
            padding: 0.8rem;
            margin-bottom: 0.4rem;
            border-radius: 6px;
            font-size: 0.7rem;
            display: grid;
            grid-template-columns: 3fr 1.2fr 1.5fr 2.3fr;
            gap: 0.2rem;
            align-items: center;
            color: var(--text-primary);
            transition: all 0.2s ease;
        }
 
        .policy-item:hover {
            background: rgba(255, 255, 255, 1);
            transform: translateX(5px);
        }
         
        .policy-name {
            font-weight: 500;
            overflow: hidden;
            text-overflow: ellipsis;
            white-space: nowrap;
        }
         
        .policy-assignment {
            text-align: left;
            font-weight: 600;
            color: var(--primary-color);
        }
         
        .policy-filter {
            text-align: left;
            font-size: 0.65rem;
            color: var(--text-secondary);
            font-style: italic;
            font-weight: bold;
        }
         
        /* Right-align specific filter columns data */
        .step-dynamic-group .policy-filter:last-child,
        .step-autopilot .policy-filter:last-child,
        .step-esp .policy-filter:last-child {
            text-align: right;
            justify-content: flex-end;
            display: flex;
        }
         
        /* Responsive design for smaller screens */
        @media (max-width: 768px) {
            .policy-item {
                grid-template-columns: 1fr;
                gap: 0.5rem;
                padding: 0.6rem;
            }
             
            .step-applications .policy-item {
                grid-template-columns: 1fr;
                gap: 0.3rem;
            }
             
            .policy-assignment,
            .policy-filter {
                text-align: left;
                justify-content: flex-start;
            }
             
            .app-item-continuation .policy-name {
                display: none;
            }
        }
         
        [data-theme="dark"] .policy-item {
            background: rgba(51, 65, 85, 0.8);
            border-color: var(--border-color);
        }
 
        [data-theme="dark"] .policy-item:hover {
            background: rgba(51, 65, 85, 1);
        }
         
        [data-theme="dark"] .step-applications .policy-item {
            background: rgba(51, 65, 85, 0.6);
        }
         
        [data-theme="dark"] .step-applications .policy-item:nth-child(odd) {
            background: rgba(245, 158, 11, 0.05);
        }
         
        [data-theme="dark"] .step-applications .policy-item:nth-child(even) {
            background: rgba(51, 65, 85, 0.6);
        }
         
        [data-theme="dark"] .step-applications .policy-item:hover {
            background: rgba(245, 158, 11, 0.1);
        }
         
        .assignment-badge {
            font-size: 0.6rem;
            padding: 4px 8px;
            border-radius: 12px;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.3px;
            white-space: nowrap;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }
         
        .badge-all-devices {
            background: #0ea5e9;
            color: white;
        }
         
        .badge-all-users {
            background: #8b5a3c;
            color: white;
        }
         
        .badge-group {
            background: var(--accent-color);
            color: white;
        }
         
        .badge-group-exclude {
            background: var(--permanent-color);
            color: white;
            border: 2px solid #dc3545;
            position: relative;
        }
         
        .badge-group-exclude::before {
            content: '⚠️';
            margin-right: 4px;
        }
         
        .filter-badge {
            color: white;
            font-size: 0.6rem;
            padding: 4px 8px;
            border-radius: 12px;
            font-weight: 500;
            text-transform: uppercase;
            letter-spacing: 0.3px;
            display: inline-block;
            white-space: nowrap;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }
         
        .filter-include {
            background: var(--eligible-color);
        }
         
        .filter-exclude {
            background: var(--permanent-color);
        }
         
        .filter-none {
            background: var(--text-secondary);
            opacity: 0.7;
        }
         
        .assignment-row {
            margin-bottom: 0.15rem;
        }
         
        .assignment-row:last-child {
            margin-bottom: 0;
        }
         
        .filter-row {
            margin-bottom: 0.15rem;
        }
         
        .filter-row:last-child {
            margin-bottom: 0;
        }
         
        .assignment-badges {
            display: flex;
            align-items: center;
            gap: 6px;
            flex-wrap: wrap;
        }
         
        .intent-badge {
            display: inline-block;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 0.65rem;
            font-weight: 600;
            color: white;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin: 0;
            white-space: nowrap;
            box-shadow: 0 1px 2px rgba(0,0,0,0.1);
        }
        /* Group type badge for better contrast in group section */
        .group-type-badge {
            display: inline-block;
            padding: 4px 8px;
            border-radius: 12px;
            font-size: 0.65rem;
            font-weight: 600;
            color: #333;
            background: #e0e7ef;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin: 0;
            white-space: nowrap;
            box-shadow: 0 1px 2px rgba(0,0,0,0.08);
        }
        [data-theme="dark"] .group-type-badge {
            color: #fff;
            background: #444b5a;
        }
         
        .intent-required {
            background: #dc3545;
        }
         
        .intent-available {
            background: #28a745;
        }
         
        .intent-uninstall {
            background: #fd7e14;
        }
         
        .intent-available-no-enrollment {
            background: #17a2b8;
        }
         
        .intent-unknown {
            background: #6c757d;
        }
         
        .app-item .policy-assignment {
            line-height: 1.4;
        }
         
        .app-item .assignment-badge {
            margin-bottom: 2px;
        }
         
        .no-flows-message {
            text-align: center;
            padding: 2rem 1rem;
            color: var(--text-secondary);
        }
         
        .no-flows-message .icon {
            font-size: 3rem;
            margin-bottom: 1rem;
            opacity: 0.5;
        }
         
        .no-flows-message h3 {
            margin-bottom: 0.6rem;
            color: var(--text-primary);
            font-size: 1.2rem;
        }
         
        .no-flows-message ul {
            text-align: left;
            display: inline-block;
            color: var(--text-secondary);
            font-size: 0.8rem;
        }
         
        /* Theme toggle styles */
        .theme-toggle {
            position: absolute;
            top: 10px;
            right: 10px;
            z-index: 1050;
            display: flex;
            align-items: center;
            gap: 6px;
            background-color: var(--card-bg);
            padding: 6px 10px;
            border-radius: 30px;
            box-shadow: var(--shadow-lg);
            transition: all 0.3s ease;
            border: 1px solid var(--border-color);
        }
 
        [data-theme="dark"] .theme-toggle {
            background-color: var(--card-bg);
            border-color: var(--border-color);
        }
         
        .theme-toggle-switch {
            position: relative;
            display: inline-block;
            width: 40px;
            height: 20px;
        }
         
        .theme-toggle-switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }
         
        .theme-toggle-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #cbd5e1;
            transition: .4s;
            border-radius: 20px;
        }
         
        .theme-toggle-slider:before {
            position: absolute;
            content: "";
            height: 16px;
            width: 16px;
            left: 2px;
            bottom: 2px;
            background-color: white;
            transition: .4s;
            border-radius: 50%;
            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
        }
         
        input:checked + .theme-toggle-slider {
            background-color: var(--primary-color);
        }
         
        input:checked + .theme-toggle-slider:before {
            transform: translateX(20px);
        }
         
        .theme-icon {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            color: var(--text-primary);
        }
         
        footer {
            background: var(--gradient-primary);
            color: white;
            text-align: center;
            padding: 0.6rem 0;
            margin-top: auto;
        }
         
        footer p {
            margin: 0;
            font-weight: 500;
            font-size: 0.65rem;
        }
         
        /* Responsive design */
        @media (max-width: 768px) {
            .app-container {
                padding: 0.6rem;
            }
             
            .dashboard-header {
                padding: 1.5rem 0.6rem;
            }
             
            .dashboard-title {
                flex-direction: column;
                gap: 0.6rem;
            }
             
            .dashboard-title h1 {
                font-size: 1.2rem;
            }
             
            .logo {
                height: 32px;
                width: 32px;
            }
             
            .stats-card {
                min-height: 100px;
                padding: 0.8rem 0.6rem;
            }
             
            .stats-card .number {
                font-size: 1.5rem;
            }
             
            .policy-item {
                grid-template-columns: 1fr;
                gap: 0.5rem;
                text-align: left;
            }
             
            .policy-assignment {
                text-align: left;
                font-size: 0.65rem;
            }
             
            .policy-filter {
                text-align: left;
                font-size: 0.6rem;
            }
             
            .flow-container {
                padding: 1rem 0.6rem;
            }
             
            .flow-title h2 {
                font-size: 1.2rem;
            }
             
            .theme-toggle {
                position: relative;
                top: auto;
                right: auto;
                margin-bottom: 1rem;
                align-self: center;
            }
        }
         
        @media (max-width: 576px) {
            .step-header {
                padding: 1rem 1.5rem;
                font-size: 1.2rem;
            }
             
            .step-content {
                padding: 1.5rem;
            }
             
            .flow-step:not(:last-child)::after {
                content: '';
                position: absolute;
                bottom: -20px;
                left: 50%;
                transform: translateX(-50%);
                width: 0;
                height: 0;
                border-left: 15px solid transparent;
                border-right: 15px solid transparent;
                border-top: 20px solid var(--primary-color);
                z-index: 1000;
                filter: drop-shadow(0 2px 4px rgba(0,0,0,0.3));
            }
             
            .flow-step:not(:last-child)::before {
                content: '';
                position: absolute;
                bottom: -8px;
                left: 50%;
                transform: translateX(-50%);
                width: 4px;
                height: 12px;
                background: var(--primary-color);
                z-index: 999;
                border-radius: 2px;
            }
        }
    </style>
</head>
<body>
    <!-- Dark Mode Toggle -->
    <div class="theme-toggle">
        <div class="theme-icon">
            <i class="fas fa-sun"></i>
        </div>
        <label class="theme-toggle-switch">
            <input type="checkbox" id="themeToggle">
            <span class="theme-toggle-slider"></span>
        </label>
        <div class="theme-icon">
            <i class="fas fa-moon"></i>
        </div>
    </div>
     
    <div class="app-container">
        <div class="container">
        <div class="dashboard-header">
            <div class="dashboard-title">
                <svg class="logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
                    <path fill="#ff5722" d="M6 6H22V22H6z" transform="rotate(-180 14 14)"/>
                    <path fill="#4caf50" d="M26 6H42V22H26z" transform="rotate(-180 34 14)"/>
                    <path fill="#ffc107" d="M6 26H22V42H6z" transform="rotate(-180 14 34)"/>
                    <path fill="#03a9f4" d="M26 26H42V42H26z" transform="rotate(-180 34 34)"/>
                </svg>
                <h1>$TenantName Enrollment Flow Visualization</h1>
            </div>
            <div class="report-date">
                <i class="fas fa-calendar-alt me-2"></i> Report generated on: $(Get-Date -Format 'MMMM dd, yyyy \a\t HH:mm')
            </div>
        </div>
         
        <div class="stats-container">
            <div class="stats-card primary-bg">
                <i class="fas fa-sitemap"></i>
                <h3>Complete Flows</h3>
                <div class="number">$($EnrollmentFlows.Count)</div>
            </div>
            <div class="stats-card info-bg">
                <i class="fas fa-users"></i>
                <h3>Groups</h3>
                <div class="number">$uniqueGroups</div>
            </div>
            <div class="stats-card warning-bg">
                <i class="fas fa-rocket"></i>
                <h3>Autopilot Profiles</h3>
                <div class="number">$uniqueAutopilotProfiles</div>
            </div>
            <div class="stats-card success-bg">
                <i class="fas fa-cog"></i>
                <h3>Total Policies</h3>
                <div class="number">$totalPolicies</div>
            </div>
        </div>
 
        <!-- Enrollment Flows -->
        <div class="row">
            <div class="col-12">
"@
)
    
    if ($EnrollmentFlows.Count -eq 0) {
        $null = $htmlBuilder.AppendLine(@"
                <div class="card">
                    <div class="card-body">
                        <div class="no-flows-message">
                            <div class="icon">
                                <i class="fas fa-exclamation-triangle"></i>
                            </div>
                            <h3>No Enrollment Flows Found</h3>
                            <p>No complete enrollment flows were detected in this tenant.</p>
                            <p>This could mean:</p>
                            <ul>
                                <li>No Autopilot profiles are assigned to Groups</li>
                                <li>Groups don't have valid membership rules</li>
                                <li>Autopilot profiles are assigned to static groups only</li>
                            </ul>
                        </div>
                    </div>
                </div>
"@
)
    }
    else {
        # Generate navigation tabs
        $null = $htmlBuilder.AppendLine(@"
                <ul class="nav nav-tabs" id="flowTabs" role="tablist">
"@
)
        
        for ($i = 0; $i -lt $EnrollmentFlows.Count; $i++) {
            $autopilotProfileName = $EnrollmentFlows[$i].AutopilotProfile.Name
            $isActive = if ($i -eq 0) { "active" } else { "" }
            $null = $htmlBuilder.AppendLine(@"
                    <li class="nav-item" role="presentation">
                        <button class="nav-link $isActive" id="flow-$i-tab" data-bs-toggle="tab" data-bs-target="#flow-$i" type="button" role="tab" aria-controls="flow-$i" aria-selected="$(if ($i -eq 0) { "true" } else { "false" })">
                            <i class="fas fa-rocket me-2"></i>$autopilotProfileName
                        </button>
                    </li>
"@
)
        }
        
        $null = $htmlBuilder.AppendLine(@"
                </ul>
                 
                <div class="tab-content" id="flowTabContent">
"@
)
        # Generate flow content for each tab - BUILD HTML IN CHUNKS FOR BETTER PERFORMANCE
        $htmlChunks = [System.Collections.Generic.List[string]]::new()
        
        # Global ESP deduplication - track ESP profiles across all flows
        $globalESPProfilesProcessed = @{}
        
        for ($i = 0; $i -lt $EnrollmentFlows.Count; $i++) {
            $flow = $EnrollmentFlows[$i]
            $isActive = if ($i -eq 0) { "show active" } else { "" }
            
            # Build the entire flow HTML as a single string to minimize AppendLine calls
            $flowHtml = @"
                    <div class="tab-pane fade $isActive" id="flow-$i" role="tabpanel" aria-labelledby="flow-$i-tab" tabindex="0">
                        <div class="flow-container">
                            <div class="text-center mb-4">
                                <h2><i class="fas fa-route me-3"></i>$($flow.AutopilotProfile.Name)</h2>
                                <p class="text-muted">Complete enrollment flow visualization</p>
                            </div>
                             
                            <!-- Group Step -->
                            <div class="flow-step step-dynamic-group">
                                <div class="step-header">
                                    <i class="fas fa-users"></i>
                                    <span>Group</span>
                                </div>
                                <div class="step-content">
                                    <button class="btn btn-outline-primary btn-sm step-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#dynamicGroupStep-$i" aria-expanded="false" aria-controls="dynamicGroupStep-$i">
                                        <i class="fas fa-chevron-down"></i>Expand Group Details
                                    </button>
                                    <div class="collapse" id="dynamicGroupStep-$i">
"@

            
            # Show all assigned groups
            if ($flow.AssignedGroups -and $flow.AssignedGroups.Count -gt 0) {
                # Check if any groups have tags
                $hasGroupTags = $false
                foreach ($group in $flow.AssignedGroups) {
                    if ($group.GroupTag) {
                        $hasGroupTags = $true
                        break
                    }
                }
                
                $flowHtml += @"
                                        <div class="step-name">$($flow.AssignedGroups.Count) Assigned Groups</div>
                                     
                                    <!-- Group Assignment Information -->
                                    <div class="policy-category">
                                        <h5>
                                            Group Assignment
                                            <span class="policy-count">$($flow.AssignedGroups.Count)</span>
                                        </h5>
"@

                
                # For visual consistency, use 3-column layout for Group Assignment (removed assignment column)
                $flowHtml += @"
                                        <div class="policy-table-header" style="grid-template-columns: 3fr 1.5fr 2.3fr;">
                                            <div class="header-policy-name">Group</div>
                                            <div class="header-filter">Type</div>
                                            <div class="header-filter">Group Tag</div>
                                        </div>
"@

                
                # Add each assigned group
                foreach ($group in $flow.AssignedGroups) {
                    # Use 3-column layout for group items (removed assignment column)
                    $flowHtml += @"
                                        <div class="policy-item" style="grid-template-columns: 3fr 1.5fr 2.3fr;">
                                            <div class="policy-name">$($group.Name)</div>
                                            <div class="policy-filter">
                                                <span class="group-type-badge">$(if ($group.IsDynamic) { "Dynamic" } else { "Static" })</span>
                                            </div>
                                            <div class="policy-filter">
"@

                        
                        # Add group tag if available
                        if ($group.GroupTag) {
                            $flowHtml += @"
                                                Tag: $($group.GroupTag)
"@

                        }
                        
                        $flowHtml += @"
                                            </div>
                                        </div>
"@

                }
                
                $flowHtml += @"
                                    </div>
                                     
                                    <!-- Group Configuration Details -->
                                    <div class="dynamic-group-config-section">
                                        <button class="btn btn-outline-secondary btn-sm config-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#dynamicGroupConfig-$i" aria-expanded="false" aria-controls="dynamicGroupConfig-$i">
                                            <i class="fas fa-chevron-down me-2"></i>Show Configuration Details
                                        </button>
                                        <div class="collapse mt-3" id="dynamicGroupConfig-$i">
                                            <div class="autopilot-settings">
"@

                
                # Show configuration details for each group
                foreach ($group in $flow.AssignedGroups) {
                    $flowHtml += @"
                                                <div class="group-config-item">
                                                    <h6>$($group.Name)</h6>
                                                    <dl class="row">
"@

                    
                    # Show membership rule only for dynamic groups
                    if ($group.IsDynamic -and $group.MembershipRule) {
                        $flowHtml += @"
                                                        <dt class="col-sm-3">Membership Rule:</dt>
                                                        <dd class="col-sm-9">$($group.MembershipRule)</dd>
"@

                    }
                    else {
                        $flowHtml += @"
                                                        <dt class="col-sm-3">Group Type:</dt>
                                                        <dd class="col-sm-9">Static Group (manually managed membership)</dd>
"@

                    }
                    
                    $flowHtml += @"
                                                    </dl>
                                                </div>
"@

                }
                
                $flowHtml += @"
                                            </div>
                                        </div>
                                    </div>
"@

            }
            else {
                $flowHtml += @"
                                        <div class="step-name">No Groups Assigned</div>
                                     
                                    <!-- Group Assignment Information -->
                                    <div class="policy-category">
                                        <h5>
                                            Group Assignment
                                            <span class="policy-count">0</span>
                                        </h5>
                                        <div class="alert alert-warning">
                                            No groups are assigned to this Autopilot profile.
                                        </div>
                                    </div>
"@

            }
            
            $flowHtml += @"
                                    </div>
                                </div>
                            </div>
                             
                            <!-- Autopilot Profile Step -->
                            <div class="flow-step step-autopilot">
                                <div class="step-header">
                                    <i class="fas fa-rocket"></i>
                                    <span>Autopilot Profile</span>
                                </div>
                                <div class="step-content">
                                    <button class="btn btn-outline-primary btn-sm step-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#autopilotStep-$i" aria-expanded="false" aria-controls="autopilotStep-$i">
                                        <i class="fas fa-chevron-down"></i>Expand Autopilot Profile Details
                                    </button>
                                    <div class="collapse" id="autopilotStep-$i">
                                        <div class="step-name">$($flow.AutopilotProfile.Name)</div>
                                     
                                    <!-- Autopilot Assignment Information -->
                                    <div class="policy-category">
                                        <h5>
                                            Autopilot Assignment
                                            <span class="policy-count">1</span>
                                        </h5>
"@

            
            # Process autopilot assignments efficiently - Show profile once with all assignments
            if ($flow.AutopilotProfile.Assignments -and $flow.AutopilotProfile.Assignments.Count -gt 0) {
                # Get unique assignment types (consolidate all groups into single badge)
                $uniqueAssignmentTypes = @{}
                $hasFilters = $false
                $filterInfo = @()
                
                foreach ($assignment in $flow.AutopilotProfile.Assignments) {
                    # Consolidate all group assignments into a single "Group" type
                    $assignmentType = switch ($assignment.AssignmentType) {
                        "Group (Include)" { "Group" }
                        "Group (Exclude)" { "Group (Exclude)" }
                        default { $assignment.AssignmentType }
                    }
                    
                    # Track unique assignment types
                    if ($assignmentType -and -not $uniqueAssignmentTypes.ContainsKey($assignmentType)) {
                        $uniqueAssignmentTypes[$assignmentType] = $true
                    }
                    
                    # Collect filter information
                    if ($assignment.FilterName -and $assignment.FilterName -ne "No Filter" -and $assignment.FilterName -ne "Filter Not Found") {
                        $filterClass = switch ($assignment.FilterType) {
                            "include" { "filter-include" }
                            "exclude" { "filter-exclude" }
                            default { $null }
                        }
                        $filterTypeText = switch ($assignment.FilterType) {
                            "include" { "Include" }
                            "exclude" { "Exclude" }
                            default { $null }
                        }
                        
                        # Only add filter info if we have valid filter type and class
                        if ($filterTypeText -and $filterClass) {
                            $hasFilters = $true
                            $filterKey = "$filterTypeText|$($assignment.FilterName)"
                            if ($filterInfo -notcontains $filterKey) {
                                $filterInfo += $filterKey
                            }
                        }
                    }
                }
                
                # Use 3-column layout for Autopilot (removed language column)
                $flowHtml += @"
                                        <div class="policy-table-header" style="grid-template-columns: 3fr 1.2fr 2.3fr;">
                                            <div class="header-policy-name">Autopilot Profile</div>
                                            <div class="header-assignment">Assignment</div>
                                            <div class="header-filter">Filter</div>
                                        </div>
"@

                
                # Build assignment text for unique types only
                $assignmentTexts = @()
                foreach ($assignmentType in $uniqueAssignmentTypes.Keys) {
                    $assignmentText = switch ($assignmentType) {
                        "All Devices" { "All Devices" }
                        "All Users" { "All Users" }
                        "Group" { "Group" }
                        "Group (Exclude)" { "Group (Exclude)" }
                        default { $assignmentType }
                    }
                    
                    $assignmentTexts += $assignmentText
                }
                
                # Build filter text
                $filterTexts = @()
                if ($hasFilters -and $filterInfo.Count -gt 0) {
                    foreach ($filter in $filterInfo) {
                        $filterParts = $filter -split '\|'
                        $filterTypeText = $filterParts[0]
                        $filterName = $filterParts[1]
                        $filterTexts += "${filterTypeText}: $filterName"
                    }
                }
                
                # Create a single policy item with all assignments using 3-column layout
                $allAssignmentText = $assignmentTexts -join ", "
                $allFilterText = if ($filterTexts.Count -gt 0) { $filterTexts -join ", " } else { "" }
                
                # Use 3-column layout for Autopilot (removed language column)
                $flowHtml += @"
                                        <div class="policy-item" style="grid-template-columns: 3fr 1.2fr 2.3fr;">
                                            <div class="policy-name">$($flow.AutopilotProfile.Name)</div>
                                            <div class="policy-assignment">$allAssignmentText</div>
                                            <div class="policy-filter">$allFilterText</div>
                                        </div>
"@

            }
            else {
                $flowHtml += @"
                                        <div class="policy-item" style="grid-template-columns: 3fr 1.2fr 2.3fr;">
                                            <div class="policy-name">$($flow.AutopilotProfile.Name)</div>
                                            <div class="policy-assignment">No Assignment</div>
                                            <div class="policy-filter"></div>
                                        </div>
"@

            }
            
            $flowHtml += @"
                                    </div>
"@

            
            # Add autopilot configuration details if available
            if ($flow.AutopilotProfile.DeviceNameTemplate -or $flow.AutopilotProfile.Language -or $flow.AutopilotProfile.Locale) {
                $flowHtml += @"
                                     
                                    <!-- Autopilot Configuration Details -->
                                    <div class="autopilot-config-section">
                                        <button class="btn btn-outline-secondary btn-sm config-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#autopilotConfig-$i" aria-expanded="false" aria-controls="autopilotConfig-$i">
                                            <i class="fas fa-chevron-down me-2"></i>Show Configuration Details
                                        </button>
                                        <div class="collapse mt-3" id="autopilotConfig-$i">
                                            <div class="autopilot-settings">
"@

                
                if ($flow.AutopilotProfile.DeviceNameTemplate) {
                    $flowHtml += @"
                                                <dt>Device Name Template:</dt>
                                                <dd>$($flow.AutopilotProfile.DeviceNameTemplate)</dd>
"@

                }
                
                if ($flow.AutopilotProfile.Language) {
                    $flowHtml += @"
                                                <dt>Language:</dt>
                                                <dd>$($flow.AutopilotProfile.Language)</dd>
"@

                }
                
                if ($flow.AutopilotProfile.Locale) {
                    $flowHtml += @"
                                                <dt>Locale:</dt>
                                                <dd>$($flow.AutopilotProfile.Locale)</dd>
"@

                }
                
                $flowHtml += @"
                                            </div>
                                        </div>
                                    </div>
"@

            }
            
            $flowHtml += @"
                                    </div>
                                </div>
                            </div>
                             
                            <!-- ESP Step -->
                            <div class="flow-step step-esp">
                                <div class="step-header">
                                    <i class="fas fa-shield-alt"></i>
                                    <span>Enrollment Status Page</span>
                                </div>
                                <div class="step-content">
                                    <button class="btn btn-outline-primary btn-sm step-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#espStep-$i" aria-expanded="false" aria-controls="espStep-$i">
                                        <i class="fas fa-chevron-down"></i>Expand ESP Configuration
                                    </button>
                                    <div class="collapse" id="espStep-$i">
"@

            
            # Add ESP configuration content
            if ($flow.ESPConfiguration) {
                # Always show ESP configuration details for each flow (removed global deduplication)
                $flowHtml += @"
                                        <div class="step-name">$($flow.ESPConfiguration.Name)</div>
                                         
                                        <!-- ESP Assignment Information -->
                                        <div class="policy-category">
                                            <h5>
                                                ESP Assignment
                                                <span class="policy-count">1</span>
                                            </h5>
"@

                    
                    # Use 3-column layout for ESP (removed apps column)
                    $flowHtml += @"
                                            <div class="policy-table-header" style="grid-template-columns: 3fr 1.2fr 2.3fr;">
                                                <div class="header-policy-name">ESP Profile</div>
                                                <div class="header-assignment">Assignment</div>
                                                <div class="header-filter">Filter</div>
                                            </div>
"@

                    
                    # Collect all assignments from all flows that use this ESP profile
                    $allESPAssignments = @()
                    foreach ($checkFlow in $EnrollmentFlows) {
                        if ($checkFlow.ESPConfiguration -and $checkFlow.ESPConfiguration.Id -eq $flow.ESPConfiguration.Id) {
                            $allESPAssignments += $checkFlow.ESPConfiguration.Assignments
                        }
                    }
                    
                    # Collect unique assignment types and filters for this ESP profile from all flows
                    $uniqueAssignmentTypes = @{}
                    $hasFilters = $false
                    $filterInfo = @()
                    
                    foreach ($assignment in $allESPAssignments) {
                        # Track unique assignment types
                        $assignmentType = $assignment.Type
                        if ($assignmentType -and -not $uniqueAssignmentTypes.ContainsKey($assignmentType)) {
                            $uniqueAssignmentTypes[$assignmentType] = $true
                        }
                        
                        # Check for actual filters (not just FilterId existence)
                        if ($assignment.FilterId -and $assignment.FilterId -ne "" -and $null -ne $assignment.FilterId) {
                            $filterClass = switch ($assignment.FilterType) {
                                "include" { "filter-include" }
                                "exclude" { "filter-exclude" }
                                default { $null }
                            }
                            $filterTypeText = switch ($assignment.FilterType) {
                                "include" { "Include" }
                                "exclude" { "Exclude" }
                                default { $null }
                            }
                            
                            # Only add filter info if we have valid filter type and class
                            if ($filterTypeText -and $filterClass) {
                                $hasFilters = $true
                                $filterKey = "$filterTypeText|$($assignment.FilterId)"
                                if ($filterInfo -notcontains $filterKey) {
                                    $filterInfo += $filterKey
                                }
                            }
                        }
                    }
                    
                    # Build assignment text for unique types only
                    $assignmentTexts = @()
                    foreach ($assignmentType in $uniqueAssignmentTypes.Keys) {
                        $assignmentText = switch ($assignmentType) {
                            "All Devices" { "All Devices" }
                            "All Users" { "All Users" }
                            "Group" { "Group" }
                            default { $assignmentType }
                        }
                        
                        $assignmentTexts += $assignmentText
                    }
                    
                    # Build filter text only if there are actual filters
                    $filterTexts = @()
                    if ($hasFilters -and $filterInfo.Count -gt 0) {
                        foreach ($filter in $filterInfo) {
                            $filterParts = $filter -split '\|'
                            $filterTypeText = $filterParts[0]
                            $filterName = $filterParts[1]
                            $filterTexts += "${filterTypeText}: $filterName"
                        }
                    }
                    
                    # Create a single entry for the ESP profile with deduplicated assignments using 3-column layout
                    $allAssignmentText = $assignmentTexts -join ", "
                    $allFilterText = if ($filterTexts.Count -gt 0) { $filterTexts -join ", " } else { "" }
                    
                    # Use 3-column layout for ESP (removed apps column)
                    $blockedAppsText = if ($flow.ESPConfiguration.BlockedApps -and $flow.ESPConfiguration.BlockedApps.Count -gt 0) {
                        "$($flow.ESPConfiguration.BlockedApps.Count) blocked"
                    } else {
                        "No blocked apps"
                    }
                    
                    $flowHtml += @"
                                            <div class="policy-item" style="grid-template-columns: 3fr 1.2fr 2.3fr;">
                                                <div class="policy-name">$($flow.ESPConfiguration.Name)</div>
                                                <div class="policy-assignment">$allAssignmentText</div>
                                                <div class="policy-filter">$allFilterText</div>
                                            </div>
"@

                
                    $flowHtml += @"
                                        </div>
                                         
                                        <!-- ESP Configuration Details -->
                                        <div class="esp-config-section">
                                            <button class="btn btn-outline-secondary btn-sm config-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#espConfig-$i" aria-expanded="false" aria-controls="espConfig-$i">
                                                <i class="fas fa-chevron-down me-2"></i>Show Configuration Details
                                            </button>
                                            <div class="collapse mt-3" id="espConfig-$i">
                                                <div class="autopilot-settings">
"@

                    
                    if ($flow.ESPConfiguration.Description) {
                        $flowHtml += @"
                                                    <dt>Description:</dt>
                                                    <dd>$($flow.ESPConfiguration.Description)</dd>
"@

                    }
                    
                    if ($flow.ESPConfiguration.InstallProgressTimeoutInMinutes) {
                        $flowHtml += @"
                                                    <dt>Show an error when installation takes longer than specified number of minutes:</dt>
                                                    <dd>$($flow.ESPConfiguration.InstallProgressTimeoutInMinutes) minutes</dd>
"@

                    }
                    
                    $flowHtml += @"
                                                    <dt>Show app and profile configuration progress:</dt>
                                                    <dd>$($flow.ESPConfiguration.ShowInstallationProgress)</dd>
                                                    <dt>Track Progress for Autopilot Only:</dt>
                                                    <dd>$($flow.ESPConfiguration.TrackInstallProgressForAutopilotOnly)</dd>
                                                    <dt>Install Quality Updates:</dt>
                                                    <dd>$($flow.ESPConfiguration.InstallQualityUpdates)</dd>
                                                    <dt>Allow Device Reset on Install Failure:</dt>
                                                    <dd>$($flow.ESPConfiguration.AllowDeviceResetOnInstallFailure)</dd>
                                                    <dt>Allow Device Use on Install Failure:</dt>
                                                    <dd>$($flow.ESPConfiguration.AllowDeviceUseOnInstallFailure)</dd>
                                                    <dt>Allow Log Collection on Install Failure:</dt>
                                                    <dd>$($flow.ESPConfiguration.AllowLogCollectionOnInstallFailure)</dd>
                                                    <dt>Allow Non-Blocking App Installation:</dt>
                                                    <dd>$($flow.ESPConfiguration.AllowNonBlockingAppInstallation)</dd>
"@

                    
                    if ($flow.ESPConfiguration.BlockedApps -and $flow.ESPConfiguration.BlockedApps.Count -gt 0) {
                        $flowHtml += @"
                                                    <dt>Blocked Apps:</dt>
                                                    <dd>
                                                        <ul class="blocked-apps-list">
"@

                        foreach ($blockedApp in $flow.ESPConfiguration.BlockedApps) {
                            $flowHtml += @"
                                                            <li><strong>$($blockedApp.DisplayName)</strong> - $($blockedApp.Publisher)</li>
"@

                        }
                        $flowHtml += @"
                                                        </ul>
                                                    </dd>
"@

                    }
                    
                    $flowHtml += @"
                                                </div>
                                            </div>
                                        </div>
"@

                }
            else {
                $flowHtml += @"
                                        <div class="step-name">No ESP Configuration Found</div>
                                        <p class="text-muted">No ESP configuration is assigned to this enrollment flow.</p>
"@

            }
            
            $flowHtml += @"
                                    </div>
                                </div>
                            </div>
                             
                            <!-- Configuration Policies Step -->
                            <div class="flow-step step-policies">
                                <div class="step-header">
                                    <i class="fas fa-cogs"></i>
                                    <span>Configuration Policies</span>
                                </div>
                                <div class="step-content">
                                    <button class="btn btn-outline-primary btn-sm step-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#policiesStep-$i" aria-expanded="false" aria-controls="policiesStep-$i">
                                        <i class="fas fa-chevron-down"></i>Expand Configuration Policies ($($flow.TotalPolicies))
                                    </button>
                                    <div class="collapse" id="policiesStep-$i">
"@

            
            # Add configuration policies content
            if ($flow.AssignedPolicies -and $flow.AssignedPolicies.Count -gt 0) {
                foreach ($category in $flow.AssignedPolicies) {
                    # Check if any policies in this category have filters
                    $hasFilters = $false
                    foreach ($policy in $category.Policies) {
                        if ($policy.FilterName -and $policy.FilterName -ne "" -and $policy.FilterName -ne "No Filter" -and $policy.FilterName -ne "Filter Not Found") {
                            $hasFilters = $true
                            break
                        }
                    }
                    
                    $flowHtml += @"
                                        <div class="policy-category">
                                            <h5>
                                                $($category.Category)
                                                <span class="policy-count">$($category.Count)</span>
                                            </h5>
"@

                    
                    # For visual consistency, use 4-column layout for Configuration Policies (removed badge column)
                    $flowHtml += @"
                                        <div class="policy-table-header" style="grid-template-columns: 3fr 1.5fr 1.2fr 2fr;">
                                            <div class="header-policy-name">Policy Name</div>
                                            <div class="header-assignment">Assignment</div>
                                            <div class="header-filter">Intent</div>
                                            <div class="header-filter">Filter</div>
                                        </div>
"@

                    
                    # Group policies by name and collect all assignments from global data
                    $groupedPolicies = @{}
                    foreach ($policy in $category.Policies) {
                        if (-not $groupedPolicies.ContainsKey($policy.Name)) {
                            $groupedPolicies[$policy.Name] = @()
                        }
                        $groupedPolicies[$policy.Name] += $policy
                    }
                    

                    
                    foreach ($policyName in $groupedPolicies.Keys) {
                        # ALWAYS use global data to show ALL assignments for each policy
                        if ($globalPolicyAssignments -and $globalPolicyAssignments.Count -gt 0) {
                            # Use global data to get all assignments for this policy across all flows
                            # Note: Flow data uses 'Name' property, global data uses 'PolicyName' property
                            $allPolicyAssignments = $globalPolicyAssignments | Where-Object { $_.PolicyName -eq $policyName }
                        } 
                        
                        # Only fall back to flow-specific data if we have NO global data at all
                        if ((-not $globalPolicyAssignments) -or ($globalPolicyAssignments.Count -eq 0)) {
                            # Fallback to flow-specific data if global data is not available
                            $allPolicyAssignments = $groupedPolicies[$policyName]
                        }
                        
                        # If global lookup failed (policy name mismatch), fall back to flow data
                        if ($allPolicyAssignments.Count -eq 0 -and $groupedPolicies[$policyName]) {
                            $allPolicyAssignments = $groupedPolicies[$policyName]
                        }
                        

                        
                        # Sort assignments: Include first, then Exclude
                        $sortedPolicyAssignments = $allPolicyAssignments | Sort-Object @(
                            @{ Expression = {
                                switch ($_.AssignmentType) {
                                    "All Devices" { 1 }
                                    "All Users" { 2 }
                                    "Group (Include)" { 3 }
                                    "Group (Exclude)" { 4 }
                                    default { 5 }
                                }
                            }; Ascending = $true }
                        )
                        
                        # Build all assignment details for this policy
                        $assignmentLines = @()
                        $intentLines = @()
                        $filterLines = @()
                        
                        foreach ($policy in $sortedPolicyAssignments) {
                            # Assignment text with group name and type
                            $assignmentText = switch ($policy.AssignmentType) {
                                "All Devices" { "All Devices" }
                                "All Users" { "All Users" }
                                "Group (Include)" { $policy.TargetName }
                                "Group (Exclude)" { $policy.TargetName }
                                default { $policy.TargetName }
                            }
                            
                            # Intent text
                            $intentText = switch ($policy.AssignmentType) {
                                "Group (Include)" { "Include" }
                                "Group (Exclude)" { "Exclude" }
                                "All Devices" { "Include" }
                                "All Users" { "Include" }
                                default { "Include" }
                            }
                            
                            # Filter text - include it on the same line as assignment
                            if ($policy.FilterName -and $policy.FilterName -ne "" -and $policy.FilterName -ne "No Filter" -and $policy.FilterName -ne "Filter Not Found") {
                                $filterTypeText = switch ($policy.FilterType) {
                                    "include" { "Include" }
                                    "exclude" { "Exclude" }
                                    default { "Filter" }
                                }
                                $filterText = "${filterTypeText}: $($policy.FilterName)"
                            } else {
                                $filterText = ""
                            }
                            
                            # Build combined lines to maintain alignment
                            $assignmentLines += $assignmentText
                            $intentLines += $intentText  
                            $filterLines += $filterText
                        }
                        
                        # Join all assignment details with line breaks to maintain one-to-one alignment
                        $assignmentHtml = $assignmentLines -join "<br>"
                        $intentHtml = $intentLines -join "<br>"
                        $filterHtml = $filterLines -join "<br>"
                        
                        # Use 4-column layout for Configuration Policies (removed badge column)
                        $flowHtml += @"
                                        <div class="policy-item" style="grid-template-columns: 3fr 1.5fr 1.2fr 2fr;">
                                            <div class="policy-name">$policyName</div>
                                            <div class="policy-assignment">$assignmentHtml</div>
                                            <div class="policy-filter">$intentHtml</div>
                                            <div class="policy-filter">$filterHtml</div>
                                        </div>
"@

                    }
                    
                    $flowHtml += @"
                                        </div>
"@

                }
            }
            else {
                $flowHtml += @"
                                        <div class="policy-category">
                                            <h5>No Configuration Policies</h5>
                                            <p class="text-muted">No configuration policies are assigned to this enrollment flow.</p>
                                        </div>
"@

            }
            
            $flowHtml += @"
                                    </div>
                                </div>
                            </div>
                             
                            <!-- Applications Step -->
                            <div class="flow-step step-applications">
                                <div class="step-header">
                                    <i class="fas fa-mobile-alt"></i>
                                    <span>Applications</span>
                                </div>
                                <div class="step-content">
                                    <button class="btn btn-outline-primary btn-sm step-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#applicationsStep-$i" aria-expanded="false" aria-controls="applicationsStep-$i">
                                        <i class="fas fa-chevron-down"></i>Expand Applications ($($flow.AssignedApplications.Count))
                                    </button>
                                    <div class="collapse" id="applicationsStep-$i">
"@

            
            # Add applications content
            if ($flow.AssignedApplications -and $flow.AssignedApplications.Count -gt 0) {
                $flowHtml += @"
                                        <div class="policy-category">
                                            <h5>
                                                Applications
                                                <span class="policy-count">$($flow.AssignedApplications.Count)</span>
                                            </h5>
                                            <div class="policy-table-header" style="grid-template-columns: 3fr 1.5fr 1.2fr 2fr;">
                                                <div class="header-policy-name">Application Name</div>
                                                <div class="header-assignment">Group</div>
                                                <div class="header-filter">Intent</div>
                                                <div class="header-filter">Filter</div>
                                            </div>
"@

                
                foreach ($application in $flow.AssignedApplications) {
                    # Sort assignments: Required > Available > Uninstall, and Include before Exclude
                    $sortedAssignments = $application.Assignments | Sort-Object @(
                        @{ Expression = {
                            switch ($_.AssignmentIntent) {
                                "required" { 1 }
                                "available" { 2 }
                                "availableWithoutEnrollment" { 3 }
                                "uninstall" { 4 }
                                default { 5 }
                            }
                        }; Ascending = $true },
                        @{ Expression = {
                            switch ($_.AssignmentType) {
                                "All Devices" { 1 }
                                "All Users" { 2 }
                                "Group (Include)" { 3 }
                                "Group (Exclude)" { 4 }
                                default { 5 }
                            }
                        }; Ascending = $true }
                    )
                    
                    # Build all assignment details for this application
                    $groupNames = @()
                    $intentTexts = @()
                    $filterTexts = @()
                    
                    foreach ($assignment in $sortedAssignments) {
                        # Group name
                        $groupName = switch ($assignment.AssignmentType) {
                            "All Devices" { "All Devices" }
                            "All Users" { "All Users" }
                            "Group (Include)" { $assignment.TargetName }
                            "Group (Exclude)" { $assignment.TargetName }
                            default { $assignment.TargetName }
                        }
                        $groupNames += $groupName
                        
                        # Intent text (no badges)
                        $intentText = switch ($assignment.AssignmentIntent) {
                            "required" { "Required" }
                            "available" { "Available" }
                            "uninstall" { "Uninstall" }
                            "availableWithoutEnrollment" { "Available (No Enrollment)" }
                            default { "Unknown" }
                        }
                        $intentTexts += $intentText
                        
                        # Filter text (no badges)
                        if ($assignment.FilterName) {
                            $filterTypeText = switch ($assignment.FilterType) {
                                "include" { "Include" }
                                "exclude" { "Exclude" }
                                default { "Filter" }
                            }
                            $filterTexts += "${filterTypeText}: $($assignment.FilterName)"
                        } else {
                            $filterTexts += ""
                        }
                    }
                    
                    # Join all text with line breaks to maintain alignment
                    $groupHtml = $groupNames -join "<br>"
                    $intentHtml = $intentTexts -join "<br>"
                    $filterHtml = $filterTexts -join "<br>"
                    
                    $flowHtml += @"
                                            <div class="policy-item" style="grid-template-columns: 3fr 1.5fr 1.2fr 2fr;">
                                                <div class="policy-name">$($application.Name)</div>
                                                <div class="policy-assignment">$groupHtml</div>
                                                <div class="policy-filter">$intentHtml</div>
                                                <div class="policy-filter">$filterHtml</div>
                                            </div>
"@

                }
                
                $flowHtml += @"
                                        </div>
"@

            }
            else {
                $flowHtml += @"
                                        <div class="policy-category">
                                            <h5>No Applications</h5>
                                            <p class="text-muted">No applications are assigned to this enrollment flow.</p>
                                        </div>
"@

            }
            
            $flowHtml += @"
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
"@

            
            # Add the completed flow HTML to chunks
            $htmlChunks.Add($flowHtml)
        }
        
        # Add all flow chunks at once to StringBuilder
        $null = $htmlBuilder.AppendLine(($htmlChunks -join ""))
        
        $null = $htmlBuilder.AppendLine( @"
                </div>
"@
)
    } # End of else block
    
    $null = $htmlBuilder.AppendLine( @"
            </div>
        </div>
    </div>
"@
)
    $null = $htmlBuilder.AppendLine( @"
    <footer>
        <p>Generated by Roy Klooster - RK Solutions</p>
    </footer>
 
    <script>
        // Initialize DataTables if any tables exist
        document.addEventListener('DOMContentLoaded', function() {
            // Theme toggling functionality
            const themeToggle = document.getElementById('themeToggle');
            const prefersDarkScheme = window.matchMedia('(prefers-color-scheme: dark)');
             
            // Check for saved user preference, or use system preference
            const savedTheme = localStorage.getItem('theme');
             
            if (savedTheme === 'dark' || (!savedTheme && prefersDarkScheme.matches)) {
                document.documentElement.setAttribute('data-theme', 'dark');
                themeToggle.checked = true;
            }
             
            // Add event listener for theme toggle
            themeToggle.addEventListener('change', function() {
                if (this.checked) {
                    document.documentElement.setAttribute('data-theme', 'dark');
                    localStorage.setItem('theme', 'dark');
                } else {
                    document.documentElement.setAttribute('data-theme', 'light');
                    localStorage.setItem('theme', 'light');
                }
            });
 
            // Handle system theme changes
            prefersDarkScheme.addEventListener('change', (e) => {
                if (!localStorage.getItem('theme')) {
                    if (e.matches) {
                        document.documentElement.setAttribute('data-theme', 'dark');
                        themeToggle.checked = true;
                    } else {
                        document.documentElement.setAttribute('data-theme', 'light');
                        themeToggle.checked = false;
                    }
                }
            });
             
            // Handle ESP configuration collapse toggle
            document.querySelectorAll('.config-toggle').forEach(function(button) {
                button.addEventListener('click', function() {
                    // Use a timeout to allow Bootstrap to update the aria-expanded attribute
                    setTimeout(() => {
                        const isExpanded = this.getAttribute('aria-expanded') === 'true';
                        const icon = this.querySelector('i');
                         
                        if (isExpanded) {
                            this.innerHTML = '<i class="fas fa-chevron-up me-2"></i>Hide Configuration Details';
                        } else {
                            this.innerHTML = '<i class="fas fa-chevron-down me-2"></i>Show Configuration Details';
                        }
                    }, 100);
                });
            });
             
            // Handle step-level collapse toggle
            document.querySelectorAll('.step-toggle').forEach(function(button) {
                button.addEventListener('click', function() {
                    // Use a timeout to allow Bootstrap to update the aria-expanded attribute
                    setTimeout(() => {
                        const isExpanded = this.getAttribute('aria-expanded') === 'true';
                        const icon = this.querySelector('i');
                        const stepName = this.textContent.replace('Expand ', '').replace('Collapse ', '');
                         
                        if (isExpanded) {
                            this.innerHTML = '<i class="fas fa-chevron-up"></i>Collapse ' + stepName;
                        } else {
                            this.innerHTML = '<i class="fas fa-chevron-down"></i>Expand ' + stepName;
                        }
                    }, 100);
                });
            });
             
            if (jQuery && jQuery.fn.DataTable) {
                jQuery('table').each(function() {
                    if (!jQuery(this).hasClass('dataTable')) {
                        jQuery(this).DataTable({
                            responsive: true,
                            pageLength: 25,
                            order: [[0, 'asc']],
                            dom: 'Bfrtip',
                            buttons: [
                                'copy', 'csv', 'excel', 'pdf', 'print'
                            ]
                        });
                    }
                });
            }
 
            // Add smooth scrolling for anchor links
            document.querySelectorAll('a[href^="#"]').forEach(anchor => {
                anchor.addEventListener('click', function (e) {
                    e.preventDefault();
                    const target = document.querySelector(this.getAttribute('href'));
                    if (target) {
                        target.scrollIntoView({
                            behavior: 'smooth',
                            block: 'start'
                        });
                    }
                });
            });
 
            // Initialize Bootstrap tooltips
            const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
            tooltipTriggerList.map(function (tooltipTriggerEl) {
                return new bootstrap.Tooltip(tooltipTriggerEl);
            });
 
            // Add animation on scroll
            const observerOptions = {
                threshold: 0.1,
                rootMargin: '0px 0px -50px 0px'
            };
 
            const observer = new IntersectionObserver((entries) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        entry.target.style.opacity = '1';
                        entry.target.style.transform = 'translateY(0)';
                    }
                });
            }, observerOptions);
 
            // Observe flow steps for animation
            document.querySelectorAll('.flow-step').forEach(step => {
                step.style.opacity = '0';
                step.style.transform = 'translateY(20px)';
                step.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
                observer.observe(step);
            });
 
            // Enhance tab switching
            document.querySelectorAll('[data-bs-toggle="tab"]').forEach(tab => {
                tab.addEventListener('shown.bs.tab', function (e) {
                    // Re-trigger animations when switching tabs
                    const targetPane = document.querySelector(e.target.getAttribute('data-bs-target'));
                    if (targetPane) {
                        const steps = targetPane.querySelectorAll('.flow-step');
                        steps.forEach((step, index) => {
                            setTimeout(() => {
                                step.style.opacity = '1';
                                step.style.transform = 'translateY(0)';
                            }, index * 200);
                        });
                    }
                });
            });
        });
 
        // Export functions
        window.exportAllFlows = function() {
            alert('Combined view coming soon! For now, you can see individual flows in separate tabs.');
        };
    </script>
</body>
</html>
"@
)
    $htmlContent = $htmlBuilder.ToString()
    if ($htmlContent.Length -eq 0) {
        Write-Host "ERROR: HTML content is empty!" -ForegroundColor Red
        return $null
    }
    
    try {
        # Convert to absolute path if relative
        $absolutePath = if (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
            Join-Path (Get-Location) $OutputPath
        }
        else {
            $OutputPath
        }
        
        # Ensure directory exists
        $directory = [System.IO.Path]::GetDirectoryName($absolutePath)
        if (-not (Test-Path $directory)) {
            Write-Host "Creating directory: $directory" -ForegroundColor Gray
            New-Item -ItemType Directory -Path $directory -Force | Out-Null
        }
        
        # Use UTF8 without BOM for better compatibility
        Write-Host "Writing HTML content to file: $absolutePath" -ForegroundColor Gray
        Write-Host "HTML content length: $($htmlContent.Length) characters" -ForegroundColor Gray
        [System.IO.File]::WriteAllText($absolutePath, $htmlContent, [System.Text.UTF8Encoding]::new($false))
        
        # Wait a moment for file system to catch up
        Start-Sleep -Milliseconds 100
        
        # Verify file was created
        if (Test-Path $absolutePath) {
            $fileInfo = Get-Item $absolutePath
            Write-Host "File created successfully: $($fileInfo.FullName)" -ForegroundColor Green
            Write-Host "File size: $($fileInfo.Length) bytes" -ForegroundColor Gray
            
            # Open the HTML file automatically
            try {
                Write-Host "Opening HTML report in default browser..." -ForegroundColor Cyan
                Invoke-Item $absolutePath
            }
            catch {
                Write-Host "Could not automatically open HTML file: $($_.Exception.Message)" -ForegroundColor Yellow
                Write-Host "You can manually open: $absolutePath" -ForegroundColor Cyan
            }
            
            return $absolutePath
        }
        else {
            Write-Host "ERROR: File was not created at $absolutePath" -ForegroundColor Red
            # Try alternative method
            Write-Host "Attempting alternative file creation method..." -ForegroundColor Yellow
            $htmlBuilder.ToString() | Out-File -FilePath $absolutePath -Encoding UTF8
            
            if (Test-Path $absolutePath) {
                $fileInfo = Get-Item $absolutePath
                Write-Host "File created successfully using alternative method: $($fileInfo.FullName)" -ForegroundColor Green
                
                # Open the HTML file automatically
                try {
                    Write-Host "Opening HTML report in default browser..." -ForegroundColor Cyan
                    Invoke-Item $absolutePath
                }
                catch {
                    Write-Host "Could not automatically open HTML file: $($_.Exception.Message)" -ForegroundColor Yellow
                    Write-Host "You can manually open: $absolutePath" -ForegroundColor Cyan
                }
                
                return $absolutePath
            }
            else {
                Write-Host "ERROR: Alternative method also failed" -ForegroundColor Red
                return $null
            }
        }
    }
    catch {
        Write-Error "Error generating HTML report: $($_.Exception.Message)"
        return $null
    }

}
# Connect to the Graph API
Write-Verbose "Connecting to Graph API..."
try {
    # Determine which authentication method to use based on parameters
    $connectionParams = @{
        RequiredScopes = $RequiredScopes
        Verbose        = $VerbosePreference -eq 'Continue'
    }

    # Add parameters based on which ones were provided
    if ($PSCmdlet.ParameterSetName -eq "Interactive") {
        if ($TenantId) { $connectionParams.TenantId = $TenantId }
        if ($ClientId) { $connectionParams.ClientId = $ClientId }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "ClientSecret") {
        $connectionParams.TenantId = $TenantId
        $connectionParams.ClientId = $ClientId
        $connectionParams.ClientSecret = $ClientSecret
    }
    elseif ($PSCmdlet.ParameterSetName -eq "Certificate") {
        $connectionParams.TenantId = $TenantId
        $connectionParams.ClientId = $ClientId
        $connectionParams.CertificateThumbprint = $CertificateThumbprint
    }
    elseif ($PSCmdlet.ParameterSetName -eq "Identity") {
        $connectionParams.Identity = $true
        if ($TenantId) { $connectionParams.TenantId = $TenantId }
    }
    elseif ($PSCmdlet.ParameterSetName -eq "AccessToken") {
        $connectionParams.AccessToken = $AccessToken
        $connectionParams.TenantId = $TenantId
    }
    
    $connected = Connect-ToMgGraph @connectionParams

    if ($connected) {
        Write-Verbose "Connected to Graph API successfully."

        # Get Tenant Name
        $tenantInfo = Invoke-MgGraphRequest -Uri "beta/organization" -Method Get -OutputType PSObject
        $tenantName = $tenantInfo.value[0].displayName
        Write-Host "Connected to tenant: $tenantName" -ForegroundColor Green

        # Execute the comprehensive policy retrieval
        Write-Host "`nExecuting comprehensive Intune policy and assignment analysis..." -ForegroundColor Cyan
        
        # Get all policies with their detailed assignments
        $allPolicyAssignments = Get-AllIntunePoliciesWithAssignments -IncludeFilterDetails:$IncludeFilterDetails -ExportToCsv:$ExportToCsv -ExportPath $ExportPath
        
        Write-Host "`nAnalysis complete! Found $($allPolicyAssignments.Count) total policy assignments." -ForegroundColor Green
        
        # Get ESP configurations
        $espConfigurations = Get-ESPConfigurations
        Write-Host "Found $($espConfigurations.Count) ESP configurations" -ForegroundColor Green
        

        
        # Generate HTML Enrollment Flow Report if requested
        Write-Host "`nChecking if HTML report should be generated..." -ForegroundColor Cyan
        if ($GenerateHtmlReport) {
            Write-Host "HTML report generation requested - proceeding..." -ForegroundColor Green
            Write-Host "`nGenerating HTML Enrollment Flow Report..." -ForegroundColor Cyan
            
            try {
                $enrollmentFlows = Get-EnrollmentFlowData -PolicyAssignments $allPolicyAssignments -ESPConfigurations $espConfigurations
                Write-Host "Found $($enrollmentFlows.Count) enrollment flows" -ForegroundColor Yellow
                
                # Always generate HTML report, even if no flows found
                $htmlPath = if ($HtmlReportPath) { 
                    # Convert to absolute path if relative
                    if (-not [System.IO.Path]::IsPathRooted($HtmlReportPath)) {
                        Join-Path (Get-Location) $HtmlReportPath
                    }
                    else {
                        $HtmlReportPath
                    }
                }
                else { 
                    # Use absolute path for default filename
                    Join-Path (Get-Location) "EnrollmentFlowReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
                }
                Write-Host "Generating HTML report at: $htmlPath" -ForegroundColor Gray
                
                $generatedReport = New-EnrollmentFlowHtmlReport -EnrollmentFlows $enrollmentFlows -OutputPath $htmlPath -TenantName $tenantName -AllPolicyAssignments $allPolicyAssignments
                
                Write-Host "`n=== FILE VERIFICATION ===" -ForegroundColor Magenta
                if ($generatedReport -and (Test-Path $generatedReport)) {
                    $fileInfo = Get-Item $generatedReport
                    Write-Host "HTML Enrollment Flow Report generated: $($fileInfo.FullName)" -ForegroundColor Green
                    Write-Host "File size: $($fileInfo.Length) bytes" -ForegroundColor Green
                    Write-Host "You can open it with: Invoke-Item '$($fileInfo.FullName)'" -ForegroundColor Cyan
                    
                    if ($enrollmentFlows.Count -gt 0) {
                        # Show enrollment flow summary
                        Write-Host "`n=== ENROLLMENT FLOW SUMMARY ===" -ForegroundColor Magenta
                        Write-Host "Total enrollment flows: $($enrollmentFlows.Count)" -ForegroundColor White
                        
                        foreach ($flow in $enrollmentFlows) {
                            Write-Host "`nFlow: $($flow.FlowName)" -ForegroundColor Cyan
                            Write-Host " Group: $($flow.DynamicGroup.Name)" -ForegroundColor Gray
                            if ($flow.DynamicGroup.GroupTag) {
                                Write-Host " Group Tag: $($flow.DynamicGroup.GroupTag)" -ForegroundColor Yellow
                            }
                            Write-Host " Autopilot Profile: $($flow.AutopilotProfile.Name)" -ForegroundColor Gray
                            Write-Host " Assigned Policies: $($flow.TotalPolicies)" -ForegroundColor Gray
                            Write-Host " └─ Direct Group: $($flow.PolicyBreakdown.DirectGroup)" -ForegroundColor DarkGray
                            Write-Host " └─ All Devices: $($flow.PolicyBreakdown.AllDevices)" -ForegroundColor DarkGray
                            Write-Host " └─ All Users: $($flow.PolicyBreakdown.AllUsers)" -ForegroundColor DarkGray
                            Write-Host " └─ Excluded: $($flow.PolicyBreakdown.Excluded)" -ForegroundColor DarkGray
                            
                            if ($flow.AssignedPolicies) {
                                foreach ($category in $flow.AssignedPolicies) {
                                    Write-Host " $($category.Category): $($category.Count) policies" -ForegroundColor DarkGray
                                }
                            }
                        }
                    }
                    else {
                        Write-Host "`nNo enrollment flows found, but HTML report was generated with all policy data" -ForegroundColor Yellow
                    }
                }
                else {
                    Write-Host "Failed to generate HTML report" -ForegroundColor Red
                }
            }
            catch {
                Write-Host "Error generating HTML report: $($_.Exception.Message)" -ForegroundColor Red
                Write-Host "Stack trace: $($_.ScriptStackTrace)" -ForegroundColor Red
            }
        }
        else {
            Write-Host "HTML report generation NOT requested (GenerateHtmlReport = $GenerateHtmlReport)" -ForegroundColor Yellow
        }
        
        # Additional analysis - Show policies by assignment type
        Write-Host "`n=== ASSIGNMENT TYPE BREAKDOWN ===" -ForegroundColor Magenta
        $assignmentTypeGroups = $allPolicyAssignments | Group-Object -Property AssignmentType
        foreach ($group in $assignmentTypeGroups | Sort-Object Name) {
            Write-Host "$($group.Name): $($group.Count) assignments" -ForegroundColor White
        }
        
        # Show filter usage (only if filters are actually used)
        $filterGroups = $allPolicyAssignments | Where-Object { $_.FilterName -ne "No Filter" -and $_.FilterName -ne "Filter Not Found" } | Group-Object -Property FilterName
        if ($filterGroups) {
            Write-Host "`n=== FILTER USAGE ANALYSIS ===" -ForegroundColor Magenta
            Write-Host "Policies using filters: $($filterGroups.Count) unique filters" -ForegroundColor White
            foreach ($group in $filterGroups | Sort-Object Name) {
                Write-Host " $($group.Name): Used in $($group.Count) assignments" -ForegroundColor Gray
            }
        }

    }
    else {
        throw "Failed to connect to Microsoft Graph API."
    }
}
catch { 
    Write-Error "Error: $_"
    throw $_
}
finally {
    # Disconnect from Microsoft Graph if connected
    $contextInfo = Get-MgContext -ErrorAction SilentlyContinue
    if ($contextInfo) {
        Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null 
        Write-Host "Disconnected from Microsoft Graph." -ForegroundColor Green
    }
    else {
        Write-Host "No active connection to disconnect." -ForegroundColor Yellow
    }
}