#////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Borealis.ps1

[CmdletBinding()]
param(
    [switch]$Server,
    [switch]$Agent,
    [ValidateSet('install','repair','remove','launch','')]
    [string]$AgentAction = '',
    [switch]$Vite,
    [switch]$Flask,
    [switch]$Quick
)

# Preselect menu choices from CLI args (optional)
$choice = $null
$modeChoice = $null
$agentSubChoice = $null

if ($Server -and $Agent) {
    Write-Host "Cannot use -Server and -Agent together." -ForegroundColor Red
    exit 1
}

if ($Vite -and $Flask) {
    Write-Host "Cannot combine -Vite and -Flask." -ForegroundColor Red
    exit 1
}

if ($Server) {
    # Auto-select main menu option for Server when -Server flag is provided
    $choice = '1'
} elseif ($Agent) {
    $choice = '2'
    switch ($AgentAction) {
        'install' { $agentSubChoice = '1' }
        'repair'  { $agentSubChoice = '2' }
        'remove'  { $agentSubChoice = '3' }
        'launch'  { $agentSubChoice = '4' }
        default   { $agentSubChoice = '1' }
    }
}

if ($Server) {
    if     ($Vite)             { $modeChoice = '3' }
    elseif ($Flask -and $Quick){ $modeChoice = '2' }
    elseif ($Flask)            { $modeChoice = '1' }
}
$host.UI.RawUI.WindowTitle = "Borealis"
Clear-Host

## Note: Heavy dependency downloads are deferred until selecting Server (option 1)
# ---------------------- ASCII Art Terminal Required Changes ----------------------
# Set the .NET Console output encoding to UTF8
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8

# Change the Windows OEM code page to 65001 (UTF-8)
chcp.com 65001 > $null

# ---------------------- Add Common Functions Used Throughout Script ----------------------
$symbols = @{
    Success = [char]0x2705
    Running = [char]0x23F3
    Fail    = [char]0x274C
    Info    = [char]0x2139
}

# Admin/Elevation helpers for Agent deployment
function Test-IsAdmin {
    try {
        $id = [Security.Principal.WindowsIdentity]::GetCurrent()
        $p  = New-Object Security.Principal.WindowsPrincipal($id)
        return $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
    } catch { return $false }
}

function Request-AgentElevation {
    param(
        [string]$ScriptPath,
        [string]$AgentActionParam,
        [switch]$Auto
    )
    if (Test-IsAdmin) { return $true }

    if (-not $Auto) {
        Write-Host ""  # spacer
        Write-Host "Agent requires Administrator permissions to register scheduled tasks and run reliably." -ForegroundColor Yellow -BackgroundColor Black
        Write-Host "Grant elevated permissions now? (Y/N)" -ForegroundColor Yellow -BackgroundColor Black
        $resp = Read-Host
        if ($resp -notin @('y','Y','yes','YES')) { return $false }
    }

    $args = @('-NoProfile','-ExecutionPolicy','Bypass','-File', '"' + $ScriptPath + '"', '-Agent')
    if ($AgentActionParam -and $AgentActionParam.Trim()) {
        $args += @('-AgentAction', $AgentActionParam)
    }
    try {
        Start-Process -FilePath 'powershell.exe' -Verb RunAs -ArgumentList $args -WindowStyle Normal | Out-Null
        return $false  # stop current non-elevated instance
    } catch {
        Write-Host "Elevation was denied or failed." -ForegroundColor Red
        return $false
    }
}

# Ensure log directories
function Ensure-AgentLogDir {
    $logRoot = Join-Path $scriptDir 'Logs'
    $agentLogDir = Join-Path $logRoot 'Agent'
    if (-not (Test-Path $agentLogDir)) { New-Item -ItemType Directory -Path $agentLogDir -Force | Out-Null }
    return $agentLogDir
}

function Write-AgentLog {
    param(
        [string]$FileName,
        [string]$Message
    )
    $dir = Ensure-AgentLogDir
    $path = Join-Path $dir $FileName
    $ts = Get-Date -Format s
    "[$ts] $Message" | Out-File -FilePath $path -Append -Encoding UTF8
}

$script:Utf8CodePageChanged = $false

function Ensure-SystemUtf8CodePage {
    param([string]$LogName = 'Install.log')

    $codePageKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Nls\CodePage'
    $target = '65001'
    try {
        $props = Get-ItemProperty -Path $codePageKey -ErrorAction Stop
        $currentAcp = ($props.ACP | ForEach-Object { $_.ToString() })
        $currentOem = ($props.OEMCP | ForEach-Object { $_.ToString() })
        Write-AgentLog -FileName $LogName -Message ("[UTF8] Detected ACP={0} OEMCP={1}" -f $currentAcp,$currentOem)
    } catch {
        Write-AgentLog -FileName $LogName -Message ("[UTF8] Failed to read code page info: {0}" -f $_.Exception.Message)
        return
    }

    if ($currentAcp -eq $target -and $currentOem -eq $target) {
        Write-AgentLog -FileName $LogName -Message '[UTF8] System code pages already set to 65001 (UTF-8).'
        return
    }

    Write-AgentLog -FileName $LogName -Message '[UTF8] Updating system code pages to UTF-8 (65001). Requires reboot to finalize.'
    try {
        Set-ItemProperty -Path $codePageKey -Name 'ACP' -Value $target -Force
        Set-ItemProperty -Path $codePageKey -Name 'OEMCP' -Value $target -Force
        try { Set-ItemProperty -Path $codePageKey -Name 'MACCP' -Value $target -Force } catch {}
        $script:Utf8CodePageChanged = $true
        Write-AgentLog -FileName $LogName -Message '[UTF8] Code page registry values updated successfully.'
    } catch {
        Write-AgentLog -FileName $LogName -Message ("[UTF8] Failed to update code pages: {0}" -f $_.Exception.Message)
    }
}

