Microsoft.AVS.CDR.psm1

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

class DependencyGraphNode {
    [string]$Name
    [string]$Version
    [System.Collections.ArrayList]$Dependencies
    [System.Collections.ArrayList]$Constraints
    [bool]$NotFound
    [string]$Repository
    [string]$InstalledLocation

    DependencyGraphNode(
        [string]$Name,
        [string]$Version,
        [System.Collections.IList]$Dependencies,
        [bool]$NotFound,
        [string]$Repository,
        [string]$InstalledLocation
    ) {
        $this.Name = $Name
        $this.Version = $Version
        $this.Dependencies = [System.Collections.ArrayList]::new($Dependencies)
        $this.Constraints = [System.Collections.ArrayList]::new()
        $this.NotFound = $NotFound
        $this.Repository = $Repository
        $this.InstalledLocation = $InstalledLocation
    }
}

function Get-ConcreteVersionConstraint {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version
    )

    return @{
        OriginalSpec   = $Version
        Minimum        = $Version
        Maximum        = $Version
        IncludeMinimum = $true
        IncludeMaximum = $true
        IsExact        = $true
        IsHardPin      = $true
        ConcretePin    = $Version
    }
}

function Merge-DependencyConstraint {
    param(
        [Parameter(Mandatory = $true)]
        [DependencyGraphNode]$Node,

        [Parameter(Mandatory = $true)]
        [hashtable]$Constraint
    )

    $duplicate = $Node.Constraints | Where-Object {
        $_.OriginalSpec -eq $Constraint.OriginalSpec -and
        $_.IsHardPin -eq $Constraint.IsHardPin -and
        $_.ConcretePin -eq $Constraint.ConcretePin
    }
    if (-not $duplicate) {
        [void]$Node.Constraints.Add($Constraint)
    }
}

$script:defaultRedirectMap = @{
}

$script:moduleMapCache = @{
}

<#
.SYNOPSIS
    Parses a version specification into its exactness and normalized concrete
    version. Mirrors NuGet range notation: "[1.0, 1.0]" is exact (one concrete
    version) while open-ended or unequal-endpoint ranges are not.

.OUTPUTS
    Hashtable describing exactness and NuGet range bounds.
#>

function Get-NormalizedVersionSpec {
    param(
        [Parameter(Mandatory = $true)]
        [AllowEmptyString()]
        [string]$Version
    )

    $isExact = $true
    $normalized = $Version
    $minimum = $Version
    $maximum = $Version
    $includeMinimum = $true
    $includeMaximum = $true

    # NuGet singleton exact range: "[1.0]"
    if ($Version -match '^\[\s*([^,\[\]]+?)\s*\]$') {
        $normalized = $matches[1]
        $minimum = $normalized
        $maximum = $normalized
    }
    # Version range like "[1.0, 1.0]", "[1.0, )", "(, 2.0]"
    elseif ($Version -match '^(\[|\()([^,]*),\s*([^\]\)]*)(\]|\))$') {
        $openBracket = $matches[1]
        $minVer = $matches[2]
        $maxVer = $matches[3]
        $closeBracket = $matches[4]

        if ($minVer -and $maxVer -and ($minVer -eq $maxVer) -and ($openBracket -eq '[') -and ($closeBracket -eq ']')) {
            $normalized = $minVer  # exact: [1.0, 1.0]
            $minimum = $minVer
            $maximum = $maxVer
        }
        else {
            $isExact = $false
            $normalized = $minVer
            $minimum = $minVer
            $maximum = $maxVer
            $includeMinimum = $openBracket -eq '['
            $includeMaximum = $closeBracket -eq ']'
        }
    }

    return @{
        IsExact        = $isExact
        Normalized     = $normalized
        Minimum        = $minimum
        Maximum        = $maximum
        IncludeMinimum = $includeMinimum
        IncludeMaximum = $includeMaximum
    }
}

function Test-VersionSatisfiesConstraint {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version,

        [Parameter(Mandatory = $true)]
        [hashtable]$Constraint
    )

    if ($Constraint.IsHardPin -and $Constraint.ConcretePin) {
        return Test-EquivalentConcreteVersion -Version1 $Version -Version2 $Constraint.ConcretePin
    }

    if ($Constraint.Minimum) {
        $minimumComparison = Compare-SemVer -Version1 $Version -Version2 $Constraint.Minimum
        if ($minimumComparison -lt 0 -or ($minimumComparison -eq 0 -and (-not $Constraint.IncludeMinimum))) {
            return $false
        }
    }

    if ($Constraint.Maximum) {
        $maximumComparison = Compare-SemVer -Version1 $Version -Version2 $Constraint.Maximum
        if ($maximumComparison -gt 0 -or ($maximumComparison -eq 0 -and (-not $Constraint.IncludeMaximum))) {
            return $false
        }
    }

    return $true
}

function Test-EquivalentConcreteVersion {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version1,

        [Parameter(Mandatory = $true)]
        [string]$Version2
    )

    $parts1 = $Version1 -split '-', 2
    $parts2 = $Version2 -split '-', 2
    $prerelease1 = if ($parts1.Count -gt 1) { $parts1[1] } else { $null }
    $prerelease2 = if ($parts2.Count -gt 1) { $parts2[1] } else { $null }

    if (-not [string]::Equals($prerelease1, $prerelease2, [StringComparison]::OrdinalIgnoreCase)) {
        return $false
    }

    try {
        $version1Parts = @([version]$parts1[0]).Major, @([version]$parts1[0]).Minor,
            @([version]$parts1[0]).Build, @([version]$parts1[0]).Revision
        $version2Parts = @([version]$parts2[0]).Major, @([version]$parts2[0]).Minor,
            @([version]$parts2[0]).Build, @([version]$parts2[0]).Revision

        for ($index = 0; $index -lt 4; $index++) {
            $left = if ($version1Parts[$index] -lt 0) { 0 } else { $version1Parts[$index] }
            $right = if ($version2Parts[$index] -lt 0) { 0 } else { $version2Parts[$index] }
            if ($left -ne $right) {
                return $false
            }
        }
        return $true
    }
    catch {
        return [string]::Equals($parts1[0], $parts2[0], [StringComparison]::OrdinalIgnoreCase)
    }
}

function Test-ConcreteVersion {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version
    )

    $numericVersion = $null
    $semanticVersion = $null
    if ([semver]::TryParse($Version, [ref]$semanticVersion)) {
        return $true
    }

    # SemVer has no revision component; validate that separately from its suffix.
    $suffixIndex = $Version.IndexOfAny([char[]]'-+')
    $coreVersion = if ($suffixIndex -lt 0) { $Version } else { $Version.Substring(0, $suffixIndex) }
    if (-not [version]::TryParse($coreVersion, [ref]$numericVersion)) {
        return $false
    }

    return $suffixIndex -lt 0 -or ($numericVersion.Revision -ge 0 -and
        [semver]::TryParse("$($numericVersion.ToString(3))$($Version.Substring($suffixIndex))", [ref]$semanticVersion))
}

<#
.SYNOPSIS
    Finds and validates a redirect for a dependency version.
    
.PARAMETER RedirectMap
    Map entries: "Name@Version" -> "NewVersion", "Name" -> "Version",
    "Name@Version" -> "*" or "Name" -> "*" (retain version, normalize casing).
    
.OUTPUTS
    Hashtable with ResolvedVersion, ResolvedName, and IsRedirected.
#>

