$ErrorActionPreference = 'Stop'
$VendooDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$Version = '1.41.2'
$Port = 8124
$InstallModeFile = Join-Path $VendooDir '.vendoo-install-mode'

try {
    $utf8NoBom = New-Object System.Text.UTF8Encoding($false)
    [Console]::InputEncoding = $utf8NoBom
    [Console]::OutputEncoding = $utf8NoBom
    $OutputEncoding = $utf8NoBom
} catch {}


function Write-Utf8NoBomFile([string]$Path, [string]$Content) {
    $utf8 = New-Object System.Text.UTF8Encoding($false)
    [System.IO.File]::WriteAllText($Path, $Content, $utf8)
}

function Write-Header {
    Clear-Host
    Write-Host ''
    Write-Host '  ╔══════════════════════════════════════╗' -ForegroundColor DarkGreen
    Write-Host '  ║                                      ║' -ForegroundColor DarkGreen
    Write-Host '  ║     V E N D O O   S E T U P          ║' -ForegroundColor Green
    Write-Host '  ║     Listing-Generator v'$Version'        ║' -ForegroundColor DarkGreen
    Write-Host '  ║                                      ║' -ForegroundColor DarkGreen
    Write-Host '  ╚══════════════════════════════════════╝' -ForegroundColor DarkGreen
    Write-Host ''
}

function Write-Step($step, $text) {
    Write-Host "  [$step] " -ForegroundColor Cyan -NoNewline
    Write-Host $text
}

function Write-OK($text) {
    Write-Host "  [OK] " -ForegroundColor Green -NoNewline
    Write-Host $text
}

function Write-Warn($text) {
    Write-Host "  [!] " -ForegroundColor Yellow -NoNewline
    Write-Host $text
}

function Write-Err($text) {
    Write-Host "  [X] " -ForegroundColor Red -NoNewline
    Write-Host $text
}

