Private/Invoke-Win32ToolkitWingetWizard.ps1

function Invoke-Win32ToolkitWingetWizard {
    <#
    .SYNOPSIS
        Guided winget packaging wizard (Spectre): search → select → arch → template → options →
        summary → confirm → run. A thin front-end over Invoke-Win32Toolkit (driven non-interactively).
        See knowledge-base/designs/tui.md.
    #>

    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$BasePath)

    Clear-Host
    Write-SpectreRule -Title 'Package an app from winget' -Color Blue

    if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
        Format-SpectrePanel -Data "[red]winget is not available on this PC.[/]`nInstall 'App Installer' from the Microsoft Store, or use a manual app instead." -Header 'winget missing' -Border Rounded -Color Red
        Read-SpectrePause -Message 'Press any key to return' -AnyKey | Out-Null
        return
    }

    # 1. Search term
    $term = Read-SpectreText -Message 'Search winget for (e.g. "notepad++", "git")'
    if ([string]::IsNullOrWhiteSpace($term)) { return }

    # 2. Search + select
    Write-SpectreHost "[grey]Searching winget for '$(Get-SpectreEscapedText -Text ([string]$term))'…[/]"
    $apps = @(Search-WingetApps -SearchTerm $term | Where-Object { $_.Source -ne 'msstore' })
    if ($apps.Count -eq 0) {
        Format-SpectrePanel -Data "No winget results for [yellow]$(Get-SpectreEscapedText -Text ([string]$term))[/]." -Header 'Nothing found' -Border Rounded -Color Yellow
        Read-SpectrePause -Message 'Press any key to return' -AnyKey | Out-Null
        return
    }
    # Use plain string choices + a lookup (robust regardless of how selection returns objects).
    # Choice labels render as Spectre markup — escape names that may contain [ ]; key the lookup
    # by the same (escaped) label so the returned selection maps back to the app.
    $labelMap = [ordered]@{}
    $labels = foreach ($a in $apps) {
        $label = Get-SpectreEscapedText -Text ('{0} ({1}) v{2}' -f $a.Name, $a.Id, $a.Version)
        $labelMap[$label] = $a
        $label
    }
    $chosen = Read-SpectreSelection -Message 'Select the application (type to filter)' -Choices @($labels) -Color Blue -EnableSearch -PageSize 15
    $picked = $labelMap[$chosen]
    if (-not $picked -or [string]::IsNullOrWhiteSpace($picked.Id)) {
        Write-SpectreHost '[yellow]No application selected.[/]'
        Read-SpectrePause -Message 'Press any key to return' -AnyKey | Out-Null
        return
    }

    # 3. Architecture (Get-WingetAppDetails may return a single string; force an array; 6>$null quiets it)
    $archs = @(@(Get-WingetAppDetails -AppId $picked.Id 6>$null) | Where-Object { $_ -in @('x64', 'x86', 'arm64') })
    if ($archs.Count -eq 0) { $archs = @('x64', 'x86', 'arm64') }
    $arch = if ($archs.Count -eq 1) { $archs[0] } else { Read-SpectreSelection -Message 'Target architecture' -Choices @($archs) -Color Blue }

    # 4. Template (client)
    $template = Get-Win32ToolkitTemplateChoice -BasePath $BasePath
    if ([string]::IsNullOrWhiteSpace($template)) { return }

    # 4b. Dependencies — apps Intune must install BEFORE this one (e.g. a VC++ redistributable).
    # Sourced from winget or an already-packaged project; stored as data in AppConfig.json.
    $deps = @()
    if (Read-SpectreConfirm -Message 'Does this app need another app installed FIRST (e.g. a VC++ redistributable)?' -DefaultAnswer 'n') {
        $deps = @(Show-Win32ToolkitDependencyPicker -BasePath $BasePath)
    }

    # 5. What to do after building. NOTE: the documentation capture ALWAYS runs, and both it and the
    # optional test follow the CONFIGURED backend — so name the real backend instead of hard-coding
    # "Windows Sandbox" (the TUI used to claim Sandbox while actually running in the Hyper-V VM).
    $bi = Get-Win32ToolkitBackendInfo
    if ($bi.FellBack) {
        Write-SpectreHost "[yellow]Hyper-V is selected but NOT ready — falling back to Windows Sandbox:[/] $(Get-SpectreEscapedText -Text ($bi.Reasons -join '; '))"
    }
    $testLabel = "Run an install/uninstall test in $($bi.Label)"
    $actions = @(Read-SpectreMultiSelection -Message 'After building, also… (space to toggle, enter to accept)' -Choices @(
            $testLabel
            'Package to .intunewin'
            'Publish to Intune'
        ) -Color Blue -AllowEmpty)
    $doTest    = $actions -contains $testLabel
    $doPackage = $actions -contains 'Package to .intunewin'
    $doPublish = $actions -contains 'Publish to Intune'

    # 6. Pre-flight summary
    $after = @()
    if ($doTest)    { $after += 'install/uninstall test' }
    if ($doPackage) { $after += 'package (.intunewin)' }
    if ($doPublish) { $after += 'PUBLISH to Intune' }
    $afterStr = if ($after) { $after -join ', ' } else { 'nothing (just build the project)' }
    $summary = @(
        "Application : $($picked.Name) (v$($picked.Version))"
        "Winget ID : $($picked.Id)"
        "Architecture : $arch"
        "Template : $template"
        "Base folder : $BasePath"
        "Test/capture : $($bi.Label)"
        "Depends on : $(if ($deps) { $deps -join ', ' } else { '(nothing)' })"
        "After build : $afterStr"
    ) -join "`n"
    Format-SpectrePanel -Data (Get-SpectreEscapedText -Text $summary) -Header 'Review' -Border Rounded -Color Blue

    # 7. Confirm (+ extra gate for publish)
    if (-not (Read-SpectreConfirm -Message 'Build this now?' -DefaultAnswer 'n')) {
        Write-SpectreHost '[yellow]Cancelled.[/]'; Read-SpectrePause -Message 'Press any key to return' -AnyKey | Out-Null; return
    }
    if ($doPublish -and -not (Read-SpectreConfirm -Message 'Publishing UPLOADS to your Intune tenant (you will sign in). Continue?' -DefaultAnswer 'n')) {
        $doPublish = $false
        Write-SpectreHost '[yellow]Publish skipped — will build/package only.[/]'
    }

    # 8. Run (Invoke shows its own progress; the capture/test run in the resolved backend)
    Clear-Host
    Write-SpectreRule -Title "Building $(Get-SpectreEscapedText -Text $picked.Name)…" -Color Blue
    $p = @{ Id = $picked.Id; Architecture = $arch; TemplateName = $template; BasePath = $BasePath; Force = $true }
    if ($deps)      { $p.DependsOn = $deps }
    if ($doTest)    { $p.RunTest = 'InstallUninstall' }
    if ($doPackage) { $p.PackageIntune = $true }
    if ($doPublish) { $p.PublishIntune = $true }
    try {
        Invoke-Win32Toolkit @p
        Format-SpectrePanel -Data "Finished [green]$(Get-SpectreEscapedText -Text $picked.Name)[/].`nUse [blue]Browse projects[/] to find it; review the messages above for details." -Header 'Done' -Border Rounded -Color Green
    }
    catch {
        Format-SpectrePanel -Data "[red]$(Get-SpectreEscapedText -Text $_.Exception.Message)[/]" -Header 'Something went wrong' -Border Rounded -Color Red
    }
    Read-SpectrePause -Message 'Press any key to return to the menu' -AnyKey | Out-Null
}