Private/UI/Write-PSMBMenuHeader.ps1

function Get-PSMBHeaderModel {
    <#
    .SYNOPSIS
        Builds the interactive header content model from module metadata.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param()

    if (-not $script:PSMBHeaderModelCache) {
        $moduleVersion = '0.1.0'

        try {
            $manifestPath = Join-Path -Path $PSScriptRoot -ChildPath '../../PSModuleBrowser.psd1'
            if (Test-Path -Path $manifestPath -PathType Leaf) {
                $manifest = Import-PowerShellDataFile -Path $manifestPath
                if ($manifest.ModuleVersion) {
                    $moduleVersion = [string]$manifest.ModuleVersion
                }
            }
        }
        catch { }

        $script:PSMBHeaderModelCache = [PSCustomObject]@{
            Title = 'PSModuleBrowser'
            Subtitle = 'Browse, Search & Install PowerShell Modules'
            VersionText = ('v{0}' -f $moduleVersion)
            RepositoryText = 'github.com/PSModuleBrowser/PSModuleBrowser'
        }
    }

    return $script:PSMBHeaderModelCache
}

function Get-PSMBHeroLines {
    <#
    .SYNOPSIS
        Returns ASCII art hero lines for the header.
    .PARAMETER Premium
        When set, returns Unicode block art for PS 7+ terminals.
    #>

    [CmdletBinding()]
    [OutputType([string[]])]
    param(
        [Parameter()]
        [switch]$Premium
    )

    if ($Premium) {
        return @(
            ' ____ ____ __ __ ____ '
            '| _ \/ ___|| \/ || __ ) '
            '| |_) \___ \| |\/| || _ \ '
            '| __/ ___) | | | || |_) |'
            '|_| |____/|_| |_||____/ '
        )
    }
    else {
        return @(
            ' ____ ____ __ __ ____ '
            '| _ \/ ___|| \/ || __ )'
            '| |_) \___ \| |\/| || _ \'
            '| __/ ___) | | | || |_) |'
            '|_| |____/|_| |_||____/'
        )
    }
}

function Write-PSMBMenuHeader {
    <#
    .SYNOPSIS
        Displays the PSModuleBrowser interactive console banner.
    .DESCRIPTION
        Renders a premium box-drawn header with gradient title on PS 7+,
        or a plain ASCII border header in hosts without arrow-key support.
    #>

    [CmdletBinding()]
    param()

    $headerModel = Get-PSMBHeaderModel
    if (Test-PSMBArrowKeySupport) {
        Write-PSMBMenuHeaderPremium -HeaderModel $headerModel
    }
    else {
        Write-PSMBMenuHeaderClassic -HeaderModel $headerModel
    }
}

