Private/Console.psm1

using namespace System
using namespace System.IO
using namespace System.Linq
using namespace System.Text
using namespace System.Diagnostics
using namespace System.Globalization
using namespace System.ComponentModel
using namespace System.Collections.Generic
using namespace System.Diagnostics.CodeAnalysis
using namespace System.Runtime.InteropServices
using namespace System.Management.Automation

using module .\Console\Ansi.psm1
using module .\Console\Boxes.psm1
using module .\Console\Charts.psm1
using module .\Console\Colors.psm1
using module .\Console\Emojis.psm1
using module .\Console\Figlet.psm1
using module .\Console\Internal.psm1
using module .\Console\Json.psm1
using module .\Console\Layout.psm1
using module .\Console\List.psm1
using module .\Console\Live.psm1
using module .\Console\Progress.psm1
using module .\Console\Prompts.psm1
using module .\Console\Renderer.psm1
using module .\Console\Rendering.psm1
using module .\Console\Spinners.psm1
using module .\Console\Status.psm1
using module .\Console\Syntax.psm1
using module .\Console\Table.psm1
using module .\Console\TableRenderer.psm1
using module .\Console\Tables.psm1
using module .\Console\Tree.psm1
using module .\Console\Ui.psm1
using module .\Console\Utilities.psm1
using module .\Console\Widgets.psm1

