modules/LiveDisplay/PerformanceOptimizer.psm1

# Performance Optimizer Module for Live Display
# Implements differential screen updates, frame rate limiting, and memory optimization

using namespace System.Management.Automation
using namespace System.Text
using namespace System.Collections.Generic

# Differential screen update manager
class DifferentialScreenUpdater {
    [hashtable] $LastScreenState = @{}
    [StringBuilder] $UpdateBuffer
    [int] $ScreenWidth = 80
    [int] $ScreenHeight = 24
    [hashtable] $DirtyRegions = @{}
    [bool] $FullRedrawRequired = $true
    [int] $UpdateCount = 0
    [DateTime] $LastFullRedraw = [DateTime]::MinValue
    [int] $FullRedrawInterval = 30000  # Force full redraw every 30 seconds
    
    DifferentialScreenUpdater() {
        $this.UpdateBuffer = [StringBuilder]::new()
        $this.DetectScreenSize()
    }
    
    [void] DetectScreenSize() {
        try {
            $this.ScreenWidth = $Host.UI.RawUI.WindowSize.Width
            $this.ScreenHeight = $Host.UI.RawUI.WindowSize.Height
        } catch {
            # Use defaults if detection fails
            $this.ScreenWidth = 80
            $this.ScreenHeight = 24
        }
    }
    
    [bool] HasContentChanged([hashtable]$newContent) {
        if ($this.FullRedrawRequired) {
            return $true
        }
        
        # Check if screen size changed
        $this.DetectScreenSize()
        if ($this.LastScreenState.ContainsKey('ScreenWidth') -and 
            ($this.LastScreenState.ScreenWidth -ne $this.ScreenWidth -or 
             $this.LastScreenState.ScreenHeight -ne $this.ScreenHeight)) {
            $this.FullRedrawRequired = $true
            return $true
        }
        
        # Check for content changes
        $keyFields = @('track_id', 'track_name', 'artist_name', 'album_name', 'is_playing', 'progress_ms', 'duration_ms')
        
        foreach ($field in $keyFields) {
            $oldValue = $this.LastScreenState.ContainsKey($field) ? $this.LastScreenState[$field] : $null
            $newValue = $newContent.ContainsKey($field) ? $newContent[$field] : $null
            
            if ($field -eq 'progress_ms') {
                # Only update progress if change is significant (> 2 seconds) to reduce flicker
                if ($oldValue -and $newValue) {
                    $diff = [Math]::Abs($newValue - $oldValue)
                    if ($diff -gt 2000) {
                        $this.DirtyRegions['progress'] = $true
                        return $true
                    }
                } elseif ($oldValue -ne $newValue) {
                    $this.DirtyRegions['progress'] = $true
                    return $true
                }
            } else {
                if ($oldValue -ne $newValue) {
                    $this.DirtyRegions[$field] = $true
                    return $true
                }
            }
        }
        
        # Force full redraw periodically to prevent display corruption
        $timeSinceLastFullRedraw = [DateTime]::UtcNow - $this.LastFullRedraw
        if ($timeSinceLastFullRedraw.TotalMilliseconds -gt $this.FullRedrawInterval) {
            $this.FullRedrawRequired = $true
            return $true
        }
        
        return $false
    }
    
    [string] GenerateDifferentialUpdate([hashtable]$newContent) {
        $this.UpdateBuffer.Clear()
        
        if ($this.FullRedrawRequired) {
            # Full screen update
            $this.UpdateBuffer.Append([AnsiCodes]::ClearScreen) | Out-Null
            $this.UpdateBuffer.Append([AnsiCodes]::CursorHome) | Out-Null
            $this.GenerateFullDisplay($newContent)
            $this.FullRedrawRequired = $false
            $this.LastFullRedraw = [DateTime]::UtcNow
        } else {
            # Partial updates for dirty regions
            $this.GeneratePartialUpdates($newContent)
        }
        
        # Update last screen state
        $this.LastScreenState = $newContent.Clone()
        $this.LastScreenState['ScreenWidth'] = $this.ScreenWidth
        $this.LastScreenState['ScreenHeight'] = $this.ScreenHeight
        $this.UpdateCount++
        
        # Clear dirty regions
        $this.DirtyRegions.Clear()
        
        return $this.UpdateBuffer.ToString()
    }
    
