param(
  [string]$SetupExe,
  [string]$InstallDir,
  [string]$SmokeOutput,
  [int]$SmokePort = 4318,
  [string]$SmokeToken = 'smoke-token',
  [string]$RuntimeMode,
  [switch]$ExpectBundledRuntime,
  [string]$UserDataPath,
  [string]$LogsPath,
  [int]$InstallTimeoutSeconds = 480,
  [int]$TimeoutSeconds = 240,
  [int]$MaxInstallAttempts = 2,
  [int]$InstallRetryDelaySeconds = 2,
  [string[]]$AdditionalPriorInstallDirs = @(),
  [switch]$SkipInstalledAppSmoke
)

$ErrorActionPreference = 'Stop'

$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
$installerDir = Join-Path $repoRoot 'dist\installer'

if (-not $SetupExe) {
  $latestSetup = Get-ChildItem -Path $installerDir -Filter '*-Setup-*.exe' |
    Sort-Object LastWriteTime -Descending |
    Select-Object -First 1

  if (-not $latestSetup) {
    throw "No installer exe found under $installerDir"
  }

  $SetupExe = $latestSetup.FullName
}

$SetupExe = (Resolve-Path $SetupExe).Path
$setupFileName = [System.IO.Path]::GetFileName($SetupExe)
$setupMarker = '-Setup-'
$setupMarkerIndex = $setupFileName.LastIndexOf($setupMarker, [System.StringComparison]::Ordinal)
if ($setupMarkerIndex -le 0) {
  throw "Unable to derive product name from setup executable: $setupFileName"
}

$productName = $setupFileName.Substring(0, $setupMarkerIndex)
$uninstallerFileName = "Uninstall $productName.exe"
$expectedUninstallerFileNames = @($uninstallerFileName, 'Uninstall qjclaw.exe') | Select-Object -Unique
$installedExeName = "$productName.exe"

$stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
if (-not $InstallDir) {
  $InstallDir = Join-Path $repoRoot ".tmp\installer-smoke\$stamp\$productName"
}
$InstallDir = [System.IO.Path]::GetFullPath($InstallDir)

if (-not $SmokeOutput) {
  $SmokeOutput = Join-Path (Split-Path $InstallDir -Parent) 'installer-smoke-result.json'
}
$SmokeOutput = [System.IO.Path]::GetFullPath($SmokeOutput)

if (-not $UserDataPath) {
  $UserDataPath = Join-Path (Split-Path $InstallDir -Parent) 'user-data'
}
$UserDataPath = [System.IO.Path]::GetFullPath($UserDataPath)

if (-not $LogsPath) {
  $LogsPath = Join-Path (Split-Path $InstallDir -Parent) 'logs'
}
$LogsPath = [System.IO.Path]::GetFullPath($LogsPath)

$appSmokeOutput = Join-Path (Split-Path $SmokeOutput -Parent) ("{0}.app.json" -f [System.IO.Path]::GetFileNameWithoutExtension($SmokeOutput))
$appSmokeTracePath = "$appSmokeOutput.trace.log"
$installParent = Split-Path $InstallDir -Parent

$installedExe = Join-Path $InstallDir $installedExeName
$uninstallerPath = Join-Path $InstallDir $uninstallerFileName
$resourcesAsar = Join-Path $InstallDir 'resources\app.asar'
$runtimeResourceDir = Join-Path $InstallDir 'resources\vendor\openclaw-runtime'
$packagedPythonExe = Join-Path $runtimeResourceDir 'python\python.exe'
$packagedPythonManifest = Join-Path $runtimeResourceDir 'python\python-manifest.json'
$packagedWorkspaceTemplate = Join-Path $runtimeResourceDir 'openclaw\package\docs\reference\templates\AGENTS.md'
$expectedInstalledFileDescription = $productName
$expectedInstalledProductName = $productName
$expectedInstalledInternalName = $productName
$expectedInstalledOriginalFilename = $installedExeName

$installAttempts = @()
$installerStageSummary = $null
$appStageSummary = $null
$failureStage = ''
$failureClassification = ''
$failureError = ''

function Write-Utf8File {
  param([string]$FilePath, [string]$Content)

  $encoding = New-Object System.Text.UTF8Encoding $false
  $directory = Split-Path $FilePath -Parent
  if ($directory) {
    New-Item -ItemType Directory -Force -Path $directory | Out-Null
  }
  [System.IO.File]::WriteAllText($FilePath, $Content, $encoding)
}

function Write-JsonFile {
  param([string]$FilePath, [object]$Payload)

  Write-Utf8File -FilePath $FilePath -Content ($Payload | ConvertTo-Json -Depth 20)
}

function Resolve-InstalledUninstallerPath {
  param([string]$InstallPath)

  if ([string]::IsNullOrWhiteSpace($InstallPath) -or -not (Test-Path -LiteralPath $InstallPath)) {
    return ''
  }

  foreach ($candidateFileName in $expectedUninstallerFileNames) {
    $candidatePath = Join-Path $InstallPath $candidateFileName
    if (Test-Path -LiteralPath $candidatePath) {
      return $candidatePath
    }
  }

  $fallbackMatch = Get-ChildItem -LiteralPath $InstallPath -Filter 'Uninstall *.exe' -File -ErrorAction SilentlyContinue |
    Sort-Object Name |
    Select-Object -First 1
  if ($fallbackMatch) {
    return $fallbackMatch.FullName
  }

  return ''
}

function Get-InstallSnapshot {
  param([string]$Path)

  if (-not (Test-Path $Path)) {
    return @{
      FileCount = 0
      TotalBytes = [int64]0
    }
  }

  $files = Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue
  $totalBytes = ($files | Measure-Object -Property Length -Sum).Sum
  if ($null -eq $totalBytes) {
    $totalBytes = 0
  }

  return @{
    FileCount = @($files).Count
    TotalBytes = [int64]$totalBytes
  }
}

function Get-UninstallPathFromString {
  param([string]$UninstallString)

  if ([string]::IsNullOrWhiteSpace($UninstallString)) {
    return ''
  }

  if ($UninstallString -match '"([^"]+\.exe)"') {
    return $matches[1]
  }

  if ($UninstallString -match '^([^ ]+\.exe)') {
    return $matches[1]
  }

  return ''
}

