Private/Show-AACReportView.ps1

function Show-AACReportView {
    <#
    .SYNOPSIS
        Draws a report model at the console - the same model the HTML and
        PDF reports are written from - so the expert commands' views all look
        alike.
    .DESCRIPTION
        The model (a hashtable):
          Facts ordered: label -> value, the grey line under the title
          Status Success, Warning or Failed - the headline's state
          Headline one line: what matters most
          Tiles @{ Value; Label; Tone } (tone: good, bad, warn, info,
                     neutral, violet), drawn as Show-AACTileRow tiles
          Notices @{ Status; Text } status lines
          Charts @{ Title; Items = @{ Label; Value }; Console = $true } -
                     those marked Console are drawn as bar charts
          Tables @{ Title; Rows; Columns; ConsoleLimit (15) } - the
                     columns marked Console (else the first six shown) of
                     the first ConsoleLimit rows; badge columns in their
                     tone's colour, severities with their status symbol
          Hint a grey line at the end (the parameters to go further)
        Tables with no rows are left out, unless they say Empty (a callout).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Collections.IDictionary] $Report
    )

    $escape = { param($Text) [Spectre.Console.Markup]::Escape([string]$Text) }
    $glyph = Get-AACGlyph
    $toneColor = @{ good = 'green3'; bad = 'red1'; warn = 'orange1'; info = 'deepskyblue1'; neutral = 'grey70'; violet = 'mediumpurple2' }
    $severity = Get-AACSeverityRank
    $has = { param([string] $Key) $Report.Contains($Key) -and $null -ne $Report[$Key] }

    $facts = [System.Collections.Generic.List[string]]::new()
    if ($script:AACSession) { $facts.Add("[white]$(& $escape $script:AACSession.Account)[/]") }
    if (& $has 'Facts') { foreach ($key in $Report.Facts.Keys) { $facts.Add("$(& $escape $key): $(& $escape $Report.Facts[$key])") } }
    $facts.Add((Get-Date).ToString('d MMM yyyy HH:mm'))
    Write-AACMarkup "[grey58]$($facts -join " $($glyph.Dot) ")[/]"
    [Spectre.Console.AnsiConsole]::WriteLine()
    if (& $has 'Headline') {
        Write-AACStatusLine $(if (& $has 'Status') { $Report.Status } else { 'Info' }) ([string]$Report.Headline)
        [Spectre.Console.AnsiConsole]::WriteLine()
    }
    if ((& $has 'Tiles') -and @($Report.Tiles).Count) {
        Show-AACTileRow -Tile @(foreach ($t in $Report.Tiles) { @{ Value = [string]$t.Value; Caption = [string]$t.Label; Color = $(if ($t.Contains('Tone') -and $toneColor.Contains([string]$t.Tone)) { $toneColor[[string]$t.Tone] } else { 'deepskyblue1' }) } })
        [Spectre.Console.AnsiConsole]::WriteLine()
    }
    if (& $has 'Notices') {
        foreach ($n in @($Report.Notices)) { Write-AACStatusLine $(if ($n.Contains('Status')) { $n.Status } else { 'Warning' }) ([string]$n.Text) }
        if (@($Report.Notices).Count) { [Spectre.Console.AnsiConsole]::WriteLine() }
    }
    if (& $has 'Charts') {
        foreach ($c in @($Report.Charts | Where-Object { $_.Contains('Console') -and $_.Console -and @($_.Items).Count })) {
            Show-AACBarChart -Item @(foreach ($i in @($c.Items) | Select-Object -First 12) { @{ Label = [string]$i.Label; Value = [double]$i.Value } }) -Title ([string]$c.Title)
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }

    foreach ($t in @(if (& $has 'Tables') { $Report.Tables })) {
        $rows = @($t.Rows | Where-Object { $null -ne $_ })
        if (-not $rows.Count) {
            if ($t.Contains('Empty') -and $t.Empty) { Show-AACCallout $(if ($t.Contains('EmptyStatus')) { $t.EmptyStatus } else { 'Success' }) "[bold]$(& $escape $t.Empty)[/]" -Title ([string]$t.Title); [Spectre.Console.AnsiConsole]::WriteLine() }
            continue
        }
        if ($t.Contains('NoConsole') -and $t.NoConsole) { continue }
        $columns = @($t.Columns | Where-Object { -not ($_.Contains('Hidden') -and $_.Hidden) })
        $chosen = @($columns | Where-Object { $_.Contains('Console') -and $_.Console })
        if (-not $chosen.Count) { $chosen = @($columns | Select-Object -First 6) }
        $limit = if ($t.Contains('ConsoleLimit')) { [int]$t.ConsoleLimit } else { 15 }
        $table = [Spectre.Console.Table]::new()
        $table.Border = [Spectre.Console.TableBorder]::Rounded
        $table.BorderStyle = [Spectre.Console.Style]::Parse('grey35')
        $title = "[bold]$(& $escape $t.Title)[/]$(if ($rows.Count -gt $limit) { " [grey58](top $limit of $($rows.Count))[/]" } else { " [grey58]($($rows.Count))[/]" })"
        $table.Title = [Spectre.Console.TableTitle]::new($title)
        foreach ($c in $chosen) {
            $column = [Spectre.Console.TableColumn]::new("[grey70]$(& $escape $c.Label)[/]")
            if ($c.Contains('Type') -and $c.Type -in 'number', 'money', 'score') { $column.Alignment = [Spectre.Console.Justify]::Right }
            $null = $table.AddColumn($column)
        }
        foreach ($row in @($rows | Select-Object -First $limit)) {
            $cells = foreach ($c in $chosen) {
                $value = if ($row -is [System.Collections.IDictionary]) { $row[$c.Key] } else { $p = $row.PSObject.Properties[[string]$c.Key]; if ($p) { $p.Value } }
                $text = if ($value -is [datetime]) { $value.ToString('d MMM HH:mm') } elseif ($value -is [double] -or $value -is [decimal]) { if ($c.Contains('Format')) { $value.ToString($c.Format) } else { '{0:N2}' -f $value } } elseif ($value -is [int] -or $value -is [long]) { '{0:N0}' -f $value } else { [string]$value }
                $type = if ($c.Contains('Type')) { $c.Type } else { 'text' }
                if ($c.Key -eq 'Severity' -and $severity.Status.Contains($text)) {
                    "$((Get-AACStatus $severity.Status[$text]).Markup) [$($toneColor[$severity.Tone[$text]])]$(& $escape $text)[/]"
                }
                elseif ($c.Key -eq 'Status' -and $text -in 'Success', 'Warning', 'Failed', 'Info', 'InProgress') {
                    $state = Get-AACStatus $text
                    "$($state.Markup) [$($state.Color)]$(& $escape $text)[/]"
                }
                elseif ($type -eq 'badge' -and $c.Contains('Tones') -and $c.Tones -and $c.Tones.Contains($text) -and $toneColor.Contains([string]$c.Tones[$text])) {
                    "[$($toneColor[[string]$c.Tones[$text]])]$(& $escape $text)[/]"
                }
                elseif ($type -eq 'resource') { "[white]$(& $escape $text)[/]" }
                elseif ($type -in 'mono', 'link') { "[grey58]$(& $escape $text)[/]" }
                elseif ($type -eq 'wide') { "[grey70]$(& $escape $text)[/]" }
                else { & $escape $text }
            }
            $null = [Spectre.Console.TableExtensions]::AddRow($table, [Spectre.Console.Rendering.IRenderable[]]@($cells | ForEach-Object { [Spectre.Console.Markup]::new($_) }))
        }
        [Spectre.Console.AnsiConsole]::Write($table)
        [Spectre.Console.AnsiConsole]::WriteLine()
    }
    if (& $has 'Hint') { Write-AACMarkup "[grey42]$(& $escape $Report.Hint)[/]" }
}