[CmdletBinding()]
param(
    [string]$BuildPreset = "windows-msvc-default",
    [string]$Configuration = "Debug",
    [string[]]$BuildTargets = @("PanoPainter", "pano_cli"),
    [string]$TestPreset = "desktop-fast",
    [string]$TestRegex = "",
    [switch]$Configure,
    [switch]$SkipBuild,
    [switch]$SkipTests,
    [string]$CMakeCommand = "",
    [string]$CTestCommand = "",
    [string]$LogDir = "out/logs/quiet-validation",
    [string]$IgnoreFilterFile = "",
    [string[]]$IgnorePattern = @(),
    [int]$FailureTailLines = 0,
    [switch]$IncludePlatformBuild,
    [string[]]$PlatformBuildPresets = @("android-arm64", "android-x64", "android-quest-arm64", "android-focus-arm64"),
    [string[]]$PlatformBuildTargets = @(),
    [switch]$IncludeAppleRemote,
    [string[]]$AppleRemotePresets = @("macos", "ios-simulator", "ios-device")
)

$ErrorActionPreference = "Stop"

function Resolve-CMakeCommand {
    param([string]$Requested)

    if ($Requested.Length -gt 0) {
        return $Requested
    }

    $vsCmake = "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
    if (Test-Path -LiteralPath $vsCmake) {
        return $vsCmake
    }

    return "cmake"
}

function Resolve-CTestCommand {
    param(
        [string]$Requested,
        [string]$ResolvedCMake
    )

    if ($Requested.Length -gt 0) {
        return $Requested
    }

    if ($ResolvedCMake.EndsWith("cmake.exe", [System.StringComparison]::OrdinalIgnoreCase)) {
        $candidate = Join-Path -Path (Split-Path -Parent $ResolvedCMake) -ChildPath "ctest.exe"
        if (Test-Path -LiteralPath $candidate) {
            return $candidate
        }
    }

    return "ctest"
}

function Read-IgnorePatterns {
    param(
        [string]$FilterFile,
        [string[]]$InlinePatterns
    )

    $patterns = @()
    if ($FilterFile.Length -eq 0) {
        $defaultFile = Join-Path -Path $PSScriptRoot -ChildPath "quiet-validation-ignore.txt"
        if (Test-Path -LiteralPath $defaultFile) {
            $FilterFile = $defaultFile
        }
    }

    if ($FilterFile.Length -gt 0 -and (Test-Path -LiteralPath $FilterFile)) {
        $patterns += Get-Content -LiteralPath $FilterFile |
            Where-Object { $_.Trim().Length -gt 0 -and -not $_.TrimStart().StartsWith("#") }
    }

    $patterns += $InlinePatterns
    return @($patterns | Where-Object { $_ -and $_.Length -gt 0 })
}

function Expand-ArgumentList {
    param([string[]]$Values)

    $expanded = @()
    foreach ($value in $Values) {
        if ($null -eq $value) {
            continue
        }
        $expanded += $value -split "[,\s]+" | Where-Object { $_.Length -gt 0 }
    }
    return @($expanded)
}

function Limit-LogSlug {
    param(
        [string]$Value,
        [int]$MaxLength = 96
    )

    if ($Value.Length -le $MaxLength) {
        return $Value
    }
    return $Value.Substring(0, $MaxLength)
}

function Test-IgnoredLine {
    param(
        [string]$Line,
        [string[]]$Patterns
    )

    foreach ($pattern in $Patterns) {
        if ($Line -match $pattern) {
            return $true
        }
    }
    return $false
}

function Measure-Log {
    param(
        [string]$Path,
        [string[]]$IgnorePatterns
    )

    $errorPattern = "(?i)(:\s*(fatal\s+)?error\s+[A-Z0-9]+:|^LINK\s*:\s*fatal error|^CMake Error|Errors while running CTest|Unable to find executable|\*\*\*Failed)"
    $warningPattern = "(?i)(:\s*warning\s+[A-Z0-9]+:|^LINK\s*:\s*warning\s+[A-Z0-9]+:|warning:)"
    $ctestSummaryPattern = "(\d+)% tests passed, (\d+) tests failed out of (\d+)"

    $lineCount = 0
    $rawErrors = 0
    $rawWarnings = 0
    $visibleErrors = 0
    $visibleWarnings = 0
    $ignoredErrors = 0
    $ignoredWarnings = 0
    $testsFailed = $null
    $testsTotal = $null

    if (Test-Path -LiteralPath $Path) {
        foreach ($line in Get-Content -LiteralPath $Path) {
            ++$lineCount
            $ignored = Test-IgnoredLine -Line $line -Patterns $IgnorePatterns
            if ($line -match $ctestSummaryPattern) {
                $testsFailed = [int]$Matches[2]
                $testsTotal = [int]$Matches[3]
            }
            if ($line -match $errorPattern) {
                ++$rawErrors
                if ($ignored) { ++$ignoredErrors } else { ++$visibleErrors }
            }
            if ($line -match $warningPattern) {
                ++$rawWarnings
                if ($ignored) { ++$ignoredWarnings } else { ++$visibleWarnings }
            }
        }
    }

    return [ordered]@{
        lineCount = $lineCount
        errors = $visibleErrors
        warnings = $visibleWarnings
        rawErrors = $rawErrors
        rawWarnings = $rawWarnings
        ignoredErrors = $ignoredErrors
        ignoredWarnings = $ignoredWarnings
        testsFailed = $testsFailed
        testsTotal = $testsTotal
    }
}

