completers/py_completer/py_completer.ps1

Set-StrictMode -Version 2.0

function New-PyCompletionResult {
    param(
        [string]$CompletionText,
        [string]$ResultType = 'ParameterValue',
        [string]$ToolTip,
        [string]$ListItemText
    )

    if ([string]::IsNullOrWhiteSpace($ListItemText)) {
        $ListItemText = $CompletionText
    }

    if ([string]::IsNullOrWhiteSpace($ToolTip)) {
        $ToolTip = $ListItemText
    }

    [System.Management.Automation.CompletionResult]::new(
        $CompletionText,
        $ListItemText,
        $ResultType,
        $ToolTip
    )
}

function Test-PyStartsWith {
    param(
        [string]$Candidate,
        [string]$Prefix
    )

    [string]::IsNullOrEmpty($Prefix) -or $Candidate.StartsWith($Prefix, [System.StringComparison]::OrdinalIgnoreCase)
}

function New-PyOptionSpec {
    param(
        [string[]]$Tokens,
        [string]$Description,
        [string]$ValueKind = '',
        [bool]$IsTerminal = $false
    )

    [pscustomobject]@{
        Tokens      = @($Tokens)
        Description = $Description
        ValueKind   = $ValueKind
        IsTerminal  = $IsTerminal
    }
}

function Get-PyLegacyOptionTable {
    # Python Launcher for Windows (PEP 397). -32/-64 exist only as suffixes on a version selector.
    @(
        New-PyOptionSpec -Tokens @('-2') -Description 'Launch the latest Python 2.x runtime.'
        New-PyOptionSpec -Tokens @('-3') -Description 'Launch the latest Python 3.x runtime.'
        New-PyOptionSpec -Tokens @('-0', '--list') -Description 'List the available Python runtimes.' -IsTerminal $true
        New-PyOptionSpec -Tokens @('-0p', '--list-paths') -Description 'List the available Python runtimes with paths.' -IsTerminal $true
        New-PyOptionSpec -Tokens @('-h', '-?', '--help') -Description 'Show Python Launcher for Windows help.' -IsTerminal $true
        New-PyOptionSpec -Tokens @('-V:') -Description 'Select a runtime by tag or by COMPANY\TAG.'
    )
}

function Get-PyManagerRootOptionTable {
    # Python install manager: launch forms accepted before any command.
    @(
        New-PyOptionSpec -Tokens @('-3') -Description 'Launch a PythonCore 3.x runtime (-3<VERSION>, platform suffix allowed).'
        New-PyOptionSpec -Tokens @('-V:') -Description 'Launch the runtime identified by <TAG> (include the company unless PythonCore).'
    )
}

function Get-PyGlobalOptionTable {
    # Python install manager global options; they must follow the command.
    @(
        New-PyOptionSpec -Tokens @('-v', '--verbose') -Description 'Increased output (log_level=20).'
        New-PyOptionSpec -Tokens @('-vv') -Description 'Further increased output (log_level=10).'
        New-PyOptionSpec -Tokens @('-q', '--quiet') -Description 'Less output (log_level=30).'
        New-PyOptionSpec -Tokens @('-qq') -Description 'Even less output (log_level=40).'
        New-PyOptionSpec -Tokens @('-y', '--yes') -Description 'Always accept confirmation prompts (confirm=false).'
        New-PyOptionSpec -Tokens @('-h', '-?', '--help') -Description 'Show help for a specific command.' -IsTerminal $true
        New-PyOptionSpec -Tokens @('--config') -Description 'Override configuration with a JSON file (--config=<PATH>).' -ValueKind 'ConfigPath'
    )
}

function Get-PyCommandTable {
    @(
        [pscustomobject]@{ Name = 'exec';      Description = 'Launch a runtime (-V:<TAG> or -3<VERSION>), installing it if needed.' }
        [pscustomobject]@{ Name = 'help';      Description = 'Show help for Python installation manager commands.' }
        [pscustomobject]@{ Name = 'install';   Description = 'Download new Python runtimes, or pass --update to update existing installs.' }
        [pscustomobject]@{ Name = 'list';      Description = 'Show installed Python runtimes, optionally filtered.' }
        [pscustomobject]@{ Name = 'uninstall'; Description = 'Remove one or more runtimes from your machine.' }
    )
}