function Remove-LegacyTryOnComponents {
    $migrationMarker = Join-Path $VendooDir '.migration-image-stack-cleanup-1.20.0'
    if (Test-Path -LiteralPath $migrationMarker) { return }

    $legacyRoot = Join-Path $VendooDir 'local-ai\fashn-vton'
    $legacyUploadRoot = Join-Path $VendooDir 'uploads\vto-models'
    $legacyArchiveRoot = Join-Path $VendooDir 'uploads\ai-generated\legacy-models'
    $hasLegacyRuntime = (Test-Path -LiteralPath $legacyRoot) -or (Test-Path -LiteralPath $legacyUploadRoot)
    if ($hasLegacyRuntime) {
        Write-Host ''
        Write-Step 'Cleanup' 'Entferne den aufgegebenen lokalen Try-on-Stack. Eigene Bilder bleiben erhalten ...'
    }

    try {
        Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
            Where-Object { $_.CommandLine -match 'fashn-vton|local-vto|service\.py.+8190' } |
            ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }
    } catch {}
    try { & schtasks.exe /Delete /F /TN 'Vendoo FASHN VTON' 2>$null | Out-Null } catch {}
    try { Remove-ItemProperty -Path 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run' -Name 'VendooFashnVton' -ErrorAction SilentlyContinue } catch {}
    try {
        $startup = [Environment]::GetFolderPath('Startup')
        if ($startup) { Remove-Item -LiteralPath (Join-Path $startup 'Vendoo-FASHN-VTON.cmd') -Force -ErrorAction SilentlyContinue }
    } catch {}

    if (Test-Path -LiteralPath $legacyUploadRoot) {
        Get-ChildItem -LiteralPath $legacyUploadRoot -File -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
            $relative = $_.FullName.Substring($legacyUploadRoot.Length).TrimStart('\')
            $target = Join-Path $legacyArchiveRoot $relative
            New-Item -ItemType Directory -Force -Path (Split-Path -Parent $target) | Out-Null
            if (-not (Test-Path -LiteralPath $target)) { Move-Item -LiteralPath $_.FullName -Destination $target -Force -ErrorAction SilentlyContinue }
        }
        Remove-Item -LiteralPath $legacyUploadRoot -Recurse -Force -ErrorAction SilentlyContinue
    }

    if (Test-Path -LiteralPath $legacyRoot) {
        Write-Step 'Cleanup' 'Entferne alte Python-, Modell- und Dienstdateien ...'
        Remove-Item -LiteralPath $legacyRoot -Recurse -Force -ErrorAction SilentlyContinue
    }
    @(
        'scripts\install-local-vto.ps1','scripts\uninstall-local-vto.ps1',
        'lib\ai-vto-bfl.mjs','lib\ai-vto-local.mjs','lib\ai-vto-model-jobs.mjs','lib\ai-vto-model-prompts.mjs',
        'local-ai\workflows\vendoo_flux_tryon_api.json'
    ) | ForEach-Object { Remove-Item -LiteralPath (Join-Path $VendooDir $_) -Force -ErrorAction SilentlyContinue }
    Get-ChildItem -LiteralPath (Join-Path $VendooDir 'logs') -Filter 'local-vto*' -File -ErrorAction SilentlyContinue |
        Remove-Item -Force -ErrorAction SilentlyContinue

    $legacyEnv = Join-Path $VendooDir 'local-ai\.env.local-flux'
    if (Test-Path -LiteralPath $legacyEnv) {
        $cleanLines = Get-Content -LiteralPath $legacyEnv -ErrorAction SilentlyContinue | Where-Object { $_ -notmatch '^LOCAL_IMAGE_TRYON_WORKFLOW_PATH=' }
        Write-Utf8NoBomFile -Path $legacyEnv -Content (($cleanLines -join [Environment]::NewLine) + [Environment]::NewLine)
    }

    $rootEnv = Join-Path $VendooDir '.env'
    if (Test-Path -LiteralPath $rootEnv) {
        $cleanRootEnv = Get-Content -LiteralPath $rootEnv -ErrorAction SilentlyContinue | Where-Object {
            $_ -notmatch '^(BFL_API_KEY|LOCAL_IMAGE_TRYON_WORKFLOW_PATH|VENDOO_VTO_URL|VENDOO_VTO_TOKEN)='
        }
        Write-Utf8NoBomFile -Path $rootEnv -Content (($cleanRootEnv -join [Environment]::NewLine) + [Environment]::NewLine)
    }

    $legacyDocs = @(
        'SLICE_24_VIRTUAL_TRYON_PROMPT_POPUP.md','SLICE_24_1_TRYON_WORKFLOW_SELF_HEAL.md',
        'SLICE_25_PRODUCTIVE_VIRTUAL_TRYON.md','SLICE_25_SHA256.json','SLICE_25_VERIFICATION.json',
        'SLICE_26_LOCAL_FASHN_VTON_FLUX_PROMPT.md','SLICE_26_SHA256.json','SLICE_26_VERIFICATION.json',
        'SLICE_26_1_PYTHON_INSTALLER_LIVE_STATUS.md','SLICE_26_1_SHA256.json','SLICE_26_1_VERIFICATION.json',
        'SLICE_26_2_INSTALLER_ENCODING_PYTHON_DETECTION.md','SLICE_26_2_SHA256.json','SLICE_26_2_VERIFICATION.json',
        'SLICE_26_3_PORTABLE_PYTHON_RUNTIME.md','SLICE_26_3_SHA256.json','SLICE_26_3_VERIFICATION.json',
        'SLICE_26_4_UV_PYTHON_DETECTION.md','SLICE_26_4_SHA256.json','SLICE_26_4_VERIFICATION.json',
        'SLICE_26_5_NATIVE_PROCESS_RUNNER.md','SLICE_26_5_VERIFICATION.json',
        'SLICE_26_6_VTO_PIP_BOOTSTRAP_STATUS.md','SLICE_26_6_VERIFICATION.json',
        'SLICE_26_7_NPM_PUBLIC_REGISTRY_ATOMIC_REPAIR.md','SLICE_26_7_VERIFICATION.json',
        'SLICE_26_8_VTO_DEPENDENCY_INSTALLER.md','SLICE_26_8_VERIFICATION.json',
        'SLICE_26_9_VTO_UTF8_PARSER_AUTOSTART.md','SLICE_27_FLUX_MODEL_STUDIO_LIBRARY.md','SLICE_27_VERIFICATION.json'
    )
    foreach ($doc in $legacyDocs) { Remove-Item -LiteralPath (Join-Path $VendooDir ('docs\' + $doc)) -Force -ErrorAction SilentlyContinue }

    Write-Utf8NoBomFile -Path $migrationMarker -Content "Vendoo 1.20.0 image-stack cleanup completed."
    if ($hasLegacyRuntime) { Write-OK 'Alter Try-on-Stack wurde entfernt; FLUX bleibt vollständig erhalten.' }
}


function Test-VendooPowerShellFiles {
    $relativeFiles = @(
        'scripts\install-local-flux.ps1',
        'scripts\uninstall-local-flux.ps1',
        'setup-gui.ps1'
    )
    $failed = $false
    foreach ($relative in $relativeFiles) {
        $path = Join-Path $VendooDir $relative
        if (-not (Test-Path -LiteralPath $path)) {
            Write-Err ("Pflichtskript fehlt: {0}" -f $relative)
            $failed = $true
            continue
        }
        $tokens = $null
        $errors = $null
        [System.Management.Automation.Language.Parser]::ParseFile($path, [ref]$tokens, [ref]$errors) | Out-Null
        if ($errors.Count -gt 0) {
            Write-Err ("PowerShell-Syntaxfehler in {0}" -f $relative)
            foreach ($parseError in $errors) {
                Write-Host ("      Zeile {0}: {1}" -f $parseError.Extent.StartLineNumber, $parseError.Message) -ForegroundColor Red
            }
            $failed = $true
        }
    }
    return (-not $failed)
}

function Test-NodeInstalled {
    try {
        $v = & node --version 2>$null
        if ($v -match '^v(\d+)') {
            $major = [int]$Matches[1]
            return @{ Installed = $true; Version = $v; Major = $major }
        }
    } catch {}
    return @{ Installed = $false; Version = $null; Major = 0 }
}

function Test-NpmInstalled {
    try {
        $v = & npm --version 2>$null
        if ($v) { return $true }
    } catch {}
    return $false
}

function Get-VendooInstallState {
    $nodeModules = Test-Path (Join-Path $VendooDir 'node_modules')
    $envFile = Test-Path (Join-Path $VendooDir '.env')
    $dbFile = Test-Path (Join-Path $VendooDir 'db\vendoo.db')
    $state = 'not-installed'
    if ($nodeModules -and $envFile) { $state = 'installed' }
    elseif ($nodeModules -or $envFile -or $dbFile) { $state = 'partial' }
    return @{ State = $state; NodeModules = $nodeModules; EnvFile = $envFile; DbFile = $dbFile }
}

function Get-LocalFluxScriptPath {
    return Join-Path $VendooDir 'scripts\install-local-flux.ps1'
}


function Stop-VendooNodeProcesses {
    $stopped = 0
    try {
        $escapedDir = [regex]::Escape($VendooDir)
        $processes = Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue |
            Where-Object { $_.CommandLine -and $_.CommandLine -match $escapedDir }
        foreach ($process in $processes) {
            try {
                Stop-Process -Id $process.ProcessId -Force -ErrorAction Stop
                $stopped++
            } catch {}
        }
    } catch {}
    if ($stopped -gt 0) {
        Write-Warn "$stopped laufende Vendoo-Node-Prozess(e) beendet"
        Start-Sleep -Milliseconds 750
    }
}

function Remove-NodeModulesSafe {
    $nm = Join-Path $VendooDir 'node_modules'
    if (-not (Test-Path $nm)) { return $true }

    Stop-VendooNodeProcesses
    Write-Step '...' 'Unvollstaendige node_modules werden bereinigt...'

    try {
        Remove-Item $nm -Recurse -Force -Confirm:$false -ErrorAction Stop
    } catch {
        try {
            & cmd.exe /d /c "rmdir /s /q `"$nm`"" | Out-Null
        } catch {}
    }

    if (Test-Path $nm) {
        Write-Err 'node_modules konnte nicht vollstaendig entfernt werden.'
        Write-Host '  Ursache ist meist ein noch laufender Node-Prozess, Virenscanner oder Explorer-Zugriff.' -ForegroundColor Yellow
        Write-Host '  Bitte alle Vendoo-/Node-Fenster schliessen, notfalls Windows neu starten und Reparieren erneut ausfuehren.' -ForegroundColor Yellow
        return $false
    }
    return $true
}

function Repair-VendooNpmRegistryConfiguration {
    $lockFile = Join-Path $VendooDir 'package-lock.json'
    $npmrcFile = Join-Path $VendooDir '.npmrc'
    $publicRegistry = 'https://registry.npmjs.org/'

    $npmrcContent = @(
        'registry=https://registry.npmjs.org/',
        'audit=false',
        'fund=false',
        'strict-ssl=true',
        'prefer-offline=true',
        'fetch-retries=5',
        'fetch-retry-factor=2',
        'fetch-retry-mintimeout=10000',
        'fetch-retry-maxtimeout=120000'
    ) -join [Environment]::NewLine
    Write-Utf8NoBomFile -Path $npmrcFile -Content ($npmrcContent + [Environment]::NewLine)

    if (-not (Test-Path -LiteralPath $lockFile)) { return $true }
    try {
        $raw = [System.IO.File]::ReadAllText($lockFile)
        $original = $raw
        $raw = [regex]::Replace(
            $raw,
            'https://packages\.applied-caas-gateway\d+\.internal\.api\.openai\.org/artifactory/api/npm/npm-public/',
            $publicRegistry,
            [System.Text.RegularExpressions.RegexOptions]::IgnoreCase
        )
        if ($raw -match '(?i)internal\.api\.openai\.org|applied-caas-gateway|/artifactory/api/npm/') {
            Write-Err 'package-lock.json enthält weiterhin eine nicht öffentliche Registry-URL.'
            Write-Host '  Das Paket wurde aus einer Build-Umgebung verunreinigt. npm ci wird aus Sicherheitsgründen nicht gestartet.' -ForegroundColor Yellow
            return $false
        }
        if ($raw -ne $original) {
            Write-Warn 'Interne Build-Registry im package-lock.json wurde automatisch auf registry.npmjs.org korrigiert.'
            Write-Utf8NoBomFile -Path $lockFile -Content $raw
        }
        return $true
    } catch {
        Write-Err ("package-lock.json konnte nicht geprüft werden: {0}" -f $_.Exception.Message)
        return $false
    }
}

function Get-VendooNodeModulesBackupPath {
    return (Join-Path $VendooDir 'node_modules.vendoo-backup')
}

function Restore-VendooNodeModulesBackup {
    $nodeModules = Join-Path $VendooDir 'node_modules'
    $backup = Get-VendooNodeModulesBackupPath
    if (-not (Test-Path -LiteralPath $backup)) { return }
    try {
        if (Test-Path -LiteralPath $nodeModules) {
            Remove-Item -LiteralPath $nodeModules -Recurse -Force -ErrorAction SilentlyContinue
        }
        Move-Item -LiteralPath $backup -Destination $nodeModules -Force -ErrorAction Stop
        Write-Warn 'Vorherige node_modules wurden nach dem fehlgeschlagenen npm-Lauf wiederhergestellt.'
    } catch {
        Write-Warn ("node_modules-Sicherung konnte nicht automatisch wiederhergestellt werden: {0}" -f $_.Exception.Message)
    }
}

function Prepare-VendooNodeModulesBackup {
    $nodeModules = Join-Path $VendooDir 'node_modules'
    $backup = Get-VendooNodeModulesBackupPath
    Stop-VendooNodeProcesses

    if ((Test-Path -LiteralPath $backup) -and -not (Test-Path -LiteralPath $nodeModules)) {
        try { Move-Item -LiteralPath $backup -Destination $nodeModules -Force -ErrorAction Stop } catch {}
    }
    if (Test-Path -LiteralPath $backup) {
        try { Remove-Item -LiteralPath $backup -Recurse -Force -ErrorAction Stop } catch {
            Write-Err 'Alte node_modules-Sicherung konnte nicht entfernt werden.'
            return $false
        }
    }
    if (Test-Path -LiteralPath $nodeModules) {
        try {
            Move-Item -LiteralPath $nodeModules -Destination $backup -Force -ErrorAction Stop
            Write-Step '...' 'Bestehende node_modules wurden vorübergehend gesichert.'
        } catch {
            Write-Warn 'node_modules konnten nicht atomar gesichert werden. Vendoo versucht die robuste Bereinigung.'
            if (-not (Remove-NodeModulesSafe)) { return $false }
        }
    }
    return $true
}

function Remove-VendooNodeModulesBackup {
    $backup = Get-VendooNodeModulesBackupPath
    if (Test-Path -LiteralPath $backup) {
        try { Remove-Item -LiteralPath $backup -Recurse -Force -ErrorAction Stop } catch {
            Write-Warn 'Die alte node_modules-Sicherung konnte nicht gelöscht werden und kann später manuell entfernt werden.'
        }
    }
}

function Invoke-VendooNpmInstall([switch]$ForceClean) {
    $logDir = Join-Path $VendooDir 'logs'
    if (-not (Test-Path $logDir)) { New-Item -ItemType Directory -Path $logDir -Force | Out-Null }
    $logFile = Join-Path $logDir 'last-npm-install.log'
    $lockFile = Join-Path $VendooDir 'package-lock.json'
    $command = if (Test-Path $lockFile) { 'ci' } else { 'install' }

    if (-not (Repair-VendooNpmRegistryConfiguration)) { return $false }
    if (-not (Prepare-VendooNodeModulesBackup)) { return $false }

    $npmCommand = $null
    try { $npmCommand = (Get-Command 'npm.cmd' -ErrorAction Stop).Source } catch {}
    if ([string]::IsNullOrWhiteSpace([string]$npmCommand)) {
        Restore-VendooNodeModulesBackup
        Write-Err 'npm.cmd wurde nicht gefunden.'
        return $false
    }

    $attempt = 1
    $maxAttempts = 3
    while ($attempt -le $maxAttempts) {
        $stdoutFile = Join-Path $logDir ('npm-install-{0}.stdout.log' -f $attempt)
        $stderrFile = Join-Path $logDir ('npm-install-{0}.stderr.log' -f $attempt)
        $runnerFile = Join-Path $logDir ('npm-install-{0}.cmd' -f $attempt)
        Remove-Item -LiteralPath $stdoutFile,$stderrFile,$runnerFile -Force -ErrorAction SilentlyContinue

        $runnerContent = @(
            '@echo off',
            ('cd /d "{0}"' -f $VendooDir),
            ('call "{0}" {1} --registry=https://registry.npmjs.org/ --no-audit --no-fund --loglevel=warn' -f $npmCommand, $command),
            'set "VENDOO_NPM_EXIT=%ERRORLEVEL%"',
            'exit /b %VENDOO_NPM_EXIT%'
        ) -join "`r`n"
        [System.IO.File]::WriteAllText($runnerFile, $runnerContent + "`r`n", [System.Text.Encoding]::ASCII)

        $process = $null
        $output = @()
        $exitCode = 1
        $npmTimedOut = $false
        try {
            $cmdArguments = ('/d /s /c ""{0}""' -f $runnerFile)
            $process = Start-Process -FilePath $env:ComSpec -ArgumentList $cmdArguments -WorkingDirectory $VendooDir -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdoutFile -RedirectStandardError $stderrFile
            $started = Get-Date
            $lastShown = -1
            $npmTimeoutSeconds = 1800
            while (-not $process.HasExited) {
                Start-Sleep -Seconds 1
                $process.Refresh()
                $elapsed = [int]((Get-Date) - $started).TotalSeconds
                if ($elapsed -ge $npmTimeoutSeconds) {
                    $npmTimedOut = $true
                    try { $process.Kill() } catch {}
                    break
                }
                if ($elapsed -ge 2 -and [Math]::Floor($elapsed / 2) -gt $lastShown) {
                    $lastShown = [Math]::Floor($elapsed / 2)
                    $consoleWidth = 118
                    try { if ($Host.UI.RawUI.WindowSize.Width -gt 20) { $consoleWidth = [Math]::Max(40, $Host.UI.RawUI.WindowSize.Width - 1) } } catch {}
                    $statusText = ("  npm $command läuft seit ${elapsed}s (Versuch $attempt/$maxAttempts)")
                    if ($statusText.Length -gt $consoleWidth) { $statusText = $statusText.Substring(0, $consoleWidth) }
                    Write-Host ("`r" + $statusText.PadRight($consoleWidth)) -NoNewline -ForegroundColor DarkGray
                }
            }
            $process.WaitForExit()
            $process.Refresh()
            $consoleWidth = 118
            try { if ($Host.UI.RawUI.WindowSize.Width -gt 20) { $consoleWidth = [Math]::Max(40, $Host.UI.RawUI.WindowSize.Width - 1) } } catch {}
            Write-Host ("`r" + (' ' * $consoleWidth) + "`r") -NoNewline
            $exitCode = if ($npmTimedOut) { -2 } else { [int]$process.ExitCode }

            $stdoutLines = if (Test-Path -LiteralPath $stdoutFile) { @(Get-Content -LiteralPath $stdoutFile -ErrorAction SilentlyContinue) } else { @() }
            $stderrLines = if (Test-Path -LiteralPath $stderrFile) { @(Get-Content -LiteralPath $stderrFile -ErrorAction SilentlyContinue) } else { @() }
            $output = @($stdoutLines + $stderrLines)
            if ($npmTimedOut) { $output += 'Vendoo hat npm nach 30 Minuten ohne Abschluss beendet.' }

            # Zweite Sicherheitsstufe: Ein echter npm-Fehler darf niemals als Exit-Code 0 durchrutschen.
            $fatalOutput = @($output | Where-Object { ([string]$_) -match '(?i)^\s*npm\s+(error|ERR!)\b' })
            if ($exitCode -eq 0 -and $fatalOutput.Count -gt 0) {
                $exitCode = 1
                $output += 'Vendoo-Sicherheitsprüfung: npm meldete Fehlertext trotz Exit-Code 0. Lauf wird als fehlgeschlagen gewertet.'
            }
        } catch {
            $output = @($_.Exception.Message)
            $exitCode = 1
        }

        $header = @(
            ('Zeit: {0}' -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')),
            ('Befehl: npm {0} --registry=https://registry.npmjs.org/ --no-audit --no-fund --loglevel=warn' -f $command),
            ('Arbeitsordner: {0}' -f $VendooDir),
            ('Versuch: {0}/{1}' -f $attempt, $maxAttempts),
            ('Exit-Code: {0}' -f $exitCode),
            ''
        )
        Write-Utf8NoBomFile -Path $logFile -Content ((@($header + $output) -join [Environment]::NewLine) + [Environment]::NewLine)

        Remove-Item -LiteralPath $stdoutFile,$stderrFile,$runnerFile -Force -ErrorAction SilentlyContinue

        if ($exitCode -eq 0) {
            $warnings = @($output | Where-Object { ([string]$_) -match '(?i)^npm\s+warn|deprecated' })
            if ($warnings.Count -gt 0) {
                Write-Warn ("npm $command war erfolgreich, meldete aber $($warnings.Count) Warnung(en). Details: $logFile")
            }
            Remove-VendooNodeModulesBackup
            Write-OK "Dependencies mit npm $command installiert"
            return $true
        }

        $text = ($output | Out-String)
        $isNetworkError = $text -match '(?i)ETIMEDOUT|ECONNRESET|ENOTFOUND|EAI_AGAIN|ECONNREFUSED|network request|502 Bad Gateway|503 Service Unavailable'
        if ($attempt -lt $maxAttempts -and $isNetworkError) {
            $waitSeconds = if ($attempt -eq 1) { 5 } else { 15 }
            Write-Warn ("npm-Netzwerkfehler. Neuer Versuch in $waitSeconds Sekunden über registry.npmjs.org.")
            if (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules')) {
                Remove-Item -LiteralPath (Join-Path $VendooDir 'node_modules') -Recurse -Force -ErrorAction SilentlyContinue
            }
            Start-Sleep -Seconds $waitSeconds
            $attempt++
            continue
        }

        Write-Err "npm $command fehlgeschlagen (Exit-Code $exitCode)"
        Write-Host "  Vollständiges Protokoll: $logFile" -ForegroundColor Yellow
        $lastLines = @($output | Where-Object { -not [string]::IsNullOrWhiteSpace([string]$_) } | Select-Object -Last 20)
        foreach ($line in $lastLines) { Write-Host "  $line" -ForegroundColor DarkYellow }
        Restore-VendooNodeModulesBackup
        return $false
    }

    Restore-VendooNodeModulesBackup
    return $false
}


function Get-VendooInstallMode {
    if (Test-Path -LiteralPath $InstallModeFile) {
        try {
            $mode = (Get-Content -LiteralPath $InstallModeFile -Raw).Trim().ToLowerInvariant()
            if (@('windows-local', 'docker') -contains $mode) { return $mode }
        } catch {}
    }
    if (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules')) { return 'windows-local' }
    return 'windows-local'
}

function Set-VendooInstallMode([string]$Mode) {
    Write-Utf8NoBomFile -Path $InstallModeFile -Content ($Mode.Trim().ToLowerInvariant() + "`n")
}

function Test-DockerAvailable {
    try {
        $version = & docker version --format '{{.Server.Version}}' 2>$null
        if (-not $version) { return @{ Available = $false; Reason = 'Docker Engine ist nicht erreichbar.' } }
        & docker compose version 1>$null 2>$null
        if ($LASTEXITCODE -ne 0) { return @{ Available = $false; Reason = 'Das Docker-Compose-Plugin fehlt.' } }
        return @{ Available = $true; Version = $version; Reason = '' }
    } catch {
        return @{ Available = $false; Reason = 'Docker Desktop bzw. Docker Engine wurde nicht gefunden.' }
    }
}

function Get-SecureSetupToken {
    $bytes = New-Object byte[] 32
    $rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
    try { $rng.GetBytes($bytes) } finally { $rng.Dispose() }
    return (($bytes | ForEach-Object { $_.ToString('x2') }) -join '')
}

function Set-VendooEnvValue([string]$Key, [string]$Value) {
    $envFile = Join-Path $VendooDir '.env'
    if (-not (Test-Path -LiteralPath $envFile)) { return }
    $content = Get-Content -LiteralPath $envFile -Raw
    $escaped = [regex]::Escape($Key)
    if ($content -match "(?m)^$escaped=") {
        $content = [regex]::Replace($content, "(?m)^$escaped=.*$", "$Key=$Value")
    } else {
        $content = $content.TrimEnd() + "`r`n$Key=$Value`r`n"
    }
    Write-Utf8NoBomFile -Path $envFile -Content $content
}

function Get-VendooEnvValue([string]$Key) {
    $envFile = Join-Path $VendooDir '.env'
    if (-not (Test-Path -LiteralPath $envFile)) { return '' }
    $line = Get-Content -LiteralPath $envFile | Where-Object { $_ -match ('^' + [regex]::Escape($Key) + '=') } | Select-Object -Last 1
    if (-not $line) { return '' }
    return ($line -replace ('^' + [regex]::Escape($Key) + '='), '').Trim()
}

function Initialize-VendooEnvironment([string]$Mode = 'docker') {
    $envFile = Join-Path $VendooDir '.env'
    $envExample = Join-Path $VendooDir '.env.example'
    if (-not (Test-Path -LiteralPath $envFile)) {
        if (-not (Test-Path -LiteralPath $envExample)) { throw '.env.example fehlt.' }
        Copy-Item -LiteralPath $envExample -Destination $envFile
        Write-OK '.env aus sicherer Vorlage erstellt'
    }
    if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_SETUP_TOKEN'))) {
        $token = Get-SecureSetupToken
        Set-VendooEnvValue 'VENDOO_SETUP_TOKEN' $token
        Write-Host ''
        Write-Host '  ERST-ADMIN-SETUP-CODE:' -ForegroundColor Yellow
        Write-Host "  $token" -ForegroundColor White
        Write-Warn 'Diesen Code jetzt sicher notieren. Er wird bei der ersten Einrichtung benötigt.'
    }
    if ($Mode -eq 'docker') {
        if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS'))) { Set-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS' '127.0.0.1' }
        Set-VendooEnvValue 'VENDOO_BIND_HOST' '0.0.0.0'
        Set-VendooEnvValue 'LOCAL_IMAGE_URL' 'http://host.docker.internal:8188'
    } else {
        if ([string]::IsNullOrWhiteSpace((Get-VendooEnvValue 'VENDOO_BIND_HOST'))) { Set-VendooEnvValue 'VENDOO_BIND_HOST' '127.0.0.1' }
        Set-VendooEnvValue 'LOCAL_IMAGE_URL' 'http://127.0.0.1:8188'
    }
    Set-VendooEnvValue 'VENDOO_COOKIE_SECURE' 'auto'
    Set-VendooEnvValue 'VENDOO_REQUIRE_SETUP_TOKEN' 'true'
}

function Invoke-VendooDockerCompose([string[]]$Arguments) {
    Push-Location $VendooDir
    try {
        $composeArgs = @('compose', '-f', (Join-Path $VendooDir 'compose.yaml')) + $Arguments
        & docker @composeArgs
        return $LASTEXITCODE
    } finally { Pop-Location }
}

function Install-VendooDocker {
    Write-Header
    Write-Host '  Docker-Installation auf diesem Windows-PC ...' -ForegroundColor White
    Write-Host ''
    $docker = Test-DockerAvailable
    if (-not $docker.Available) {
        Write-Err $docker.Reason
        Write-Host '  Installiere bzw. starte Docker Desktop und führe den Installer erneut aus.' -ForegroundColor Yellow
        Write-Host '  Docker Desktop enthält Docker Engine, CLI und Docker Compose.' -ForegroundColor DarkGray
        return
    }
    Write-OK "Docker Engine $($docker.Version) erreichbar"
    Initialize-VendooEnvironment -Mode 'docker'
    Set-VendooInstallMode 'docker'
    Write-Step '1/3' 'Compose-Konfiguration prüfen ...'
    if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
    Write-Step '2/3' 'Vendoo-Container bauen ...'
    if ((Invoke-VendooDockerCompose @('build', '--pull')) -ne 0) { Write-Err 'Docker-Build fehlgeschlagen.'; return }
    Write-Step '3/3' 'Vendoo starten ...'
    if ((Invoke-VendooDockerCompose @('up', '-d', '--remove-orphans')) -ne 0) { Write-Err 'Containerstart fehlgeschlagen.'; return }
    Write-OK 'Vendoo läuft im Docker-Modus.'
    Write-Host "  Readiness: http://127.0.0.1:$Port/readyz" -ForegroundColor Cyan
    Start-Process "http://127.0.0.1:$Port/login.html"
}

function Show-VendooRemoteDeploymentGuide([string]$Target) {
    Write-Header
    Write-Host "  $Target-Installation" -ForegroundColor White
    Write-Host ''
    Write-Host '  Ein VPS oder NAS wird nicht unsicher vom Windows-PC ferninstalliert.' -ForegroundColor Yellow
    Write-Host '  Kopiere den Vendoo-Ordner auf das Zielsystem und starte dort:' -ForegroundColor White
    Write-Host ''
    Write-Host '    chmod +x setup.sh' -ForegroundColor Cyan
    Write-Host '    ./setup.sh' -ForegroundColor Cyan
    Write-Host ''
    Write-Host '  Der Linux-Assistent bietet Docker, NAS/Portainer und VPS hinter HTTPS-Reverse-Proxy an.' -ForegroundColor DarkGray
    Write-Host '  Datenbank, Uploads, .env, Backups und Operations bleiben persistent und werden nicht gelöscht.' -ForegroundColor DarkGray
}

function Configure-VendooConsole {
    Write-Header
    Write-Host '  Geführte Konfiguration – keine Datei muss manuell bearbeitet werden.' -ForegroundColor White
    Write-Host ''
    $mode = Get-VendooInstallMode
    $portInput = Read-Host "  Port [$Port]"
    if (-not [string]::IsNullOrWhiteSpace($portInput)) {
        $parsed = 0
        if (-not [int]::TryParse($portInput, [ref]$parsed) -or $parsed -lt 1024 -or $parsed -gt 65535) { Write-Err 'Port muss zwischen 1024 und 65535 liegen.'; return }
        $script:Port = $parsed
    }
    Initialize-VendooEnvironment -Mode ($(if ($mode -eq 'docker') { 'docker' } else { 'windows-local' }))
    Set-VendooEnvValue 'PORT' ([string]$Port)
    Set-VendooEnvValue 'VENDOO_HOST_PORT' ([string]$Port)
    $lan = Read-Host '  Zugriff im privaten LAN erlauben? (ja/nein)'
    if ($mode -eq 'docker') {
        Set-VendooEnvValue 'VENDOO_PUBLISH_ADDRESS' ($(if ($lan -eq 'ja') { '0.0.0.0' } else { '127.0.0.1' }))
    } else {
        Set-VendooEnvValue 'VENDOO_BIND_HOST' ($(if ($lan -eq 'ja') { '0.0.0.0' } else { '127.0.0.1' }))
    }
    if ($lan -eq 'ja') {
        $baseUrl = Read-Host '  LAN-Basisadresse für QR-Uploads (optional, z. B. http://192.168.1.20:8124)'
        if (-not [string]::IsNullOrWhiteSpace($baseUrl)) {
            if ($baseUrl -notmatch '^https?://') { Write-Err 'Adresse muss mit http:// oder https:// beginnen.'; return }
            Set-VendooEnvValue 'PUBLIC_BASE_URL' $baseUrl.Trim().TrimEnd('/')
        }
    }
    $flux = Read-Host '  Lokales FLUX aktivieren? (ja/nein)'
    Set-VendooEnvValue 'LOCAL_IMAGE_ENABLED' ($(if ($flux -eq 'ja') { 'true' } else { 'false' }))
    $token = Get-VendooEnvValue 'VENDOO_SETUP_TOKEN'
    try { Set-Clipboard -Value $token -ErrorAction Stop; Write-OK 'Erst-Admin-Code wurde in die Zwischenablage kopiert.' } catch {}
    Write-OK 'Konfiguration gespeichert.'
    Write-Host '  AI- und Marktplatz-Schlüssel werden später in Vendoo → Einstellungen gepflegt.' -ForegroundColor DarkGray
}

function Install-Vendoo {
    Write-Header
    Write-Host '  Wo soll Vendoo betrieben werden?' -ForegroundColor White
    Write-Host ''
    Write-Host '    1  Lokal auf diesem Windows-PC (Node.js)' -ForegroundColor Cyan
    Write-Host '    2  Docker Desktop auf diesem Windows-PC' -ForegroundColor Cyan
    Write-Host '    3  NAS / Portainer / Docker-Server' -ForegroundColor Cyan
    Write-Host '    4  VPS / öffentlicher Linux-Server' -ForegroundColor Cyan
    Write-Host '    0  Abbrechen' -ForegroundColor DarkGray
    Write-Host ''
    $choice = Read-Host '  Installationsziel'
    switch ($choice) {
        '1' { Set-VendooInstallMode 'windows-local'; Install-VendooLocal }
        '2' { Install-VendooDocker }
        '3' { Show-VendooRemoteDeploymentGuide 'NAS / Portainer' }
        '4' { Show-VendooRemoteDeploymentGuide 'VPS / Linux' }
        '0' { return }
        default { Write-Warn 'Ungültiges Installationsziel.' }
    }
}

function Start-Vendoo {
    if ((Get-VendooInstallMode) -eq 'docker') {
        $docker = Test-DockerAvailable
        if (-not $docker.Available) { Write-Err $docker.Reason; return }
        if ((Invoke-VendooDockerCompose @('up', '-d')) -eq 0) {
            Write-OK 'Vendoo-Container gestartet.'
            Start-Process "http://127.0.0.1:$Port/"
        }
        return
    }
    Start-VendooLocal
}

function Update-Vendoo {
    if ((Get-VendooInstallMode) -eq 'docker') {
        $docker = Test-DockerAvailable
        if (-not $docker.Available) { Write-Err $docker.Reason; return }
        Initialize-VendooEnvironment -Mode 'docker'
        if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
        if ((Invoke-VendooDockerCompose @('build', '--pull')) -ne 0) { Write-Err 'Docker-Update-Build fehlgeschlagen.'; return }
        if ((Invoke-VendooDockerCompose @('up', '-d', '--remove-orphans')) -ne 0) { Write-Err 'Docker-Update konnte nicht gestartet werden.'; return }
        Write-OK 'Docker-Update abgeschlossen.'
        return
    }
    Update-VendooLocal
}

function Repair-Vendoo {
    if ((Get-VendooInstallMode) -eq 'docker') {
        $docker = Test-DockerAvailable
        if (-not $docker.Available) { Write-Err $docker.Reason; return }
        if ((Invoke-VendooDockerCompose @('config')) -ne 0) { Write-Err 'Compose-Konfiguration ist ungültig.'; return }
        if ((Invoke-VendooDockerCompose @('build', '--no-cache')) -ne 0) { Write-Err 'Docker-Reparatur-Build fehlgeschlagen.'; return }
        if ((Invoke-VendooDockerCompose @('up', '-d', '--force-recreate', '--remove-orphans')) -ne 0) { Write-Err 'Docker-Reparaturstart fehlgeschlagen.'; return }
        Write-OK 'Docker-Container neu gebaut und gestartet.'
        return
    }
    Repair-VendooLocal
}

function Install-VendooLocal {
    Write-Header
    Write-Host '  Installation starten...' -ForegroundColor White
    Write-Host ''

    $installState = Get-VendooInstallState
    if ($installState.State -eq 'installed') {
        Write-Warn 'Vendoo scheint bereits installiert zu sein.'
        Write-Host '  Statt versehentlich neu zu installieren, nutze besser Starten, Update oder Reparieren.' -ForegroundColor Yellow
        $choice = Read-Host '  Aktion: [S]tarten / [U]pdate / [R]eparieren / [N]eu installieren / [A]bbrechen'
        if ([string]::IsNullOrWhiteSpace($choice)) {
            $choice = ''
        }
        $choice = $choice.ToLowerInvariant()
        switch ($choice) {
            's' { Start-VendooLocal; return }
            'u' { Update-VendooLocal; return }
            'r' { Repair-VendooLocal; return }
            'a' { return }
            'n' { Write-Host '  Vollständige Neuinstallation wird fortgesetzt...' -ForegroundColor DarkGray }
            default {
                Write-Warn 'Keine gültige Aktion gewählt. Installation wurde abgebrochen.'
                return
            }
        }
    }

    # 1. Node.js pruefen
    Write-Step '1/5' 'Node.js pruefen...'
    $node = Test-NodeInstalled
    if (-not $node.Installed) {
        Write-Err 'Node.js ist nicht installiert!'
        Write-Host ''
        Write-Host '  Node.js wird benoetigt (mindestens v18).' -ForegroundColor Yellow
        Write-Host '  Download: https://nodejs.org/' -ForegroundColor Cyan
        Write-Host ''
        $dl = Read-Host '  Node.js jetzt automatisch installieren? (j/n)'
        if ($dl -eq 'j') {
            Write-Step '...' 'Lade Node.js Installer herunter...'
            $installerUrl = 'https://nodejs.org/dist/v22.16.0/node-v22.16.0-x64.msi'
            $installerPath = Join-Path $env:TEMP 'node-installer.msi'
            try {
                [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
                Invoke-WebRequest -Uri $installerUrl -OutFile $installerPath -UseBasicParsing
                Write-Step '...' 'Installiere Node.js...'
                Start-Process msiexec.exe -ArgumentList "/i `"$installerPath`" /qn" -Wait -NoNewWindow
                $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')
                Start-Sleep -Seconds 2
                $node = Test-NodeInstalled
                if ($node.Installed) {
                    Write-OK "Node.js $($node.Version) installiert"
                } else {
                    Write-Err 'Installation fehlgeschlagen. Bitte manuell installieren.'
                    return
                }
            } catch {
                Write-Err "Download fehlgeschlagen: $_"
                Write-Host '  Bitte installiere Node.js manuell: https://nodejs.org/' -ForegroundColor Yellow
                return
            }
        } else {
            Write-Host '  Installation abgebrochen.' -ForegroundColor Yellow
            return
        }
    } elseif ($node.Major -lt 18) {
        Write-Warn "Node.js $($node.Version) ist zu alt. Mindestens v18 erforderlich."
        Write-Host '  Update: https://nodejs.org/' -ForegroundColor Cyan
        return
    } else {
        Write-OK "Node.js $($node.Version) gefunden"
    }

    # 2. npm pruefen
    Write-Step '2/5' 'npm pruefen...'
    if (Test-NpmInstalled) {
        $npmV = & npm --version 2>$null
        Write-OK "npm v$npmV gefunden"
    } else {
        Write-Err 'npm nicht gefunden. Wird mit Node.js mitgeliefert.'
        return
    }

    # 3. Dependencies reproduzierbar installieren
    Write-Step '3/5' 'Dependencies installieren (npm ci)...'
    if (-not (Invoke-VendooNpmInstall)) { return }

    # 4. Sichere Konfiguration erzeugen bzw. übernehmen
    Write-Step '4/5' 'Sichere Konfiguration prüfen...'
    Initialize-VendooEnvironment -Mode 'windows-local'
    Write-OK 'Konfiguration ist vollständig; keine manuelle Dateibearbeitung erforderlich'

    # 5. Ordner pruefen
    Write-Step '5/5' 'Verzeichnisse pruefen...'
    $dirs = @('uploads', 'db')
    foreach ($d in $dirs) {
        $p = Join-Path $VendooDir $d
        if (-not (Test-Path $p)) {
            New-Item -ItemType Directory -Path $p -Force | Out-Null
        }
    }
    Write-OK 'Verzeichnisse OK'

    Write-Host ''
    Write-Host '  ========================================' -ForegroundColor Green
    Write-Host '  Installation abgeschlossen!' -ForegroundColor Green
    Write-Host '  ========================================' -ForegroundColor Green
    Write-Host ''
    Write-Host "  Starte Vendoo mit:  " -NoNewline
    Write-Host 'npm start' -ForegroundColor Cyan
    Write-Host "  Oder im Dev-Modus:  " -NoNewline
    Write-Host 'npm run dev' -ForegroundColor Cyan
    Write-Host "  Dann oeffne:        " -NoNewline
    Write-Host "http://localhost:$Port" -ForegroundColor Cyan
    Write-Host ''

    if ($script:AutoStartAfterInstall) {
        Start-VendooLocalDetached
        return
    }
    $start = Read-Host '  Vendoo jetzt starten? (j/n)'
    if ($start -eq 'j') {
        Start-VendooLocal
    }
}

function Start-VendooLocalDetached {
    Write-Step '...' 'Starte Vendoo im eigenen Prozess ...'
    Start-Process -FilePath 'node.exe' -ArgumentList @('bootstrap.mjs') -WorkingDirectory $VendooDir -WindowStyle Minimized
    Start-Sleep -Seconds 2
    Write-OK 'Vendoo wurde gestartet.'
}

function Start-VendooLocal {
    Write-Host ''
    Write-Step '...' 'Starte Vendoo-Server...'
    Push-Location $VendooDir
    Start-Process "http://localhost:$Port/?build=1.41.2"
    & node bootstrap.mjs
    Pop-Location
}

function Update-VendooLocal {
    Write-Header
    Write-Host '  Update durchfuehren...' -ForegroundColor White
    Write-Host ''

    # Dependencies reproduzierbar aktualisieren
    Write-Step '1/2' 'Dependencies aktualisieren...'
    if (-not (Invoke-VendooNpmInstall)) { return }

    # Neue .env Eintraege pruefen
    Write-Step '2/2' '.env pruefen...'
    $envFile = Join-Path $VendooDir '.env'
    $envExample = Join-Path $VendooDir '.env.example'
    if ((Test-Path $envFile) -and (Test-Path $envExample)) {
        $current = Get-Content $envFile -Raw
        $example = Get-Content $envExample
        $added = 0
        foreach ($line in $example) {
            if ($line -match '^([A-Z_]+)=' -and $current -notmatch "(?m)^$($Matches[1])=") {
                Add-Content $envFile "`n$line"
                $added++
            }
        }
        if ($added -gt 0) {
            Write-OK "$added neue Konfigurationseintraege hinzugefuegt"
        } else {
            Write-OK '.env ist aktuell'
        }
    }

    Write-Host ''
    Write-Host '  Update abgeschlossen!' -ForegroundColor Green
    Write-Host ''
}

function Repair-VendooLocal {
    Write-Header
    Write-Host '  Reparatur durchfuehren...' -ForegroundColor White
    Write-Host ''

    # 1. node_modules robust loeschen und reproduzierbar neu installieren
    Write-Step '1/3' 'node_modules neu installieren...'
    if (-not (Invoke-VendooNpmInstall -ForceClean)) { return }

    # 2. .env pruefen
    Write-Step '2/3' '.env pruefen...'
    $envFile = Join-Path $VendooDir '.env'
    if (-not (Test-Path $envFile)) {
        $envExample = Join-Path $VendooDir '.env.example'
        if (Test-Path $envExample) { Copy-Item $envExample $envFile }
        Write-Warn '.env fehlte und wurde neu erstellt'
    } else {
        Write-OK '.env vorhanden'
    }

    # 3. Ordner pruefen
    Write-Step '3/3' 'Verzeichnisse pruefen...'
    foreach ($d in @('uploads', 'db')) {
        $p = Join-Path $VendooDir $d
        if (-not (Test-Path $p)) { New-Item -ItemType Directory -Path $p -Force | Out-Null }
    }
    Write-OK 'Verzeichnisse OK'

    Write-Host ''
    Write-Host '  Reparatur abgeschlossen!' -ForegroundColor Green
    Write-Host ''
}

function Uninstall-VendooLocal {
    Write-Header
    Write-Host '  Vendoo deinstallieren' -ForegroundColor Red
    Write-Host ''
    Write-Warn 'Dies entfernt node_modules und optionale Daten.'
    Write-Host ''

    $confirm = Read-Host '  Wirklich deinstallieren? (ja/nein)'
    if ($confirm -ne 'ja') {
        Write-Host '  Abgebrochen.' -ForegroundColor Yellow
        return
    }

    $keepData = Read-Host '  Datenbank und Uploads behalten? (j/n)'

    Write-Step '1/2' 'node_modules entfernen...'
    $nm = Join-Path $VendooDir 'node_modules'
    if (Test-Path $nm) {
        Remove-Item $nm -Recurse -Force -Confirm:$false
        Write-OK 'node_modules entfernt'
    } else {
        Write-OK 'node_modules nicht vorhanden'
    }

    if ($keepData -ne 'j') {
        Write-Step '2/2' 'Daten entfernen...'
        $dbDir = Join-Path $VendooDir 'db'
        $uploadsDir = Join-Path $VendooDir 'uploads'
        if (Test-Path $dbDir) {
            Remove-Item (Join-Path $dbDir '*.db'), (Join-Path $dbDir '*.db-shm'), (Join-Path $dbDir '*.db-wal') -Force -ErrorAction SilentlyContinue
            Write-OK 'Datenbank entfernt'
        }
        if (Test-Path $uploadsDir) {
            Get-ChildItem $uploadsDir -File | Remove-Item -Force -Confirm:$false
            Write-OK 'Uploads entfernt'
        }
        $envFile = Join-Path $VendooDir '.env'
        if (Test-Path $envFile) {
            Remove-Item $envFile -Force -Confirm:$false
            Write-OK '.env entfernt'
        }
    } else {
        Write-OK 'Datenbank und Uploads beibehalten'
    }

    Write-Host ''
    Write-Host '  Vendoo wurde deinstalliert.' -ForegroundColor Green
    Write-Host '  Die Projektdateien bleiben erhalten.' -ForegroundColor DarkGray
    Write-Host '  Zum erneuten Installieren: setup.bat ausfuehren.' -ForegroundColor DarkGray
    Write-Host ''
}

function Install-LocalFlux {
    Write-Header
    Write-Host '  Lokales FLUX / ComfyUI installieren' -ForegroundColor White
    Write-Host ''
    $script = Get-LocalFluxScriptPath
    if (-not (Test-Path $script)) {
        Write-Err 'Installationsskript fuer lokales FLUX wurde nicht gefunden.'
        return
    }

    $stateFile = Join-Path $VendooDir 'local-ai\install-state.json'
    $existingInstall = Test-Path (Join-Path $VendooDir 'local-ai\comfyui\ComfyUI_windows_portable\ComfyUI\main.py')
    if ($existingInstall) {
        Write-OK 'ComfyUI ist bereits installiert.'
        $existingChoice = Read-Host '  [S]tatus / [U]pdate / [M]odell nachinstallieren / [N]eu installieren / [A]bbrechen'
        if ([string]::IsNullOrWhiteSpace($existingChoice)) { return }
        $existingChoice = $existingChoice.ToLowerInvariant()
        switch ($existingChoice) {
            's' { Show-LocalFluxStatus; return }
            'u' {
                & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script -UpdateOnly -StartNow
                return
            }
            'm' {
                & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script -InstallModel -StartNow
                return
            }
            'n' { }
            default { return }
        }
    }

    Write-Host '  Hinweis: Das FLUX-Modell ist sehr gross und kann lange herunterladen.' -ForegroundColor Yellow
    $modelChoice = Read-Host '  FLUX.1-schnell FP8 direkt mitinstallieren? (j/n)'
    $autoChoice = Read-Host '  Lokalen Bildserver bei Windows-Anmeldung automatisch starten? (j/n)'
    $startChoice = Read-Host '  Nach Installation sofort starten? (j/n)'

    $arguments = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $script)
    if ($modelChoice -eq 'j') { $arguments += '-InstallModel' }
    if ($autoChoice -eq 'j') { $arguments += '-EnableAutostart' }
    if ($startChoice -eq 'j') { $arguments += '-StartNow' }
    if ($existingInstall) { $arguments += '-Force' }

    Write-Step '1/1' 'Starte automatischen lokalen FLUX-Installer...'
    & powershell.exe @arguments
    if ($LASTEXITCODE -eq 0) {
        Write-OK 'Lokales FLUX / ComfyUI wurde eingerichtet.'
    } else {
        Write-Err 'Lokale FLUX-Installation ist fehlgeschlagen.'
        if (Test-Path -LiteralPath $stateFile) {
            try {
                $failedState = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json
                Write-Host "  Phase: $($failedState.phase)" -ForegroundColor Yellow
                Write-Host "  Fortschritt: $($failedState.progress)%" -ForegroundColor Yellow
                Write-Host "  Fehler: $($failedState.message)" -ForegroundColor Red
            } catch {}
            Write-Host "  Statusdatei: $stateFile" -ForegroundColor Yellow
        }
        Write-Host "  Log: $(Join-Path $VendooDir 'logs\local-flux-install.log')" -ForegroundColor Yellow
        Write-Host '  Option 7 kann nach dem Fix erneut ausgeführt werden; vorhandene vollständige Downloads werden weiterverwendet.' -ForegroundColor DarkGray
    }
    Write-Host ''
}


function Get-LocalFluxStartupRecovery {
    $stateFile = Join-Path $VendooDir 'local-ai\install-state.json'
    $extractRoot = Join-Path $VendooDir 'local-ai\extract-temp'
    $stdoutFile = Join-Path $VendooDir 'logs\local-flux-7zip-output.log'
    $stderrFile = Join-Path $VendooDir 'logs\local-flux-7zip-error.log'
    $portableRoot = Join-Path $VendooDir 'local-ai\comfyui\ComfyUI_windows_portable'
    $finalMain = Join-Path $portableRoot 'ComfyUI\main.py'
    $embeddedPython = Join-Path $portableRoot 'python_embeded\python.exe'
    $modelPath = Join-Path $portableRoot 'ComfyUI\models\checkpoints\flux1-schnell-fp8.safetensors'
    $partialModel = "$modelPath.part"

    if (-not (Test-Path -LiteralPath $stateFile)) { return $null }

    try {
        $state = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json
    } catch {
        return $null
    }

    try {
        $active = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue |
            Where-Object {
                $_.CommandLine -and
                $_.CommandLine -match [regex]::Escape($VendooDir) -and
                ($_.Name -match '^(7z|7zG|powershell)\.exe$') -and
                $_.CommandLine -match 'install-local-flux|extract-temp|ComfyUI_windows_portable|flux1-schnell'
            })
        if ($active.Count -gt 0) { return $null }
    } catch {}

    # Bekannter Slice-22-Fehlalarm nach erfolgreicher 7-Zip-Entpackung.
    if ((Test-Path -LiteralPath $extractRoot) -and -not (Test-Path -LiteralPath $finalMain)) {
        $knownFalseFailure = (
            $state.status -eq 'failed' -and
            [bool]$state.can_resume -and
            [string]$state.message -match '7-Zip Exit-Code\s*\)' -and
            [string]$state.message -match 'Everything is Ok'
        )

        if ($knownFalseFailure) {
            $stderrText = ''
            if (Test-Path -LiteralPath $stderrFile) {
                try { $stderrText = (Get-Content -LiteralPath $stderrFile -Raw -ErrorAction Stop).Trim() } catch {}
            }

            $stdoutText = ''
            if (Test-Path -LiteralPath $stdoutFile) {
                try { $stdoutText = Get-Content -LiteralPath $stdoutFile -Raw -ErrorAction Stop } catch {}
            }

            if ([string]::IsNullOrWhiteSpace($stderrText) -and $stdoutText -match '(?im)^Everything is Ok\s*$') {
                $portable = Get-ChildItem -LiteralPath $extractRoot -Directory -Recurse -ErrorAction SilentlyContinue |
                    Where-Object {
                        $_.Name -match '^ComfyUI_windows_portable' -and
                        (Test-Path -LiteralPath (Join-Path $_.FullName 'ComfyUI\main.py')) -and
                        (Test-Path -LiteralPath (Join-Path $_.FullName 'python_embeded\python.exe'))
                    } |
                    Select-Object -First 1

                if ($portable) {
                    return @{
                        Mode = 'extract'
                        State = $state
                        PortablePath = $portable.FullName
                        ExtractRoot = $extractRoot
                    }
                }
            }
        }
    }

    # Unterbrochener oder scheinbar hängender Modell-Download.
    $comfyReady = (Test-Path -LiteralPath $finalMain) -and (Test-Path -LiteralPath $embeddedPython)
    $modelReady = $false
    if (Test-Path -LiteralPath $modelPath) {
        try { $modelReady = (Get-Item -LiteralPath $modelPath).Length -ge 1GB } catch {}
    }

    $partialBytes = 0L
    if (Test-Path -LiteralPath $partialModel) {
        try { $partialBytes = [long](Get-Item -LiteralPath $partialModel).Length } catch {}
    }

    $downloadState = ([string]$state.phase -eq 'download-model') -and ([string]$state.status -in @('running','failed'))
    if ($comfyReady -and -not $modelReady -and ($downloadState -or $partialBytes -gt 0)) {
        return @{
            Mode = 'model-download'
            State = $state
            PartialPath = $partialModel
            PartialBytes = $partialBytes
        }
    }

    return $null
}

