<#
.SYNOPSIS
    Measure Windows boot performance from the Diagnostics-Performance
    log. Surfaces which boots were slow and what specifically dragged
    each one down.

.DESCRIPTION
    The Microsoft-Windows-Diagnostics-Performance/Operational log records
    detailed timing for every boot event (boot main path, post-boot,
    total) and flags individual components that exceeded the system's
    "fast boot" threshold:

      Event 100 — "Windows successfully booted in X ms"
                  Contains: BootTime, BootMainPathTime, BootPostBootTime,
                  IsDegradation, IncidentTime
      Event 101 — "App took longer than usual to start"
      Event 102 — "Driver took longer than usual to start"
      Event 103 — "Service took longer than usual to start"

    Reading this log requires Administrator. Without admin, the script
    falls back to a kernel-event-based inference using Event 12 (kernel
    start) and Event 6005 (event log service started) — coarser but
    still useful for trend detection.

.PARAMETER LastN
    Number of recent boots to report. Default: 10.

.PARAMETER Json
    Machine-readable JSON output.

.EXAMPLE
    scripts/boot-perf.ps1
    Show the last 10 boots with their durations and degradation flags.

.EXAMPLE
    scripts/boot-perf.ps1 -LastN 30 -Json | jq '.boots[] | select(.degraded)'
    Filter to only degraded boots from machine-readable output.

.NOTES
    Exit codes:
      0 success
      5 precondition (no boot events found at all)
#>

[CmdletBinding()]
param(
    [ValidateRange(1, 100)][int]$LastN = 10,
    [switch]$Json
)

$ErrorActionPreference = 'Stop'
. "$PSScriptRoot\_lib\common.ps1"
. (Join-Path $PSScriptRoot '..\..\_lib\term.ps1')
Initialize-Term

$elevated = Test-IsElevated
$boots = New-Object System.Collections.Generic.List[hashtable]
$slowComponents = New-Object System.Collections.Generic.List[hashtable]
$source = 'diagnostics-perf'