function Get-RegistryViewLabel {
  param([Microsoft.Win32.RegistryView]$RegistryView)

  switch ($RegistryView) {
    ([Microsoft.Win32.RegistryView]::Registry64) { return 'registry64' }
    ([Microsoft.Win32.RegistryView]::Registry32) { return 'registry32' }
    default { return 'default' }
  }
}

function Get-PriorInstallEvidence {
  param([string]$ProductName)

  $entries = @()
  $seen = @{}
  $registryTargets = @(
    @{
      HiveName = 'HKCU'
      Hive = [Microsoft.Win32.RegistryHive]::CurrentUser
      Views = @([Microsoft.Win32.RegistryView]::Default)
    },
    @{
      HiveName = 'HKLM'
      Hive = [Microsoft.Win32.RegistryHive]::LocalMachine
      Views = @([Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32)
    }
  )

  foreach ($target in $registryTargets) {
    foreach ($view in $target.Views) {
      try {
        $baseKey = [Microsoft.Win32.RegistryKey]::OpenBaseKey($target.Hive, $view)
        $uninstallRoot = $baseKey.OpenSubKey('Software\Microsoft\Windows\CurrentVersion\Uninstall')
        if ($null -eq $uninstallRoot) {
          continue
        }

        foreach ($subKeyName in $uninstallRoot.GetSubKeyNames()) {
          $subKey = $uninstallRoot.OpenSubKey($subKeyName)
          if ($null -eq $subKey) {
            continue
          }

          $displayName = [string]$subKey.GetValue('DisplayName', '')
          $uninstallString = [string]$subKey.GetValue('UninstallString', '')
          $quietUninstallString = [string]$subKey.GetValue('QuietUninstallString', '')
          $installLocation = [string]$subKey.GetValue('InstallLocation', '')
          $matchesProduct = $displayName -like "$ProductName*" -or $uninstallString -like "*$ProductName*" -or $quietUninstallString -like "*$ProductName*"
          if (-not $matchesProduct) {
            continue
          }

          $entryKey = '{0}|{1}|{2}' -f $target.HiveName, $view.ToString(), $subKeyName
          if ($seen.ContainsKey($entryKey)) {
            continue
          }
          $seen[$entryKey] = $true

          $uninstaller = Get-UninstallPathFromString -UninstallString $uninstallString
          if ([string]::IsNullOrWhiteSpace($installLocation) -and -not [string]::IsNullOrWhiteSpace($uninstaller)) {
            $installLocation = Split-Path $uninstaller -Parent
          }

          $entries += [ordered]@{
            hive = $target.HiveName
            registryView = Get-RegistryViewLabel -RegistryView $view
            subKey = $subKeyName
            displayName = $displayName
            displayVersion = [string]$subKey.GetValue('DisplayVersion', '')
            installLocation = $installLocation
            uninstallString = $uninstallString
            quietUninstallString = $quietUninstallString
            uninstallerPath = $uninstaller
          }
        }
      } catch {
        continue
      }
    }
  }

  return $entries
}

function Get-InstallerRelatedProcesses {
  param([string[]]$KnownPaths = @())

  $normalizedKnownPaths = @($KnownPaths | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
  $processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue
  if (-not $processes) {
    return @()
  }

  $matches = @()
  foreach ($process in $processes) {
    $commandLine = [string]$process.CommandLine
    $name = [string]$process.Name
    $isMatch = $false

    if ($name -eq $installedExeName -or $expectedUninstallerFileNames -contains $name) {
      $isMatch = $true
    }

    if (-not $isMatch -and $name -eq 'node.exe' -and $commandLine -and (
      $commandLine -like '*openclaw.runtime.json*' -or
      $commandLine -like '*vendor\openclaw-runtime*'
    )) {
      $isMatch = $true
    }

    if (-not $isMatch -and $commandLine) {
      if ($commandLine -like "*$productName*" -or $commandLine -like '*openclaw.runtime.json*') {
        $isMatch = $true
      } else {
        foreach ($knownPath in $normalizedKnownPaths) {
          if ($commandLine -like "*$knownPath*") {
            $isMatch = $true
            break
          }
        }
      }
    }

    if ($isMatch) {
      $matches += [ordered]@{
        processId = [int]$process.ProcessId
        name = $name
        executablePath = [string]$process.ExecutablePath
        commandLine = $commandLine
      }
    }
  }

  return $matches
}

function Stop-SmokeAppProcesses {
  param([string[]]$KnownPaths = @())

  $relatedProcesses = Get-InstallerRelatedProcesses -KnownPaths $KnownPaths
  if (-not $relatedProcesses) {
    return
  }

  $processIds = @(
    $relatedProcesses |
      ForEach-Object {
        if ($_ -is [System.Collections.IDictionary]) {
          $_['processId']
        } else {
          $_.processId
        }
      } |
      Where-Object { $_ } |
      Select-Object -Unique
  )
  foreach ($processId in $processIds) {
    Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue
  }
}

function Get-InstallObservation {
  param([string]$Path)

  $snapshot = Get-InstallSnapshot -Path $Path
  $observedInstalledExe = Join-Path $Path $installedExeName
  $observedUninstallerPath = Resolve-InstalledUninstallerPath -InstallPath $Path
  $observedResourcesAsar = Join-Path $Path 'resources\app.asar'
  $observedRuntimeResourceDir = Join-Path $Path 'resources\vendor\openclaw-runtime'
  $observedPackagedPythonExe = Join-Path $observedRuntimeResourceDir 'python\python.exe'
  $observedPackagedPythonManifest = Join-Path $observedRuntimeResourceDir 'python\python-manifest.json'
  return [ordered]@{
    installDir = $Path
    installDirExists = Test-Path $Path
    installedExeExists = Test-Path $observedInstalledExe
    resourcesAsarExists = Test-Path $observedResourcesAsar
    runtimeResourceDirExists = Test-Path $observedRuntimeResourceDir
    packagedPythonExeExists = Test-Path $observedPackagedPythonExe
    packagedPythonManifestExists = Test-Path $observedPackagedPythonManifest
    uninstallerExists = -not [string]::IsNullOrWhiteSpace($observedUninstallerPath)
    uninstallerPath = $observedUninstallerPath
    uninstallerFileName = if ($observedUninstallerPath) { [System.IO.Path]::GetFileName($observedUninstallerPath) } else { '' }
    fileCount = [int]$snapshot.FileCount
    totalBytes = [int64]$snapshot.TotalBytes
  }
}

function Get-RuntimePayloadSummary {
  param([string]$RuntimeDir)

  $manifestPath = Join-Path $RuntimeDir 'runtime-manifest.json'
  if (-not (Test-Path $RuntimeDir)) {
    return [ordered]@{
      runtimeDir = $RuntimeDir
      manifestPath = $manifestPath
      exists = $false
      fileCount = 0
      sizeBytes = [int64]0
      topLevelBreakdown = @()
      manifestFileCount = $null
      manifestSizeBytes = $null
      payloadStats = $null
      cleanupSummary = $null
    }
  }

  $snapshot = Get-InstallSnapshot -Path $RuntimeDir
  $topLevelBreakdown = @(
    Get-ChildItem -LiteralPath $RuntimeDir -Force -ErrorAction SilentlyContinue |
      Sort-Object Name |
      ForEach-Object {
        if ($_.PSIsContainer) {
          $childSnapshot = Get-InstallSnapshot -Path $_.FullName
          [ordered]@{
            path = $_.Name
            fileCount = [int]$childSnapshot.FileCount
            sizeBytes = [int64]$childSnapshot.TotalBytes
          }
        } else {
          [ordered]@{
            path = $_.Name
            fileCount = 1
            sizeBytes = [int64]$_.Length
          }
        }
      }
  )

  $manifest = $null
  if (Test-Path $manifestPath) {
    try {
      $manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
    } catch {
      $manifest = $null
    }
  }

  return [ordered]@{
    runtimeDir = $RuntimeDir
    manifestPath = $manifestPath
    exists = $true
    fileCount = [int]$snapshot.FileCount
    sizeBytes = [int64]$snapshot.TotalBytes
    topLevelBreakdown = $topLevelBreakdown
    manifestFileCount = if ($manifest -and $null -ne $manifest.fileCount) { [int]$manifest.fileCount } else { $null }
    manifestSizeBytes = if ($manifest -and $null -ne $manifest.sizeBytes) { [int64]$manifest.sizeBytes } else { $null }
    payloadStats = if ($manifest -and $null -ne $manifest.payloadStats) { $manifest.payloadStats } else { $null }
    cleanupSummary = if ($manifest -and $null -ne $manifest.cleanupSummary) { $manifest.cleanupSummary } else { $null }
  }
}

function Get-InstallerFailureClassification {
  param([object]$Observation)

  if ($Observation.fileCount -eq 0 -and -not $Observation.installedExeExists -and -not $Observation.uninstallerExists) {
    return 'empty-exit-zero-files'
  }

  if ($Observation.installedExeExists -and $Observation.resourcesAsarExists -and $Observation.runtimeResourceDirExists -and $Observation.packagedPythonExeExists -and $Observation.packagedPythonManifestExists -and -not $Observation.uninstallerExists) {
    return 'missing-uninstaller-only'
  }

  if ($Observation.installDirExists -or $Observation.fileCount -gt 0) {
    return 'partial-materialization'
  }

  return 'empty-exit-zero-files'
}

function Prepare-SmokeWorkspaceFixture {
  param([string]$WorkspaceRoot)

  $projectId = 'installer-smoke'
  $projectName = 'Installer Smoke'
  $projectDescription = 'Preseeded workspace fixture for packaged installer validation.'
  $projectRoot = Join-Path $WorkspaceRoot (Join-Path 'projects' $projectId)
  $manifestRoot = Join-Path $WorkspaceRoot 'manifests'
  $memoryRoot = Join-Path $projectRoot 'memory'
  $timestamp = (Get-Date).ToUniversalTime().ToString('o')

  New-Item -ItemType Directory -Force -Path $projectRoot, $manifestRoot, $memoryRoot | Out-Null

  $projectPayload = [ordered]@{
    id = $projectId
    name = $projectName
    description = $projectDescription
    ready = $true
    boundSkillIds = @()
    updatedAt = $timestamp
    workspaceEntryEnabled = $true
  }
  Write-Utf8File -FilePath (Join-Path $projectRoot 'project.json') -Content ($projectPayload | ConvertTo-Json -Depth 6)

  $readmeContents = @(
    '# Installer Smoke',
    '',
    'This project is created automatically so the packaged desktop smoke can open a chat session immediately.'
  ) -join [Environment]::NewLine
  Write-Utf8File -FilePath (Join-Path $projectRoot 'README.md') -Content $readmeContents

  $markerContents = @(
    '# Installer Smoke Fixture',
    '',
    'This workspace exists only for packaged installer validation.',
    'If referenced during smoke, confirm that the packaged app can access this project root.'
  ) -join [Environment]::NewLine
  Write-Utf8File -FilePath (Join-Path $projectRoot 'AGENT.md') -Content $markerContents

  $activeProjectPayload = [ordered]@{
    projectId = $projectId
  }
  Write-Utf8File -FilePath (Join-Path $manifestRoot 'active-project.json') -Content ($activeProjectPayload | ConvertTo-Json -Depth 3)
}

function Invoke-InstallerMaterializationAttempt {
  param(
    [int]$AttemptNumber,
    [int]$AttemptTimeoutSeconds
  )

  if (Test-Path $InstallDir) {
    Write-Host "Removing existing install directory at $InstallDir"
    Remove-Item -Path $InstallDir -Recurse -Force -ErrorAction SilentlyContinue
  }

  Write-Host "Installing $SetupExe to $InstallDir (attempt $AttemptNumber/$MaxInstallAttempts)"
  $attemptStartedAt = (Get-Date).ToUniversalTime().ToString('o')
  $setupProcess = Start-Process -FilePath $SetupExe -ArgumentList @('/S', "/D=$InstallDir") -PassThru
  $installDeadline = (Get-Date).AddSeconds($AttemptTimeoutSeconds)
  $installReady = $false
  $requiredPathsReady = $false
  $stabilityThreshold = 8
  $stablePollCount = 0
  $lastSnapshot = $null

  while ((Get-Date) -lt $installDeadline) {
    $observation = Get-InstallObservation -Path $InstallDir
    $requiredPathsReady = $observation.installedExeExists -and $observation.resourcesAsarExists -and $observation.runtimeResourceDirExists -and $observation.packagedPythonExeExists -and $observation.packagedPythonManifestExists -and $observation.uninstallerExists

    if ($requiredPathsReady) {
      $currentSnapshot = @{
        FileCount = $observation.fileCount
        TotalBytes = $observation.totalBytes
      }
      if ($lastSnapshot -and $currentSnapshot.FileCount -eq $lastSnapshot.FileCount -and $currentSnapshot.TotalBytes -eq $lastSnapshot.TotalBytes) {
        $stablePollCount += 1
      } else {
        $stablePollCount = 0
        $lastSnapshot = $currentSnapshot
      }

      if ($stablePollCount -ge $stabilityThreshold) {
        $installReady = $true
        break
      }
    } else {
      $stablePollCount = 0
      $lastSnapshot = $null
    }

    Start-Sleep -Milliseconds 500
  }

  if (-not $setupProcess.HasExited) {
    Wait-Process -Id $setupProcess.Id -Timeout 15 -ErrorAction SilentlyContinue
  }
  if (-not $setupProcess.HasExited -and -not $installReady) {
    Stop-Process -Id $setupProcess.Id -Force -ErrorAction SilentlyContinue
  }
  if (-not $setupProcess.HasExited -and $installReady) {
    Write-Host 'Installer process remained alive after files were installed; terminating lingering process.'
    Stop-Process -Id $setupProcess.Id -Force -ErrorAction SilentlyContinue
  }

  $setupExitCode = $null
  if ($setupProcess.HasExited) {
    $setupExitCode = [int]$setupProcess.ExitCode
  }

  $finalObservation = Get-InstallObservation -Path $InstallDir
  $failureClassification = if ($installReady) { '' } else { Get-InstallerFailureClassification -Observation $finalObservation }

  return [ordered]@{
    attempt = $AttemptNumber
    startedAt = $attemptStartedAt
    finishedAt = (Get-Date).ToUniversalTime().ToString('o')
    setupProcessId = [int]$setupProcess.Id
    installerExitCode = $setupExitCode
    installReady = $installReady
    requiredPathsReady = $requiredPathsReady
    stablePollCount = $stablePollCount
    stabilityThreshold = $stabilityThreshold
    observation = $finalObservation
    failureClassification = $failureClassification
    retryEligible = (-not $installReady -and $failureClassification -eq 'empty-exit-zero-files')
  }
}

function Invoke-InstalledAppSmoke {
  param()

  if (Test-Path $appSmokeOutput) {
    Remove-Item -LiteralPath $appSmokeOutput -Force -ErrorAction SilentlyContinue
  }
  if (Test-Path $appSmokeTracePath) {
    Remove-Item -LiteralPath $appSmokeTracePath -Force -ErrorAction SilentlyContinue
  }
  if (Test-Path $UserDataPath) {
    Remove-Item -Path $UserDataPath -Recurse -Force -ErrorAction SilentlyContinue
  }
  if (Test-Path $LogsPath) {
    Remove-Item -Path $LogsPath -Recurse -Force -ErrorAction SilentlyContinue
  }
  New-Item -ItemType Directory -Force -Path $UserDataPath, $LogsPath | Out-Null
  Prepare-SmokeWorkspaceFixture -WorkspaceRoot $UserDataPath

  $env:QJCLAW_SMOKE_OUTPUT = $appSmokeOutput
  $env:QJCLAW_SMOKE_CLOUD_API_BASE_URL = "http://127.0.0.1:$SmokePort"
  $env:QJCLAW_SMOKE_AUTH_TOKEN = $SmokeToken
  $env:QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY = 'smoke-runtime-api-key'
  $env:QJCLAW_RUNTIME_CLOUD_HEARTBEAT_INTERVAL_MS = 1000
  $env:QJCLAW_RUNTIME_CLOUD_CONFIG_SYNC_INTERVAL_MS = 1500
  $env:QJCLAW_RUNTIME_CLOUD_EVENT_FLUSH_INTERVAL_MS = 800
  $env:QJCLAW_RUNTIME_CLOUD_EVENT_BATCH_SIZE = 3
  $env:QJCLAW_USER_DATA_PATH = $UserDataPath
  $env:QJCLAW_LOGS_PATH = $LogsPath
  if ($RuntimeMode) {
    $env:QJCLAW_RUNTIME_MODE = $RuntimeMode
  }

  $runtimeModeValue = if ($RuntimeMode) { $RuntimeMode } else { '' }
  $expectBundledValue = if ($ExpectBundledRuntime) { 'true' } else { 'false' }
  $runtimeManagerLog = Join-Path $LogsPath 'runtime-manager.log'

  try {
    Write-Host "Running installed app smoke: $installedExe"
    $appProcess = Start-Process -FilePath $installedExe -PassThru
    $deadline = (Get-Date).AddSeconds($TimeoutSeconds)
    while ((Get-Date) -lt $deadline) {
      if (Test-Path $appSmokeOutput) {
        break
      }

      $alive = Get-Process -Id $appProcess.Id -ErrorAction SilentlyContinue
      if (-not $alive) {
        break
      }

      Start-Sleep -Milliseconds 500
    }

    $alive = Get-Process -Id $appProcess.Id -ErrorAction SilentlyContinue
    if ($alive) {
      Wait-Process -Id $appProcess.Id -Timeout 15 -ErrorAction SilentlyContinue
      $alive = Get-Process -Id $appProcess.Id -ErrorAction SilentlyContinue
    }

    if ($alive -and -not (Test-Path $appSmokeOutput)) {
      Stop-Process -Id $appProcess.Id -Force -ErrorAction SilentlyContinue
      throw "Installed smoke process did not finish within $TimeoutSeconds seconds."
    }

    $appExitCode = if ($appProcess.HasExited) { [int]$appProcess.ExitCode } else { $null }
    if (-not (Test-Path $appSmokeOutput)) {
      throw "Smoke output file was not created: $appSmokeOutput"
    }

    $validator = @"
const fs = require('fs');

const [smokeOutput, expectedUserData, expectedLogs, runtimeMode, expectBundled, runtimeResourceDir, packagedPythonExe, packagedPythonManifest, setupExe, installDir, installedExe, installedAppExitCode] = process.argv.slice(1);
const result = JSON.parse(fs.readFileSync(smokeOutput, 'utf8'));
if (!result.ok) {
  throw new Error('Installed smoke failed: ' + (result.error || 'Unknown smoke failure.'));
}
const sendResult = result.sendResult || {};
const streamSmoke = sendResult.streamSmoke || {};
const persistedAssistantContent = String(
  (sendResult.lastAssistantMessage && sendResult.lastAssistantMessage.content) ||
  (sendResult.lastMessage && sendResult.lastMessage.role === 'assistant' && sendResult.lastMessage.content) ||
  ''
);
const renderedAssistantContent = String(streamSmoke.renderedContent || streamSmoke.finalContent || persistedAssistantContent || '');
const runtimeTelemetryAfterWait = sendResult.runtimeTelemetryAfterWait || {};
const gatewayOperationalDuringSmoke = Boolean(
  (sendResult.health && sendResult.health.ok)
  || (sendResult.status && sendResult.status.state === 'connected')
  || (runtimeTelemetryAfterWait.state === 'running' && renderedAssistantContent)
);
const streamReachedAcceptableTerminalState = streamSmoke.phase === 'completed'
  || (streamSmoke.phase === 'error' && persistedAssistantContent.length > 0);
if (!sendResult.system || !sendResult.system.isPackaged) {
  throw new Error('Installed smoke did not report packaged mode.');
}
if (!sendResult.session || sendResult.session.state !== 'authenticated') {
  throw new Error('Installed smoke reported unexpected auth state: ' + (sendResult.session && sendResult.session.state));
}
const diagnosticsPath = String(sendResult.diagnostics && sendResult.diagnostics.filePath || '');
if (!diagnosticsPath || !fs.existsSync(diagnosticsPath)) {
  throw new Error('Installed smoke did not produce a diagnostics export.');
}
const tracePath = smokeOutput + '.trace.log';
if (!fs.existsSync(tracePath)) {
  throw new Error('Installed smoke did not produce a bootstrap trace log.');
}
const traceLines = fs.readFileSync(tracePath, 'utf8').split(/\r?\n/).filter(Boolean);
const windowCreatedTraceIndex = traceLines.findIndex((line) => line.includes('bootstrap:window-created'));
const bootstrapWarmupScheduledTraceIndex = traceLines.findIndex((line) => line.includes('bootstrap:bootstrap-warmup-scheduled'));
if (windowCreatedTraceIndex < 0) {
  throw new Error('Installed smoke trace did not record window creation.');
}
if (bootstrapWarmupScheduledTraceIndex < 0) {
  throw new Error('Installed smoke trace did not record bootstrap warmup scheduling.');
}
if (windowCreatedTraceIndex > bootstrapWarmupScheduledTraceIndex) {
  throw new Error('Installed smoke created the window after bootstrap warmup scheduling.');
}
const diagnostics = JSON.parse(fs.readFileSync(diagnosticsPath, 'utf8'));
const resourcesPath = String(diagnostics.paths && diagnostics.paths.resourcesPath || '');
if (!resourcesPath || !fs.existsSync(resourcesPath)) {
  throw new Error('Diagnostics did not report a valid resources path.');
}
if (String(sendResult.system.userDataPath) !== expectedUserData) {
  throw new Error('Installed smoke ran against an unexpected userData path: ' + sendResult.system.userDataPath);
}
if (String(sendResult.system.logsPath) !== expectedLogs) {
  throw new Error('Installed smoke ran against an unexpected logs path: ' + sendResult.system.logsPath);
}
if (!streamReachedAcceptableTerminalState) {
  throw new Error('Installed renderer stream smoke did not complete successfully: ' + streamSmoke.phase);
}
if (streamSmoke.fallbackUsed) {
  throw new Error('Installed renderer stream smoke fell back to non-streaming sendPrompt.');
}
if (Number(streamSmoke.startedEventCount || 0) < 1) {
  throw new Error('Installed renderer stream smoke did not observe a started event.');
}
if (Number(streamSmoke.completedEventCount || 0) < 1 && !persistedAssistantContent) {
  throw new Error('Installed renderer stream smoke did not observe a completed event or persist a final assistant reply.');
}
if (Number(streamSmoke.deltaEventCount || 0) < 1 && !String(streamSmoke.finalContent || '') && !persistedAssistantContent) {
  throw new Error('Installed renderer stream smoke did not observe a delta event or final assistant content.');
}
if (Number(streamSmoke.errorEventCount || 0) !== 0 && !persistedAssistantContent) {
  throw new Error('Installed renderer stream smoke observed unexpected error events: ' + streamSmoke.errorEventCount);
}
if (!renderedAssistantContent) {
  throw new Error('Installed renderer stream smoke did not render assistant content.');
}
if (expectBundled === 'true') {
  const runtimeStatus = sendResult.runtimeStatusAfterProbe || {};
  const runtimeHealth = sendResult.runtimeHealthAfterProbe || {};
  if (runtimeStatus.selectedMode !== 'bundled-runtime') {
    throw new Error('Installed smoke did not select bundled-runtime mode: ' + runtimeStatus.selectedMode);
  }
  if (runtimeStatus.payloadState !== 'ready') {
    throw new Error('Installed smoke bundled runtime payload is not ready: ' + runtimeStatus.payloadState);
  }
  if (runtimeStatus.activeMode !== 'bundled-runtime') {
    throw new Error('Installed smoke did not switch to bundled-runtime mode: ' + runtimeStatus.activeMode);
  }
  if (runtimeStatus.processState !== 'running') {
    throw new Error('Installed smoke bundled runtime is not running: ' + runtimeStatus.processState);
  }
  if (!runtimeHealth.ok) {
    throw new Error('Installed smoke bundled runtime health check failed.');
  }
  if (!runtimeStatus.pythonReady) {
    throw new Error('Installed smoke bundled runtime did not report a ready Python payload.');
  }
  if (!gatewayOperationalDuringSmoke) {
    throw new Error('Installed smoke did not preserve a usable Gateway/runtime path after bundled runtime start: ' + (sendResult.status && sendResult.status.state));
  }
  if (!String(runtimeStatus.gatewayUrl || '').startsWith('ws://127.0.0.1:')) {
    throw new Error('Installed smoke reported unexpected bundled runtime Gateway URL: ' + runtimeStatus.gatewayUrl);
  }
} else if (!gatewayOperationalDuringSmoke) {
  throw new Error('Installed smoke reported unexpected Gateway state: ' + (sendResult.status && sendResult.status.state));
}
const summary = {
  ok: true,
  smokeOutput,
  appSmokeTracePath: tracePath,
  setupExe,
  installDir,
  installedExe,
  runtimeMode,
  expectBundledRuntime: expectBundled === 'true',
  appPath: String(sendResult.system.appPath || ''),
  resourcesPath: String(sendResult.system.resourcesPath || ''),
  userDataPath: String(sendResult.system.userDataPath || ''),
  logsPath: String(sendResult.system.logsPath || ''),
  gatewayState: String(sendResult.status && sendResult.status.state || ''),
  gatewayHealthOk: Boolean(sendResult.health && sendResult.health.ok),
  runtimeActiveMode: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.activeMode || ''),
  runtimeProcessState: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.processState || ''),
  runtimeGatewayUrl: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.gatewayUrl || ''),
  runtimePayloadState: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.payloadState || ''),
  runtimeLastError: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.lastError || ''),
  runtimePythonReady: Boolean(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.pythonReady),
  runtimePythonVersion: String(sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.pythonVersion || ''),
  runtimePythonPackages: sendResult.runtimeStatusAfterProbe && sendResult.runtimeStatusAfterProbe.installedPythonPackages || [],
  runtimeTelemetryState: String(runtimeTelemetryAfterWait.state || ''),
  authState: String(sendResult.session && sendResult.session.state || ''),
  skillCount: Array.isArray(sendResult.skills) ? sendResult.skills.length : 0,
  streamPhase: String(streamSmoke.phase || ''),
  streamStartedEventCount: Number(streamSmoke.startedEventCount || 0),
  streamDeltaEventCount: Number(streamSmoke.deltaEventCount || 0),
  streamCompletedEventCount: Number(streamSmoke.completedEventCount || 0),
  diagnosticsPath,
  runtimeResourceDir,
  bundledPythonExecutable: packagedPythonExe,
  bundledPythonManifest: packagedPythonManifest,
  installedAppExitCode,
};
console.log(JSON.stringify(summary, null, 2));
"@
    $summaryJson = & node -e $validator $appSmokeOutput $UserDataPath $LogsPath $runtimeModeValue $expectBundledValue $runtimeResourceDir $packagedPythonExe $packagedPythonManifest $SetupExe $InstallDir $installedExe ([string]$appExitCode)
    if ($LASTEXITCODE -ne 0) {
      throw 'Installed smoke validation failed.'
    }

    return ($summaryJson | ConvertFrom-Json)
  } catch {
    if (Test-Path $runtimeManagerLog) {
      Write-Host '==== runtime-manager.log ===='
      Get-Content $runtimeManagerLog -Tail 200 | Write-Host
    }
    throw
  } finally {
    Remove-Item Env:QJCLAW_SMOKE_OUTPUT -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_CLOUD_API_BASE_URL -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_AUTH_TOKEN -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_SMOKE_RUNTIME_CLOUD_API_KEY -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_RUNTIME_CLOUD_HEARTBEAT_INTERVAL_MS -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_RUNTIME_CLOUD_CONFIG_SYNC_INTERVAL_MS -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_RUNTIME_CLOUD_EVENT_FLUSH_INTERVAL_MS -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_RUNTIME_CLOUD_EVENT_BATCH_SIZE -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_USER_DATA_PATH -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_LOGS_PATH -ErrorAction SilentlyContinue
    Remove-Item Env:QJCLAW_RUNTIME_MODE -ErrorAction SilentlyContinue
  }
}

