[CmdletBinding()]
param()

Set-StrictMode -Version 2.0
$ErrorActionPreference = "Stop"

Add-Type -AssemblyName PresentationFramework
Add-Type -AssemblyName PresentationCore
Add-Type -AssemblyName WindowsBase
Add-Type -AssemblyName System.Xaml
Add-Type -AssemblyName System.IO.Compression.FileSystem

$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$ClientRoot = Split-Path -Parent $ScriptDir
$ConfigPath = Join-Path $ScriptDir "config.json"
$LocalFeedPath = Join-Path $ScriptDir "local-feed.json"
$XamlPath = Join-Path $ScriptDir "Launcher.xaml"
$BackgroundPath = Join-Path $ScriptDir "assets\launcher-background.png"
$LogDir = Join-Path $ScriptDir "logs"
$BackupDir = Join-Path $ScriptDir "backups"
$DownloadDir = Join-Path $ScriptDir "downloads"
$UserDataDir = Join-Path $env:APPDATA "VendelsePoortTBCLauncher"
$UserSettingsPath = Join-Path $UserDataDir "settings.json"
$LogPath = Join-Path $LogDir "launcher.log"

foreach ($dir in @($LogDir, $BackupDir, $DownloadDir, $UserDataDir)) {
    if (-not (Test-Path -LiteralPath $dir)) {
        New-Item -ItemType Directory -Path $dir -Force | Out-Null
    }
}

function Write-LauncherLog {
    param([string]$Message)
    $line = "{0}  {1}" -f (Get-Date -Format "yyyy-MM-dd HH:mm:ss"), $Message
    Add-Content -LiteralPath $LogPath -Value $line -Encoding UTF8
}

function Show-Info {
    param([string]$Text, [string]$Title = "Vendelse Poort TBC Launcher")
    [System.Windows.MessageBox]::Show(
        $Text, $Title,
        [System.Windows.MessageBoxButton]::OK,
        [System.Windows.MessageBoxImage]::Information
    ) | Out-Null
}

function Show-ErrorMessage {
    param([string]$Text)
    [System.Windows.MessageBox]::Show(
        $Text, "Vendelse Poort TBC Launcher",
        [System.Windows.MessageBoxButton]::OK,
        [System.Windows.MessageBoxImage]::Error
    ) | Out-Null
}

function Ask-YesNo {
    param([string]$Text, [string]$Title = "Vendelse Poort TBC Launcher")
    return [System.Windows.MessageBox]::Show(
        $Text, $Title,
        [System.Windows.MessageBoxButton]::YesNo,
        [System.Windows.MessageBoxImage]::Question
    ) -eq [System.Windows.MessageBoxResult]::Yes
}

function Read-Json {
    param([string]$Path)
    return (Get-Content -LiteralPath $Path -Raw -Encoding UTF8 | ConvertFrom-Json)
}

if (-not (Test-Path -LiteralPath $ConfigPath)) {
    throw "Konfigurationsdatei fehlt: $ConfigPath"
}
if (-not (Test-Path -LiteralPath $XamlPath)) {
    throw "Oberflächendatei fehlt: $XamlPath"
}
if (-not (Test-Path -LiteralPath $BackgroundPath)) {
    throw "Hintergrundbild fehlt: $BackgroundPath"
}

$Config = Read-Json $ConfigPath
$LocalFeed = Read-Json $LocalFeedPath

$UserSettings = [pscustomobject]@{
    account_name = ""
    remember_account = $false
}
if (Test-Path -LiteralPath $UserSettingsPath) {
    try {
        $loadedSettings = Read-Json $UserSettingsPath
        if ($null -ne $loadedSettings.account_name) {
            $UserSettings.account_name = [string]$loadedSettings.account_name
        }
        if ($null -ne $loadedSettings.remember_account) {
            $UserSettings.remember_account = [bool]$loadedSettings.remember_account
        }
    }
    catch {
        Write-LauncherLog "Benutzereinstellungen konnten nicht gelesen werden: $($_.Exception.Message)"
    }
}

[xml]$XamlDocument = Get-Content -LiteralPath $XamlPath -Raw -Encoding UTF8
$XmlReader = New-Object System.Xml.XmlNodeReader $XamlDocument
$Window = [Windows.Markup.XamlReader]::Load($XmlReader)

function UI {
    param([string]$Name)
    $control = $Window.FindName($Name)
    if ($null -eq $control) {
        throw "UI-Element fehlt: $Name"
    }
    return $control
}

