$ErrorActionPreference = 'Stop'

$repo = Join-Path $env:USERPROFILE 'TibiaPilotNG'
$tibiaRoot = Join-Path $env:LOCALAPPDATA 'Tibia'
$localRoot = Join-Path $env:LOCALAPPDATA 'TibiaPilotNG'
$profileMarker = Join-Path $localRoot 'active-profile.txt'
$credentialFile = Join-Path $localRoot 'credentials.bin'
$stateFile = Join-Path $repo '.pilotng-state.json'
$resumeFile = Join-Path $repo '.pilotng-resume.json'
$logFile = Join-Path $repo '.one-click.log'
$resultFile = Join-Path $repo '.one-click-result.json'
$python = Join-Path $repo '.venv\Scripts\python.exe'
$mutex = [Threading.Mutex]::new($false, 'Local\TibiaPilotNG-OneClick')
$failureMessage = $null

if (!$mutex.WaitOne(0)) { exit 0 }

function Write-Status([string]$Message) {
    "$(Get-Date -Format o) $Message" | Add-Content $logFile -Encoding UTF8
}

function Save-Resume {
    if (!(Test-Path $stateFile)) { return }
    try {
        $state = Get-Content $stateFile -Raw | ConvertFrom-Json
        if ($null -ne $state.waypointIndex -and $state.coordinate.Count -eq 3) {
            [ordered]@{
                waypointIndex = [int]$state.waypointIndex
                coordinate = @($state.coordinate | ForEach-Object { [int]$_ })
            } | ConvertTo-Json -Compress | Set-Content $resumeFile -Encoding UTF8
        }
    } catch {}
}

function Stop-TibiaProcesses {
    Get-Process ApplicationFrameHost -ErrorAction SilentlyContinue |
        Stop-Process -Force -ErrorAction SilentlyContinue
    Get-Process client, Tibia -ErrorAction SilentlyContinue |
        Stop-Process -Force -ErrorAction SilentlyContinue
    Get-CimInstance Win32_Process | Where-Object {
        $_.ExecutablePath -like "$tibiaRoot\*"
    } | ForEach-Object {
        & taskkill.exe /PID $_.ProcessId /T /F | Out-Null
    }
}

function Get-TibiaGameProcess {
    Get-Process client, Tibia -ErrorAction SilentlyContinue |
        Where-Object MainWindowTitle -Match '^Tibia\s+-' |
        Select-Object -First 1
}

function Invoke-ToolScript([string]$Path, [string[]]$Arguments = @()) {
    & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass `
        -File $Path @Arguments
    if ($LASTEXITCODE -ne 0) {
        throw "Tool failed: $(Split-Path $Path -Leaf) ($LASTEXITCODE)"
    }
}

function Test-Arduino {
    $stdout = [IO.Path]::GetTempFileName()
    $stderr = [IO.Path]::GetTempFileName()
    try {
        $process = Start-Process `
            -FilePath $python `
            -ArgumentList "`"$(Join-Path $repo 'tools\Check-Arduino.py')`"" `
            -WindowStyle Hidden `
            -RedirectStandardOutput $stdout `
            -RedirectStandardError $stderr `
            -Wait `
            -PassThru
        return $process.ExitCode -eq 0
    } finally {
        Remove-Item $stdout, $stderr -Force -ErrorAction SilentlyContinue
    }
}

function Get-InputMethod {
    $file = Join-Path $repo '.input-method'
    if (Test-Path $file) {
        $value = (Get-Content $file -Raw).Trim().ToLower()
        if ($value -eq 'usb' -or $value -eq 'mac_bt') { return $value }
    }
    return 'usb'
}

