src/PowerShell/Picker.ps1

# cdp PowerShell domain: Picker.ps1
# Loaded by src/cdp.psm1; do not dot-source peer files.

function Get-CdpCurrentModule {
    Get-Module -Name cdp |
        Sort-Object -Property Version -Descending |
        Select-Object -First 1
}

function Get-CdpCurrentModuleVersion {
    $module = Get-CdpCurrentModule

    if ($module -and $module.Version) {
        return [version]$module.Version
    }

    return $null
}

function Get-CdpVersionText {
    $version = Get-CdpCurrentModuleVersion
    if ($version) {
        return $version.ToString()
    }

    return "unknown"
}

function Write-CdpBrandHeader {
    $versionText = Get-CdpVersionText
    $logo = @(
        ' _'
        ' ___ __| |_ __'
        ' / __/ _` | "_ \'
        '| (_| (_| | |_) |'
        ' \___\__,_| .__/'
        ' |_|'
    )

    Write-Host ""
    foreach ($line in $logo) {
        Write-Host $line -ForegroundColor Cyan
    }
    Write-Host "cdp v$versionText" -ForegroundColor Green
    Write-Host "fast project switching for PowerShell and WSL" -ForegroundColor Gray
    Write-Host ""
}

function Get-CdpPickerHeader {
    param(
        [Parameter(Mandatory = $true)]
        [int]$ShownProjectCount,

        [Parameter(Mandatory = $true)]
        [int]$TotalProjectCount,

        [Parameter(Mandatory = $true)]
        [string]$ConfigPath
    )

    $versionText = Get-CdpVersionText
    $projectText = if ($ShownProjectCount -eq $TotalProjectCount) {
        "$TotalProjectCount projects"
    } else {
        "$ShownProjectCount shown / $TotalProjectCount projects"
    }

    "cdp v$versionText | $projectText | enter to warp | $ConfigPath"
}

function Format-CdpAnsiText {
    param(
        [AllowNull()]
        [string]$Text,

        [Parameter(Mandatory = $true)]
        [string]$Code
    )

    $escape = [char]27
    "${escape}[${Code}m$Text${escape}[0m"
}

function ConvertTo-CdpPickerField {
    param(
        [AllowNull()]
        [string]$Value
    )

    if ($null -eq $Value) {
        return ""
    }

    $Value -replace "[`r`n`t]+", " "
}

function Test-CdpProjectPinned {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Project
    )

    $pinnedProperty = $Project.PSObject.Properties['pinned']
    return $null -ne $pinnedProperty -and $Project.pinned -eq $true
}

function Get-CdpProjectStringList {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Project,

        [Parameter(Mandatory = $true)]
        [string]$PropertyName
    )

    $property = $Project.PSObject.Properties[$PropertyName]
    if ($null -eq $property -or $null -eq $property.Value) {
        return @()
    }

    @($property.Value | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | ForEach-Object { [string]$_ })
}

function New-CdpPickerLine {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Project,

        [Parameter(Mandatory = $true)]
        [int]$Index,

        [Parameter(Mandatory = $false)]
        [string]$Profile
    )

    $resolution = Resolve-CdpProjectPath -Project $Project -Profile $Profile
    $rawName = ConvertTo-CdpPickerField -Value $Project.name
    $rawPath = ConvertTo-CdpPickerField -Value $Project.rootPath
    $resolvedPath = if ($resolution.ErrorCode) {
        "<invalid $($resolution.Source)>"
    } else {
        ConvertTo-CdpPickerField -Value $resolution.ResolvedPath
    }
    $displayIndex = Format-CdpAnsiText -Text ("{0,3}" -f $Index) -Code "38;5;242"
    $nameText = if (Test-CdpProjectPinned -Project $Project) { "[pin] $rawName" } else { $rawName }
    $displayName = Format-CdpAnsiText -Text $nameText -Code "1;38;5;81"
    $displayPath = Format-CdpAnsiText -Text $resolvedPath -Code "38;5;245"

    "{0}`t{1}`t{2}`t{3}`t{4}`t{5}" -f $Index, $rawName, $rawPath, $displayIndex, $displayName, $displayPath
}

