Support/Package/Execution/Eigenverft.Manifested.Package.Package.VersionSelection.ps1

<#
    Eigenverft.Manifested.Package.Package.VersionSelection
 
    Version ordering policy for authored package releases:
    - normalVersion: full [version] values such as 1.15.7, 24.15.0, 1.4.8.1.
    - plainInteger: single integer build versions such as 9094.
    - dateHash: date-like dotted numeric prefix with a suffix, such as 2026.05.09-0afadcc.
    - numericPrefix: fallback for other values that contain a numeric dotted prefix.
 
    Selection is intentionally local to authored, compatible release candidates; no strategy in
    this file performs a network "latest" lookup.
#>


function ConvertTo-PackageVersion {
<#
.SYNOPSIS
Converts package version text to a comparable Version object.
 
.DESCRIPTION
Keeps existing semantic-like version comparisons while also supporting single
integer package versions as Major.0 and suffix-bearing versions by their first
numeric dotted prefix.
#>

    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$VersionText
    )

    if ([string]::IsNullOrWhiteSpace($VersionText)) {
        return [version]'0.0.0'
    }

    $text = ([string]$VersionText).Trim()
    if ($text -match '^\d+$') {
        return [version]('{0}.0' -f $text)
    }

    try {
        return [version]$text
    }
    catch {
        $match = [regex]::Match($text, '\d+(?:\.\d+){0,3}')
        if ($match.Success) {
            try {
                $numericText = $match.Value
                if ($numericText -match '^\d+$') {
                    $numericText = '{0}.0' -f $numericText
                }
                return [version]$numericText
            }
            catch {
            }
        }
    }

    return [version]'0.0.0'
}

function Get-PackageVersionOrderingInfo {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$VersionText,

        [int]$AuthorIndex = 0,

        [int]$CandidateIndex = 0
    )

    $text = if ([string]::IsNullOrWhiteSpace($VersionText)) { '' } else { ([string]$VersionText).Trim() }
    $orderingKind = 'numericPrefix'
    if ([string]::IsNullOrWhiteSpace($text)) {
        $orderingKind = 'empty'
    }
    elseif ($text -match '^\d+$') {
        $orderingKind = 'plainInteger'
    }
    else {
        try {
            $null = [version]$text
            $orderingKind = 'normalVersion'
        }
        catch {
            if ($text -match '^\d{4}\.\d{1,2}\.\d{1,2}(?:\.\d+)?[-+_].+$') {
                $orderingKind = 'dateHash'
            }
        }
    }

    return [pscustomobject]@{
        VersionText    = $text
        OrderingKind   = $orderingKind
        SortVersion    = ConvertTo-PackageVersion -VersionText $text
        AuthorIndex    = $AuthorIndex
        CandidateIndex = $CandidateIndex
    }
}