# Forcefully remove legacy and current Borealis services and tasks
function Remove-BorealisServicesAndTasks {
    param([string]$LogName)
    $svcNames = @('BorealisAgent','BorealisScriptService','BorealisScriptAgent')
    foreach ($n in $svcNames) {
        Write-AgentLog -FileName $LogName -Message "Attempting to stop service: $n"
        try { sc.exe stop $n 2>$null | Out-Null } catch {}
        Start-Sleep -Milliseconds 300
        Write-AgentLog -FileName $LogName -Message "Attempting to delete service: $n"
        try { sc.exe delete $n 2>$null | Out-Null } catch {}
    }
    # Remove all Borealis scheduled tasks (supervisor/watchdog/legacy/user helper)
    try {
        $tasks = @()
        try { $tasks = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'Borealis Agent*' -or $_.TaskName -like 'Borealis*Supervisor*' -or $_.TaskName -like 'Borealis*Watchdog*' } } catch {}
        foreach ($t in $tasks) {
            Write-AgentLog -FileName $LogName -Message ("Deleting scheduled task: {0}" -f $t.TaskName)
            try { Unregister-ScheduledTask -TaskName $t.TaskName -Confirm:$false -ErrorAction SilentlyContinue } catch {}
        }
        # Fallback to schtasks for machines without the ScheduledTasks module
        foreach ($tn in @('Borealis Agent','Borealis Agent (UserHelper)','Borealis Agent - Supervisor','Borealis Agent - Watchdog')) {
            try { schtasks.exe /Delete /TN "$tn" /F 2>$null | Out-Null } catch {}
        }
    } catch {}

    # Gracefully stop only Agent venv Python processes (avoid killing dev web UI/node)
    Write-Host "Stopping Agent Python processes scoped to Agent venv..." -ForegroundColor Yellow
    Write-AgentLog -FileName $LogName -Message "Stopping Agent Python processes in Agent\\*"
    try {
        Get-Process python,pythonw -ErrorAction SilentlyContinue |
            Where-Object { $_.Path -like (Join-Path $scriptDir 'Agent\*') } |
            ForEach-Object { try { $_ | Stop-Process -Force } catch {} }
    } catch {}
    # Remove legacy watchdog script if present
    try { Remove-Item -Force -ErrorAction SilentlyContinue (Join-Path $env:ProgramData 'Borealis\watchdog.ps1') } catch {}
}

# Repair routine: cleans services, ensures venv files, reinstalls and starts BorealisAgent
function Repair-BorealisAgent {
    $logName = 'Repair.log'
    Write-AgentLog -FileName $logName -Message "=== Repair start ==="
    Remove-BorealisServicesAndTasks -LogName $logName
    InstallOrUpdate-BorealisAgent
    Write-AgentLog -FileName $logName -Message "=== Repair end ==="
}

function Remove-BorealisAgent {
    $logName = 'Removal.log'
    Write-AgentLog -FileName $logName -Message "=== Removal start ==="
    Remove-BorealisServicesAndTasks -LogName $logName
    # Kill stray helpers
    Write-AgentLog -FileName $logName -Message "Terminating stray helper processes"
    Get-Process python,pythonw -ErrorAction SilentlyContinue | Where-Object { $_.Path -like (Join-Path $scriptDir 'Agent\*') } | ForEach-Object {
        try { $_ | Stop-Process -Force } catch {}
    }
    # Remove venv folder
    $venvFolder = Join-Path $scriptDir 'Agent'
    Write-AgentLog -FileName $logName -Message "Removing folder: $venvFolder"
    try { Remove-Item $venvFolder -Recurse -Force -ErrorAction SilentlyContinue } catch {}
    Write-AgentLog -FileName $logName -Message "=== Removal end ==="
}

function Write-ProgressStep {
    param (
        [string]$Message,
        [string]$Status = $symbols["Info"]
    )
    Write-Host "`r$Status $Message... " -NoNewline
}

function Run-Step {
    param (
        [string]     $Message,
        [scriptblock]$Script
    )
    Write-ProgressStep -Message $Message -Status "$($symbols.Running)"
    try {
        & $Script
        if ($LASTEXITCODE -eq 0 -or $?) {
            Write-Host "`r$($symbols.Success) $Message                        "
        } else {
            throw "Non-zero exit code"
        }
    } catch {
        Write-Host "`r$($symbols.Fail) $Message - Failed: $_                        " -ForegroundColor Red
        exit 1
    }
}

# ---------------------- Server Deployment / Operation Mode Variables ----------------------
# Define the default operation mode: production  | developer
[string]$borealis_operation_mode = 'production'

