lib/Status.ps1

function Write-Status {
    <#
    .SYNOPSIS
        Writes a colored status line to the Information stream using
        ANSI escapes. Falls back to plain text when $script:UseColor is
        $false (set in the entry-point script based on
        $Host.UI.SupportsVirtualTerminal).

        Standardised log levels (correspond to the bracket marker
        callers should put at the start of $Message):

            Info [INFO] informational; both work in progress
                              and individual successful outcomes
            Warn [WARN] non-fatal anomaly
            Error [ERROR] unrecoverable failure
            Skip [SKIP] action deliberately not taken
                              (config-driven no-op, missing prereq)
            Debug [DEBUG] low-signal troubleshooting output;
                              always rendered (gray) so it's visible
                              on failures without needing a re-run
            Ok (none) end-of-run / overall-success summary;
                              green styling, no bracket marker.
                              Per-step successes are Info, not Ok.
            Header (none) section divider, no bracket marker
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Message,

        [ValidateSet('Info', 'Skip', 'Warn', 'Error', 'Header', 'Debug', 'Ok')]
        [string] $Level = 'Info'
    )

    if (-not $script:UseColor) {
        Write-Information $Message
        return
    }

    $esc   = [char]27
    $reset = "$esc[0m"
    $color = switch ($Level) {
        'Skip'    { "$esc[90m" }      # gray
        'Warn'    { "$esc[33m" }      # yellow
        'Error'   { "$esc[31m" }      # red
        'Header'  { "$esc[1;37m" }    # bold white
        'Debug'   { "$esc[90m" }      # gray
        'Ok'      { "$esc[32m" }      # green - reserved for end-of-run summaries
        default   { '' }              # Info: default terminal color
    }

    Write-Information ('{0}{1}{2}' -f $color, $Message, $reset)
}

function Write-Header {
    <#
    .SYNOPSIS
        Writes a three-line boxed section banner via Write-Status
        -Level Header, shaped like:

            (blank)
            ╔════════════════════════════════════════════╗
            ║ <Name> ║
            ╚════════════════════════════════════════════╝

        Box-drawing chars: U+2554/2557/255A/255D corners,
        U+2550 horizontal, U+2551 vertical. Width is the total visual
        width (default 60); the inside ($Width - 4) is padded with
        spaces so successive boxes line up regardless of name length.
        Names longer than that just spill past the right border -
        we keep the layout simple rather than wrapping or truncating.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string] $Name,

        [Parameter()]
        [int] $Width = 60
    )

    $h  = [string]([char]0x2550) * ($Width - 2)
    $tl = [char]0x2554; $tr = [char]0x2557
    $bl = [char]0x255A; $br = [char]0x255D
    $v  = [char]0x2551

    $top    = "${tl}${h}${tr}"
    $middle = ('{0} {1} {2}' -f $v, $Name.PadRight([Math]::Max(0, $Width - 4)), $v)
    $bottom = "${bl}${h}${br}"

    Write-Status -Level Header -Message "`n$top`n$middle`n$bottom"
}

function Format-ToolOutput {
    <#
    .SYNOPSIS
        Pipeline-friendly output gate for external tools (winget, wsl, nvm,
        etc.). The surrounding [INSTALLING]/[MOVE]/[OK] status lines tell
        the user what's happening; the tool's own chatter is noise unless
        you're debugging.

        Default: silently consume the input.
        -Verbose ($VerbosePreference -ne 'SilentlyContinue'): stream each
        non-empty line indented two spaces under the parent status line.
        Lines matching any -SkipPattern regex are dropped even under
        -Verbose (useful for stripping legal boilerplate).

        Usage:
            wsl --install -d Debian --no-launch 2>&1 | Format-ToolOutput

        $LASTEXITCODE from the upstream external command remains visible
        to the caller after the pipeline returns.
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline)]
        $InputObject,

        [Parameter()]
        [string[]] $SkipPattern = @()
    )
    begin {
        $verbose = $VerbosePreference -ne 'SilentlyContinue'
    }
    process {
        if (-not $verbose) { return }
        if ($null -eq $InputObject) { return }
        $s = [string]$InputObject
        if (-not $s) { return }
        foreach ($p in $SkipPattern) {
            if ($s -match $p) { return }
        }
        Write-Information " $s"
    }
}