completers/wsb_completer/wsb_completer.ps1

<#
.SYNOPSIS
    Registers a native PowerShell argument completer for wsb.

.DESCRIPTION
    Provides a static-first native completer for `wsb` and `wsb.exe`.

    The completer covers:
    - top-level commands and aliases
    - command-specific options
    - inline `--option=value` completion for selected option kinds
    - safe local discovery of running sandbox IDs via `wsb list --raw`
    - local directory completion for `--host-path`
    - placeholder-driven completion for free-form config, command, and sandbox-path slots

    The script keeps its top level compatible with `Import-CompleterScript`.
#>


Set-StrictMode -Version Latest

function New-WsbCompletionResult {
    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 New-WsbOptionSpec {
    param(
        [string[]]$Tokens,
        [string]$Description,
        [string]$ValueKind,
        [string]$CompletionText
    )

    foreach ($token in @($Tokens)) {
        [pscustomobject]@{
            Token          = $token
            Description    = $Description
            ValueKind      = $ValueKind
            CompletionText = if ([string]::IsNullOrWhiteSpace($CompletionText)) { $token } else { $CompletionText }
        }
    }
}

function New-WsbCommandSpec {
    param(
        [string[]]$Names,
        [string]$Description,
        [object[]]$Options
    )

    [pscustomobject]@{
        Names       = @($Names)
        Canonical   = $Names[0]
        Description = $Description
        Options     = @($Options)
    }
}

function Get-WsbMetadata {
    if (Get-Variable -Name WsbMetadata -Scope Script -ErrorAction Ignore) {
        return $script:WsbMetadata
    }

    # Every subcommand's --help lists only --raw and -?/-h/--help; --version is
    # accepted at the root only.
    $sharedOptions = @(
        New-WsbOptionSpec -Tokens @('--raw') -Description 'Format output as JSON.'
        New-WsbOptionSpec -Tokens @('-?', '-h', '--help') -Description 'Show help.'
    )
    $globalOptions = @(
        $sharedOptions
        New-WsbOptionSpec -Tokens @('--version') -Description 'Show version information.'
    )

    $commands = @(
        New-WsbCommandSpec -Names @('StartSandbox', 'start') -Description 'Start an instance of Windows Sandbox.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            New-WsbOptionSpec -Tokens @('-c', '--config') -Description 'Formatted config string used to create the Windows Sandbox environment.' -ValueKind 'ConfigString'
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('ListRunningSandboxes', 'list') -Description 'List the IDs of all running Windows Sandbox environments.' -Options @(
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('Execute', 'exec') -Description 'Execute a command in the running Windows Sandbox environment.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            New-WsbOptionSpec -Tokens @('-c', '--command') -Description 'The command to execute within Windows Sandbox.' -ValueKind 'CommandString'
            New-WsbOptionSpec -Tokens @('-d', '--working-directory') -Description 'Directory inside Windows Sandbox to execute the command in.' -ValueKind 'SandboxPath'
            New-WsbOptionSpec -Tokens @('-r', '--run-as') -Description 'User context to run the command as.' -ValueKind 'RunAs'
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('ShareFolder', 'share') -Description 'Share a host folder into the Windows Sandbox session.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            New-WsbOptionSpec -Tokens @('-f', '--host-path') -Description 'Host folder path to share.' -ValueKind 'HostDirectoryPath'
            New-WsbOptionSpec -Tokens @('-s', '--sandbox-path') -Description 'Destination path inside Windows Sandbox.' -ValueKind 'SandboxPath'
            New-WsbOptionSpec -Tokens @('-w', '--allow-write') -Description 'Allow writes from the sandbox to the shared host folder.'
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('StopSandbox', 'stop') -Description 'Terminate a running Windows Sandbox.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('ConnectToSandbox', 'connect') -Description 'Start a remote session for a Windows Sandbox environment.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            $sharedOptions
        )
        New-WsbCommandSpec -Names @('GetIpAddress', 'ip') -Description 'Get the IP address of the Windows Sandbox environment.' -Options @(
            New-WsbOptionSpec -Tokens @('--id') -Description 'ID of the Windows Sandbox environment.' -ValueKind 'SandboxId'
            $sharedOptions
        )
    )

    $commandLookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase)
    $commandSuggestions = New-Object System.Collections.Generic.List[object]
    foreach ($command in $commands) {
        foreach ($name in $command.Names) {
            $commandLookup[$name] = $command
            [void]$commandSuggestions.Add([pscustomobject]@{ CompletionText = $name; ToolTip = $command.Description })
        }
    }

    $script:WsbMetadata = [pscustomobject]@{
        GlobalOptions       = @($globalOptions)
        Commands            = @($commands)
        CommandLookup       = $commandLookup
        CommandSuggestions  = @($commandSuggestions.ToArray())
        RunAsValues         = @('ExistingLogin', 'System')
    }

    $script:WsbMetadata
}