    [void] GenerateFullDisplay([hashtable]$content) {
        # Header
        $this.UpdateBuffer.AppendLine("╔══════════════════════════════════════════════════════════════════════════════╗") | Out-Null
        $this.UpdateBuffer.AppendLine("║ Spotify Live Display ║") | Out-Null
        $this.UpdateBuffer.AppendLine("╠══════════════════════════════════════════════════════════════════════════════╣") | Out-Null
        
        # Track information
        $this.AppendTrackInfo($content)
        
        # Progress bar
        $this.AppendProgressBar($content)
        
        # Footer
        $this.UpdateBuffer.AppendLine("╚══════════════════════════════════════════════════════════════════════════════╝") | Out-Null
    }
    
    [void] GeneratePartialUpdates([hashtable]$content) {
        # Update only changed regions
        if ($this.DirtyRegions.ContainsKey('track_name') -or 
            $this.DirtyRegions.ContainsKey('artist_name') -or 
            $this.DirtyRegions.ContainsKey('album_name') -or 
            $this.DirtyRegions.ContainsKey('is_playing')) {
            
            # Move to track info area and update
            $this.UpdateBuffer.Append([AnsiCodes]::MoveCursor(4, 1)) | Out-Null
            $this.AppendTrackInfo($content)
        }
        
        if ($this.DirtyRegions.ContainsKey('progress')) {
            # Move to progress bar area and update
            $this.UpdateBuffer.Append([AnsiCodes]::MoveCursor(8, 1)) | Out-Null
            $this.AppendProgressBar($content)
        }
    }
    
    [void] AppendTrackInfo([hashtable]$content) {
        $isPlaying = $content.ContainsKey('is_playing') ? $content.is_playing : $false
        $statusIcon = if ($isPlaying) { "▶" } else { "⏸" }
        
        # Track name
        $trackName = $content.ContainsKey('track_name') ? $content.track_name : "Unknown Track"
        if ($trackName.Length -gt 70) { $trackName = $trackName.Substring(0, 67) + "..." }
        $this.UpdateBuffer.AppendLine("║ $statusIcon $($trackName.PadRight(72)) ║") | Out-Null
        
        # Artist
        $artistName = $content.ContainsKey('artist_name') ? $content.artist_name : "Unknown Artist"
        if ($artistName.Length -gt 70) { $artistName = $artistName.Substring(0, 67) + "..." }
        $this.UpdateBuffer.AppendLine("║ 👤 $($artistName.PadRight(72)) ║") | Out-Null
        
        # Album
        $albumName = $content.ContainsKey('album_name') ? $content.album_name : "Unknown Album"
        if ($albumName.Length -gt 70) { $albumName = $albumName.Substring(0, 67) + "..." }
        $this.UpdateBuffer.AppendLine("║ 📀 $($albumName.PadRight(72)) ║") | Out-Null
    }
    
    [void] AppendProgressBar([hashtable]$content) {
        if ($content.ContainsKey('duration_ms') -and $content.duration_ms -gt 0) {
            $progress = $content.ContainsKey('progress_ms') ? $content.progress_ms : 0
            $percentage = [Math]::Round(($progress / $content.duration_ms) * 100)
            
            $barWidth = 60
            $filled = [Math]::Floor(($percentage / 100) * $barWidth)
            $empty = $barWidth - $filled
            
            $progressBar = ("█" * $filled) + ("░" * $empty)
            $this.UpdateBuffer.AppendLine("║ [$progressBar] $($percentage.ToString().PadLeft(3))% ║") | Out-Null
            
            $currentTime = $this.FormatDuration($progress)
            $totalTime = $this.FormatDuration($content.duration_ms)
            $timeDisplay = "$currentTime / $totalTime"
            $this.UpdateBuffer.AppendLine("║ $($timeDisplay.PadRight(76)) ║") | Out-Null
        } else {
            $this.UpdateBuffer.AppendLine("║ No playback information available ║") | Out-Null
            $this.UpdateBuffer.AppendLine("║ ║") | Out-Null
        }
    }
    
    [string] FormatDuration([int]$milliseconds) {
        $totalSeconds = [Math]::Floor($milliseconds / 1000)
        $minutes = [Math]::Floor($totalSeconds / 60)
        $seconds = $totalSeconds % 60
        return "{0}:{1:D2}" -f $minutes, $seconds
    }
    
    [void] ForceFullRedraw() {
        $this.FullRedrawRequired = $true
    }
    
    [hashtable] GetStats() {
        return @{
            UpdateCount = $this.UpdateCount
            LastFullRedraw = $this.LastFullRedraw
            ScreenSize = @{ Width = $this.ScreenWidth; Height = $this.ScreenHeight }
            DirtyRegions = $this.DirtyRegions.Keys -join ", "
        }
    }
}