$BackgroundImage = UI "BackgroundImage"
$DragArea = UI "DragArea"
$BtnTopSettings = UI "BtnTopSettings"
$BtnMinimize = UI "BtnMinimize"
$BtnClose = UI "BtnClose"
$BtnStart = UI "BtnStart"
$BtnNews = UI "BtnNews"
$BtnServerStatus = UI "BtnServerStatus"
$BtnAccount = UI "BtnAccount"
$BtnSettings = UI "BtnSettings"
$BtnSupport = UI "BtnSupport"
$BtnReadMore = UI "BtnReadMore"
$BtnPatchNotes = UI "BtnPatchNotes"
$BtnEventDetails = UI "BtnEventDetails"
$BtnPasswordHelp = UI "BtnPasswordHelp"
$BtnClientCheck = UI "BtnClientCheck"
$BtnRepair = UI "BtnRepair"
$BtnUpdates = UI "BtnUpdates"
$BtnOpenFolder = UI "BtnOpenFolder"
$BtnPlay = UI "BtnPlay"

$TxtNewsTitle = UI "TxtNewsTitle"
$TxtNewsBody = UI "TxtNewsBody"
$TxtPatchNotes = UI "TxtPatchNotes"
$TxtEventTitle = UI "TxtEventTitle"
$TxtEventDate = UI "TxtEventDate"
$TxtEventText = UI "TxtEventText"
$TxtRealmName = UI "TxtRealmName"
$TxtRealmStatus = UI "TxtRealmStatus"
$TxtLoginStatus = UI "TxtLoginStatus"
$TxtWorldStatus = UI "TxtWorldStatus"
$TxtVersion = UI "TxtVersion"
$DotRealm = UI "DotRealm"
$DotLogin = UI "DotLogin"
$DotWorld = UI "DotWorld"
$TxtAccountName = UI "TxtAccountName"
$ChkRememberAccount = UI "ChkRememberAccount"
$TxtFooterHost = UI "TxtFooterHost"
$TxtFooterRealm = UI "TxtFooterRealm"
$DotSystemReady = UI "DotSystemReady"
$TxtSystemReady = UI "TxtSystemReady"

$Bitmap = New-Object Windows.Media.Imaging.BitmapImage
$Bitmap.BeginInit()
$Bitmap.CacheOption = [Windows.Media.Imaging.BitmapCacheOption]::OnLoad
$Bitmap.UriSource = New-Object System.Uri($BackgroundPath, [System.UriKind]::Absolute)
$Bitmap.EndInit()
$Bitmap.Freeze()
$BackgroundImage.Source = $Bitmap

$TxtAccountName.Text = [string]$UserSettings.account_name
$ChkRememberAccount.IsChecked = [bool]$UserSettings.remember_account

$script:ActiveHost = ""
$script:NewsUrl = [string]$Config.news_url
$script:PatchUrl = [string]$Config.news_url
$script:EventUrl = [string]$Config.news_url
$script:AccountUrl = [string]$Config.account_url
$script:PasswordHelpUrl = [string]$Config.password_help_url
$script:SupportUrl = [string]$Config.support_url

$BrushOnline = New-Object Windows.Media.SolidColorBrush ([Windows.Media.Color]::FromRgb(58, 204, 83))
$BrushOffline = New-Object Windows.Media.SolidColorBrush ([Windows.Media.Color]::FromRgb(226, 78, 70))
$BrushChecking = New-Object Windows.Media.SolidColorBrush ([Windows.Media.Color]::FromRgb(216, 169, 77))
$BrushTextOnline = $BrushOnline
$BrushTextOffline = $BrushOffline
$BrushTextChecking = $BrushChecking

function Set-Indicator {
    param(
        $TextBlock,
        $Dot,
        [Nullable[bool]]$Online,
        [string]$CheckingText = "Prüfe ..."
    )

    if ($null -eq $Online) {
        $TextBlock.Text = $CheckingText
        $TextBlock.Foreground = $BrushTextChecking
        $Dot.Fill = $BrushChecking
    }
    elseif ([bool]$Online) {
        $TextBlock.Text = "Online"
        $TextBlock.Foreground = $BrushTextOnline
        $Dot.Fill = $BrushOnline
    }
    else {
        $TextBlock.Text = "Offline"
        $TextBlock.Foreground = $BrushTextOffline
        $Dot.Fill = $BrushOffline
    }
}