function Get-WsbTokenState {
    param(
        [string]$Line,
        [int]$CursorPosition
    )

    if ($null -eq $Line) {
        $Line = ''
    }

    $safeCursor = [Math]::Min([Math]::Max($CursorPosition, 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-WsbArgumentsFromTokenState {
    param([pscustomobject]$TokenState)

    $tokensBeforeCurrent = @($TokenState.TokensBeforeCurrent)
    $currentArgument = if ($null -eq $TokenState.CurrentToken) { '' } else { $TokenState.CurrentToken }

    if ($tokensBeforeCurrent.Count -gt 0) {
        $argumentsBeforeCurrent = @($tokensBeforeCurrent | Select-Object -Skip 1)
    } else {
        $argumentsBeforeCurrent = @()
    }

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

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

function Get-WsbRunningSandboxIds {
    # Sandbox IDs are cached for 30 seconds whether or not the probe found any,
    # so a slow or stopped Windows Sandbox service is asked at most twice a
    # minute rather than on every keystroke.
    if (Get-Variable -Name WsbSandboxIdCache -Scope Script -ErrorAction Ignore) {
        $cache = $script:WsbSandboxIdCache
        if (((Get-Date) - $cache.UpdatedAt).TotalSeconds -lt 30) {
            return @($cache.Values)
        }

        $commandPath = $cache.CommandPath
    } else {
        $command = Get-Command -Name wsb.exe -ErrorAction SilentlyContinue
        $commandPath = if ($command) { $command.Source } else { $null }
    }

    $values = @()
    if ($commandPath) {
        # The child process runs with stdin closed and a 5 s bound so a hung
        # service cannot hang the completion thread with it.
        try {
            $startInfo = [System.Diagnostics.ProcessStartInfo]::new()
            $startInfo.FileName = $commandPath
            $startInfo.ArgumentList.Add('list')
            $startInfo.ArgumentList.Add('--raw')
            $startInfo.UseShellExecute = $false
            $startInfo.CreateNoWindow = $true
            $startInfo.RedirectStandardInput = $true
            $startInfo.RedirectStandardOutput = $true
            $startInfo.RedirectStandardError = $true
            $startInfo.StandardOutputEncoding = [System.Text.Encoding]::UTF8
            $startInfo.StandardErrorEncoding = [System.Text.Encoding]::UTF8

            $process = [System.Diagnostics.Process]::Start($startInfo)
            try {
                $process.StandardInput.Close()
                $outputTask = $process.StandardOutput.ReadToEndAsync()
                $errorTask = $process.StandardError.ReadToEndAsync()
                if ($process.WaitForExit(5000)) {
                    [void]$errorTask.Result
                    $jsonText = $outputTask.Result -replace '\e\[[0-9;?]*[ -/]*[@-~]', ''
                    if (-not [string]::IsNullOrWhiteSpace($jsonText)) {
                        $parsed = $jsonText | ConvertFrom-Json -ErrorAction Stop
                        if ($parsed.WindowsSandboxEnvironments) {
                            foreach ($entry in @($parsed.WindowsSandboxEnvironments)) {
                                if ($entry.Id) {
                                    $values += [string]$entry.Id
                                }
                            }
                        }
                    }
                } else {
                    $process.Kill()
                }
            } finally {
                $process.Dispose()
            }
        } catch {
            Write-Debug ('wsb completer: sandbox discovery failed: ' + $_.Exception.Message)
            $values = @()
        }
    }

    $values = @($values | Sort-Object -Unique)
    $script:WsbSandboxIdCache = [pscustomobject]@{ UpdatedAt = Get-Date; Values = $values; CommandPath = $commandPath }
    @($values)
}

function Remove-WsbOuterQuotes {
    param([string]$Value)

    if ([string]::IsNullOrEmpty($Value)) {
        return ''
    }

    if ($Value.Length -ge 2 -and (($Value.StartsWith('"') -and $Value.EndsWith('"')) -or ($Value.StartsWith("'") -and $Value.EndsWith("'")))) {
        return $Value.Substring(1, $Value.Length - 2)
    }

    $Value.TrimStart('"', "'")
}

function ConvertTo-WsbQuotedValue {
    param(
        [string]$Value,
        [bool]$AlwaysQuote = $false
    )

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

    if (($AlwaysQuote -or $Value -match '\s') -and -not ($Value.StartsWith('"') -and $Value.EndsWith('"'))) {
        $escaped = $Value.Replace('`', '``').Replace('"', '`"')
        return '"' + $escaped + '"'
    }

    $Value
}

function Get-WsbOptionLookup {
    param([object[]]$Options)

    $lookup = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($option in @($Options)) {
        $lookup[$option.Token] = $option
    }

    $lookup
}

function Get-WsbActiveCommand {
    param([string[]]$ArgumentsBeforeCurrent)

    $metadata = Get-WsbMetadata
    foreach ($token in @($ArgumentsBeforeCurrent)) {
        if ($token.StartsWith('-')) {
            continue
        }

        if ($metadata.CommandLookup.ContainsKey($token)) {
            return $metadata.CommandLookup[$token]
        }
    }

    $null
}

function Get-WsbInlineOptionInfo {
    param(
        [string]$Token,
        [object[]]$Options
    )

    if ([string]::IsNullOrWhiteSpace($Token) -or -not $Token.StartsWith('--')) {
        return $null
    }

    $match = [regex]::Match($Token, '^(?<name>--[^=]+)=(?<value>.*)$')
    if (-not $match.Success) {
        return $null
    }

    $optionLookup = Get-WsbOptionLookup -Options $Options
    $name = $match.Groups['name'].Value
    if (-not $optionLookup.ContainsKey($name)) {
        return $null
    }

    [pscustomobject]@{
        Prefix = $name + '='
        Value  = $match.Groups['value'].Value
        Option = $optionLookup[$name]
    }
}

function Get-WsbOptionCompletions {
    param(
        [string]$CurrentWord,
        [object[]]$Options
    )

    $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
    foreach ($option in @($Options)) {
        if (-not $seen.Add($option.Token)) {
            continue
        }

        if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and
            -not $option.Token.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        New-WsbCompletionResult -CompletionText $option.CompletionText -ResultType 'ParameterName' -ToolTip $option.Description
    }
}

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

    foreach ($suggestion in (Get-WsbMetadata).CommandSuggestions) {
        if (-not [string]::IsNullOrWhiteSpace($CurrentWord) -and
            -not $suggestion.CompletionText.StartsWith($CurrentWord, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        New-WsbCompletionResult -CompletionText $suggestion.CompletionText -ResultType 'ParameterValue' -ToolTip $suggestion.ToolTip
    }
}

function Get-WsbPlaceholderCompletions {
    param(
        [string]$CurrentWord,
        [string]$Placeholder,
        [string]$ToolTip,
        [string]$Prefix = ''
    )

    if ([string]::IsNullOrWhiteSpace($CurrentWord)) {
        return @(
            New-WsbCompletionResult -CompletionText ($Prefix + $Placeholder) -ResultType 'ParameterValue' -ToolTip $ToolTip
        )
    }

    @(
        New-WsbCompletionResult -CompletionText ($Prefix + $CurrentWord) -ResultType 'ParameterValue' -ToolTip $ToolTip
    )
}

function Get-WsbEnumCompletions {
    param(
        [string]$CurrentWord,
        [string[]]$Values,
        [string]$ToolTip,
        [string]$Prefix = ''
    )

    $typedValue = Remove-WsbOuterQuotes -Value $CurrentWord
    $results = New-Object System.Collections.Generic.List[object]
    foreach ($value in @($Values)) {
        if (-not [string]::IsNullOrWhiteSpace($typedValue) -and
            -not $value.StartsWith($typedValue, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        [void]$results.Add((New-WsbCompletionResult -CompletionText ($Prefix + $value) -ResultType 'ParameterValue' -ToolTip $ToolTip))
    }

    if ($results.Count -eq 0) {
        $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Prefix + '<value>' } else { $Prefix + $CurrentWord }
        [void]$results.Add((New-WsbCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip $ToolTip))
    }

    @($results.ToArray())
}

function Get-WsbSandboxIdCompletions {
    param(
        [string]$CurrentWord,
        [string]$Prefix = ''
    )

    $typedValue = Remove-WsbOuterQuotes -Value $CurrentWord
    $results = New-Object System.Collections.Generic.List[object]
    foreach ($id in @(Get-WsbRunningSandboxIds)) {
        if (-not [string]::IsNullOrWhiteSpace($typedValue) -and
            -not $id.StartsWith($typedValue, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        [void]$results.Add((New-WsbCompletionResult -CompletionText ($Prefix + $id) -ResultType 'ParameterValue' -ToolTip 'Running Windows Sandbox ID.'))
    }

    if ($results.Count -eq 0) {
        $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Prefix + '<sandbox-id>' } else { $Prefix + $CurrentWord }
        [void]$results.Add((New-WsbCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip 'Running Windows Sandbox ID.'))
    }

    @($results.ToArray())
}

function Get-WsbDirectoryPathCompletions {
    param(
        [string]$CurrentWord,
        [string]$Prefix = ''
    )

    $typedValue = Remove-WsbOuterQuotes -Value $CurrentWord
    $alwaysQuote = $CurrentWord.StartsWith('"')
    $results = New-Object System.Collections.Generic.List[object]

    $parentPath = '.'
    $leaf = ''
    if (-not [string]::IsNullOrWhiteSpace($typedValue)) {
        if ($typedValue.EndsWith('\') -or $typedValue.EndsWith('/')) {
            $parentPath = $typedValue
        } else {
            try {
                $candidateParent = Split-Path -Path $typedValue -Parent
            } catch {
                $candidateParent = ''
            }

            if ([string]::IsNullOrWhiteSpace($candidateParent)) {
                $leaf = $typedValue
            } else {
                $parentPath = $candidateParent
                try {
                    $leaf = Split-Path -Path $typedValue -Leaf
                } catch {
                    $leaf = $typedValue
                }
            }
        }
    }

    try {
        $items = @(Get-ChildItem -LiteralPath $parentPath -Directory -ErrorAction Stop)
    } catch {
        $items = @()
    }

    foreach ($item in $items) {
        if (-not [string]::IsNullOrWhiteSpace($leaf) -and
            -not $item.Name.StartsWith($leaf, [System.StringComparison]::OrdinalIgnoreCase)) {
            continue
        }

        $candidate = if ($parentPath -eq '.') { $item.Name } else { Join-Path -Path $parentPath -ChildPath $item.Name }
        if (-not ($candidate.EndsWith('\') -or $candidate.EndsWith('/'))) {
            $candidate += '\'
        }

        $completionText = ConvertTo-WsbQuotedValue -Value $candidate -AlwaysQuote $alwaysQuote
        [void]$results.Add((New-WsbCompletionResult -CompletionText ($Prefix + $completionText) -ResultType 'ParameterValue' -ToolTip $item.FullName))
    }

    if ($results.Count -eq 0) {
        $fallback = if ([string]::IsNullOrWhiteSpace($CurrentWord)) { $Prefix + '<host-path>' } else { $Prefix + $CurrentWord }
        [void]$results.Add((New-WsbCompletionResult -CompletionText $fallback -ResultType 'ParameterValue' -ToolTip 'Host directory path.'))
    }

    @($results.ToArray())
}

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

    switch ($ValueKind) {
        'SandboxId' {
            return @(Get-WsbSandboxIdCompletions -CurrentWord $CurrentWord -Prefix $Prefix)
        }
        'RunAs' {
            return @(Get-WsbEnumCompletions -CurrentWord $CurrentWord -Values (Get-WsbMetadata).RunAsValues -ToolTip 'User context for command execution.' -Prefix $Prefix)
        }
        'HostDirectoryPath' {
            return @(Get-WsbDirectoryPathCompletions -CurrentWord $CurrentWord -Prefix $Prefix)
        }
        'ConfigString' {
            return @(Get-WsbPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<config>' -ToolTip 'Formatted Windows Sandbox config string.' -Prefix $Prefix)
        }
        'CommandString' {
            return @(Get-WsbPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<command>' -ToolTip 'Command to execute inside Windows Sandbox.' -Prefix $Prefix)
        }
        'SandboxPath' {
            return @(Get-WsbPlaceholderCompletions -CurrentWord $CurrentWord -Placeholder '<sandbox-path>' -ToolTip 'Path inside Windows Sandbox.' -Prefix $Prefix)
        }
    }

    @()
}

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

    $completionText = if ([string]::IsNullOrEmpty($CurrentWord)) { ' ' } else { $CurrentWord }
    @(
        New-WsbCompletionResult -CompletionText $completionText -ResultType 'ParameterValue' -ToolTip 'No further arguments are valid after help.'
    )
}

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

    $metadata = Get-WsbMetadata
    $tokenState = Get-WsbTokenState -Line $commandAst.ToString() -CursorPosition ($cursorPosition - $commandAst.Extent.StartOffset)
    $argumentState = Get-WsbArgumentsFromTokenState -TokenState $tokenState
    $hasTrailingSpace = [string]::IsNullOrEmpty($wordToComplete)

    if ($hasTrailingSpace -and -not [string]::IsNullOrEmpty($argumentState.CurrentArgument)) {
        $currentWord = ''
        $argumentsBeforeCurrent = @($argumentState.ArgumentsBeforeCurrent + $argumentState.CurrentArgument)
    } else {
        $currentWord = if ($null -eq $argumentState.CurrentArgument) { '' } else { $argumentState.CurrentArgument }
        $argumentsBeforeCurrent = @($argumentState.ArgumentsBeforeCurrent)
    }

    $activeCommand = Get-WsbActiveCommand -ArgumentsBeforeCurrent $argumentsBeforeCurrent
    $commandIndex = -1
    if ($null -ne $activeCommand) {
        for ($i = 0; $i -lt $argumentsBeforeCurrent.Count; $i++) {
            if ($activeCommand.Names -contains $argumentsBeforeCurrent[$i]) {
                $commandIndex = $i
                break
            }
        }
    }

    $argumentsAfterCommand = @(
        if ($commandIndex -ge 0 -and $commandIndex -lt ($argumentsBeforeCurrent.Count - 1)) {
            $argumentsBeforeCurrent[($commandIndex + 1)..($argumentsBeforeCurrent.Count - 1)]
        }
    )

    $availableOptions = if ($null -ne $activeCommand) { $activeCommand.Options } else { $metadata.GlobalOptions }
    $helpRequested = ($argumentsBeforeCurrent + $currentWord) | Where-Object { $_ -in @('-?', '-h', '--help') }
    if ($helpRequested) {
        return @(Get-WsbTerminalCompletions -CurrentWord $currentWord)
    }

    $optionLookup = Get-WsbOptionLookup -Options $availableOptions

    if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('--')) {
        $inline = Get-WsbInlineOptionInfo -Token $currentWord -Options $availableOptions
        if ($null -ne $inline -and -not [string]::IsNullOrWhiteSpace($inline.Option.ValueKind)) {
            return @(Get-WsbValueCompletions -ValueKind $inline.Option.ValueKind -CurrentWord $inline.Value -Prefix $inline.Prefix)
        }
    }

    if ($argumentsAfterCommand.Count -gt 0) {
        $previousToken = $argumentsAfterCommand[-1]
        if ($optionLookup.ContainsKey($previousToken) -and -not [string]::IsNullOrWhiteSpace($optionLookup[$previousToken].ValueKind)) {
            return @(Get-WsbValueCompletions -ValueKind $optionLookup[$previousToken].ValueKind -CurrentWord $currentWord)
        }
    } elseif ($commandIndex -lt 0 -and $argumentsBeforeCurrent.Count -gt 0) {
        $previousToken = $argumentsBeforeCurrent[-1]
        if ($optionLookup.ContainsKey($previousToken) -and -not [string]::IsNullOrWhiteSpace($optionLookup[$previousToken].ValueKind)) {
            return @(Get-WsbValueCompletions -ValueKind $optionLookup[$previousToken].ValueKind -CurrentWord $currentWord)
        }
    }

    if ($null -eq $activeCommand) {
        $results = New-Object System.Collections.Generic.List[object]
        if ([string]::IsNullOrWhiteSpace($currentWord) -or -not $currentWord.StartsWith('-')) {
            foreach ($commandCompletion in @(Get-WsbCommandCompletions -CurrentWord $currentWord)) {
                [void]$results.Add($commandCompletion)
            }
        }

        foreach ($optionCompletion in @(Get-WsbOptionCompletions -CurrentWord $currentWord -Options $metadata.GlobalOptions)) {
            [void]$results.Add($optionCompletion)
        }

        $seen = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
        foreach ($result in @($results.ToArray())) {
            if ($seen.Add($result.CompletionText)) {
                $result
            }
        }

        return
    }

    if (-not [string]::IsNullOrWhiteSpace($currentWord) -and $currentWord.StartsWith('-')) {
        return @(Get-WsbOptionCompletions -CurrentWord $currentWord -Options $availableOptions)
    }

    if ([string]::IsNullOrWhiteSpace($currentWord)) {
        return @(Get-WsbOptionCompletions -CurrentWord $currentWord -Options $availableOptions)
    }

    @()
}

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

    Complete-Wsb -wordToComplete $wordToComplete -commandAst $commandAst -cursorPosition $cursorPosition
}