# Frame rate limiter with adaptive refresh intervals
class FrameRateLimiter {
    [int] $TargetFPS = 2  # 2 FPS for live display (500ms intervals)
    [int] $MinRefreshInterval = 500   # Minimum 500ms
    [int] $MaxRefreshInterval = 5000  # Maximum 5 seconds
    [DateTime] $LastFrameTime = [DateTime]::MinValue
    [Queue[DateTime]] $FrameHistory
    [int] $FrameHistorySize = 10
    [bool] $AdaptiveMode = $true
    [int] $CurrentRefreshInterval
    [int] $ConsecutiveSkippedFrames = 0
    [int] $MaxSkippedFrames = 3
    
    FrameRateLimiter([int]$targetFPS = 2, [bool]$adaptiveMode = $true) {
        $this.TargetFPS = $targetFPS
        $this.AdaptiveMode = $adaptiveMode
        $this.CurrentRefreshInterval = [Math]::Max($this.MinRefreshInterval, 1000 / $this.TargetFPS)
        $this.FrameHistory = [Queue[DateTime]]::new()
    }
    
    [bool] ShouldRender() {
        $now = [DateTime]::UtcNow
        $timeSinceLastFrame = $now - $this.LastFrameTime
        
        if ($timeSinceLastFrame.TotalMilliseconds -ge $this.CurrentRefreshInterval) {
            $this.RecordFrame($now)
            $this.ConsecutiveSkippedFrames = 0
            
            if ($this.AdaptiveMode) {
                $this.AdjustRefreshInterval()
            }
            
            return $true
        }
        
        $this.ConsecutiveSkippedFrames++
        return $false
    }
    
    [void] RecordFrame([DateTime]$frameTime) {
        $this.LastFrameTime = $frameTime
        $this.FrameHistory.Enqueue($frameTime)
        
        # Keep frame history size manageable
        while ($this.FrameHistory.Count -gt $this.FrameHistorySize) {
            $this.FrameHistory.Dequeue() | Out-Null
        }
    }
    
    [void] AdjustRefreshInterval() {
        if ($this.FrameHistory.Count -lt 3) {
            return
        }
        
        # Calculate actual FPS from recent frames
        $frames = $this.FrameHistory.ToArray()
        $timeSpan = $frames[-1] - $frames[0]
        $actualFPS = ($frames.Length - 1) / $timeSpan.TotalSeconds
        
        # Adjust refresh interval based on performance
        if ($actualFPS -lt ($this.TargetFPS * 0.8)) {
            # Running slower than target, increase interval
            $this.CurrentRefreshInterval = [Math]::Min($this.MaxRefreshInterval, $this.CurrentRefreshInterval * 1.2)
        } elseif ($actualFPS -gt ($this.TargetFPS * 1.2)) {
            # Running faster than target, decrease interval
            $this.CurrentRefreshInterval = [Math]::Max($this.MinRefreshInterval, $this.CurrentRefreshInterval * 0.8)
        }
    }
    
    [int] GetWaitTime() {
        $now = [DateTime]::UtcNow
        $timeSinceLastFrame = $now - $this.LastFrameTime
        $waitTime = $this.CurrentRefreshInterval - $timeSinceLastFrame.TotalMilliseconds
        return [Math]::Max(0, [int]$waitTime)
    }
    
    [double] GetCurrentFPS() {
        if ($this.FrameHistory.Count -lt 2) {
            return 0
        }
        
        $frames = $this.FrameHistory.ToArray()
        $timeSpan = $frames[-1] - $frames[0]
        return ($frames.Length - 1) / $timeSpan.TotalSeconds
    }
    
    [hashtable] GetStats() {
        return @{
            TargetFPS = $this.TargetFPS
            CurrentFPS = $this.GetCurrentFPS()
            CurrentRefreshInterval = $this.CurrentRefreshInterval
            ConsecutiveSkippedFrames = $this.ConsecutiveSkippedFrames
            AdaptiveMode = $this.AdaptiveMode
            FrameHistoryCount = $this.FrameHistory.Count
        }
    }
    
    [void] SetTargetFPS([int]$fps) {
        $this.TargetFPS = [Math]::Max(1, [Math]::Min(10, $fps))
        $this.CurrentRefreshInterval = [Math]::Max($this.MinRefreshInterval, 1000 / $this.TargetFPS)
    }
}

# Memory optimizer for long-running live sessions
class MemoryOptimizer {
    [int] $MaxMemoryUsageMB = 100
    [DateTime] $LastCleanup = [DateTime]::UtcNow
    [int] $CleanupIntervalMs = 60000  # Cleanup every minute
    [hashtable] $MemoryStats = @{}
    [System.GC] $GarbageCollector
    [int] $ForceGCThresholdMB = 80
    [Queue[hashtable]] $MemoryHistory
    [int] $MemoryHistorySize = 20
    
