internal/functions/Initialize-EasyPIMPolicies.ps1

# Initialize-EasyPIMPolicies function for EasyPIM.Orchestrator
# Simplified version focused on the orchestrator's needs

function Initialize-EasyPIMPolicies {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [PSCustomObject]$Config,

        [Parameter(Mandatory = $false)]
        [hashtable]$PolicyTemplates = @{},

        [Parameter(Mandatory = $false)]
        [ValidateSet("All", "AzureRoles", "EntraRoles", "GroupRoles")]
        [string[]]$PolicyOperations = @("All"),

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

    Write-Verbose "Starting Initialize-EasyPIMPolicies (Orchestrator)"

    try {
        # Simplified validation - skip complex validation for now
        Write-Verbose "� Processing configuration..."

        $processedConfig = @{}

        # Initialize policy templates - merge parameter and config templates
        $policyTemplates = $PolicyTemplates.Clone()
        if ($Config.PSObject.Properties['PolicyTemplates'] -and $Config.PolicyTemplates) {
            $cfgTemplates = $Config.PolicyTemplates
            if ($cfgTemplates -is [System.Collections.IDictionary]) {
                foreach ($templateName in $cfgTemplates.Keys) {
                    $policyTemplates[$templateName] = $cfgTemplates[$templateName]
                }
            } else {
                foreach ($templateName in $cfgTemplates.PSObject.Properties.Name) {
                    $policyTemplates[$templateName] = $cfgTemplates.$templateName
                }
            }
        }
        Write-Verbose "Found $($policyTemplates.Keys.Count) policy templates"

        $processAzure  = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'AzureRoles')
        $processEntra  = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'EntraRoles')
        $processGroups = ($PolicyOperations -contains 'All' -or $PolicyOperations -contains 'GroupRoles')

        # Entra policy source detection and conflict check
        $entraArrayTopPresent = ($Config.PSObject.Properties['EntraRolePolicies'] -and $Config.EntraRolePolicies)
        $entraNestedPresent   = ($Config.PSObject.Properties['EntraRoles'] -and $Config.EntraRoles.PSObject.Properties['Policies'] -and $Config.EntraRoles.Policies)

        if ($processEntra -and $entraArrayTopPresent -and $entraNestedPresent) {
            throw "Both EntraRolePolicies and EntraRoles.Policies are present. Only one format is allowed."
        }

        if ($processEntra -and ($entraArrayTopPresent -or $entraNestedPresent)) {
            Write-Verbose "Processing Entra Role policies"
            if (-not $processedConfig.PSObject.Properties['EntraRolePolicies']) {
                $processedConfig.EntraRolePolicies = @()
            }

            $polNode = if ($entraNestedPresent) { $Config.EntraRoles.Policies } else { $Config.EntraRolePolicies }
            $sourceLabel = if ($entraNestedPresent) { "EntraRoles.Policies" } else { "EntraRolePolicies" }

            if ($polNode -is [System.Collections.IDictionary] -or
                ($polNode -is [psobject] -and
                 $polNode.PSObject -and
                 $polNode.PSObject.Properties.Count -gt 0 -and
                 -not ($polNode -is [System.Collections.IEnumerable] -and $polNode -isnot [string]))) {

                Write-Verbose "Processing $sourceLabel as object/dictionary format"
                foreach ($roleName in $polNode.PSObject.Properties.Name) {
                    $policyContent = $polNode.$roleName
                    if (-not $policyContent) { continue }

                    if ($policyContent.PSObject.Properties['Template'] -and $policyContent.Template) {
                        $policyDefinition = [PSCustomObject]@{
                            RoleName = $roleName
                            PolicySource = 'template'
                            Template = $policyContent.Template
                        }
                        foreach ($prop in $policyContent.PSObject.Properties) {
                            if ($prop.Name -ne 'Template') {
                                $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
                            }
                        }
                    } else {
                        $policyDefinition = [PSCustomObject]@{
                            RoleName = $roleName
                            PolicySource = 'inline'
                            Policy = $policyContent
                        }
                    }
                    $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'EntraRole'
                    $processedConfig.EntraRolePolicies += $processedPolicy
                }
            }
            elseif ($polNode -is [System.Collections.IEnumerable] -and $polNode -isnot [string]) {
                Write-Verbose "Processing $sourceLabel as array format"
                foreach ($entry in $polNode) {
                    if (-not ($entry -is [psobject])) { continue }

                    $roleName = if ($entry.PSObject.Properties['RoleName']) { $entry.RoleName } else { $null }
                    if (-not $roleName) {
                        throw "EntraRole array policy entry missing required property: RoleName"
                    }

                    $hasTemplate = ($entry.PSObject.Properties['Template'] -and $entry.Template)
                    $hasInlinePolicy = ($entry.PSObject.Properties['Policy'] -and $entry.Policy)
                    $hasPolicySource = ($entry.PSObject.Properties['PolicySource'] -and $entry.PolicySource)

                    if ($hasTemplate) {
                        $policyDefinition = [PSCustomObject]@{
                            RoleName = $roleName
                            PolicySource = 'template'
                            Template = $entry.Template
                        }
                        foreach ($prop in $entry.PSObject.Properties) {
                            if ($prop.Name -notin @('Template', 'RoleName', 'Policy')) {
                                try {
                                    $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
                                } catch {
                                }
                            }
                        }
                    }
                    elseif ($hasInlinePolicy -or ($hasPolicySource -and $entry.PolicySource -eq 'inline')) {
                        $inlinePolicy = if ($hasInlinePolicy) {
                            $entry.Policy
                        } else {
                            $props = @{}
                            foreach ($prop in $entry.PSObject.Properties) {
                                if ($prop.Name -notin @('RoleName', 'Template', 'PolicySource')) {
                                    $props[$prop.Name] = $prop.Value
                                }
                            }
                            [PSCustomObject]$props
                        }
                        $policyDefinition = [PSCustomObject]@{
                            RoleName = $roleName
                            PolicySource = 'inline'
                            Policy = $inlinePolicy
                        }
                    }
                    else {
                        throw "EntraRole array policy requires Template or inline Policy for role '$roleName'"
                    }

                    $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'EntraRole'
                    $processedConfig.EntraRolePolicies += $processedPolicy
                }
            }
            else {
                throw "Unsupported type for Entra policies: $($polNode.GetType().FullName)"
            }

            Write-Verbose "Processed $($processedConfig.EntraRolePolicies.Count) Entra Role policies from $sourceLabel"
        }

        # Azure policy source detection and conflict check
        $azureArrayPresent = ($Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies'] -and $Config.AzureRoles.Policies)
        $azureObjectPresent = ($Config.PSObject.Properties['AzureRolePolicies'] -and $Config.AzureRolePolicies)

        if ($processAzure -and $azureArrayPresent -and $azureObjectPresent) {
            throw "Both AzureRoles.Policies and AzureRolePolicies are present. Only one format is allowed."
        }
        # New format sections - AzureRoles.Policies
        if ($processAzure -and (($Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies'] -and $Config.AzureRoles.Policies) -or ($Config.PSObject.Properties['AzureRolePolicies'] -and $Config.AzureRolePolicies))) {
            $processedConfig.AzureRolePolicies = @()
            $polNode = $null
            if ($Config.PSObject.Properties['AzureRoles'] -and $Config.AzureRoles.PSObject.Properties['Policies'] -and $Config.AzureRoles.Policies) {
                Write-Verbose "Processing AzureRoles.Policies section"
                $polNode = $Config.AzureRoles.Policies
            } elseif ($Config.PSObject.Properties['AzureRolePolicies'] -and $Config.AzureRolePolicies) {
                Write-Verbose "Processing legacy AzureRolePolicies section"
                $polNode = $Config.AzureRolePolicies
            }

            # Backward-compatible dictionary/object format: { "RoleName": { ... } }
            if ($polNode -is [System.Collections.IDictionary] -or ($polNode -is [psobject] -and $polNode.PSObject -and $polNode.PSObject.Properties.Count -gt 0)) {
                foreach ($roleName in $polNode.PSObject.Properties.Name) {
                    $policyContent = $polNode.$roleName
                    $scope = $null
                    if ($policyContent.PSObject.Properties['Scope'] -and $policyContent.Scope) { $scope = $policyContent.Scope }
                    if ($policyContent.PSObject.Properties['Template'] -and $policyContent.Template) {
                        $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'template'; Template = $policyContent.Template }
                        foreach ($prop in $policyContent.PSObject.Properties) {
                            if ($prop.Name -ne 'Template' -and $prop.Name -ne 'Scope') {
                                $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
                            }
                        }
                    } else {
                        $policyOnly = $policyContent | Select-Object -Property * -ExcludeProperty Scope
                        $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'inline'; Policy = $policyOnly }
                    }
                    $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'AzureRole'
                    $processedConfig.AzureRolePolicies += $processedPolicy
                }
            }
            # New array format: [ { RoleName, Scope, Template|Policy, PolicySource, ... }, ... ]
            elseif ($polNode -is [System.Collections.IEnumerable]) {
                foreach ($entry in $polNode) {
                    if (-not ($entry -is [psobject])) { continue }
                    $roleName = $null
                    if ($entry.PSObject.Properties['RoleName']) { $roleName = $entry.RoleName }
                    $scope = $null
                    if ($entry.PSObject.Properties['Scope']) { $scope = $entry.Scope }

                    if (-not $roleName) { throw "AzureRole array policy missing required property: RoleName" }
                    if (-not $scope) { throw "AzureRole array policy missing required property: Scope for role '$roleName'" }

                    $policyDefinition = $null
                    $hasTemplate = ($entry.PSObject.Properties['Template'] -and $entry.Template)
                    $hasInline   = ($entry.PSObject.Properties['Policy'] -and $entry.Policy)

                    if ($hasTemplate) {
                        $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'template'; Template = $entry.Template }
                        foreach ($prop in $entry.PSObject.Properties) {
                            if ($prop.Name -notin @('Template','Scope','RoleName','Policy')) {
                                try { $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force } catch { }
                            }
                        }
                    } elseif ($hasInline -or ($entry.PSObject.Properties['PolicySource'] -and ($entry.PolicySource -eq 'inline'))) {
                        $inlinePolicy = $null
                        if ($hasInline) { $inlinePolicy = $entry.Policy } else { $inlinePolicy = ($entry | Select-Object -Property * -ExcludeProperty RoleName,Scope,Template,PolicySource) }
                        $policyDefinition = [PSCustomObject]@{ RoleName = $roleName; Scope = $scope; PolicySource = 'inline'; Policy = $inlinePolicy }
                    } else {
                        throw "AzureRole array policy requires Template or Inline Policy for role '$roleName' at scope '$scope'"
                    }

                    $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'AzureRole'
                    $processedConfig.AzureRolePolicies += $processedPolicy
                }
            }
            else {
                throw "Unsupported type for AzureRoles.Policies: $($polNode.GetType().FullName)"
            }

            Write-Verbose "Processed $($processedConfig.AzureRolePolicies.Count) Azure Role policies from AzureRoles.Policies"
        }

        # Group policy source detection and conflict check
        $groupArrayTopPresent = ($Config.PSObject.Properties['GroupPolicies'] -and $Config.GroupPolicies)
        $groupNestedPresent   = ($Config.PSObject.Properties['Groups'] -and $Config.Groups.PSObject.Properties['Policies'] -and $Config.Groups.Policies)
        $groupLegacyPresent   = ($Config.PSObject.Properties['GroupRoles'] -and $Config.GroupRoles.PSObject.Properties['Policies'] -and $Config.GroupRoles.Policies)

        if ($processGroups -and (($groupArrayTopPresent -and $groupNestedPresent) -or ($groupArrayTopPresent -and $groupLegacyPresent) -or ($groupNestedPresent -and $groupLegacyPresent))) {
            throw "Multiple Group policy formats are present (GroupPolicies, Groups.Policies, GroupRoles.Policies). Only one format is allowed."
        }

        if ($processGroups -and ($groupArrayTopPresent -or $groupNestedPresent -or $groupLegacyPresent)) {
            Write-Verbose "Processing Group policies"
            if (-not $processedConfig.PSObject.Properties['GroupPolicies']) {
                $processedConfig.GroupPolicies = @()
            }

            $polNode = $null
            $sourceLabel = ""
            if ($groupArrayTopPresent) {
                $polNode = $Config.GroupPolicies
                $sourceLabel = "GroupPolicies"
            } elseif ($groupNestedPresent) {
                $polNode = $Config.Groups.Policies
                $sourceLabel = "Groups.Policies"
            } else {
                $polNode = $Config.GroupRoles.Policies
                $sourceLabel = "GroupRoles.Policies"
            }

            # Check if polNode is an array or object format
            if ($polNode -is [System.Collections.IEnumerable] -and $polNode -isnot [string]) {
                Write-Verbose "Processing $sourceLabel as array format"
                foreach ($entry in $polNode) {
                    if (-not ($entry -is [psobject])) { continue }

                    $groupId = if ($entry.PSObject.Properties['GroupId']) { $entry.GroupId } else { $null }
                    $groupName = if ($entry.PSObject.Properties['GroupName']) { $entry.GroupName } else { $null }

                    if (-not $groupId -and -not $groupName) {
                        throw "Group array policy entry missing required property: GroupId or GroupName"
                    }

                    $roleName = if ($entry.PSObject.Properties['RoleName']) { $entry.RoleName } else { $null }
                    if (-not $roleName) {
                        throw "Group array policy entry missing required property: RoleName"
                    }

                    if ($roleName -notin @('Member', 'Owner')) {
                        throw "Group array policy RoleName must be 'Member' or 'Owner', got: $roleName"
                    }

                    $hasTemplate = ($entry.PSObject.Properties['Template'] -and $entry.Template)
                    $hasInlinePolicy = ($entry.PSObject.Properties['Policy'] -and $entry.Policy)
                    $hasPolicySource = ($entry.PSObject.Properties['PolicySource'] -and $entry.PolicySource)

                    if ($hasTemplate) {
                        $policyDefinition = [PSCustomObject]@{
                            PolicySource = 'template'
                            Template = $entry.Template
                            RoleName = $roleName
                        }
                        if ($groupId) {
                            $policyDefinition | Add-Member -NotePropertyName 'GroupId' -NotePropertyValue $groupId -Force
                        }
                        if ($groupName) {
                            $policyDefinition | Add-Member -NotePropertyName 'GroupName' -NotePropertyValue $groupName -Force
                        }
                        foreach ($prop in $entry.PSObject.Properties) {
                            if ($prop.Name -notin @('Template', 'RoleName', 'GroupId', 'GroupName', 'Policy')) {
                                try {
                                    $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
                                } catch {
                                }
                            }
                        }
                    }
                    elseif ($hasInlinePolicy -or ($hasPolicySource -and $entry.PolicySource -eq 'inline')) {
                        $inlinePolicy = if ($hasInlinePolicy) {
                            $entry.Policy
                        } else {
                            $props = @{}
                            foreach ($prop in $entry.PSObject.Properties) {
                                if ($prop.Name -notin @('RoleName', 'GroupId', 'GroupName', 'Template', 'PolicySource')) {
                                    $props[$prop.Name] = $prop.Value
                                }
                            }
                            [PSCustomObject]$props
                        }
                        $policyDefinition = [PSCustomObject]@{
                            PolicySource = 'inline'
                            Policy = $inlinePolicy
                            RoleName = $roleName
                        }
                        if ($groupId) {
                            $policyDefinition | Add-Member -NotePropertyName 'GroupId' -NotePropertyValue $groupId -Force
                        }
                        if ($groupName) {
                            $policyDefinition | Add-Member -NotePropertyName 'GroupName' -NotePropertyValue $groupName -Force
                        }
                    }
                    else {
                        throw "Group array policy requires Template or inline Policy for group $($groupId ?? $groupName) role $roleName"
                    }

                    $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'Group'
                    $processedConfig.GroupPolicies += $processedPolicy
                }
            }
            else {
                Write-Verbose "Processing $sourceLabel as object format"
                foreach ($groupKey in $polNode.PSObject.Properties.Name) {
                    $groupNode = $polNode.$groupKey
                    $isGuid = $false
                    if ($groupKey -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') { $isGuid = $true }
                    foreach ($roleProp in $groupNode.PSObject.Properties) {
                        $roleName = $roleProp.Name
                        if ($roleName -in @('Member','Owner')) {
                            $roleContent = $roleProp.Value
                            if ($roleContent.PSObject.Properties['Template'] -and $roleContent.Template) {
                                if ($isGuid) {
                                    $policyDefinition = [PSCustomObject]@{ PolicySource = 'template'; Template = $roleContent.Template; RoleName = $roleName; GroupId = $groupKey }
                                } else {
                                    $policyDefinition = [PSCustomObject]@{ PolicySource = 'template'; Template = $roleContent.Template; RoleName = $roleName; GroupName = $groupKey }
                                }

                                foreach ($prop in $roleContent.PSObject.Properties) {
                                    if ($prop.Name -ne 'Template') {
                                        $policyDefinition | Add-Member -NotePropertyName $prop.Name -NotePropertyValue $prop.Value -Force
                                    }
                                }
                            } else {
                                if ($isGuid) {
                                    $policyDefinition = [PSCustomObject]@{ PolicySource = 'inline'; Policy = $roleContent; RoleName = $roleName; GroupId = $groupKey }
                                } else {
                                    $policyDefinition = [PSCustomObject]@{ PolicySource = 'inline'; Policy = $roleContent; RoleName = $roleName; GroupName = $groupKey }
                                }
                            }
                            $processedPolicy = Resolve-PolicyConfiguration -PolicyDefinition $policyDefinition -Templates $policyTemplates -PolicyType 'Group'
                            $processedConfig.GroupPolicies += $processedPolicy
                        }
                    }
                }
            }
            Write-Verbose "Processed $($processedConfig.GroupPolicies.Count) Group policies from $sourceLabel"
        }

        # Pass-through other sections
        $existingSections = @('AzureRoles', 'AzureRolesActive', 'EntraIDRoles', 'EntraIDRolesActive', 'GroupRoles', 'GroupRolesActive', 'ProtectedUsers', 'Assignments')
        foreach ($section in $existingSections) {
            if ($Config.PSObject.Properties[$section] -and $Config.$section) { $processedConfig[$section] = $Config.$section }
        }

        Write-Verbose "Initialize-EasyPIMPolicies completed successfully"
        return $processedConfig
    }
    catch {
        Write-Error "Failed to initialize PIM policies: $($_.Exception.Message)"
        throw
    }
}