function Test-TcpPort {
    param(
        [Parameter(Mandatory = $true)][string]$HostName,
        [Parameter(Mandatory = $true)][int]$Port,
        [int]$TimeoutMs = 1200
    )

    $client = New-Object System.Net.Sockets.TcpClient
    try {
        $async = $client.BeginConnect($HostName, $Port, $null, $null)
        if (-not $async.AsyncWaitHandle.WaitOne($TimeoutMs, $false)) {
            return $false
        }
        $client.EndConnect($async)
        return $true
    }
    catch {
        return $false
    }
    finally {
        try { $client.Close() } catch {}
    }
}

function Get-HostCandidates {
    $mode = ([string]$Config.connection_mode).ToLowerInvariant()
    $internal = [string]$Config.internal_host
    $public = [string]$Config.public_host

    switch ($mode) {
        "internal" { return @($internal) }
        "public"   { return @($public) }
        default    { return @($internal, $public) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique }
    }
}

function Resolve-ServerHost {
    $candidates = @(Get-HostCandidates)
    foreach ($hostName in $candidates) {
        if (Test-TcpPort -HostName $hostName -Port ([int]$Config.login_port) -TimeoutMs 900) {
            return $hostName
        }
    }
    if ($candidates.Count -gt 0) {
        return [string]$candidates[0]
    }
    return [string]$Config.public_host
}

function Apply-Feed {
    param($Feed)

    if ($null -ne $Feed.server) {
        if ($null -ne $Feed.server.realm_name) {
            $TxtRealmName.Text = [string]$Feed.server.realm_name
            $TxtFooterRealm.Text = "Realm: " + [string]$Feed.server.realm_name
        }
        if ($null -ne $Feed.server.version) {
            $TxtVersion.Text = [string]$Feed.server.version
        }
    }

    if ($null -ne $Feed.news) {
        $TxtNewsTitle.Text = [string]$Feed.news.title
        $TxtNewsBody.Text = [string]$Feed.news.body
        if ($null -ne $Feed.news.url -and -not [string]::IsNullOrWhiteSpace([string]$Feed.news.url)) {
            $script:NewsUrl = [string]$Feed.news.url
        }
    }

    if ($null -ne $Feed.patch_notes) {
        $notes = @($Feed.patch_notes | ForEach-Object { "• " + [string]$_ })
        $TxtPatchNotes.Text = $notes -join [Environment]::NewLine
    }

    if ($null -ne $Feed.event) {
        $TxtEventTitle.Text = [string]$Feed.event.title
        $dateLine = [string]$Feed.event.date
        if ($null -ne $Feed.event.time -and -not [string]::IsNullOrWhiteSpace([string]$Feed.event.time)) {
            $dateLine += [Environment]::NewLine + [string]$Feed.event.time
        }
        $TxtEventDate.Text = $dateLine
        $TxtEventText.Text = [string]$Feed.event.text
        if ($null -ne $Feed.event.url -and -not [string]::IsNullOrWhiteSpace([string]$Feed.event.url)) {
            $script:EventUrl = [string]$Feed.event.url
        }
    }
}

function Load-LauncherFeed {
    Apply-Feed $LocalFeed

    $feedUrl = [string]$Config.feed_url
    if ([string]::IsNullOrWhiteSpace($feedUrl)) {
        return
    }

    try {
        $remoteFeed = Invoke-RestMethod -Uri $feedUrl -Method Get -TimeoutSec 4
        Apply-Feed $remoteFeed
        Write-LauncherLog "Remote-Feed erfolgreich geladen: $feedUrl"
    }
    catch {
        Write-LauncherLog "Remote-Feed nicht erreichbar; lokaler Inhalt aktiv: $($_.Exception.Message)"
    }
}