function Assert-InstalledExeBranding {
  param()

  if (-not (Test-Path -LiteralPath $installedExe)) {
    throw "Installed executable not found for branding validation: $installedExe"
  }

  $versionInfo = (Get-Item -LiteralPath $installedExe).VersionInfo

  if ([string]$versionInfo.FileDescription -ne $expectedInstalledFileDescription) {
    throw "Installed executable FileDescription mismatch. expected=$expectedInstalledFileDescription actual=$($versionInfo.FileDescription)"
  }
  if ([string]$versionInfo.ProductName -ne $expectedInstalledProductName) {
    throw "Installed executable ProductName mismatch. expected=$expectedInstalledProductName actual=$($versionInfo.ProductName)"
  }
  if ([string]$versionInfo.InternalName -ne $expectedInstalledInternalName) {
    throw "Installed executable InternalName mismatch. expected=$expectedInstalledInternalName actual=$($versionInfo.InternalName)"
  }
  if ([string]$versionInfo.OriginalFilename -ne $expectedInstalledOriginalFilename) {
    throw "Installed executable OriginalFilename mismatch. expected=$expectedInstalledOriginalFilename actual=$($versionInfo.OriginalFilename)"
  }

  return [ordered]@{
    fileDescription = [string]$versionInfo.FileDescription
    productName = [string]$versionInfo.ProductName
    internalName = [string]$versionInfo.InternalName
    originalFilename = [string]$versionInfo.OriginalFilename
    companyName = [string]$versionInfo.CompanyName
  }
}

