[CmdletBinding()]
param(
    [string]$InstallRoot = "$env:LOCALAPPDATA\\ImagineTech\\GT",
    [switch]$InstallVsCodeExtension,
    [switch]$CreateDesktopShortcut,
    [switch]$CreateStartMenuShortcut,
    [switch]$Force,
    [switch]$Silent
)

$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest
Add-Type -AssemblyName System.Security
Add-Type -AssemblyName System.Drawing

function Protect-GTRuntimeBlobForCurrentUser([byte[]]$PlainBytes, [string]$Purpose) {
    $entropy = [System.Text.Encoding]::UTF8.GetBytes($Purpose)
    return [System.Security.Cryptography.ProtectedData]::Protect(
        $PlainBytes,
        $entropy,
        [System.Security.Cryptography.DataProtectionScope]::CurrentUser
    )
}

function Get-GTInstalledVersion([string]$VsCodeDistPath) {
    $vsix = Get-ChildItem -Path $VsCodeDistPath -Filter "gt-ghostos-terminal-*.vsix" -ErrorAction SilentlyContinue |
        Sort-Object LastWriteTime -Descending |
        Select-Object -First 1

    if (-not $vsix) {
        throw "No GT VSIX package was found under: $VsCodeDistPath"
    }

    if ($vsix.BaseName -match '^gt-ghostos-terminal-(.+)$') {
        return @{
            Version = $Matches[1]
            Vsix = $vsix
        }
    }

    throw "Could not derive GT version from VSIX filename: $($vsix.Name)"
}

function Get-GTRuntimeKey([string]$Version) {
    $seed = "GT::PUBLIC::RUNTIME::SEALED::$Version::ghostOS"
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($seed)
    $sha = [System.Security.Cryptography.SHA256]::Create()
    try {
        return $sha.ComputeHash($bytes)
    } finally {
        $sha.Dispose()
    }
}

function Decrypt-GTRuntimeFile([string]$SourcePath, [string]$DestinationPath, [string]$Version) {
    $payload = [System.IO.File]::ReadAllBytes($SourcePath)
    if ($payload.Length -le 16) {
        throw "Sealed runtime artifact is too small to decrypt: $SourcePath"
    }

    $iv = New-Object byte[] 16
    [Array]::Copy($payload, 0, $iv, 0, 16)
    $cipherBytes = New-Object byte[] ($payload.Length - 16)
    [Array]::Copy($payload, 16, $cipherBytes, 0, $cipherBytes.Length)

    $key = Get-GTRuntimeKey $Version
    $aes = [System.Security.Cryptography.Aes]::Create()
    $aes.Mode = [System.Security.Cryptography.CipherMode]::CBC
    $aes.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7
    $aes.Key = $key
    $aes.IV = $iv
    try {
        $decryptor = $aes.CreateDecryptor()
        try {
            $plainBytes = $decryptor.TransformFinalBlock($cipherBytes, 0, $cipherBytes.Length)
        } finally {
            $decryptor.Dispose()
        }
    } finally {
        $aes.Dispose()
    }

    [System.IO.File]::WriteAllBytes($DestinationPath, $plainBytes)
}

function Report-GTInstallProgress([scriptblock]$ProgressCallback, [int]$Percent, [string]$Status) {
    if ($null -ne $ProgressCallback) {
        & $ProgressCallback $Percent $Status
    }
}

function New-GTShortcut(
    [string]$ShortcutPath,
    [string]$TargetPath,
    [string]$WorkingDirectory,
    [string]$Description,
    [string]$IconLocation,
    [string]$Arguments = ""
) {
    $shortcutDir = Split-Path -Parent $ShortcutPath
    if (-not (Test-Path $shortcutDir)) {
        New-Item -ItemType Directory -Force -Path $shortcutDir | Out-Null
    }

    if (Test-Path $ShortcutPath) {
        Remove-Item -LiteralPath $ShortcutPath -Force -ErrorAction SilentlyContinue
    }

    $shell = New-Object -ComObject WScript.Shell
    $shortcut = $shell.CreateShortcut($ShortcutPath)
    $shortcut.TargetPath = $TargetPath
    $shortcut.WorkingDirectory = $WorkingDirectory
    $shortcut.Description = $Description
    if (-not [string]::IsNullOrWhiteSpace($Arguments)) {
        $shortcut.Arguments = $Arguments
    }
    if (-not [string]::IsNullOrWhiteSpace($IconLocation) -and (Test-Path $IconLocation)) {
        $shortcut.IconLocation = $IconLocation
    }
    $shortcut.Save()
}

function New-GTRoundedIconFile([string]$SourceImagePath, [string]$DestinationIconPath, [int]$Size = 256, [int]$Radius = 40) {
    $image = [System.Drawing.Image]::FromFile($SourceImagePath)
    try {
        $bitmap = New-Object System.Drawing.Bitmap $Size, $Size, ([System.Drawing.Imaging.PixelFormat]::Format32bppArgb)
        try {
            $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
            try {
                $graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
                $graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
                $graphics.Clear([System.Drawing.Color]::Transparent)

                $path = New-Object System.Drawing.Drawing2D.GraphicsPath
                $diameter = $Radius * 2
                $path.AddArc(0, 0, $diameter, $diameter, 180, 90)
                $path.AddArc($Size - $diameter, 0, $diameter, $diameter, 270, 90)
                $path.AddArc($Size - $diameter, $Size - $diameter, $diameter, $diameter, 0, 90)
                $path.AddArc(0, $Size - $diameter, $diameter, $diameter, 90, 90)
                $path.CloseFigure()

                $graphics.SetClip($path)
                $graphics.DrawImage($image, 0, 0, $Size, $Size)
                $graphics.ResetClip()
            } finally {
                $graphics.Dispose()
            }

            $icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon())
            try {
                $stream = [System.IO.File]::Create($DestinationIconPath)
                try {
                    $icon.Save($stream)
                } finally {
                    $stream.Dispose()
                }
            } finally {
                $icon.Dispose()
            }
        } finally {
            $bitmap.Dispose()
        }
    } finally {
        $image.Dispose()
    }
}

function Resolve-GTWindowsTerminal {
    $candidates = @(
        (Join-Path $env:LOCALAPPDATA "Microsoft\\WindowsApps\\wt.exe"),
        (Join-Path $env:ProgramFiles "WindowsApps\\wt.exe")
    )

    foreach ($candidate in $candidates) {
        if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path $candidate)) {
            return $candidate
        }
    }

    $command = Get-Command "wt.exe" -ErrorAction SilentlyContinue
    if ($null -ne $command -and -not [string]::IsNullOrWhiteSpace($command.Source)) {
        return $command.Source
    }

    return $null
}

