private/Format-WtwTable.ps1

<#
.SYNOPSIS
    Renders a hex color as an ANSI true-color swatch with contrasting foreground text.

.DESCRIPTION
    Validates '#RRGGBB', then emits escape sequences for 24-bit foreground and background.
    Used by Format-WtwTable for the Color column.

.PARAMETER Hex
    Six-digit hex color including leading '#'.

.EXAMPLE
    Format-WtwColorSwatch -Hex '#e05d44'
    Returns an ANSI-colored string showing the hex value on a colored background.

.NOTES
    Depends on: Get-ContrastForeground
#>

function Format-WtwColorSwatch {
    param([string] $Hex)
    if ($Hex -notmatch '^#[0-9a-fA-F]{6}$') { return $Hex }
    $r = [convert]::ToInt32($Hex.Substring(1, 2), 16)
    $g = [convert]::ToInt32($Hex.Substring(3, 2), 16)
    $b = [convert]::ToInt32($Hex.Substring(5, 2), 16)
    $fg = Get-ContrastForeground $Hex
    $fr = [convert]::ToInt32($fg.Substring(1, 2), 16)
    $fg2 = [convert]::ToInt32($fg.Substring(3, 2), 16)
    $fb = [convert]::ToInt32($fg.Substring(5, 2), 16)
    $esc = [char]27
    # Render: colored block with hex text inside
    return "${esc}[38;2;${fr};${fg2};${fb}m${esc}[48;2;${r};${g};${b}m ${Hex} ${esc}[0m"
}

function Get-WtwVisibleStringLength {
    <#
    .SYNOPSIS
        Length of a string as displayed in a terminal (ANSI SGR sequences stripped).
    #>

    param([string] $Text)
    if ([string]::IsNullOrEmpty($Text)) {
        return 0
    }
    $esc = [regex]::Escape([char]27)
    $stripped = [regex]::Replace($Text, "$esc\[[0-9;]*m", '')
    return $stripped.Length
}

function Split-WtwTableCellIntoLines {
    <#
    .NOTES
        Do not use `-split "`n", [StringSplitOptions]::None` — in PowerShell the second
        -split operand is *max substrings* (int); None = 0 and breaks the split, leaving a
        scalar string so `$s[$i]` indexes chars and PadRight fails.
    #>

    param([string] $Value)
    if ($null -eq $Value) {
        return @('')
    }
    $normalized = "$Value"
    if ($normalized -eq '') {
        return @('')
    }
    $parts = $normalized.Split("`n", [StringSplitOptions]::None)
    return [string[]]$parts
}

function ConvertTo-WtwTableCellLineArray {
    <#
    .SYNOPSIS
        Ensures a cell line collection is a string[] (scalar string → one row, never char-wise).
    #>

    param($CellLines)
    if ($null -eq $CellLines) {
        return @('')
    }
    if ($CellLines -is [string]) {
        return @([string]$CellLines)
    }
    return [string[]]@($CellLines | ForEach-Object { "$_" })
}

<#
.SYNOPSIS
    Prints a table of objects to the host with aligned columns.