function Invoke-LocalFluxStartupRecovery {
    $recovery = Get-LocalFluxStartupRecovery
    if (-not $recovery) { return }

    Write-Header
    Write-Host '  Automatische FLUX-Wiederaufnahme' -ForegroundColor White
    Write-Host '  ────────────────────────────────────' -ForegroundColor DarkGray
    Write-Host ''

    if ($recovery.Mode -eq 'extract') {
        Write-OK 'Das ComfyUI-Archiv wurde bereits vollständig und fehlerfrei entpackt.'
        Write-Warn 'Der frühere Installer hat den erfolgreichen 7-Zip-Lauf falsch bewertet.'
        Write-Host '  Der vorhandene Entpackstand wird übernommen; nichts wird neu entpackt.' -ForegroundColor Green
    } elseif ($recovery.Mode -eq 'model-download') {
        $partialMb = [math]::Round(([long]$recovery.PartialBytes) / 1MB, 1)
        Write-Warn 'Ein unterbrochener oder nicht sichtbar fortschreitender FLUX-Download wurde erkannt.'
        if ($partialMb -gt 0) {
            Write-OK "Bereits geladene Daten bleiben erhalten: $partialMb MB."
        }
        Write-Host '  Der Download wird mit sichtbarem Fortschritt automatisch fortgesetzt.' -ForegroundColor Green
        Write-Host '  Anzeige: Prozent · geladene MB · Gesamtgröße · Geschwindigkeit · Restzeit' -ForegroundColor DarkGray
    }

    Write-Host ''
    Write-Step 'AUTO' 'Fortsetzung startet automatisch...'
    Start-Sleep -Seconds 2

    $script = Get-LocalFluxScriptPath
    if (-not (Test-Path -LiteralPath $script)) {
        Write-Err 'FLUX-Installationsskript fehlt. Automatische Fortsetzung ist nicht möglich.'
        return
    }

    $logDir = Join-Path $VendooDir 'logs'
    if (-not (Test-Path -LiteralPath $logDir)) {
        New-Item -ItemType Directory -Path $logDir -Force | Out-Null
    }
    $selfHealLog = Join-Path $logDir 'setup-self-heal.log'
    "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [INFO] Automatische FLUX-Wiederaufnahme gestartet. Modus: $($recovery.Mode)." |
        Add-Content -LiteralPath $selfHealLog -Encoding utf8

    & powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File $script -InstallModel -StartNow
    $exitCode = $LASTEXITCODE

    if ($exitCode -eq 0) {
        "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [OK] Automatische FLUX-Wiederaufnahme erfolgreich abgeschlossen." |
            Add-Content -LiteralPath $selfHealLog -Encoding utf8
        Write-OK 'ComfyUI und FLUX wurden automatisch fertig eingerichtet.'
        Write-Host ''
        Show-LocalFluxStatus
    } else {
        "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [ERROR] Automatische FLUX-Wiederaufnahme fehlgeschlagen. Exit-Code $exitCode." |
            Add-Content -LiteralPath $selfHealLog -Encoding utf8
        Write-Err "Automatische Fortsetzung ist fehlgeschlagen (Exit-Code $exitCode)."
        Write-Host "  Diagnose: $selfHealLog" -ForegroundColor Yellow
        Write-Host "  Installer-Log: $(Join-Path $VendooDir 'logs\local-flux-install.log')" -ForegroundColor Yellow
    }

    Write-Host ''
}