# ---------------------- Bundle Executables Setup ----------------------
$scriptDir  = Split-Path $MyInvocation.MyCommand.Path -Parent
$depsRoot   = Join-Path $scriptDir 'Dependencies'
$pythonExe  = Join-Path $depsRoot 'Python\python.exe'
$nodeExe    = Join-Path $depsRoot 'NodeJS\node.exe'
$sevenZipExe    = Join-Path $depsRoot "7zip\7z.exe"
$npmCmd     = Join-Path (Split-Path $nodeExe) 'npm.cmd'
$npxCmd     = Join-Path (Split-Path $nodeExe) 'npx.cmd'
$node7zUrl      = "https://nodejs.org/dist/v23.11.0/node-v23.11.0-win-x64.7z"
$nodeInstallDir = Join-Path $depsRoot "NodeJS"
$node7zPath     = Join-Path $depsRoot "node-v23.11.0-win-x64.7z"

# ---------------------- Dependency Installation Functions ----------------------
function Install_Shared_Dependencies {
    # Python (shared by Server and Agent)
    Run-Step "Dependency: Python" {
        $pythonInstallDir = Join-Path $scriptDir "Dependencies\Python"
        $localPythonExe   = Join-Path $pythonInstallDir "python.exe"

        $pythonMsiBaseUrl = "https://www.python.org/ftp/python/3.13.3/amd64/"
        $pythonMsiFiles = @(
            "core.msi",
            "exe.msi",
            "lib.msi",
            "pip.msi",
            "dev.msi"
        )

        if (-not (Test-Path $localPythonExe)) {
            if (-not (Test-Path $pythonInstallDir)) {
                New-Item -ItemType Directory -Path $pythonInstallDir | Out-Null
            }

            foreach ($file in $pythonMsiFiles) {
                $url = "$pythonMsiBaseUrl$file"
                $localPath = Join-Path $scriptDir "Dependencies\$file"

                # Download if missing
                if (-not (Test-Path $localPath)) {
                    Invoke-WebRequest -Uri $url -OutFile $localPath
                }

                # Extract MSI into install directory
                Start-Process -Wait -NoNewWindow -FilePath "msiexec.exe" `
                    -ArgumentList "/a `"$localPath`" /qn TARGETDIR=`"$pythonInstallDir`""
            }

            # Clean up downloaded MSIs
            foreach ($file in $pythonMsiFiles) {
                $localPath = Join-Path $scriptDir "Dependencies\$file"
                Remove-Item $localPath -Force -ErrorAction SilentlyContinue
            }

            # Validate success
            if (-not (Test-Path $localPythonExe)) {
                throw "Python executable not found after MSI extraction."
            }
        }
    }
}

function Install_Server_Dependencies {
    # Tesseract OCR Engine
    Run-Step "Dependency: Tesseract-OCR" {
        $tessExeUrl     = "https://github.com/tesseract-ocr/tesseract/releases/download/5.5.0/tesseract-ocr-w64-setup-5.5.0.20241111.exe"
        $tessExePath    = Join-Path $depsRoot "tesseract-installer.exe"
        $tessInstallDir = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR"

        if (-not (Test-Path (Join-Path $tessInstallDir "tesseract.exe"))) {
            # Download the installer if it doesn't exist
            if (-not (Test-Path $tessExePath)) {
                Invoke-WebRequest -Uri $tessExeUrl -OutFile $tessExePath
            }

            # Extract using 7-Zip
            if (-not (Test-Path $sevenZipExe)) {
                throw "7-Zip CLI not found at: $sevenZipExe"
            }

            if (Test-Path $tessInstallDir) {
                Remove-Item $tessInstallDir -Recurse -Force -ErrorAction SilentlyContinue
            }
            New-Item -ItemType Directory -Path $tessInstallDir | Out-Null

            & $sevenZipExe x $tessExePath "-o$tessInstallDir" -y | Out-Null

            # Optional cleanup
            Remove-Item $tessExePath -Force -ErrorAction SilentlyContinue
        }
    }

    # Tesseract Language Data
    Run-Step "Dependency: Tesseract-OCR - Pre-Trained Model Data" {
        $langDataDir = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR\tessdata"
        $engPath     = Join-Path $langDataDir "eng.traineddata"
        $osdPath     = Join-Path $langDataDir "osd.traineddata"

        if (-not (Test-Path $engPath)) {
            Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata/raw/main/eng.traineddata" -OutFile $engPath
        }

        if (-not (Test-Path $osdPath)) {
            Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata/raw/main/osd.traineddata" -OutFile $osdPath
        }
    }

    # NodeJS (required for Vite / Web UI)
    Run-Step "Dependency: NodeJS" {
        if (-not (Test-Path $nodeExe)) {
            # Download archive if not present
            if (-not (Test-Path $node7zPath)) {
                Invoke-WebRequest -Uri $node7zUrl -OutFile $node7zPath
            }

            # Extract using bundled 7z
            if (-not (Test-Path $sevenZipExe)) {
                throw "7-Zip CLI not found at: $sevenZipExe"
            }

            & $sevenZipExe x $node7zPath "-o$nodeInstallDir" -y | Out-Null

            # The extracted contents might live under a subfolder; flatten if needed
            $extracted = Get-ChildItem $nodeInstallDir | Where-Object { $_.PSIsContainer } | Select-Object -First 1
            if ($extracted) {
                Get-ChildItem $extracted.FullName | Move-Item -Destination $nodeInstallDir -Force
                Remove-Item $extracted.FullName -Recurse -Force
            }

            # Clean Up 7z File After Extraction
            Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $node7zPath
        }
    }
}