function Refresh-ServerStatus {
    Set-Indicator $TxtRealmStatus $DotRealm $null
    Set-Indicator $TxtLoginStatus $DotLogin $null
    Set-Indicator $TxtWorldStatus $DotWorld $null
    $TxtSystemReady.Text = "Prüfe Systeme ..."
    $DotSystemReady.Fill = $BrushChecking

    try {
        $hostName = Resolve-ServerHost
        $script:ActiveHost = $hostName

        $loginOnline = Test-TcpPort -HostName $hostName -Port ([int]$Config.login_port)
        $worldOnline = Test-TcpPort -HostName $hostName -Port ([int]$Config.world_port)
        $realmOnline = $loginOnline -and $worldOnline

        Set-Indicator $TxtLoginStatus $DotLogin ([Nullable[bool]]$loginOnline)
        Set-Indicator $TxtWorldStatus $DotWorld ([Nullable[bool]]$worldOnline)
        Set-Indicator $TxtRealmStatus $DotRealm ([Nullable[bool]]$realmOnline)

        $TxtFooterHost.Text = "Serverziel: " + [string]$Config.public_host
        $TxtRealmName.Text = [string]$Config.realm_name
        $TxtVersion.Text = [string]$Config.client_version

        $wowPath = Join-Path $ClientRoot ([string]$Config.wow_exe)
        if ($realmOnline -and (Test-Path -LiteralPath $wowPath)) {
            $TxtSystemReady.Text = "Alle Systeme bereit"
            $DotSystemReady.Fill = $BrushOnline
        }
        elseif (-not (Test-Path -LiteralPath $wowPath)) {
            $TxtSystemReady.Text = "Wow.exe fehlt"
            $DotSystemReady.Fill = $BrushOffline
        }
        elseif ($loginOnline -and -not $worldOnline) {
            $TxtSystemReady.Text = "Weltserver startet später"
            $DotSystemReady.Fill = $BrushChecking
        }
        else {
            $TxtSystemReady.Text = "Server nicht erreichbar"
            $DotSystemReady.Fill = $BrushOffline
        }

        Write-LauncherLog "Status: Host=$hostName Login=$loginOnline Welt=$worldOnline"
    }
    catch {
        Set-Indicator $TxtRealmStatus $DotRealm ([Nullable[bool]]$false)
        Set-Indicator $TxtLoginStatus $DotLogin ([Nullable[bool]]$false)
        Set-Indicator $TxtWorldStatus $DotWorld ([Nullable[bool]]$false)
        $TxtSystemReady.Text = "Statusprüfung fehlgeschlagen"
        $DotSystemReady.Fill = $BrushOffline
        Write-LauncherLog "Statusprüfung fehlgeschlagen: $($_.Exception.Message)"
    }
}

function Save-UserSettings {
    $remember = [bool]$ChkRememberAccount.IsChecked
    $account = ""
    if ($remember) {
        $account = ([string]$TxtAccountName.Text).Trim()
    }

    $data = [ordered]@{
        account_name = $account
        remember_account = $remember
    }
    $data | ConvertTo-Json | Set-Content -LiteralPath $UserSettingsPath -Encoding UTF8
}

function Backup-FileIfPresent {
    param([string]$Path, [string]$TargetFolder)
    if (Test-Path -LiteralPath $Path) {
        Copy-Item -LiteralPath $Path -Destination $TargetFolder -Force
    }
}

function Write-Realmlist {
    param([string]$HostName)

    $line = "set realmlist $HostName"
    $targets = New-Object System.Collections.Generic.List[string]
    $targets.Add((Join-Path $ClientRoot "realmlist.wtf"))

    $dataDir = Join-Path $ClientRoot "Data"
    if (Test-Path -LiteralPath $dataDir) {
        Get-ChildItem -LiteralPath $dataDir -Directory -ErrorAction SilentlyContinue |
            Where-Object { $_.Name -match "^[a-z]{2}[A-Z]{2}$" } |
            ForEach-Object {
                $targets.Add((Join-Path $_.FullName "realmlist.wtf"))
            }
    }

    foreach ($target in ($targets | Select-Object -Unique)) {
        $parent = Split-Path -Parent $target
        if (-not (Test-Path -LiteralPath $parent)) {
            New-Item -ItemType Directory -Path $parent -Force | Out-Null
        }
        Set-Content -LiteralPath $target -Value $line -Encoding ASCII
    }

    Write-LauncherLog "Realmlist gesetzt: $HostName"
}