function Find-DependencyRedirect {
    param(
        [Parameter(Mandatory = $true)]
        [string]$DependencyName,
        
        [Parameter(Mandatory = $false)]
        [AllowEmptyString()]
        [string]$DependencyVersion,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$RedirectMap,
        
        [Parameter(Mandatory = $false)]
        [string]$Indent = "",

        [Parameter(Mandatory = $false)]
        [switch]$DependencyVersionIsRange
    )
    
    if ([string]::IsNullOrWhiteSpace($DependencyVersion)) {
        if ($RedirectMap.ContainsKey($DependencyName)) {
            $depVersion = $RedirectMap[$DependencyName].Trim()
            if ($depVersion -eq "*" -or (-not (Test-ConcreteVersion -Version $depVersion))) {
                throw "${Indent}Redirect for unversioned dependency '$DependencyName' must specify a concrete version."
            }
            Write-Verbose "${Indent}Resolved unversioned dependency: $DependencyName -> $depVersion (from redirect map)"
            
            $resolvedName = $DependencyName
            foreach ($entry in $RedirectMap.GetEnumerator()) {
                if ($entry.Key -eq $DependencyName) {
                    $resolvedName = $entry.Key
                    break
                }
            }
            
            return @{
                ResolvedVersion = $depVersion
                ResolvedName = $resolvedName
                IsRedirected = $true
                IsHardPin = $true
                Constraint = Get-ConcreteVersionConstraint -Version $depVersion
            }
        }
        else {
            throw "${Indent}Cannot conservatively resolve version for dependency '$DependencyName'. Please add a redirect mapping for this module."
        }
    }
    
    $depSpec = Get-NormalizedVersionSpec -Version $DependencyVersion
    $isExactVersion = $depSpec.IsExact
    $normalizedDepVersion = $depSpec.Normalized

    # PSResource dependency VersionRange uses a bare version as an inclusive
    # minimum. Top-level RequiredVersion values use the same spelling for an
    # exact requirement, so callers must identify dependency-range metadata.
    if ($DependencyVersionIsRange -and $DependencyVersion -notmatch '^(\[|\()') {
        $isExactVersion = $false
        $depSpec.Minimum = $DependencyVersion
        $depSpec.Maximum = $null
        $depSpec.IncludeMinimum = $true
        $depSpec.IncludeMaximum = $false
    }

    $constraint = @{
        OriginalSpec   = $DependencyVersion
        Minimum        = $depSpec.Minimum
        Maximum        = $depSpec.Maximum
        IncludeMinimum = $depSpec.IncludeMinimum
        IncludeMaximum = $depSpec.IncludeMaximum
        IsExact        = $isExactVersion
        IsHardPin      = $isExactVersion
        ConcretePin    = $normalizedDepVersion
    }
    
    # Check name@version first, then name-only fallback
    $depKeyPattern = "${DependencyName}@${normalizedDepVersion}"
    $versionSpecificEntry = $null
    $nameOnlyEntry = $null
    
    foreach ($entry in $RedirectMap.GetEnumerator()) {
        if ($entry.Key -eq $depKeyPattern) {
            $versionSpecificEntry = $entry
            break
        }
        elseif ($null -eq $nameOnlyEntry -and $entry.Key -eq $DependencyName) {
            $nameOnlyEntry = $entry
        }
    }
    
    $matchedEntry = if ($versionSpecificEntry) { $versionSpecificEntry } else { $nameOnlyEntry }
    $isNameOnlyMatch = $null -eq $versionSpecificEntry -and $null -ne $nameOnlyEntry
    
    if ($matchedEntry) {
        $resolvedVersion = $matchedEntry.Value.Trim()
        
        # "*" retains version but normalizes dependency name casing
        if ($resolvedVersion -eq "*") {
            if ((-not $isExactVersion) -and
                ((-not $constraint.Minimum) -or (-not $constraint.IncludeMinimum))) {
                throw "${Indent}Cannot conservatively resolve dependency '$DependencyName' range '$DependencyVersion' without an inclusive minimum. Please add an explicit redirect mapping for this module."
            }
            $resolvedVersion = $normalizedDepVersion
            
            if ($isNameOnlyMatch) {
                $resolvedName = $matchedEntry.Key
            }
            else {
                $resolvedName = $matchedEntry.Key -replace '@.*$', ''
            }
            
            Write-Verbose "${Indent}Normalizing dependency name: $DependencyName -> $resolvedName (version $normalizedDepVersion retained)"
            
            return @{
                ResolvedVersion = $resolvedVersion
                ResolvedName = $resolvedName
                IsRedirected = $true
                IsHardPin = $isExactVersion
                Constraint = $constraint
            }
        }

        if (-not (Test-ConcreteVersion -Version $resolvedVersion)) {
            throw "${Indent}Redirect target for '$DependencyName' must specify a concrete version; received '$resolvedVersion'."
        }
        
        if ($isExactVersion -and
            (-not (Test-EquivalentConcreteVersion -Version1 $resolvedVersion -Version2 $normalizedDepVersion))) {
            throw "${Indent}Cannot redirect exact version dependency '$DependencyName' from version $normalizedDepVersion to $resolvedVersion. Exact version specifications must redirect to the same version or have no redirect."
        }

        if ((-not $isExactVersion) -and
            (-not (Test-VersionSatisfiesConstraint -Version $resolvedVersion -Constraint $constraint))) {
            throw "${Indent}Redirect target '$resolvedVersion' does not satisfy dependency range '$DependencyVersion' for '$DependencyName'."
        }

        $constraint.IsHardPin = $true
        $constraint.ConcretePin = $resolvedVersion
        
        if ($isNameOnlyMatch) {
            $resolvedName = $matchedEntry.Key
            Write-Verbose "${Indent}Redirecting dependency: $DependencyName $DependencyVersion -> $resolvedVersion (from name-only redirect)"
        }
        else {
            $resolvedName = $matchedEntry.Key -replace '@.*$', ''
            Write-Verbose "${Indent}Redirecting dependency: $DependencyName $DependencyVersion -> $resolvedVersion (from redirect map)"
        }
        
        return @{
            ResolvedVersion = $resolvedVersion
            ResolvedName = $resolvedName
            IsRedirected = $true
            IsHardPin = $true
            Constraint = $constraint
        }
    }
    else {
        if ((-not $isExactVersion) -and
            ((-not $constraint.Minimum) -or (-not $constraint.IncludeMinimum))) {
            throw "${Indent}Cannot conservatively resolve dependency '$DependencyName' range '$DependencyVersion' without an inclusive minimum. Please add an explicit redirect mapping for this module."
        }

        $constraint.ConcretePin = $normalizedDepVersion
        return @{
            ResolvedVersion = $normalizedDepVersion
            ResolvedName = $DependencyName
            IsRedirected = $false
            IsHardPin = $isExactVersion
            Constraint = $constraint
        }
    }
}

function Resolve-ExactDependency {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,

        [Parameter(Mandatory = $true)]
        [string]$RequiredVersion,

        [Parameter(Mandatory = $true)]
        [hashtable]$RedirectMap
    )

    $specification = Get-NormalizedVersionSpec -Version $RequiredVersion
    if ((-not $specification.IsExact) -or
        (-not (Test-ConcreteVersion -Version $specification.Normalized))) {
        throw "RequiredVersion must identify one exact version; '$RequiredVersion' is a range."
    }

    return Find-DependencyRedirect -DependencyName $Name -DependencyVersion $RequiredVersion `
        -RedirectMap $RedirectMap
}

<#
.SYNOPSIS
    Merges redirect maps — OuterMap takes precedence. Loads module-specific map files from maps/ dir.
#>

function Get-MergedRedirectMap {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$OuterMap,
        
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [string]$Version
    )
    
    $redirectMap = $OuterMap
    
    if ([string]::IsNullOrWhiteSpace($Version)) {
        $versionPatterns = @()
    }
    else {
        $baseVersion = $Version
        if ($Version -match '[\[\(]([0-9][0-9a-zA-Z.\-]*)') {
            $baseVersion = $matches[1]
        }
        
        $versionParts = $baseVersion -split '[.\-]'
        $major = $versionParts[0]
        $minor = if ($versionParts.Count -gt 1) { $versionParts[1] } else { "0" }
        $versionPatterns = @(
            $baseVersion,  # Full version (e.g., 1.4.0.15939652 or 1.0.0-preview)
            "$major.$minor",  # Major.Minor (e.g., 1.4)
            "$major"  # Major only (e.g., 1)
        )
    }
    
    $moduleMap = $null
    foreach ($versionPattern in $versionPatterns) {
        $cacheKey = "$Name@$versionPattern"
        
        if ($script:moduleMapCache.ContainsKey($cacheKey)) {
            Write-Verbose "Using cached redirect map for: $cacheKey"
            $moduleMap = $script:moduleMapCache[$cacheKey]
            break
        }
        
        $testPath = Join-Path $PSScriptRoot "maps" "$Name@$versionPattern.json"
        if (Test-Path $testPath) {
            Write-Verbose "Loading module-specific redirect map from: $testPath"
            $moduleMap = Get-Content $testPath -Raw | ConvertFrom-Json -AsHashtable
            $script:moduleMapCache[$cacheKey] = $moduleMap
            break
        }
    }
    
    if ($moduleMap) {
        $mergedMap = @{}
        foreach ($key in $moduleMap.Keys) {
            $mergedMap[$key] = $moduleMap[$key]
        }
        foreach ($key in $redirectMap.Keys) {
            $mergedMap[$key] = $redirectMap[$key]  # Outer map wins
        }
        $redirectMap = $mergedMap
    }
    
    return $redirectMap
}

<#
.SYNOPSIS
    Builds a dependency graph by recursively querying a remote repository via Find-PSResource.
#>

function Build-RemoteDependencyGraph {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,
        
        [Parameter(Mandatory = $true)]
        [string]$ModuleVersion,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$Graph,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$RedirectMap,
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$Prerelease,
        
        [Parameter(Mandatory = $false)]
        [int]$Depth = 0,

        [Parameter(Mandatory = $false)]
        [hashtable]$Constraint
    )
    
    $indent = " " * $Depth
    if (-not $Constraint) {
        $Constraint = Get-ConcreteVersionConstraint -Version $ModuleVersion
    }
    $moduleKey = "${ModuleName}@${ModuleVersion}"
    if ($Graph.ContainsKey($moduleKey)) {
        Merge-DependencyConstraint -Node $Graph[$moduleKey] -Constraint $Constraint
        Write-Verbose "${indent}Already in graph: $moduleKey"
        return $moduleKey
    }

    $findParams = @{
        Name = $ModuleName
        Version = $ModuleVersion
    }
    if ($Repository) {
        $findParams['Repository'] = $Repository
    }
    if ($Credential) {
        $findParams['Credential'] = $Credential
    }
    if ($Prerelease) {
        $findParams['Prerelease'] = $Prerelease
    }
    
    Write-Verbose "Looking for dependencies: $ModuleName version $ModuleVersion"
    $moduleInfo = Find-PSResource @findParams -ErrorAction SilentlyContinue | Select-Object -First 1
    
    $notFound = $false
    if (-not $moduleInfo) {
        Write-Verbose "${indent}Module not found in repository: $ModuleName version $ModuleVersion (will validate after resolution)"
        $notFound = $true
    }

    $actualVersion = if ($moduleInfo) {
        $version = $moduleInfo.Version.ToString()
        $prereleaseProperty = $moduleInfo.PSObject.Properties['Prerelease']
        if ($prereleaseProperty -and $prereleaseProperty.Value) {
            "$version-$($prereleaseProperty.Value)"
        }
        else {
            $version
        }
    }
    else {
        $ModuleVersion
    }

    if ($moduleInfo -and (-not (Test-EquivalentConcreteVersion -Version1 $actualVersion -Version2 $ModuleVersion))) {
        throw "Module '$ModuleName' resolved to version $actualVersion instead of concrete pin $ModuleVersion."
    }
    
    Write-Verbose "${indent}Building graph for: $ModuleName version $ModuleVersion"
    
    $graphNode = [DependencyGraphNode]::new(
        $ModuleName,
        $ModuleVersion,
        [System.Collections.ArrayList]@(),
        $notFound,
        $(if ($moduleInfo) { $moduleInfo.Repository } else { $null }),
        $null
    )
    $Graph[$moduleKey] = $graphNode
    Merge-DependencyConstraint -Node $graphNode -Constraint $Constraint
    
    if ($notFound) {
        return $moduleKey
    }
    
    $deps = $moduleInfo.Dependencies
    if (-not $deps -or $deps.Count -eq 0) {
        Write-Verbose "${indent}No dependencies for $ModuleName"
        return $moduleKey
    }
    
    Write-Verbose "${indent}Found $($deps.Count) dependency(ies)"
    $effectiveRedirectMap = Get-MergedRedirectMap -OuterMap $RedirectMap -Name $ModuleName -Version $ModuleVersion
    
    foreach ($dep in $deps) {
        $depName = $dep.Name
        $depVersion = $dep.VersionRange
        
        $redirectResult = Find-DependencyRedirect -DependencyName $depName -DependencyVersion $depVersion `
            -RedirectMap $effectiveRedirectMap -Indent $indent -DependencyVersionIsRange
        
        $resolvedDepVersion = $redirectResult.ResolvedVersion
        $resolvedDepName = $redirectResult.ResolvedName
        
        $depKey = Build-RemoteDependencyGraph -ModuleName $resolvedDepName -ModuleVersion $resolvedDepVersion `
            -Graph $Graph -RedirectMap $effectiveRedirectMap -Repository $Repository -Credential $Credential `
            -Prerelease:$Prerelease -Depth ($Depth + 1) -Constraint $redirectResult.Constraint
        Write-Verbose "${indent} Dependency: $depKey"
        
        if (-not $graphNode.Dependencies.Contains($depKey)) {
            [void]$graphNode.Dependencies.Add($depKey)
        }
    }

    return $moduleKey
}