function Invoke-QuietStep {
    param(
        [string]$Name,
        [string]$Command,
        [string[]]$Arguments,
        [string]$LogPath,
        [string[]]$IgnorePatterns,
        [int]$FailureTailLines
    )

    $started = Get-Date
    $exitCode = 0
    $restoreNativeCommandPreference = $false
    if (Get-Variable -Name PSNativeCommandUseErrorActionPreference -ErrorAction SilentlyContinue) {
        $previousNativeCommandPreference = $PSNativeCommandUseErrorActionPreference
        $PSNativeCommandUseErrorActionPreference = $false
        $restoreNativeCommandPreference = $true
    }
    try {
        & $Command @Arguments *> $LogPath
        $exitCode = $LASTEXITCODE
        if ($null -eq $exitCode) {
            $exitCode = 0
        }
    }
    catch {
        $_ | Out-File -LiteralPath $LogPath -Append -Encoding utf8
        $exitCode = 1
    }
    finally {
        if ($restoreNativeCommandPreference) {
            $PSNativeCommandUseErrorActionPreference = $previousNativeCommandPreference
        }
    }

    $elapsed = [int]((Get-Date) - $started).TotalMilliseconds
    $summary = Measure-Log -Path $LogPath -IgnorePatterns $IgnorePatterns
    $result = [ordered]@{
        name = $Name
        exitCode = $exitCode
        elapsedMs = $elapsed
        log = $LogPath
        summary = $summary
    }

    if ($exitCode -ne 0 -and $FailureTailLines -gt 0 -and (Test-Path -LiteralPath $LogPath)) {
        $result.failureTail = @(Get-Content -LiteralPath $LogPath -Tail $FailureTailLines | ForEach-Object { [string]$_ })
    }

    return $result
}

$resolvedCMake = Resolve-CMakeCommand -Requested $CMakeCommand
$resolvedCTest = Resolve-CTestCommand -Requested $CTestCommand -ResolvedCMake $resolvedCMake
$BuildTargets = @(Expand-ArgumentList -Values $BuildTargets)
$IgnorePattern = @(Expand-ArgumentList -Values $IgnorePattern)
$PlatformBuildPresets = @(Expand-ArgumentList -Values $PlatformBuildPresets)
$PlatformBuildTargets = @(Expand-ArgumentList -Values $PlatformBuildTargets)
$AppleRemotePresets = @(Expand-ArgumentList -Values $AppleRemotePresets)
$ignorePatterns = Read-IgnorePatterns -FilterFile $IgnoreFilterFile -InlinePatterns $IgnorePattern

New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
$runId = Get-Date -Format "yyyyMMdd-HHmmss"
$started = Get-Date
$results = @()
$overallExitCode = 0

if ($Configure) {
    $log = Join-Path -Path $LogDir -ChildPath "$runId-configure-$BuildPreset.log"
    $result = Invoke-QuietStep `
        -Name "configure:$BuildPreset" `
        -Command $resolvedCMake `
        -Arguments @("--preset", $BuildPreset) `
        -LogPath $log `
        -IgnorePatterns $ignorePatterns `
        -FailureTailLines $FailureTailLines
    $results += $result
    if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
        $overallExitCode = $result.exitCode
    }
}

if (-not $SkipBuild) {
    $targets = @($BuildTargets | Where-Object { $_ -and $_.Length -gt 0 })
    if ($targets.Count -gt 0) {
        $safeTargets = Limit-LogSlug -Value (($targets -join "_") -replace "[^A-Za-z0-9_.-]", "_")
        $log = Join-Path -Path $LogDir -ChildPath "$runId-build-$BuildPreset-$Configuration-$safeTargets.log"
        $buildArgs = @("--build", "--preset", $BuildPreset, "--config", $Configuration, "--target") + $targets
        $result = Invoke-QuietStep `
            -Name ("build:{0}:{1}" -f $BuildPreset, $Configuration) `
            -Command $resolvedCMake `
            -Arguments $buildArgs `
            -LogPath $log `
            -IgnorePatterns $ignorePatterns `
            -FailureTailLines $FailureTailLines
        $result.targets = $targets
        $results += $result
        if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
            $overallExitCode = $result.exitCode
        }
    }
}