function Write-AccountNameToWowConfig {
    param([string]$AccountName)

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

    $safeAccount = $AccountName.Replace('"', '').Replace("`r", "").Replace("`n", "")
    $wtfDir = Join-Path $ClientRoot "WTF"
    $configWtf = Join-Path $wtfDir "Config.wtf"
    if (-not (Test-Path -LiteralPath $wtfDir)) {
        New-Item -ItemType Directory -Path $wtfDir -Force | Out-Null
    }

    $replacement = 'SET accountName "' + $safeAccount + '"'
    $lines = @()
    if (Test-Path -LiteralPath $configWtf) {
        $lines = @(Get-Content -LiteralPath $configWtf -ErrorAction SilentlyContinue)
    }

    $found = $false
    for ($i = 0; $i -lt $lines.Count; $i++) {
        if ($lines[$i] -match '^\s*SET\s+accountName\s+') {
            $lines[$i] = $replacement
            $found = $true
        }
    }

    if (-not $found) {
        $lines += $replacement
    }

    Set-Content -LiteralPath $configWtf -Value $lines -Encoding ASCII
}

function Start-Game {
    try {
        $wowPath = Join-Path $ClientRoot ([string]$Config.wow_exe)
        if (-not (Test-Path -LiteralPath $wowPath)) {
            Show-ErrorMessage "Die Datei Wow.exe wurde nicht gefunden.`n`nEntpacke den Launcher direkt in den WoW-TBC-Ordner, in dem auch Wow.exe liegt.`n`nErwarteter Pfad:`n$wowPath"
            return
        }

        Save-UserSettings
        $hostName = Resolve-ServerHost
        $script:ActiveHost = $hostName
        Write-Realmlist -HostName $hostName

        if ([bool]$ChkRememberAccount.IsChecked) {
            Write-AccountNameToWowConfig -AccountName ([string]$TxtAccountName.Text)
        }

        Write-LauncherLog "Starte WoW: $wowPath"
        Start-Process -FilePath $wowPath -WorkingDirectory $ClientRoot
        $Window.WindowState = [System.Windows.WindowState]::Minimized
    }
    catch {
        Write-LauncherLog "Spielstart fehlgeschlagen: $($_.Exception.Message)"
        Show-ErrorMessage "WoW konnte nicht gestartet werden.`n`n$($_.Exception.Message)"
    }
}

function Get-WowVersion {
    param([string]$WowPath)
    try {
        return (Get-Item -LiteralPath $WowPath).VersionInfo.FileVersion
    }
    catch {
        return "nicht ermittelbar"
    }
}

function Check-Client {
    try {
        $results = New-Object System.Collections.Generic.List[string]
        $wowPath = Join-Path $ClientRoot ([string]$Config.wow_exe)

        if (Test-Path -LiteralPath $wowPath) {
            $results.Add("[OK] Wow.exe vorhanden")
            $results.Add("     Dateiversion: $(Get-WowVersion $wowPath)")
        }
        else {
            $results.Add("[FEHLER] Wow.exe fehlt")
        }

        $dataPath = Join-Path $ClientRoot "Data"
        if (Test-Path -LiteralPath $dataPath) {
            $results.Add("[OK] Data-Ordner vorhanden")
        }
        else {
            $results.Add("[FEHLER] Data-Ordner fehlt")
        }

        $requiredFiles = @(
            "Data\common.MPQ",
            "Data\expansion.MPQ"
        )
        foreach ($relative in $requiredFiles) {
            $path = Join-Path $ClientRoot $relative
            if (Test-Path -LiteralPath $path) {
                $results.Add("[OK] $relative")
            }
            else {
                $results.Add("[HINWEIS] $relative nicht gefunden")
            }
        }

        $hostName = Resolve-ServerHost
        $loginOnline = Test-TcpPort -HostName $hostName -Port ([int]$Config.login_port)
        $worldOnline = Test-TcpPort -HostName $hostName -Port ([int]$Config.world_port)
        $results.Add("")
        $results.Add("Verbindungsziel: $hostName")
        $results.Add("Loginserver: " + $(if ($loginOnline) { "Online" } else { "Offline" }))
        $results.Add("Weltserver: " + $(if ($worldOnline) { "Online" } else { "Offline" }))

        $realmlistPath = Join-Path $ClientRoot "realmlist.wtf"
        if (Test-Path -LiteralPath $realmlistPath) {
            $realmlist = (Get-Content -LiteralPath $realmlistPath -Raw -ErrorAction SilentlyContinue).Trim()
            $results.Add("Realmlist: $realmlist")
        }
        else {
            $results.Add("Realmlist: wird beim ersten Start automatisch erstellt")
        }

        Show-Info ($results -join [Environment]::NewLine) "Clientprüfung"
    }
    catch {
        Show-ErrorMessage "Die Clientprüfung ist fehlgeschlagen.`n`n$($_.Exception.Message)"
    }
}