function Install_Agent_Dependencies {
    # AutoHotKey portable
    Run-Step "Dependency: AutoHotKey" {
        $ahkVersion    = "2.0.19"
        $ahkVersionTag = "v$ahkVersion"
        $ahkZipName    = "AutoHotkey_$ahkVersion.zip"
        $ahkZipUrl     = "https://github.com/AutoHotkey/AutoHotkey/releases/download/$ahkVersionTag/$ahkZipName"
        $ahkZipPath    = Join-Path $depsRoot $ahkZipName
        $ahkInstallDir = Join-Path $depsRoot "AutoHotKey"
        $ahkExePath    = Join-Path $ahkInstallDir "AutoHotkey64.exe"

        if (-not (Test-Path $ahkExePath)) {
            if (-not (Test-Path $ahkZipPath)) {
                Invoke-WebRequest -Uri $ahkZipUrl -OutFile $ahkZipPath
            }

            if (-not (Test-Path $sevenZipExe)) {
                throw "7-Zip CLI not found at: $sevenZipExe"
            }

            if (Test-Path $ahkInstallDir) {
                Remove-Item $ahkInstallDir -Recurse -Force -ErrorAction SilentlyContinue
            }
            New-Item -ItemType Directory -Path $ahkInstallDir | Out-Null
            & $sevenZipExe x $ahkZipPath "-o$ahkInstallDir" -y | Out-Null

            Remove-Item $ahkZipPath -Force -ErrorAction SilentlyContinue

            if (-not (Test-Path $ahkExePath)) {
                throw "AutoHotKey executable not found after extraction."
            }
        }
    }
}