function Get-PyCommandOptionTable {
    param([string]$Command)

    $specs = switch ($Command) {
        'install' {
            @(
                New-PyOptionSpec -Tokens @('-s', '--source') -Description 'Specify index.json to use (install.source=...).' -ValueKind 'Uri'
                New-PyOptionSpec -Tokens @('-t', '--target') -Description 'Extract runtime to location instead of installing.' -ValueKind 'DirectoryPath'
                New-PyOptionSpec -Tokens @('-d', '--download') -Description 'Prepare an offline index with one or more runtimes.' -ValueKind 'DirectoryPath'
                New-PyOptionSpec -Tokens @('-f', '--force') -Description 'Re-download and overwrite existing install.'
                New-PyOptionSpec -Tokens @('-u', '--update') -Description 'Overwrite existing install if a newer version is available.'
                New-PyOptionSpec -Tokens @('--dry-run') -Description 'Choose runtime but do not install.'
                New-PyOptionSpec -Tokens @('--refresh') -Description 'Update shortcuts and aliases for all installed versions.'
                New-PyOptionSpec -Tokens @('--configure') -Description 'Re-run the system configuration helper.'
                New-PyOptionSpec -Tokens @('--by-id') -Description 'Require TAG to exactly match the install ID.'
            )
        }
        'list' {
            @(
                New-PyOptionSpec -Tokens @('-f', '--format') -Description 'Specify list format, defaults to table.' -ValueKind 'ListFormat'
                New-PyOptionSpec -Tokens @('-1', '--one') -Description 'Only display first result that matches the filter.'
                New-PyOptionSpec -Tokens @('--online') -Description 'List runtimes available to install from the default index.'
                New-PyOptionSpec -Tokens @('-s', '--source') -Description 'List runtimes from a particular index.' -ValueKind 'Uri'
                New-PyOptionSpec -Tokens @('--only-managed') -Description 'Only list Python installs managed by the tool.'
            )
        }
        'uninstall' {
            @(
                New-PyOptionSpec -Tokens @('--purge') -Description 'Remove all runtimes, shortcuts, and cached files. Ignores tags.'
                New-PyOptionSpec -Tokens @('--by-id') -Description 'Require TAG to exactly match the install ID.'
            )
        }
        default { @() }
    }

    @($specs) + @(Get-PyGlobalOptionTable)
}

function Get-PyOptionMap {
    param([object[]]$Specs)

    # Ordinal: -v (verbose) and -V: (selector) are different tokens.
    $map = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal)
    foreach ($spec in @($Specs)) {
        foreach ($token in $spec.Tokens) {
            $map[$token] = $spec
        }
    }

    $map
}

function Resolve-PyCommandName {
    $cache = Get-Variable -Name PyCompletionResolvedCommand -Scope Script -ErrorAction Ignore
    if ($cache) {
        return $cache.Value
    }

    $resolved = $null
    $command = Get-Command -Name py.exe, py -CommandType Application -ErrorAction Ignore | Select-Object -First 1
    if ($command) {
        $resolved = if ([string]::IsNullOrWhiteSpace($command.Source)) { $command.Name } else { $command.Source }
    }

    Set-Variable -Name PyCompletionResolvedCommand -Scope Script -Value $resolved
    $resolved
}

function Invoke-PyProcessOutput {
    param([string[]]$Arguments)

    $commandName = Resolve-PyCommandName
    if ([string]::IsNullOrWhiteSpace($commandName)) {
        return $null
    }

    try {
        $startInfo = [System.Diagnostics.ProcessStartInfo]::new()
        $startInfo.FileName = $commandName
        foreach ($argument in $Arguments) {
            $startInfo.ArgumentList.Add($argument)
        }
        $startInfo.UseShellExecute = $false
        $startInfo.CreateNoWindow = $true
        $startInfo.RedirectStandardInput = $true
        $startInfo.RedirectStandardOutput = $true
        $startInfo.RedirectStandardError = $true

        $process = [System.Diagnostics.Process]::Start($startInfo)
        try {
            $process.StandardInput.Close()
            $outputTask = $process.StandardOutput.ReadToEndAsync()
            $errorTask = $process.StandardError.ReadToEndAsync()
            if (-not $process.WaitForExit(5000)) {
                $process.Kill()
                return $null
            }

            $text = ($outputTask.Result -replace '\e\[[0-9;?]*[ -/]*[@-~]', '')
            if ([string]::IsNullOrWhiteSpace($text)) {
                $text = ($errorTask.Result -replace '\e\[[0-9;?]*[ -/]*[@-~]', '')
            }

            return $text
        } finally {
            $process.Dispose()
        }
    } catch {
        Write-Debug "py completer: '$commandName $($Arguments -join ' ')' failed: $($_.Exception.Message)"
        return $null
    }
}

function Get-PyLauncherFlavor {
    # 'manager' for the Python install manager (py exec|help|install|list|uninstall),
    # 'legacy' for the Python Launcher for Windows (-0, -0p, -2 ...).
    $cache = Get-Variable -Name PyCompletionFlavor -Scope Script -ErrorAction Ignore
    if ($cache) {
        return $cache.Value
    }

    $flavor = 'legacy'
    $commandName = Resolve-PyCommandName
    if (-not [string]::IsNullOrWhiteSpace($commandName)) {
        $siblingManager = $null
        try {
            $siblingManager = Join-Path -Path (Split-Path -Path $commandName -Parent) -ChildPath 'pymanager.exe'
        } catch {
            $siblingManager = $null
        }

        if (($siblingManager -and (Test-Path -LiteralPath $siblingManager)) -or $commandName -match 'PythonManager') {
            $flavor = 'manager'
        } else {
            $help = Invoke-PyProcessOutput -Arguments @('-h')
            if (-not [string]::IsNullOrWhiteSpace($help) -and $help -notmatch 'Python Launcher for Windows') {
                $flavor = 'manager'
            }
        }
    }

    Set-Variable -Name PyCompletionFlavor -Scope Script -Value $flavor
    $flavor
}

