RecMonTUI.ps1

<#PSScriptInfo
    .VERSION 1.0.8
    .GUID 5b2a3cd0-8d4b-4c4f-9ef2-5bde600fa324
    .AUTHOR Robert
    .COMPANYNAME OAOA-DEV
    .COPYRIGHT
    .TAGS Big TUI Tools PowerShellToolkit
    .LICENSEURI
    .PROJECTURI https://oaoa.dev/tools
    .ICONURI
    .EXTERNALMODULEDEPENDENCIES
    .REQUIREDMODULES
    .EXTERNALSCRIPTDEPENDENCIES
    .RELEASENOTES
    .PRIVATEDATA
    .DESCRIPTION A console TUI application for real-time Resource Monitor performance telemetry.
#>


<#
.SYNOPSIS
    Provides a text user interface (TUI) inside the PowerShell console to monitor CPU, memory, disk, and network resources.
.DESCRIPTION
    A console TUI application for real-time Resource Monitor performance telemetry.
.PARAMETER
    None
.EXAMPLE
    RecMonTUI
#>


# --- TUI LAYOUT ENGINE MODULE FUNCTIONS ---
# Generic Reusable PowerShell Console TUI Library
# Author: Antigravity

$ESC = [char]27

# Global Layout Variables (defaults, dynamically updated on resize)
$global:leftWidth = 35
# mainHeight will be dynamically updated
$global:mainHeight = 25

# ANSI Color Utilities
function Get-ANSIColor($name) {
    switch ($name) {
        'Reset'            { "$ESC[0m" }
        'Bold'             { "$ESC[1m" }
        'Inverse'          { "$ESC[7m" }
        'SelectedActive'   { "$ESC[37;44m" } # White on Blue
        'SelectedInactive' { "$ESC[37;100m" } # White on Dark Gray
        'Error'            { "$ESC[91m" }    # Bright Red
        'Warning'          { "$ESC[93m" }    # Bright Yellow
        'Info'             { "$ESC[92m" }    # Bright Green
        'Gray'             { "$ESC[90m" }    # Dark Gray
        'White'            { "$ESC[97m" }    # White
        'Cyan'             { "$ESC[96m" }    # Cyan
        'Blue'             { "$ESC[94m" }    # Blue
        'Header'           { "$ESC[30;47m" } # Black on Light Gray
        'ErrorRow'         { "$ESC[37;41m" } # White on Red background
        'WarningRow'       { "$ESC[30;43m" } # Black on Yellow background
        'GreenRow'         { "$ESC[32m" }    # Green text
        default            { "$ESC[0m" }
    }
}

# Position the cursor
function Set-Cursor($x, $y) {
    [Console]::SetCursorPosition($x, $y)
}

# Write text at specific coordinates
function Write-At($x, $y, $text, $colorName = 'Reset') {
    $color = Get-ANSIColor $colorName
    $reset = Get-ANSIColor 'Reset'
    Set-Cursor $x $y
    [Console]::Write("$color$text$reset")
}