    MemoryOptimizer([int]$maxMemoryUsageMB = 100) {
        $this.MaxMemoryUsageMB = $maxMemoryUsageMB
        $this.MemoryHistory = [Queue[hashtable]]::new()
        $this.UpdateMemoryStats()
    }
    
    [void] UpdateMemoryStats() {
        try {
            $process = Get-Process -Id $PID
            $workingSetMB = [Math]::Round($process.WorkingSet64 / 1MB, 2)
            $privateMemoryMB = [Math]::Round($process.PrivateMemorySize64 / 1MB, 2)
            
            $this.MemoryStats = @{
                WorkingSetMB = $workingSetMB
                PrivateMemoryMB = $privateMemoryMB
                Timestamp = [DateTime]::UtcNow
                GCTotalMemoryMB = [Math]::Round([GC]::GetTotalMemory($false) / 1MB, 2)
            }
            
            # Add to history
            $this.MemoryHistory.Enqueue($this.MemoryStats.Clone())
            while ($this.MemoryHistory.Count -gt $this.MemoryHistorySize) {
                $this.MemoryHistory.Dequeue() | Out-Null
            }
            
        } catch {
            Write-Warning "Failed to update memory stats: $($_.Exception.Message)"
        }
    }
    
    [bool] ShouldPerformCleanup() {
        $this.UpdateMemoryStats()
        
        $timeSinceLastCleanup = [DateTime]::UtcNow - $this.LastCleanup
        $memoryPressure = $this.MemoryStats.WorkingSetMB -gt $this.MaxMemoryUsageMB
        $timeForScheduledCleanup = $timeSinceLastCleanup.TotalMilliseconds -gt $this.CleanupIntervalMs
        
        return $memoryPressure -or $timeForScheduledCleanup
    }
    
    [void] PerformCleanup() {
        try {
            $beforeMemory = $this.MemoryStats.WorkingSetMB
            
            # Force garbage collection if memory usage is high
            if ($this.MemoryStats.WorkingSetMB -gt $this.ForceGCThresholdMB) {
                [GC]::Collect()
                [GC]::WaitForPendingFinalizers()
                [GC]::Collect()
            }
            
            # Update memory stats after cleanup
            $this.UpdateMemoryStats()
            $afterMemory = $this.MemoryStats.WorkingSetMB
            
            $this.LastCleanup = [DateTime]::UtcNow
            
            Write-Verbose "Memory cleanup completed. Before: $beforeMemory MB, After: $afterMemory MB"
            
        } catch {
            Write-Warning "Error during memory cleanup: $($_.Exception.Message)"
        }
    }
    
    [hashtable] GetMemoryTrend() {
        if ($this.MemoryHistory.Count -lt 2) {
            return @{ Trend = "Unknown"; Change = 0 }
        }
        
        $history = $this.MemoryHistory.ToArray()
        $oldest = $history[0]
        $newest = $history[-1]
        
        $change = $newest.WorkingSetMB - $oldest.WorkingSetMB
        $trend = if ($change -gt 5) { "Increasing" } elseif ($change -lt -5) { "Decreasing" } else { "Stable" }
        
        return @{
            Trend = $trend
            Change = [Math]::Round($change, 2)
            TimeSpan = $newest.Timestamp - $oldest.Timestamp
        }
    }
    
    [hashtable] GetStats() {
        $this.UpdateMemoryStats()
        $trend = $this.GetMemoryTrend()
        
        return @{
            Current = $this.MemoryStats
            MaxMemoryUsageMB = $this.MaxMemoryUsageMB
            LastCleanup = $this.LastCleanup
            Trend = $trend
            HistoryCount = $this.MemoryHistory.Count
        }
    }
    
    [bool] IsMemoryUsageHealthy() {
        $this.UpdateMemoryStats()
        return $this.MemoryStats.WorkingSetMB -lt ($this.MaxMemoryUsageMB * 0.9)
    }
}

# Optimized Console Renderer with performance enhancements
class OptimizedConsoleRenderer : ConsoleRenderer {
    [DifferentialScreenUpdater] $ScreenUpdater
    [FrameRateLimiter] $FrameLimiter
    [MemoryOptimizer] $MemoryOptimizer
    [bool] $PerformanceMode = $true
    [hashtable] $PerformanceStats = @{}
    [DateTime] $LastStatsUpdate = [DateTime]::UtcNow
    