<#
.SYNOPSIS
    Compares two semver strings including prerelease labels.
    Returns -1, 0, or 1. Prerelease < release (1.0.0-alpha < 1.0.0).
#>

function Compare-SemVer {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Version1,
        
        [Parameter(Mandatory = $true)]
        [string]$Version2
    )
    
    $v1Parts = $Version1 -split '-', 2
    $v2Parts = $Version2 -split '-', 2
    
    $v1Base = $v1Parts[0]
    $v2Base = $v2Parts[0]
    $v1Prerelease = if ($v1Parts.Count -gt 1) { $v1Parts[1] } else { $null }
    $v2Prerelease = if ($v2Parts.Count -gt 1) { $v2Parts[1] } else { $null }
    
    try {
        $v1Ver = [System.Version]$v1Base
        $v2Ver = [System.Version]$v2Base
        $v1Components = $v1Ver.Major, $v1Ver.Minor, $v1Ver.Build, $v1Ver.Revision
        $v2Components = $v2Ver.Major, $v2Ver.Minor, $v2Ver.Build, $v2Ver.Revision
        $baseCompare = 0
        for ($componentIndex = 0; $componentIndex -lt 4; $componentIndex++) {
            $component1 = if ($v1Components[$componentIndex] -lt 0) { 0 } else { $v1Components[$componentIndex] }
            $component2 = if ($v2Components[$componentIndex] -lt 0) { 0 } else { $v2Components[$componentIndex] }
            if ($component1 -ne $component2) {
                $baseCompare = $component1.CompareTo($component2)
                break
            }
        }
    }
    catch {
        $baseCompare = [string]::Compare($v1Base, $v2Base, [StringComparison]::OrdinalIgnoreCase)
    }
    
    if ($baseCompare -ne 0) {
        return $baseCompare
    }
    
    # No prerelease > any prerelease (1.0.0 > 1.0.0-alpha)
    if ($null -eq $v1Prerelease -and $null -eq $v2Prerelease) {
        return 0
    }
    if ($null -eq $v1Prerelease) {
        return 1  # v1 is release, v2 is prerelease
    }
    if ($null -eq $v2Prerelease) {
        return -1  # v1 is prerelease, v2 is release
    }
    
    $identifiers1 = $v1Prerelease -split '\.'
    $identifiers2 = $v2Prerelease -split '\.'
    $identifierCount = [Math]::Max($identifiers1.Count, $identifiers2.Count)

    for ($index = 0; $index -lt $identifierCount; $index++) {
        if ($index -ge $identifiers1.Count) {
            return -1
        }
        if ($index -ge $identifiers2.Count) {
            return 1
        }

        $identifier1 = $identifiers1[$index]
        $identifier2 = $identifiers2[$index]
        $isNumeric1 = $identifier1 -match '^\d+$'
        $isNumeric2 = $identifier2 -match '^\d+$'

        if ($isNumeric1 -and $isNumeric2) {
            $number1 = $identifier1.TrimStart('0')
            $number2 = $identifier2.TrimStart('0')
            if ($number1.Length -eq 0) {
                $number1 = "0"
            }
            if ($number2.Length -eq 0) {
                $number2 = "0"
            }
            if ($number1.Length -ne $number2.Length) {
                return $number1.Length.CompareTo($number2.Length)
            }
            $numericComparison = [string]::Compare($number1, $number2, [StringComparison]::Ordinal)
            if ($numericComparison -ne 0) {
                return $numericComparison
            }
        }
        elseif ($isNumeric1 -ne $isNumeric2) {
            if ($isNumeric1) {
                return -1
            }
            return 1
        }
        else {
            $identifierComparison = [string]::Compare(
                $identifier1,
                $identifier2,
                [StringComparison]::OrdinalIgnoreCase
            )
            if ($identifierComparison -ne 0) {
                return $identifierComparison
            }
        }
    }

    return 0
}

<#
.SYNOPSIS
    Resolves diamond dependencies — selects the highest found version and updates all graph references.
#>

function Resolve-DiamondDependencies {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$Graph
    )
    
    # Group nodes by module name
    $moduleVersions = @{}
    foreach ($nodeKey in $Graph.Keys) {
        $node = $Graph[$nodeKey]
        $moduleName = $node.Name
        if (-not $moduleVersions.ContainsKey($moduleName)) {
            $moduleVersions[$moduleName] = @()
        }
        $moduleVersions[$moduleName] += @{
            Key = $nodeKey
            VersionString = $node.Version
            Node = $node
            NotFound = $node.NotFound
        }
    }
    
    $redirectedKeys = @{}

    # Resolve conflicts by choosing the lowest found candidate satisfying every
    # incoming constraint. Exact and redirect pins are hard requirements.
    foreach ($moduleName in $moduleVersions.Keys) {
        $versions = $moduleVersions[$moduleName]
        if ($versions.Count -gt 1) {
            $constraints = [System.Collections.ArrayList]@()
            foreach ($version in $versions) {
                if ($version.Node.Constraints.Count -eq 0) {
                    [void]$constraints.Add(@{
                        OriginalSpec   = "[$($version.VersionString), )"
                        Minimum        = $version.VersionString
                        Maximum        = $null
                        IncludeMinimum = $true
                        IncludeMaximum = $false
                        IsExact        = $false
                        IsHardPin      = $false
                        ConcretePin    = $version.VersionString
                    })
                }
                else {
                    foreach ($constraint in $version.Node.Constraints) {
                        [void]$constraints.Add($constraint)
                    }
                }
            }

            $hardPins = [System.Collections.ArrayList]@()
            foreach ($pin in @($constraints | Where-Object IsHardPin | Select-Object -ExpandProperty ConcretePin)) {
                $equivalentPin = $hardPins | Where-Object {
                    Test-EquivalentConcreteVersion -Version1 $_ -Version2 $pin
                }
                if (-not $equivalentPin) {
                    [void]$hardPins.Add($pin)
                }
            }
            if ($hardPins.Count -gt 1) {
                throw "Conflicting hard pins for '$moduleName': $($hardPins -join ', ')."
            }

            $foundCandidates = @($versions | Where-Object { -not $_.NotFound })
            if ($foundCandidates.Count -eq 0) {
                throw "Module not found: $moduleName versions $($versions.VersionString -join ', '). No available version satisfies the dependency."
            }

            $selected = $null
            foreach ($candidate in $foundCandidates) {
                if ($hardPins.Count -eq 1 -and
                    (-not (Test-EquivalentConcreteVersion -Version1 $candidate.VersionString -Version2 $hardPins[0]))) {
                    continue
                }

                $satisfiesAll = $true
                foreach ($constraint in $constraints) {
                    if (-not (Test-VersionSatisfiesConstraint -Version $candidate.VersionString -Constraint $constraint)) {
                        $satisfiesAll = $false
                        break
                    }
                }

                if ($satisfiesAll -and
                    ($null -eq $selected -or
                    (Compare-SemVer -Version1 $candidate.VersionString -Version2 $selected.VersionString) -lt 0)) {
                    $selected = $candidate
                }
            }

            if (-not $selected) {
                throw "No version of '$moduleName' satisfies all dependency constraints: $($constraints.OriginalSpec -join ', ')."
            }

            $conflicts = $versions | Where-Object { $_.Key -ne $selected.Key }
            Write-Warning "Diamond dependency detected for '$moduleName': versions $($versions.VersionString -join ', '). Using lowest compatible: $($selected.VersionString)"

            foreach ($constraint in $constraints) {
                Merge-DependencyConstraint -Node $selected.Node -Constraint $constraint
            }

            foreach ($conflict in $conflicts) {
                $oldKey = $conflict.Key
                $newKey = $selected.Key
                $redirectedKeys[$oldKey] = $newKey
                
                Write-Verbose " Redirecting $oldKey -> $newKey"
                
                # Update all references from old version to new
                foreach ($nodeKey in $Graph.Keys) {
                    $node = $Graph[$nodeKey]
                    for ($i = 0; $i -lt $node.Dependencies.Count; $i++) {
                        if ($node.Dependencies[$i] -eq $oldKey) {
                            $node.Dependencies[$i] = $newKey
                        }
                    }
                }
                
                $Graph.Remove($oldKey)
            }
        }
    }
    
    # Validate all remaining nodes were found
    foreach ($nodeKey in $Graph.Keys) {
        $node = $Graph[$nodeKey]
        if ($node.NotFound) {
            throw "Module not found: $($node.Name) version $($node.Version). No alternative version available to satisfy the dependency."
        }
    }

    return $redirectedKeys
}