function Start-GTOperatorSurface([string]$TargetPath, [string]$Arguments, [string]$WorkingDirectory) {
    if ([string]::IsNullOrWhiteSpace($TargetPath)) {
        throw "No GT launch target was prepared."
    }

    if ($TargetPath.EndsWith(".cmd", [System.StringComparison]::OrdinalIgnoreCase) -or
        $TargetPath.EndsWith(".bat", [System.StringComparison]::OrdinalIgnoreCase)) {
        $cmdArguments = @("/k", "`"$TargetPath`"")
        if (-not [string]::IsNullOrWhiteSpace($Arguments)) {
            $cmdArguments += $Arguments
        }

        Start-Process -FilePath $env:ComSpec -ArgumentList $cmdArguments -WorkingDirectory $WorkingDirectory | Out-Null
        return
    }

    if ([string]::IsNullOrWhiteSpace($Arguments)) {
        Start-Process -FilePath $TargetPath -WorkingDirectory $WorkingDirectory | Out-Null
    } else {
        Start-Process -FilePath $TargetPath -ArgumentList $Arguments -WorkingDirectory $WorkingDirectory | Out-Null
    }
}

function Set-GTRoundedButtonStyle(
    [System.Windows.Forms.Button]$Button,
    [System.Drawing.Color]$BackColor,
    [System.Drawing.Color]$ForeColor,
    [System.Drawing.Color]$BorderColor,
    [int]$Radius
) {
    $applyShape = {
        $rect = New-Object System.Drawing.Rectangle 0, 0, $Button.Width, $Button.Height
        $path = New-Object System.Drawing.Drawing2D.GraphicsPath
        $diameter = [Math]::Max(2, $Radius * 2)
        $path.AddArc($rect.X, $rect.Y, $diameter, $diameter, 180, 90)
        $path.AddArc($rect.Right - $diameter, $rect.Y, $diameter, $diameter, 270, 90)
        $path.AddArc($rect.Right - $diameter, $rect.Bottom - $diameter, $diameter, $diameter, 0, 90)
        $path.AddArc($rect.X, $rect.Bottom - $diameter, $diameter, $diameter, 90, 90)
        $path.CloseFigure()
        $Button.Region = New-Object System.Drawing.Region($path)
    }

    $Button.FlatStyle = "Flat"
    $Button.BackColor = $BackColor
    $Button.ForeColor = $ForeColor
    $Button.FlatAppearance.BorderSize = 0
    $Button.FlatAppearance.BorderColor = $BorderColor
    $Button.FlatAppearance.MouseOverBackColor = $BackColor
    $Button.FlatAppearance.MouseDownBackColor = $BackColor
    $Button.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
    $Button.TabStop = $false
    $Button.Add_SizeChanged($applyShape)
    & $applyShape
}

function Resolve-GTVsCodeCli {
    $candidates = @(
        (Join-Path $env:LOCALAPPDATA "Programs\\Microsoft VS Code\\bin\\code.cmd"),
        (Join-Path $env:ProgramFiles "Microsoft VS Code\\bin\\code.cmd")
    )

    if (${env:ProgramFiles(x86)}) {
        $candidates += (Join-Path ${env:ProgramFiles(x86)} "Microsoft VS Code\\bin\\code.cmd")
    }

    foreach ($candidate in $candidates) {
        if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path $candidate)) {
            return $candidate
        }
    }

    $command = Get-Command "code.cmd" -ErrorAction SilentlyContinue
    if ($null -ne $command -and -not [string]::IsNullOrWhiteSpace($command.Source)) {
        return $command.Source
    }

    return $null
}

function Resolve-GTVmrunCli {
    $candidates = @(
        "C:\Program Files (x86)\VMware\VMware Player\vmrun.exe",
        "C:\Program Files\VMware\VMware Workstation\vmrun.exe"
    )

    foreach ($candidate in $candidates) {
        if (-not [string]::IsNullOrWhiteSpace($candidate) -and (Test-Path $candidate)) {
            return $candidate
        }
    }

    $command = Get-Command "vmrun.exe" -ErrorAction SilentlyContinue
    if ($null -ne $command -and -not [string]::IsNullOrWhiteSpace($command.Source)) {
        return $command.Source
    }

    return $null
}

function Stop-GTRuntimeForInstall([string]$InstallRoot) {
    if ([string]::IsNullOrWhiteSpace($InstallRoot) -or -not (Test-Path $InstallRoot)) {
        return
    }

    $normalizedInstallRoot = [System.IO.Path]::GetFullPath($InstallRoot)
    $escapedRoot = [Regex]::Escape($normalizedInstallRoot)
    $processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object {
        $_.ProcessId -ne $PID -and (
            ($_.ExecutablePath -and $_.ExecutablePath.StartsWith($normalizedInstallRoot, [System.StringComparison]::OrdinalIgnoreCase)) -or
            ($_.CommandLine -and $_.CommandLine -match $escapedRoot)
        ) -and @(
            "ghostterm_host.exe",
            "cmd.exe",
            "powershell.exe",
            "pwsh.exe",
            "WindowsTerminal.exe",
            "wt.exe",
            "conhost.exe"
        ).Contains($_.Name)
    }

    foreach ($process in $processes) {
        try {
            Stop-Process -Id $process.ProcessId -Force -ErrorAction SilentlyContinue
        } catch {
        }
    }

    Start-Sleep -Milliseconds 1200
}

function Remove-GTDirectoryWithRetry([string]$Path) {
    if (-not (Test-Path $Path)) {
        return
    }

    for ($attempt = 0; $attempt -lt 8; $attempt++) {
        try {
            Get-ChildItem -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue | ForEach-Object {
                try {
                    $_.Attributes = [System.IO.FileAttributes]::Normal
                } catch {
                }
            }
            try {
                (Get-Item -LiteralPath $Path -Force -ErrorAction SilentlyContinue).Attributes = [System.IO.FileAttributes]::Directory
            } catch {
            }
            Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction Stop
            return
        } catch {
            if ($attempt -eq 7) {
                throw
            }
            Start-Sleep -Milliseconds 700
        }
    }
}

function Remove-GTInstallPayloadWithRetry([string]$InstallRoot) {
    $payloadPaths = @(
        (Join-Path $InstallRoot "bin"),
        (Join-Path $InstallRoot "vscode"),
        (Join-Path $InstallRoot "docs"),
        (Join-Path $InstallRoot "adapters"),
        (Join-Path $InstallRoot "releases\bootstrap\current"),
        (Join-Path $InstallRoot "releases\runtime\current"),
        (Join-Path $InstallRoot "runtime\sealed")
    )

    $payloadFiles = @(
        (Join-Path $InstallRoot "LICENSE.txt"),
        (Join-Path $InstallRoot "EULA.md"),
        (Join-Path $InstallRoot "gt.cmd"),
        (Join-Path $InstallRoot "gt_host.cmd"),
        (Join-Path $InstallRoot "gt_shell.cmd"),
        (Join-Path $InstallRoot "INSTALLATION.txt"),
        (Join-Path $InstallRoot "gt.ico")
    )

    foreach ($path in $payloadPaths) {
        if (Test-Path $path) {
            Remove-GTDirectoryWithRetry -Path $path
        }
    }

    foreach ($file in $payloadFiles) {
        if (-not (Test-Path $file)) {
            continue
        }

        for ($attempt = 0; $attempt -lt 8; $attempt++) {
            try {
                (Get-Item -LiteralPath $file -Force -ErrorAction SilentlyContinue).Attributes = [System.IO.FileAttributes]::Normal
            } catch {
            }

            try {
                Remove-Item -LiteralPath $file -Force -ErrorAction Stop
                break
            } catch {
                if ($attempt -eq 7) {
                    throw
                }
                Start-Sleep -Milliseconds 500
            }
        }
    }
}

function Get-GTInstallIdentityHash([string]$Value) {
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Value)
    $sha = [System.Security.Cryptography.SHA256]::Create()
    try {
        $hash = $sha.ComputeHash($bytes)
    } finally {
        $sha.Dispose()
    }

    -join ($hash[0..5] | ForEach-Object { $_.ToString("x2") })
}

function Convert-HostWifiAuthenticationToSeedToken([string]$Authentication) {
    $value = $Authentication.Trim().ToLowerInvariant()
    if ($value.Contains("open")) { return "open" }
    if ($value.Contains("wep")) { return "wep" }
    if ($value.Contains("wpa3")) { return "wpa3" }
    if ($value.Contains("wpa2")) {
        if ($value.Contains("enterprise")) { return "enterprise" }
        return "wpa2"
    }
    if ($value.Contains("wpa")) {
        if ($value.Contains("enterprise")) { return "enterprise" }
        return "wpa"
    }
    return "open"
}

function Convert-HostWifiSignalPercentToDbm([uint32]$SignalPercent) {
    if ($SignalPercent -gt 100) { $SignalPercent = 100 }
    return [int](-100 + [Math]::Floor($SignalPercent / 2))
}

function Convert-HostWifiChannelToFrequencyMhz([uint32]$Channel) {
    if ($Channel -eq 14) { return 2484 }
    if ($Channel -ge 1 -and $Channel -le 13) { return 2412 + (($Channel - 1) * 5) }
    if ($Channel -gt 14) { return 5000 + ($Channel * 5) }
    return 0
}

function Sanitize-HostWifiSeedSsid([string]$Ssid) {
    if ($null -eq $Ssid) { return "hidden-network" }
    $trimmed = $Ssid.Trim()
    if ($trimmed.Length -eq 0) { return "hidden-network" }
    $builder = New-Object System.Text.StringBuilder
    foreach ($character in $trimmed.ToCharArray()) {
        $code = [int][char]$character
        if ($code -ge 32 -and $code -le 126 -and $character -ne '"' -and $character -ne '\') {
            [void]$builder.Append($character)
        } else {
            [void]$builder.Append('_')
        }
    }
    $sanitized = $builder.ToString().Trim()
    if ([string]::IsNullOrWhiteSpace($sanitized)) { return "hidden-network" }
    if ($sanitized.Length -gt 32) { return $sanitized.Substring(0, 32) }
    return $sanitized
}

function Invoke-GTHostWiFiProbeCache([string]$CachePath) {
    $scanOutput = & netsh wlan show networks mode=bssid 2>$null | Out-String
    if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($scanOutput)) {
        return $false
    }

    $connectedSsid = "NONE"
    $interfacesOutput = & netsh wlan show interfaces 2>$null | Out-String
    if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($interfacesOutput)) {
        $state = ""
        foreach ($rawLine in ($interfacesOutput -split "`r?`n")) {
            $line = $rawLine.Trim()
            if ($line -match '^State\s*:\s*(.*)$') {
                $state = $matches[1].Trim().ToLowerInvariant()
                continue
            }
            if ($line -match '^SSID\s*:\s*(.*)$' -and $line -notmatch '^SSID name' -and $line -notmatch '^SSID broadcast' -and $line -notmatch '^SSIDs') {
                if ($state -eq "connected") {
                    $connectedSsid = Sanitize-HostWifiSeedSsid $matches[1]
                }
            }
        }
    }

    $current = $null
    $networks = New-Object System.Collections.Generic.List[object]
    foreach ($rawLine in ($scanOutput -split "`r?`n")) {
        $line = $rawLine.Trim()
        if ([string]::IsNullOrWhiteSpace($line)) { continue }

        if ($line -match '^SSID\s+\d+\s+:\s*(.*)$') {
            if ($null -ne $current -and -not [string]::IsNullOrWhiteSpace($current.Ssid)) {
                $networks.Add($current)
            }
            $current = [ordered]@{
                Ssid = Sanitize-HostWifiSeedSsid $matches[1]
                Security = "open"
                SignalDbm = -100
                Channel = 0
                Frequency = 0
            }
            continue
        }

        if ($null -eq $current) { continue }

        if ($line -match '^Authentication\s*:\s*(.*)$') {
            $current.Security = Convert-HostWifiAuthenticationToSeedToken $matches[1]
            continue
        }
        if ($line -match '^Signal\s*:\s*(\d+)%') {
            $current.SignalDbm = Convert-HostWifiSignalPercentToDbm ([uint32]$matches[1])
            continue
        }
        if ($line -match '^Channel\s*:\s*(\d+)$') {
            $current.Channel = [uint32]$matches[1]
            $current.Frequency = Convert-HostWifiChannelToFrequencyMhz $current.Channel
            continue
        }
    }

    if ($null -ne $current -and -not [string]::IsNullOrWhiteSpace($current.Ssid)) {
        $networks.Add($current)
    }

    if ($networks.Count -eq 0) {
        return $false
    }

    $cacheDir = Split-Path -Parent $CachePath
    if (-not (Test-Path $cacheDir)) {
        New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null
    }

    $lines = New-Object System.Collections.Generic.List[string]
    $lines.Add("CONNECTED|$connectedSsid")
    foreach ($network in ($networks | Sort-Object -Property @{ Expression = { $_.SignalDbm }; Descending = $true }, @{ Expression = { $_.Ssid }; Descending = $false } | Select-Object -First 32)) {
        $lines.Add(("ITEM|{0}|{1}|{2}|{3}|{4}" -f $network.Security, $network.SignalDbm, $network.Channel, $network.Frequency, $network.Ssid))
    }

    [System.IO.File]::WriteAllLines($CachePath, $lines, [System.Text.Encoding]::UTF8)
    return $true
}

function Write-GTHostWiFiProbeScript([string]$ScriptPath, [string]$CachePath) {
    $scriptDir = Split-Path -Parent $ScriptPath
    if (-not (Test-Path $scriptDir)) {
        New-Item -ItemType Directory -Force -Path $scriptDir | Out-Null
    }

    $scriptLines = @'
$ErrorActionPreference = "Stop"

function Convert-HostWifiAuthenticationToSeedToken([string]$Authentication) {
    $value = $Authentication.Trim().ToLowerInvariant()
    if ($value.Contains("open")) { return "open" }
    if ($value.Contains("wep")) { return "wep" }
    if ($value.Contains("wpa3")) { return "wpa3" }
    if ($value.Contains("wpa2")) {
        if ($value.Contains("enterprise")) { return "enterprise" }
        return "wpa2"
    }
    if ($value.Contains("wpa")) {
        if ($value.Contains("enterprise")) { return "enterprise" }
        return "wpa"
    }
    return "open"
}

function Convert-HostWifiSignalPercentToDbm([uint32]$SignalPercent) {
    if ($SignalPercent -gt 100) { $SignalPercent = 100 }
    return [int](-100 + [Math]::Floor($SignalPercent / 2))
}

function Convert-HostWifiChannelToFrequencyMhz([uint32]$Channel) {
    if ($Channel -eq 14) { return 2484 }
    if ($Channel -ge 1 -and $Channel -le 13) { return 2412 + (($Channel - 1) * 5) }
    if ($Channel -gt 14) { return 5000 + ($Channel * 5) }
    return 0
}

function Sanitize-HostWifiSeedSsid([string]$Ssid) {
    if ($null -eq $Ssid) { return "hidden-network" }
    $trimmed = $Ssid.Trim()
    if ($trimmed.Length -eq 0) { return "hidden-network" }
    $builder = New-Object System.Text.StringBuilder
    foreach ($character in $trimmed.ToCharArray()) {
        $code = [int][char]$character
        if ($code -ge 32 -and $code -le 126 -and $character -ne '"' -and $character -ne '\') {
            [void]$builder.Append($character)
        } else {
            [void]$builder.Append('_')
        }
    }
    $sanitized = $builder.ToString().Trim()
    if ([string]::IsNullOrWhiteSpace($sanitized)) { return "hidden-network" }
    if ($sanitized.Length -gt 32) { return $sanitized.Substring(0, 32) }
    return $sanitized
}

$scanOutput = & netsh wlan show networks mode=bssid 2>$null | Out-String
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($scanOutput)) {
    exit 1
}

$connectedSsid = "NONE"
$interfacesOutput = & netsh wlan show interfaces 2>$null | Out-String
if ($LASTEXITCODE -eq 0 -and -not [string]::IsNullOrWhiteSpace($interfacesOutput)) {
    $state = ""
    foreach ($rawLine in ($interfacesOutput -split "`r?`n")) {
        $line = $rawLine.Trim()
        if ($line -match '^State\s*:\s*(.*)$') {
            $state = $matches[1].Trim().ToLowerInvariant()
            continue
        }
        if ($line -match '^SSID\s*:\s*(.*)$' -and $line -notmatch '^SSID name' -and $line -notmatch '^SSID broadcast' -and $line -notmatch '^SSIDs') {
            if ($state -eq "connected") {
                $connectedSsid = Sanitize-HostWifiSeedSsid $matches[1]
            }
        }
    }
}

$current = $null
$networks = New-Object System.Collections.Generic.List[object]
foreach ($rawLine in ($scanOutput -split "`r?`n")) {
    $line = $rawLine.Trim()
    if ([string]::IsNullOrWhiteSpace($line)) { continue }

    if ($line -match '^SSID\s+\d+\s+:\s*(.*)$') {
        if ($null -ne $current -and -not [string]::IsNullOrWhiteSpace($current.Ssid)) {
            $networks.Add($current)
        }
        $current = [ordered]@{
            Ssid = Sanitize-HostWifiSeedSsid $matches[1]
            Security = "open"
            SignalDbm = -100
            Channel = 0
            Frequency = 0
        }
        continue
    }

    if ($null -eq $current) { continue }

    if ($line -match '^Authentication\s*:\s*(.*)$') {
        $current.Security = Convert-HostWifiAuthenticationToSeedToken $matches[1]
        continue
    }
    if ($line -match '^Signal\s*:\s*(\d+)%') {
        $current.SignalDbm = Convert-HostWifiSignalPercentToDbm ([uint32]$matches[1])
        continue
    }
    if ($line -match '^Channel\s*:\s*(\d+)$') {
        $current.Channel = [uint32]$matches[1]
        $current.Frequency = Convert-HostWifiChannelToFrequencyMhz $current.Channel
        continue
    }
}

if ($null -ne $current -and -not [string]::IsNullOrWhiteSpace($current.Ssid)) {
    $networks.Add($current)
}

$lines = New-Object System.Collections.Generic.List[string]
$lines.Add("CONNECTED|$connectedSsid")
foreach ($network in ($networks | Sort-Object -Property @{ Expression = { $_.SignalDbm }; Descending = $true }, @{ Expression = { $_.Ssid }; Descending = $false } | Select-Object -First 32)) {
    $lines.Add(("ITEM|{0}|{1}|{2}|{3}|{4}" -f $network.Security, $network.SignalDbm, $network.Channel, $network.Frequency, $network.Ssid))
}
[System.IO.File]::WriteAllLines("__CACHE_PATH__", $lines, [System.Text.Encoding]::UTF8)
'@

    $resolvedLines = $scriptLines.Replace("__CACHE_PATH__", $CachePath.Replace('\', '\\'))
    Set-Content -Path $ScriptPath -Value $resolvedLines -Encoding UTF8
}

function Register-GTHostWiFiProbeTask([string]$TaskName, [string]$ScriptPath) {
    $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument ('-NoProfile -ExecutionPolicy Bypass -File "' + $ScriptPath + '"')
    $trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date).Date.AddHours(23))
    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
    try {
        $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
        Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
        return
    } catch {
    }

    $user = if ([string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { $env:USERNAME } else { "$env:USERDOMAIN\$env:USERNAME" }
    $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
    Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
}

function Invoke-GTHostWiFiRefresh([string]$GhosttermExecutable, [switch]$Quiet) {
    if (-not (Test-Path $GhosttermExecutable)) {
        throw "ghostterm_host.exe was not found: $GhosttermExecutable"
    }

    $arguments = @("hostwifi", "refresh-cache")
    if ($Quiet) {
        $arguments += "--quiet"
    }

    $process = Start-Process -FilePath $GhosttermExecutable `
        -ArgumentList $arguments `
        -Wait `
        -PassThru `
        -WindowStyle Hidden

    return ($process.ExitCode -eq 0)
}

function Register-GTHostWiFiRefreshTask([string]$TaskName, [string]$GhosttermExecutable) {
    $action = New-ScheduledTaskAction -Execute $GhosttermExecutable -Argument "hostwifi refresh-cache --quiet"
    $trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date).Date.AddHours(23))
    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
    $user = if ([string]::IsNullOrWhiteSpace($env:USERDOMAIN)) { $env:USERNAME } else { "$env:USERDOMAIN\$env:USERNAME" }
    $principal = New-ScheduledTaskPrincipal -UserId $user -LogonType Interactive -RunLevel Highest
    Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
}

function Install-GTVsCodeExtensionDirect([string]$VsixPath) {
    $extensionsRoot = Join-Path $env:USERPROFILE ".vscode\\extensions"
    New-Item -ItemType Directory -Force -Path $extensionsRoot | Out-Null

    $tempRoot = Join-Path $env:TEMP ("gt_vsix_unpack_" + [guid]::NewGuid().ToString("N"))
    New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null

    try {
        Expand-Archive -LiteralPath $VsixPath -DestinationPath $tempRoot -Force
        $packageJsonPath = Join-Path $tempRoot "extension\\package.json"
        if (-not (Test-Path $packageJsonPath)) {
            throw "The VSIX did not contain extension\\package.json."
        }

        $package = Get-Content $packageJsonPath -Raw | ConvertFrom-Json
        $publisher = [string]$package.publisher
        $name = [string]$package.name
        $version = [string]$package.version
        if ([string]::IsNullOrWhiteSpace($publisher) -or [string]::IsNullOrWhiteSpace($name) -or [string]::IsNullOrWhiteSpace($version)) {
            throw "The VSIX manifest is missing publisher, name, or version."
        }

        $extensionIdPrefix = ($publisher + "." + $name).ToLowerInvariant()
        $targetDir = Join-Path $extensionsRoot ($extensionIdPrefix + "-" + $version)
        if (Test-Path $targetDir) {
            Remove-Item -LiteralPath $targetDir -Recurse -Force
        }

        New-Item -ItemType Directory -Force -Path $targetDir | Out-Null
        Copy-Item -Path (Join-Path $tempRoot "extension\\*") -Destination $targetDir -Recurse -Force

        return [pscustomobject]@{
            Target = $targetDir
            ExtensionIdPrefix = $extensionIdPrefix
            Version = $version
        }
    } finally {
        if (Test-Path $tempRoot) {
            Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
}

function Install-GTPublicBundle {
    param(
        [Parameter(Mandatory = $true)]
        [string]$InstallRoot,
        [Parameter(Mandatory = $true)]
        [bool]$InstallVsCodeExtension,
        [Parameter(Mandatory = $true)]
        [bool]$CreateDesktopShortcut,
        [Parameter(Mandatory = $true)]
        [bool]$CreateStartMenuShortcut,
        [Parameter(Mandatory = $true)]
        [bool]$Force,
        [scriptblock]$ProgressCallback
    )

    Report-GTInstallProgress $ProgressCallback 5 "Validating GT installer payload..."

    $bundleRoot = Resolve-Path $PSScriptRoot
    $payloadRoot = Join-Path $bundleRoot "payload"
    $payloadVsCode = Join-Path $payloadRoot "vscode\\dist"
    $vsCodeInstallStatus = "not-requested"
    $vsCodeInstallMessage = ""
    $installWarnings = New-Object System.Collections.Generic.List[string]

    if (-not (Test-Path $payloadRoot)) {
        throw "Installer payload was not found: $payloadRoot"
    }

    if ((Test-Path $InstallRoot) -and -not $Force) {
        throw "Install root already exists. Re-run with -Force to overwrite: $InstallRoot"
    }

    if ((Test-Path $InstallRoot) -and $Force) {
        Report-GTInstallProgress $ProgressCallback 12 "Preparing the existing GT install for update..."
        Stop-GTRuntimeForInstall -InstallRoot $InstallRoot
        Remove-GTInstallPayloadWithRetry -InstallRoot $InstallRoot
    }

    Report-GTInstallProgress $ProgressCallback 18 "Copying GT public payload..."
    New-Item -ItemType Directory -Force -Path $InstallRoot | Out-Null
    Copy-Item -Path (Join-Path $payloadRoot "*") -Destination $InstallRoot -Recurse -Force

    $versionInfo = Get-GTInstalledVersion $payloadVsCode
    $gtVersion = [string]$versionInfo.Version
    $sealedRuntimeRoot = Join-Path $InstallRoot "runtime\\sealed"
    $runtimeCurrentRoot = Join-Path $InstallRoot "releases\\runtime\\current"
    New-Item -ItemType Directory -Force -Path $runtimeCurrentRoot | Out-Null

    Report-GTInstallProgress $ProgressCallback 30 "Verifying sealed runtime manifest..."
    $sealedManifestPath = Join-Path $sealedRuntimeRoot "runtime_manifest.txt.gtr"
    if (-not (Test-Path $sealedManifestPath)) {
        throw "Required sealed runtime artifact was not found: $sealedManifestPath"
    }
    $runtimeManifestPath = Join-Path $runtimeCurrentRoot "runtime_manifest.txt"
    Decrypt-GTRuntimeFile -SourcePath $sealedManifestPath -DestinationPath $runtimeManifestPath -Version $gtVersion

    $runtimeDiskFiles = @()
    $runtimeNvramFile = $null
    if (Test-Path $runtimeManifestPath) {
        $runtimeNvramFile = Get-Content $runtimeManifestPath |
            Where-Object { $_ -like 'nvram_file=*' } |
            Select-Object -First 1
        if ($runtimeNvramFile) {
            $payload = $runtimeNvramFile.Substring('nvram_file='.Length)
            $runtimeNvramFile = ($payload -split '\|')[0]
        }
        $runtimeDiskFiles = Get-Content $runtimeManifestPath |
            Where-Object { $_ -like 'disk_file=*' } |
            ForEach-Object {
                $payload = $_.Substring('disk_file='.Length)
                ($payload -split '\|')[0]
            } |
            Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
    }

    $sealedIsoPath = Join-Path $sealedRuntimeRoot "ghostos_mobile.iso.gtr"
    if (-not (Test-Path $sealedIsoPath)) {
        throw "Required sealed runtime ISO was not found: $sealedIsoPath"
    }

    Report-GTInstallProgress $ProgressCallback 48 "Materializing the runtime for this Windows user..."
    $tempIsoPath = Join-Path $runtimeCurrentRoot "ghostos_mobile.iso"
    $protectedIsoPath = Join-Path $runtimeCurrentRoot "ghostos_mobile.iso.dpapi"
    try {
        Decrypt-GTRuntimeFile -SourcePath $sealedIsoPath -DestinationPath $tempIsoPath -Version $gtVersion
        $isoBytes = [System.IO.File]::ReadAllBytes($tempIsoPath)
        $protectedIsoBytes = Protect-GTRuntimeBlobForCurrentUser -PlainBytes $isoBytes -Purpose "GhostOS-GT-BundledRuntimeIso-v1"
        [System.IO.File]::WriteAllBytes($protectedIsoPath, $protectedIsoBytes)
    } finally {
        if (Test-Path $tempIsoPath) {
            Remove-Item -LiteralPath $tempIsoPath -Force
        }
    }

    $hiddenAttributes = [System.IO.FileAttributes]::Hidden -bor [System.IO.FileAttributes]::System -bor [System.IO.FileAttributes]::Archive
    [System.IO.File]::SetAttributes($protectedIsoPath, $hiddenAttributes)

    foreach ($legacyRuntime in @(
        (Join-Path $runtimeCurrentRoot "ghostos_mobile.elf"),
        (Join-Path $runtimeCurrentRoot "ghostos_mobile.bin"),
        (Join-Path $runtimeCurrentRoot "ghostos_mobile.iso")
    )) {
        if (Test-Path $legacyRuntime) {
            Remove-Item -LiteralPath $legacyRuntime -Force
        }
    }

    if ($runtimeNvramFile) {
        $sealedNvramPath = Join-Path $sealedRuntimeRoot ($runtimeNvramFile + ".gtr")
        if (-not (Test-Path $sealedNvramPath)) {
            throw "Required sealed runtime NVRAM was not found: $sealedNvramPath"
        }

        $targetNvramPath = Join-Path $runtimeCurrentRoot $runtimeNvramFile
        Decrypt-GTRuntimeFile -SourcePath $sealedNvramPath -DestinationPath $targetNvramPath -Version $gtVersion
        [System.IO.File]::SetAttributes($targetNvramPath, $hiddenAttributes)
    }

    foreach ($runtimeDiskFile in $runtimeDiskFiles) {
        $sealedDiskPath = Join-Path $sealedRuntimeRoot ($runtimeDiskFile + ".gtr")
        if (-not (Test-Path $sealedDiskPath)) {
            throw "Required sealed runtime disk was not found: $sealedDiskPath"
        }

        $targetDiskPath = Join-Path $runtimeCurrentRoot $runtimeDiskFile
        Decrypt-GTRuntimeFile -SourcePath $sealedDiskPath -DestinationPath $targetDiskPath -Version $gtVersion
        [System.IO.File]::SetAttributes($targetDiskPath, $hiddenAttributes)
    }

    if (Test-Path $sealedRuntimeRoot) {
        Remove-Item -LiteralPath $sealedRuntimeRoot -Recurse -Force
    }

    Report-GTInstallProgress $ProgressCallback 66 "Writing GT launcher and install manifest..."
    $launcherPath = Join-Path $InstallRoot "gt.cmd"
    $launcherLines = @(
        "@echo off"
        "setlocal"
        "chcp 65001 >nul",
        "title GT - GhostOS Terminal",
        "set TERM=xterm-256color",
        "if ""%~1""=="""" (",
        '  "%~dp0bin\ghostterm_host.exe" enter ghostos --shell --headless --restart-vm',
        ") else (",
        '  "%~dp0bin\ghostterm_host.exe" %*',
        ")"
    )
    Set-Content -Path $launcherPath -Value $launcherLines -Encoding ASCII

    $hostLauncherPath = Join-Path $InstallRoot "gt_host.cmd"
    $hostLauncherLines = @(
        "@echo off",
        "setlocal",
        "chcp 65001 >nul",
        "title GT Host Shell",
        "set TERM=xterm-256color",
        '"%~dp0bin\ghostterm_host.exe" %*'
    )
    Set-Content -Path $hostLauncherPath -Value $hostLauncherLines -Encoding ASCII

    $shellLauncherPath = Join-Path $InstallRoot "gt_shell.cmd"
    $shellLauncherLines = @(
        "@echo off",
        "setlocal",
        "chcp 65001 >nul",
        "title GT - GhostOS Terminal",
        "set TERM=xterm-256color",
        '"%~dp0gt.cmd %*',
        "set ""GT_EXITCODE=%ERRORLEVEL%""",
        "if not ""%GT_EXITCODE%""==""0"" (",
        "  echo.",
        "  echo GT exited with code %GT_EXITCODE%.",
        "  pause",
        ")",
        "exit /b %GT_EXITCODE%"
    )
    Set-Content -Path $shellLauncherPath -Value $shellLauncherLines -Encoding ASCII

    $installManifest = Join-Path $InstallRoot "INSTALLATION.txt"
    $installedUtc = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
    $runtimeRoot = Join-Path $InstallRoot "runtime"
    $hostWiFiProbeScriptPath = Join-Path $runtimeRoot "gt_wifi_probe.ps1"
    $hostWiFiCachePath = Join-Path $runtimeRoot "gt_wifi_cache.txt"
    $hostWiFiTaskName = "ImagineTech_GT_HostWiFi_" + (Get-GTInstallIdentityHash $InstallRoot)
    $ghosttermExecutable = Join-Path $InstallRoot "bin\\ghostterm_host.exe"
    $manifestLines = @(
        "product=GT public install"
        "gt_version=$gtVersion"
        "installed_utc=$installedUtc"
        "install_root=$InstallRoot"
        "launcher=gt.cmd"
        "host_launcher=gt_host.cmd"
        "shell_launcher=gt_shell.cmd"
        "license=LICENSE.txt"
        "eula=EULA.md"
        "bootstrap_ops=releases\\bootstrap\\current\\payload"
        "runtime=releases\\runtime\\current\\ghostos_mobile.iso.dpapi"
        "host_wifi_task=$hostWiFiTaskName"
        "host_wifi_cache=runtime\\gt_wifi_cache.txt"
    )
    Set-Content -Path $installManifest -Value $manifestLines -Encoding ASCII

    try {
        if (-not (Invoke-GTHostWiFiRefresh -GhosttermExecutable $ghosttermExecutable -Quiet)) {
            $installWarnings.Add("GT could not capture visible host Wi-Fi networks during setup. If Windows blocks Wi-Fi enumeration for this session, GT may temporarily show only the VMware uplink until the installed host Wi-Fi task refreshes successfully.")
        }
        Register-GTHostWiFiRefreshTask -TaskName $hostWiFiTaskName -GhosttermExecutable $ghosttermExecutable
    } catch {
        $installWarnings.Add("Host Wi-Fi helper could not be registered automatically. GT may only see the VMware fallback network until the installer is run once with administrator approval.")
    }

    $iconPath = Join-Path $InstallRoot "gt.ico"
    try {
        New-GTRoundedIconFile -SourceImagePath (Join-Path $payloadRoot "vscode\\media\\gt.png") -DestinationIconPath $iconPath
    } catch {
        $iconPath = $ghosttermExecutable
    }

    $terminalHost = Resolve-GTWindowsTerminal
    $shortcutTarget = $launcherPath
    $shortcutArguments = ""
    if (-not [string]::IsNullOrWhiteSpace($terminalHost)) {
        $shortcutTarget = $terminalHost
        $shortcutArguments = "new-tab --title ""GT - GhostOS Terminal"" cmd /k ""$launcherPath"""
    }

    if ($CreateDesktopShortcut) {
        Report-GTInstallProgress $ProgressCallback 76 "Creating desktop shortcut..."
        $desktopShortcut = Join-Path ([Environment]::GetFolderPath('Desktop')) "GT - GhostOS Terminal.lnk"
        try {
            New-GTShortcut -ShortcutPath $desktopShortcut `
                -TargetPath $shortcutTarget `
                -WorkingDirectory $InstallRoot `
                -Description "GT - GhostOS Terminal" `
                -IconLocation $iconPath `
                -Arguments $shortcutArguments
        } catch {
            $installWarnings.Add("Desktop shortcut could not be created automatically. You can launch GT from: $launcherPath")
        }
    }

    if ($CreateStartMenuShortcut) {
        Report-GTInstallProgress $ProgressCallback 82 "Creating Start Menu shortcut..."
        $programsDir = [Environment]::GetFolderPath('Programs')
        $startMenuShortcut = Join-Path $programsDir "Imagine Tech\\GT - GhostOS Terminal.lnk"
        try {
            New-GTShortcut -ShortcutPath $startMenuShortcut `
                -TargetPath $shortcutTarget `
                -WorkingDirectory $InstallRoot `
                -Description "GT - GhostOS Terminal" `
                -IconLocation $iconPath `
                -Arguments $shortcutArguments
        } catch {
            $installWarnings.Add("Start Menu shortcut could not be created automatically. You can launch GT from: $launcherPath")
        }
    }

    if ($InstallVsCodeExtension) {
        Report-GTInstallProgress $ProgressCallback 90 "Installing the GT VS Code extension..."
        $vsix = $versionInfo.Vsix
        $codeCli = Resolve-GTVsCodeCli
        if (-not $codeCli) {
            try {
                $directInstall = Install-GTVsCodeExtensionDirect -VsixPath $vsix.FullName
                $vsCodeInstallStatus = "installed"
                $vsCodeInstallMessage = "GT installed the VS Code extension directly into $($directInstall.Target). Reload VS Code if it was already open."
            } catch {
                $vsCodeInstallStatus = "manual-required"
                $vsCodeInstallMessage = "GT installed the bundled runtime, but VS Code CLI was not found and direct VSIX deployment failed. Install the bundled VSIX manually from: $($vsix.FullName)"
            }
        } else {
            try {
                $commandLine = "`"$codeCli`" --install-extension `"$($vsix.FullName)`" --force"
                $process = Start-Process -FilePath $env:ComSpec `
                    -ArgumentList @("/d", "/c", $commandLine) `
                    -Wait `
                    -PassThru `
                    -WindowStyle Hidden

                if ($process.ExitCode -eq 0) {
                    $vsCodeInstallStatus = "installed"
                } else {
                    $directInstall = Install-GTVsCodeExtensionDirect -VsixPath $vsix.FullName
                    $vsCodeInstallStatus = "installed"
                    $vsCodeInstallMessage = "GT installed the VS Code extension directly into $($directInstall.Target) after the VS Code CLI returned an error. Reload VS Code if it was already open."
                }
            } catch {
                try {
                    $directInstall = Install-GTVsCodeExtensionDirect -VsixPath $vsix.FullName
                    $vsCodeInstallStatus = "installed"
                    $vsCodeInstallMessage = "GT installed the VS Code extension directly into $($directInstall.Target) after the automatic CLI install failed. Reload VS Code if it was already open."
                } catch {
                    $vsCodeInstallStatus = "manual-required"
                    $vsCodeInstallMessage = "GT installed successfully, but VS Code extension installation failed. Install the bundled VSIX manually from: $($vsix.FullName)"
                }
            }
        }
    }

    Report-GTInstallProgress $ProgressCallback 100 "GT is installed and ready."

    return [pscustomobject]@{
        Root = $InstallRoot
        Launcher = $launcherPath
        HostLauncher = $hostLauncherPath
        ShellLauncher = $shellLauncherPath
        ShortcutTarget = $shortcutTarget
        ShortcutArguments = $shortcutArguments
        Runtime = $protectedIsoPath
        Version = $gtVersion
        Vsix = $versionInfo.Vsix.FullName
        VsCodeInstallStatus = $vsCodeInstallStatus
        VsCodeInstallMessage = $vsCodeInstallMessage
        Warnings = @($installWarnings)
    }
}