function Repair-Client {
    if (-not (Ask-YesNo "Die sichere Reparatur führt nur folgende Schritte aus:`n`n• Sicherung vorhandener Realmlist- und Config-Dateien`n• Realmlist neu setzen`n• WoW-Cache/WDB löschen`n`nSpieldaten und AddOns werden nicht gelöscht.`n`nReparatur jetzt starten?" "Client reparieren")) {
        return
    }

    try {
        $stamp = Get-Date -Format "yyyyMMdd_HHmmss"
        $targetBackup = Join-Path $BackupDir $stamp
        New-Item -ItemType Directory -Path $targetBackup -Force | Out-Null

        Backup-FileIfPresent (Join-Path $ClientRoot "realmlist.wtf") $targetBackup
        Backup-FileIfPresent (Join-Path $ClientRoot "WTF\Config.wtf") $targetBackup

        $hostName = Resolve-ServerHost
        Write-Realmlist -HostName $hostName

        foreach ($cachePath in @(
            (Join-Path $ClientRoot "Cache\WDB"),
            (Join-Path $ClientRoot "WDB")
        )) {
            if (Test-Path -LiteralPath $cachePath) {
                Remove-Item -LiteralPath $cachePath -Recurse -Force
            }
        }

        Write-LauncherLog "Sichere Reparatur abgeschlossen. Backup: $targetBackup"
        Show-Info "Die sichere Reparatur wurde abgeschlossen.`n`nSicherung:`n$targetBackup" "Client reparieren"
    }
    catch {
        Write-LauncherLog "Reparatur fehlgeschlagen: $($_.Exception.Message)"
        Show-ErrorMessage "Die Reparatur ist fehlgeschlagen.`n`n$($_.Exception.Message)"
    }
}

function Open-ConfiguredUrl {
    param([string]$Url, [string]$Label)

    if ([string]::IsNullOrWhiteSpace($Url)) {
        Show-Info "Für „$Label“ ist noch keine Adresse in config.json eingetragen."
        return
    }

    try {
        Start-Process $Url
    }
    catch {
        Show-ErrorMessage "Die Seite konnte nicht geöffnet werden:`n$Url"
    }
}

