private/StatusPanel.ps1

# FR-024: fixed, live activity panel pinned to the bottom of the console, using native
# cursor positioning (no external TUI dependency - research.md). Shown during both dry-run
# scanning and apply.
#
# ponytail: fixed-row overwrite of a single reserved block, not a real scrolling split-pane.
# Callers must bracket a scan/apply block with Initialize-OctaActivityPanel ... Write-OctaActivity
# (repeatable) ... Complete-OctaActivityPanel, and must not Write-Host anything else in between -
# upgrade to a proper scroll region if a future screen needs to interleave the two.

$script:OctaActivityLog = [System.Collections.Generic.List[string]]::new()
$script:OctaActivityPanelEnabled = $false
$script:OctaActivityPanelRow = -1
$script:OctaActivityPanelLines = 4 # divider + header + 2 most-recent events

function Test-OctaInteractiveConsole {
    try {
        return ($null -ne $Host.UI.RawUI -and -not [Console]::IsOutputRedirected)
    }
    catch {
        return $false
    }
}

function Initialize-OctaActivityPanel {
    [CmdletBinding()]
    param()

    $script:OctaActivityLog.Clear()
    $script:OctaActivityPanelEnabled = Test-OctaInteractiveConsole
    if (-not $script:OctaActivityPanelEnabled) { return }

    for ($i = 0; $i -lt $script:OctaActivityPanelLines; $i++) { Write-Host '' }
    $script:OctaActivityPanelRow = $Host.UI.RawUI.CursorPosition.Y - $script:OctaActivityPanelLines
}

function Write-OctaActivity {
    [CmdletBinding()]
    param([Parameter(Mandatory)][string]$Message)

    if (-not $script:OctaActivityPanelEnabled) {
        # Non-interactive / redirected-output fallback: plain scrolling line, same content.
        Write-Host $Message
        return
    }

    $script:OctaActivityLog.Add($Message)
    if ($script:OctaActivityLog.Count -gt 2) {
        $script:OctaActivityLog.RemoveAt(0)
    }

    $savedCursor = $Host.UI.RawUI.CursorPosition
    $width = $Host.UI.RawUI.WindowSize.Width

    $lines = @(('-' * [Math]::Min(64, $width)), 'Actividad en vivo / Live activity:') + $script:OctaActivityLog
    while ($lines.Count -lt $script:OctaActivityPanelLines) { $lines += '' }

    for ($i = 0; $i -lt $script:OctaActivityPanelLines; $i++) {
        $Host.UI.RawUI.CursorPosition = [System.Management.Automation.Host.Coordinates]::new(0, $script:OctaActivityPanelRow + $i)
        $text = $lines[$i]
        if ($text.Length -gt $width) { $text = $text.Substring(0, $width) }
        Write-Host $text.PadRight($width) -NoNewline
    }

    $Host.UI.RawUI.CursorPosition = $savedCursor
}

function Complete-OctaActivityPanel {
    <#
        Moves the cursor past the reserved panel rows so anything printed afterwards (the
        dry-run preview list, a confirmation prompt, etc.) starts on a fresh line below it.
    #>

    [CmdletBinding()]
    param()

    if (-not $script:OctaActivityPanelEnabled) { return }
    $Host.UI.RawUI.CursorPosition = [System.Management.Automation.Host.Coordinates]::new(0, $script:OctaActivityPanelRow + $script:OctaActivityPanelLines)
}