function Resolve-GraphRootKey {
    param(
        [Parameter(Mandatory = $true)]
        [string[]]$RootKeys,

        [Parameter(Mandatory = $true)]
        [hashtable]$RedirectedKeys
    )

    return @($RootKeys | ForEach-Object {
        $resolvedKey = $_
        while ($RedirectedKeys.ContainsKey($resolvedKey)) {
            $resolvedKey = $RedirectedKeys[$resolvedKey]
        }
        $resolvedKey
    })
}

<#
.SYNOPSIS
    Returns module keys in topological order (dependencies first). Warns on cycles.
    Traversal is anchored on -RootKeys (in given order) then a sorted pass over the
    rest, so the order is deterministic and follows each node's reported dependency
    order (matching native Import-Module's RequiredModules array-order walk).
#>

function Get-TopologicalOrder {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$Graph,

        [Parameter(Mandatory = $false)]
        [string[]]$RootKeys
    )
    
    $visited = @{}
    $visiting = @{}  # For cycle detection
    $order = [System.Collections.ArrayList]@()
    
    function Visit {
        param([string]$NodeKey)
        
        if ($visited.ContainsKey($NodeKey)) {
            return
        }
        
        if ($visiting.ContainsKey($NodeKey)) {
            Write-Warning "Circular dependency detected involving: $NodeKey"
            return
        }
        
        $visiting[$NodeKey] = $true
        
        if ($Graph.ContainsKey($NodeKey)) {
            $node = $Graph[$NodeKey]
            foreach ($depKey in $node.Dependencies) {
                Visit -NodeKey $depKey
            }
        }
        
        $visiting.Remove($NodeKey)
        $visited[$NodeKey] = $true
        [void]$order.Add($NodeKey)
    }

    # Anchor on the declared roots (in order), then a deterministic sorted pass over the rest.
    if ($RootKeys) {
        foreach ($rootKey in $RootKeys) {
            if ($Graph.ContainsKey($rootKey)) {
                Visit -NodeKey $rootKey
            }
        }
    }

    foreach ($nodeKey in ($Graph.Keys | Sort-Object)) {
        Visit -NodeKey $nodeKey
    }
    
    return $order.ToArray()
}

<#
.SYNOPSIS
    Builds a dependency graph for installed modules via Get-PSResource.
#>

function Build-InstalledDependencyGraph {
    param(
        [Parameter(Mandatory = $true)]
        [string]$ModuleName,
        
        [Parameter(Mandatory = $true)]
        [string]$ModuleVersion,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$Graph,
        
        [Parameter(Mandatory = $true)]
        [hashtable]$RedirectMap,
        
        [Parameter(Mandatory = $false)]
        [int]$Depth = 0,

        [Parameter(Mandatory = $false)]
        [hashtable]$Constraint
    )
    
    $indent = " " * $Depth
    if (-not $Constraint) {
        $Constraint = Get-ConcreteVersionConstraint -Version $ModuleVersion
    }
    $moduleKey = "${ModuleName}@${ModuleVersion}"
    if ($Graph.ContainsKey($moduleKey)) {
        Merge-DependencyConstraint -Node $Graph[$moduleKey] -Constraint $Constraint
        Write-Verbose "${indent}Already in graph: $moduleKey"
        return $moduleKey
    }

    # Find the installed module
    $installedModule = Get-PSResource -Name $ModuleName -Version $ModuleVersion -ErrorAction SilentlyContinue | Select-Object -First 1
    
    $notFound = $false
    if (-not $installedModule) {
        Write-Verbose "${indent}Module not installed: $ModuleName version $ModuleVersion (will validate after resolution)"
        $notFound = $true
    }
    
    $actualVersion = if ($installedModule) {
        $version = $installedModule.Version.ToString()
        $prereleaseProperty = $installedModule.PSObject.Properties['Prerelease']
        if ($prereleaseProperty -and $prereleaseProperty.Value) {
            "$version-$($prereleaseProperty.Value)"
        }
        else {
            $version
        }
    }
    else {
        $ModuleVersion
    }

    if ($installedModule -and (-not (Test-EquivalentConcreteVersion -Version1 $actualVersion -Version2 $ModuleVersion))) {
        throw "Installed module '$ModuleName' resolved to version $actualVersion instead of concrete pin $ModuleVersion."
    }
    Write-Verbose "${indent}Building graph for: $ModuleName version $ModuleVersion"
    
    # InstalledLocation is the base modules folder; append ModuleName/Version
    $moduleVersionPath = if ($installedModule) {
        Join-Path $installedModule.InstalledLocation $installedModule.Name $installedModule.Version.ToString()
    }
    else {
        $null
    }
    
    $graphNode = [DependencyGraphNode]::new(
        $ModuleName,
        $ModuleVersion,
        [System.Collections.ArrayList]@(),
        $notFound,
        $null,
        $moduleVersionPath
    )
    $Graph[$moduleKey] = $graphNode
    Merge-DependencyConstraint -Node $graphNode -Constraint $Constraint
    
    if ($notFound) {
        return $moduleKey
    }
    
    $deps = $installedModule.Dependencies
    if (-not $deps -or $deps.Count -eq 0) {
        Write-Verbose "${indent}No dependencies for $ModuleName"
        return $moduleKey
    }
    
    Write-Verbose "${indent}Found $($deps.Count) dependency(ies)"
    $effectiveRedirectMap = Get-MergedRedirectMap -OuterMap $RedirectMap -Name $ModuleName -Version $ModuleVersion
    
    foreach ($dep in $deps) {
        $depName = $dep.Name
        $depVersion = $dep.VersionRange
        
        $redirectResult = Find-DependencyRedirect -DependencyName $depName -DependencyVersion $depVersion `
            -RedirectMap $effectiveRedirectMap -Indent $indent -DependencyVersionIsRange
        
        $resolvedDepVersion = $redirectResult.ResolvedVersion
        $resolvedDepName = $redirectResult.ResolvedName
        
        $depKey = Build-InstalledDependencyGraph -ModuleName $resolvedDepName -ModuleVersion $resolvedDepVersion `
            -Graph $Graph -RedirectMap $effectiveRedirectMap -Depth ($Depth + 1) -Constraint $redirectResult.Constraint
        Write-Verbose "${indent} Dependency: $depKey"
        
        if (-not $graphNode.Dependencies.Contains($depKey)) {
            [void]$graphNode.Dependencies.Add($depKey)
        }
    }

    return $moduleKey
}

function Install-PSResourcePinned {
    <#
    .SYNOPSIS
        Installs a module with pinned dependency versions.
        Works around PowerCLI not following semver (13.4 breaks backward-compat).
        
    .EXAMPLE
        Install-PSResourcePinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [string]$RequiredVersion,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentUser',
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$Prerelease,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    
    # Load redirect map
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Using default redirect map"
        $redirectMap = $script:defaultRedirectMap
    }
    
    $redirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $Name -Version $RequiredVersion
    
    Write-Verbose "Building dependency graph for $Name version $RequiredVersion"
    $dependencyGraph = @{}
    
    $root = Resolve-ExactDependency -Name $Name -RequiredVersion $RequiredVersion -RedirectMap $redirectMap
    $rootKey = Build-RemoteDependencyGraph -ModuleName $root.ResolvedName -ModuleVersion $root.ResolvedVersion `
        -Graph $dependencyGraph -RedirectMap $redirectMap -Repository $Repository -Credential $Credential `
        -Prerelease:$Prerelease -Constraint $root.Constraint
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $rootKey = @(Resolve-GraphRootKey -RootKeys @($rootKey) -RedirectedKeys $redirectedKeys)[0]
    
    Write-Verbose "Computing topological order"
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys @($rootKey))
    
    Write-Verbose "Install order ($($topologicalOrder.Count) modules):"
    for ($i = 0; $i -lt $topologicalOrder.Count; $i++) {
        Write-Verbose " $($i + 1). $($topologicalOrder[$i])"
    }
    
    # Install modules in topological order
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        $modName = $node.Name
        $modVersion = $node.Version
        
        $installed = $null
        if (-not $Force) {
            $installed = Get-PSResource -Name $modName -ErrorAction SilentlyContinue | 
                Where-Object {
                    if (-not $_) { return $false }
                    $installedVersion = $_.Version.ToString()
                    if ($_.Prerelease) {
                        $installedVersion = "$installedVersion-$($_.Prerelease)"
                    }
                    $installedVersion -eq $modVersion
                }
        }
        
        if (-not $installed) {
            Write-Verbose "Installing: $modName version $modVersion"
            $installParams = @{
                Name = $modName
                Version = $modVersion
                Scope = $Scope
                Prerelease = $Prerelease
                TrustRepository = $true
                SkipDependencyCheck = $true
            }
            if ($Repository) {
                $installParams['Repository'] = $Repository
            }
            if ($Credential) {
                $installParams['Credential'] = $Credential
            }
            if ($Force) {
                $installParams['Reinstall'] = $true
            }
            
            Install-PSResource @installParams
        }
        else {
            Write-Verbose "Already installed: $modName version $modVersion"
        }
    }
    
    $mainNode = $dependencyGraph[$rootKey]
    Write-Host "Successfully installed $Name version $($mainNode.Version)"
}