    OptimizedConsoleRenderer([bool]$performanceMode = $true) : base() {
        $this.PerformanceMode = $performanceMode
        
        if ($this.PerformanceMode) {
            $this.ScreenUpdater = [DifferentialScreenUpdater]::new()
            $this.FrameLimiter = [FrameRateLimiter]::new(2, $true)  # 2 FPS with adaptive mode
            $this.MemoryOptimizer = [MemoryOptimizer]::new(100)     # 100MB limit
        }
    }
    
    [void] UpdateTrackInfo([hashtable]$trackData) {
        if (-not $this.PerformanceMode) {
            # Fall back to base implementation
            ([ConsoleRenderer]$this).UpdateTrackInfo($trackData)
            return
        }
        
        # Check if we should render this frame
        if (-not $this.FrameLimiter.ShouldRender()) {
            return
        }
        
        # Prepare content for differential update
        $content = $this.PrepareContentForUpdate($trackData)
        
        # Check if content has changed
        if (-not $this.ScreenUpdater.HasContentChanged($content)) {
            return
        }
        
        # Generate and apply differential update
        $updateString = $this.ScreenUpdater.GenerateDifferentialUpdate($content)
        
        if (-not [string]::IsNullOrEmpty($updateString)) {
            [Console]::Write($updateString)
        }
        
        # Perform memory cleanup if needed
        if ($this.MemoryOptimizer.ShouldPerformCleanup()) {
            $this.MemoryOptimizer.PerformCleanup()
        }
        
        # Update performance stats periodically
        $this.UpdatePerformanceStats()
    }
    
    [hashtable] PrepareContentForUpdate([hashtable]$trackData) {
        $content = @{}
        
        if ($trackData) {
            $content['track_id'] = $trackData.ContainsKey('id') ? $trackData.id : ""
            $content['track_name'] = $trackData.ContainsKey('name') ? $trackData.name : "Unknown Track"
            $content['is_playing'] = $trackData.ContainsKey('is_playing') ? $trackData.is_playing : $false
            $content['progress_ms'] = $trackData.ContainsKey('progress_ms') ? $trackData.progress_ms : 0
            $content['duration_ms'] = $trackData.ContainsKey('duration_ms') ? $trackData.duration_ms : 0
            
            # Handle artists
            if ($trackData.ContainsKey('artists') -and $trackData.artists) {
                $content['artist_name'] = if ($trackData.artists -is [array]) {
                    ($trackData.artists | ForEach-Object { if ($_ -is [hashtable]) { $_.name } else { $_ } }) -join ", "
                } else {
                    $trackData.artists
                }
            } else {
                $content['artist_name'] = "Unknown Artist"
            }
            
            # Handle album
            if ($trackData.ContainsKey('album') -and $trackData.album) {
                $content['album_name'] = if ($trackData.album -is [hashtable]) { $trackData.album.name } else { $trackData.album }
            } else {
                $content['album_name'] = "Unknown Album"
            }
        }
        
        return $content
    }
    
    [void] UpdatePerformanceStats() {
        $now = [DateTime]::UtcNow
        if (($now - $this.LastStatsUpdate).TotalSeconds -lt 5) {
            return  # Update stats only every 5 seconds
        }
        
        $this.PerformanceStats = @{
            FrameRate = $this.FrameLimiter.GetStats()
            Memory = $this.MemoryOptimizer.GetStats()
            ScreenUpdater = $this.ScreenUpdater.GetStats()
            LastUpdate = $now
        }
        
        $this.LastStatsUpdate = $now
    }
    
    [hashtable] GetPerformanceStats() {
        $this.UpdatePerformanceStats()
        return $this.PerformanceStats
    }
    
    [void] SetPerformanceMode([bool]$enabled) {
        $this.PerformanceMode = $enabled
        
        if ($enabled -and (-not $this.ScreenUpdater -or -not $this.FrameLimiter -or -not $this.MemoryOptimizer)) {
            $this.ScreenUpdater = [DifferentialScreenUpdater]::new()
            $this.FrameLimiter = [FrameRateLimiter]::new(2, $true)
            $this.MemoryOptimizer = [MemoryOptimizer]::new(100)
        }
    }
    
    [void] SetTargetFPS([int]$fps) {
        if ($this.FrameLimiter) {
            $this.FrameLimiter.SetTargetFPS($fps)
        }
    }
    
    [void] ForceFullRedraw() {
        if ($this.ScreenUpdater) {
            $this.ScreenUpdater.ForceFullRedraw()
        }
    }
    
    [void] CleanupAndExit() {
        # Perform final cleanup
        if ($this.MemoryOptimizer) {
            $this.MemoryOptimizer.PerformCleanup()
        }
        
        # Call base cleanup
        ([ConsoleRenderer]$this).CleanupAndExit()
    }
}

# Export classes and functions
Export-ModuleMember -Function @() -Variable @()