function Add-PyRuntimeTag {
    param(
        [System.Collections.Generic.HashSet[string]]$TagSet,
        [System.Collections.Generic.HashSet[string]]$QualifiedSet,
        [string]$Company,
        [string]$Tag
    )

    if ([string]::IsNullOrWhiteSpace($Tag) -or $Tag.Contains('[') -or $Tag.Contains(']')) {
        return
    }

    [void]$TagSet.Add($Tag)
    if (-not [string]::IsNullOrWhiteSpace($Company)) {
        [void]$QualifiedSet.Add("$Company\$Tag")
    }
}

function Get-PyRuntimeTagCatalog {
    $cache = Get-Variable -Name PyCompletionRuntimeTagCatalog -Scope Script -ErrorAction Ignore
    if ($cache) {
        $age = (Get-Date) - $cache.Value.UpdatedAt
        if ($age.TotalSeconds -lt 300) {
            return $cache.Value
        }
    }

    $tagSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $qualifiedSet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $companySet = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $loaded = $false

    if ((Get-PyLauncherFlavor) -eq 'manager') {
        # Structured source: company and tag fields need no display-syntax parsing.
        $json = Invoke-PyProcessOutput -Arguments @('list', '-f=json')
        if (-not [string]::IsNullOrWhiteSpace($json)) {
            try {
                $document = $json | ConvertFrom-Json -ErrorAction Stop
                foreach ($version in @($document.versions)) {
                    $company = if ($version.PSObject.Properties['company']) { [string]$version.company } else { '' }
                    if (-not [string]::IsNullOrWhiteSpace($company)) {
                        [void]$companySet.Add($company)
                    }

                    if ($version.PSObject.Properties['tag']) {
                        Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag ([string]$version.tag)
                    }

                    if ($version.PSObject.Properties['install-for']) {
                        foreach ($alias in @($version.'install-for')) {
                            Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag ([string]$alias)
                        }
                    }

                    if ($version.PSObject.Properties['run-for']) {
                        foreach ($entry in @($version.'run-for')) {
                            if ($entry.PSObject.Properties['tag']) {
                                Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag ([string]$entry.tag)
                            }
                        }
                    }

                    $loaded = $true
                }
            } catch {
                Write-Debug "py completer: could not parse 'py list -f=json': $($_.Exception.Message)"
            }
        }
    }

    if (-not $loaded) {
        # Legacy launcher (or JSON unavailable): scrape 'py -0p'. The display notation '3.14[-64]'
        # means an optional platform suffix, so it expands to '3.14' and '3.14-64'.
        $text = Invoke-PyProcessOutput -Arguments @('-0p')
        foreach ($line in @(($text -split '\r?\n'))) {
            $match = [regex]::Match($line, '(?i)-V:(?<selector>\S+)')
            if (-not $match.Success) {
                continue
            }

            $selector = $match.Groups['selector'].Value.Trim()
            $company = ''
            $tag = $selector
            $separator = $selector.IndexOfAny([char[]]@('/', '\'))
            if ($separator -ge 0) {
                $company = $selector.Substring(0, $separator)
                $tag = $selector.Substring($separator + 1)
                if (-not [string]::IsNullOrWhiteSpace($company)) {
                    [void]$companySet.Add($company)
                }
            }

            $bracket = [regex]::Match($tag, '^(?<base>[^\[]+)\[(?<optional>[^\]]+)\]$')
            if ($bracket.Success) {
                Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag $bracket.Groups['base'].Value
                Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag ($bracket.Groups['base'].Value + $bracket.Groups['optional'].Value)
            } else {
                Add-PyRuntimeTag -TagSet $tagSet -QualifiedSet $qualifiedSet -Company $company -Tag $tag
            }
        }
    }

    $catalog = [pscustomobject]@{
        UpdatedAt          = Get-Date
        Tags               = @($tagSet | Sort-Object)
        QualifiedSelectors = @($qualifiedSet | Sort-Object)
        Companies          = @($companySet | Sort-Object)
    }

    Set-Variable -Name PyCompletionRuntimeTagCatalog -Scope Script -Value $catalog
    $catalog
}

function Get-PyCommandLineState {
    param(
        [System.Management.Automation.Language.CommandAst]$CommandAst,
        [int]$CursorPosition
    )

    $line = if ($null -eq $CommandAst) { '' } else { $CommandAst.Extent.Text }
    if ($null -eq $line) {
        $line = ''
    }

    if ($null -ne $CommandAst -and $CursorPosition -gt $CommandAst.Extent.EndOffset) {
        $line += [string]::new([char]32, ($CursorPosition - $CommandAst.Extent.EndOffset))
    }

    $relativeCursor = if ($null -eq $CommandAst) {
        $CursorPosition
    } else {
        $CursorPosition - $CommandAst.Extent.StartOffset
    }

    $safeCursor = [Math]::Min([Math]::Max($relativeCursor, 0), $line.Length)
    $prefix = $line.Substring(0, $safeCursor)
    $tokens = New-Object System.Collections.Generic.List[string]
    $builder = New-Object System.Text.StringBuilder
    $quoteChar = [char]0

    foreach ($character in $prefix.ToCharArray()) {
        if (($character -eq [char]34) -or ($character -eq [char]39)) {
            if ($quoteChar -eq [char]0) {
                $quoteChar = $character
            } elseif ($quoteChar -eq $character) {
                $quoteChar = [char]0
            }

            [void]$builder.Append($character)
            continue
        }

        if ([char]::IsWhiteSpace($character) -and $quoteChar -eq [char]0) {
            if ($builder.Length -gt 0) {
                $tokens.Add($builder.ToString())
                [void]$builder.Clear()
            }

            continue
        }

        [void]$builder.Append($character)
    }

    $hasTrailingSpace = $prefix -match '\s$'
    if ($builder.Length -gt 0) {
        $tokens.Add($builder.ToString())
    }

    if ($hasTrailingSpace) {
        return [pscustomobject]@{
            TokensBeforeCurrent = @($tokens)
            CurrentToken        = ''
        }
    }

    if ($tokens.Count -gt 0) {
        return [pscustomobject]@{
            TokensBeforeCurrent = @($tokens | Select-Object -First ($tokens.Count - 1))
            CurrentToken        = $tokens[$tokens.Count - 1]
        }
    }

    [pscustomobject]@{
        TokensBeforeCurrent = @()
        CurrentToken        = ''
    }
}

function Get-PyArgumentState {
    param([pscustomobject]$CommandLineState)

    $tokensBeforeCurrent = @($CommandLineState.TokensBeforeCurrent)
    $currentArgument = if ($null -eq $CommandLineState.CurrentToken) { '' } else { $CommandLineState.CurrentToken }
    $argumentsBeforeCurrent = if ($tokensBeforeCurrent.Count -gt 0) {
        @($tokensBeforeCurrent | Select-Object -Skip 1)
    } else {
        @()
    }

    if ($tokensBeforeCurrent.Count -eq 0 -and $currentArgument -match '^(?i:py(?:\.exe)?)$') {
        $currentArgument = ''
    }

    [pscustomobject]@{
        ArgumentsBeforeCurrent = $argumentsBeforeCurrent
        CurrentArgument        = $currentArgument
    }
}

function Test-PyVersionSelectorToken {
    param([string]$Token)

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

    # -3, -3.14, -3.14-64, -3.14.6-64, -3-arm64 (and -2... on the legacy launcher)
    $Token -cmatch '^-[23](?:$|[.\-][\w.\-]*$)' -or $Token.StartsWith('-V:', [System.StringComparison]::Ordinal)
}

function Test-PyVersionSelectorPrefix {
    param([string]$Token)

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

    $Token -cmatch '^-[23](?:$|[.\-][\w.\-]*$)'
}

function Get-PyContextOptionTable {
    param(
        [string]$Flavor,
        [string]$Command
    )

    if ($Flavor -ne 'manager') {
        return @(Get-PyLegacyOptionTable)
    }

    if ([string]::IsNullOrEmpty($Command)) {
        return @(Get-PyManagerRootOptionTable)
    }

    if ($Command -eq 'exec') {
        return @(Get-PyManagerRootOptionTable) + @(Get-PyGlobalOptionTable)
    }

    @(Get-PyCommandOptionTable -Command $Command)
}

function Get-PyCompletionContext {
    param(
        [string[]]$ArgumentsBeforeCurrent,
        [string]$Flavor
    )

    $command = ''
    $runtimeSelected = $false
    $terminalMode = ''
    $pendingValueKind = ''
    $positionals = New-Object System.Collections.Generic.List[string]
    $commandNames = @((Get-PyCommandTable) | ForEach-Object { $_.Name })

    foreach ($argument in @($ArgumentsBeforeCurrent)) {
        if (-not [string]::IsNullOrEmpty($terminalMode)) {
            break
        }

        if ([string]::IsNullOrWhiteSpace($argument)) {
            continue
        }

        if (-not [string]::IsNullOrEmpty($pendingValueKind)) {
            $pendingValueKind = ''
            continue
        }

        $isDash = $argument.StartsWith('-', [System.StringComparison]::Ordinal)
        if ($Flavor -eq 'manager' -and [string]::IsNullOrEmpty($command) -and -not $runtimeSelected -and -not $isDash -and $positionals.Count -eq 0 -and ($commandNames -contains $argument)) {
            $command = $argument.ToLowerInvariant()
            continue
        }

        if ($isDash -and (Test-PyVersionSelectorToken -Token $argument) -and ($Flavor -ne 'manager' -or [string]::IsNullOrEmpty($command) -or $command -eq 'exec')) {
            $runtimeSelected = $true
            continue
        }

        if ($isDash) {
            $optionMap = Get-PyOptionMap -Specs (Get-PyContextOptionTable -Flavor $Flavor -Command $command)
            $name = $argument
            $hasAttachedValue = $false
            $equals = $argument.IndexOf('=')
            if ($equals -gt 0) {
                $name = $argument.Substring(0, $equals)
                $hasAttachedValue = $true
            }

            if ($optionMap.ContainsKey($name)) {
                $spec = $optionMap[$name]
                if ($spec.IsTerminal) {
                    $terminalMode = 'LauncherTerminal'
                } elseif (-not $hasAttachedValue -and -not [string]::IsNullOrWhiteSpace($spec.ValueKind)) {
                    $pendingValueKind = $spec.ValueKind
                }
            }

            continue
        }

        if ($Flavor -eq 'manager' -and $command -in @('install', 'uninstall', 'list', 'help')) {
            $positionals.Add($argument)
            continue
        }

        $terminalMode = 'ScriptTail'
    }

    [pscustomobject]@{
        Command          = $command
        RuntimeSelected  = $runtimeSelected
        TerminalMode     = $terminalMode
        PendingValueKind = $pendingValueKind
        Positionals      = @($positionals)
    }
}

function Get-PyUniqueResults {
    param([object[]]$Results)

    $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
    $unique = New-Object System.Collections.Generic.List[object]

    foreach ($result in @($Results)) {
        if ($null -eq $result) {
            continue
        }

        if ($seen.Add($result.CompletionText)) {
            [void]$unique.Add($result)
        }
    }

    @($unique.ToArray())
}

function Get-PyPlaceholderCompletions {
    param(
        [string]$CurrentWord,
        [string]$Placeholder,
        [string]$ToolTip
    )

    $completionText = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Placeholder } else { $CurrentWord }
    @(
        New-PyCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip $ToolTip -ListItemText $Placeholder
    )
}

function Get-PyTerminalCompletions {
    param(
        [string]$CurrentWord,
        [string]$ToolTip,
        [string]$Placeholder
    )

    $completionText = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { ' ' } else { $CurrentWord }
    @(
        New-PyCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip $ToolTip -ListItemText $Placeholder
    )
}

function Get-PyPathCompletions {
    param(
        [string]$CurrentWord,
        [string]$Placeholder,
        [string]$AttachedPrefix = '',
        [switch]$ContainersOnly
    )

    $results = New-Object System.Collections.Generic.List[object]

    foreach ($item in [System.Management.Automation.CompletionCompleters]::CompleteFilename($CurrentWord)) {
        if ($ContainersOnly -and $item.ResultType -ne [System.Management.Automation.CompletionResultType]::ProviderContainer) {
            continue
        }

        [void]$results.Add([System.Management.Automation.CompletionResult]::new(
                ($AttachedPrefix + $item.CompletionText),
                $item.ListItemText,
                $item.ResultType,
                $item.ToolTip
            ))
    }

    if ($results.Count -eq 0) {
        $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $AttachedPrefix + $Placeholder } else { $AttachedPrefix + $CurrentWord }
        return @(New-PyCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip 'Filesystem path.' -ListItemText $Placeholder)
    }

    @($results.ToArray())
}