function Save-PSResourcePinned {
    <#
    .SYNOPSIS
        Downloads a module and its dependencies with pinned versions.
        Saves as expanded module folders by default; pass -AsNupkg to save as NuGet packages.
        
    .EXAMPLE
        Save-PSResourcePinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0" -Path "./packages"

    .EXAMPLE
        Save-PSResourcePinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0" -Path "./packages" -AsNupkg
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [string]$RequiredVersion,
        
        [Parameter(Mandatory = $true)]
        [string]$Path,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$AsNupkg,
        
        [Parameter(Mandatory = $false)]
        [switch]$Prerelease
    )
    
    # Validate and create destination path
    if (-not (Test-Path $Path)) {
        Write-Verbose "Creating destination directory: $Path"
        New-Item -ItemType Directory -Path $Path -Force | Out-Null
    }
    
    $resolvedPath = Resolve-Path $Path
    Write-Verbose "Saving packages to: $resolvedPath"
    
    # Load redirect map
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Using default redirect map"
        $redirectMap = $script:defaultRedirectMap
    }
    
    $redirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $Name -Version $RequiredVersion
    
    Write-Verbose "Building dependency graph for $Name version $RequiredVersion"
    $dependencyGraph = @{}
    
    $root = Resolve-ExactDependency -Name $Name -RequiredVersion $RequiredVersion -RedirectMap $redirectMap
    $rootKey = Build-RemoteDependencyGraph -ModuleName $root.ResolvedName -ModuleVersion $root.ResolvedVersion `
        -Graph $dependencyGraph -RedirectMap $redirectMap -Repository $Repository -Credential $Credential `
        -Prerelease:$Prerelease -Constraint $root.Constraint
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $rootKey = @(Resolve-GraphRootKey -RootKeys @($rootKey) -RedirectedKeys $redirectedKeys)[0]
    
    Write-Verbose "Computing topological order"
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys @($rootKey))
    
    Write-Verbose "Save order ($($topologicalOrder.Count) modules):"
    for ($i = 0; $i -lt $topologicalOrder.Count; $i++) {
        Write-Verbose " $($i + 1). $($topologicalOrder[$i])"
    }
    
    # Save modules in topological order
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        $modName = $node.Name
        $modVersion = $node.Version
        
        # Existing-output detection depends on output format:
        # -AsNupkg -> $Path/$modName.$modVersion.nupkg
        # (default) -> $Path/$modName/$modVersion (expanded module folder)
        if ($AsNupkg) {
            $expectedPath = Join-Path $resolvedPath.Path "$modName.$modVersion.nupkg"
        }
        else {
            $expectedPath = Join-Path $resolvedPath.Path $modName $modVersion
        }
        
        if (-not (Test-Path $expectedPath)) {
            Write-Verbose "Saving: $modName version $modVersion"
            $saveParams = @{
                Name = $modName
                Version = $modVersion
                Path = $resolvedPath.Path
                Prerelease = $Prerelease
                TrustRepository = $true
                SkipDependencyCheck = $true
            }
            if ($AsNupkg) {
                $saveParams['AsNupkg'] = $true
            }
            if ($Repository) {
                $saveParams['Repository'] = $Repository
            }
            if ($Credential) {
                $saveParams['Credential'] = $Credential
            }
            
            Save-PSResource @saveParams
        }
        else {
            Write-Verbose "Already saved: $modName version $modVersion"
        }
    }
    
    $mainNode = $dependencyGraph[$rootKey]
    Write-Host "Successfully saved $Name version $($mainNode.Version) and dependencies to $resolvedPath"
}

<#
.SYNOPSIS
    Extracts RequiredModules and ModuleList from a manifest, deduped (RequiredModules wins).
    NuGet feeds package both as dependencies; reading both keeps the local graph consistent
    with remote graphs and prevents "assembly already loaded" errors during pre-loading.

.OUTPUTS
    Array of @{ Name; Version; IsVersionRange } hashtables.
#>

function Get-ManifestModuleDependencies {
    param(
        [Parameter(Mandatory = $true)]
        [hashtable]$Manifest
    )

    $hasRequired = $Manifest.ContainsKey('RequiredModules') -and $Manifest.RequiredModules -and $Manifest.RequiredModules.Count -gt 0
    $hasModuleList = $Manifest.ContainsKey('ModuleList') -and $Manifest.ModuleList -and $Manifest.ModuleList.Count -gt 0

    if (-not $hasRequired -and -not $hasModuleList) {
        return @()
    }

    # Parse a single manifest entry into @{ Name; Version }
    function ParseEntry {
        param([object]$Entry, [string]$Source)

        if ($Entry -is [string]) {
            throw "$Source entry '$Entry' has no version. All entries must specify a version (RequiredVersion or ModuleVersion)."
        }
        elseif ($Entry -is [hashtable]) {
            $name = if ($Entry.ContainsKey('ModuleName')) { $Entry.ModuleName } else { $null }
            if (-not $name) {
                throw "$Source entry has no module name: $($Entry | ConvertTo-Json -Compress)"
            }
            $version = $null
            $isVersionRange = $false
            if ($Entry.ContainsKey('RequiredVersion')) {
                $version = $Entry.RequiredVersion.ToString()
            }
            elseif ($Entry.ContainsKey('ModuleVersion')) {
                $version = "[$($Entry.ModuleVersion), )"
                $isVersionRange = $true
            }
            if (-not $version) {
                throw "$Source entry '$name' has no version. All entries must specify a version (RequiredVersion or ModuleVersion)."
            }
            return @{ Name = $name; Version = $version; IsVersionRange = $isVersionRange }
        }
        else {
            throw "Unrecognized $Source format in manifest: $Entry. Expected string or hashtable."
        }
    }

    # RequiredModules take precedence
    $seen = @{}
    $results = [System.Collections.ArrayList]@()

    if ($hasRequired) {
        Write-Verbose "Found $($Manifest.RequiredModules.Count) module(s) in RequiredModules"
        foreach ($entry in $Manifest.RequiredModules) {
            $parsed = ParseEntry -Entry $entry -Source 'RequiredModules'
            $key = $parsed.Name.ToLowerInvariant()
            if (-not $seen.ContainsKey($key)) {
                $seen[$key] = $true
                [void]$results.Add($parsed)
            }
        }
    }

    if ($hasModuleList) {
        Write-Verbose "Found $($Manifest.ModuleList.Count) module(s) in ModuleList"
        foreach ($entry in $Manifest.ModuleList) {
            $parsed = ParseEntry -Entry $entry -Source 'ModuleList'
            $key = $parsed.Name.ToLowerInvariant()
            if (-not $seen.ContainsKey($key)) {
                $seen[$key] = $true
                [void]$results.Add($parsed)
            }
            else {
                Write-Verbose "Skipping ModuleList entry '$($parsed.Name)' — already declared in RequiredModules"
            }
        }
    }

    return $results.ToArray()
}