function New-FinalSummary {
  param(
    [bool]$Ok,
    [string]$FailureStage,
    [string]$FailureClassification,
    [string]$ErrorMessage
  )

  $latestAttempt = $null
  if (@($installAttempts).Count -gt 0) {
    $latestAttempt = $installAttempts[-1]
  }

  $summary = [ordered]@{
    ok = $Ok
    failureStage = $FailureStage
    failureClassification = $FailureClassification
    error = $ErrorMessage
    setupExe = $SetupExe
    installDir = $InstallDir
    installedExe = $installedExe
    uninstallerPath = if ($selectedAttempt.observation.uninstallerPath) { $selectedAttempt.observation.uninstallerPath } else { $uninstallerPath }
    smokeOutput = $SmokeOutput
    appSmokeOutput = $appSmokeOutput
    appSmokeTracePath = $appSmokeTracePath
    installerStage = $installerStageSummary
    installedAppStage = $appStageSummary
  }

  if ($latestAttempt) {
    $summary.installerExitCode = $latestAttempt.installerExitCode
    $summary.installDirExists = $latestAttempt.observation.installDirExists
    $summary.installedExeExists = $latestAttempt.observation.installedExeExists
    $summary.uninstallerExists = $latestAttempt.observation.uninstallerExists
    if ($latestAttempt.observation.uninstallerPath) {
      $summary.uninstallerPath = $latestAttempt.observation.uninstallerPath
    }
    $summary.fileCount = $latestAttempt.observation.fileCount
    $summary.totalBytes = [int64]$latestAttempt.observation.totalBytes
  }

  if ($installerStageSummary -and $installerStageSummary.Contains('runtimePayload')) {
    $runtimePayloadSummary = $installerStageSummary['runtimePayload']
    $summary.runtimePayloadFileCount = $runtimePayloadSummary.fileCount
    $summary.runtimePayloadSizeBytes = [int64]$runtimePayloadSummary.sizeBytes
    $summary.runtimePayloadTopLevelBreakdown = $runtimePayloadSummary.topLevelBreakdown
  }

  if ($appStageSummary) {
    if ($appStageSummary -is [System.Collections.IDictionary]) {
      foreach ($key in $appStageSummary.Keys) {
        if (-not $summary.Contains($key)) {
          $summary[$key] = $appStageSummary[$key]
        }
      }
    } else {
      foreach ($property in $appStageSummary.PSObject.Properties) {
        if (-not $summary.Contains($property.Name)) {
          $summary[$property.Name] = $property.Value
        }
      }
    }
  }

  return $summary
}