function Start-LocalFlux {
    $script = Join-Path $VendooDir 'local-ai\start-local-flux.ps1'
    if (-not (Test-Path $script)) {
        Write-Warn 'Startskript fehlt. Bitte lokales FLUX zuerst installieren.'
        return
    }
    & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script
    Start-Sleep -Seconds 3
    Show-LocalFluxStatus
}

function Stop-LocalFlux {
    $script = Join-Path $VendooDir 'local-ai\stop-local-flux.ps1'
    if (-not (Test-Path $script)) {
        Write-Warn 'Stoppskript fehlt.'
        return
    }
    & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script
    Write-OK 'Lokaler FLUX / ComfyUI-Prozess wurde beendet.'
}

function Show-LocalFluxStatus {
    Write-Header
    Write-Host '  Lokales FLUX / ComfyUI Status' -ForegroundColor White
    Write-Host '  ────────────────────────────────────' -ForegroundColor DarkGray

    $localRoot = Join-Path $VendooDir 'local-ai'
    $stateFile = Join-Path $localRoot 'install-state.json'
    $logFile = Join-Path $VendooDir 'logs\local-flux-install.log'
    $portableRoot = Join-Path $localRoot 'comfyui\ComfyUI_windows_portable'
    $pythonPath = Join-Path $portableRoot 'python_embeded\python.exe'
    $comfyMain = Join-Path $portableRoot 'ComfyUI\main.py'
    $modelPath = Join-Path $portableRoot 'ComfyUI\models\checkpoints\flux1-schnell-fp8.safetensors'
    $workflowPath = Join-Path $localRoot 'workflows\vendoo_flux_schnell_api.json'
    $startScript = Join-Path $localRoot 'start-local-flux.ps1'
    $stopScript = Join-Path $localRoot 'stop-local-flux.ps1'

    $state = $null
    if (Test-Path -LiteralPath $stateFile) {
        try { $state = Get-Content -LiteralPath $stateFile -Raw | ConvertFrom-Json } catch {}
    }

    if ($state) {
        $statusColor = 'Yellow'
        if ($state.status -eq 'completed') { $statusColor = 'Green' }
        elseif ($state.status -eq 'failed') { $statusColor = 'Red' }
        $heartbeatAge = $null
        try {
            $heartbeatTime = [DateTime]::Parse([string]$(if ($state.heartbeat_at) { $state.heartbeat_at } else { $state.updated_at }))
            $heartbeatAge = [math]::Round(((Get-Date) - $heartbeatTime).TotalSeconds)
        } catch {}
        $staleThreshold = if ($state.phase -eq 'extract-comfyui') { 20 } else { 120 }
        if ($state.status -eq 'running' -and $heartbeatAge -ne $null -and $heartbeatAge -gt $staleThreshold) {
            $statusColor = 'Red'
            Write-Host '  Status:      HÄNGT / keine Aktualisierung' -ForegroundColor $statusColor
        } else {
            Write-Host "  Status:      $($state.status)" -ForegroundColor $statusColor
        }
        Write-Host "  Phase:       $($state.phase)" -ForegroundColor DarkGray
        Write-Host "  Fortschritt: $($state.progress)%" -ForegroundColor DarkGray
        Write-Host "  Meldung:     $($state.message)" -ForegroundColor DarkGray
        if ($state.elapsed_seconds -ne $null) { Write-Host "  Laufzeit:    $($state.elapsed_seconds) Sekunden" -ForegroundColor DarkGray }
        if ($state.extract_files -ne $null) { Write-Host "  Entpackt:    $($state.extract_files) Dateien / $($state.extract_mb) MB" -ForegroundColor DarkGray }
        if ($heartbeatAge -ne $null) { Write-Host "  Heartbeat:   vor $heartbeatAge Sekunden" -ForegroundColor DarkGray }
        Write-Host "  Aktualisiert:$($state.updated_at)" -ForegroundColor DarkGray
    } else {
        Write-Warn 'Noch keine Installationsstatusdatei vorhanden.'
    }

    Write-Host ''
    if (Test-Path -LiteralPath "$env:ProgramFiles\7-Zip\7z.exe") { Write-OK '7-Zip       Vorhanden' }
    else { Write-Warn '7-Zip       Nicht im Standardpfad gefunden' }
    if (Test-Path -LiteralPath $pythonPath) { Write-OK 'Python       Vorhanden' }
    else { Write-Warn 'Python       Fehlt' }
    if (Test-Path -LiteralPath $comfyMain) { Write-OK 'ComfyUI      Installiert' }
    else { Write-Warn 'ComfyUI      Noch nicht vollständig installiert' }
    if (Test-Path -LiteralPath $workflowPath) { Write-OK 'Workflow     Vorhanden' }
    else { Write-Warn 'Workflow     Fehlt' }
    if (Test-Path -LiteralPath $modelPath) {
        $modelSize = [math]::Round((Get-Item -LiteralPath $modelPath).Length / 1GB, 2)
        Write-OK "FLUX-Modell  Vorhanden ($modelSize GB)"
    } else { Write-Warn 'FLUX-Modell  Fehlt' }
    if (Test-Path -LiteralPath $startScript) { Write-OK 'Startskript  Vorhanden' }
    else { Write-Warn 'Startskript  Fehlt' }
    if (Test-Path -LiteralPath $stopScript) { Write-OK 'Stoppskript  Vorhanden' }
    else { Write-Warn 'Stoppskript  Fehlt' }

    $envFile = Join-Path $VendooDir '.env'
    $url = 'http://127.0.0.1:8188'
    if (Test-Path -LiteralPath $envFile) {
        $envContent = Get-Content -LiteralPath $envFile -Raw
        if ($envContent -match '(?m)^LOCAL_IMAGE_URL=(.+)$') { $url = $Matches[1].Trim() }
    }
    $serverReachable = $false
    try {
        $resp = Invoke-WebRequest -UseBasicParsing -Uri ($url.TrimEnd('/') + '/system_stats') -TimeoutSec 5
        if ($resp.StatusCode -ge 200 -and $resp.StatusCode -lt 300) {
            $serverReachable = $true
            Write-OK 'Server       Erreichbar'
        } else { Write-Warn "Server       HTTP $($resp.StatusCode)" }
    } catch {
        Write-Warn 'Server       Nicht erreichbar'
    }

    if (-not $serverReachable) {
        $runtimeStateFile = Join-Path $VendooDir 'logs\local-flux-runtime-state.json'
        if (Test-Path -LiteralPath $runtimeStateFile) {
            try {
                $runtimeState = Get-Content -LiteralPath $runtimeStateFile -Raw | ConvertFrom-Json
                Write-Host "  Runtime:     $($runtimeState.status)" -ForegroundColor $(if ($runtimeState.status -eq 'failed') { 'Red' } elseif ($runtimeState.status -eq 'starting') { 'Yellow' } else { 'DarkGray' })
                Write-Host "  Startinfo:   $($runtimeState.message)" -ForegroundColor DarkGray
                if ($runtimeState.error) { Write-Host "  Fehler:      $($runtimeState.error)" -ForegroundColor Red }
            } catch {}
        }
        foreach ($runtimeLog in @(
            (Join-Path $VendooDir 'logs\local-flux-runtime-error.log'),
            (Join-Path $VendooDir 'logs\local-flux-runtime.log')
        )) {
            if (Test-Path -LiteralPath $runtimeLog) {
                $runtimeLines = @(Get-Content -LiteralPath $runtimeLog -Tail 8 -ErrorAction SilentlyContinue)
                if ($runtimeLines.Count -gt 0) {
                    Write-Host "  Runtime-Log: $runtimeLog" -ForegroundColor White
                    $runtimeLines | ForEach-Object {
                        $clean = [regex]::Replace([string]$_, ([string][char]27) + '\[[0-?]*[ -/]*[@-~]', '')
                        Write-Host "  $clean" -ForegroundColor DarkGray
                    }
                }
            }
        }
    }

    if (Test-Path -LiteralPath $logFile) {
        Write-Host ''
        Write-Host '  Letzte Installationsmeldungen:' -ForegroundColor White
        Get-Content -LiteralPath $logFile -Tail 12 | ForEach-Object { Write-Host "  $_" -ForegroundColor DarkGray }
    }
    $sevenZipError = Join-Path $VendooDir 'logs\local-flux-7zip-error.log'
    if (Test-Path -LiteralPath $sevenZipError) {
        $sevenZipLines = @(Get-Content -LiteralPath $sevenZipError -Tail 8 -ErrorAction SilentlyContinue)
        if ($sevenZipLines.Count -gt 0) {
            Write-Host ''
            Write-Host '  Letzte 7-Zip-Fehlerausgabe:' -ForegroundColor White
            $sevenZipLines | ForEach-Object { Write-Host "  $_" -ForegroundColor Yellow }
        }
    }
    Write-Host ''
}