function Find-PSResourceDependencies {
    <#
    .SYNOPSIS
        Resolves all dependencies (RequiredModules + ModuleList) from a .psd1 manifest via remote repository.
        
    .EXAMPLE
        Find-PSResourceDependencies -ManifestPath "./MyModule/MyModule.psd1"
        
    .OUTPUTS
        Array of PSCustomObject with Name, Version, Repository, and IsRedirected properties.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ManifestPath,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$Prerelease
    )
    
    if (-not (Test-Path $ManifestPath)) {
        throw "Manifest file not found: $ManifestPath"
    }
    
    $resolvedPath = Resolve-Path $ManifestPath
    if (-not $resolvedPath.Path.EndsWith('.psd1')) {
        throw "File must be a PowerShell module manifest (.psd1): $ManifestPath"
    }
    
    Write-Verbose "Reading manifest from: $resolvedPath"
    
    $manifest = Import-PowerShellDataFile -Path $resolvedPath
    
    $moduleDependencies = @(Get-ManifestModuleDependencies -Manifest $manifest)
    if ($moduleDependencies.Count -eq 0) {
        Write-Verbose "No module dependencies found in manifest (RequiredModules or ModuleList)"
        return @()
    }
    
    $manifestModuleName = [System.IO.Path]::GetFileNameWithoutExtension($resolvedPath.Path)
    $manifestModuleVersion = if ($manifest.ModuleVersion) { $manifest.ModuleVersion.ToString() } else { "" }
    
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Looking for redirect map based on manifest: $manifestModuleName version $manifestModuleVersion"
        $redirectMap = Get-MergedRedirectMap -OuterMap $script:defaultRedirectMap -Name $manifestModuleName -Version $manifestModuleVersion
    }
    
    Write-Verbose "Found $($moduleDependencies.Count) module dependency(ies) in manifest"
    
    $dependencyGraph = @{}
    $rootKeys = [System.Collections.ArrayList]@()
    
    foreach ($depEntry in $moduleDependencies) {
        $moduleName = $depEntry.Name
        $moduleVersion = $depEntry.Version
        
        $mergedRedirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $moduleName -Version ($moduleVersion ?? "")
        $redirectResult = Find-DependencyRedirect -DependencyName $moduleName -DependencyVersion $moduleVersion `
            -RedirectMap $mergedRedirectMap -Indent "" -DependencyVersionIsRange:$depEntry.IsVersionRange
        
        $rootKey = Build-RemoteDependencyGraph -ModuleName $redirectResult.ResolvedName -ModuleVersion $redirectResult.ResolvedVersion `
            -Graph $dependencyGraph -RedirectMap $mergedRedirectMap -Repository $Repository -Credential $Credential `
            -Prerelease:$Prerelease -Constraint $redirectResult.Constraint
        [void]$rootKeys.Add($rootKey)
    }
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $resolvedRootKeys = @(Resolve-GraphRootKey -RootKeys $rootKeys.ToArray() -RedirectedKeys $redirectedKeys)
    $rootKeys = [System.Collections.ArrayList]@($resolvedRootKeys)
    
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys $rootKeys.ToArray())
    
    $resolvedDependencies = [System.Collections.ArrayList]@()
    
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        
        [void]$resolvedDependencies.Add([PSCustomObject]@{
            Name = $node.Name
            Version = $node.Version
            Repository = $node.Repository
            IsRedirected = $false  # Graph already has redirected versions applied
        })
    }
    
    Write-Verbose "Resolved $($resolvedDependencies.Count) module(s) (including transitive dependencies)"
    
    return $resolvedDependencies.ToArray()
}

function Install-PSResourceDependencies {
    <#
    .SYNOPSIS
        Installs all manifest dependencies using Find-PSResourceDependencies.
        
    .EXAMPLE
        Install-PSResourceDependencies -ManifestPath "./MyModule/MyModule.psd1"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ManifestPath,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet('CurrentUser', 'AllUsers')]
        [string]$Scope = 'CurrentUser',
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force
    )
    
    $findParams = @{
        ManifestPath = $ManifestPath
    }
    if ($RedirectMapPath) {
        $findParams['RedirectMapPath'] = $RedirectMapPath
    }
    if ($Repository) {
        $findParams['Repository'] = $Repository
    }
    if ($Credential) {
        $findParams['Credential'] = $Credential
    }
    
    $resolvedDependencies = Find-PSResourceDependencies @findParams
    
    if (-not $resolvedDependencies -or $resolvedDependencies.Count -eq 0) {
        Write-Verbose "No dependencies to install"
        return
    }
    
    Write-Verbose "Installing $($resolvedDependencies.Count) resolved dependency(ies)"
    
    foreach ($dependency in $resolvedDependencies) {
        $installed = $null
        if (-not $Force) {
            $installed = Get-PSResource -Name $dependency.Name -ErrorAction SilentlyContinue | 
                Where-Object { $_.Version.ToString() -eq $dependency.Version }
        }
        
        if (-not $installed) {
            Write-Host "Installing dependency: $($dependency.Name) version $($dependency.Version)"
            
            $installParams = @{
                Name = $dependency.Name
                Version = $dependency.Version
                Scope = $Scope
                TrustRepository = $true
                SkipDependencyCheck = $true
            }
            if ($Repository) {
                $installParams['Repository'] = $Repository
            }
            if ($Credential) {
                $installParams['Credential'] = $Credential
            }
            if ($Force) {
                $installParams['Reinstall'] = $true
            }
            
            Install-PSResource @installParams
        }
        else {
            Write-Verbose "Already installed: $($dependency.Name) version $($dependency.Version)"
        }
    }
    
    Write-Host "Successfully installed all dependencies from manifest"
}

function Get-InstalledModuleManifestPath {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Node
    )

    $moduleName = Split-Path -Path (Split-Path -Path $Node.InstalledLocation -Parent) -Leaf
    return Join-Path -Path $Node.InstalledLocation -ChildPath "$moduleName.psd1"
}

function Import-PSResourceDependencies {
    <#
    .SYNOPSIS
        Imports all manifest dependencies (RequiredModules + ModuleList) in topological order
        with pinned versions. Prevents "assembly already loaded" errors from incomplete graphs.
        
    .EXAMPLE
        Import-PSResourceDependencies -ManifestPath "./MyModule/MyModule.psd1"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$ManifestPath,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force,
        
        [Parameter(Mandatory = $false)]
        [switch]$PassThru
    )
    
    # Validate manifest path
    if (-not (Test-Path $ManifestPath)) {
        throw "Manifest file not found: $ManifestPath"
    }
    
    $resolvedPath = Resolve-Path $ManifestPath
    if (-not $resolvedPath.Path.EndsWith('.psd1')) {
        throw "File must be a PowerShell module manifest (.psd1): $ManifestPath"
    }
    
    Write-Verbose "Reading manifest from: $resolvedPath"
    
    # Parse the manifest
    $manifest = Import-PowerShellDataFile -Path $resolvedPath
    
    # Extract module dependencies from both RequiredModules and ModuleList
    $moduleDependencies = @(Get-ManifestModuleDependencies -Manifest $manifest)
    if ($moduleDependencies.Count -eq 0) {
        Write-Verbose "No module dependencies found in manifest (RequiredModules or ModuleList)"
        return
    }
    
    $manifestModuleName = [System.IO.Path]::GetFileNameWithoutExtension($resolvedPath.Path)
    $manifestModuleVersion = if ($manifest.ModuleVersion) { $manifest.ModuleVersion.ToString() } else { "" }
    
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Looking for redirect map based on manifest: $manifestModuleName version $manifestModuleVersion"
        $redirectMap = Get-MergedRedirectMap -OuterMap $script:defaultRedirectMap -Name $manifestModuleName -Version $manifestModuleVersion
    }
    
    Write-Verbose "Found $($moduleDependencies.Count) module dependency(ies) in manifest"
    
    $dependencyGraph = @{}
    $rootKeys = [System.Collections.ArrayList]@()
    
    foreach ($depEntry in $moduleDependencies) {
        $moduleName = $depEntry.Name
        $moduleVersion = $depEntry.Version
        
        $mergedRedirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $moduleName -Version ($moduleVersion ?? "")
        $redirectResult = Find-DependencyRedirect -DependencyName $moduleName -DependencyVersion $moduleVersion `
            -RedirectMap $mergedRedirectMap -Indent "" -DependencyVersionIsRange:$depEntry.IsVersionRange
        
        $rootKey = Build-InstalledDependencyGraph -ModuleName $redirectResult.ResolvedName -ModuleVersion $redirectResult.ResolvedVersion `
            -Graph $dependencyGraph -RedirectMap $mergedRedirectMap -Constraint $redirectResult.Constraint
        [void]$rootKeys.Add($rootKey)
    }
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $resolvedRootKeys = @(Resolve-GraphRootKey -RootKeys $rootKeys.ToArray() -RedirectedKeys $redirectedKeys)
    $rootKeys = [System.Collections.ArrayList]@($resolvedRootKeys)
    
    # Compute topological order
    Write-Verbose "Computing topological order"
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys $rootKeys.ToArray())
    
    Write-Verbose "Import order ($($topologicalOrder.Count) modules):"
    for ($i = 0; $i -lt $topologicalOrder.Count; $i++) {
        Write-Verbose " $($i + 1). $($topologicalOrder[$i])"
    }
    
    Write-Verbose "Pre-loading all modules in topological order"
    
    $importedModules = @{}
    
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        $modName = $node.Name
        $modVersion = $node.Version
        $isPrerelease = $modVersion -match '-'
        
        $loadedModule = Get-Module -Name $modName | Where-Object {
            if ($isPrerelease) {
                $_.ModuleBase -eq $node.InstalledLocation
            }
            else {
                $_.Version.ToString() -eq $modVersion
            }
        }
        
        if ($loadedModule -and -not $Force) {
            Write-Verbose "Already loaded: $modName version $modVersion"
            $importedModules[$moduleKey] = $loadedModule
            continue
        }
        
        # -Global ensures modules persist after this function returns
        $importParams = @{
            Name = if ($isPrerelease) { Get-InstalledModuleManifestPath -Node $node } else { $modName }
            ErrorAction = 'Stop'
            DisableNameChecking = $true
            Global = $true
        }
        if (-not $isPrerelease) {
            $importParams['RequiredVersion'] = $modVersion
        }
        
        if ($Force) {
            $importParams['Force'] = $true
        }
        
        try {
            Write-Verbose "Importing: $modName version $modVersion"
            $imported = Import-Module @importParams -PassThru
            $importedModules[$moduleKey] = $imported
        }
        catch {
            throw "Failed to import $modName version $($modVersion): $_"
        }
    }
    
    Write-Verbose "Successfully imported $($importedModules.Count) module(s) from manifest"
    
    if ($PassThru) {
        return $importedModules.Values
    }
}

function Import-ModulePinned {
    <#
    .SYNOPSIS
        Imports a module after pre-loading ALL transitive dependencies at exact versions.
        Prevents PowerShell from loading wrong versions via minimum-version semantics.
        
    .EXAMPLE
        Import-ModulePinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0"
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$RequiredVersion,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [switch]$Force,
        
        [Parameter(Mandatory = $false)]
        [string]$Prefix,
        
        [Parameter(Mandatory = $false)]
        [switch]$PassThru
    )
    
    # Resolve the full dependency list (pinned versions, topological order)
    $findParams = @{
        Name = $Name
        RequiredVersion = $RequiredVersion
    }
    if ($RedirectMapPath) {
        $findParams['RedirectMapPath'] = $RedirectMapPath
    }
    
    $resolvedModules = @(Get-PSResourcesPinned @findParams)
    
    Write-Verbose "Import order ($($resolvedModules.Count) modules):"
    for ($i = 0; $i -lt $resolvedModules.Count; $i++) {
        $node = $resolvedModules[$i]
        Write-Verbose " $($i + 1). $($node.Name)@$($node.Version)"
        Write-Verbose " Location: $($node.InstalledLocation)"
        if ($node.Dependencies.Count -gt 0) {
            Write-Verbose " Dependencies:"
            foreach ($dep in $node.Dependencies) {
                Write-Verbose " -> $dep"
            }
        }
        else {
            Write-Verbose " Dependencies: (none)"
        }
    }
    
    Write-Verbose "Pre-loading all modules in topological order"
    
    $importedModules = @{}
    
    foreach ($node in $resolvedModules) {
        $modName = $node.Name
        $modVersion = $node.Version
        $moduleKey = "${modName}@${modVersion}"
        $isPrerelease = $modVersion -match '-'
        
        $loadedModule = Get-Module -Name $modName | Where-Object {
            if ($isPrerelease) {
                $_.ModuleBase -eq $node.InstalledLocation
            }
            else {
                $_.Version.ToString() -eq $modVersion
            }
        }
        
        if ($loadedModule -and -not $Force) {
            Write-Verbose "Already loaded: $modName version $modVersion"
            $importedModules[$moduleKey] = $loadedModule
            continue
        }
        
        # -Global ensures modules persist after this function returns
        $importParams = @{
            Name = if ($isPrerelease) { Get-InstalledModuleManifestPath -Node $node } else { $modName }
            ErrorAction = 'Stop'
            DisableNameChecking = $true
            Global = $true
        }
        if (-not $isPrerelease) {
            $importParams['RequiredVersion'] = $modVersion
        }
        
        if ($Force) {
            $importParams['Force'] = $true
        }
        
        try {
            Write-Verbose "Importing: $modName version $modVersion"
            $imported = Import-Module @importParams -PassThru
            $importedModules[$moduleKey] = $imported
        }
        catch {
            throw "Failed to import $modName version $($modVersion): $_"
        }
    }
    
    Write-Verbose "Returning main module"
    
    $mainNode = $resolvedModules |
        Where-Object { $_.Name -eq $Name } |
        Select-Object -Last 1
    $mainModuleKey = "${Name}@$($mainNode.Version)"
    $mainModule = $importedModules[$mainModuleKey]
    
    if (-not $mainModule) {
        $mainModule = Get-Module -Name $Name | Where-Object {
            $_.Version.ToString() -eq $mainNode.Version
        }
    }
    
    Write-Verbose "Successfully imported $Name version $($mainNode.Version) (and $($importedModules.Count - 1) dependencies)"
    
    if ($PassThru) {
        return $mainModule
    }
}

function Get-PSResourcesPinned {
    <#
    .SYNOPSIS
        Resolves an installed module and all of its installed dependencies with pinned versions.
        Returns results in topological order (dependencies first).
        Installed-module counterpart to Find-PSResourcesPinned.
        
    .EXAMPLE
        Get-PSResourcesPinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0"
        
    .OUTPUTS
        Array of objects with Name, Version, InstalledLocation, and Dependencies properties.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [string]$RequiredVersion,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath
    )
    
    # Load redirect map
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Using default redirect map"
        $redirectMap = $script:defaultRedirectMap
    }
    
    $redirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $Name -Version $RequiredVersion
    
    Write-Verbose "Building dependency graph for $Name version $RequiredVersion"
    $dependencyGraph = @{}
    
    $root = Resolve-ExactDependency -Name $Name -RequiredVersion $RequiredVersion -RedirectMap $redirectMap
    $rootKey = Build-InstalledDependencyGraph -ModuleName $root.ResolvedName -ModuleVersion $root.ResolvedVersion `
        -Graph $dependencyGraph -RedirectMap $redirectMap -Constraint $root.Constraint
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $rootKey = @(Resolve-GraphRootKey -RootKeys @($rootKey) -RedirectedKeys $redirectedKeys)[0]
    
    Write-Verbose "Computing topological order"
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys @($rootKey))
    
    $resolvedModules = [System.Collections.ArrayList]@()
    
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        
        [void]$resolvedModules.Add([PSCustomObject]@{
            Name = $node.Name
            Version = $node.Version
            InstalledLocation = $node.InstalledLocation
            Dependencies = $node.Dependencies
        })
    }
    
    Write-Verbose "Found $($resolvedModules.Count) module(s) (including main module and all dependencies)"
    
    return $resolvedModules.ToArray()
}