function Resolve-PolicyConfiguration {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)] [PSCustomObject]$PolicyDefinition,
        [Parameter(Mandatory = $true)] [hashtable]$Templates,
        [Parameter(Mandatory = $true)] [ValidateSet('AzureRole','EntraRole','Group')] [string]$PolicyType
    )

    # Helper function to auto-configure permanent assignment flags based on duration settings
    function Set-AutoPermanentFlags {
        param([PSCustomObject]$Policy, [string]$RoleName)

        if (-not $Policy) { return }

        $autoConfigured = @()

        # Auto-configure AllowPermanentEligibility based on MaximumEligibilityDuration
        if ($Policy.PSObject.Properties['MaximumEligibilityDuration'] -and
            $Policy.MaximumEligibilityDuration -and
            $Policy.MaximumEligibilityDuration -ne '') {

            if (-not $Policy.PSObject.Properties['AllowPermanentEligibility']) {
                $Policy | Add-Member -NotePropertyName 'AllowPermanentEligibility' -NotePropertyValue $false
                $autoConfigured += "AllowPermanentEligibility=false (MaximumEligibilityDuration specified)"
            }
        }

        # Auto-configure AllowPermanentActiveAssignment based on MaximumActiveAssignmentDuration
        if ($Policy.PSObject.Properties['MaximumActiveAssignmentDuration'] -and
            $Policy.MaximumActiveAssignmentDuration -and
            $Policy.MaximumActiveAssignmentDuration -ne '') {

            if (-not $Policy.PSObject.Properties['AllowPermanentActiveAssignment']) {
                $Policy | Add-Member -NotePropertyName 'AllowPermanentActiveAssignment' -NotePropertyValue $false
                $autoConfigured += "AllowPermanentActiveAssignment=false (MaximumActiveAssignmentDuration specified)"
            }
        }

        # Log auto-configuration for transparency
        if ($autoConfigured.Count -gt 0) {
            Write-Verbose "[Auto-Config] ${RoleName}: $($autoConfigured -join ', ')"
        }
    }

    Write-Verbose "Resolving policy configuration for $PolicyType"
    $resolvedPolicy = @{}

    # Copy non-empty properties from PolicyDefinition first
    foreach ($property in $PolicyDefinition.PSObject.Properties) {
        # Only copy non-empty values to allow template defaults to fill in blanks
        if ($null -ne $property.Value -and $property.Value -ne '') {
            $resolvedPolicy[$property.Name] = $property.Value
        }
    }

    switch ($PolicyType) {
        'AzureRole' { if (-not $PolicyDefinition.RoleName) { throw 'AzureRole policy missing required property: RoleName' }
                      if (-not $PolicyDefinition.Scope) { throw 'AzureRole policy missing required property: Scope' } }
        'EntraRole' { if (-not $PolicyDefinition.RoleName) { throw 'EntraRole policy missing required property: RoleName' } }
        'Group'     { if (-not $PolicyDefinition.GroupId -and -not $PolicyDefinition.GroupName) { throw 'Group policy missing required property: GroupId or GroupName' }
                      if (-not $PolicyDefinition.RoleName) { throw 'Group policy missing required property: RoleName' } }
    }

    $policySource = $PolicyDefinition.policySource
    if (-not $policySource) { if ($PolicyDefinition.template) { $policySource = 'template' } else { throw 'Policy definition missing policySource property' } }

    switch ($policySource.ToLower()) {
        'inline'   { if (-not $PolicyDefinition.policy) { throw 'Inline policy source specified but policy property is missing' }
                     $resolvedPolicy.ResolvedPolicy = $PolicyDefinition.policy
                     Set-AutoPermanentFlags -Policy $resolvedPolicy.ResolvedPolicy -RoleName $PolicyDefinition.RoleName }
        'template' { $templateName = $PolicyDefinition.template; if (-not $templateName) { $templateName = $PolicyDefinition.policyTemplate }
                     if (-not $templateName) { throw 'Template policy source specified but neither template nor policyTemplate property is provided' }
                     if (-not $Templates.ContainsKey($templateName)) { throw "Template '$templateName' not found in PolicyTemplates" }
                     $templatePolicy = $Templates[$templateName]

                     # Merge template policy with PolicyDefinition, allowing template to provide defaults
                     $mergedPolicy = @{}
                     # Start with template values as defaults
                     foreach ($templateProp in $templatePolicy.PSObject.Properties) {
                         $mergedPolicy[$templateProp.Name] = $templateProp.Value
                     }
                     # Override with ANY explicitly provided PolicyDefinition values (including false, empty string, etc.)
                     foreach ($property in $PolicyDefinition.PSObject.Properties) {
                         if ($property.Name -notin @('policySource', 'template', 'policyTemplate', 'policy')) {
                             $mergedPolicy[$property.Name] = $property.Value
                         }
                     }
                     $resolvedPolicy.ResolvedPolicy = [PSCustomObject]$mergedPolicy
                     Set-AutoPermanentFlags -Policy $resolvedPolicy.ResolvedPolicy -RoleName $PolicyDefinition.RoleName }
        default    { throw "Unknown policySource: $policySource" }
    }

    return [PSCustomObject]$resolvedPolicy
}