# vfp_ui_harness.ps1 - harness reutilizabil pentru teste UI VFP cu capturi de ecran.
# Lanseaza un test VFP care afiseaza un formular real, il conduce pas cu pas prin semafoare
# (ready_<n>.txt scris de test <-> cont_<n>.txt scris de harness), face un screenshot dupa
# fiecare pas si il valideaza. Testul VFP foloseste procedurile din ui_harness.prg.
#
# Exemplu:
#   powershell -ExecutionPolicy Bypass -File vfp_ui_harness.ps1 `
#       -TestPrg 'D:\...\test_x_ui.prg' -Steps @('afisat','actiune1','actiune2')
#
# Capcane rezolvate (vezi docs\testare-ui-vfp.md): precompilare izolata, lansare .fxp,
# detectie pornire pe mtime log, timeout-uri generoase, kill zombi vfp9.
#
# FARA FURT DE FOCUS (cerinta Marius, 17/07/2026): capturile NU mai folosesc
# Graphics.CopyFromScreen (poza ecranului intreg - fura focus / se corupe daca utilizatorul
# lucreaza in paralel). Se foloseste PrintWindow pe handle-ul ferestrei vfp9 + fereastra e
# mutata OFF-SCREEN (x=-4000, HWND_BOTTOM, SWP_NOACTIVATE) imediat ce apare handle-ul si
# re-impinsa inainte de fiecare pas (VFP isi poate re-activa fereastra la Show()/dialoguri).
# Fereastra ramane "visible" pentru GDI (PrintWindow merge), doar nu e pe ecranul vizibil
# utilizatorului si nu ia focus.
param(
    [Parameter(Mandatory=$true)][string]$TestPrg,
    [Parameter(Mandatory=$true)][string[]]$Steps,   # etichete pentru pasii 0..N-1
    [int]$StepTimeoutSec  = 130,                     # cat astept fiecare ready_<n> (dupa pas 0)
    [int]$ReadyTimeoutSec = 180,                     # cat astept ready_0 (afisarea formularului, incarcare lenta)
    [string]$ShotsDir,
    [string]$SyncDir,
    [string]$Vfp = 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe'
)
$ErrorActionPreference = 'Stop'

$TestDir = [System.IO.Path]::GetDirectoryName($TestPrg)
$TestFxp = [System.IO.Path]::ChangeExtension($TestPrg, 'fxp')
$TestLog = [System.IO.Path]::ChangeExtension($TestPrg, $null) + '_log.txt'
if (-not $SyncDir)  { $SyncDir  = Join-Path $TestDir 'uisync' }
if (-not $ShotsDir) { $ShotsDir = Join-Path $TestDir 'screenshots' }
$Precompile = Join-Path $PSScriptRoot '_precompile.ps1'

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

Add-Type @"
using System;
using System.Runtime.InteropServices;
public class Win32Ui {
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT { public int Left, Top, Right, Bottom; }
    [DllImport("user32.dll")]
    public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdc, uint nFlags);
    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
    [DllImport("user32.dll")]
    public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    [DllImport("user32.dll")]
    public static extern bool IsWindow(IntPtr hWnd);
    public static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
    public const uint SWP_NOSIZE = 0x0001;
    public const uint SWP_NOACTIVATE = 0x0010;
    public const uint SWP_NOZORDER = 0x0004;
}
"@

function Clear-Dir([string]$Dir) {
    if (Test-Path $Dir) {
        Get-ChildItem -Path $Dir -File -ErrorAction SilentlyContinue | ForEach-Object {
            try { [System.IO.File]::Delete($_.FullName) } catch {}
        }
    } else { New-Item -ItemType Directory -Path $Dir | Out-Null }
}

# Muta fereastra vfp9 off-screen (x=-4000), fara sa o activeze/minimizeze - ramane randabila
# pentru PrintWindow, dar nu se vede si nu fura focus. Se cheama repetat (VFP isi reactiveaza
# singur fereastra la Show()/dialoguri modale).
function Push-Offscreen([IntPtr]$Hwnd) {
    if ($Hwnd -eq [IntPtr]::Zero) { return }
    if (-not [Win32Ui]::IsWindow($Hwnd)) { return }
    [Win32Ui]::SetWindowPos($Hwnd, [Win32Ui]::HWND_BOTTOM, -4000, 0, 0, 0, ([Win32Ui]::SWP_NOSIZE -bor [Win32Ui]::SWP_NOACTIVATE)) | Out-Null
}