function Ensure-Hid {
    # Bis zu 25 Sekunden auf den Arduino warten, bevor geflasht wird.
    # Wurde der Bot gerade neu gestartet (z. B. nach einem
    # Bildschirmwechsel), gibt der beendete Prozess den seriellen Port erst
    # nach einigen Sekunden frei - der Chip ist dann kurz "belegt", aber
    # voll funktionsfaehig. Ein Flash waere hier destruktiv (setzt das Board
    # zurueck, der COM-Port re-nummeriert) und voellig unnoetig. Ist der
    # Arduino da, klappt der erste Versuch sofort - also keine Verzoegerung
    # im Normalfall.
    $deadline = (Get-Date).AddSeconds(25)
    while ((Get-Date) -lt $deadline) {
        if (Test-Arduino) { return }
        Start-Sleep -Milliseconds 500
    }
    if ((Get-InputMethod) -eq 'usb') {
        Write-Status 'arduino-flash'
        & (Join-Path $repo 'tools\Flash-Leonardo.ps1') | Out-Null
        if (!(Test-Arduino)) {
            throw 'Arduino-HID ist nicht verbunden oder antwortet nicht.'
        }
    } else {
        throw 'Mac-Bluetooth-HID ist nicht verbunden. Bitte den Mac per Bluetooth koppeln und die Bridge (BTRemote) starten.'
    }
}

function Ensure-PilotDisplay {
    Add-Type -AssemblyName System.Windows.Forms
    $settingsFile = Join-Path $localRoot 'display.json'
    $selectedDevice = if (Test-Path $settingsFile) {
        (Get-Content $settingsFile -Raw | ConvertFrom-Json).device
    } else { $null }
    $screens = @([System.Windows.Forms.Screen]::AllScreens)
    $selectedAvailable = $selectedDevice -and ($screens.DeviceName -contains $selectedDevice)
    if (!$selectedAvailable -and $screens.Count -lt 3) {
        $deviceInstaller = Join-Path $env:ProgramData 'TibiaPilotNG\usbmmidd\usbmmidd_v2\deviceinstaller64.exe'
        if (!(Test-Path $deviceInstaller)) {
            throw 'Der Treiber für Bildschirm 3 wurde nicht gefunden.'
        }
        & $deviceInstaller enableidd 1 | Out-Null
        if ($LASTEXITCODE -ne 0) {
            throw 'Der virtuelle Bildschirm 3 konnte nicht aktiviert werden.'
        }
        Start-Sleep -Seconds 3
    }
    & $python (Join-Path $repo 'tools\Prepare-Bot-Display.py') | Out-Null
    if ($LASTEXITCODE -ne 0) {
        throw 'Der ausgewählte Bildschirm konnte nicht auf 1920×1080 vorbereitet werden.'
    }
}