function Show-GTInstallerUi {
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing

    $imagePath = Join-Path $PSScriptRoot "payload\\vscode\\media\\gt.png"
    $defaultInstallRoot = $InstallRoot
    $codeCli = Resolve-GTVsCodeCli
    $codeAvailable = -not [string]::IsNullOrWhiteSpace($codeCli)
    $installResult = $null
    $installBusy = $false

    $form = New-Object System.Windows.Forms.Form
    $form.Text = "GT - GhostOS Terminal Setup"
    $form.StartPosition = "CenterScreen"
    $form.Size = New-Object System.Drawing.Size(1024, 660)
    $form.MinimumSize = New-Object System.Drawing.Size(1024, 660)
    $form.BackColor = [System.Drawing.Color]::FromArgb(248, 250, 252)
    $form.FormBorderStyle = "FixedDialog"
    $form.MaximizeBox = $false
    $form.MinimizeBox = $true

    $rightPanel = New-Object System.Windows.Forms.Panel
    $rightPanel.Dock = "Fill"
    $rightPanel.BackColor = [System.Drawing.Color]::FromArgb(248, 250, 252)
    $form.Controls.Add($rightPanel)

    $leftPanel = New-Object System.Windows.Forms.Panel
    $leftPanel.Dock = "Left"
    $leftPanel.Width = 380
    $leftPanel.BackColor = [System.Drawing.Color]::FromArgb(0, 0, 0)
    $form.Controls.Add($leftPanel)

    if (Test-Path $imagePath) {
        $picture = New-Object System.Windows.Forms.PictureBox
        $picture.Image = [System.Drawing.Image]::FromFile($imagePath)
        $picture.SizeMode = "Zoom"
        $picture.Location = New-Object System.Drawing.Point(28, 28)
        $picture.Size = New-Object System.Drawing.Size(324, 208)
        $leftPanel.Controls.Add($picture)
    }

    $brandLabel = New-Object System.Windows.Forms.Label
    $brandLabel.Text = "GT"
    $brandLabel.ForeColor = [System.Drawing.Color]::FromArgb(241, 245, 249)
    $brandLabel.Font = New-Object System.Drawing.Font("Segoe UI", 11, [System.Drawing.FontStyle]::Bold)
    $brandLabel.AutoSize = $true
    $brandLabel.Location = New-Object System.Drawing.Point(28, 258)
    $leftPanel.Controls.Add($brandLabel)

    $titleLabel = New-Object System.Windows.Forms.Label
    $titleLabel.Text = "GhostOS Terminal"
    $titleLabel.ForeColor = [System.Drawing.Color]::White
    $titleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 24, [System.Drawing.FontStyle]::Bold)
    $titleLabel.AutoSize = $true
    $titleLabel.Location = New-Object System.Drawing.Point(24, 286)
    $leftPanel.Controls.Add($titleLabel)

    $subtitleLabel = New-Object System.Windows.Forms.Label
    $subtitleLabel.Text = "A public operator install with a sealed ghostOS runtime, serial-first identity, MultiNode onboarding, and live P2P bring-up."
    $subtitleLabel.ForeColor = [System.Drawing.Color]::FromArgb(203, 213, 225)
    $subtitleLabel.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $subtitleLabel.Size = New-Object System.Drawing.Size(320, 84)
    $subtitleLabel.Location = New-Object System.Drawing.Point(28, 338)
    $leftPanel.Controls.Add($subtitleLabel)

    $bulletLabel = New-Object System.Windows.Forms.Label
    $bulletLabel.Text = ([string]::Join([Environment]::NewLine, @(
        "- Binary-only public bundle",
        "- Sealed runtime re-protected with DPAPI",
        "- VS Code integration included",
        "- Wallet, Wi-Fi, MultiNode and P2P flows",
        "- VM runtime staged only when needed"
    )))
    $bulletLabel.ForeColor = [System.Drawing.Color]::FromArgb(203, 213, 225)
    $bulletLabel.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $bulletLabel.Size = New-Object System.Drawing.Size(320, 140)
    $bulletLabel.Location = New-Object System.Drawing.Point(28, 432)
    $leftPanel.Controls.Add($bulletLabel)

    $heading = New-Object System.Windows.Forms.Label
    $heading.Text = "Install GT"
    $heading.Font = New-Object System.Drawing.Font("Segoe UI", 24, [System.Drawing.FontStyle]::Bold)
    $heading.ForeColor = [System.Drawing.Color]::FromArgb(15, 23, 42)
    $heading.AutoSize = $true
    $heading.Location = New-Object System.Drawing.Point(44, 42)
    $rightPanel.Controls.Add($heading)

    $subheading = New-Object System.Windows.Forms.Label
    $subheading.Text = "Set the install location, choose your shortcuts, and let GT prepare the secure runtime for this Windows user."
    $subheading.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $subheading.ForeColor = [System.Drawing.Color]::FromArgb(71, 85, 105)
    $subheading.Size = New-Object System.Drawing.Size(520, 48)
    $subheading.Location = New-Object System.Drawing.Point(46, 88)
    $rightPanel.Controls.Add($subheading)

    $locationLabel = New-Object System.Windows.Forms.Label
    $locationLabel.Text = "Install location"
    $locationLabel.Font = New-Object System.Drawing.Font("Segoe UI", 10, [System.Drawing.FontStyle]::Bold)
    $locationLabel.ForeColor = [System.Drawing.Color]::FromArgb(15, 23, 42)
    $locationLabel.AutoSize = $true
    $locationLabel.Location = New-Object System.Drawing.Point(48, 154)
    $rightPanel.Controls.Add($locationLabel)

    $locationBox = New-Object System.Windows.Forms.TextBox
    $locationBox.Text = $defaultInstallRoot
    $locationBox.Location = New-Object System.Drawing.Point(48, 182)
    $locationBox.Size = New-Object System.Drawing.Size(520, 32)
    $locationBox.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $rightPanel.Controls.Add($locationBox)

    $optionsPanel = New-Object System.Windows.Forms.Panel
    $optionsPanel.Location = New-Object System.Drawing.Point(48, 238)
    $optionsPanel.Size = New-Object System.Drawing.Size(520, 148)
    $optionsPanel.BackColor = [System.Drawing.Color]::White
    $optionsPanel.BorderStyle = "FixedSingle"
    $rightPanel.Controls.Add($optionsPanel)

    $desktopCheck = New-Object System.Windows.Forms.CheckBox
    $desktopCheck.Text = "Create desktop shortcut"
    $desktopCheck.Checked = $true
    $desktopCheck.Location = New-Object System.Drawing.Point(20, 20)
    $desktopCheck.Size = New-Object System.Drawing.Size(220, 24)
    $desktopCheck.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $optionsPanel.Controls.Add($desktopCheck)

    $startMenuCheck = New-Object System.Windows.Forms.CheckBox
    $startMenuCheck.Text = "Create Start Menu shortcut"
    $startMenuCheck.Checked = $true
    $startMenuCheck.Location = New-Object System.Drawing.Point(20, 54)
    $startMenuCheck.Size = New-Object System.Drawing.Size(240, 24)
    $startMenuCheck.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $optionsPanel.Controls.Add($startMenuCheck)

    $vsCodeCheck = New-Object System.Windows.Forms.CheckBox
    $vsCodeCheck.Text = "Install VS Code extension"
    $vsCodeCheck.Checked = $codeAvailable
    $vsCodeCheck.Enabled = $codeAvailable
    $vsCodeCheck.Location = New-Object System.Drawing.Point(20, 88)
    $vsCodeCheck.Size = New-Object System.Drawing.Size(220, 24)
    $vsCodeCheck.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $optionsPanel.Controls.Add($vsCodeCheck)

    $forceCheck = New-Object System.Windows.Forms.CheckBox
    $forceCheck.Text = "Overwrite existing GT install"
    $forceCheck.Checked = $false
    $forceCheck.Location = New-Object System.Drawing.Point(20, 122)
    $forceCheck.Size = New-Object System.Drawing.Size(240, 24)
    $forceCheck.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $optionsPanel.Controls.Add($forceCheck)

    $vsCodeHint = New-Object System.Windows.Forms.Label
    $vsCodeHint.Text = if ($codeAvailable) { "VS Code CLI detected. The extension can be installed during setup." } else { "VS Code CLI was not found. You can install the VSIX manually later." }
    $vsCodeHint.ForeColor = [System.Drawing.Color]::FromArgb(71, 85, 105)
    $vsCodeHint.Font = New-Object System.Drawing.Font("Segoe UI", 9)
    $vsCodeHint.AutoSize = $false
    $vsCodeHint.Size = New-Object System.Drawing.Size(260, 48)
    $vsCodeHint.Location = New-Object System.Drawing.Point(258, 84)
    $optionsPanel.Controls.Add($vsCodeHint)

    $progressLabel = New-Object System.Windows.Forms.Label
    $progressLabel.Text = "Ready to install GT."
    $progressLabel.Font = New-Object System.Drawing.Font("Segoe UI", 10)
    $progressLabel.ForeColor = [System.Drawing.Color]::FromArgb(15, 23, 42)
    $progressLabel.AutoSize = $true
    $progressLabel.Location = New-Object System.Drawing.Point(48, 416)
    $rightPanel.Controls.Add($progressLabel)

    $progressBar = New-Object System.Windows.Forms.ProgressBar
    $progressBar.Location = New-Object System.Drawing.Point(48, 446)
    $progressBar.Size = New-Object System.Drawing.Size(520, 18)
    $progressBar.Style = "Continuous"
    $progressBar.Value = 0
    $rightPanel.Controls.Add($progressBar)

    $summaryLabel = New-Object System.Windows.Forms.Label
    $summaryLabel.Text = "The public installer keeps the runtime sealed-at-rest and prepares the live ISO only when GT launches a session."
    $summaryLabel.Font = New-Object System.Drawing.Font("Segoe UI", 9)
    $summaryLabel.ForeColor = [System.Drawing.Color]::FromArgb(71, 85, 105)
    $summaryLabel.Size = New-Object System.Drawing.Size(520, 54)
    $summaryLabel.Location = New-Object System.Drawing.Point(48, 482)
    $rightPanel.Controls.Add($summaryLabel)

    $installButton = New-Object System.Windows.Forms.Button
    $installButton.Text = "Install GT"
    $installButton.Location = New-Object System.Drawing.Point(48, 558)
    $installButton.Size = New-Object System.Drawing.Size(156, 42)
    $rightPanel.Controls.Add($installButton)
    Set-GTRoundedButtonStyle -Button $installButton `
        -BackColor ([System.Drawing.Color]::FromArgb(0, 0, 0)) `
        -ForeColor ([System.Drawing.Color]::White) `
        -BorderColor ([System.Drawing.Color]::FromArgb(0, 0, 0)) `
        -Radius 18

    $doneButton = New-Object System.Windows.Forms.Button
    $doneButton.Text = "Close"
    $doneButton.Location = New-Object System.Drawing.Point(218, 558)
    $doneButton.Size = New-Object System.Drawing.Size(120, 42)
    $rightPanel.Controls.Add($doneButton)
    Set-GTRoundedButtonStyle -Button $doneButton `
        -BackColor ([System.Drawing.Color]::FromArgb(237, 240, 244)) `
        -ForeColor ([System.Drawing.Color]::FromArgb(15, 23, 42)) `
        -BorderColor ([System.Drawing.Color]::FromArgb(237, 240, 244)) `
        -Radius 18

    $launchButton = New-Object System.Windows.Forms.Button
    $launchButton.Text = "Launch GT"
    $launchButton.Location = New-Object System.Drawing.Point(352, 558)
    $launchButton.Size = New-Object System.Drawing.Size(120, 42)
    $launchButton.Visible = $false
    $rightPanel.Controls.Add($launchButton)
    Set-GTRoundedButtonStyle -Button $launchButton `
        -BackColor ([System.Drawing.Color]::FromArgb(0, 0, 0)) `
        -ForeColor ([System.Drawing.Color]::White) `
        -BorderColor ([System.Drawing.Color]::FromArgb(0, 0, 0)) `
        -Radius 18

    $folderButton = New-Object System.Windows.Forms.Button
    $folderButton.Text = "Open Folder"
    $folderButton.Location = New-Object System.Drawing.Point(484, 558)
    $folderButton.Size = New-Object System.Drawing.Size(120, 42)
    $folderButton.Visible = $false
    $rightPanel.Controls.Add($folderButton)
    Set-GTRoundedButtonStyle -Button $folderButton `
        -BackColor ([System.Drawing.Color]::FromArgb(237, 240, 244)) `
        -ForeColor ([System.Drawing.Color]::FromArgb(15, 23, 42)) `
        -BorderColor ([System.Drawing.Color]::FromArgb(237, 240, 244)) `
        -Radius 18

    $installButton.Add_Click({
        if ($installBusy) {
            return
        }

        if ([string]::IsNullOrWhiteSpace($locationBox.Text)) {
            [System.Windows.Forms.MessageBox]::Show("Choose an install location for GT.", "GT Setup") | Out-Null
            return
        }

        $installBusy = $true
        $installButton.Text = "Installing..."
        $doneButton.Enabled = $false
        $progressBar.Value = 0
        $progressLabel.Text = "Starting installation..."
        $summaryLabel.Text = "Preparing the public bundle..."

        try {
            $installResult = Install-GTPublicBundle `
                -InstallRoot $locationBox.Text.Trim() `
                -InstallVsCodeExtension:$vsCodeCheck.Checked `
                -CreateDesktopShortcut:$desktopCheck.Checked `
                -CreateStartMenuShortcut:$startMenuCheck.Checked `
                -Force:$forceCheck.Checked `
                -ProgressCallback {
                    param($Percent, $Status)
                    $progressBar.Value = [Math]::Min(100, [Math]::Max(0, $Percent))
                    $progressLabel.Text = $Status
                    [System.Windows.Forms.Application]::DoEvents()
                }

            $progressBar.Value = 100
            $progressLabel.Text = "GT installed successfully."
            $summaryLabel.Text = "GT is ready. The runtime is stored as a DPAPI-protected payload and will be staged only when you launch a live session."
            if ($installResult.VsCodeInstallStatus -eq "manual-required") {
                $summaryLabel.Text = $installResult.VsCodeInstallMessage
            }
            if ($installResult.Warnings.Count -gt 0) {
                $summaryLabel.Text = (($summaryLabel.Text.TrimEnd('.')) + ". " + ($installResult.Warnings -join " "))
            }
            $launchButton.Visible = $true
            $folderButton.Visible = $true
            $doneButton.Text = "Done"
            $installButton.Text = "Installed"
        } catch {
            $progressLabel.Text = "Installation failed."
            $summaryLabel.Text = $_.Exception.Message
            [System.Windows.Forms.MessageBox]::Show($_.Exception.Message, "GT Setup Failed", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
            $installButton.Text = "Install GT"
        } finally {
            if (-not $launchButton.Visible) {
                $installBusy = $false
            }
            $doneButton.Enabled = $true
        }
    })

    $doneButton.Add_Click({
        $form.Close()
    })

    $launchButton.Add_Click({
        if ($null -ne $installResult) {
            Start-GTOperatorSurface -TargetPath $installResult.ShortcutTarget -Arguments $installResult.ShortcutArguments -WorkingDirectory $installResult.Root
        }
    })

    $folderButton.Add_Click({
        if ($null -ne $installResult) {
            Start-Process -FilePath $installResult.Root
        }
    })

    [void]$form.ShowDialog()
}