# Polleaza MainWindowHandle pana devine nenul (fereastra principala vfp9 aparuta).
function Wait-MainWindowHandle([System.Diagnostics.Process]$Proc, [int]$TimeoutSec) {
    $deadline = (Get-Date).AddSeconds($TimeoutSec)
    while ((Get-Date) -lt $deadline) {
        try {
            $Proc.Refresh()
            if ($Proc.HasExited) { return [IntPtr]::Zero }
            if ($Proc.MainWindowHandle -ne [IntPtr]::Zero) { return $Proc.MainWindowHandle }
        } catch {}
        Start-Sleep -Milliseconds 200
    }
    return [IntPtr]::Zero
}

function Wait-File([string]$Path, [int]$TimeoutSec, [IntPtr]$Hwnd = [IntPtr]::Zero) {
    $deadline = (Get-Date).AddSeconds($TimeoutSec)
    while (-not (Test-Path $Path)) {
        if ((Get-Date) -gt $deadline) { return $false }
        if ($Hwnd -ne [IntPtr]::Zero) { Push-Offscreen $Hwnd }
        Start-Sleep -Milliseconds 300
    }
    return $true
}

# Captura pe HANDLE cu PrintWindow (nu depinde de foreground/ecran vizibil). flag 2 =
# PW_RENDERFULLCONTENT (randare completa, necesar pt. multe ferestre GDI+/themed); daca da
# imagine goala, se incearca fallback cu flag 0.
function Take-Screenshot([IntPtr]$Hwnd, [string]$Path) {
    if ($Hwnd -eq [IntPtr]::Zero -or -not [Win32Ui]::IsWindow($Hwnd)) {
        Write-Warning "Take-Screenshot: handle invalid, sar captura ($Path)."
        return
    }
    $rect = New-Object Win32Ui+RECT
    [Win32Ui]::GetWindowRect($Hwnd, [ref]$rect) | Out-Null
    $w = $rect.Right - $rect.Left
    $h = $rect.Bottom - $rect.Top
    if ($w -le 0 -or $h -le 0) { $w = 1920; $h = 1080 }
    $bmp = New-Object System.Drawing.Bitmap $w, $h
    $g = [System.Drawing.Graphics]::FromImage($bmp)
    $hdc = $g.GetHdc()
    $ok = [Win32Ui]::PrintWindow($Hwnd, $hdc, 2)
    $g.ReleaseHdc($hdc)
    $g.Dispose()
    if (-not $ok) {
        $g2 = [System.Drawing.Graphics]::FromImage($bmp)
        $hdc2 = $g2.GetHdc()
        [Win32Ui]::PrintWindow($Hwnd, $hdc2, 0) | Out-Null
        $g2.ReleaseHdc($hdc2)
        $g2.Dispose()
    }
    $bmp.Save($Path, [System.Drawing.Imaging.ImageFormat]::Png)
    $bmp.Dispose()
}

# omoara doar vfp9 proprii (linia de comanda contine folderul testului), nu instante straine
function Stop-VfpProprii([string]$Marker) {
    Get-CimInstance Win32_Process -Filter "Name='vfp9.exe'" -ErrorAction SilentlyContinue | ForEach-Object {
        if ($_.CommandLine -and $_.CommandLine.ToLower().Contains($Marker.ToLower())) {
            Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
        }
    }
}

Clear-Dir $SyncDir
Clear-Dir $ShotsDir