function Show-StatusLocal {
    Write-Header
    Write-Host '  Systemstatus' -ForegroundColor White
    Write-Host '  ────────────────────────────────────' -ForegroundColor DarkGray

    $node = Test-NodeInstalled
    if ($node.Installed) { Write-OK "Node.js      $($node.Version)" }
    else { Write-Err 'Node.js      Nicht installiert' }

    if (Test-NpmInstalled) {
        $npmV = & npm --version 2>$null
        Write-OK "npm          v$npmV"
    } else { Write-Err 'npm          Nicht gefunden' }

    $nm = Join-Path $VendooDir 'node_modules'
    if (Test-Path $nm) { Write-OK 'Dependencies Installiert' }
    else { Write-Warn 'Dependencies Nicht installiert' }

    $envFile = Join-Path $VendooDir '.env'
    if (Test-Path $envFile) { Write-OK '.env         Vorhanden' }
    else { Write-Warn '.env         Fehlt' }

    $dbFile = Join-Path $VendooDir 'db' 'vendoo.db'
    if (Test-Path $dbFile) {
        $size = (Get-Item $dbFile).Length
        $sizeStr = if ($size -gt 1MB) { "{0:N1} MB" -f ($size / 1MB) } else { "{0:N0} KB" -f ($size / 1KB) }
        Write-OK "Datenbank    $sizeStr"
    } else { Write-Warn 'Datenbank    Noch nicht erstellt (wird beim ersten Start angelegt)' }

    $uploadsDir = Join-Path $VendooDir 'uploads'
    if (Test-Path $uploadsDir) {
        $fileCount = (Get-ChildItem $uploadsDir -File -ErrorAction SilentlyContinue).Count
        Write-OK "Uploads      $fileCount Dateien"
    }

    Write-Host ''
    Write-Host "  Port:        $Port" -ForegroundColor DarkGray
    Write-Host "  Verzeichnis: $VendooDir" -ForegroundColor DarkGray
    Write-Host ''
}