function Ensure-AgentTasks {
    param([string]$ScriptRoot)
    $pyw         = Join-Path $ScriptRoot 'Agent\Scripts\pythonw.exe'
    $agentPy     = Join-Path $ScriptRoot 'Agent\Borealis\agent.py'
    $svcWrapper  = Join-Path $ScriptRoot 'Agent\Borealis\launch_service.ps1'
    if (-not (Test-Path $pyw))      { Write-Host "pythonw.exe not found under Agent\Scripts" -ForegroundColor Yellow; return }
    if (-not (Test-Path $agentPy))  { Write-Host "Agent script not found under Agent\Borealis" -ForegroundColor Yellow; return }
    if (-not (Test-Path $svcWrapper)) { Write-Host "launch_service.ps1 not found under Agent\Borealis" -ForegroundColor Yellow; return }

    # Clean old tasks first
    try { Unregister-ScheduledTask -TaskName 'Borealis Agent' -Confirm:$false -ErrorAction SilentlyContinue } catch {}
    try { Unregister-ScheduledTask -TaskName 'Borealis Agent (UserHelper)' -Confirm:$false -ErrorAction SilentlyContinue } catch {}

    # SYSTEM startup task
    # Use a wrapper PowerShell to enforce WorkingDirectory and capture stdout/stderr
    $sysArg     = ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}"' -f $svcWrapper)
    $sysAction  = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $sysArg -WorkingDirectory (Split-Path $svcWrapper -Parent)
    $sysTrigger = New-ScheduledTaskTrigger -AtStartup
    $sysSet     = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Hidden -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
    $sysPrin    = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
    Register-ScheduledTask -TaskName 'Borealis Agent' -Action $sysAction -Trigger $sysTrigger -Settings $sysSet -Principal $sysPrin -Force | Out-Null
    try { Start-ScheduledTask -TaskName 'Borealis Agent' | Out-Null } catch {}

    # Optional user-session helper for interactive roles (tray, overlays)
    $helperName = 'Borealis Agent (UserHelper)'
    $usrArg     = ('"{0}" --config user' -f $agentPy)
    $usrAction  = New-ScheduledTaskAction -Execute $pyw -Argument $usrArg -WorkingDirectory (Split-Path $agentPy -Parent)
    $usrTrig    = New-ScheduledTaskTrigger -AtLogOn
    $usrSet     = New-ScheduledTaskSettingsSet -Hidden -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero)
    $currentUser= [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
    $usrPrin    = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Limited
    Register-ScheduledTask -TaskName $helperName -Action $usrAction -Trigger $usrTrig -Settings $usrSet -Principal $usrPrin -Force | Out-Null
    try { Start-ScheduledTask -TaskName $helperName | Out-Null } catch {}
}
function InstallOrUpdate-BorealisAgent {
    Write-Host "Ensuring Agent Dependencies Exist..." -ForegroundColor DarkCyan
    Install_Shared_Dependencies
    Install_Agent_Dependencies
    if (-not (Test-Path $pythonExe)) {
        Write-Host "`r$($symbols.Fail) Bundled Python not found at '$pythonExe'." -ForegroundColor Red
        exit 1
    }
    $env:PATH = '{0};{1}' -f (Split-Path $pythonExe), $env:PATH
    Write-Host "Cleaning previous agent tasks/processes..." -ForegroundColor Yellow
    Remove-BorealisServicesAndTasks -LogName 'Install.log'
    Ensure-SystemUtf8CodePage -LogName 'Install.log'
    Write-Host "Deploying Borealis Agent..." -ForegroundColor Blue

    # Resolve all paths relative to the script directory to avoid CWD issues
    $venvFolderPath         = Join-Path $scriptDir 'Agent'
    $agentSourceRoot        = Join-Path $scriptDir 'Data\Agent'
    $agentSourcePath        = Join-Path $agentSourceRoot 'agent.py'
    $agentRequirements      = Join-Path $agentSourceRoot 'agent-requirements.txt'
    $agentDestinationFolder = Join-Path $venvFolderPath 'Borealis'
    $agentDestinationFile   = Join-Path $agentDestinationFolder 'agent.py'
    $venvPython             = Join-Path $venvFolderPath 'Scripts\python.exe'

    Run-Step "Create Virtual Python Environment" {
        if (-not (Test-Path (Join-Path $venvFolderPath 'Scripts\Activate'))) {
            $pythonForVenv = $pythonExe
            if (-not (Test-Path $pythonForVenv)) {
                $pyCmd = Get-Command py -ErrorAction SilentlyContinue
                $pythonCmd = Get-Command python -ErrorAction SilentlyContinue
                if ($pyCmd) { $pythonForVenv = $pyCmd.Source }
                elseif ($pythonCmd) { $pythonForVenv = $pythonCmd.Source }
                else {
                    Write-Host "Python not found. Install Python or run Server setup (option 1)." -ForegroundColor Red
                    exit 1
                }
            }
            & $pythonForVenv -m venv $venvFolderPath
        }
        if (Test-Path $agentSourcePath) {
            # Cleanup Previous Agent Folder & Create New Folder
            Remove-Item $agentDestinationFolder -Recurse -Force -ErrorAction SilentlyContinue
            New-Item -Path $agentDestinationFolder -ItemType Directory -Force | Out-Null

            # Copy Agent Files to Virtual Python Environment
            $coreAgentFiles = @(
                (Join-Path $agentSourceRoot 'agent.py'),
                (Join-Path $agentSourceRoot 'Python_API_Endpoints'),
                (Join-Path $agentSourceRoot 'agent_deployment.py'),
                (Join-Path $agentSourceRoot 'Borealis.ico'),
                (Join-Path $agentSourceRoot 'launch_service.ps1'),
                (Join-Path $agentSourceRoot 'role_manager.py'),
                (Join-Path $agentSourceRoot 'Roles')
            )

            Copy-Item $coreAgentFiles -Destination $agentDestinationFolder -Recurse -Force
        }
        . (Join-Path $venvFolderPath 'Scripts\Activate')
    }

    Run-Step "Install Python Dependencies" {
        if (Test-Path $agentRequirements) {
            & $venvPython -m pip install --disable-pip-version-check -q -r $agentRequirements | Out-Null
        }

        $stubSource = Join-Path $agentSourceRoot 'fcntl_stub.py'
        if (Test-Path $stubSource) {
            $stubDest = Join-Path $venvFolderPath 'Lib\site-packages\fcntl.py'
            Write-AgentLog -FileName 'Install.log' -Message '[UTF8] Ensuring Windows fcntl shim is installed.'
            Copy-Item $stubSource $stubDest -Force
        }

        $termiosSource = Join-Path $agentSourceRoot 'termios_stub.py'
        if (Test-Path $termiosSource) {
            $termiosDest = Join-Path $venvFolderPath 'Lib\site-packages\termios.py'
            Write-AgentLog -FileName 'Install.log' -Message '[UTF8] Ensuring Windows termios shim is installed.'
            Copy-Item $termiosSource $termiosDest -Force
        }

        $siteCustomSource = Join-Path $agentSourceRoot 'sitecustomize.py'
        if (Test-Path $siteCustomSource) {
            $siteCustomDest = Join-Path $venvFolderPath 'Lib\site-packages\sitecustomize.py'
            Write-AgentLog -FileName 'Install.log' -Message '[UTF8] Ensuring sitecustomize shim is installed.'
            Copy-Item $siteCustomSource $siteCustomDest -Force
        }
    }

    Run-Step "Configure Agent Settings" {
        $settingsDir = Join-Path $scriptDir 'Agent\Borealis\Settings'
        $oldSettingsDir = Join-Path $scriptDir 'Agent\Settings'
        if (-not (Test-Path $settingsDir)) { New-Item -Path $settingsDir -ItemType Directory -Force | Out-Null }
        $serverUrlPath = Join-Path $settingsDir 'server_url.txt'
        # Migrate any prior interim location file if present
        $oldServerUrlPath = Join-Path $oldSettingsDir 'server_url.txt'
        if (-not (Test-Path $serverUrlPath) -and (Test-Path $oldServerUrlPath)) {
            try { Move-Item -Path $oldServerUrlPath -Destination $serverUrlPath -Force } catch { try { Copy-Item $oldServerUrlPath $serverUrlPath -Force } catch {} }
        }
        $defaultUrl = 'http://localhost:5000'
        $currentUrl = $defaultUrl
        if (Test-Path $serverUrlPath) {
            try { $txt = (Get-Content -Path $serverUrlPath -ErrorAction SilentlyContinue | Select-Object -First 1) } catch { $txt = '' }
            if ($txt -and $txt.Trim()) { $currentUrl = $txt.Trim() }
        }
        Write-Host ""; Write-Host "Set Borealis Server URL" -ForegroundColor DarkYellow
        $prompt = "Server URL [$currentUrl]"
        $inputUrl = Read-Host $prompt
        if (-not $inputUrl) { $inputUrl = $currentUrl }
        $inputUrl = $inputUrl.Trim()
        if (-not $inputUrl) { $inputUrl = $defaultUrl }
        
        # Write UTF-8 without BOM to avoid BOM being read into the URL
        $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
        [System.IO.File]::WriteAllText($serverUrlPath, $inputUrl, $utf8NoBom)
    }

    Write-Host "`nConfiguring Borealis Agent (tasks)..." -ForegroundColor Blue
    Write-Host "===================================================================================="
    Ensure-AgentTasks -ScriptRoot $scriptDir
    if ($script:Utf8CodePageChanged) {
        $msg = 'System code pages set to UTF-8. A reboot is required before Ansible can run.'
        Write-AgentLog -FileName 'Install.log' -Message ("[UTF8] {0}" -f $msg)
        Write-Host "`n$msg" -ForegroundColor Yellow
    }
}

