private/components/Format-SentenceComponent.ps1

# TODO: Fix spectre.console so it doesn't break words across multiple lines when they use markup to color each letter individually.
# That's why this mess exists here, it's manually line breaking the sentence data to avoid that issue.
# This function is large and inlined with duplicated code to make it faster.
function Format-SentenceComponent {
  param (
    [string] $Sentence,
    [string] $CurrentInput,
    [int] $AvailableWidth,
    [int] $LeftPadding = 20,
    [int] $RightPadding = 10
  )

  $AvailableWidth -= $LeftPadding + $RightPadding
  
  # Stringbuilder is faster than string concatenation
  $formattedSentence = [System.Text.StringBuilder]::new()
  $inputPosition = 0
  $sentencePosition = 0
  $linePosition = 0
  $rightMargin = 20
  $Sentence = $Sentence | Get-SpectreEscapedText
  $CurrentInput = [string]::IsNullOrEmpty($CurrentInput) ? $CurrentInput : ($CurrentInput | Get-SpectreEscapedText)

  # Process the current input (colored based on correctness)
  for ($inputPosition = 0; $inputPosition -lt $CurrentInput.Length; $inputPosition++) {
    # For the first character on a line we don't want the space
    if ($linePosition -eq 0 -and $CurrentInput[$inputPosition] -eq " ") {
      $sentencePosition++
      continue
    }
    # The current character is either correct or incorrect, we check the current position in the input string against the same position in the sentence string
    if ($CurrentInput[$inputPosition] -ceq $Sentence[$sentencePosition]) {
      $null = $formattedSentence.Append("[green]$($CurrentInput[$inputPosition])[/]")
      $sentencePosition++
    } else {
      $null = $formattedSentence.Append("[red underline]$($CurrentInput[$inputPosition])[/]")
    }
    # Spaces get converted to new lines to avoid the spectre.console issue of breaking words across multiple lines
    if ($linePosition -ge ($AvailableWidth - $rightMargin) -and $CurrentInput[$inputPosition] -eq " ") {
      $null = $formattedSentence.Append("`n")
      $linePosition = 0
    }
    $linePosition++
  }

  # Process the current position
  # Spaces get converted to new lines to avoid the spectre.console issue of breaking words across multiple lines
  $currentChar = $Sentence[$sentencePosition]
  if ($linePosition -ge ($AvailableWidth - $rightMargin) -and $currentChar -eq " ") {
    $null = $formattedSentence.Append("[grey84 on grey19]$currentChar[/]")
    $null = $formattedSentence.Append("`n")
    $linePosition = 0
    $sentencePosition++
    $currentChar = $Sentence[$sentencePosition]
  } else {
    $null = $formattedSentence.Append("[grey84 on grey19]$currentChar[/]")
    $sentencePosition++
  }
  $linePosition++

  # Process the remaining sentence
  $null = $formattedSentence.Append("[grey84]")
  for ($sentencePosition = $sentencePosition; $sentencePosition -lt $Sentence.Length; $sentencePosition++) {
    # For the first character on a line we don't want the space
    if ($linePosition -eq 0 -and $Sentence[$sentencePosition] -eq " ") {
      continue
    }
    # Spaces get converted to new lines to avoid the spectre.console issue of breaking words across multiple lines
    if ($linePosition -ge ($AvailableWidth - $rightMargin) -and $Sentence[$sentencePosition] -eq " ") {
      $null = $formattedSentence.Append("`n")
      $linePosition = 0
    } else {
      $null = $formattedSentence.Append(($Sentence[$sentencePosition]))
    }
    $linePosition++
  }
  $null = $formattedSentence.Append("[/]")

  return $formattedSentence.ToString() | Format-SpectreAligned -HorizontalAlignment Left | Format-SpectrePadded -Top 0 -Left $LeftPadding -Bottom 0 -Right $RightPadding
}