# 1) Precompilare izolata (proces copil complet, ca sa nu otraveasca sesiunea de lansare).
#    -WindowStyle Hidden pe consola powershell copil (sigur - e consola, nu GUI VFP).
Write-Host "Precompilez .fxp (izolat): $Precompile -Prg $TestPrg"
Stop-VfpProprii $TestDir
Start-Sleep -Milliseconds 500
$pc = Start-Process powershell -WindowStyle Hidden -ArgumentList @('-ExecutionPolicy','Bypass','-File', "`"$Precompile`"", '-Prg', "`"$TestPrg`"") -Wait -PassThru
Start-Sleep -Seconds 2
Stop-VfpProprii $TestDir
Start-Sleep -Seconds 2
if (-not (Test-Path $TestFxp)) { Write-Warning "Nu s-a generat $TestFxp - compilare esuata." }

# 2) Lansez .FXP-ul (nu .prg-ul). vfp9 -A porneste uneori procesul FARA sa execute (log gol);
#    discriminator: START e prima linie a testului (mtime log > momentul lansarii) => in ~2s daca
#    ruleaza. Relansez doar daca log-ul nu e scris in 30s (instanta moarta). Apoi astept ready_0.
#    Imediat ce apare MainWindowHandle, fereastra e impinsa off-screen (fara activare).
$ready0 = Join-Path $SyncDir 'ready_0.txt'
$started = $false
$hwnd = [IntPtr]::Zero
foreach ($try in 1..8) {
    if (Test-Path $ready0)  { [System.IO.File]::Delete($ready0) }
    if (Test-Path $TestLog) { [System.IO.File]::Delete($TestLog) }
    $t0 = Get-Date
    Write-Host "Lansez VFP (incercarea $try): $Vfp -A $TestFxp"
    $proc = Start-Process -FilePath $Vfp -ArgumentList @('-A', "`"$TestFxp`"") -PassThru
    $dl = (Get-Date).AddSeconds(30)
    while ((Get-Date) -lt $dl) {
        if ($hwnd -eq [IntPtr]::Zero -and -not $proc.HasExited) {
            $proc.Refresh()
            if ($proc.MainWindowHandle -ne [IntPtr]::Zero) {
                $hwnd = $proc.MainWindowHandle
                Push-Offscreen $hwnd
            }
        } else {
            Push-Offscreen $hwnd
        }
        if ((Test-Path $TestLog) -and ((Get-Item $TestLog).LastWriteTime -gt $t0)) { $started = $true; break }
        if (Test-Path $ready0) { $started = $true; break }
        Start-Sleep -Milliseconds 500
    }
    if ($started) { break }
    Write-Warning "Incercarea ${try}: testul nu a scris START in 30s (instanta moarta); relansez."
    if ($proc -and -not $proc.HasExited) { try { $proc.Kill() } catch {} }
    Stop-VfpProprii $TestDir
    Start-Sleep -Milliseconds 1500
    $hwnd = [IntPtr]::Zero
}
# Daca handle-ul principal inca nu a aparut (formular aditional creat mai tarziu), mai astept putin.
if ($hwnd -eq [IntPtr]::Zero) { $hwnd = Wait-MainWindowHandle $proc 15 }
Push-Offscreen $hwnd
if (-not (Wait-File $ready0 $ReadyTimeoutSec $hwnd)) {
    Write-Warning "Testul nu a ajuns la ready_0 in ${ReadyTimeoutSec}s. Ultima linie log: '$(Get-Content $TestLog -Tail 1 -ErrorAction SilentlyContinue)'"
}

# 3) Parcurg pasii: astept ready_<n> (re-impingand fereastra off-screen periodic), screenshot
#    pe handle (PrintWindow), scriu cont_<n>.
$ok = $true
for ($n = 0; $n -lt $Steps.Count; $n++) {
    $label = $Steps[$n]
    $ready = Join-Path $SyncDir ("ready_{0}.txt" -f $n)
    $cont  = Join-Path $SyncDir ("cont_{0}.txt"  -f $n)
    Write-Host "Astept pasul $n ($label)"
    if (-not (Wait-File $ready $StepTimeoutSec $hwnd)) {
        Write-Warning "TIMEOUT la pasul $n."
        $ok = $false
        break
    }
    Write-Host ("  ready: {0}" -f (Get-Content $ready -Raw).Trim())
    # daca fereastra principala s-a schimbat intre timp (formular nou), reincerc handle-ul
    if ($hwnd -eq [IntPtr]::Zero -or -not [Win32Ui]::IsWindow($hwnd)) { $hwnd = Wait-MainWindowHandle $proc 5 }
    Push-Offscreen $hwnd
    Start-Sleep -Milliseconds 1200   # las formularul sa se deseneze complet
    Push-Offscreen $hwnd
    $png = Join-Path $ShotsDir ("step_{0}_{1}.png" -f $n, $label)
    Take-Screenshot $hwnd $png
    Write-Host "  screenshot: $png"
    Set-Content -Path $cont -Value 'go' -Encoding ascii
}

# 4) Astept done.txt si inchid.
if ($ok) {
    $done = Join-Path $SyncDir 'done.txt'
    if (Wait-File $done 30 $hwnd) { Write-Host "done: $((Get-Content $done -Raw).Trim())" }
}
Start-Sleep -Milliseconds 800
if ($proc -and -not $proc.HasExited) { try { $proc.Kill() } catch {} }
Stop-VfpProprii $TestDir
Write-Host "GATA. Screenshots in $ShotsDir"