# ---------------------- Main ----------------------
Clear-Host

# ASCII Art Banner
@'

███████████                                        ████   ███         
░░███░░░░░███                                      ░░███  ░░░          
 ░███    ░███  ██████  ████████   ██████   ██████   ░███  ████   █████ 
 ░██████████  ███░░███░░███░░███ ███░░███ ░░░░░███  ░███ ░░███  ███░░  
 ░███░░░░░███░███ ░███ ░███ ░░░ ░███████   ███████  ░███  ░███ ░░█████ 
 ░███    ░███░███ ░███ ░███     ░███░░░   ███░░███  ░███  ░███  ░░░░███
 ███████████ ░░██████  █████    ░░██████ ░░████████ █████ █████ ██████ 
░░░░░░░░░░░   ░░░░░░  ░░░░░      ░░░░░░   ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░  
'@ | Write-Host -ForegroundColor DarkCyan
Write-Host "Automation Platform" -ForegroundColor DarkGray

if (-not $choice) {
    Write-Host " "
    Write-Host "Please choose which function you want to launch:"
    Write-Host " 1) Borealis Server" -ForegroundColor DarkGray
    Write-Host " 2) Borealis Agent" -ForegroundColor DarkGray
    Write-Host " 3) Build Electron App " -NoNewline -ForegroundColor DarkGray
    Write-Host "[Experimental]" -ForegroundColor Red
    Write-Host " 4) Package Self-Contained EXE of Server or Agent " -NoNewline -ForegroundColor DarkGray
    Write-Host "[Experimental]" -ForegroundColor Red
    Write-Host " 5) Update Borealis " -NoNewLine -ForegroundColor DarkGray
    Write-Host "[Requires Re-Build]" -ForegroundColor Red
    # (Removed) AutoHotKey experimental testing
    Write-Host "Type a number and press " -NoNewLine
    Write-Host "<ENTER>" -ForegroundColor DarkCyan
    $choice = Read-Host
}