function Sort-PackageVersionCandidates {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [object[]]$Candidates
    )

    return @($Candidates) | Sort-Object `
        @{ Expression = { $_.VersionOrdering.SortVersion }; Descending = $true },
        @{ Expression = { $_.VersionOrdering.AuthorIndex }; Descending = $false },
        @{ Expression = { $_.VersionOrdering.CandidateIndex }; Descending = $false }
}

function Resolve-PackageVersionRangeTerms {
    [CmdletBinding()]
    param(
        [AllowNull()]
        [string]$VersionRange
    )

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

    $terms = New-Object System.Collections.Generic.List[object]
    foreach ($rawToken in ([regex]::Split(([string]$VersionRange).Trim(), '[\s,]+'))) {
        $token = ([string]$rawToken).Trim()
        if ([string]::IsNullOrWhiteSpace($token)) {
            continue
        }
        if ($token -match '[\*\^~\|]' -or $token -match '(?i)(?:^|[^a-z])x(?:$|[^a-z])') {
            throw "Unsupported Package versionRange term '$token'. Use comparator terms such as '>=1.2.0' and '<2.0.0'."
        }

        $operator = '='
        $versionText = $token
        $match = [regex]::Match($token, '^(>=|<=|==|=|>|<)(.+)$')
        if ($match.Success) {
            $operator = $match.Groups[1].Value
            $versionText = $match.Groups[2].Value.Trim()
        }

        if ([string]::IsNullOrWhiteSpace($versionText)) {
            throw "Package versionRange term '$token' is missing a version."
        }
        if ($operator -ne '=' -and $operator -ne '==' -and $versionText -notmatch '\d') {
            throw "Package versionRange term '$token' does not contain a comparable version."
        }

        $terms.Add([pscustomobject]@{
            Operator    = $operator
            VersionText = $versionText
        }) | Out-Null
    }

    return @($terms.ToArray())
}

function Test-PackageVersionRange {
    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [AllowNull()]
        [string]$VersionText,

        [AllowNull()]
        [string]$VersionRange
    )

    $terms = @(Resolve-PackageVersionRangeTerms -VersionRange $VersionRange)
    if ($terms.Count -eq 0) {
        return $true
    }

    $actualComparable = ConvertTo-PackageVersion -VersionText $VersionText
    foreach ($term in @($terms)) {
        $operator = [string]$term.Operator
        $expectedText = [string]$term.VersionText
        if ($operator -eq '=' -or $operator -eq '==') {
            if (-not [string]::Equals([string]$VersionText, $expectedText, [System.StringComparison]::OrdinalIgnoreCase)) {
                return $false
            }
            continue
        }

        $expectedComparable = ConvertTo-PackageVersion -VersionText $expectedText
        $accepted = switch -Exact ($operator) {
            '>' { $actualComparable -gt $expectedComparable }
            '>=' { $actualComparable -ge $expectedComparable }
            '<' { $actualComparable -lt $expectedComparable }
            '<=' { $actualComparable -le $expectedComparable }
            default { throw "Unsupported Package versionRange operator '$operator'." }
        }
        if (-not $accepted) {
            return $false
        }
    }

    return $true
}

function Resolve-PackageVersionCandidateSelection {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$Candidates,

        [AllowNull()]
        [string]$CommandSelector = $null,

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

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

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

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

        [AllowNull()]
        [object[]]$AllVersionEntries,

        [AllowNull()]
        [string]$VersionRange = $null
    )

    if (@($Candidates).Count -eq 0) {
        throw "No Package target/release entry matched platform '$Platform', architecture '$Architecture', and releaseTrack '$ReleaseTrack'."
    }

    $rangeText = if ([string]::IsNullOrWhiteSpace($VersionRange)) { $null } else { ([string]$VersionRange).Trim() }
    if (-not [string]::IsNullOrWhiteSpace($rangeText)) {
        $null = Resolve-PackageVersionRangeTerms -VersionRange $rangeText
        $Candidates = @(
            foreach ($candidate in @($Candidates)) {
                if (Test-PackageVersionRange -VersionText ([string]$candidate.VersionEntry.version) -VersionRange $rangeText) {
                    $candidate
                }
            }
        )
        if (@($Candidates).Count -eq 0) {
            throw "No Package version satisfied versionRange '$rangeText' for definition '$DefinitionId' on platform '$Platform', architecture '$Architecture', and releaseTrack '$ReleaseTrack'."
        }
    }

    $selector = if ([string]::IsNullOrWhiteSpace($CommandSelector)) { $null } else { ([string]$CommandSelector).Trim() }
    $selectionSource = if ([string]::IsNullOrWhiteSpace($selector)) { 'definition' } else { 'command' }
    if ([string]::IsNullOrWhiteSpace($selector)) {
        $firstTarget = @($Candidates)[0].ArtifactTarget
        $selector = if ($firstTarget -and $firstTarget.PSObject.Properties['versionSelection'] -and
            $firstTarget.versionSelection -and $firstTarget.versionSelection.PSObject.Properties['strategy'] -and
            -not [string]::IsNullOrWhiteSpace([string]$firstTarget.versionSelection.strategy)) {
            [string]$firstTarget.versionSelection.strategy
        }
        else {
            'latestByVersion'
        }
    }

    $orderedCandidates = @(Sort-PackageVersionCandidates -Candidates $Candidates)
    $selected = $null
    $requestedVersion = $null
    $selectionKind = $selector

    if ([string]::Equals($selector, 'latestByVersion', [System.StringComparison]::OrdinalIgnoreCase)) {
        $selected = $orderedCandidates | Select-Object -First 1
        $selectionKind = 'latestByVersion'
    }
    elseif ([string]::Equals($selector, 'previousByVersion', [System.StringComparison]::OrdinalIgnoreCase)) {
        $selected = if ($orderedCandidates.Count -gt 1) { $orderedCandidates[1] } else { $orderedCandidates[0] }
        $selectionKind = 'previousByVersion'
    }
    else {
        $requestedVersion = $selector
        $matches = @(
            foreach ($candidate in @($Candidates)) {
                if ([string]::Equals([string]$candidate.VersionEntry.version, $requestedVersion, [System.StringComparison]::OrdinalIgnoreCase)) {
                    $candidate
                }
            }
        )

        if ($matches.Count -eq 0) {
            $versionIsAuthored = $false
            foreach ($versionEntry in @($AllVersionEntries)) {
                if ([string]::Equals([string]$versionEntry.version, $requestedVersion, [System.StringComparison]::OrdinalIgnoreCase)) {
                    $versionIsAuthored = $true
                    break
                }
            }

            if ($versionIsAuthored) {
                throw "Package version '$requestedVersion' is not available for definition '$DefinitionId' on platform '$Platform', architecture '$Architecture', and releaseTrack '$ReleaseTrack'."
            }

            throw "Package version '$requestedVersion' is not authored for definition '$DefinitionId'."
        }

        $selected = @(Sort-PackageVersionCandidates -Candidates $matches) | Select-Object -First 1
        $selectionKind = 'exact'
    }

    return [pscustomobject]@{
        Candidate        = $selected
        Selector         = $selector
        Source           = $selectionSource
        SelectionKind    = $selectionKind
        OrderingKind     = [string]$selected.VersionOrdering.OrderingKind
        RequestedVersion = $requestedVersion
        VersionRange     = $rangeText
    }
}