.DESCRIPTION
    Computes column widths, writes a header and separator, then each row. When a column
    is named 'Color' and the value matches a six-digit hex, Format-WtwColorSwatch is used.
    Cell values may contain newline (`n) characters; each line stacks in the same column.

.PARAMETER Items
    Array of objects (e.g., PSCustomObject rows) to display.

.PARAMETER Columns
    Optional ordered list of property names to show. Defaults to all properties of the first item.

.EXAMPLE
    Format-WtwTable -Items $rows -Columns @('Name', 'Color')

.NOTES
    Depends on: Format-WtwColorSwatch (when Color column holds a hex value)
#>

function Format-WtwTable {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [array] $Items,

        [Parameter(Position = 1)]
        [string[]] $Columns
    )

    if ($Items.Count -eq 0) {
        Write-Host ' (none)' -ForegroundColor DarkGray
        return
    }

    if (-not $Columns) {
        $Columns = $Items[0].PSObject.Properties.Name
    }

    $columnSeparatorWidth = 2

    # Calculate column widths (support multiline cells; Color uses visible swatch width)
    $widths = @{}
    foreach ($col in $Columns) {
        $widths[$col] = [Math]::Max($col.Length, 0)
        foreach ($item in $Items) {
            $val = "$($item.$col)"
            $isHexColorCell = $col -eq 'Color' -and ((ConvertTo-WtwTableCellLineArray (Split-WtwTableCellIntoLines $val)).Count -eq 1) -and $val -match '^#[0-9a-fA-F]{6}$'
            if ($isHexColorCell) {
                $swatchText = Format-WtwColorSwatch $val
                $visibleSwatchWidth = Get-WtwVisibleStringLength $swatchText
                $candidateWidth = [Math]::Max($val.Length, $visibleSwatchWidth)
                if ($candidateWidth -gt $widths[$col]) {
                    $widths[$col] = $candidateWidth
                }
                continue
            }
            foreach ($line in (ConvertTo-WtwTableCellLineArray (Split-WtwTableCellIntoLines $val))) {
                $lineLength = "$line".Length
                if ($lineLength -gt $widths[$col]) {
                    $widths[$col] = $lineLength
                }
            }
        }
    }

    # Header
    $header = ($Columns | ForEach-Object { $_.PadRight($widths[$_]) }) -join (' ' * $columnSeparatorWidth)
    Write-Host " $header" -ForegroundColor Cyan
    $sep = ($Columns | ForEach-Object { '-' * $widths[$_] }) -join (' ' * $columnSeparatorWidth)
    Write-Host " $sep" -ForegroundColor DarkGray

    # Rows (each logical row may span multiple terminal lines)
    foreach ($item in $Items) {
        $columnLineArrays = [ordered]@{}
        $maxLineCount = 1
        foreach ($col in $Columns) {
            $val = "$($item.$col)"
            $linesPreview = ConvertTo-WtwTableCellLineArray (Split-WtwTableCellIntoLines $val)
            $hexColorForCell = $col -eq 'Color' -and $linesPreview.Count -eq 1 -and $val -match '^#[0-9a-fA-F]{6}$'
            if ($hexColorForCell) {
                $columnLineArrays[$col] = @([string]$val)
            } else {
                $lines = ConvertTo-WtwTableCellLineArray (Split-WtwTableCellIntoLines $val)
                $columnLineArrays[$col] = $lines
                if ($lines.Count -gt $maxLineCount) {
                    $maxLineCount = $lines.Count
                }
            }
        }

        for ($lineIndex = 0; $lineIndex -lt $maxLineCount; $lineIndex++) {
            $segments = [System.Collections.Generic.List[string]]::new()
            foreach ($col in $Columns) {
                $linesForColumn = @(ConvertTo-WtwTableCellLineArray $columnLineArrays[$col])
                $hexColorForCell = $col -eq 'Color' -and $linesForColumn.Count -eq 1 -and ($linesForColumn[0] -match '^#[0-9a-fA-F]{6}$')
                if ($hexColorForCell) {
                    if ($lineIndex -eq 0) {
                        $swatchText = Format-WtwColorSwatch $linesForColumn[0]
                        $padToWidth = $widths[$col] - (Get-WtwVisibleStringLength $swatchText)
                        if ($padToWidth -gt 0) {
                            $segments.Add($swatchText + (' ' * $padToWidth))
                        } else {
                            $segments.Add($swatchText)
                        }
                    } else {
                        $segments.Add(' ' * $widths[$col])
                    }
                    continue
                }

                $pieceText = if ($lineIndex -lt $linesForColumn.Count) { [string]$linesForColumn[$lineIndex] } else { '' }
                $segments.Add($pieceText.PadRight($widths[$col]))
            }
            $rowText = ($segments.ToArray() -join (' ' * $columnSeparatorWidth))
            Write-Host " $rowText"
        }
    }
}