switch ($choice) {
    "1" {
        $host.UI.RawUI.WindowTitle = "Borealis Server"
        Write-Host "Ensuring Server Dependencies Exist..." -ForegroundColor DarkCyan

        Install_Shared_Dependencies
        Install_Server_Dependencies

        foreach ($tool in @($pythonExe, $nodeExe, $npmCmd, $npxCmd)) {
            if (-not (Test-Path $tool)) { Write-Host "`r$($symbols.Fail) Bundled executable not found at '$tool'." -ForegroundColor Red; exit 1 }
        }
        $env:PATH = '{0};{1};{2}' -f (Split-Path $pythonExe), (Split-Path $nodeExe), $env:PATH

        if (-not $modeChoice) {
            Write-Host " "
            Write-Host "Configure Borealis Server Mode:" -ForegroundColor DarkYellow
            Write-Host " 1) Build & Launch > Production Flask Server @ http://localhost:5000" -ForegroundColor DarkCyan
            Write-Host " 2) [Skip Build] & Immediately Launch > Production Flask Server @ http://localhost:5000" -ForegroundColor DarkCyan
            Write-Host " 3) Launch > [Hotload-Ready] Vite Dev Server @ http://localhost:5173" -ForegroundColor DarkCyan
            $modeChoice = Read-Host "Enter choice [1/2/3]"
        }

        switch ($modeChoice) {
            "1" { $borealis_operation_mode = "production" }
            "2" {
                Run-Step "Borealis: Launch Flask Server" {
                    Push-Location (Join-Path $scriptDir "Server")
                    & (Join-Path $scriptDir "Server\Scripts\python.exe") (Join-Path $scriptDir "Server\Borealis\server.py")
                    Pop-Location
                }
                break
            }
            "3" { $borealis_operation_mode = "developer" }
            default { Write-Host "Invalid mode choice: $modeChoice" -ForegroundColor Red; break }
        }

        Write-Host "Deploying Borealis Server in '$borealis_operation_mode' mode" -ForegroundColor Blue

        $venvFolder       = "Server"
        $dataSource       = "Data"
        $dataDestination  = "$venvFolder\Borealis"
        $customUIPath     = "$dataSource\Server\WebUI"
        $webUIDestination = "$venvFolder\web-interface"
        $venvPython       = Join-Path $venvFolder 'Scripts\python.exe'

        Run-Step "Create Borealis Virtual Python Environment" {
            if (-not (Test-Path "$venvFolder\Scripts\Activate")) { & $pythonExe -m venv $venvFolder | Out-Null }
            if (Test-Path $dataSource) {
                Remove-Item $dataDestination -Recurse -Force -ErrorAction SilentlyContinue
                New-Item -Path $dataDestination -ItemType Directory -Force | Out-Null
                Copy-Item "$dataSource\Server\Python_API_Endpoints" $dataDestination -Recurse
                Copy-Item "$dataSource\Server\Sounds"               $dataDestination -Recurse
                Copy-Item "$dataSource\Server\server.py"            $dataDestination
                Copy-Item "$dataSource\Server\job_scheduler.py"     $dataDestination 
            }
            . "$venvFolder\Scripts\Activate"
        }

        Run-Step "Install Python Dependencies into Virtual Python Environment" {
            if (Test-Path "$dataSource\Server\server-requirements.txt") { & $venvPython -m pip install --disable-pip-version-check -q -r "$dataSource\Server\server-requirements.txt" | Out-Null }
        }

        Run-Step "Copy Borealis WebUI Files into: $webUIDestination" {
            if (Test-Path $webUIDestination) {
                Remove-Item "$webUIDestination\public\*" -Recurse -Force -ErrorAction SilentlyContinue
                Remove-Item "$webUIDestination\src\*"    -Recurse -Force -ErrorAction SilentlyContinue
            } else {
                New-Item -Path $webUIDestination -ItemType Directory -Force | Out-Null
            }
            Copy-Item "$customUIPath\*" $webUIDestination -Recurse -Force
        }

        Run-Step "Vite Web Frontend: Install NPM Packages" {
            Push-Location $webUIDestination
            $env:npm_config_loglevel = "silent"
            & $npmCmd install --silent --no-fund --audit=false | Out-Null
            Pop-Location
        }

        Run-Step "Vite Web Frontend: Start ($borealis_operation_mode)" {
            Push-Location $webUIDestination
            if ($borealis_operation_mode -eq "developer") { $viteSubCommand = "dev" } else { $viteSubCommand = "build" }
            Start-Process -NoNewWindow -FilePath $npmCmd -ArgumentList @("run",$viteSubCommand)
            Pop-Location
        }

        Run-Step "Borealis: Launch Flask Server" {
            Push-Location (Join-Path $scriptDir "Server")
            $py        = Join-Path $scriptDir "Server\Scripts\python.exe"
            $server_py = Join-Path $scriptDir "Server\Borealis\server.py"
            Write-Host "`nLaunching Borealis..." -ForegroundColor Green
            Write-Host "===================================================================================="
            Write-Host "$($symbols.Running) Python Flask API Server Started..."
            & $py $server_py
            Pop-Location
        }
        break
    }

    "2" {
        $host.UI.RawUI.WindowTitle = "Borealis Agent"
        Write-Host " "
        # Ensure elevation before showing Agent menu
        $scriptPath = $PSCommandPath
        if (-not $scriptPath -or $scriptPath -eq '') { $scriptPath = $MyInvocation.MyCommand.Definition }
        # If already elevated, skip prompt; otherwise prompt, then relaunch directly to the Agent menu via -Agent
        $cont = Request-AgentElevation -ScriptPath $scriptPath -AgentActionParam $AgentAction
        if (-not $cont -and -not (Test-IsAdmin)) { return }
        if (Test-IsAdmin) {
            Write-Host "Escalated Permissions Granted > Agent is Eligible for Deployment." -ForegroundColor Green
        }
        Write-Host "Agent Menu:" -ForegroundColor Cyan
        Write-Host " 1) Install/Update Agent"
        Write-Host " 2) Repair Borealis Agent"
        Write-Host " 3) Remove Agent"
        Write-Host " 4) Launch UserSession Helper (current session)"
        Write-Host " 5) Back"
        if (-not $agentSubChoice) { $agentSubChoice = Read-Host "Select an option" }

        switch ($agentSubChoice) {
            '1' { InstallOrUpdate-BorealisAgent; break }
            '2' { Repair-BorealisAgent; break }
            '3' { Remove-BorealisAgent; break }
            '4' {
                $venvPythonw = Join-Path $scriptDir 'Agent\Scripts\pythonw.exe'
                $helper = Join-Path $scriptDir 'Agent\Borealis\agent.py'
                if (-not (Test-Path $venvPythonw)) { Write-Host "pythonw.exe not found under Agent\Scripts" -ForegroundColor Yellow }
                if (-not (Test-Path $helper)) { Write-Host "Helper not found under Agent\Borealis" -ForegroundColor Yellow }
                if ((Test-Path $venvPythonw) -and (Test-Path $helper)) {
                    Start-Process -FilePath $venvPythonw -ArgumentList @('-W','ignore::SyntaxWarning', $helper) -WorkingDirectory (Split-Path $helper -Parent)
                    Write-Host "Launched user-session helper." -ForegroundColor Green
                }
                break
            }
            default { break }
        }
    }

    "3" {
        $host.UI.RawUI.WindowTitle = "Borealis Electron"
        Clear-Host
        Write-Host "Deploying Borealis Desktop App..." -ForegroundColor Cyan
        Write-Host "===================================================================================="

        $electronSource      = "Data\Electron"
        $electronDestination = "ElectronApp"
        $scriptDir           = Split-Path $MyInvocation.MyCommand.Path -Parent

        Run-Step "Prepare ElectronApp folder" {
            if (Test-Path $electronDestination) { Remove-Item $electronDestination -Recurse -Force }
            New-Item -Path $electronDestination -ItemType Directory | Out-Null
            $deployedServer = Join-Path $scriptDir 'Server\Borealis'
            if (-not (Test-Path $deployedServer)) { throw "Server\Borealis not found - please run choice 1 first." }
            Copy-Item $deployedServer "$electronDestination\Server" -Recurse
            Copy-Item "$electronSource\package.json" "$electronDestination" -Force
            Copy-Item "$electronSource\main.js"        "$electronDestination" -Force
            $staticBuild = Join-Path $scriptDir 'Server\web-interface\build'
            if (-not (Test-Path $staticBuild)) { throw "WebUI build not found - run choice 1 to build WebUI first." }
            Copy-Item "$staticBuild\*" "$electronDestination\renderer" -Recurse -Force
        }

        Run-Step "ElectronApp: Install Node dependencies" {
            Push-Location $electronDestination
            $env:NODE_ENV = ''
            $env:npm_config_production = ''
            & $npmCmd install --silent --no-fund --audit=false
            Pop-Location
        }

        Run-Step "ElectronApp: Package with electron-builder" {
            Push-Location $electronDestination
            & $npmCmd run dist
            Pop-Location
        }

        Run-Step "ElectronApp: Launch in dev mode" {
            Push-Location $electronDestination
            & $npmCmd run dev
            Pop-Location
        }
    }

    "4" {
        $host.UI.RawUI.WindowTitle = "Borealis Packager"
        Write-Host "Choose which module to package into a self-contained EXE file:" -ForegroundColor DarkYellow
        Write-Host " 1) Server" -ForegroundColor DarkGray
        Write-Host " 2) Agent" -ForegroundColor DarkGray
        $exePackageChoice = Read-Host "Enter choice [1/2]"
        switch ($exePackageChoice) {
            "1" {
                $serverScriptDir = Join-Path -Path $PSScriptRoot -ChildPath "Data\Server"
                Set-Location -Path $serverScriptDir
                & (Join-Path -Path $serverScriptDir -ChildPath "Package-Borealis-Server.ps1")
            }
            "2" {
                $agentScriptDir = Join-Path -Path $PSScriptRoot -ChildPath "Data\Agent"
                Set-Location -Path $agentScriptDir
                & (Join-Path -Path $agentScriptDir -ChildPath "Package_Borealis-Agent.ps1")
            }
            default { Write-Host "Invalid Choice. Exiting..." -ForegroundColor Red; exit 1 }
        }
    }

    "5" {
        $host.UI.RawUI.WindowTitle = "Borealis Updater"
        Write-Host " "
        Write-Host "Updating Borealis..." -ForegroundColor Green
        $updateZip = Join-Path $scriptDir "Update_Staging\main.zip"
        $updateDir = Join-Path $scriptDir "Update_Staging\Borealis-main"
        $preservePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints\Tesseract-OCR"
        $preserveBackupPath = Join-Path $scriptDir "Update_Staging\Tesseract-OCR"

        Run-Step "Updating: Move Tesseract-OCR Folder Somewhere Safe to Restore Later" {
            if (Test-Path $preservePath) {
                $stagingPath = Join-Path $scriptDir "Update_Staging"
                if (-not (Test-Path $stagingPath)) { New-Item -ItemType Directory -Force -Path $stagingPath | Out-Null }
                Move-Item -Path $preservePath -Destination $preserveBackupPath -Force
            }
        }

        Run-Step "Updating: Clean Up Folders to Prepare for Update" {
            Remove-Item -Recurse -Force -ErrorAction SilentlyContinue `
                (Join-Path $scriptDir "Data"), `
                (Join-Path $scriptDir "Server\web-interface\src"), `
                (Join-Path $scriptDir "Server\web-interface\build"), `
                (Join-Path $scriptDir "Server\web-interface\public"), `
                (Join-Path $scriptDir "Server\Borealis")
        }

        Run-Step "Updating: Create Update Staging Folder" {
            $stagingPath = Join-Path $scriptDir "Update_Staging"
            if (-not (Test-Path $stagingPath)) { New-Item -ItemType Directory -Force -Path $stagingPath | Out-Null }
            $updateZip = Join-Path $stagingPath "main.zip"
            $updateDir = Join-Path $stagingPath "Borealis-main"
        }

        Run-Step "Updating: Download Update" { Invoke-WebRequest -Uri "https://github.com/bunny-lab-io/Borealis/archive/refs/heads/main.zip" -OutFile $updateZip }
        Run-Step "Updating: Extract Update Files" { Expand-Archive -Path $updateZip -DestinationPath (Join-Path $scriptDir "Update_Staging") -Force }
        Run-Step "Updating: Copy Update Files into Production Borealis Root Folder" { Copy-Item "$updateDir\*" $scriptDir -Recurse -Force }

        Run-Step "Updating: Restore Tesseract-OCR Folder" {
            $restorePath = Join-Path $scriptDir "Data\Server\Python_API_Endpoints"
            if (Test-Path $preserveBackupPath) {
                if (-not (Test-Path $restorePath)) { New-Item -ItemType Directory -Force -Path $restorePath | Out-Null }
                Move-Item -Path $preserveBackupPath -Destination $restorePath -Force
            }
        }

        Run-Step "Updating: Clean Up Update Staging Folder" { Remove-Item -Recurse -Force -ErrorAction SilentlyContinue (Join-Path $scriptDir "Update_Staging") }

        Write-Host "`nUpdate Complete! Please Re-Launch the Borealis Script." -ForegroundColor Green
        Read-Host "Press any key to re-launch Borealis..."
        & (Join-Path $scriptDir "Borealis.ps1")
        Exit 0
    }

    # (Removed) case "6" experimental AHK test

    default { Write-Host "Invalid selection. Exiting..." -ForegroundColor Red; exit 1 }
}