try {
    Remove-Item $resultFile -Force -ErrorAction SilentlyContinue
    Write-Status 'start'
    Ensure-Hid
    Write-Status 'hid-ready'
    Ensure-PilotDisplay
    Write-Status 'obs-start'
    Invoke-ToolScript (Join-Path $repo 'tools\Restart-OBS.ps1')
    $bridge = Get-ScheduledTask -TaskName 'TibiaPilotNG-DesktopBridge' -ErrorAction SilentlyContinue
    if (!$bridge) {
        Invoke-ToolScript (Join-Path $repo 'tools\Install-DesktopBridge.ps1') @('-Install')
    } elseif ($bridge.State -ne 'Running') {
        Start-ScheduledTask -TaskName 'TibiaPilotNG-DesktopBridge'
    }

    $activeProfile = if (Test-Path $profileMarker) {
        (Get-Content $profileMarker -Raw).Trim()
    } else { 'normal' }
    $game = Get-TibiaGameProcess

    if ($activeProfile -ne 'bot' -or !$game) {
        Save-Resume
        & (Join-Path $repo 'tools\Stop-TibiaPilotNG.ps1')
        Stop-TibiaProcesses
        & (Join-Path $repo 'tools\Set-TibiaProfile.ps1') -Profile bot | Out-Null
        # Randlos und exakt auf den Bot-Bildschirm: der Bot rechnet mit
        # genau 1920x1080 Spielflaeche. Mit Fensterrahmen blieben nur
        # 1904x1041 uebrig - OBS skaliert das hoch und jede feste
        # Koordinate sitzt daneben.
        & $python (Join-Path $repo 'tools\Set-Client-Window.py') | Out-Null
        if ($LASTEXITCODE -ne 0) {
            throw 'Das Tibia-Fenster konnte nicht auf den Bot-Bildschirm eingestellt werden.'
        }
        Write-Status 'client-launch'
        Invoke-ToolScript (Join-Path $repo 'tools\Restart-Tibia-Client.ps1')
        Invoke-ToolScript (Join-Path $repo 'tools\Run-Play-Tibia.ps1')
        Write-Status 'obs-projector'
        Invoke-ToolScript (Join-Path $repo 'tools\Run-Configure-OBS-Capture.ps1')

        if (!(Test-Path $credentialFile)) {
            Start-ScheduledTask -TaskName 'TibiaPilotNG'
            for ($attempt = 0; $attempt -lt 900 -and !(Test-Path $credentialFile); $attempt++) {
                Start-Sleep -Seconds 1
            }
            & (Join-Path $repo 'tools\Stop-TibiaPilotNG.ps1')
            if (!(Test-Path $credentialFile)) { throw 'Account setup was not completed.' }
        }

        Remove-Item (Join-Path $repo '.login-result.json') -Force -ErrorAction SilentlyContinue
        Invoke-ToolScript (Join-Path $repo 'tools\Run-Login-Tibia.ps1')
        $login = Get-Content (Join-Path $repo '.login-result.json') -Raw | ConvertFrom-Json
        if (!$login.ok) { throw 'Tibia login failed.' }
        $game = $null
        for ($attempt = 0; $attempt -lt 45 -and !$game; $attempt++) {
            Start-Sleep -Seconds 1
            $game = Get-TibiaGameProcess
        }
        if (!$game) { throw 'The character did not enter the game.' }
    }

    # Zoom der Minimap auf die Stufe stellen, bei der ein Pixel einem Feld
    # entspricht. Nur dann findet der Bot seine Position auf der Weltkarte
    # wieder. Tibia merkt sich den Zoom pro Charakter, also kann er nach
    # einem manuellen Spiel beliebig verstellt sein. Schlaegt es fehl,
    # laeuft der Start trotzdem weiter - der Bot arbeitet dann mit
    # Wegpunkten, nur die Ersterkennung ist ungenauer.
    Write-Status 'minimap-zoom'
    & $python (Join-Path $repo 'tools\Set-Minimap-Zoom.py') | Out-Null

    Write-Status 'bot-start'
    Invoke-ToolScript (Join-Path $repo 'tools\Restart-TibiaPilotNG.ps1') @('-AutoStart') | Out-Null
    Start-Sleep -Seconds 5
    $game = Get-TibiaGameProcess
    if (!$game) { throw 'The logged-in Tibia client was not found.' }
    Write-Status 'running'
    Wait-Process -Id $game.Id
}
catch {
    $failureMessage = $_.Exception.Message
    Write-Status "error=$failureMessage"
}
finally {
    try {
        Save-Resume
        & (Join-Path $repo 'tools\Stop-TibiaPilotNG.ps1')
        Stop-TibiaProcesses
        & (Join-Path $repo 'tools\Set-TibiaProfile.ps1') -Profile normal | Out-Null
        & $python (Join-Path $repo 'tools\Restore-Bot-Display.py') | Out-Null
        Write-Status 'normal-profile-restored'
    } catch {
        Write-Status "restore-error=$($_.Exception.Message)"
    }
    $mutex.ReleaseMutex()
    $mutex.Dispose()
}

if ($failureMessage) {
    [ordered]@{ ok = $false; message = $failureMessage } |
        ConvertTo-Json -Compress | Set-Content $resultFile -Encoding UTF8
    # Die Meldung laeuft bewusst in einem EIGENEN Prozess.
    # Ein modales Fenster hier wuerde diese Aufgabe blockieren, solange
    # niemand auf OK klickt - und Windows ignoriert danach JEDEN weiteren
    # Startversuch stillschweigend ("ich druecke, nichts passiert").
    $safe = $failureMessage -replace "'", "''"
    Start-Process powershell.exe -WindowStyle Hidden -ArgumentList @(
        '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
        "Add-Type -AssemblyName System.Windows.Forms; " +
        "[System.Windows.Forms.MessageBox]::Show('$safe'," +
        "'PilotNG konnte nicht starten'," +
        "[System.Windows.Forms.MessageBoxButtons]::OK," +
        "[System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null"
    ) | Out-Null
    exit 1
}