function Write-PSMBMenuHeaderPremium {
    <#
    .SYNOPSIS
        Premium box-drawn header with gradient colors for PS 7+.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$HeaderModel
    )

    $palette = Get-PSMBColorPalette
    $esc = [char]27
    $reset = "${esc}[0m"

    $innerWidth = Get-PSMBConsoleInnerWidth
    $fitText = {
        param([string]$Text)
        if ($null -eq $Text) { return '' }
        if ($Text.Length -le $innerWidth) { return $Text }
        if ($innerWidth -le 3) { return $Text.Substring(0, [Math]::Max(0, $innerWidth)) }
        return $Text.Substring(0, $innerWidth - 3) + '...'
    }

    # Amber/Gold → Rose/Pink gradient
    $amberRGB = @(168, 213, 162)
    $roseRGB = @(94, 234, 212)
    $dimRGB = @(120, 113, 108)
    $tealRGB = @(94, 234, 212)

    # Top border
    $topLine = Get-PSMBGradientLine -Character ([char]0x2500) -Length $innerWidth -StartRGB $amberRGB -EndRGB $roseRGB
    Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x256D), $topLine, ([char]0x256E)) -NoNewline
    Write-Host $palette.Reset

    # Empty line
    $emptyInner = ' ' * $innerWidth
    Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x2502), $emptyInner, ([char]0x2502)) -NoNewline
    Write-Host $palette.Reset

    # ASCII art title (centered with gradient)
    $heroArtLines = Get-PSMBHeroLines -Premium
    $heroArtWidth = ($heroArtLines | Measure-Object -Property Length -Maximum).Maximum
    foreach ($artLine in $heroArtLines) {
        $artNormalized = $artLine.PadRight($heroArtWidth)
        $artClipped = & $fitText $artNormalized
        $artPad = $innerWidth - $artClipped.Length
        $artLeft = [Math]::Floor($artPad / 2)
        $artRight = $artPad - $artLeft
        $gradientArt = Get-PSMBGradientString -Text $artClipped -StartRGB $amberRGB -EndRGB $roseRGB -Prefix "${esc}[1m"
        Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $artLeft), $gradientArt, (' ' * $artRight), $palette.Surface, ([char]0x2502)) -NoNewline
        Write-Host $palette.Reset
    }

    # Brand line
    $brandText = & $fitText $HeaderModel.Title
    $brandPad = $innerWidth - $brandText.Length
    $brandLeft = [Math]::Floor($brandPad / 2)
    $brandRight = $brandPad - $brandLeft
    $gradientBrand = Get-PSMBGradientString -Text $brandText -StartRGB $amberRGB -EndRGB $roseRGB -Prefix "${esc}[1m"
    Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $brandLeft), $gradientBrand, (' ' * $brandRight), $palette.Surface, ([char]0x2502)) -NoNewline
    Write-Host $palette.Reset

    # Subtitle (centered, gradient dim to teal, italic)
    $subtitleText = & $fitText $HeaderModel.Subtitle
    $subPad = $innerWidth - $subtitleText.Length
    $subLeft = [Math]::Floor($subPad / 2)
    $subRight = $subPad - $subLeft
    $gradientSub = Get-PSMBGradientString -Text $subtitleText -StartRGB $dimRGB -EndRGB $tealRGB -Prefix "${esc}[3m"
    Write-Host (' {0}{1}{2}{3}{4}{5}{6}' -f $palette.Surface, ([char]0x2502), (' ' * $subLeft), $gradientSub, (' ' * $subRight), $palette.Surface, ([char]0x2502)) -NoNewline
    Write-Host $palette.Reset

    # Version + repo line
    $metaText = & $fitText ('{0} | {1}' -f $HeaderModel.VersionText, $HeaderModel.RepositoryText)
    $metaPad = $innerWidth - $metaText.Length
    $metaLeft = [Math]::Floor($metaPad / 2)
    $metaRight = $metaPad - $metaLeft
    Write-Host (' {0}{1}{2}{3}{4}{5}{6}{7}' -f $palette.Surface, ([char]0x2502), (' ' * $metaLeft), $palette.Dim, $metaText, (' ' * $metaRight), $palette.Surface, ([char]0x2502)) -NoNewline
    Write-Host $palette.Reset

    # Empty line
    Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x2502), $emptyInner, ([char]0x2502)) -NoNewline
    Write-Host $palette.Reset

    # Separator (continues the box — used before menu content)
    $sepLine = Get-PSMBGradientLine -Character ([char]0x2500) -Length $innerWidth -StartRGB $amberRGB -EndRGB $roseRGB
    Write-Host (' {0}{1}{2}{3}' -f $palette.Surface, ([char]0x251C), $sepLine, ([char]0x2524)) -NoNewline
    Write-Host $palette.Reset
}

function Write-PSMBMenuHeaderClassic {
    <#
    .SYNOPSIS
        Classic ASCII header for non-interactive hosts.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [pscustomobject]$HeaderModel
    )

    $maxWidth = [Math]::Min((Get-PSMBConsoleInnerWidth), 100)
    if ($maxWidth -lt 40) { $maxWidth = 40 }

    $fitText = {
        param([string]$Text)
        if ($null -eq $Text) { return '' }
        if ($Text.Length -le $maxWidth) { return $Text }
        if ($maxWidth -le 3) { return $Text.Substring(0, [Math]::Max(0, $maxWidth)) }
        return $Text.Substring(0, $maxWidth - 3) + '...'
    }

    $centerText = {
        param([string]$Text)
        $clipped = & $fitText $Text
        if ($clipped.Length -ge $maxWidth) { return $clipped }
        $left = [Math]::Floor(($maxWidth - $clipped.Length) / 2)
        $right = $maxWidth - $clipped.Length - $left
        return (' ' * $left) + $clipped + (' ' * $right)
    }

    $heroArtLines = Get-PSMBHeroLines
    $heroArtWidth = ($heroArtLines | Measure-Object -Property Length -Maximum).Maximum
    $lines = [System.Collections.ArrayList]::new()
    foreach ($heroLine in $heroArtLines) {
        $null = $lines.Add((& $centerText $heroLine.PadRight($heroArtWidth)))
    }
    $null = $lines.Add((& $centerText $HeaderModel.Title))
    $null = $lines.Add((& $centerText $HeaderModel.Subtitle))
    $null = $lines.Add('')
    $null = $lines.Add((& $centerText ('{0} | {1}' -f $HeaderModel.VersionText, $HeaderModel.RepositoryText)))

    $border = '=' * ($maxWidth + 2)
    $shadowBorder = '-' * ($maxWidth + 2)

    Write-Host ''
    Write-Host (' {0}' -f $shadowBorder) -ForegroundColor DarkGray
    Write-Host (' {0}' -f $border) -ForegroundColor Yellow
    foreach ($line in $lines) {
        Write-Host (' |{0}|' -f $line) -ForegroundColor Yellow
    }
    Write-Host (' {0}' -f $border) -ForegroundColor Yellow
    Write-Host (' {0}' -f $shadowBorder) -ForegroundColor DarkGray
    Write-Host ''
}