Public/Select-DFPackage.ps1

#Requires -Version 7.0

function Select-DFPackage {
    <#
    .SYNOPSIS
        Fuzzy-browse packages with fzf. With a query: searches every catalog
        (like trifle) and previews each result's info card; Enter renders the
        full detail card. Without a query: browses all locally cached packages.
    .DESCRIPTION
        Query mode pre-renders each result's basic info card to a temp file so
        the fzf preview is instant — no subprocess module loads, no network
        while scrolling. The selection re-enters Find-DFPackage via its
        qualified source:packageId, which fetches full per-catalog details.

        Browse mode (no query) reads only local caches (scoop/winget indexes,
        cached web queries, the installed snapshot) so the list opens
        instantly — run Update-DFPackageCache (or any first trifle query) to
        populate it.
    .PARAMETER Query
        Search terms. When present, the list is live search results with a
        detail-card pipeline; when absent, the local-cache browser.
    .PARAMETER Categories
        Browse the full taxonomy vocabulary (every function/works-with value,
        live tool counts); Enter drills into the picked facet.
    .PARAMETER Category
        Search this specific function value directly — same preview/selection
        flow as -Query.
    .PARAMETER WorksWith
        Search this specific works-with value directly — same preview/selection
        flow as -Query. Set internally when -Categories recurses into a
        picked works-with row; also usable directly.
    .PARAMETER Source
        Restrict to packages known to these catalogs.
    .PARAMETER Readme
        After selection, also fetch and page the package readme.
    .PARAMETER GitInfo
        After selection, include GitHub stars/release/activity on the card.
    .EXAMPLE
        ftrifle zed
        Search all catalogs for 'zed', preview cards while scrolling, Enter
        shows the full detail card.
    .EXAMPLE
        ftrifle
        Browse all locally known packages; Enter renders the info card.
    .EXAMPLE
        ftrifle -Categories
        Pick a category or works-with value from the full vocabulary; Enter drills
        into that facet's tools with the same preview/selection flow as a query.
    .OUTPUTS
        None — the selection is rendered via Find-DFPackage.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Position = 0, ValueFromRemainingArguments)]
        [string[]]$Query,

        [switch]$Categories,

        [string]$Category,

        [string]$WorksWith,

        [ValidateSet('scoop', 'winget', 'choco', 'npm', 'pypi', 'crates', 'psgallery')]
        [string[]]$Source,

        [switch]$Readme,

        [switch]$GitInfo
    )

    if ($Categories) {
        $db = Get-DFCategoryDb
        if (-not $db.Raw) {
            Write-Warning 'DotForge: category database unavailable — run Update-DFCategoryDb.'
            return
        }
        $catLines = @($db.Raw.taxonomy.function | Sort-Object | ForEach-Object {
            "$_`t$(@($db.FacetIndex["function:$_"]).Count) tools`tfunction"
        })
        $wwLines = @($db.Raw.taxonomy.worksWith | Sort-Object | ForEach-Object {
            "$_`t$(@($db.FacetIndex["worksWith:$_"]).Count) tools`tworksWith"
        })
        $pickLines = @($catLines) + @('— browse by works-with —') + @($wwLines)

        $picked = Invoke-DFPicker -List { $pickLines }.GetNewClosure() `
            -Header 'Select a category or works-with facet' `
            -Delimiter "`t" -WithNth '1,2'
        if (-not $picked -or $picked -like '— browse by works-with —*') { return }

        $parts = $picked -split "`t"
        $facetArgs = ($parts[2] -eq 'worksWith') ? @{ WorksWith = $parts[0] } : @{ Category = $parts[0] }
        if ($Source) { $facetArgs.Source = $Source }
        Select-DFPackage @facetArgs -Readme:$Readme -GitInfo:$GitInfo
        return
    }

    if ($Query -or $Category -or $WorksWith) {
        $findArgs = if ($Category) { @{ Category = $Category; AsObject = $true } }
            elseif ($WorksWith) { @{ WorksWith = $WorksWith; AsObject = $true } }
            else { @{ Query = $Query; AsObject = $true } }
        if ($Source) { $findArgs.Source = $Source }
        $results = @(Find-DFPackage @findArgs)
        if (-not $results) {
            $what = if ($Category) { "category '$Category'" }
                elseif ($WorksWith) { "works-with '$WorksWith'" }
                else { "'$($Query -join ' ')'" }
            Write-Warning "DotForge: no matches for $what."
            return
        }

        # Pre-render each result's card for the fzf preview. The preview file
        # path is field 1 because fzf quotes {1} itself — a path assembled
        # around the placeholder would break on the inserted quotes.
        $previewDir = Join-Path ([System.IO.Path]::GetTempPath()) "dotforge-preview-$PID"
        New-DFDirectory $previewDir
        try {
            $lines = @(for ($i = 0; $i -lt $results.Count; $i++) {
                $info = $results[$i]
                $file = Join-Path $previewDir "$i.txt"
                (Format-DFToolInfoCard -Info $info -Color $true) -join "`n" | Set-Content -Path $file -Encoding UTF8
                $best = @($info.Sources) | Select-Object -First 1
                $qualifiedId = "$($best.Source):$($best.PackageId)"
                "$file`t$qualifiedId`t$($info.Name)`t$(@($info.Sources.Source) -join ',')`t$($info.Description)"
            })

            $previewCmd = $IsWindows ? 'type {1}' : 'cat {1}'

            $qualifiedId = Invoke-DFPicker -List { $lines }.GetNewClosure() `
                -Header 'Select package [Enter: full details]' `
                -Preview $previewCmd `
                -Delimiter "`t" -WithNth '3..' `
                -Parse { ($_ -split "`t")[1] }
        } finally {
            Remove-Item $previewDir -Recurse -Force -ErrorAction Ignore
        }
        if ($qualifiedId) {
            Find-DFPackage -Query $qualifiedId -Readme:$Readme -GitInfo:$GitInfo
        }
        return
    }

    $packages = @(Get-DFCatalogLocalPackages)
    if ($Source) {
        $packages = @($packages | Where-Object {
            @($_.Sources -split ',') | Where-Object { $_ -in $Source }
        })
    }
    if (-not $packages) {
        Write-Warning 'DotForge: no local catalog data yet — run Update-DFPackageCache or a first trifle query.'
        return
    }

    $browseLines = @($packages | ForEach-Object { "$($_.Name)`t$($_.Sources)`t$($_.Description)" })

    Invoke-DFPicker -List { $browseLines }.GetNewClosure() `
        -Header 'Select package [Enter: info card]' `
        -Delimiter "`t" -WithNth '1,3' `
        -Parse { ($_ -split "`t")[0] } `
        -Action { param($name) Find-DFPackage -Query $name }
}

Set-Alias -Name ftrifle -Value Select-DFPackage -Scope Global -Force