# todo : a console-convenience helper class to demo the features
class ConsoleHelper {
  static [void] DemoMarkup() {
    $console = [AnsiConsole]::Console
    $console.MarkupLine('[bold green]Hello from AnsiConsole[/]')
    $console.Write([Panel]::new([Markup]::new('[yellow]Pure PowerShell classes[/]')))
  }
  static [void] DemoTextandMarkup() {
    $console = [AnsiConsole]::Console
    $console.Write([Text]::new('Plain text output'))
    $console.WriteLine('')
    $console.Write([Markup]::new('[yellow]Warning:[/] disk usage high [grey]just kidding :)[/]'))
    $console.WriteLine('')
  }
  static [void] DemoPanelsRulesandAlignment() {
    $console = [AnsiConsole]::Console

    $panel = [Panel]::new([Markup]::new('[green]Build completed[/]'))
    $panel.Header = [PanelHeader]::new('CI Status')

    $aligned = [Align]::Center($panel)

    $console.Write([Rule]::new('Deployment'))
    $console.Write($aligned)
  }
  static [void] DemoTables() {
    $table = [Table]::new()
    [void]$table.AddColumn([TableColumn]::new('Name'))
    [void]$table.AddColumn([TableColumn]::new('Role'))
    [void]$table.AddRow(@('Harvey', 'Closer'))
    [void]$table.AddRow(@('Donna', 'COO'))
    [AnsiConsole]::Console.Write($table)
  }
  static [void] DemoRowsColumnsandGrid() {
    $console = [AnsiConsole]::Console

    $rows = [Rows]::new(@(
        [Markup]::new('[bold]Service A[/]'),
        [Markup]::new('[dim]Healthy[/]')
      ))
    $console.Write($rows)

    $grid = [Grid]::new()
    $grid.AddColumn() | Out-Null
    $grid.AddColumn() | Out-Null
    [void]$grid.AddRow(@(
        [Markup]::new('[bold]Region[/]'),
        [Markup]::new('[bold]Latency[/]')
      ))
    [void]$grid.AddRow(@(
        [Text]::new('us-east-1'),
        [Markup]::new('[green]24 ms[/]')
      ))
    [void]$grid.AddRow(@(
        [Text]::new('eu-west-1'),
        [Markup]::new('[yellow]67 ms[/]')
      ))

    $console.Write($grid)
  }
  static [void] DemoTreeRendering() {
    $tree = [Tree]::new('Root')
    $branch = $tree.Root.AddNode('Branch 1')
    [void]$branch.AddNode('Leaf 1.1')
    [void]$tree.Root.AddNode('Branch 2')
    [AnsiConsole]::Console.Write($tree)
  }
  static [void] DemoJSONRendering() {
    $uri = [uri]'https://jsonplaceholder.typicode.com/todos/1'
    $progressHelper = [type]"ProgressUtil"
    $RequestParams = @{
      Uri    = $uri.AbsoluteUri
      Method = 'GET'
    }
    $result = $progressHelper::WaitJob("Making GET request to $($uri.Host)", { param($rp) Invoke-RestMethod @rp }, $RequestParams)
    $json = $result.Output | ConvertTo-Json
    $tokens = [JsonTokenizer]::Tokenize($json)
    $syntax = [JsonParser]::Parse($tokens)
    [AnsiConsole]::Console.Write([JsonText]::new($syntax))
  }
  static [void] DemoChartsandCalendar() {
    $console = [AnsiConsole]::Console

    $chart = [BarChart]::new()
    $chart.AddItem('API', 92, [Color]::Green) | Out-Null
    $chart.AddItem('DB', 65, [Color]::Yellow) | Out-Null
    $chart.AddItem('IO', 35, [Color]::Red) | Out-Null
    $console.Write($chart)

    $breakdown = [BreakdownChart]::new()
    $breakdown.AddItem('Tests', 120, [Color]::Blue) | Out-Null
    $breakdown.AddItem('Lint', 35, [Color]::Green) | Out-Null
    $breakdown.AddItem('Package', 10, [Color]::Yellow) | Out-Null
    $console.Write($breakdown)

    $calendar = [Calendar]::new(
      [datetime]::new(2026, 5, 22),
      [datetime[]]@(
        [datetime]::new(2026, 5, 1),
        [datetime]::new(2026, 5, 15)
      )
    )
    $console.Write($calendar)
  }
  static [void] DemoProgress() {
    Write-Host "Text before progress bar... line 1"
    Write-Host "Text before progress bar... line 2"
    Write-Host "Text before progress bar... line 3"
    # the test is successfule if by the end of thew progress, all 3 initial lines are still visible (not overwritten)
    $console = [AnsiConsole]::Console
    $progress = [Progress]::new($console)
    $progress.RefreshRateMs = 80
    # $progress.Config = @{}

    $progress.Start([Action[ProgressContext]] {
        param([ProgressContext]$ctx)

        $task = $ctx.AddTask('[green]Loading data[/]', [ProgressTaskSettings]::new())
        foreach ($step in 1..5) {
          Start-Sleep -Milliseconds 1000
          $task.Increment(20)
        }
      }
    )
  }
  static [void] DemoFigletText() {
    $fig = [FigletText]::new([FigletFont]"DEFAULT_3D", 'alain')
    [AnsiConsole]::Console.Write($fig)
  }
  static [object] DemoSearchableListPrompt() {
    $prompt = [ListPrompt]::new('Pick a service')
    $prompt.AddItems([string[]]@('api', 'worker', 'scheduler', 'gateway'))
    $service = $prompt.Show([AnsiConsole]::Console)

    # Non-interactive preview for tests and docs:
    # $prompt.SearchFilter = 'work'
    # $prompt.Preview()
    return $service
  }
  static [object] DemoAnsiInThreadrunner() {
    $jobs = [object[]](
      @{
        n = "[yellow]calc~Primes[/]"
        s = { param($n) (1..$n | Where-Object { $_ -gt 1 -and (1..[Math]::Sqrt($_)) -notcontains $_ -or $_ -eq 2 }).Count }
        a = 1000
      },
      @{
        n = "[yellow]Fibonacci[/]"
        s = { param($n) $a, $b = 0, 1; 1..$n | ForEach-Object { $c = $a; $a = $b; $b = $c + $b; $a } }
        a = 20
      }
    )
    $runnerType = [type]"ThreadRunner"
    $results = $runnerType::Run("", $jobs, 2, "Modern")
    return $results
  }
  static [object] DemoFailingTaskInThreadrunner() {
    $runnerType = [type]"ThreadRunner"
    $results = $runnerType::Run("doing a failing task in the background...", @{
        n           = "[yellow]run fake db operations[/]"
        s           = {
          param($operationCount)
          Start-Sleep -Milliseconds 4000
          throw "idk wtf just happened!"
        }
        a           = 15
        ThrowOnFail = $false
      }
    )
    return $results
  }
  static [void] DemoSimultaneousBackgroundJobsWithFailures() {
    ## run multiple jobs in parallel:
    $Jobs = (
      @{
        Name        = "Compute Primes"
        ScriptBlock = {
          param($count)
          $primes = @(2)
          $n = 3
          while ($primes.Count -lt $count) {
            $isPrime = $true
            foreach ($p in $primes) {
              if (($p * $p) -gt $n) { break }
              if ($n % $p -eq 0) {
                $isPrime = $false
                break
              }
            }
            if ($isPrime) { $primes += $n }
            $n += 2
          }
          return @{
            Count     = $primes.Count
            LastPrime = $primes[-1]
          }
        }
        Arguments   = @(2500)
      },
      @{
        Name        = "Simulate File Processing"
        ScriptBlock = {
          param($fileCount)
          $files = @()
          for ($i = 1; $i -le $fileCount; $i++) {
            $files += "file_$i.txt"
            Start-Sleep -Milliseconds 60
          }
          return @{
            Processed = $files.Count
            Files     = $files[0..3]
          }
        }
        Arguments   = @(30)
      },
      @{
        Name        = "Simulate API Calls"
        ScriptBlock = {
          param($callCount)
          $results = @()
          for ($i = 1; $i -le $callCount; $i++) {
            $results += @{
              Call    = $i
              Status  = "Success"
              Latency = Get-Random -Minimum 50 -Maximum 200
            }
            Start-Sleep -Milliseconds 100
          }
          return $results
        }
        Arguments   = @(15)
      },
      @{
        n = "Data Analysis"
        s = {
          param($iterations)
          $data = @()
          $sum = 0
          for ($i = 1; $i -le $iterations; $i++) {
            $value = Get-Random -Minimum 1 -Maximum 100
            $data += $value
            $sum += $value
            Start-Sleep -Milliseconds 20
          }
          $avg = $sum / $data.Count
          return @{
            TotalRecords = $data.Count
            Average      = [Math]::Round($avg, 2)
          }
        }
        a = @(100)
        t = $false
      },
      @{
        Name        = "DB Operations (Fails)"
        ScriptBlock = {
          param($operationCount)
          Start-Sleep -Milliseconds 500
          throw "Connection to database failed randomly."
        }
        Arguments   = @(15)
      }
    ) -as ([type]"BackgroundJob[]")
    $runnerType = [type]"ThreadRunner"
    # get JobResult[] result
    $results = $runnerType::Run("Doing epic stuff in the background...", $Jobs)
    $results | Out-Null
  }
  static [void] DemoStatus() {
    $writer = [AnsiConsole]::Console.GetWriter()
    $status = [Status]::new($writer)
    $status.RefreshRateMs = 80

    $status.Start('Downloading metadata', [Action[StatusContext]] {
        param([StatusContext]$ctx)
        Start-Sleep -Milliseconds 450
        $ctx.Update('Finishing download')
        Start-Sleep -Milliseconds 250
      }
    )
  }
  static [string] DemoTextPrompt() {
    $textPrompt = [TextPrompt]::new([string], 'Environment')
    $textPrompt.DefaultValue = 'dev'
    $envName = $textPrompt.Show([AnsiConsole]::Console)
    return $envName
  }
  static [bool] DemoConfirmPrompt() {
    $confirm = [ConfirmationPrompt]::new('Deploy now?')
    $confirm.DefaultValue = $false
    $shouldDeploy = $confirm.Show([AnsiConsole]::Console)
    return $shouldDeploy
  }
  static [object] DemoSelectionPrompt() {
    $selection = [SelectionPrompt]::new('Pick a region')
    $selection.AddChoice('US East', 'us-east-1')
    $selection.AddChoice('EU West', 'eu-west-1')
    $selection.AddChoice('AP South', 'ap-south-1')
    $region = $selection.Show([AnsiConsole]::Console)
    return $region
  }
  static [object] DemoMultiSelectionPrompt() {
    $multi = [MultiSelectionPrompt]::new('Select components')
    $multi.AddChoice('API', 'api')
    $multi.AddChoice('Worker', 'worker')
    $multi.AddChoice('Scheduler', 'scheduler')
    $components = $multi.Show([AnsiConsole]::Console);
    return $components
  }
  static [void] DemoCliArt() {
    $art = Create-CliArt "https://pastebin.com/raw/p29UR385" -Taglines "Build. Ship. Repeat."; $art.Replace("x.y.z", "0.3.2");
    $art.Write(15, $false, $true)
  }
  static [OrderedDictionary[string, ErrorRecord]] Run_Interactive_Demos() {
    # [ConsoleHelper].GetMethods().Where({ $_.IsStatic -and $_.Name.StartsWith("Demo") }).Name
    $demos = @{
      DemoMarkup                                 = "Markup rendering"
      DemoTextandMarkup                          = "Detect text and Markup"
      DemoPanelsRulesandAlignment                = "Panels, Rules, and Alignment."
      DemoTables                                 = "Tables."
      DemoRowsColumnsandGrid                     = "Rows, Columns, and Grid."
      DemoTreeRendering                          = "Tree Rendering."
      DemoJSONRendering                          = "JSON preview/rendering."
      DemoChartsandCalendar                      = "Charts and Calendar."
      DemoProgress                               = "Animated Progress."
      DemoFigletText                             = "FigletText rendering."
      DemoSearchableListPrompt                   = "Searchable Interactive ListPrompt."
      DemoAnsiInThreadrunner                     = "Ansi color ouptut in Threadrunner"
      DemoFailingTaskInThreadrunner              = "How threadrunner handles failing tasks"
      DemoSimultaneousBackgroundJobsWithFailures = "Simultaneous Background Jobs with Failures"
      DemoStatus                                 = "Status with Spinner."
      DemoTextPrompt                             = "Text Prompt"
      DemoConfirmPrompt                          = "Confirmation Prompt"
      DemoSelectionPrompt                        = "Selection Prompt"
      DemoMultiSelectionPrompt                   = "MultiSelection Prompt"
      DemoCliArt                                 = "CliArt"
    }
    $failing = [OrderedDictionary[string, ErrorRecord]]::new()
    $demos.Keys.ForEach({
        $method_name = $_
        Write-Console "[+] " -NoNewLine; Write-Console $demos[$_] -f LimeGreen
        try {
          [ConsoleHelper]::$_()
        } catch {
          $failing.Add($method_name, $_)
        } finally {
          $Host.UI.WriteLine("`n`n")
        }
      }
    )
    return $failing
  }
}