function Use-GuiInstaller {
    if ($Silent) {
        return $false
    }

    if ($PSBoundParameters.Count -gt 0) {
        return $false
    }

    return $Host.Name -eq 'ConsoleHost'
}

if (Use-GuiInstaller) {
    Show-GTInstallerUi
    exit 0
}

$desktopShortcutRequested = $CreateDesktopShortcut.IsPresent
$startMenuShortcutRequested = $CreateStartMenuShortcut.IsPresent
$installResult = Install-GTPublicBundle `
    -InstallRoot $InstallRoot `
    -InstallVsCodeExtension:$InstallVsCodeExtension.IsPresent `
    -CreateDesktopShortcut:$desktopShortcutRequested `
    -CreateStartMenuShortcut:$startMenuShortcutRequested `
    -Force:$Force.IsPresent

Write-Host "GT installed successfully:"
Write-Host "  Root:     $($installResult.Root)"
Write-Host "  Launcher: $($installResult.Launcher)"
Write-Host "  Host:     $($installResult.HostLauncher)"
Write-Host "  Shell:    $($installResult.ShellLauncher)"
Write-Host "  Runtime:  $($installResult.Runtime)"
if ($installResult.VsCodeInstallStatus -eq "manual-required") {
    Write-Host "  Note:     $($installResult.VsCodeInstallMessage)"
}
foreach ($warning in $installResult.Warnings) {
    Write-Host "  Warning:  $warning"
}
Write-Host "Run '$($installResult.Launcher) doctor' to verify the installation."