New-Item -ItemType Directory -Force -Path $installParent | Out-Null
if (Test-Path $SmokeOutput) {
  Remove-Item -LiteralPath $SmokeOutput -Force -ErrorAction SilentlyContinue
}
if (Test-Path $appSmokeOutput) {
  Remove-Item -LiteralPath $appSmokeOutput -Force -ErrorAction SilentlyContinue
}
if (Test-Path $appSmokeTracePath) {
  Remove-Item -LiteralPath $appSmokeTracePath -Force -ErrorAction SilentlyContinue
}

$normalizedAdditionalPriorInstallDirs = @($AdditionalPriorInstallDirs | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object { [System.IO.Path]::GetFullPath($_) })
$knownObservationPaths = @($InstallDir, $installedExe, $uninstallerPath, $SetupExe) + $normalizedAdditionalPriorInstallDirs
$preflight = [ordered]@{
  collectedAt = (Get-Date).ToUniversalTime().ToString('o')
  targetObservation = Get-InstallObservation -Path $InstallDir
  priorInstallEvidence = @(Get-PriorInstallEvidence -ProductName $productName)
  additionalPriorInstallObservations = @($normalizedAdditionalPriorInstallDirs | ForEach-Object { Get-InstallObservation -Path $_ })
  relatedProcesses = @(Get-InstallerRelatedProcesses -KnownPaths $knownObservationPaths)
}