function Show-Status {
    if ((Get-VendooInstallMode) -eq 'docker') {
        Write-Header
        Write-Host '  Docker-Systemstatus' -ForegroundColor White
        Write-Host ''
        $docker = Test-DockerAvailable
        if (-not $docker.Available) { Write-Err $docker.Reason; return }
        Write-OK "Docker Engine $($docker.Version)"
        Invoke-VendooDockerCompose @('ps') | Out-Null
        try {
            $response = Invoke-WebRequest -Uri "http://127.0.0.1:$Port/readyz" -UseBasicParsing -TimeoutSec 5
            if ($response.StatusCode -eq 200) { Write-OK 'Readiness    Bereit' }
            else { Write-Warn "Readiness    HTTP $($response.StatusCode)" }
        } catch { Write-Warn 'Readiness    Nicht erreichbar' }
        Write-Host ''
        Write-Host "  Installationsmodus: Docker Desktop" -ForegroundColor DarkGray
        return
    }
    Show-StatusLocal
}

function Uninstall-Vendoo {
    if ((Get-VendooInstallMode) -eq 'docker') {
        Write-Header
        Write-Host '  Vendoo-Dockerbereitstellung entfernen' -ForegroundColor Red
        Write-Host ''
        Write-Warn 'Container werden gestoppt. Persistente Volumes bleiben standardmäßig erhalten.'
        $confirm = Read-Host '  Container wirklich entfernen? (ja/nein)'
        if ($confirm -ne 'ja') { Write-Host '  Abgebrochen.' -ForegroundColor Yellow; return }
        $docker = Test-DockerAvailable
        if (-not $docker.Available) { Write-Err $docker.Reason; return }
        Invoke-VendooDockerCompose @('down') | Out-Null
        Write-OK 'Container und Netzwerk entfernt. Datenvolumes wurden beibehalten.'
        $deleteVolumes = Read-Host '  Persistente Docker-Volumes ebenfalls endgültig löschen? Tippe dafür exakt DATEN LOESCHEN'
        if ($deleteVolumes -eq 'DATEN LOESCHEN') {
            Invoke-VendooDockerCompose @('down', '-v') | Out-Null
            Write-Warn 'Persistente Docker-Volumes wurden gelöscht.'
        }
        return
    }
    Uninstall-VendooLocal
}