function Find-PSResourcesPinned {
    <#
    .SYNOPSIS
        Resolves a module and all dependencies with pinned versions. Returns results in topological order.
        
    .EXAMPLE
        Find-PSResourcesPinned -Name "VMware.PowerCLI" -RequiredVersion "13.3.0"
        
    .OUTPUTS
        Array of objects with Name, Version, Repository, and Dependencies properties.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$Name,
        
        [Parameter(Mandatory = $true)]
        [string]$RequiredVersion,
        
        [Parameter(Mandatory = $false)]
        [string]$RedirectMapPath,
        
        [Parameter(Mandatory = $false)]
        [string]$Repository,
        
        [Parameter(Mandatory = $false)]
        [PSCredential]$Credential,
        
        [Parameter(Mandatory = $false)]
        [switch]$Prerelease
    )
    
    # Load redirect map
    if ($RedirectMapPath) {
        if (-not (Test-Path $RedirectMapPath)) {
            throw "Redirect map file not found: $RedirectMapPath"
        }
        Write-Verbose "Loading redirect map from: $RedirectMapPath"
        $redirectMap = Get-Content $RedirectMapPath -Raw | ConvertFrom-Json -AsHashtable
    }
    else {
        Write-Verbose "Using default redirect map"
        $redirectMap = $script:defaultRedirectMap
    }
    
    $redirectMap = Get-MergedRedirectMap -OuterMap $redirectMap -Name $Name -Version $RequiredVersion
    
    Write-Verbose "Building dependency graph for $Name version $RequiredVersion"
    $dependencyGraph = @{}
    
    $root = Resolve-ExactDependency -Name $Name -RequiredVersion $RequiredVersion -RedirectMap $redirectMap
    $rootKey = Build-RemoteDependencyGraph -ModuleName $root.ResolvedName -ModuleVersion $root.ResolvedVersion `
        -Graph $dependencyGraph -RedirectMap $redirectMap -Repository $Repository -Credential $Credential `
        -Prerelease:$Prerelease -Constraint $root.Constraint
    
    $redirectedKeys = Resolve-DiamondDependencies -Graph $dependencyGraph
    $rootKey = @(Resolve-GraphRootKey -RootKeys @($rootKey) -RedirectedKeys $redirectedKeys)[0]
    
    Write-Verbose "Computing topological order"
    $topologicalOrder = @(Get-TopologicalOrder -Graph $dependencyGraph -RootKeys @($rootKey))
    
    $resolvedModules = [System.Collections.ArrayList]@()
    
    foreach ($moduleKey in $topologicalOrder) {
        $node = $dependencyGraph[$moduleKey]
        
        [void]$resolvedModules.Add([PSCustomObject]@{
            Name = $node.Name
            Version = $node.Version
            Repository = $node.Repository
            Dependencies = $node.Dependencies
        })
    }
    
    Write-Verbose "Found $($resolvedModules.Count) module(s) (including main module and all dependencies)"
    
    return $resolvedModules.ToArray()
}
# SIG # Begin signature block
# MIInRAYJKoZIhvcNAQcCoIInNTCCJzECAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCE79H/mjhgrSq0
# mdbusS0eYAJaiJq3urGVvGF07+YIZKCCDLowggX1MIID3aADAgECAhMzAAACHU0Z
# yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD
# b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1
# OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD
# VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB
# DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8
# o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg
# 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4
# Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R
# X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk
# ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B
# Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O
# BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw
# HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg
# UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0
# JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh
# MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv
# Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy
# dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9
# s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H
# VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3
# w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n
# 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs
# A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo
# Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb
# SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6
# 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z
# V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v
# 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs
# /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA
# AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl
# IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow
# VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ
# KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh
# emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h
# KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd
# M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp
# yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t
# Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5
# REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs
# 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK
# Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5
# pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW
# eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ
# 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC
# NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB
# gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU
# ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny
# bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0
# dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx
# MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI
# MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4
# NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh
# ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q
# hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU
# nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb
# H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z
# uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u
# vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW
# 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV
# DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10
# 1cY2L4A7GTQG1h32HHAvfQESWP0xghngMIIZ3AIBATBuMFcxCzAJBgNVBAYTAlVT
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv
# c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w
# DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ
# KoZIhvcNAQkEMSIEIMXMRTDB8ZS/zCl3QwrP7RBy0wLo+ByINtqSxeOC8tDCMEIG
# CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAEfbXJbBUjwceSwbW
# uyD2OjauyTciYQ4MU7veqrPajoUyHIO/maTdAarV4lhpKp2+VveHAtTZ/jtO2ar/
# kY5SArc2IGk6qNNkSlXhheOPkog3owuwiPLH/mYWBHHizrPmtS+PjFrbR2amIZaf
# OlQBT9AkX1q0uV9LQz8MbnlYEera2Wb1J7CTE5Uq4ccKoeQuq+un3Wtk+fCZFYXs
# MB9to61BMV1sjXN4Dtp7ab/erTWWnCVDIY0u4jaGyJHOl/9M+InKRDVcN5bCIQZ0
# P7MBK6O9E4ERQwvQ3IrhvplalEhoIgm0xN5o46MBExTjs80rPK8+MhM1fDEbHhpN
# uuEWbaGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kw
# gheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFF
# MIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCClS17kI6KtkUlq
# hGbgTifuNfnmY1TXc5dh78Ih9WHk3wIGaq7tx0VPGBMyMDI2MDkyMzIxMzcwNS4y
# ODdaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp
# bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1OTFBLTA1RTAtRDk0NzEl
# MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIF
# EKADAgECAhMzAAACFI3NI0TuBt9yAAEAAAIUMA0GCSqGSIb3DQEBCwUAMHwxCzAJ
# BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k
# MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv
# c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgxOFoXDTI2MTEx
# MzE4NDgxOFowgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjU5MUEtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF
# AAOCAg8AMIICCgKCAgEAyU+nWgCUyvfyGP1zTFkLkdgOutXcVteP/0CeXfrF/66c
# hKl4/MZDCQ6E8Ur4kqgCxQvef7Lg1gfso1EWWKG6vix1VxtvO1kPGK4PZKmOeoeL
# 68F6+Mw2ERPy4BL2vJKf6Lo5Z7X0xkRjtcvfM9T0HDgfHUW6z1CbgQiqrExs2NH2
# 7rWpUkyTYrMG6TXy39+GdMOTgXyUDiRGVHAy3EqYNw3zSWusn0zedl6a/1DbnXIc
# vn9FaHzd/96EPNBOCd2vOpS0Ck7kgkjVxwOptsWa8I+m+DA43cwlErPaId84GbdG
# zo3VoO7YhCmQIoRab0d8or5Pmyg+VMl8jeoN9SeUxVZpBI/cQ4TXXKlLDkfbzzSQ
# riViQGJGJLtKS3DTVNuBqpjXLdu2p2Yq9ODPqZCoiNBh4CB6X2iLYUSO8tmbUVLM
# MEegbvHSLXQR88QNICjFoBBDCDydoTo9/TNkq80mO77wDM04tPdvbMmxT01GTod6
# 0JJxUGmMTgseghdBGjkN+D6GsUpY7ta7hP9PzLrs+Alxu46XT217bBn6EwJsAYAc
# 9C28mKRUcoIZWQRb+McoZaSu2EcSzuIlAaNIQNtGlz2PF3foSeGmc/V7gCGs8AHk
# iKwXzJSPftnsH8O/R3pJw2D/2hHE3JzxH2SrLX1FdI7Drw145PkL0hbFL6MVCCkC
# AwEAAaOCAUkwggFFMB0GA1UdDgQWBBTbX/bs1cSpyTYnYuf/Mt9CPNhwGzAfBgNV
# HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o
# dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU
# aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG
# CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz
# L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV
# HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIH
# gDANBgkqhkiG9w0BAQsFAAOCAgEAP3xp9D4Gu0SH9B+1JH0hswFquINaTT+RjpfE
# r8UmUOeDl4U5uV+i28/eSYXMxgem3yBZywYDyvf4qMXUvbDcllNqRyL2Rv8jSu8w
# clt/VS1+c5cVCJfM+WHvkUr+dCfUlOy9n4exCPX1L6uWwFH5eoFfqPEp3Fw30irM
# N2SonHBK3mB8vDj3D80oJKqe2tatO38yMTiREdC2HD7eVIUWL7d54UtoYxzwkJN1
# t7gEEGosgBpdmwKVYYDO1USWSNmZELglYA4LoVoGDuWbN7mD8VozYBsfkZarOyrJ
# YlF/UCDZLB8XaLfrMfMyZTMCOuEuPD4zj8jy/Jt40clrIW04cvLhkhkydBzcrmC2
# HxeE36gJsh+jzmivS9YvyiPhLkom1FP0DIFr4VlqyXHKagrtnqSF8QyEpqtQS7wS
# 7ZzZF0eZe0fsYD0J1RarbVuDxmWsq45n1vjRdontuGUdmrG2OGeKd8AtiNghfnab
# VBbgpYgcx/eLyW/n40eTbKIlsm0cseyuWvYFyOqQXjoWtL4/sUHxlWIsrjnNarNr
# +POkL8C1jGBCJuvm0UYgjhIaL+XBXavrbOtX9mrZ3y8GQDxWXn3mhqM21ZcGk83x
# SRqB9ecfGYNRG6g65v635gSzUmBKZWWcDNzwAoxsgEjTFXz6ahfyrBLqshrjJXPK
# fO+9Ar8wggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3
# DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw
# MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx
# MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l
# LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA
# 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/
# XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1
# hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7
# M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K
# Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy
# 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80
# 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc
# NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha
# YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL
# iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV
# 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG
# CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp
# zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT
# MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv
# c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI
# KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG
# MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a
# GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br
# aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG
# AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN
# AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1
# OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA
# A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz
# aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L
# GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m
# Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0
# SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko
# JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm
# PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482
# 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7
# vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCC
# AkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp
# bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1OTFBLTA1RTAtRDk0NzEl
# MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO
# AwIaAxUA2RysX196RXLTwA/P8RFWdUTpUsaggYMwgYCkfjB8MQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt
# ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO5esd8wIhgPMjAyNjA5
# MjMyMDE1MjdaGA8yMDI2MDkyNDIwMTUyN1owdzA9BgorBgEEAYRZCgQBMS8wLTAK
# AgUA7l6x3wIBADAKAgEAAgIYlgIB/zAHAgEAAgITaTAKAgUA7mADXwIBADA2Bgor
# BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAID
# AYagMA0GCSqGSIb3DQEBCwUAA4IBAQCDWDXpl/qRyvSsTxeEjRI+K7ykFHrEnTad
# 0h7sPiVtMLwq0pZMCOkwJz/4x0T4Djt/xSs2kt1m2k5MhQm2RcW7LxocTomtf/H6
# pDCAFy2O5r3w3zGUeuGPhx/K/VfpOuyi+3bOf8jyBh6Udicz4PraD7Efn2Ff/IfU
# 8XwdiiLnerv6FG0vlY+CNwqpg/Azu6YvlR0Xl7fW5Cxbp/XMFZ4O65InAwf5ZkHo
# +8Eoq3/rT9vK3w86unUru5KDKDGDg/TA3nI3N6iF6ssbfw2XwkJrpWOuT6zj86Kt
# XfBdtMe8hJnUVeaWg/h1xO9iX448JxaPtHfJCloJndTzlATSz4dZMYIEDTCCBAkC
# AQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV
# BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG
# A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIUjc0jRO4G
# 33IAAQAAAhQwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG
# 9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgJN6p1C4+WXyhIj1c+Wez4B9ZTgZ5GUXR
# JkDXX8THRScwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCA2eKvvWx5bcoi4
# 3bRO3+EttQUCvyeD2dbXy/6+0xK+xzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwAhMzAAACFI3NI0TuBt9yAAEAAAIUMCIEIOdC/i8pry9G9/gu
# O0csdTjHWm8OqgHhlnNXbBP1ltZCMA0GCSqGSIb3DQEBCwUABIICAFzjSXwQlux3
# jIoPsHxd1JfSTLoinCghJVVo3IABO3ibEKsL9Viu+D2Ld5R4ih+tsXVRC6ps77w6
# KXg7VW0TtYtrcmkAnTfZ280/7Q9t4l/pTprDpzjYv8ZfL97T0QCxTD/T31jPwWJ3
# 4iiB+063V4NI43Guc+dc9oEZttQZa0xVEi+Jc3QoCjhgzxnM61BYU3mOMdpqUkaw
# lIfGceKQBYMEPHOwvVE2qWfsttjBNeyVAVxIyfPospnUR+l3wWo3/5LofgCrfCWf
# C7An/9XzdC6JotyQ5xe2cVNKKHOYID2RhUmThNZ56uZjkHVXSedR44NSgGG0iyiT
# xpDIRM3bzCWLdQCbX0zFkaiGQ52yHqYo1FMvQnndE0KYPFUzTF0bZgeVlb1sLeqh
# Qg4Lv7/tl1X1H9dnM97FB3TeiQ+lGlivp+GNkt4kn2Ol2k2epKbml+QKknrBaGqq
# 52f5vUg+Aig1T5AYETy/jb2xARfp7gFFEezT7Z1mHFG3UrMrsiWAeq99pi6HaHOd
# 6SVVxWTlYNv6te1OvYiBd/JSDZ6tvPc4BOA26teKmNep50B//iXx2I/QkG6BodO7
# ++6yZ1DL9hUqkVxRl64sPDT3ZrZ/08p7v6aDj6uaJ49YMWEQ7925SvrA0xAB4aQS
# rigOehwqoU1rce0AgD+oS04XJWu90aYo
# SIG # End signature block