Stop-SmokeAppProcesses -KnownPaths $knownObservationPaths

try {
  for ($attempt = 1; $attempt -le $MaxInstallAttempts; $attempt += 1) {
    $attemptSummary = Invoke-InstallerMaterializationAttempt -AttemptNumber $attempt -AttemptTimeoutSeconds $InstallTimeoutSeconds
    $installAttempts += $attemptSummary

    if ($attemptSummary.installReady) {
      break
    }

    if ($attemptSummary.retryEligible -and $attempt -lt $MaxInstallAttempts) {
      Write-Warning "Installer materialization failed with $($attemptSummary.failureClassification) on attempt $attempt. Retrying..."
      Start-Sleep -Seconds $InstallRetryDelaySeconds
      continue
    }

    break
  }

  $selectedAttempt = $installAttempts[-1]
  $installerStageSummary = [ordered]@{
    ok = $selectedAttempt.installReady
    failureStage = if ($selectedAttempt.installReady) { '' } else { 'installer-materialization' }
    failureClassification = $selectedAttempt.failureClassification
    setupExe = $SetupExe
    installDir = $InstallDir
    installedExe = $installedExe
    uninstallerPath = $uninstallerPath
    preflight = $preflight
    attemptCount = @($installAttempts).Count
    attempts = $installAttempts
    installerExitCode = $selectedAttempt.installerExitCode
    installDirExists = $selectedAttempt.observation.installDirExists
    installedExeExists = $selectedAttempt.observation.installedExeExists
    resourcesAsarExists = $selectedAttempt.observation.resourcesAsarExists
    runtimeResourceDirExists = $selectedAttempt.observation.runtimeResourceDirExists
    packagedPythonExeExists = $selectedAttempt.observation.packagedPythonExeExists
    packagedPythonManifestExists = $selectedAttempt.observation.packagedPythonManifestExists
    uninstallerExists = $selectedAttempt.observation.uninstallerExists
    fileCount = $selectedAttempt.observation.fileCount
    totalBytes = [int64]$selectedAttempt.observation.totalBytes
  }

  if (-not $selectedAttempt.installReady) {
    $failureStage = 'installer-materialization'
    $failureClassification = $selectedAttempt.failureClassification
    $failureError = if ($failureClassification -eq 'empty-exit-zero-files') {
      "Installer exited without materializing files under $InstallDir."
    } elseif ($failureClassification -eq 'missing-uninstaller-only') {
      "Installer materialized app payload but did not write $uninstallerFileName."
    } else {
      "Installer did not reach a stable packaged file state within $InstallTimeoutSeconds seconds."
    }
    throw $failureError
  }

  $pythonImportProbeScript = @'
import json

result = {
    "ok": True,
    "moduleSet": False,
    "xhsModuleSet": False,
    "openaiAsyncImport": False,
    "greenletImport": False,
    "playwrightAsyncImport": False,
    "edgeTtsImport": False
}

try:
    import openpyxl, pandas, requests, bs4, lxml, urllib3, pypdf, docx, charset_normalizer, yaml, PIL, dotenv, playwright, edge_tts, certifi
    result["moduleSet"] = True

    import openai, retry, fastapi, uvicorn, multipart, pdfplumber
    result["xhsModuleSet"] = True

    from openai import AsyncOpenAI
    assert AsyncOpenAI is not None
    result["openaiAsyncImport"] = True

    import greenlet
    result["greenletImport"] = True

    from playwright.async_api import async_playwright
    assert async_playwright is not None
    result["playwrightAsyncImport"] = True
    result["edgeTtsImport"] = True
except Exception as exc:
    result["ok"] = False
    result["error"] = f"{type(exc).__name__}: {exc}"

print(json.dumps(result))
'@
  $pythonImportProbePath = Join-Path (Split-Path $SmokeOutput -Parent) 'installer-smoke-python-import-probe.py'
  Write-Utf8File -FilePath $pythonImportProbePath -Content $pythonImportProbeScript

  $pythonImportProbeRaw = ''
  $pythonImportProbeExitCode = $null
  try {
    $pythonImportProbeRaw = (& $packagedPythonExe $pythonImportProbePath 2>&1 | Out-String).Trim()
    $pythonImportProbeExitCode = $LASTEXITCODE
  } finally {
    if (Test-Path $pythonImportProbePath) {
      Remove-Item -LiteralPath $pythonImportProbePath -Force -ErrorAction SilentlyContinue
    }
  }
  $pythonImportProbeJson = $null
  try {
    if ($pythonImportProbeRaw) {
      $pythonImportProbeJson = $pythonImportProbeRaw | ConvertFrom-Json
    }
  } catch {
    $pythonImportProbeJson = $null
  }
  $pythonImportProbeOk = (
    $pythonImportProbeExitCode -eq 0 `
    -and $null -ne $pythonImportProbeJson `
    -and [bool]$pythonImportProbeJson.ok
  )

  $installerStageSummary.pythonImportProbe = [ordered]@{
    ok = $pythonImportProbeOk
    exitCode = $pythonImportProbeExitCode
    output = $pythonImportProbeRaw
    moduleSet = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.moduleSet)
    xhsModuleSet = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.xhsModuleSet)
    openaiAsyncImport = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.openaiAsyncImport)
    greenletImport = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.greenletImport)
    playwrightAsyncImport = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.playwrightAsyncImport)
    edgeTtsImport = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.edgeTtsImport)
    error = if ($pythonImportProbeJson) { [string]$pythonImportProbeJson.error } else { '' }
  }
  $installerStageSummary.greenletImportProbe = [ordered]@{
    ok = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.greenletImport)
    output = $pythonImportProbeRaw
    error = if ($pythonImportProbeJson) { [string]$pythonImportProbeJson.error } else { '' }
  }
  $installerStageSummary.playwrightAsyncImportProbe = [ordered]@{
    ok = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.playwrightAsyncImport)
    output = $pythonImportProbeRaw
    error = if ($pythonImportProbeJson) { [string]$pythonImportProbeJson.error } else { '' }
  }
  $installerStageSummary.edgeTtsImportProbe = [ordered]@{
    ok = [bool]($pythonImportProbeJson -and $pythonImportProbeJson.edgeTtsImport)
    output = $pythonImportProbeRaw
    error = if ($pythonImportProbeJson) { [string]$pythonImportProbeJson.error } else { '' }
  }

  if (-not $pythonImportProbeOk) {
    $failureStage = 'installer-materialization'
    $failureClassification = 'payload-validation-failure'
    $failureError = 'Bundled Python import probe failed for the packaged runtime payload.'
    $installerStageSummary.failureStage = $failureStage
    $installerStageSummary.failureClassification = $failureClassification
    $installerStageSummary.ok = $false
    throw $failureError
  }

  $installerStageSummary.packagedWorkspaceTemplate = [ordered]@{
    path = $packagedWorkspaceTemplate
    exists = (Test-Path $packagedWorkspaceTemplate)
  }
  if (-not $installerStageSummary.packagedWorkspaceTemplate.exists) {
    $failureStage = 'installer-materialization'
    $failureClassification = 'payload-validation-failure'
    $failureError = "Packaged runtime workspace template is missing: $packagedWorkspaceTemplate"
    $installerStageSummary.failureStage = $failureStage
    $installerStageSummary.failureClassification = $failureClassification
    $installerStageSummary.ok = $false
    throw $failureError
  }

  $runtimePayloadSummary = Get-RuntimePayloadSummary -RuntimeDir $runtimeResourceDir
  $installerStageSummary['runtimePayload'] = $runtimePayloadSummary
  $installerStageSummary['runtimePayloadFileCount'] = $runtimePayloadSummary.fileCount
  $installerStageSummary['runtimePayloadSizeBytes'] = [int64]$runtimePayloadSummary.sizeBytes
  $installerStageSummary['runtimePayloadTopLevelBreakdown'] = $runtimePayloadSummary.topLevelBreakdown

  $failureStage = 'branding-validation'
  $failureClassification = 'branding-validation-failure'
  $installedExeBrandingSummary = Assert-InstalledExeBranding
  $installerStageSummary['installedExeBranding'] = $installedExeBrandingSummary
  $failureStage = ''
  $failureClassification = ''

  if ($SkipInstalledAppSmoke) {
    $appStageSummary = [ordered]@{
      ok = $true
      skipped = $true
      reason = 'SkipInstalledAppSmoke'
    }
  } else {
    $failureStage = 'app-smoke'
    $failureClassification = 'app-smoke-failure'
    $appStageSummary = Invoke-InstalledAppSmoke
    $failureStage = ''
    $failureClassification = ''
  }

  $finalSummary = New-FinalSummary -Ok $true -FailureStage '' -FailureClassification '' -ErrorMessage ''
  Write-JsonFile -FilePath $SmokeOutput -Payload $finalSummary
  Write-Output ($finalSummary | ConvertTo-Json -Depth 20)
  Stop-SmokeAppProcesses -KnownPaths $knownObservationPaths
  exit 0
} catch {
  if (-not $failureError) {
    $failureError = $_.Exception.Message
  }
  if (-not $failureStage) {
    $failureStage = if ($appStageSummary) { 'app-smoke' } else { 'installer-materialization' }
  }
  if (-not $failureClassification) {
    $failureClassification = if ($failureStage -eq 'app-smoke') { 'app-smoke-failure' } else { 'partial-materialization' }
  }

  if ($installerStageSummary -and -not $installerStageSummary.ok -and [string]::IsNullOrWhiteSpace($installerStageSummary.failureClassification)) {
    $installerStageSummary.failureClassification = $failureClassification
  }

  $finalSummary = New-FinalSummary -Ok $false -FailureStage $failureStage -FailureClassification $failureClassification -ErrorMessage $failureError
  Write-JsonFile -FilePath $SmokeOutput -Payload $finalSummary
  Stop-SmokeAppProcesses -KnownPaths $knownObservationPaths
  throw
}