# ─────────────────────────────────────────────────────────────────────
# Primary: Diagnostics-Performance log (requires admin)
# ─────────────────────────────────────────────────────────────────────
try {
    $perfEvents = Get-WinEvent -LogName 'Microsoft-Windows-Diagnostics-Performance/Operational' `
        -ErrorAction Stop |
        Where-Object { $_.Id -in @(100, 101, 102, 103) }

    foreach ($e in $perfEvents | Where-Object { $_.Id -eq 100 }) {
        # Properties layout (Event 100):
        #   [1] BootTime
        #   [4] BootMainPathTime
        #   [5] BootPostBootTime
        #   [6] BootIsDegradation
        try {
            $bootTotal = [int64]$e.Properties[1].Value
            $bootMain  = [int64]$e.Properties[4].Value
            $bootPost  = [int64]$e.Properties[5].Value
            $degraded  = [bool]$e.Properties[6].Value
        } catch {
            $bootTotal = -1; $bootMain = -1; $bootPost = -1; $degraded = $false
        }
        $boots.Add(@{
            time            = $e.TimeCreated.ToString('o')
            bootTotalSec    = if ($bootTotal -gt 0) { [math]::Round($bootTotal / 1000, 1) } else { -1 }
            bootMainSec     = if ($bootMain  -gt 0) { [math]::Round($bootMain  / 1000, 1) } else { -1 }
            bootPostSec     = if ($bootPost  -gt 0) { [math]::Round($bootPost  / 1000, 1) } else { -1 }
            degraded        = $degraded
        })
    }

    # Slow components — events 101/102/103
    foreach ($e in $perfEvents | Where-Object { $_.Id -in @(101, 102, 103) }) {
        $kind = switch ($e.Id) { 101 {'app'} 102 {'driver'} 103 {'service'} }
        # Property layout varies by event id; the friendly name + delay are
        # usually accessible by reading the rendered message string.
        $msg = ($e.Message -replace '\s+', ' ')
        $delaySec = $null
        if ($msg -match '(\d+) ms') {
            $delaySec = [math]::Round([int]$matches[1] / 1000, 1)
        }
        # Name extraction varies; try common patterns
        $name = '(unknown)'
        if ($msg -match '"([^"]+)"') { $name = $matches[1] }
        elseif ($msg -match 'Name\s*:\s*(\S+)') { $name = $matches[1] }
        $slowComponents.Add(@{
            time     = $e.TimeCreated.ToString('o')
            kind     = $kind
            name     = $name
            delaySec = $delaySec
            message  = (Format-EventMessage -Message $msg -MaxLength 200)
        })
    }
}
catch {
    $source = 'kernel-events'
    if (-not $elevated) {
        Write-Log -Level WARN -Message "Cannot read Diagnostics-Performance log (admin required). Falling back to coarse kernel-event timing."
    } else {
        Write-Log -Level WARN -Message "Diagnostics-Performance log unavailable: $_"
    }

    # ─────────────────────────────────────────────────────────────────
    # Fallback: kernel event 12 (start) + 6005 (event log started)
    # Gap = approximate "kernel → services running" time. Not full boot
    # to usable desktop but a useful trend metric.
    # ─────────────────────────────────────────────────────────────────
    try {
        $kernelStarts = Get-WinEvent -FilterHashtable @{
            LogName='System'; Id=12; ProviderName='Microsoft-Windows-Kernel-General'
        } -MaxEvents 30 -ErrorAction SilentlyContinue
        $logStarts = Get-WinEvent -FilterHashtable @{
            LogName='System'; Id=6005
        } -MaxEvents 30 -ErrorAction SilentlyContinue

        foreach ($k in $kernelStarts) {
            # Find the 6005 closest after this 12 (within 5 min)
            $matchingLog = $logStarts | Where-Object {
                $_.TimeCreated -gt $k.TimeCreated -and ($_.TimeCreated - $k.TimeCreated).TotalSeconds -lt 300
            } | Sort-Object TimeCreated | Select-Object -First 1

            if ($matchingLog) {
                $delta = ($matchingLog.TimeCreated - $k.TimeCreated).TotalSeconds
                $boots.Add(@{
                    time           = $k.TimeCreated.ToString('o')
                    bootTotalSec   = -1   # not available without diagnostics-perf
                    bootMainSec    = [math]::Round($delta, 1)
                    bootPostSec    = -1
                    degraded       = $false
                    note           = 'inferred from kernel start -> event log start; not full boot duration'
                })
            }
        }
    } catch {}
}

# Trim to LastN
$boots = $boots | Sort-Object { [DateTime]$_.time } -Descending | Select-Object -First $LastN

# ─────────────────────────────────────────────────────────────────────
# Output
# ─────────────────────────────────────────────────────────────────────
if ($Json) {
    @{
        source         = $source
        elevated       = $elevated
        boots          = $boots
        slowComponents = $slowComponents | Sort-Object { [DateTime]$_.time } -Descending | Select-Object -First 30
    } | ConvertTo-Json -Depth 5 | ForEach-Object { [Console]::Out.WriteLine($_) }
    exit $script:EXIT_OK
}

if (-not $boots) {
    Write-Log -Level FAIL -Message "No boot events found"
    exit $script:EXIT_PRECONDITION
}

# Median + average for the summary line (whichever data we have)
$mainSecs = $boots | ForEach-Object { $_.bootMainSec } | Where-Object { $_ -gt 0 }
$median = if ($mainSecs.Count -ge 1) {
    $sorted = $mainSecs | Sort-Object
    $sorted[[math]::Floor($sorted.Count / 2)]
} else { 0 }
$avg = if ($mainSecs.Count -ge 1) {
    [math]::Round(($mainSecs | Measure-Object -Average).Average, 1)
} else { 0 }

$sourceLabel = if ($source -eq 'diagnostics-perf') { 'full data' } else { 'fallback mode' }
Write-TermLine (New-TermPanelOpen -Brand 'windows-ops' -Name 'windows-ops' -Subtitle 'boot-perf' -Indicator $sourceLabel)
Write-TermLine (New-TermPanelVert)
Write-TermLine (New-TermSummary -Text "$($boots.Count) boots · median ${median}s · avg ${avg}s")
if (-not $elevated -and $source -ne 'diagnostics-perf') {
    Write-TermLine (New-TermHint -Text 'run as Administrator for full Diagnostics-Performance log (boot phases + slow-component flags)')
}
Write-TermLine (New-TermPanelVert)

Write-TermLine (New-TermSection -State 'INFO' -Label 'boot timeline' -Count $boots.Count)
# Find slowest in window for highlighting
$slowestSec = ($mainSecs | Measure-Object -Maximum).Maximum
$idxLast = $boots.Count - 1
for ($i = 0; $i -lt $boots.Count; $i++) {
    $b = $boots[$i]
    $t = ([DateTime]$b.time).ToString('yyyy-MM-dd HH:mm')
    $secVal = if ($b.bootMainSec -gt 0) { $b.bootMainSec } else { 0 }
    # Capacity pip bar: relative to 20-second ceiling (anything ≥80% = red)
    $bar = New-TermPipBar -Type capacity -Filled ([int]($secVal * 5)) -Total 100
    $meta = "${secVal}s"
    if ($b.degraded) { $meta += ' [DEGRADED]' }
    Write-TermLine (New-TermLeaf -Name $t -Rail $bar -Meta $meta -IsLast:($i -eq $idxLast) -NameColWidth 20)
    if ($secVal -eq $slowestSec -and $boots.Count -gt 3 -and $secVal -gt 0) {
        Write-TermLine (New-TermAlert -Severity warning -Text "slowest in window · ${secVal}s vs median ${median}s")
    }
}
Write-TermLine (New-TermPanelVert)

# Slow components section
$recentSlow = $slowComponents | Sort-Object { [DateTime]$_.time } -Descending | Select-Object -First 10
if ($recentSlow) {
    Write-TermLine (New-TermSection -State 'WARN' -Label 'slow components' -Count $recentSlow.Count)
    $idxLast = $recentSlow.Count - 1
    for ($i = 0; $i -lt $recentSlow.Count; $i++) {
        $s = $recentSlow[$i]
        $delay = if ($s.delaySec) { "$($s.delaySec)s" } else { '?' }
        $when = ([DateTime]$s.time).ToString('MM-dd HH:mm')
        Write-TermLine (New-TermLeaf -Name "[$($s.kind)] $($s.name)" -Meta $delay -Age $when -IsLast:($i -eq $idxLast) -NameColWidth 36)
    }
    Write-TermLine (New-TermAlert -Severity warning -Text 'repeat offenders → safe-disable-startup.ps1 (apps) or Set-Service -StartupType Manual (services)')
    Write-TermLine (New-TermPanelVert)
}

# Footer
$health = if ($elevated -and $source -eq 'diagnostics-perf') {
    New-TermHealth -State 'healthy' -Text 'full data'
} else {
    New-TermHealth -State 'pending' -Text 'fallback'
}
$hk = (New-TermHotkey -Key '?' -Verb 'help')
Write-TermLine (New-TermPanelClose -Hotkeys $hk -Healths $health)

exit $script:EXIT_OK