if (-not $SkipTests) {
    $safeRegex = if ($TestRegex.Length -gt 0) {
        Limit-LogSlug -Value ($TestRegex -replace "[^A-Za-z0-9_.-]", "_")
    } else {
        "all"
    }
    $log = Join-Path -Path $LogDir -ChildPath "$runId-test-$TestPreset-$Configuration-$safeRegex.log"
    $testArgs = @("--preset", $TestPreset, "--build-config", $Configuration, "--output-on-failure")
    if ($TestRegex.Length -gt 0) {
        $testArgs += @("-R", $TestRegex)
    }
    $result = Invoke-QuietStep `
        -Name ("test:{0}:{1}" -f $TestPreset, $Configuration) `
        -Command $resolvedCTest `
        -Arguments $testArgs `
        -LogPath $log `
        -IgnorePatterns $ignorePatterns `
        -FailureTailLines $FailureTailLines
    if ($TestRegex.Length -gt 0) {
        $result.testRegex = $TestRegex
    }
    $results += $result
    if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
        $overallExitCode = $result.exitCode
    }
}

if ($IncludePlatformBuild) {
    $safePresets = if ($PlatformBuildPresets.Count -gt 0) {
        Limit-LogSlug -Value (($PlatformBuildPresets -join "_") -replace "[^A-Za-z0-9_.-]", "_")
    } else {
        "defaults"
    }
    $log = Join-Path -Path $LogDir -ChildPath "$runId-platform-build-$safePresets.log"
    $platformArgs = @(
        "-ExecutionPolicy", "Bypass",
        "-File", (Join-Path -Path $PSScriptRoot -ChildPath "platform-build.ps1"),
        "-Quiet",
        "-FailureTailLines", [string]$FailureTailLines
    )
    if ($PlatformBuildPresets.Count -gt 0) {
        $platformArgs += @("-Presets", ($PlatformBuildPresets -join ","))
    }
    if ($PlatformBuildTargets.Count -gt 0) {
        $platformArgs += @("-Targets", ($PlatformBuildTargets -join ","))
    }
    $result = Invoke-QuietStep `
        -Name "platform-build" `
        -Command "powershell" `
        -Arguments $platformArgs `
        -LogPath $log `
        -IgnorePatterns $ignorePatterns `
        -FailureTailLines $FailureTailLines
    if ($PlatformBuildPresets.Count -gt 0) {
        $result.presets = $PlatformBuildPresets
    }
    if ($PlatformBuildTargets.Count -gt 0) {
        $result.targets = $PlatformBuildTargets
    }
    $results += $result
    if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
        $overallExitCode = $result.exitCode
    }
}

if ($IncludeAppleRemote) {
    $safePresets = if ($AppleRemotePresets.Count -gt 0) {
        Limit-LogSlug -Value (($AppleRemotePresets -join "_") -replace "[^A-Za-z0-9_.-]", "_")
    } else {
        "defaults"
    }
    $log = Join-Path -Path $LogDir -ChildPath "$runId-apple-remote-$safePresets.log"
    $appleArgs = @(
        "-ExecutionPolicy", "Bypass",
        "-File", (Join-Path -Path $PSScriptRoot -ChildPath "apple-remote-build.ps1"),
        "-Quiet",
        "-FailureTailLines", [string]$FailureTailLines
    )
    if ($AppleRemotePresets.Count -gt 0) {
        $appleArgs += @("-Presets", ($AppleRemotePresets -join ","))
    }
    $result = Invoke-QuietStep `
        -Name "apple-remote-build" `
        -Command "powershell" `
        -Arguments $appleArgs `
        -LogPath $log `
        -IgnorePatterns $ignorePatterns `
        -FailureTailLines $FailureTailLines
    if ($AppleRemotePresets.Count -gt 0) {
        $result.presets = $AppleRemotePresets
    }
    $results += $result
    if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
        $overallExitCode = $result.exitCode
    }
}

$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
$summaryPath = Join-Path -Path $LogDir -ChildPath "$runId-summary.json"
$payload = [ordered]@{
    command = "quiet-validate"
    exitCode = $overallExitCode
    elapsedMs = $elapsed
    buildPreset = $BuildPreset
    configuration = $Configuration
    testPreset = $TestPreset
    logDir = $LogDir
    summary = $summaryPath
    ignoreFilterFile = $IgnoreFilterFile
    ignorePatternCount = $ignorePatterns.Count
    results = $results
}

$payload | ConvertTo-Json -Depth 8 | Out-File -LiteralPath $summaryPath -Encoding utf8
$payload | ConvertTo-Json -Compress -Depth 8
exit $overallExitCode