function Get-CdpPickerPreviewContent {
    param(
        [Parameter(Mandatory = $true)]
        [object]$Project,

        [Parameter(Mandatory = $false)]
        [string]$Profile
    )

    $resolution = Resolve-CdpProjectPath -Project $Project -Profile $Profile
    $resolvedPath = [string]$resolution.ResolvedPath
    $pathExists = -not $resolution.ErrorCode -and (Test-Path -LiteralPath $resolvedPath)
    $pathState = if ($resolution.ErrorCode) { $resolution.ErrorMessage } elseif ($pathExists) { "path exists" } else { "path missing" }
    $gitPath = if ([string]::IsNullOrWhiteSpace($resolvedPath)) { '' } else { Join-Path $resolvedPath ".git" }
    $gitState = if ($pathExists -and (Test-Path -LiteralPath $gitPath)) {
        "git repo detected"
    } else {
        "git repo not detected"
    }

    @(
        "cdp project"
        "-----------"
        "name $($Project.name)"
        "path $resolvedPath"
        "raw $($resolution.RawPath)"
        "profile $($resolution.Profile) ($($resolution.Source))"
        "pin $(if (Test-CdpProjectPinned -Project $Project) { 'pinned' } else { 'not pinned' })"
        "alias $((Get-CdpProjectStringList -Project $Project -PropertyName 'aliases') -join ', ')"
        "tags $((Get-CdpProjectStringList -Project $Project -PropertyName 'tags') -join ', ')"
        ""
        "state $pathState"
        "git $gitState"
        ""
        "Enter switch to this project"
        "Esc cancel"
    )
}

function New-CdpPickerPreviewDirectory {
    param(
        [Parameter(Mandatory = $true)]
        [object[]]$Projects,

        [Parameter(Mandatory = $false)]
        [string]$Profile
    )

    $previewDir = Join-Path ([System.IO.Path]::GetTempPath()) "cdp-fzf-$PID-$([guid]::NewGuid().ToString('N'))"
    New-Item -ItemType Directory -Path $previewDir -Force | Out-Null

    $index = 1
    foreach ($project in $Projects) {
        $previewPath = Join-Path $previewDir "$index.txt"
        Get-CdpPickerPreviewContent -Project $project -Profile $Profile |
            Set-Content -Path $previewPath -Encoding UTF8
        $index++
    }

    @'
param(
    [Parameter(Mandatory = $false)]
    [string]$Index
)
 
$safeIndex = $Index -replace '[^0-9]', ''
if ([string]::IsNullOrWhiteSpace($safeIndex)) {
    "cdp project"
    "-----------"
    "preview unavailable"
    return
}
 
$previewPath = Join-Path $PSScriptRoot "$safeIndex.txt"
if (Test-Path -LiteralPath $previewPath) {
    Get-Content -LiteralPath $previewPath
    return
}
 
"cdp project"
"-----------"
"preview unavailable"
"missing: $previewPath"
'@
 | Set-Content -Path (Join-Path $previewDir "preview.ps1") -Encoding UTF8

    $previewDir
}

function Get-CdpPickerPreviewCommand {
    param(
        [Parameter(Mandatory = $true)]
        [string]$PreviewDir
    )

    $scriptPath = Join-Path $PreviewDir "preview.ps1"
    $escapedScriptPath = $scriptPath -replace '"', '\"'
    "powershell -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$escapedScriptPath`" {1}"
}

function Get-CdpFzfColorTheme {
    "fg:#cdd6f4,bg:-1,hl:#89dceb,fg+:#ffffff,bg+:#313244,hl+:#f5c2e7,prompt:#94e2d5,pointer:#f38ba8,marker:#a6e3a1,border:#89b4fa,header:#bac2de,info:#fab387"
}

function Get-CdpDisplayWidth {
    param([AllowNull()][string]$Text)
    if ($null -eq $Text) { return 0 }
    $width = 0
    foreach ($ch in $Text.ToCharArray()) {
        $code = [int]$ch
        if ($code -ge 0x1100 -and (
            ($code -ge 0x1100 -and $code -le 0x115F) -or
            ($code -ge 0x2E80 -and $code -le 0xA4CF) -or
            ($code -ge 0xAC00 -and $code -le 0xD7A3) -or
            ($code -ge 0xF900 -and $code -le 0xFAFF) -or
            ($code -ge 0xFE30 -and $code -le 0xFE4F) -or
            ($code -ge 0xFF00 -and $code -le 0xFF60) -or
            ($code -ge 0xFFE0 -and $code -le 0xFFE6)
        )) {
            $width += 2
        } else {
            $width += 1
        }
    }
    return $width
}

function Pad-CdpText {
    param([AllowNull()][string]$Text, [int]$Width)
    if ($null -eq $Text) { $Text = "" }
    $displayWidth = Get-CdpDisplayWidth $Text
    $padding = $Width - $displayWidth
    if ($padding -gt 0) {
        return $Text + (" " * $padding)
    }
    return $Text
}

function Limit-CdpText {
    param(
        [AllowNull()]
        [string]$Text,

        [Parameter(Mandatory = $true)]
        [int]$MaxLength
    )

    if ($null -eq $Text -or $Text -eq "") {
        return ""
    }

    if ((Get-CdpDisplayWidth $Text) -le $MaxLength) {
        return $Text
    }

    $result = ""
    $currentWidth = 0
    foreach ($ch in $Text.ToCharArray()) {
        $chWidth = if (([int]$ch) -ge 0x2E80) { 2 } else { 1 }
        if ($currentWidth + $chWidth -gt $MaxLength - 3) {
            break
        }
        $result += $ch
        $currentWidth += $chWidth
    }
    return $result + "..."
}