Functions/Private/Convert-ToColoredSegments.ps1

<#
================================================================================
ORION DESIGN - POWERSHELL UI FRAMEWORK | Convert-ToColoredSegments Function
================================================================================
Author: Sune Alexandersen Narud
Date: August 22, 2025
Module: OrionDesign v1.6.0
Category: Private Helper Function
Dependencies: OrionDesign Theme System
 
FUNCTION PURPOSE:
Parses markup tags in text and converts them to colored segments.
Core helper function providing markup parsing for rich text formatting
across multiple OrionDesign functions with theme-aware color mapping.
 
HLD INTEGRATION:
┌─ PRIVATE HELPER ─┐ ┌─ MARKUP PARSING ─┐ ┌─ OUTPUT ─┐
│ Convert-ToColored│◄──►│ <accent> tags │───►│ Colored │
│ Segments │ │ <success><error> │ │ Text │
│ • Parse Markup │ │ Theme Mapping │ │ Segments │
│ • Theme Colors │ │ Tag Processing │ │ Array │
└──────────────────┘ └──────────────────┘ └──────────┘
================================================================================
#>


# Convert-ToColoredSegments.ps1
function Convert-ToColoredSegments {
    param([string]$Text,[hashtable]$Theme=$script:Theme)

    $pattern = '<(/?)([a-zA-Z]+)>'
    $tagMatches = [regex]::Matches($Text, $pattern)

    $segments = @(); 
    $colorStack = New-Object System.Collections.Stack
    $styleStack = New-Object System.Collections.Stack
    $pos = 0

    function MapToken($tag,$theme) {
        switch ($tag.ToLower()) {
            'success'   { $theme.Success }
            'warning'   { $theme.Warning }
            'error'     { $theme.Error }
            'accent'    { $theme.Accent }
            'muted'     { $theme.Muted }
            'text'      { $theme.Text }
            'bold'      { 'BOLD' }
            'underline' { 'UNDER' }
            default     { if ([System.Enum]::GetNames([System.ConsoleColor]) -contains $tag) { $tag } else { $null } }
        }
    }

    foreach ($m in $tagMatches) {
        if ($m.Index -gt $pos) {
            $plain = $Text.Substring($pos, $m.Index - $pos)
            $curColor = if ($colorStack.Count -gt 0) { $colorStack.Peek() } else { $null }
            $curStyle = if ($styleStack.Count -gt 0) { $styleStack.Peek() } else { $null }
            $segments += [pscustomobject]@{ Text=$plain; Color=$curColor; Style=$curStyle }
        }

        $isClose = $m.Groups[1].Value -eq '/'
        $tag = $m.Groups[2].Value

        if ($isClose) {
            switch ($tag.ToLower()) {
                'bold'      { if ($styleStack.Count -gt 0 -and $styleStack.Peek() -eq 'BOLD') { $null=$styleStack.Pop() } }
                'underline' { if ($styleStack.Count -gt 0 -and $styleStack.Peek() -eq 'UNDER') { $null=$styleStack.Pop() } }
                default     { if ($colorStack.Count -gt 0) { $null=$colorStack.Pop() } }
            }
        }
        else {
            $mapped = MapToken $tag $Theme
            if ($mapped -eq 'BOLD' -or $mapped -eq 'UNDER') { $styleStack.Push($mapped) }
            elseif ($mapped) { $colorStack.Push($mapped) }
        }

        $pos = $m.Index + $m.Length
    }

    if ($pos -lt $Text.Length) {
        $plain = $Text.Substring($pos)
        $curColor = if ($colorStack.Count -gt 0) { $colorStack.Peek() } else { $null }
        $curStyle = if ($styleStack.Count -gt 0) { $styleStack.Peek() } else { $null }
        $segments += [pscustomobject]@{ Text=$plain; Color=$curColor; Style=$curStyle }
    }

    return $segments
}