# Initialize console settings for TUI
function Initialize-Console {
    $global:originalCursorVisible = [Console]::CursorVisible
    $global:originalOutputEncoding = [Console]::OutputEncoding
    [Console]::CursorVisible = $false
    [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
    
    # Enter Alternate Screen Buffer to preserve user scrollback
    [Console]::Write("$ESC[?1049h")
    [Console]::Write("$ESC[?25l") # ANSI Hide Cursor
    [Console]::Write("$ESC[2J") # Clear entire screen
}

# Restore original console settings on exit
function Restore-Console {
    # Exit Alternate Screen Buffer
    [Console]::Write("$ESC[?1049l")
    [Console]::Write("$ESC[?25h") # ANSI Show Cursor
    [Console]::CursorVisible = $global:originalCursorVisible
    [Console]::OutputEncoding = $global:originalOutputEncoding
}

# Generic Borders Drawing
function Draw-Borders {
    param(
        [int]$Width,
        [int]$Height,
        [int]$LeftWidth,
        [int]$MainHeight,
        [int]$FocusArea,
        [string]$LeftTitle = "",
        [string]$RightTopTitle = "",
        [string]$RightBottomTitle = "",
        [bool]$HasVerticalDivider = $true,
        [bool]$HasHorizontalDivider = $true,
        [double]$RightTopWidthPercent = 1.0
    )
    
    $rightWidth = $Width - $LeftWidth - 4
    
    # 1. Draw top border
    $topBorder = "┌" + ("─" * $LeftWidth) + $(if ($HasVerticalDivider) { "┬" } else { "─" }) + ("─" * $rightWidth) + "┐"
    Write-At 0 1 $topBorder 'Gray'
    
    # 2. Draw middle lines
    for ($y = 2; $y -le ($Height - 3); $y++) {
        Write-At 0 $y "│" 'Gray'
        if ($HasVerticalDivider) {
            Write-At ($LeftWidth + 1) $y "│" 'Gray'
        }
        Write-At ($Width - 2) $y "│" 'Gray'
    }
    
    # 3. Draw horizontal divider between Table and Details (right side only, if exists)
    if ($HasHorizontalDivider) {
        $dividerStart = if ($HasVerticalDivider) { $LeftWidth + 1 } else { 0 }
        $rightDivider = $(if ($HasVerticalDivider) { "├" } else { "│" }) + ("─" * $rightWidth) + "┤"
        Write-At $dividerStart ($MainHeight + 1) $rightDivider 'Gray'
    }
    
    # 4. Draw bottom border
    $bottomBorder = "└" + ("─" * $LeftWidth) + $(if ($HasVerticalDivider) { "┴" } else { "─" }) + ("─" * $rightWidth) + "┘"
    Write-At 0 ($Height - 2) $bottomBorder 'Gray'
    
    # 5. Draw secondary vertical divider if RightTopWidthPercent is < 1.0 (for Resource Monitor graph)
    if ($HasVerticalDivider -and $RightTopWidthPercent -lt 1.0 -and $RightTopWidthPercent -gt 0.0) {
        $listWidth = [int]($rightWidth * $RightTopWidthPercent)
        $dividerX = $LeftWidth + 2 + $listWidth
        for ($y = 2; $y -le $MainHeight; $y++) {
            Write-At $dividerX $y "│" 'Gray'
        }
        Write-At $dividerX 1 "┬" 'Gray'
        if ($HasHorizontalDivider) {
            Write-At $dividerX ($MainHeight + 1) "┴" 'Gray'
        }
    }
    
    # 6. Draw highlighted titles to indicate focus
    if ($LeftTitle) {
        $lTitleText = if ($FocusArea -eq 0) { "● $LeftTitle ●" } else { " $LeftTitle " }
        $lTitleX = [int](($LeftWidth - $lTitleText.Length) / 2) + 1
        $lColor = if ($FocusArea -eq 0) { 'SelectedActive' } else { 'Header' }
        Write-At $lTitleX 1 $lTitleText $lColor
    }
    
    if ($RightTopTitle) {
        $rtTitleText = if ($FocusArea -eq 1) { "● $RightTopTitle ●" } else { " $RightTopTitle " }
        $rtWidth = if ($RightTopWidthPercent -lt 1.0) { [int]($rightWidth * $RightTopWidthPercent) } else { $rightWidth }
        $rtTitleX = $LeftWidth + 2 + [int](($rtWidth - $rtTitleText.Length) / 2)
        $rtColor = if ($FocusArea -eq 1) { 'SelectedActive' } else { 'Header' }
        Write-At $rtTitleX 1 $rtTitleText $rtColor
    }
    
    if ($RightBottomTitle -and $HasHorizontalDivider) {
        $rbTitleText = if ($FocusArea -eq 2) { "● $RightBottomTitle ●" } else { " $RightBottomTitle " }
        $rbTitleX = $LeftWidth + 2 + [int](($rightWidth - $rbTitleText.Length) / 2)
        $rbColor = if ($FocusArea -eq 2) { 'SelectedActive' } else { 'Header' }
        Write-At $rbTitleX ($MainHeight + 1) $rbTitleText $rbColor
    }
}

# Generic Menu Bar Drawing
function Draw-Menu {
    param(
        [int]$Width,
        [string[]]$Items
    )
    $menuColor = Get-ANSIColor 'Header'
    $reset = Get-ANSIColor 'Reset'
    
    $menuText = " " + ($Items -join " ")
    $paddedMenu = $menuText.PadRight($Width).Substring(0, $Width)
    Set-Cursor 0 0
    [Console]::Write("$menuColor$paddedMenu$reset")
}

# Generic Status Bar Drawing
function Draw-Status {
    param(
        [string]$StatusText,
        [string]$FocusAreaText,
        [string]$HelpText,
        [int]$Width,
        [int]$Height
    )
    $statusColor = Get-ANSIColor 'Header'
    $reset = Get-ANSIColor 'Reset'
    
    $focusPart = if ($FocusAreaText) { "[$FocusAreaText]" } else { "" }
    $helpPart = if ($HelpText) { $HelpText + " │ " } else { "" }
    
    $maxStatusLen = $Width - $focusPart.Length - $helpPart.Length - 10
    $leftText = $StatusText
    if ($leftText.Length -gt $maxStatusLen) {
        $leftText = $leftText.Substring(0, $maxStatusLen - 3) + "..."
    }
    
    $paddedStatusText = " " + $helpPart + $leftText
    # Pad status bar to exactly Width - 2 to prevent console host auto-scroll
    $remainingSpaces = ($Width - 2) - $paddedStatusText.Length - $focusPart.Length
    if ($remainingSpaces -lt 0) { $remainingSpaces = 0 }
    
    $fullStatus = $paddedStatusText + (" " * $remainingSpaces) + $focusPart
    if ($fullStatus.Length -gt ($Width - 2)) {
        $fullStatus = $fullStatus.Substring(0, $Width - 2)
    }
    
    Set-Cursor 0 ($Height - 1)
    [Console]::Write("$statusColor$fullStatus$reset")
}

# Recalculate dimensions
function Update-LayoutDimensions {
    param(
        [int]$Width,
        [int]$Height
    )
    $global:mainHeight = $Height - 16
    if ($global:mainHeight -lt 5) { $global:mainHeight = 5 }
    
    $global:leftWidth = 35
    $maxWidth = [int]($Width * 0.4)
    if ($global:leftWidth -gt $maxWidth) { $global:leftWidth = $maxWidth }
    if ($global:leftWidth -lt 15) { $global:leftWidth = 15 }
}

# --- TREE VIEW COMPONENTS ---
function Get-VisibleNodes($node) {
    $result = [System.Collections.ArrayList]::new()
    
    function Add-Node($n) {
        $result.Add($n) | Out-Null
        if (!$n.IsLeaf -and $n.IsExpanded -and $n.Children) {
            foreach ($child in $n.Children) {
                Add-Node $child
            }
        }
    }
    
    Add-Node $node
    return $result
}

function Draw-TreeView {
    param(
        [System.Collections.ArrayList]$VisibleNodes,
        [int]$SelectedIndex,
        [int]$ScrollOffset,
        [int]$LeftWidth,
        [int]$TreeHeight,
        [bool]$IsFocused = $true
    )
    $startX = 1
    $startY = 2
    $reset = Get-ANSIColor 'Reset'
    
    for ($i = 0; $i -lt $TreeHeight; $i++) {
        $nodeIndex = $ScrollOffset + $i
        $y = $startY + $i
        
        if ($nodeIndex -lt $VisibleNodes.Count) {
            $node = $VisibleNodes[$nodeIndex]
            $prefix = if ($node.IsLeaf) { " " } else { if ($node.IsExpanded) { "- " } else { "+ " } }
            
            $indent = " " * ($node.Level + 1)
            $displayText = $indent + $prefix + $node.Label
            
            if ($displayText.Length -gt $LeftWidth) {
                $displayText = $displayText.Substring(0, $LeftWidth - 3) + "..."
            }
            $paddedText = $displayText.PadRight($LeftWidth)
            
            if ($nodeIndex -eq $SelectedIndex) {
                $color = if ($IsFocused) { Get-ANSIColor 'SelectedActive' } else { Get-ANSIColor 'SelectedInactive' }
            } else {
                $color = Get-ANSIColor 'Reset'
            }
            
            Write-At $startX $y $paddedText
            # Set highlight
            Set-Cursor $startX $y
            [Console]::Write("$color$paddedText$reset")
        } else {
            Write-At $startX $y (" " * $LeftWidth)
        }
    }
}

# --- GENERIC TABLE VIEW COMPONENTS ---

# Helper to format bytes
function Format-Bytes {
    param($bytes)
    if ($null -eq $bytes) { return "0 B" }
    try { $bytes = [double]$bytes } catch { return "0 B" }
    if ($bytes -ge 1GB) { return "{0:N2} GB" -f ($bytes / 1GB) }
    if ($bytes -ge 1MB) { return "{0:N2} MB" -f ($bytes / 1MB) }
    if ($bytes -ge 1KB) { return "{0:N2} KB" -f ($bytes / 1KB) }
    return "{0:N0} B" -f $bytes
}

# Helper to format date strings like "20260527" -> "2026-05-27"
function Format-DateString {
    param($str)
    if ($null -eq $str -or $str.Length -ne 8) { return $str }
    return "$($str.Substring(0,4))-$($str.Substring(4,2))-$($str.Substring(6,2))"
}

# Draw generic table header
function Draw-TableHeader {
    param(
        [int]$StartX,
        [int]$StartY,
        [array]$Columns, # @{ Label="Name"; Width=15; Align="Left" }
        [int]$Width
    )
    $reset = Get-ANSIColor 'Reset'
    $headerColor = Get-ANSIColor 'Header'
    
    $headerParts = @()
    foreach ($col in $Columns) {
        $label = $col.Label
        $w = $col.Width
        if ($label.Length -gt $w) { $label = $label.Substring(0, $w) }
        
        $padded = if ($col.Align -eq 'Right') { $label.PadLeft($w) } else { $label.PadRight($w) }
        $headerParts += $padded
    }
    $headerText = $headerParts -join "│"
    $paddedHeader = $headerText.PadRight($Width).Substring(0, $Width)
    
    Set-Cursor $startX $startY
    [Console]::Write("$headerColor$paddedHeader$reset")
}

# Draw generic table rows
function Draw-TableRows {
    param(
        [System.Collections.ArrayList]$Items,
        [int]$SelectedIndex,
        [int]$ScrollOffset,
        [array]$Columns,
        [int]$StartX,
        [int]$StartY,
        [int]$Width,
        [int]$Height,
        [bool]$IsFocused = $true,
        [scriptblock]$RowColorScript = $null
    )
    $reset = Get-ANSIColor 'Reset'
    
    for ($i = 0; $i -lt $Height; $i++) {
        $itemIndex = $ScrollOffset + $i
        $y = $startY + $i
        
        if ($itemIndex -lt $Items.Count) {
            $item = $Items[$itemIndex]
            $rowParts = @()
            
            foreach ($col in $Columns) {
                $val = $null
                if ($null -ne $item) {
                    if ($item -is [hashtable]) {
                        $val = $item[$col.Prop]
                    } else {
                        $val = $item.$($col.Prop)
                    }
                }
                if ($null -eq $val) { $val = "" }
                
                # Format cell values
                $formattedVal = switch ($col.Format) {
                    'Percent'     { "{0:F1} %" -f $val }
                    'MB'          { "{0:N1} MB" -f ($val / 1MB) }
                    'Bytes'       { Format-Bytes $val }
                    'Decimal'     { "{0:F1}" -f $val }
                    'DateTime'    { if ($val -is [DateTime]) { $val.ToString("yyyy-MM-dd HH:mm:ss") } else { $val.ToString() } }
                    'RegistryDate'{ Format-DateString $val }
                    default       { $val.ToString() }
                }
                
                # Strip control characters to prevent cursor shifting
                $formattedVal = $formattedVal -replace "`r", "" -replace "`n", "" -replace "`t", " "
                
                $w = $col.Width
                if ($formattedVal.Length -gt $w) {
                    $formattedVal = if ($w -gt 2) { $formattedVal.Substring(0, $w - 2) + ".." } else { $formattedVal.Substring(0, $w) }
                }
                
                $padded = if ($col.Align -eq 'Right') { $formattedVal.PadLeft($w) } else { $formattedVal.PadRight($w) }
                $rowParts += $padded
            }
            
            $rowText = $rowParts -join "│"
            
            # Select background based on active selection
            if ($itemIndex -eq $SelectedIndex) {
                $color = if ($IsFocused) { Get-ANSIColor 'SelectedActive' } else { Get-ANSIColor 'SelectedInactive' }
            } else {
                if ($null -ne $RowColorScript) {
                    $colorName = & $RowColorScript $item
                    $color = Get-ANSIColor $colorName
                } else {
                    $color = Get-ANSIColor 'Reset'
                }
            }
            
            $paddedRow = $rowText.PadRight($Width).Substring(0, $Width)
            Set-Cursor $StartX $y
            [Console]::Write("$color$paddedRow$reset")
        } else {
            # Empty row
            Set-Cursor $StartX $y
            [Console]::Write(" " * $Width)
        }
    }
}

# --- DETAILS VIEW COMPONENTS ---
function Get-DetailPropertyLine($lbl1, $val1, $lbl2, $val2, $colWidth) {
    $lblW = 12
    $valW = $colWidth - $lblW
    if ($valW -lt 5) { $valW = 5 }
    
    $lbl1Str = $lbl1.PadRight($lblW).Substring(0, $lblW)
    $val1Str = if ($null -ne $val1) { $val1.ToString() } else { "" }
    if ($val1Str.Length -gt $valW) {
        $val1Str = $val1Str.Substring(0, $valW - 3) + "..."
    }
    $col1Text = ($lbl1Str + $val1Str).PadRight($colWidth)
    
    $lbl2Str = $lbl2.PadRight($lblW).Substring(0, $lblW)
    $val2Str = if ($null -ne $val2) { $val2.ToString() } else { "" }
    if ($val2Str.Length -gt $valW) {
        $val2Str = $val2Str.Substring(0, $valW - 3) + "..."
    }
    $col2Text = ($lbl2Str + $val2Str).PadRight($colWidth)
    
    return "$col1Text │ $col2Text"
}

# Wrap text lines helper
function Wrap-Text($text, $maxLength) {
    if ([string]::IsNullOrEmpty($text)) {
        return @("")
    }
    
    $paragraphs = $text.Split(@("`r`n", "`n"), [System.StringSplitOptions]::None)
    $lines = [System.Collections.ArrayList]::new()
    
    foreach ($para in $paragraphs) {
        if ($para.Length -le $maxLength) {
            $lines.Add($para) | Out-Null
            continue
        }
        
        $words = $para.Split(' ')
        $currentLine = ""
        
        foreach ($word in $words) {
            if ($currentLine.Length -eq 0) {
                $currentLine = $word
            } elseif (($currentLine.Length + 1 + $word.Length) -le $maxLength) {
                $currentLine += " " + $word
            } else {
                $lines.Add($currentLine) | Out-Null
                $currentLine = $word
            }
        }
        if ($currentLine.Length -gt 0) {
            $lines.Add($currentLine) | Out-Null
        }
    }
    
    return $lines
}

function Draw-Details {
    param(
        [array]$HeaderLines,
        [array]$MessageLines,
        [int]$ScrollOffset,
        [int]$StartX,
        [int]$StartY,
        [int]$Width,
        [int]$Height
    )
    
    $hasHeader = ($null -ne $HeaderLines -and $HeaderLines.Count -gt 0)
    
    if ($hasHeader) {
        # Draw the header lines (up to 4)
        $headerCount = $HeaderLines.Count
        for ($i = 0; $i -lt 4; $i++) {
            $y = $StartY + $i
            $lineText = if ($i -lt $headerCount) { $HeaderLines[$i] } else { "" }
            if ($lineText.Length -gt $Width) {
                $lineText = $lineText.Substring(0, $Width)
            }
            $lineText = $lineText -replace "`r", "" -replace "`n", "" -replace "`t", " "
            Set-Cursor $StartX $y
            [Console]::Write($lineText.PadRight($Width))
        }
        
        # Draw divider line
        $yDivider = $StartY + 4
        $reset = Get-ANSIColor 'Reset'
        $gray = Get-ANSIColor 'Gray'
        Set-Cursor $StartX $yDivider
        [Console]::Write("$gray" + ("─" * $Width) + "$reset")
        
        $scrollAreaHeight = $Height - 5
        $bodyStartY = $StartY + 5
    } else {
        $scrollAreaHeight = $Height
        $bodyStartY = $StartY
    }
    
    # Draw scrollable body message lines
    $messageCount = if ($null -eq $MessageLines) { 0 } else { $MessageLines.Count }
    for ($i = 0; $i -lt $scrollAreaHeight; $i++) {
        $lineIndex = $ScrollOffset + $i
        $y = $bodyStartY + $i
        
        $lineText = if ($lineIndex -lt $messageCount) { $MessageLines[$lineIndex] } else { "" }
        if ($lineText.Length -gt $Width) {
            $lineText = $lineText.Substring(0, $Width)
        }
        $lineText = $lineText -replace "`r", "" -replace "`n", "" -replace "`t", " "
        Set-Cursor $StartX $y
        [Console]::Write($lineText.PadRight($Width))
    }
    
    # Fill remaining space
    for ($y = $bodyStartY + $scrollAreaHeight; $y -lt $StartY + $Height; $y++) {
        Set-Cursor $StartX $y
        [Console]::Write(" " * $Width)
    }
}

# --- TEXT DIALOG INPUT BOX ---
function Show-InputBox {
    param(
        [string]$Prompt,
        [string]$Title,
        [int]$Width,
        [int]$Height
    )
    $boxW = 60
    $boxH = 5
    $boxX = [int](($Width - $boxW) / 2)
    $boxY = [int](($Height - $boxH) / 2)
    
    $reset = Get-ANSIColor 'Reset'
    $borderC = Get-ANSIColor 'White'
    $titleC = Get-ANSIColor 'SelectedActive'
    
    # 1. Draw outer frames of the box
    $topB = "╔" + ("═" * ($boxW - 2)) + "╗"
    $midB = "║" + (" " * ($boxW - 2)) + "║"
    $botB = "╚" + ("═" * ($boxW - 2)) + "╝"
    
    Write-At $boxX $boxY $topB 'White'
    Write-At $boxX ($boxY + 1) $midB 'White'
    Write-At $boxX ($boxY + 2) $midB 'White'
    Write-At $boxX ($boxY + 3) $midB 'White'
    Write-At $boxX ($boxY + 4) $botB 'White'
    
    # Draw title
    $titleText = " $Title "
    $titleX = $boxX + [int](($boxW - $titleText.Length) / 2)
    Write-At $titleX $boxY $titleText 'SelectedActive'
    
    # Draw prompt text
    Write-At ($boxX + 2) ($boxY + 1) $Prompt
    
    $inputText = ""
    $inputX = $boxX + 2
    $inputY = $boxY + 2
    $maxInputLen = $boxW - 4
    
    # Temporarily show cursor
    [Console]::Write("$ESC[?25h")
    [Console]::CursorVisible = $true
    
    while ($true) {
        # Render current text
        Set-Cursor $inputX $inputY
        $displayText = $inputText
        if ($displayText.Length -gt $maxInputLen) {
            $displayText = "..." + $displayText.Substring($displayText.Length - $maxInputLen + 3)
        }
        [Console]::Write($displayText.PadRight($maxInputLen))
        
        $cursorX = $inputX + [Math]::Min($displayText.Length, $maxInputLen)
        Set-Cursor $cursorX $inputY
        
        $key = [Console]::ReadKey($true)
        
        if ($key.Key -eq 'Enter') {
            break
        }
        if ($key.Key -eq 'Escape') {
            $inputText = $null
            break
        }
        if ($key.Key -eq 'Backspace') {
            if ($inputText.Length -gt 0) {
                $inputText = $inputText.Substring(0, $inputText.Length - 1)
            }
        } else {
            if ($key.KeyChar -ge 32 -and $key.KeyChar -le 126) {
                $inputText += $key.KeyChar
            }
        }
    }
    
    [Console]::Write("$ESC[?25l")
    [Console]::CursorVisible = $false
    return $inputText
}

# --- SCROLLABLE CHECKLIST DIALOG WITH SEARCH FILTERING ---
function Show-CheckListDialog {
    param(
        [string]$Prompt,
        [string]$Title,
        [System.Collections.ArrayList]$Items, # Array of @{ Label = 'Name'; Checked = $true/$false }
        [int]$Width,
        [int]$Height
    )
    
    $boxW = 80
    if ($boxW -gt ($Width - 4)) { $boxW = $Width - 4 }
    if ($boxW -lt 40) { $boxW = 40 }
    
    $boxH = 18
    $boxX = [int](($Width - $boxW) / 2)
    if ($boxX -lt 0) { $boxX = 0 }
    
    $boxY = [int](($Height - $boxH) / 2)
    if ($boxY -lt 1) { $boxY = 1 }
    
    $reset = Get-ANSIColor 'Reset'
    
    $topB = "╔" + ("═" * ($boxW - 2)) + "╗"
    $midB = "║" + (" " * ($boxW - 2)) + "║"
    $botB = "╚" + ("═" * ($boxW - 2)) + "╝"
    
    $searchQuery = ""
    $selectedIndex = 0
    $scrollOffset = 0
    $listHeight = $boxH - 8
    $focusedControl = 'Search' # 'Search' or 'List'
    
    while ($true) {
        # 1. Draw outer frame
        Write-At $boxX $boxY $topB 'White'
        for ($i = 1; $i -lt ($boxH - 1); $i++) {
            Write-At $boxX ($boxY + $i) $midB 'White'
        }
        Write-At $boxX ($boxY + $boxH - 1) $botB 'White'
        
        # Title
        $titleText = " $Title "
        $titleX = $boxX + [int](($boxW - $titleText.Length) / 2)
        Write-At $titleX $boxY $titleText 'SelectedActive'
        
        # Prompt
        Write-At ($boxX + 2) ($boxY + 1) $Prompt
        
        # Search Box label and input field
        $searchLabel = "Search Filter: "
        $inputW = $boxW - 22
        if ($inputW -lt 10) { $inputW = 10 }
        
        $dispQuery = $searchQuery
        if ($dispQuery.Length -gt ($inputW - 2)) {
            $dispQuery = $dispQuery.Substring($dispQuery.Length - ($inputW - 2))
        }
        
        if ($focusedControl -eq 'Search') {
            Write-At ($boxX + 2) ($boxY + 2) $searchLabel 'White'
            $fieldVal = " $dispQuery" + "_"
            $fieldVal = $fieldVal.PadRight($inputW)
            Write-At ($boxX + 17) ($boxY + 2) $fieldVal 'SelectedActive'
        } else {
            Write-At ($boxX + 2) ($boxY + 2) $searchLabel 'Gray'
            $fieldVal = " $dispQuery"
            $fieldVal = $fieldVal.PadRight($inputW)
            Write-At ($boxX + 17) ($boxY + 2) $fieldVal 'SelectedInactive'
        }
        Write-At ($boxX + 2) ($boxY + 3) ("─" * ($boxW - 4)) 'Gray'
        
        # Filter items based on search query
        $filteredItems = [System.Collections.ArrayList]::new()
        foreach ($item in $Items) {
            if ([string]::IsNullOrEmpty($searchQuery) -or $item.Label.IndexOf($searchQuery, [System.StringComparison]::OrdinalIgnoreCase) -ge 0) {
                $filteredItems.Add($item) | Out-Null
            }
        }
        
        # Bounds check
        if ($selectedIndex -ge $filteredItems.Count) {
            $selectedIndex = [Math]::Max(0, $filteredItems.Count - 1)
        }
        if ($selectedIndex -lt 0) { $selectedIndex = 0 }
        
        if ($selectedIndex -lt $scrollOffset) {
            $scrollOffset = $selectedIndex
        }
        if ($selectedIndex -ge ($scrollOffset + $listHeight)) {
            $scrollOffset = $selectedIndex - $listHeight + 1
        }
        if ($scrollOffset -lt 0) { $scrollOffset = 0 }
        
        # Draw Checklist items
        for ($i = 0; $i -lt $listHeight; $i++) {
            $itemIdx = $scrollOffset + $i
            $y = $boxY + 4 + $i
            
            if ($itemIdx -lt $filteredItems.Count) {
                $item = $filteredItems[$itemIdx]
                $chk = if ($item.Checked) { "[x]" } else { "[ ]" }
                $text = " $chk $($item.Label) "
                if ($text.Length -gt ($boxW - 6)) {
                    $text = $text.Substring(0, $boxW - 9) + "..."
                }
                $paddedText = $text.PadRight($boxW - 6)
                
                if ($itemIdx -eq $selectedIndex) {
                    $colorName = if ($focusedControl -eq 'List') { 'SelectedActive' } else { 'SelectedInactive' }
                    $color = Get-ANSIColor $colorName
                } else {
                    $color = Get-ANSIColor 'Reset'
                }
                
                Set-Cursor ($boxX + 3) $y
                [Console]::Write("$color$paddedText$reset")
            } else {
                Write-At ($boxX + 3) $y (" " * ($boxW - 6))
            }
        }
        
        Write-At ($boxX + 2) ($boxY + $boxH - 3) ("─" * ($boxW - 4)) 'Gray'
        
        # Show shortcut guidelines dynamically based on focus
        if ($focusedControl -eq 'Search') {
            $shortcuts = " [Tab] List | [Backspace] Delete | [Enter] OK | [Esc] Cancel"
        } else {
            $shortcuts = " [Tab] Search | [Space] Toggle | [Arrows] Move | [Enter] OK | [Esc] Cancel"
        }
        $paddedShortcuts = $shortcuts.PadRight($boxW - 4)
        Write-At ($boxX + 2) ($boxY + $boxH - 2) $paddedShortcuts 'Header'
        
        # Read Key
        $key = [Console]::ReadKey($true)
        
        if ($key.Key -eq 'Escape') {
            return $null
        }
        if ($key.Key -eq 'Enter') {
            return $Items
        }
        if ($key.Key -eq 'Tab') {
            if ($focusedControl -eq 'Search') {
                $focusedControl = 'List'
            } else {
                $focusedControl = 'Search'
            }
        }
        elseif ($focusedControl -eq 'Search') {
            if ($key.Key -eq 'Backspace') {
                if ($searchQuery.Length -gt 0) {
                    $searchQuery = $searchQuery.Substring(0, $searchQuery.Length - 1)
                    $selectedIndex = 0
                    $scrollOffset = 0
                }
            }
            elseif ($key.Key -eq 'Space') {
                $searchQuery += " "
                $selectedIndex = 0
                $scrollOffset = 0
            }
            else {
                if ($key.KeyChar -ge 32 -and $key.KeyChar -le 126) {
                    $searchQuery += $key.KeyChar
                    $selectedIndex = 0
                    $scrollOffset = 0
                }
            }
        }
        elseif ($focusedControl -eq 'List') {
            if ($key.Key -eq 'Space') {
                if ($filteredItems.Count -gt 0) {
                    $selectedItem = $filteredItems[$selectedIndex]
                    $selectedItem.Checked = -not $selectedItem.Checked
                }
            }
            elseif ($key.Key -eq 'UpArrow') {
                if ($selectedIndex -gt 0) { $selectedIndex-- }
            }
            elseif ($key.Key -eq 'DownArrow') {
                if ($selectedIndex -lt ($filteredItems.Count - 1)) { $selectedIndex++ }
            }
            elseif ($key.Key -eq 'PageUp') {
                $selectedIndex = [Math]::Max(0, $selectedIndex - $listHeight)
            }
            elseif ($key.Key -eq 'PageDown') {
                $selectedIndex = [Math]::Min($filteredItems.Count - 1, $selectedIndex + $listHeight)
            }
        }
    }
}

# Export functions

# --- MAIN UTILITY DRIVER EXECUTION ---
# PowerShell TUI Resource Monitor Main Driver
# Run this script to start the Resource Monitor console interface.
# Author: Antigravity

$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path

# --- PERFORMANCE DATA COLLECTOR ENGINE ---
$global:perfRunspace = $null
$global:perfPowerShell = $null
$global:perfAsyncResult = $null

function Start-PerformanceDataCollector {
    $global:sharedData = [hashtable]::Synchronized(@{
        NewDataAvailable = $false
        Running = $true
        
        CPUUsage = 0.0
        MemoryAvailableBytes = 0.0
        MemoryCommittedBytes = 0.0
        MemoryCommitLimit = 0.0
        MemoryPagesPerSec = 0.0
        MemoryPageFaultsPerSec = 0.0
        MemoryTotalPhysical = 0.0
        MemoryUsedPhysical = 0.0
        
        DiskReadBytesPerSec = 0.0
        DiskWriteBytesPerSec = 0.0
        DiskPercentTime = 0.0
        
        NetworkBytesTotalPerSec = 0.0
        
        OverviewProcesses = [System.Collections.ArrayList]::new()
        CPUProcesses = [System.Collections.ArrayList]::new()
        MemoryProcesses = [System.Collections.ArrayList]::new()
        DiskProcesses = [System.Collections.ArrayList]::new()
        NetworkProcesses = [System.Collections.ArrayList]::new()
        NetworkInterfaces = [System.Collections.ArrayList]::new()
        
        ErrorMsg = ""
    })
    
    $collectorScript = {
        param($shared)
        
        try {
            Add-Type -AssemblyName Microsoft.VisualBasic -ErrorAction SilentlyContinue
            $compInfo = New-Object Microsoft.VisualBasic.Devices.ComputerInfo
            $shared.MemoryTotalPhysical = $compInfo.TotalPhysicalMemory
        } catch {
            $shared.MemoryTotalPhysical = 8GB # fallback
        }
        
        $numProcs = [Environment]::ProcessorCount
        if ($numProcs -le 0) { $numProcs = 1 }
        
        $warmupCounters = @(
            '\Processor(_Total)\% Processor Time',
            '\Process(*)\% Processor Time'
        )
        Get-Counter -Counter $warmupCounters -ErrorAction SilentlyContinue | Out-Null
        
        while ($shared.Running) {
            $startLoop = Get-Date
            
            try {
                $counters = @(
                    '\Processor(_Total)\% Processor Time',
                    '\Memory\Available Bytes',
                    '\Memory\Committed Bytes',
                    '\Memory\Commit Limit',
                    '\Memory\Pages/sec',
                    '\Memory\Page Faults/sec',
                    '\PhysicalDisk(_Total)\Disk Read Bytes/sec',
                    '\PhysicalDisk(_Total)\Disk Write Bytes/sec',
                    '\PhysicalDisk(_Total)\% Disk Time',
                    '\Network Interface(*)\Bytes Total/sec',
                    
                    '\Process(*)\ID Process',
                    '\Process(*)\% Processor Time',
                    '\Process(*)\Working Set',
                    '\Process(*)\Working Set - Private',
                    '\Process(*)\Private Bytes',
                    '\Process(*)\Thread Count',
                    '\Process(*)\Handle Count',
                    '\Process(*)\IO Read Bytes/sec',
                    '\Process(*)\IO Write Bytes/sec',
                    '\Process(*)\IO Other Bytes/sec',
                    '\Process(*)\Page Faults/sec'
                )
                
                $data = Get-Counter -Counter $counters -ErrorAction SilentlyContinue
                
                if ($null -ne $data) {
                    $procHashes = @{}
                    $netInterfaces = @{}
                    $netTotal = 0.0
                    
                    foreach ($sample in $data.CounterSamples) {
                        $path = $sample.Path
                        $cooked = $sample.CookedValue
                        
                        if ($path -like '*\processor(_total)\% processor time*') {
                            $shared.CPUUsage = $cooked
                        }
                        elseif ($path -like '*\memory\available bytes*') {
                            $shared.MemoryAvailableBytes = $cooked
                            $shared.MemoryUsedPhysical = $shared.MemoryTotalPhysical - $cooked
                        }
                        elseif ($path -like '*\memory\committed bytes*') {
                            $shared.MemoryCommittedBytes = $cooked
                        }
                        elseif ($path -like '*\memory\commit limit*') {
                            $shared.MemoryCommitLimit = $cooked
                        }
                        elseif ($path -like '*\memory\pages/sec*') {
                            $shared.MemoryPagesPerSec = $cooked
                        }
                        elseif ($path -like '*\memory\page faults/sec*') {
                            $shared.MemoryPageFaultsPerSec = $cooked
                        }
                        elseif ($path -like '*\physicaldisk(_total)\disk read bytes/sec*') {
                            $shared.DiskReadBytesPerSec = $cooked
                        }
                        elseif ($path -like '*\physicaldisk(_total)\disk write bytes/sec*') {
                            $shared.DiskWriteBytesPerSec = $cooked
                        }
                        elseif ($path -like '*\physicaldisk(_total)\% disk time*') {
                            $shared.DiskPercentTime = $cooked
                        }
                        elseif ($path -match '\\network interface\(([^)]+)\)\\bytes total/sec') {
                            $interfaceName = $Matches[1]
                            if ($interfaceName -ne '_total') {
                                $netInterfaces[$interfaceName] = $cooked
                            }
                            $netTotal += $cooked
                        }
                        elseif ($path -match '\\process\(([^)]+)\)\\(.+)$') {
                            $instance = $Matches[1]
                            $counterName = $Matches[2].ToLower().Trim()
                            
                            if ($instance -eq '_total' -or $instance -eq 'idle') {
                                continue
                            }
                            
                            if (-not $procHashes.ContainsKey($instance)) {
                                $procHashes[$instance] = @{
                                    Instance = $instance
                                    Name = $instance -replace '#\d+$', ''
                                    PID = $null
                                    CPU = 0.0
                                    WS = 0.0
                                    Private = 0.0
                                    Commit = 0.0
                                    Threads = 0
                                    Handles = 0
                                    IORead = 0.0
                                    IOWrite = 0.0
                                    Network = 0.0
                                    PageFaults = 0.0
                                }
                            }
                            
                            switch ($counterName) {
                                'id process' { $procHashes[$instance]['PID'] = [int]$cooked }
                                '% processor time' { $procHashes[$instance]['CPU'] = [double]$cooked / $numProcs }
                                'working set' { $procHashes[$instance]['WS'] = [double]$cooked }
                                'working set - private' { $procHashes[$instance]['Private'] = [double]$cooked }
                                'private bytes' { $procHashes[$instance]['Commit'] = [double]$cooked }
                                'thread count' { $procHashes[$instance]['Threads'] = [int]$cooked }
                                'handle count' { $procHashes[$instance]['Handles'] = [int]$cooked }
                                'io read bytes/sec' { $procHashes[$instance]['IORead'] = [double]$cooked }
                                'io write bytes/sec' { $procHashes[$instance]['IOWrite'] = [double]$cooked }
                                'io other bytes/sec' { $procHashes[$instance]['Network'] = [double]$cooked }
                                'page faults/sec' { $procHashes[$instance]['PageFaults'] = [double]$cooked }
                            }
                        }
                    }
                    
                    $shared.NetworkBytesTotalPerSec = $netTotal
                    
                    $allProcs = [System.Collections.ArrayList]::new()
                    foreach ($h in $procHashes.Values) {
                        if ($null -ne $h['PID'] -and $h['PID'] -gt 0) {
                            # Calculate total disk for overview
                            $h['DiskTotal'] = $h['IORead'] + $h['IOWrite']
                            $allProcs.Add([PSCustomObject]$h) | Out-Null
                        }
                    }
                    
                    $shared.OverviewProcesses = [System.Collections.ArrayList]::new(($allProcs | Sort-Object CPU -Descending))
                    $shared.CPUProcesses = $shared.OverviewProcesses
                    $shared.MemoryProcesses = [System.Collections.ArrayList]::new(($allProcs | Sort-Object WS -Descending))
                    $shared.DiskProcesses = [System.Collections.ArrayList]::new(($allProcs | Sort-Object DiskTotal -Descending))
                    $shared.NetworkProcesses = [System.Collections.ArrayList]::new(($allProcs | Sort-Object Network -Descending))
                    
                    $netInterfaceList = [System.Collections.ArrayList]::new()
                    foreach ($k in $netInterfaces.Keys) {
                        $netInterfaceList.Add([PSCustomObject]@{
                            Interface = $k
                            BytesTotalPerSec = $netInterfaces[$k]
                        }) | Out-Null
                    }
                    $shared.NetworkInterfaces = $netInterfaceList
                    
                    $shared.NewDataAvailable = $true
                    $shared.ErrorMsg = ""
                }
            } catch {
                $shared.ErrorMsg = $_.ToString()
            }
            
            $elapsed = (Get-Date) - $startLoop
            $sleepMs = 1000 - $elapsed.TotalMilliseconds
            if ($sleepMs -lt 100) { $sleepMs = 100 }
            
            $sleepSteps = [int]($sleepMs / 50)
            for ($s = 0; $s -lt $sleepSteps; $s++) {
                if (-not $shared.Running) { break }
                Start-Sleep -Milliseconds 50
            }
        }
    }
    
    $global:perfRunspace = [runspacefactory]::CreateRunspace()
    $global:perfRunspace.Open()
    
    $global:perfPowerShell = [PowerShell]::Create()
    $global:perfPowerShell.Runspace = $global:perfRunspace
    
    [void]$global:perfPowerShell.AddScript($collectorScript).AddArgument($global:sharedData)
    $global:perfAsyncResult = $global:perfPowerShell.BeginInvoke()
}

function Stop-PerformanceDataCollector {
    if ($null -ne $global:sharedData) {
        $global:sharedData.Running = $false
    }
    if ($null -ne $global:perfPowerShell) {
        try {
            $global:perfPowerShell.EndInvoke($global:perfAsyncResult)
        } catch {}
        $global:perfPowerShell.Dispose()
        $global:perfPowerShell = $null
    }
    if ($null -ne $global:perfRunspace) {
        $global:perfRunspace.Close()
        $global:perfRunspace.Dispose()
        $global:perfRunspace = $null
    }
}

function Get-ActiveResMonData {
    if ($null -eq $global:sharedData) { return $false }
    if ($global:sharedData.NewDataAvailable) {
        $global:sharedData.NewDataAvailable = $false
        
        $global:cpuUsage = $global:sharedData.CPUUsage
        $global:memAvailable = $global:sharedData.MemoryAvailableBytes
        $global:memCommitted = $global:sharedData.MemoryCommittedBytes
        $global:memCommitLimit = $global:sharedData.MemoryCommitLimit
        $global:memPagesPerSec = $global:sharedData.MemoryPagesPerSec
        $global:memPageFaults = $global:sharedData.MemoryPageFaultsPerSec
        $global:memTotal = $global:sharedData.MemoryTotalPhysical
        $global:memUsed = $global:sharedData.MemoryUsedPhysical
        
        $global:diskRead = $global:sharedData.DiskReadBytesPerSec
        $global:diskWrite = $global:sharedData.DiskWriteBytesPerSec
        $global:diskActive = $global:sharedData.DiskPercentTime
        
        $global:networkTotal = $global:sharedData.NetworkBytesTotalPerSec
        $global:networkInterfaces = $global:sharedData.NetworkInterfaces.Clone()
        
        $global:overviewProcs = $global:sharedData.OverviewProcesses.Clone()
        $global:cpuProcs = $global:sharedData.CPUProcesses.Clone()
        $global:memProcs = $global:sharedData.MemoryProcesses.Clone()
        $global:diskProcs = $global:sharedData.DiskProcesses.Clone()
        $global:networkProcs = $global:sharedData.NetworkProcesses.Clone()
        
        return $true
    }
    return $false
}

# --- ROLLING GRAPH RENDERER ---
function Draw-RollingGraph($startX, $startY, $width, $height, $history, $maxVal, $colorName, $unit) {
    if ($maxVal -le 0) { $maxVal = 1 }
    
    $axisWidth = 0
    if ($width -ge 18) { $axisWidth = 11 }
    $drawWidth = $width - $axisWidth
    
    $historyCount = if ($null -eq $history) { 0 } else { $history.Count }
    $data = [double[]]::new($drawWidth)
    for ($i = 0; $i -lt $drawWidth; $i++) {
        $histIndex = $historyCount - $drawWidth + $i
        if ($histIndex -ge 0 -and $histIndex -lt $historyCount) {
            $data[$i] = [double]$history[$histIndex]
        } else {
            $data[$i] = 0.0
        }
    }
    
    $reset = Get-ANSIColor 'Reset'
    $color = Get-ANSIColor $colorName
    $axisColor = Get-ANSIColor 'Gray'
    
    for ($row = $height - 1; $row -ge 0; $row--) {
        $axisPart = ""
        if ($axisWidth -gt 0) {
            $label = ""
            $tick = "│"
            if ($row -eq $height - 1) {
                if ($unit -eq '%') { $label = "100 %" }
                else {
                    if ($maxVal -ge 1GB) { $maxStr = "{0:N1} GB" -f ($maxVal / 1GB) }
                    elseif ($maxVal -ge 1MB) { $maxStr = "{0:N1} MB" -f ($maxVal / 1MB) }
                    elseif ($maxVal -ge 1KB) { $maxStr = "{0:N1} KB" -f ($maxVal / 1KB) }
                    else { $maxStr = "{0:N0} B" -f $maxVal }
                    $label = "$maxStr/s"
                }
                $tick = "┤"
            }
            elseif ($row -eq 0) {
                if ($unit -eq '%') { $label = "0 %" } else { $label = "0 B/s" }
                $tick = "┴"
            }
            elseif ($row -eq [int]($height / 2)) {
                if ($unit -eq '%') { $label = "50 %" }
                else {
                    $midVal = $maxVal / 2
                    if ($midVal -ge 1GB) { $midStr = "{0:N1} GB" -f ($midVal / 1GB) }
                    elseif ($midVal -ge 1MB) { $midStr = "{0:N1} MB" -f ($midVal / 1MB) }
                    elseif ($midVal -ge 1KB) { $midStr = "{0:N1} KB" -f ($midVal / 1KB) }
                    else { $midStr = "{0:N0} B" -f $midVal }
                    $label = "$midStr/s"
                }
                $tick = "┤"
            }
            
            $paddedLabel = $label.PadLeft(10)
            $axisPart = "$axisColor$paddedLabel$tick$reset"
        }
        
        $lineChars = [char[]]::new($drawWidth)
        for ($col = 0; $col -lt $drawWidth; $col++) {
            $val = $data[$col]
            $h = ($val / $maxVal) * $height
            if ($h -gt $row) {
                $lineChars[$col] = '+'
            } else {
                $lineChars[$col] = ' '
            }
        }
        
        $y = $startY + ($height - 1 - $row)
        Set-Cursor $startX $y
        $rowStr = [string]::new($lineChars)
        [Console]::Write("$axisPart$color$rowStr$reset")
    }
}

# --- DETAILS / HEALTH ALERTS PANEL ---
function Draw-ResMonSummary($tabIndex, $width, $height) {
    $startX = $global:leftWidth + 2
    $startY = $global:mainHeight + 2
    $summaryHeight = $height - $global:mainHeight - 4
    if ($summaryHeight -lt 1) { return }
    
    $reset = Get-ANSIColor 'Reset'
    $lines = [System.Collections.ArrayList]::new()
    $alertColor = 'Reset'
    $alertStartIdx = $null
    
    switch ($tabIndex) {
        0 {
            $lines.Add(" SYSTEM OVERVIEW METRICS") | Out-Null
            $lines.Add(" ───────────────────────") | Out-Null
            $lines.Add(" Global CPU Usage: $("{0:F1} %" -f $global:cpuUsage)") | Out-Null
            $memPct = if ($global:memTotal -gt 0) { ($global:memUsed / $global:memTotal) * 100 } else { 0 }
            $lines.Add(" Physical RAM Used: $(Format-Bytes $global:memUsed) / $(Format-Bytes $global:memTotal) ($("{0:F1} %" -f $memPct))") | Out-Null
            $lines.Add(" Commit Charge: $(Format-Bytes $global:memCommitted) / $(Format-Bytes $global:memCommitLimit)") | Out-Null
            $lines.Add(" Disk Read/Write: Read: $(Format-Bytes $global:diskRead)/s | Write: $(Format-Bytes $global:diskWrite)/s") | Out-Null
            $lines.Add(" Disk Active Time: $("{0:F1} %" -f $global:diskActive)") | Out-Null
            $lines.Add(" Network Bandwidth: Total: $(Format-Bytes $global:networkTotal)/s") | Out-Null
        }
        1 {
            $lines.Add(" CPU METRICS & SUMMARY") | Out-Null
            $lines.Add(" ─────────────────────") | Out-Null
            $lines.Add(" Logical Processor Count: $([Environment]::ProcessorCount)") | Out-Null
            $lines.Add(" Overall CPU Usage: $("{0:F1} %" -f $global:cpuUsage)") | Out-Null
            if ($global:cpuProcs -and $global:cpuProcs.Count -gt 0) {
                $top = $global:cpuProcs[0]
                $lines.Add(" Top Process CPU Hog: $($top.Name) (PID: $($top.PID)) - $("{0:F1} %" -f $top.CPU)") | Out-Null
            } else {
                $lines.Add(" Top Process CPU Hog: None") | Out-Null
            }
        }
        2 {
            $lines.Add(" PHYSICAL RAM & SWAP PAGE FILE") | Out-Null
            $lines.Add(" ─────────────────────────────") | Out-Null
            $lines.Add(" Available Physical Memory: $(Format-Bytes $global:memAvailable)") | Out-Null
            $lines.Add(" Used Physical Memory: $(Format-Bytes $global:memUsed) / $(Format-Bytes $global:memTotal)") | Out-Null
            
            $commitPct = if ($global:memCommitLimit -gt 0) { ($global:memCommitted / $global:memCommitLimit) * 100 } else { 0 }
            $lines.Add(" Commit Charge Status: $(Format-Bytes $global:memCommitted) / $(Format-Bytes $global:memCommitLimit) ($("{0:F1} %" -f $commitPct))") | Out-Null
            $lines.Add(" Memory Paging Rates: Pages/sec: $("{0:F1}" -f $global:memPagesPerSec) | Page Faults/sec: $("{0:F1}" -f $global:memPageFaults)") | Out-Null
            $lines.Add("") | Out-Null
            
            $isExhausted = ($global:memAvailable -lt 512MB) -or ($commitPct -gt 95)
            $isThrashing = ($commitPct -gt 80) -and (($global:memPagesPerSec -gt 150) -or ($global:memPageFaults -gt 8000))
            
            if ($isExhausted) {
                $lines.Add(" ┌─────────────────────────────────────────────────────────────┐") | Out-Null
                $lines.Add(" │ CRITICAL ALERT: PHYSICAL MEMORY EXHAUSTED │") | Out-Null
                $lines.Add(" │ Available RAM is critically low. Close active programs. │") | Out-Null
                $lines.Add(" └─────────────────────────────────────────────────────────────┘") | Out-Null
                $alertColor = 'Error'
                $alertStartIdx = $lines.Count - 4
            } elseif ($isThrashing) {
                $lines.Add(" ┌─────────────────────────────────────────────────────────────┐") | Out-Null
                $lines.Add(" │ WARNING ALERT: RAM THRASHING DETECTED │") | Out-Null
                $lines.Add(" │ High paging rates. Hard disk swapping is slowing down. │") | Out-Null
                $lines.Add(" └─────────────────────────────────────────────────────────────┘") | Out-Null
                $alertColor = 'Warning'
                $alertStartIdx = $lines.Count - 4
            } else {
                $lines.Add(" ┌─────────────────────────────────────────────────────────────┐") | Out-Null
                $lines.Add(" │ STATUS: SYSTEM MEMORY HEALTHY │") | Out-Null
                $lines.Add(" │ RAM utilization and paging activity are within normal range.│") | Out-Null
                $lines.Add(" └─────────────────────────────────────────────────────────────┘") | Out-Null
                $alertColor = 'Info'
                $alertStartIdx = $lines.Count - 4
            }
        }
        3 {
            $lines.Add(" DISK ACTIVITY METRICS") | Out-Null
            $lines.Add(" ─────────────────────") | Out-Null
            $lines.Add(" Total Disk Read Rate: $(Format-Bytes $global:diskRead)/s") | Out-Null
            $lines.Add(" Total Disk Write Rate: $(Format-Bytes $global:diskWrite)/s") | Out-Null
            $lines.Add(" Physical Disk Active %: $("{0:F1} %" -f $global:diskActive)") | Out-Null
        }
        4 {
            $lines.Add(" NETWORK BANDWIDTH SUMMARY") | Out-Null
            $lines.Add(" ─────────────────────────") | Out-Null
            $lines.Add(" Total Interface Activity: $(Format-Bytes $global:networkTotal)/s") | Out-Null
            $lines.Add("") | Out-Null
            $lines.Add(" Network Interfaces:") | Out-Null
            
            $activeIfaces = $global:networkInterfaces | Where-Object { $_.BytesTotalPerSec -gt 0 } | Sort-Object BytesTotalPerSec -Descending
            if ($activeIfaces) {
                foreach ($iface in $activeIfaces) {
                    $lines.Add(" - $($iface.Interface): $(Format-Bytes $iface.BytesTotalPerSec)/s") | Out-Null
                }
            } else {
                $allIfaces = $global:networkInterfaces | Sort-Object Interface
                if ($allIfaces) {
                    foreach ($iface in ($allIfaces | Select-Object -First 3)) {
                        $lines.Add(" - $($iface.Interface): 0 B/s") | Out-Null
                    }
                } else {
                    $lines.Add(" - No network interfaces detected.") | Out-Null
                }
            }
        }
    }
    
    for ($i = 0; $i -lt $summaryHeight; $i++) {
        $y = $startY + $i
        $lineText = ""
        $color = 'Reset'
        
        if ($i -lt $lines.Count) {
            $lineText = $lines[$i]
            if ($tabIndex -eq 2 -and $null -ne $alertStartIdx -and $i -ge $alertStartIdx) {
                $color = $alertColor
            }
        }
        
        $padded = $lineText.PadRight($width).Substring(0, $width)
        Write-At $startX $y $padded $color
    }
}

# --- STATE VARIABLES ---
$global:resMonSelectedIndex = 0
$resMonSelectedIndex = 0
$resMonScrollOffset = 0

$global:cpuUsage = 0.0
$global:memAvailable = 0.0
$global:memCommitted = 0.0
$global:memCommitLimit = 0.0
$global:memPagesPerSec = 0.0
$global:memPageFaults = 0.0
$global:memTotal = 0.0
$global:memUsed = 0.0
$global:diskRead = 0.0
$global:diskWrite = 0.0
$global:diskActive = 0.0
$global:networkTotal = 0.0

$global:overviewProcs = [System.Collections.ArrayList]::new()
$global:cpuProcs = [System.Collections.ArrayList]::new()
$global:memProcs = [System.Collections.ArrayList]::new()
$global:diskProcs = [System.Collections.ArrayList]::new()
$global:networkProcs = [System.Collections.ArrayList]::new()
$global:networkInterfaces = [System.Collections.ArrayList]::new()

$global:cpuHistory = [System.Collections.ArrayList]::new()
$global:memHistory = [System.Collections.ArrayList]::new()
$global:diskHistory = [System.Collections.ArrayList]::new()
$global:networkHistory = [System.Collections.ArrayList]::new()

$resMonTabs = [System.Collections.ArrayList]::new(@(
    [PSCustomObject]@{ Label = "Overview"; Level = 0; IsLeaf = $true }
    [PSCustomObject]@{ Label = "CPU"; Level = 0; IsLeaf = $true }
    [PSCustomObject]@{ Label = "Memory"; Level = 0; IsLeaf = $true }
    [PSCustomObject]@{ Label = "Disk"; Level = 0; IsLeaf = $true }
    [PSCustomObject]@{ Label = "Network"; Level = 0; IsLeaf = $true }
))

# Columns config
$overviewCols = @(
    @{ Label = "Image Name"; Prop = "Name"; Align = "Left"; Width = 15 }
    @{ Label = "PID"; Prop = "PID"; Align = "Right"; Width = 8 }
    @{ Label = "CPU %"; Prop = "CPU"; Align = "Right"; Width = 8; Format = "Percent" }
    @{ Label = "Working Set"; Prop = "WS"; Align = "Right"; Width = 12; Format = "MB" }
    @{ Label = "Disk Total"; Prop = "DiskTotal"; Align = "Right"; Width = 12; Format = "Bytes" }
)
$cpuCols = @(
    @{ Label = "Image Name"; Prop = "Name"; Align = "Left"; Width = 15 }
    @{ Label = "PID"; Prop = "PID"; Align = "Right"; Width = 8 }
    @{ Label = "CPU %"; Prop = "CPU"; Align = "Right"; Width = 8; Format = "Percent" }
    @{ Label = "Threads"; Prop = "Threads"; Align = "Right"; Width = 8 }
    @{ Label = "Handles"; Prop = "Handles"; Align = "Right"; Width = 8 }
)
$memCols = @(
    @{ Label = "Image Name"; Prop = "Name"; Align = "Left"; Width = 15 }
    @{ Label = "PID"; Prop = "PID"; Align = "Right"; Width = 8 }
    @{ Label = "Commit"; Prop = "Commit"; Align = "Right"; Width = 12; Format = "MB" }
    @{ Label = "Working Set"; Prop = "WS"; Align = "Right"; Width = 12; Format = "MB" }
    @{ Label = "Private"; Prop = "Private"; Align = "Right"; Width = 12; Format = "MB" }
    @{ Label = "Page Faults"; Prop = "PageFaults"; Align = "Right"; Width = 12; Format = "Decimal" }
)
$diskCols = @(
    @{ Label = "Image Name"; Prop = "Name"; Align = "Left"; Width = 15 }
    @{ Label = "PID"; Prop = "PID"; Align = "Right"; Width = 8 }
    @{ Label = "Read Bytes"; Prop = "IORead"; Align = "Right"; Width = 12; Format = "Bytes" }
    @{ Label = "Write Bytes"; Prop = "IOWrite"; Align = "Right"; Width = 12; Format = "Bytes" }
    @{ Label = "Total Bytes"; Prop = "DiskTotal"; Align = "Right"; Width = 12; Format = "Bytes" }
)
$networkCols = @(
    @{ Label = "Image Name"; Prop = "Name"; Align = "Left"; Width = 15 }
    @{ Label = "PID"; Prop = "PID"; Align = "Right"; Width = 8 }
    @{ Label = "I/O Other"; Prop = "Network"; Align = "Right"; Width = 15; Format = "Bytes" }
)

# Start background performance runspace collector
Start-PerformanceDataCollector

$width = [Console]::WindowWidth
$height = [Console]::WindowHeight
Update-LayoutDimensions $width $height
$rightWidth = $width - $global:leftWidth - 4

$global:focusArea = 0

Initialize-Console
[Console]::Write("$ESC[2J")

$redrawAll = $true
$needsTreeRedraw = $true
$needsTableRedraw = $true
$needsDetailsRedraw = $true
$needsStatusRedraw = $true
$statusText = "Gathering telemetry... Use Up/Down to navigate tabs."

try {
    while ($true) {
        # 1. Resize Check
        $newWidth = [Console]::WindowWidth
        $newHeight = [Console]::WindowHeight
        if ($newWidth -ne $width -or $newHeight -ne $height) {
            $width = $newWidth
            $height = $newHeight
            Update-LayoutDimensions $width $height
            $rightWidth = $width - $global:leftWidth - 4
            
            [Console]::Write("$ESC[2J")
            $redrawAll = $true
        }
        
        # 2. Telemetry Refresh (once per second)
        if (Get-ActiveResMonData) {
            $tableWidth = [int]($rightWidth * 0.6)
            $graphWidth = $rightWidth - $tableWidth - 1
            $maxHistory = $graphWidth
            if ($maxHistory -lt 10) { $maxHistory = 10 }
            
            # CPU
            $global:cpuHistory.Add($global:cpuUsage) | Out-Null
            while ($global:cpuHistory.Count -gt $maxHistory) { $global:cpuHistory.RemoveAt(0) }
            
            # Memory
            $memPercent = if ($global:memTotal -gt 0) { ($global:memUsed / $global:memTotal) * 100 } else { 0 }
            $global:memHistory.Add($memPercent) | Out-Null
            while ($global:memHistory.Count -gt $maxHistory) { $global:memHistory.RemoveAt(0) }
            
            # Disk
            $global:diskHistory.Add($global:diskActive) | Out-Null
            while ($global:diskHistory.Count -gt $maxHistory) { $global:diskHistory.RemoveAt(0) }
            
            # Network
            $global:networkHistory.Add($global:networkTotal) | Out-Null
            while ($global:networkHistory.Count -gt $maxHistory) { $global:networkHistory.RemoveAt(0) }
            
            $needsTableRedraw = $true
            $needsDetailsRedraw = $true
        }
        
        # 3. Redraw Screen
        if ($redrawAll) {
            Draw-Borders $width $height $global:leftWidth $global:mainHeight $global:focusArea "CATEGORIES" "PROCESS LIST" "SYSTEM SUMMARY" -RightTopWidthPercent 0.6
            Draw-Menu $width @("Ctrl+1: Overview", "Ctrl+2: CPU", "Ctrl+3: Memory", "Ctrl+4: Disk", "Ctrl+5: Network", "Ctrl+Q: Exit")
            $needsTreeRedraw = $true
            $needsTableRedraw = $true
            $needsDetailsRedraw = $true
            $needsStatusRedraw = $true
            $redrawAll = $false
        }
        
        if ($needsTreeRedraw) {
            # Use Tree component mapping
            Draw-TreeView $resMonTabs $global:resMonSelectedIndex 0 $global:leftWidth ($height - 4) ($global:focusArea -eq 0)
            $needsTreeRedraw = $false
        }
        
        if ($needsTableRedraw) {
            $tableWidth = [int]($rightWidth * 0.6)
            $cols = switch ($global:resMonSelectedIndex) {
                0 { $overviewCols }
                1 { $cpuCols }
                2 { $memCols }
                3 { $diskCols }
                4 { $networkCols }
            }
            $activeProcs = switch ($global:resMonSelectedIndex) {
                0 { $global:overviewProcs }
                1 { $global:cpuProcs }
                2 { $global:memProcs }
                3 { $global:diskProcs }
                4 { $global:networkProcs }
            }
            
            # Dynamically size 'Name' column to fill table width
            $totalW = 0
            foreach ($col in $cols) {
                if ($col.Prop -ne 'Name') { $totalW += $col.Width + 1 }
            }
            $nameCol = $cols | Where-Object { $_.Prop -eq 'Name' }
            if ($nameCol) {
                $nameCol.Width = $tableWidth - $totalW - 1
                if ($nameCol.Width -lt 8) { $nameCol.Width = 8 }
            }
            
            Draw-TableHeader ($global:leftWidth + 2) 2 $cols $tableWidth
            Draw-TableRows $activeProcs $resMonSelectedIndex $resMonScrollOffset $cols ($global:leftWidth + 2) 3 $tableWidth ($global:mainHeight - 2) ($global:focusArea -eq 1)
            
            # Draw Rolling Graph
            $graphWidth = $rightWidth - $tableWidth - 1
            $graphX = $global:leftWidth + 3 + $tableWidth
            $graphHistory = switch ($global:resMonSelectedIndex) {
                0 { $global:cpuHistory }
                1 { $global:cpuHistory }
                2 { $global:memHistory }
                3 { $global:diskHistory }
                4 { $global:networkHistory }
            }
            $maxVal = switch ($global:resMonSelectedIndex) {
                0 { 100.0 }
                1 { 100.0 }
                2 { 100.0 }
                3 { 100.0 }
                4 {
                    $maxN = 100KB
                    foreach ($v in $global:networkHistory) { if ($v -gt $maxN) { $maxN = $v } }
                    $maxN
                }
            }
            $colorName = switch ($global:resMonSelectedIndex) {
                0 { 'Cyan' }
                1 { 'Cyan' }
                2 { 'Warning' }
                3 { 'Info' }
                4 { 'Blue' }
            }
            $unit = if ($global:resMonSelectedIndex -eq 4) { '/s' } else { '%' }
            Draw-RollingGraph $graphX 2 $graphWidth ($global:mainHeight - 1) $graphHistory $maxVal $colorName $unit
            
            $needsTableRedraw = $false
        }
        
        if ($needsDetailsRedraw) {
            Draw-ResMonSummary $global:resMonSelectedIndex $rightWidth $height
            $needsDetailsRedraw = $false
        }
        
        if ($needsStatusRedraw) {
            $focusTxt = switch ($global:focusArea) {
                0 { "Categories" }
                1 { "Process List" }
                2 { "Summary" }
            }
            Draw-Status $statusText $focusTxt "Tab: Switch Pane" $width $height
            $needsStatusRedraw = $false
        }
        
        Set-Cursor 0 0
        
        # 4. Input handling
        if (-not [Console]::KeyAvailable) {
            Start-Sleep -Milliseconds 15
            continue
        }
        
        $key = [Console]::ReadKey($true)
        $isCtrl = ($key.Modifiers -band [System.ConsoleModifiers]::Control) -eq [System.ConsoleModifiers]::Control
        
        if ($isCtrl) {
            if ($key.Key -eq 'D1') {
                $global:resMonSelectedIndex = 0
                $resMonSelectedIndex = 0
                $resMonScrollOffset = 0
                $statusText = "Selected Overview category."
                $redrawAll = $true
                continue
            }
            if ($key.Key -eq 'D2') {
                $global:resMonSelectedIndex = 1
                $resMonSelectedIndex = 0
                $resMonScrollOffset = 0
                $statusText = "Selected CPU category."
                $redrawAll = $true
                continue
            }
            if ($key.Key -eq 'D3') {
                $global:resMonSelectedIndex = 2
                $resMonSelectedIndex = 0
                $resMonScrollOffset = 0
                $statusText = "Selected Memory category."
                $redrawAll = $true
                continue
            }
            if ($key.Key -eq 'D4') {
                $global:resMonSelectedIndex = 3
                $resMonSelectedIndex = 0
                $resMonScrollOffset = 0
                $statusText = "Selected Disk category."
                $redrawAll = $true
                continue
            }
            if ($key.Key -eq 'D5') {
                $global:resMonSelectedIndex = 4
                $resMonSelectedIndex = 0
                $resMonScrollOffset = 0
                $statusText = "Selected Network category."
                $redrawAll = $true
                continue
            }
            if ($key.Key -eq 'Q') {
                break
            }
        }
        
        # Focus Panel keys
        if ($global:focusArea -eq 0) {
            # Categories
            if ($key.Key -eq 'UpArrow') {
                if ($global:resMonSelectedIndex -gt 0) {
                    $global:resMonSelectedIndex--
                    $resMonSelectedIndex = 0
                    $resMonScrollOffset = 0
                    $needsTreeRedraw = $true
                    $needsTableRedraw = $true
                    $needsDetailsRedraw = $true
                }
            }
            elseif ($key.Key -eq 'DownArrow') {
                if ($global:resMonSelectedIndex -lt 4) {
                    $global:resMonSelectedIndex++
                    $resMonSelectedIndex = 0
                    $resMonScrollOffset = 0
                    $needsTreeRedraw = $true
                    $needsTableRedraw = $true
                    $needsDetailsRedraw = $true
                }
            }
            elseif ($key.Key -eq 'Tab') {
                $global:focusArea = 1
                $redrawAll = $true
            }
        }
        elseif ($global:focusArea -eq 1) {
            # Process List
            $tableHeight = $global:mainHeight - 1
            $activeProcs = switch ($global:resMonSelectedIndex) {
                0 { $global:overviewProcs }
                1 { $global:cpuProcs }
                2 { $global:memProcs }
                3 { $global:diskProcs }
                4 { $global:networkProcs }
            }
            $procsCount = if ($null -eq $activeProcs) { 0 } else { $activeProcs.Count }
            
            if ($key.Key -eq 'UpArrow') {
                if ($resMonSelectedIndex -gt 0) {
                    $resMonSelectedIndex--
                    if ($resMonSelectedIndex -lt $resMonScrollOffset) { $resMonScrollOffset = $resMonSelectedIndex }
                    $needsTableRedraw = $true
                }
            }
            elseif ($key.Key -eq 'DownArrow') {
                if ($resMonSelectedIndex -lt ($procsCount - 1)) {
                    $resMonSelectedIndex++
                    if ($resMonSelectedIndex -ge ($resMonScrollOffset + $tableHeight)) {
                        $resMonScrollOffset = $resMonSelectedIndex - $tableHeight + 1
                    }
                    $needsTableRedraw = $true
                }
            }
            elseif ($key.Key -eq 'PageUp') {
                $resMonSelectedIndex = [Math]::Max(0, $resMonSelectedIndex - $tableHeight)
                $resMonScrollOffset = [Math]::Max(0, $resMonScrollOffset - $tableHeight)
                if ($resMonSelectedIndex -lt $resMonScrollOffset) { $resMonSelectedIndex = $resMonScrollOffset }
                $needsTableRedraw = $true
            }
            elseif ($key.Key -eq 'PageDown') {
                $resMonSelectedIndex = [Math]::Min($procsCount - 1, $resMonSelectedIndex + $tableHeight)
                $resMonScrollOffset = [Math]::Min($procsCount - $tableHeight, $resMonScrollOffset + $tableHeight)
                if ($resMonScrollOffset -lt 0) { $resMonScrollOffset = 0 }
                if ($resMonSelectedIndex -ge ($resMonScrollOffset + $tableHeight)) {
                    $resMonSelectedIndex = $resMonScrollOffset + $tableHeight - 1
                }
                $needsTableRedraw = $true
            }
            elseif ($key.Key -eq 'Escape') {
                $global:focusArea = 0
                $redrawAll = $true
            }
            elseif ($key.Key -eq 'Tab') {
                $isShift = ($key.Modifiers -band [System.ConsoleModifiers]::Shift) -eq [System.ConsoleModifiers]::Shift
                $global:focusArea = if ($isShift) { 0 } else { 2 }
                $redrawAll = $true
            }
        }
        elseif ($global:focusArea -eq 2) {
            # Summary
            if ($key.Key -eq 'Tab') {
                $isShift = ($key.Modifiers -band [System.ConsoleModifiers]::Shift) -eq [System.ConsoleModifiers]::Shift
                $global:focusArea = if ($isShift) { 1 } else { 0 }
                $redrawAll = $true
            }
            elseif ($key.Key -eq 'Escape') {
                $global:focusArea = 0
                $redrawAll = $true
            }
        }
    }
} finally {
    Stop-PerformanceDataCollector
    Restore-Console
}