# ── Vendoo Operations Center 1.30.0 ──
function Get-VendooOperationsDir { return (Join-Path $VendooDir 'operations') }
function Get-VendooPendingOperationFile { return (Join-Path (Get-VendooOperationsDir) 'pending-operation.json') }
function Get-VendooLastOperationFile { return (Join-Path (Get-VendooOperationsDir) 'last-operation.json') }

function Test-VendooServerStopped {
    try {
        $listeners = @(Get-NetTCPConnection -LocalPort $Port -State Listen -ErrorAction SilentlyContinue)
        if ($listeners.Count -gt 0) {
            Write-Err "Vendoo lauscht noch auf Port $Port. Bitte das Serverfenster zuerst schliessen."
            return $false
        }
    } catch {
        try {
            $client = New-Object System.Net.Sockets.TcpClient
            $async = $client.BeginConnect('127.0.0.1', $Port, $null, $null)
            if ($async.AsyncWaitHandle.WaitOne(350, $false) -and $client.Connected) {
                $client.Close()
                Write-Err "Vendoo ist noch aktiv. Bitte das Serverfenster zuerst schliessen."
                return $false
            }
            $client.Close()
        } catch {}
    }
    return $true
}

function Copy-VendooCodeSnapshot([string]$Target) {
    New-Item -ItemType Directory -Path $Target -Force | Out-Null
    $rootFiles = @('.env.example','.gitignore','.npmrc','CHANGELOG.md','CLAUDE.md','FEATURES.md','README.md','SECURITY.md','THIRD_PARTY_NOTICES.md','package.json','package-lock.json','server.mjs','setup.bat','setup.ps1','update-manifest.json')
    foreach ($name in $rootFiles) {
        $source = Join-Path $VendooDir $name
        if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -Force }
    }
    foreach ($name in @('public','lib','platforms','scripts','tools','docs','extensions','extension','MockupDesign')) {
        $source = Join-Path $VendooDir $name
        if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -Recurse -Force }
    }
    $localAiTarget = Join-Path $Target 'local-ai'
    New-Item -ItemType Directory -Path $localAiTarget -Force | Out-Null
    foreach ($name in @('start-local-flux.ps1','stop-local-flux.ps1')) {
        $source = Join-Path $VendooDir ('local-ai\' + $name)
        if (Test-Path -LiteralPath $source) { Copy-Item -LiteralPath $source -Destination (Join-Path $localAiTarget $name) -Force }
    }
    $workflowSource = Join-Path $VendooDir 'local-ai\workflows'
    if (Test-Path -LiteralPath $workflowSource) { Copy-Item -LiteralPath $workflowSource -Destination (Join-Path $localAiTarget 'workflows') -Recurse -Force }
    $schemaSource = Join-Path $VendooDir 'db\schema.sql'
    if (Test-Path -LiteralPath $schemaSource) {
        New-Item -ItemType Directory -Path (Join-Path $Target 'db') -Force | Out-Null
        Copy-Item -LiteralPath $schemaSource -Destination (Join-Path $Target 'db\schema.sql') -Force
    }
}

function Copy-VendooRuntimeSnapshot([string]$Target) {
    New-Item -ItemType Directory -Path $Target -Force | Out-Null
    $dbFile = Join-Path $VendooDir 'db\vendoo.db'
    if (Test-Path -LiteralPath $dbFile) {
        New-Item -ItemType Directory -Path (Join-Path $Target 'db') -Force | Out-Null
        Copy-Item -LiteralPath $dbFile -Destination (Join-Path $Target 'db\vendoo.db') -Force
    }
    foreach ($name in @('uploads','config','modules')) {
        $source = Join-Path $VendooDir $name
        if (Test-Path -LiteralPath $source) {
            Copy-Item -LiteralPath $source -Destination (Join-Path $Target $name) -Recurse -Force
        }
    }
    $envFile = Join-Path $VendooDir '.env'
    if (Test-Path -LiteralPath $envFile) { Copy-Item -LiteralPath $envFile -Destination (Join-Path $Target '.env') -Force }
}

function Copy-VendooTree([string]$Source, [string]$Target) {
    if (-not (Test-Path -LiteralPath $Source)) { throw "Quellverzeichnis fehlt: $Source" }
    New-Item -ItemType Directory -Path $Target -Force | Out-Null
    $null = & robocopy.exe $Source $Target /E /COPY:DAT /DCOPY:DAT /R:2 /W:1 /NFL /NDL /NJH /NJS /NP
    $code = $LASTEXITCODE
    if ($code -gt 7) { throw "Robocopy fehlgeschlagen (Exit-Code $code): $Source -> $Target" }
}

function Restore-VendooCodeSnapshot([string]$Source) {
    if (-not (Test-Path -LiteralPath $Source)) { throw "Rollback-Snapshot fehlt: $Source" }
    Copy-VendooTree -Source $Source -Target $VendooDir
}

function Restore-VendooRuntimeSnapshot([string]$Source) {
    if (-not (Test-Path -LiteralPath $Source)) { return }
    $dbSource = Join-Path $Source 'db\vendoo.db'
    if (Test-Path -LiteralPath $dbSource) {
        New-Item -ItemType Directory -Path (Join-Path $VendooDir 'db') -Force | Out-Null
        Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-wal') -Force -ErrorAction SilentlyContinue
        Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-shm') -Force -ErrorAction SilentlyContinue
        Copy-Item -LiteralPath $dbSource -Destination (Join-Path $VendooDir 'db\vendoo.db') -Force
    }
    foreach ($name in @('uploads','config','modules')) {
        $sourcePath = Join-Path $Source $name
        if (Test-Path -LiteralPath $sourcePath) {
            $targetPath = Join-Path $VendooDir $name
            if (Test-Path -LiteralPath $targetPath) { Remove-Item -LiteralPath $targetPath -Recurse -Force }
            Copy-Item -LiteralPath $sourcePath -Destination $targetPath -Recurse -Force
        }
    }
    $envSource = Join-Path $Source '.env'
    if (Test-Path -LiteralPath $envSource) { Copy-Item -LiteralPath $envSource -Destination (Join-Path $VendooDir '.env') -Force }
}

function Find-VendooUpdateRoot([string]$ExtractRoot) {
    $packages = @(Get-ChildItem -LiteralPath $ExtractRoot -Filter 'package.json' -File -Recurse -ErrorAction SilentlyContinue)
    foreach ($package in $packages) {
        try {
            $json = Get-Content -LiteralPath $package.FullName -Raw | ConvertFrom-Json
            if ($json.name -eq 'vendoo' -and (Test-Path -LiteralPath (Join-Path $package.Directory.FullName 'server.mjs'))) { return $package.Directory.FullName }
        } catch {}
    }
    return $null
}