function Get-PyOptionCompletionList {
    param(
        [object[]]$Specs,
        [string]$CurrentWord
    )

    $results = New-Object System.Collections.Generic.List[object]

    foreach ($spec in @($Specs)) {
        foreach ($token in $spec.Tokens) {
            # Ordinal: py's options are case-sensitive (-v is --verbose, -V: is the runtime selector).
            if ([string]::IsNullOrEmpty($CurrentWord) -or $token.StartsWith($CurrentWord, [System.StringComparison]::Ordinal)) {
                $completionText = if (-not [string]::IsNullOrWhiteSpace($spec.ValueKind) -and $token.StartsWith('--')) { $token + '=' } else { $token }
                [void]$results.Add((New-PyCompletionResult -CompletionText $completionText -ResultType 'ParameterName' -ToolTip $spec.Description -ListItemText $token))
            }
        }
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

function Get-PyVCompletions {
    param([string]$CurrentWord)

    $results = New-Object System.Collections.Generic.List[object]
    $catalog = Get-PyRuntimeTagCatalog
    $suffix = if ($CurrentWord.Length -ge 3) { $CurrentWord.Substring(3) } else { '' }
    $separatorIndex = $suffix.IndexOfAny([char[]]@('/', '\'))

    if ($separatorIndex -ge 0) {
        # COMPANY\TAG (the documented form) or COMPANY/TAG; keep whichever separator was typed.
        $companyPrefix = $suffix.Substring(0, $separatorIndex + 1)
        $tagPrefix = $suffix.Substring($separatorIndex + 1)

        foreach ($tag in @($catalog.Tags)) {
            if (Test-PyStartsWith -Candidate $tag -Prefix $tagPrefix) {
                [void]$results.Add((New-PyCompletionResult -CompletionText "-V:$companyPrefix$tag" -ResultType 'ParameterName' -ToolTip 'Runtime selector by COMPANY\TAG.'))
            }
        }

        if ($results.Count -eq 0) {
            return @(Get-PyPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder "-V:$companyPrefix<TAG>" -ToolTip 'Runtime selector by COMPANY\TAG.')
        }

        return @(Get-PyUniqueResults -Results $results.ToArray())
    }

    foreach ($tag in @($catalog.Tags)) {
        if (Test-PyStartsWith -Candidate $tag -Prefix $suffix) {
            [void]$results.Add((New-PyCompletionResult -CompletionText "-V:$tag" -ResultType 'ParameterName' -ToolTip 'Installed runtime selector by tag.'))
        }
    }

    foreach ($selector in @($catalog.QualifiedSelectors)) {
        if (Test-PyStartsWith -Candidate $selector -Prefix $suffix) {
            [void]$results.Add((New-PyCompletionResult -CompletionText "-V:$selector" -ResultType 'ParameterName' -ToolTip 'Installed runtime selector by COMPANY\TAG.'))
        }
    }

    if ([string]::IsNullOrWhiteSpace($suffix)) {
        [void]$results.Add((New-PyCompletionResult -CompletionText '-V:<TAG>' -ResultType 'ParameterName' -ToolTip 'Runtime selector by tag.'))
        [void]$results.Add((New-PyCompletionResult -CompletionText '-V:<COMPANY\TAG>' -ResultType 'ParameterName' -ToolTip 'Runtime selector by COMPANY\TAG.'))
    } elseif ($results.Count -eq 0) {
        return @(Get-PyPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '-V:<TAG>' -ToolTip 'Runtime selector.')
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

function Get-PyVersionSelectorCompletions {
    param(
        [string]$CurrentWord,
        [string]$Flavor
    )

    $results = New-Object System.Collections.Generic.List[object]
    $catalog = Get-PyRuntimeTagCatalog
    $majors = if ($Flavor -eq 'manager') { @('3') } else { @('2', '3') }

    foreach ($major in $majors) {
        if (Test-PyStartsWith -Candidate "-$major" -Prefix $CurrentWord) {
            [void]$results.Add((New-PyCompletionResult -CompletionText "-$major" -ResultType 'ParameterName' -ToolTip "Launch the latest Python $major.x runtime."))
        }
    }

    foreach ($tag in @($catalog.Tags)) {
        if ($tag -notmatch '^\d') {
            continue
        }

        if ($Flavor -eq 'manager' -and -not $tag.StartsWith('3')) {
            continue
        }

        $candidate = "-$tag"
        if (Test-PyStartsWith -Candidate $candidate -Prefix $CurrentWord) {
            [void]$results.Add((New-PyCompletionResult -CompletionText $candidate -ResultType 'ParameterName' -ToolTip 'Installed runtime version selector.'))
        }
    }

    if ($results.Count -eq 0) {
        return @(Get-PyPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '-3.X' -ToolTip 'Version selector form (-3<VERSION>).')
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

function Get-PyTagCompletionList {
    param(
        [string]$CurrentWord,
        [string]$Command
    )

    $results = New-Object System.Collections.Generic.List[object]
    $catalog = Get-PyRuntimeTagCatalog

    if ($Command -eq 'help') {
        foreach ($commandSpec in @(Get-PyCommandTable)) {
            if (Test-PyStartsWith -Candidate $commandSpec.Name -Prefix $CurrentWord) {
                [void]$results.Add((New-PyCompletionResult -CompletionText $commandSpec.Name -ResultType 'ParameterValue' -ToolTip $commandSpec.Description))
            }
        }

        if ($results.Count -eq 0) {
            return @(Get-PyPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<CMD>' -ToolTip 'Command to show help for.')
        }

        return @(Get-PyUniqueResults -Results $results.ToArray())
    }

    $toolTip = switch ($Command) {
        'install'   { 'Runtime tag to install (Company\Tag format).' }
        'uninstall' { 'Installed runtime to remove (Company\Tag format).' }
        default     { 'Filter (Company\Tag with optional <, <=, >, >= prefix).' }
    }

    $comparisonPrefix = ''
    $tagWord = $CurrentWord
    if ($Command -eq 'list' -and $CurrentWord -match '^(?<op><=|>=|<|>)(?<rest>.*)$') {
        $comparisonPrefix = $Matches.op
        $tagWord = $Matches.rest
    }

    foreach ($tag in @($catalog.Tags)) {
        if (Test-PyStartsWith -Candidate $tag -Prefix $tagWord) {
            [void]$results.Add((New-PyCompletionResult -CompletionText ($comparisonPrefix + $tag) -ResultType 'ParameterValue' -ToolTip $toolTip))
        }
    }

    foreach ($selector in @($catalog.QualifiedSelectors)) {
        if (Test-PyStartsWith -Candidate $selector -Prefix $tagWord) {
            [void]$results.Add((New-PyCompletionResult -CompletionText ($comparisonPrefix + $selector) -ResultType 'ParameterValue' -ToolTip $toolTip))
        }
    }

    if ($Command -eq 'install') {
        foreach ($hint in @('3', '3.14', '3.13', '3.12')) {
            if (Test-PyStartsWith -Candidate $hint -Prefix $tagWord) {
                [void]$results.Add((New-PyCompletionResult -CompletionText $hint -ResultType 'ParameterValue' -ToolTip 'PythonCore version to install (latest matching release).'))
            }
        }
    }

    if ($results.Count -eq 0) {
        $placeholder = if ($Command -eq 'list') { '<FILTER>' } else { '<TAG>' }
        return @(Get-PyPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder $placeholder -ToolTip $toolTip)
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

function Get-PyValueCompletionList {
    param(
        [string]$ValueKind,
        [string]$CurrentWord,
        [string]$AttachedPrefix = ''
    )

    switch ($ValueKind) {
        'ListFormat' {
            $formats = @(
                @{ Name = 'table';        Description = 'Lists as a user-friendly table.' }
                @{ Name = 'csv';          Description = 'List as a comma-separated value table.' }
                @{ Name = 'json';         Description = 'Lists as a single JSON object.' }
                @{ Name = 'jsonl';        Description = 'Lists as JSON on each line.' }
                @{ Name = 'id';           Description = 'Lists the runtime ID.' }
                @{ Name = 'exe';          Description = 'Lists the main executable path.' }
                @{ Name = 'prefix';       Description = 'Lists the prefix directory.' }
                @{ Name = 'url';          Description = 'Lists the original source URL.' }
                @{ Name = 'legacy';       Description = 'List runtimes using the old format.' }
                @{ Name = 'legacy-paths'; Description = 'List runtime paths using the old format.' }
                @{ Name = 'formats';      Description = 'List the available list formats.' }
                @{ Name = 'config';       Description = 'List the current config.' }
            )

            $results = New-Object System.Collections.Generic.List[object]
            foreach ($format in $formats) {
                if (Test-PyStartsWith -Candidate $format.Name -Prefix $CurrentWord) {
                    [void]$results.Add((New-PyCompletionResult -CompletionText ($AttachedPrefix + $format.Name) -ResultType 'ParameterValue' -ToolTip $format.Description -ListItemText $format.Name))
                }
            }

            if ($results.Count -eq 0) {
                return @(Get-PyPlaceholderCompletions -CurrentWord ($AttachedPrefix + $CurrentWord) -Placeholder ($AttachedPrefix + '<format>') -ToolTip 'List format.')
            }

            return @($results.ToArray())
        }
        'ConfigPath' {
            return @(Get-PyPathCompletions -CurrentWord $CurrentWord -Placeholder '<config.json>' -AttachedPrefix $AttachedPrefix)
        }
        'DirectoryPath' {
            return @(Get-PyPathCompletions -CurrentWord $CurrentWord -Placeholder '<directory>' -AttachedPrefix $AttachedPrefix -ContainersOnly)
        }
        'Uri' {
            return @(Get-PyPlaceholderCompletions -CurrentWord ($AttachedPrefix + $CurrentWord) -Placeholder ($AttachedPrefix + '<URI>') -ToolTip 'Index URL or index.json path.')
        }
    }

    @()
}

function Get-PyTailCompletions {
    param([string]$CurrentWord)

    if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and -not $CurrentWord.StartsWith('-')) {
        return @(Get-PyPathCompletions -CurrentWord $CurrentWord -Placeholder '<script-arg-path>')
    }

    @(Get-PyTerminalCompletions -CurrentWord $CurrentWord -Placeholder '<script-arg>' -ToolTip 'Launcher option completion stops after the script operand.')
}

function Get-PyLaunchSlotCompletionList {
    param(
        [string]$CurrentWord,
        [string]$Flavor,
        [bool]$RuntimeSelected,
        [object[]]$OptionSpecs,
        [bool]$IncludeCommands
    )

    $results = New-Object System.Collections.Generic.List[object]

    if ($CurrentWord.StartsWith('-V:', [System.StringComparison]::Ordinal)) {
        return @(Get-PyVCompletions -CurrentWord $CurrentWord)
    }

    if ([string]::Equals($CurrentWord, '-V', [System.StringComparison]::Ordinal)) {
        return @(New-PyCompletionResult -CompletionText '-V:' -ResultType 'ParameterName' -ToolTip 'Select a runtime by tag or by COMPANY\TAG.')
    }

    if ($RuntimeSelected) {
        # Only one selector is accepted; what follows are regular Python options or the script.
        if ($CurrentWord.StartsWith('-', [System.StringComparison]::Ordinal)) {
            return @(Get-PyTerminalCompletions -CurrentWord $CurrentWord -Placeholder '<python-option>' -ToolTip 'Regular Python interpreter option (see python -h).')
        }

        return @(Get-PyPathCompletions -CurrentWord $CurrentWord -Placeholder '<script-path>')
    }

    if ($CurrentWord.StartsWith('-', [System.StringComparison]::Ordinal)) {
        foreach ($item in @(Get-PyOptionCompletionList -Specs $OptionSpecs -CurrentWord $CurrentWord)) {
            [void]$results.Add($item)
        }

        if ((Test-PyVersionSelectorPrefix -Token $CurrentWord) -or $CurrentWord -eq '-') {
            foreach ($item in @(Get-PyVersionSelectorCompletions -CurrentWord $CurrentWord -Flavor $Flavor)) {
                [void]$results.Add($item)
            }
        }

        if ($results.Count -eq 0) {
            if ($Flavor -eq 'manager') {
                # Global options must follow a command; anything else here is passed to python.
                return @(Get-PyTerminalCompletions -CurrentWord $CurrentWord -Placeholder '<python-option>' -ToolTip 'Regular Python interpreter option (see python -h).')
            }

            return @(Get-PyTerminalCompletions -CurrentWord $CurrentWord -Placeholder '<launcher-arg>' -ToolTip 'Launcher-specific argument slot.')
        }

        return @(Get-PyUniqueResults -Results $results.ToArray())
    }

    if ([string]::IsNullOrWhiteSpace($CurrentWord)) {
        foreach ($item in @(Get-PyOptionCompletionList -Specs $OptionSpecs -CurrentWord $CurrentWord)) {
            [void]$results.Add($item)
        }
    }

    if ($IncludeCommands) {
        foreach ($commandSpec in @(Get-PyCommandTable)) {
            if (Test-PyStartsWith -Candidate $commandSpec.Name -Prefix $CurrentWord) {
                [void]$results.Add((New-PyCompletionResult -CompletionText $commandSpec.Name -ResultType 'ParameterValue' -ToolTip $commandSpec.Description))
            }
        }
    }

    foreach ($item in @(Get-PyPathCompletions -CurrentWord $CurrentWord -Placeholder '<script-path>')) {
        if ($results.Count -gt 0 -and [string]::Equals($item.CompletionText, $CurrentWord, [System.StringComparison]::Ordinal)) {
            # Skip the echo placeholder when a command name already matched.
            continue
        }

        [void]$results.Add($item)
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

function Complete-Py {
    param(
        [string]$wordToComplete,
        [System.Management.Automation.Language.CommandAst]$commandAst,
        [int]$cursorPosition
    )

    $flavor = Get-PyLauncherFlavor
    $commandLineState = Get-PyCommandLineState -CommandAst $commandAst -CursorPosition $cursorPosition
    $argumentState = Get-PyArgumentState -CommandLineState $commandLineState
    $context = Get-PyCompletionContext -ArgumentsBeforeCurrent $argumentState.ArgumentsBeforeCurrent -Flavor $flavor
    $currentWord = if ($null -eq $argumentState.CurrentArgument) { '' } else { $argumentState.CurrentArgument }
    $optionSpecs = @(Get-PyContextOptionTable -Flavor $flavor -Command $context.Command)

    if ($context.TerminalMode -eq 'LauncherTerminal') {
        return @(Get-PyTerminalCompletions -CurrentWord $currentWord -Placeholder '<complete>' -ToolTip 'No further launcher arguments are valid after this terminal launcher option.')
    }

    if ($context.TerminalMode -eq 'ScriptTail') {
        return @(Get-PyTailCompletions -CurrentWord $currentWord)
    }

    # Attached --option=value form (the idiom the tool's own help uses: -f=exe, --target=.\runtime).
    if ($currentWord -match '^(?<option>--?[^=:]+)=(?<value>.*)$') {
        $optionMap = Get-PyOptionMap -Specs $optionSpecs
        $optionName = $Matches.option
        if ($optionMap.ContainsKey($optionName) -and -not [string]::IsNullOrWhiteSpace($optionMap[$optionName].ValueKind)) {
            return @(Get-PyValueCompletionList -ValueKind $optionMap[$optionName].ValueKind -CurrentWord $Matches.value -AttachedPrefix ($optionName + '='))
        }
    }

    if (-not [string]::IsNullOrEmpty($context.PendingValueKind)) {
        return @(Get-PyValueCompletionList -ValueKind $context.PendingValueKind -CurrentWord $currentWord)
    }

    if ($flavor -ne 'manager' -or [string]::IsNullOrEmpty($context.Command) -or $context.Command -eq 'exec') {
        return @(Get-PyLaunchSlotCompletionList -CurrentWord $currentWord -Flavor $flavor -RuntimeSelected $context.RuntimeSelected -OptionSpecs $optionSpecs -IncludeCommands ($flavor -eq 'manager' -and [string]::IsNullOrEmpty($context.Command)))
    }

    # install | list | uninstall | help
    if ($currentWord.StartsWith('-', [System.StringComparison]::Ordinal)) {
        $results = @(Get-PyOptionCompletionList -Specs $optionSpecs -CurrentWord $currentWord)
        if ($results.Count -eq 0) {
            return @(Get-PyTerminalCompletions -CurrentWord $currentWord -Placeholder '<option>' -ToolTip "Option for 'py $($context.Command)'.")
        }

        return $results
    }

    $results = New-Object System.Collections.Generic.List[object]
    if ([string]::IsNullOrWhiteSpace($currentWord)) {
        foreach ($item in @(Get-PyOptionCompletionList -Specs $optionSpecs -CurrentWord $currentWord)) {
            [void]$results.Add($item)
        }
    }

    foreach ($item in @(Get-PyTagCompletionList -CurrentWord $currentWord -Command $context.Command)) {
        [void]$results.Add($item)
    }

    @(Get-PyUniqueResults -Results $results.ToArray())
}

Register-ArgumentCompleter -Native -CommandName @('py', 'py.exe') -ScriptBlock {
    param($wordToComplete, $commandAst, $cursorPosition)

    Complete-Py -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition
}