function Check-Updates {
    $manifestUrl = [string]$Config.update_manifest_url
    if ([string]::IsNullOrWhiteSpace($manifestUrl)) {
        Open-ConfiguredUrl -Url ([string]$Config.updates_url) -Label "Updates"
        return
    }

    try {
        $manifest = Invoke-RestMethod -Uri $manifestUrl -Method Get -TimeoutSec 8
        $localVersion = New-Object System.Version([string]$Config.launcher_version)
        $remoteVersion = New-Object System.Version([string]$manifest.version)

        if ($remoteVersion -le $localVersion) {
            Show-Info "Der Launcher ist aktuell.`n`nInstalliert: $localVersion`nVerfügbar: $remoteVersion" "Updates"
            return
        }

        $notes = ""
        if ($null -ne $manifest.notes) {
            $notes = "`n`n" + [string]$manifest.notes
        }

        if (-not (Ask-YesNo "Eine neue Launcher-Version ist verfügbar.`n`nInstalliert: $localVersion`nNeu: $remoteVersion$notes`n`nPaket jetzt herunterladen?" "Launcher-Update")) {
            return
        }

        $fileName = "VendelsePoortLauncher_$remoteVersion.zip"
        if ($null -ne $manifest.file_name -and -not [string]::IsNullOrWhiteSpace([string]$manifest.file_name)) {
            $fileName = [string]$manifest.file_name
        }

        $target = Join-Path $DownloadDir $fileName
        Invoke-WebRequest -Uri ([string]$manifest.package_url) -OutFile $target -UseBasicParsing

        if ($null -ne $manifest.sha256 -and -not [string]::IsNullOrWhiteSpace([string]$manifest.sha256)) {
            $actual = (Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant()
            $expected = ([string]$manifest.sha256).ToLowerInvariant()
            if ($actual -ne $expected) {
                Remove-Item -LiteralPath $target -Force -ErrorAction SilentlyContinue
                throw "SHA-256-Prüfung fehlgeschlagen."
            }
        }

        Write-LauncherLog "Updatepaket heruntergeladen: $target"
        Show-Info "Das Updatepaket wurde geprüft und heruntergeladen.`n`n$target`n`nDer Launcher öffnet jetzt den Downloadordner." "Updates"
        Start-Process explorer.exe -ArgumentList "/select,`"$target`""
    }
    catch {
        Write-LauncherLog "Updateprüfung fehlgeschlagen: $($_.Exception.Message)"
        Show-ErrorMessage "Die Updateprüfung ist fehlgeschlagen.`n`n$($_.Exception.Message)"
    }
}

function Open-Settings {
    try {
        Start-Process notepad.exe -ArgumentList "`"$ConfigPath`""
        Show-Info "Die Konfiguration wurde in Notepad geöffnet.`n`nNach Änderungen den Launcher bitte neu starten.`n`nDie Einstellung connection_mode kann auto, internal oder public sein." "Einstellungen"
    }
    catch {
        Show-ErrorMessage "Die Konfiguration konnte nicht geöffnet werden."
    }
}

$DragArea.Add_MouseLeftButtonDown({
    param($sender, $eventArgs)
    if ($eventArgs.ClickCount -ge 2) {
        if ($Window.WindowState -eq [System.Windows.WindowState]::Maximized) {
            $Window.WindowState = [System.Windows.WindowState]::Normal
        }
        else {
            $Window.WindowState = [System.Windows.WindowState]::Maximized
        }
    }
    else {
        try { $Window.DragMove() } catch {}
    }
})

$BtnClose.Add_Click({ $Window.Close() })
$BtnMinimize.Add_Click({ $Window.WindowState = [System.Windows.WindowState]::Minimized })
$BtnTopSettings.Add_Click({ Open-Settings })
$BtnSettings.Add_Click({ Open-Settings })

$BtnStart.Add_Click({ Start-Game })
$BtnPlay.Add_Click({ Start-Game })

$BtnServerStatus.Add_Click({ Refresh-ServerStatus })
$BtnClientCheck.Add_Click({ Check-Client })
$BtnRepair.Add_Click({ Repair-Client })
$BtnUpdates.Add_Click({ Check-Updates })
$BtnOpenFolder.Add_Click({ Start-Process explorer.exe -ArgumentList "`"$ClientRoot`"" })

$BtnNews.Add_Click({ Open-ConfiguredUrl -Url $script:NewsUrl -Label "News" })
$BtnReadMore.Add_Click({ Open-ConfiguredUrl -Url $script:NewsUrl -Label "News" })
$BtnPatchNotes.Add_Click({ Open-ConfiguredUrl -Url $script:PatchUrl -Label "Patchnotizen" })
$BtnEventDetails.Add_Click({ Open-ConfiguredUrl -Url $script:EventUrl -Label "Events" })
$BtnAccount.Add_Click({ Open-ConfiguredUrl -Url $script:AccountUrl -Label "Account" })
$BtnPasswordHelp.Add_Click({ Open-ConfiguredUrl -Url $script:PasswordHelpUrl -Label "Passworthilfe" })
$BtnSupport.Add_Click({ Open-ConfiguredUrl -Url $script:SupportUrl -Label "Support" })

$RefreshTimer = New-Object Windows.Threading.DispatcherTimer
$RefreshSeconds = 20
try {
    $RefreshSeconds = [Math]::Max(10, [int]$Config.status_refresh_seconds)
}
catch {}
$RefreshTimer.Interval = [TimeSpan]::FromSeconds($RefreshSeconds)
$RefreshTimer.Add_Tick({ Refresh-ServerStatus })

$Window.Add_ContentRendered({
    try {
        Load-LauncherFeed
        Refresh-ServerStatus
        $RefreshTimer.Start()
    }
    catch {
        Write-LauncherLog "Initialisierung fehlgeschlagen: $($_.Exception.Message)"
        Show-ErrorMessage "Der Launcher konnte nicht vollständig initialisiert werden.`n`n$($_.Exception.Message)"
    }
})

$Window.Add_Closed({
    try {
        Save-UserSettings
        $RefreshTimer.Stop()
    }
    catch {}
})

try {
    Write-LauncherLog "Launcher gestartet. ClientRoot=$ClientRoot"
    $null = $Window.ShowDialog()
}
catch {
    Write-LauncherLog "Schwerer Launcherfehler: $($_.Exception.Message)"
    Show-ErrorMessage "Der Launcher wurde wegen eines Fehlers beendet.`n`n$($_.Exception.Message)`n`nProtokoll:`n$LogPath"
}
