[CmdletBinding()]
param(
    [Parameter(Mandatory = $true)]
    [string]$ReferenceDirectory,
    [Parameter(Mandatory = $true)]
    [string]$Checkpoint,
    [string]$GodotPath,
    [string]$Output = "user://render_camera_pose_sweep",
    [double[]]$YawOffsets = @(-10.0, 0.0, 10.0),
    [double[]]$PitchOffsets = @(-10.0, 0.0, 10.0),
    [double]$CameraFov = 62.0,
    [double[]]$CameraFovValues = @(),
    [int]$ViewportWidth = 1280,
    [int]$ViewportHeight = 900,
    [int[]]$ComparisonRegion = @(),
    [double]$WaitSeconds = 8.0,
    [double]$MeasureSeconds = 0.1,
    [switch]$PlanOnly
)

$ErrorActionPreference = "Stop"
$repoRoot = Split-Path -Parent $PSScriptRoot
if (-not $GodotPath) {
    $godotCommand = Get-Command godot -ErrorAction SilentlyContinue
    if ($godotCommand) {
        $GodotPath = $godotCommand.Source
    } else {
        $GodotPath = Join-Path $env:TEMP "godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe"
    }
}
if (-not (Test-Path -LiteralPath $GodotPath)) {
    throw "Godot executable not found: $GodotPath"
}
if (-not (Test-Path -LiteralPath $ReferenceDirectory)) {
    throw "Reference directory not found: $ReferenceDirectory"
}
if ($ComparisonRegion.Count -notin @(0, 4)) {
    throw "ComparisonRegion requires x,y,width,height"
}

$invariantCulture = [Globalization.CultureInfo]::InvariantCulture
$isJointFovSweep = $CameraFovValues.Count -gt 0
$effectiveCameraFovValues = if ($isJointFovSweep) { $CameraFovValues } else { @($CameraFov) }
$waitSecondsText = $WaitSeconds.ToString($invariantCulture)
$measureSecondsText = $MeasureSeconds.ToString($invariantCulture)
$absoluteOutput = if ($Output.StartsWith("user://")) {
    Join-Path $env:APPDATA "Godot\app_userdata\OpenWC\$($Output.Substring(7))"
} else {
    [IO.Path]::GetFullPath((Join-Path $repoRoot $Output))
}
$ranking = [Collections.Generic.List[object]]::new()

Push-Location $repoRoot
try {
    foreach ($cameraFovValue in $effectiveCameraFovValues) {
        foreach ($yawOffset in $YawOffsets) {
            foreach ($pitchOffset in $PitchOffsets) {
            $cameraFovText = $cameraFovValue.ToString("0.###", $invariantCulture)
            $yawText = $yawOffset.ToString("0.###", $invariantCulture)
            $pitchText = $pitchOffset.ToString("0.###", $invariantCulture)
            $candidateName = "yaw_$($yawText.Replace('-', 'n').Replace('.', '_'))__pitch_$($pitchText.Replace('-', 'n').Replace('.', '_'))"
            if ($isJointFovSweep) {
                $candidateName = "fov_$($cameraFovText.Replace('.', '_'))__$candidateName"
            }
            $candidateOutput = "$Output/$candidateName"
            $comparisonReport = Join-Path $absoluteOutput "$candidateName.json"

            if ($PlanOnly) {
                $ranking.Add([pscustomobject]@{
                    yaw_offset_degrees = $yawOffset
                    pitch_offset_degrees = $pitchOffset
                    camera_fov_degrees = $cameraFovValue
                    candidate_directory = $candidateOutput
                })
                continue
            }

            $captureArguments = @(
                "--path", ".", "--script", "res://src/tools/capture_render_checkpoints.gd", "--",
                "--output", $candidateOutput, "--only", $Checkpoint, "--single-pass",
                "--camera-fov", $cameraFovText,
                "--camera-yaw-offset", $yawText, "--camera-pitch-offset", $pitchText,
                "--viewport-width", $ViewportWidth, "--viewport-height", $ViewportHeight,
                "--wait", $waitSecondsText, "--measure", $measureSecondsText
            )
            $captureProcess = Start-Process -FilePath $GodotPath -ArgumentList $captureArguments -Wait -PassThru
            if ($captureProcess.ExitCode -ne 0) {
                throw "Capture failed for yaw=$yawText pitch=$pitchText"
            }

            $comparisonArguments = @(
                "--headless", "--path", ".", "--script", "res://src/tools/compare_render_checkpoints.gd", "--",
                "--reference", $ReferenceDirectory, "--candidate", $candidateOutput,
                "--only", $Checkpoint, "--output", $comparisonReport
            )
            if ($ComparisonRegion.Count -eq 4) {
                $comparisonArguments += @("--region", ($ComparisonRegion -join ","))
            }
            $comparisonProcess = Start-Process -FilePath $GodotPath -ArgumentList $comparisonArguments -Wait -PassThru -NoNewWindow
            if ($comparisonProcess.ExitCode -notin @(0, 1)) {
                throw "Comparison failed for yaw=$yawText pitch=$pitchText"
            }

            $comparison = Get-Content -Raw -LiteralPath $comparisonReport | ConvertFrom-Json
            if ($comparison.compared_count -lt 1) {
                throw "No paired image was compared for yaw=$yawText pitch=$pitchText"
            }
            $metricResults = @($comparison.results | Where-Object { $null -ne $_.mean_perceptual_error })
            if ($metricResults.Count -ne $comparison.compared_count) {
                $statuses = ($comparison.results.status | Sort-Object -Unique) -join ","
                throw "Comparison produced no numeric metric for yaw=$yawText pitch=$pitchText statuses=$statuses"
            }
            $meanError = ($metricResults | Measure-Object -Property mean_perceptual_error -Average).Average
            $changedRatio = ($metricResults | Measure-Object -Property changed_pixel_ratio -Average).Average
            $ranking.Add([pscustomobject]@{
                yaw_offset_degrees = $yawOffset
                pitch_offset_degrees = $pitchOffset
                camera_fov_degrees = $cameraFovValue
                mean_perceptual_error = $meanError
                changed_pixel_ratio = $changedRatio
                candidate_directory = $candidateOutput
                comparison_report = $comparisonReport
            })
            }
        }
    }

    $rankedCandidates = if ($PlanOnly) {
        @($ranking)
    } else {
        @($ranking | Sort-Object mean_perceptual_error, changed_pixel_ratio)
    }
    $rankingPath = Join-Path $absoluteOutput "ranking.json"
    $rankingDocument = [ordered]@{
        schema_version = 1
        checkpoint = $Checkpoint
        reference_directory = [IO.Path]::GetFullPath($ReferenceDirectory)
        viewport = @($ViewportWidth, $ViewportHeight)
        wait_seconds = $WaitSeconds
        measure_seconds = $MeasureSeconds
        comparison_region = @($ComparisonRegion)
        candidates = $rankedCandidates
    }
    New-Item -ItemType Directory -Force -Path $absoluteOutput | Out-Null
    $rankingDocument | ConvertTo-Json -Depth 6 | Set-Content -Encoding UTF8 -LiteralPath $rankingPath
    $rankedCandidates | Format-Table camera_fov_degrees, yaw_offset_degrees, pitch_offset_degrees, mean_perceptual_error, changed_pixel_ratio
    $completionKind = if ($PlanOnly) { "plan" } else { "ranking" }
    Write-Output "Camera pose sweep $completionKind completed: $rankingPath"
} finally {
    Pop-Location
}