function Apply-VendooPendingOperation {
    Write-Header
    Write-Host '  Vorbereitete Systemoperation anwenden' -ForegroundColor White
    Write-Host ''
    $pendingFile = Get-VendooPendingOperationFile
    if (-not (Test-Path -LiteralPath $pendingFile)) { Write-Warn 'Keine vorbereitete Operation vorhanden.'; return }
    if (-not (Test-VendooServerStopped)) { return }

    try { $operation = Get-Content -LiteralPath $pendingFile -Raw | ConvertFrom-Json }
    catch { Write-Err 'pending-operation.json ist ungueltig.'; return }
    if (-not $operation.verified) { Write-Err 'Die Operation wurde nicht als verifiziert markiert.'; return }
    if (-not (Test-Path -LiteralPath $operation.archive_path)) { Write-Err "Archiv fehlt: $($operation.archive_path)"; return }

    Write-Host "  Typ:       $($operation.type)" -ForegroundColor DarkGray
    if ($operation.version_to) { Write-Host "  Version:   $($operation.version_from) -> $($operation.version_to)" -ForegroundColor DarkGray }
    Write-Host "  Archiv:    $($operation.archive_path)" -ForegroundColor DarkGray
    Write-Host ''
    $confirm = Read-Host '  Jetzt anwenden? (ja/nein)'
    if ($confirm -ne 'ja') { Write-Warn 'Operation abgebrochen.'; return }

    $stamp = Get-Date -Format 'yyyyMMdd-HHmmss'
    $rollbackRoot = Join-Path (Get-VendooOperationsDir) ("rollback\" + $stamp)
    $codeRollback = Join-Path $rollbackRoot 'code'
    $runtimeRollback = Join-Path $rollbackRoot 'runtime'
    $extractRoot = Join-Path (Get-VendooOperationsDir) ("apply-" + $stamp)
    New-Item -ItemType Directory -Path $extractRoot -Force | Out-Null

    try {
        Write-Step '1/5' 'Rollback-Snapshot erstellen...'
        Copy-VendooCodeSnapshot -Target $codeRollback
        Copy-VendooRuntimeSnapshot -Target $runtimeRollback
        Write-OK 'Rollback-Snapshot erstellt'

        Write-Step '2/5' 'Archiv entpacken...'
        Expand-Archive -LiteralPath $operation.archive_path -DestinationPath $extractRoot -Force
        Write-OK 'Archiv entpackt'

        if ($operation.type -eq 'update') {
            Write-Step '3/5' 'Vendoo-Programmdateien aktualisieren...'
            $sourceRoot = Find-VendooUpdateRoot -ExtractRoot $extractRoot
            if (-not $sourceRoot) { throw 'Vendoo-Quellordner wurde im Update nicht gefunden.' }
            Copy-VendooTree -Source $sourceRoot -Target $VendooDir
            Write-OK 'Programmdateien aktualisiert'
            Write-Step '4/5' 'Dependencies und Konfiguration aktualisieren...'
            Update-Vendoo
            & node --check (Join-Path $VendooDir 'server.mjs')
            if ($LASTEXITCODE -ne 0) { throw 'server.mjs ist nach dem Update syntaktisch ungueltig.' }
            if (-not (Test-Path -LiteralPath (Join-Path $VendooDir 'node_modules'))) { throw 'node_modules fehlt nach dem Update.' }
        }
        elseif ($operation.type -eq 'restore') {
            Write-Step '3/5' 'Datenbank wiederherstellen...'
            $dbSource = Join-Path $extractRoot 'db\vendoo.db'
            if (-not (Test-Path -LiteralPath $dbSource)) { throw 'Datenbank fehlt im Backup.' }
            New-Item -ItemType Directory -Path (Join-Path $VendooDir 'db') -Force | Out-Null
            Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-wal') -Force -ErrorAction SilentlyContinue
            Remove-Item -LiteralPath (Join-Path $VendooDir 'db\vendoo.db-shm') -Force -ErrorAction SilentlyContinue
            Copy-Item -LiteralPath $dbSource -Destination (Join-Path $VendooDir 'db\vendoo.db') -Force
            Write-OK 'Datenbank wiederhergestellt'
            Write-Step '4/5' 'Uploads und Konfiguration wiederherstellen...'
            $backupManifest = $null
            $manifestPath = Join-Path $extractRoot 'backup-manifest.json'
            if (Test-Path -LiteralPath $manifestPath) {
                try { $backupManifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json } catch {}
            }
            $uploadsSource = Join-Path $extractRoot 'uploads'
            if ($backupManifest -and $backupManifest.include_uploads) {
                $uploadsTarget = Join-Path $VendooDir 'uploads'
                if (Test-Path -LiteralPath $uploadsTarget) { Remove-Item -LiteralPath $uploadsTarget -Recurse -Force }
                if (Test-Path -LiteralPath $uploadsSource) { Copy-Item -LiteralPath $uploadsSource -Destination $uploadsTarget -Recurse -Force }
                else { New-Item -ItemType Directory -Path $uploadsTarget -Force | Out-Null }
            }
            $configSource = Join-Path $extractRoot 'config'
            if (Test-Path -LiteralPath $configSource) {
                $configTarget = Join-Path $VendooDir 'config'
                if (Test-Path -LiteralPath $configTarget) { Remove-Item -LiteralPath $configTarget -Recurse -Force }
                Copy-Item -LiteralPath $configSource -Destination $configTarget -Recurse -Force
            }
            $modulesSource = Join-Path $extractRoot 'modules'
            if (Test-Path -LiteralPath $modulesSource) {
                $modulesTarget = Join-Path $VendooDir 'modules'
                if (Test-Path -LiteralPath $modulesTarget) { Remove-Item -LiteralPath $modulesTarget -Recurse -Force }
                Copy-Item -LiteralPath $modulesSource -Destination $modulesTarget -Recurse -Force
            }
            $moduleStateSource = Join-Path $extractRoot 'module-state\modules-state.json'
            if (Test-Path -LiteralPath $moduleStateSource) {
                $configTarget = Join-Path $VendooDir 'config'
                New-Item -ItemType Directory -Path $configTarget -Force | Out-Null
                Copy-Item -LiteralPath $moduleStateSource -Destination (Join-Path $configTarget 'modules-state.json') -Force
            }
            $envSource = Join-Path $extractRoot 'config\.env'
            if (Test-Path -LiteralPath $envSource) { Copy-Item -LiteralPath $envSource -Destination (Join-Path $VendooDir '.env') -Force }
            Write-OK 'Uploads, Konfiguration, Modulpakete und Modulzustand wiederhergestellt'
        }
        else { throw "Unbekannter Operationstyp: $($operation.type)" }

        Write-Step '5/5' 'Operation abschliessen...'
        $last = [ordered]@{
            id = [string]$operation.id
            type = [string]$operation.type
            status = 'applied'
            version_from = [string]$operation.version_from
            version_to = [string]$operation.version_to
            rollback_path = $rollbackRoot
            applied_at = (Get-Date).ToString('o')
        }
        $last | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Get-VendooLastOperationFile) -Encoding UTF8
        Remove-Item -LiteralPath $pendingFile -Force
        Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
        Write-OK 'Operation erfolgreich abgeschlossen'
        Write-Host ''
        Write-Host '  Starte Vendoo jetzt ueber Menuepunkt 2 und pruefe Admin > System.' -ForegroundColor Green
    } catch {
        Write-Err ("Operation fehlgeschlagen: " + $_.Exception.Message)
        Write-Warn 'Der vorherige Stand wird aus dem gerade erstellten Snapshot wiederhergestellt.'
        try {
            Restore-VendooCodeSnapshot -Source $codeRollback
            Restore-VendooRuntimeSnapshot -Source $runtimeRollback
            Write-OK 'Vorheriger Stand wiederhergestellt'
        } catch { Write-Err ("Automatischer Rollback fehlgeschlagen: " + $_.Exception.Message) }
    } finally {
        Remove-Item -LiteralPath $extractRoot -Recurse -Force -ErrorAction SilentlyContinue
    }
}

function Invoke-VendooRollback {
    Write-Header
    Write-Host '  Letzte Systemoperation zuruecksetzen' -ForegroundColor White
    Write-Host ''
    if (-not (Test-VendooServerStopped)) { return }
    $lastFile = Get-VendooLastOperationFile
    if (-not (Test-Path -LiteralPath $lastFile)) { Write-Warn 'Kein Rollback-Snapshot vorhanden.'; return }
    try { $last = Get-Content -LiteralPath $lastFile -Raw | ConvertFrom-Json }
    catch { Write-Err 'last-operation.json ist ungueltig.'; return }
    if (-not $last.rollback_path -or -not (Test-Path -LiteralPath $last.rollback_path)) { Write-Err 'Rollback-Verzeichnis wurde nicht gefunden.'; return }
    Write-Host "  Letzte Operation: $($last.type)" -ForegroundColor DarkGray
    Write-Host "  Zeitpunkt:         $($last.applied_at)" -ForegroundColor DarkGray
    Write-Host ''
    if ((Read-Host '  Vorherigen Stand wiederherstellen? (ja/nein)') -ne 'ja') { Write-Warn 'Rollback abgebrochen.'; return }
    try {
        Restore-VendooCodeSnapshot -Source (Join-Path $last.rollback_path 'code')
        Restore-VendooRuntimeSnapshot -Source (Join-Path $last.rollback_path 'runtime')
        $last.status = 'rolled_back'
        $last | Add-Member -NotePropertyName rolled_back_at -NotePropertyValue (Get-Date).ToString('o') -Force
        $last | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $lastFile -Encoding UTF8
        Write-OK 'Vorheriger Vendoo-Stand wurde wiederhergestellt.'
        Write-Warn 'Fuehre bei Bedarf danach Menuepunkt 3 – Update aus.'
    } catch { Write-Err ("Rollback fehlgeschlagen: " + $_.Exception.Message) }
}

# ── Main Menu ──
function Show-Menu {
    while ($true) {
        Write-Header
        Write-Host '  Waehle eine Option:' -ForegroundColor White
        Write-Host ''
        Write-Host '    1  Installieren / Installationsziel wählen' -ForegroundColor Cyan
        Write-Host '    2  Starten' -ForegroundColor Cyan
        Write-Host '    3  Update' -ForegroundColor Cyan
        Write-Host '    4  Reparieren' -ForegroundColor Cyan
        Write-Host '    5  Deinstallieren' -ForegroundColor Cyan
        Write-Host '    6  Status anzeigen' -ForegroundColor Cyan
        Write-Host '   13  Konfiguration ändern (Assistent)' -ForegroundColor Cyan
        Write-Host '    7  Lokales FLUX / ComfyUI installieren oder aktualisieren' -ForegroundColor Cyan
        Write-Host '    8  Lokales FLUX / ComfyUI Status' -ForegroundColor Cyan
        Write-Host '    9  Lokales FLUX / ComfyUI starten' -ForegroundColor Cyan
        Write-Host '   10  Lokales FLUX / ComfyUI stoppen' -ForegroundColor Cyan
        Write-Host '   11  Vorbereitete Operation anwenden' -ForegroundColor Cyan
        Write-Host '   12  Letzte Operation zuruecksetzen' -ForegroundColor Cyan
        Write-Host '    0  Beenden' -ForegroundColor DarkGray
        Write-Host ''
        $choice = Read-Host '  Auswahl'

        switch ($choice) {
            '1' { Install-Vendoo }
            '2' { Start-Vendoo }
            '3' { Update-Vendoo }
            '4' { Repair-Vendoo }
            '5' { Uninstall-Vendoo }
            '6' { Show-Status }
            '7' { Install-LocalFlux }
            '8' { Show-LocalFluxStatus }
            '9' { Start-LocalFlux }
            '10' { Stop-LocalFlux }
            '11' { Apply-VendooPendingOperation }
            '12' { Invoke-VendooRollback }
            '13' { Configure-VendooConsole }
            '0' { Write-Host '  Auf Wiedersehen!' -ForegroundColor Green; return }
            default { Write-Warn 'Ungueltige Auswahl' }
        }
        Write-Host ''
        Read-Host '  Druecke Enter um fortzufahren'
    }
}

Remove-LegacyTryOnComponents

if (-not (Test-VendooPowerShellFiles)) {
    Write-Host ''
    Write-Err 'Vendoo Setup wurde wegen fehlerhafter oder fehlender Pflichtskripte beendet.'
    exit 12
}

# Automatische Wiederaufnahme des exakt bestätigten Slice-22-Fehlzustands.
# Direkte CLI-Kommandos bleiben davon unberührt.
if ($args.Count -eq 0) {
    Invoke-LocalFluxStartupRecovery
}

# Handle CLI arguments for direct commands
if ($args.Count -gt 0) {
    switch ($args[0]) {
        'install'   { Install-Vendoo }
        'install-local' { $script:AutoStartAfterInstall = $true; Set-VendooInstallMode 'windows-local'; Install-VendooLocal }
        'install-docker' { Install-VendooDocker }
        'configure' { Configure-VendooConsole }
        'menu' { Show-Menu }
        'start'     { Start-Vendoo }
        'update'    { Update-Vendoo }
        'repair'    { Repair-Vendoo }
        'uninstall' { Uninstall-Vendoo }
        'status'    { Show-Status }
        'install-local-flux' { Install-LocalFlux }
        'flux-status' { Show-LocalFluxStatus }
        'flux-start' { Start-LocalFlux }
        'flux-stop' { Stop-LocalFlux }
        'apply-operation' { Apply-VendooPendingOperation }
        'rollback' { Invoke-VendooRollback }
        default     { Show-Menu }
    }
} else {
    Show-Menu
}
