param(
    [Parameter(Mandatory = $true)]
    [string]$ReplayPath,
    [string]$ProjectRoot = 'C:\HyperTwist',
    [string]$UnrealEditorCmdPath = 'C:\Program Files\Epic Games\UE_5.7\Engine\Binaries\Win64\UnrealEditor-Cmd.exe',
    [string]$OutputDirectory,
    [string]$MapPath = '/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining',
    [string]$LevelSequencePath = '/Game/HyperTwistTraining/Sequences/LS_HyperTwist_ClassicReplayCapture.LS_HyperTwist_ClassicReplayCapture',
    [int]$OutputWidth = 1280,
    [int]$OutputHeight = 720,
    [int]$OutputFrameRate = 30,
    [int]$TailDurationMs = 1500
)

$ErrorActionPreference = 'Stop'

function Format-HyperTwistMilliseconds {
    param([int]$Milliseconds)

    $SafeMilliseconds = [Math]::Max($Milliseconds, 0)
    $Minutes = [int][Math]::Floor($SafeMilliseconds / 60000)
    $Seconds = [int][Math]::Floor(($SafeMilliseconds / 1000) % 60)
    $Remainder = [int]($SafeMilliseconds % 1000)
    return '{0:D2}:{1:D2}.{2:D3}' -f $Minutes, $Seconds, $Remainder
}

function ConvertTo-HyperTwistFileStem {
    param([string]$Value)

    $Lowered = $Value.ToLowerInvariant()
    $Sanitized = [regex]::Replace($Lowered, '[^a-z0-9]+', '-')
    $Sanitized = [regex]::Replace($Sanitized, '-+', '-').Trim('-')
    if ([string]::IsNullOrWhiteSpace($Sanitized)) {
        return 'classic-cube-replay'
    }

    return $Sanitized
}

function Get-ClassicCubeReplayMetadata {
    param([string]$ReplayFilePath)

    $ReplayJson = Get-Content -LiteralPath $ReplayFilePath -Raw | ConvertFrom-Json
    $ReplayId = [string]$ReplayJson.replayId
    if ([string]::IsNullOrWhiteSpace($ReplayId)) {
        $ReplayId = [string]$ReplayJson.sessionId
    }
    if ([string]::IsNullOrWhiteSpace($ReplayId)) {
        $ReplayId = 'classic-cube-replay'
    }

    $DerivedSummary = $ReplayJson.derivedSummary
    $ReplayDurationMs = 0
    if ($null -ne $DerivedSummary -and $null -ne $DerivedSummary.totalTimeMs) {
        $ReplayDurationMs = [int]$DerivedSummary.totalTimeMs
    }

    foreach ($Event in @($ReplayJson.events)) {
        if ($null -ne $Event.timeMs) {
            $ReplayDurationMs = [Math]::Max($ReplayDurationMs, [int]$Event.timeMs)
        }
    }

    $MoveCount = 0
    if ($null -ne $DerivedSummary -and $null -ne $DerivedSummary.moveCount) {
        $MoveCount = [int]$DerivedSummary.moveCount
    }

    $Tps = 0.0
    if ($null -ne $DerivedSummary -and $null -ne $DerivedSummary.tps) {
        $Tps = [double]$DerivedSummary.tps
    }

    $Result = ''
    if ($null -ne $DerivedSummary -and $null -ne $DerivedSummary.result) {
        $Result = [string]$DerivedSummary.result
    }

    $Metadata = $null
    if ($null -ne $ReplayJson.annotationsJson -and -not [string]::IsNullOrWhiteSpace([string]$ReplayJson.annotationsJson)) {
        $Metadata = ([string]$ReplayJson.annotationsJson | ConvertFrom-Json)
    }

    [pscustomobject]@{
        ReplayId = $ReplayId
        ReplayDurationMs = $ReplayDurationMs
        MoveCount = $MoveCount
        TPS = $Tps
        Result = $Result
        ScrambleNotation = if ($null -ne $Metadata) { [string]$Metadata.scrambleNotation } else { '' }
        ScrambleLength = if ($null -ne $Metadata -and $null -ne $Metadata.scrambleLength) { [int]$Metadata.scrambleLength } else { 0 }
        FinalTimeLabel = if ($Result -ieq 'dnf') { 'DNF' } else { Format-HyperTwistMilliseconds -Milliseconds $ReplayDurationMs }
    }
}

function Invoke-ClassicCubeReplayExportRender {
    param(
        [string]$UProjectPath,
        [string]$MapAssetPath,
        [string]$ReplayFilePath,
        [string]$OutputKind,
        [string]$OutputBaseName,
        [string]$OutputRoot,
        [string]$SequenceAssetPath,
        [int]$FrameRate,
        [int]$Width,
        [int]$Height,
        [int]$CustomEndFrame,
        [string]$ReportPath,
        [double]$AutoQuitTailSeconds
    )

    $ExecutorArgs = @(
        $UProjectPath,
        $MapAssetPath,
        '-game',
        '-MoviePipelineLocalExecutorClass=/Script/MovieRenderPipelineCore.MoviePipelinePythonHostExecutor',
        '-ExecutorPythonClass=/Engine/PythonTypes.HyperTwistMoviePipelineReplayExecutor',
        "-LevelSequence=$SequenceAssetPath",
        "-HyperTwistReplayPath=$ReplayFilePath",
        "-HyperTwistRenderMapPath=$MapAssetPath",
        '-HyperTwistDisableHud',
        '-HyperTwistAutoQuitAfterReplay',
        "-HyperTwistReplayAutoQuitTailSeconds=$AutoQuitTailSeconds",
        "-HyperTwistOutputDirectory=$OutputRoot",
        "-HyperTwistOutputBaseName=$OutputBaseName",
        "-HyperTwistOutputKind=$OutputKind",
        "-HyperTwistOutputWidth=$Width",
        "-HyperTwistOutputHeight=$Height",
        "-HyperTwistOutputFrameRate=$FrameRate",
        '-HyperTwistCustomStartFrame=0',
        "-HyperTwistCustomEndFrame=$CustomEndFrame",
        "-HyperTwistExportReportPath=$ReportPath",
        '-RenderOffScreen',
        '-windowed',
        "-ResX=$Width",
        "-ResY=$Height",
        '-unattended',
        '-nop4',
        '-nosplash',
        '-stdout',
        '-FullStdOutLogOutput',
        '-log'
    )

    if (Test-Path -LiteralPath $ReportPath) {
        Remove-Item -LiteralPath $ReportPath -Force
    }

    Get-ChildItem -LiteralPath $OutputRoot -Recurse -File -ErrorAction SilentlyContinue |
        Where-Object { $_.BaseName -like "$OutputBaseName*" } |
        Remove-Item -Force -ErrorAction SilentlyContinue

    & $UnrealEditorCmdPath @ExecutorArgs
    if ($LASTEXITCODE -ne 0) {
        throw "Replay export render '$OutputKind' failed with exit code $LASTEXITCODE."
    }

    if (-not (Test-Path -LiteralPath $ReportPath)) {
        throw "Replay export render '$OutputKind' did not produce the expected report '$ReportPath'."
    }

    $RenderReport = Get-Content -LiteralPath $ReportPath -Raw | ConvertFrom-Json
    if (-not $RenderReport.success) {
        throw "Replay export render '$OutputKind' reported failure in '$ReportPath'."
    }

    if (@($RenderReport.files).Count -le 0) {
        throw "Replay export render '$OutputKind' produced no output files according to '$ReportPath'."
    }

    return $RenderReport
}

function Write-HyperTwistShareCard {
    param(
        [string]$BackgroundImagePath,
        [string]$ShareCardPath,
        [string]$Title,
        [string]$Subtitle,
        [string]$Footer
    )

    Add-Type -AssemblyName System.Drawing

    $BackgroundImage = [System.Drawing.Image]::FromFile($BackgroundImagePath)
    try {
        $Bitmap = New-Object System.Drawing.Bitmap($BackgroundImage.Width, $BackgroundImage.Height)
        $Graphics = [System.Drawing.Graphics]::FromImage($Bitmap)
        try {
            $Graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
            $Graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
            $Graphics.DrawImage($BackgroundImage, 0, 0, $Bitmap.Width, $Bitmap.Height)

            $OverlayHeight = [int]($Bitmap.Height * 0.34)
            $OverlayTop = $Bitmap.Height - $OverlayHeight
            $OverlayBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(192, 8, 13, 22))
            $Graphics.FillRectangle($OverlayBrush, 0, $OverlayTop, $Bitmap.Width, $OverlayHeight)
            $OverlayBrush.Dispose()

            $AccentPen = New-Object System.Drawing.Pen([System.Drawing.Color]::FromArgb(210, 245, 184, 65), 6)
            $Graphics.DrawLine($AccentPen, 48, $OverlayTop + 24, $Bitmap.Width - 48, $OverlayTop + 24)
            $AccentPen.Dispose()

            $TitleFont = New-Object System.Drawing.Font('Segoe UI Semibold', 42)
            $SubtitleFont = New-Object System.Drawing.Font('Segoe UI', 20)
            $FooterFont = New-Object System.Drawing.Font('Segoe UI', 16)
            $PrimaryBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(255, 250, 250, 248))
            $SecondaryBrush = New-Object System.Drawing.SolidBrush([System.Drawing.Color]::FromArgb(230, 232, 238, 246))

            $Graphics.DrawString($Title, $TitleFont, $PrimaryBrush, 48, $OverlayTop + 42)
            $Graphics.DrawString($Subtitle, $SubtitleFont, $SecondaryBrush, 48, $OverlayTop + 108)
            $Graphics.DrawString($Footer, $FooterFont, $SecondaryBrush, 48, $OverlayTop + 146)

            $TitleFont.Dispose()
            $SubtitleFont.Dispose()
            $FooterFont.Dispose()
            $PrimaryBrush.Dispose()
            $SecondaryBrush.Dispose()

            $ShareDirectory = Split-Path -Parent $ShareCardPath
            if (-not [string]::IsNullOrWhiteSpace($ShareDirectory)) {
                New-Item -ItemType Directory -Force -Path $ShareDirectory | Out-Null
            }

            $Bitmap.Save($ShareCardPath, [System.Drawing.Imaging.ImageFormat]::Png)
        }
        finally {
            $Graphics.Dispose()
            $Bitmap.Dispose()
        }
    }
    finally {
        $BackgroundImage.Dispose()
    }
}

if (-not (Test-Path -LiteralPath $UnrealEditorCmdPath)) {
    throw "UnrealEditor-Cmd.exe was not found at '$UnrealEditorCmdPath'."
}

$UProjectPath = Join-Path $ProjectRoot 'UnrealHyperTwist\UnrealHyperTwist.uproject'
if (-not (Test-Path -LiteralPath $UProjectPath)) {
    throw "UnrealHyperTwist project file was not found at '$UProjectPath'."
}

if (-not (Test-Path -LiteralPath $ReplayPath)) {
    throw "Replay file was not found at '$ReplayPath'."
}

if ([string]::IsNullOrWhiteSpace($OutputDirectory)) {
    $OutputDirectory = Join-Path $ProjectRoot 'Saved\MediaExport\ClassicCube'
}

$AuthoringScriptPath = Join-Path $ProjectRoot 'scripts\hypertwist_author_classic_cube_export_assets.py'
if (-not (Test-Path -LiteralPath $AuthoringScriptPath)) {
    throw "Classic-cube export authoring script was not found at '$AuthoringScriptPath'."
}

$ReplayInfo = Get-ClassicCubeReplayMetadata -ReplayFilePath $ReplayPath
$OutputBaseName = 'classic-cube-{0}' -f (ConvertTo-HyperTwistFileStem -Value $ReplayInfo.ReplayId)
$ResolvedOutputDirectory = [System.IO.Path]::GetFullPath($OutputDirectory)
$VideoDirectory = Join-Path $ResolvedOutputDirectory 'video'
$StillDirectory = Join-Path $ResolvedOutputDirectory 'still'
$ValidationDirectory = Join-Path $ResolvedOutputDirectory 'validation'

New-Item -ItemType Directory -Force -Path $VideoDirectory, $StillDirectory, $ValidationDirectory | Out-Null

$RenderEndFrame = [Math]::Max(
    1,
    [int][Math]::Ceiling((($ReplayInfo.ReplayDurationMs + $TailDurationMs) / 1000.0) * $OutputFrameRate)
)
$AutoQuitTailSeconds = [Math]::Round($TailDurationMs / 1000.0, 2)

$NormalizedAuthoringScriptPath = $AuthoringScriptPath -replace '\\', '/'
$AuthoringArgs = @(
    $UProjectPath,
    $MapPath,
    "-ExecutePythonScript=$NormalizedAuthoringScriptPath",
    '-NullRHI',
    '-unattended',
    '-nop4',
    '-nosplash',
    '-stdout',
    '-FullStdOutLogOutput',
    '-log'
)

& $UnrealEditorCmdPath @AuthoringArgs
if ($LASTEXITCODE -ne 0) {
    throw "Replay export asset authoring failed with exit code $LASTEXITCODE."
}

$VideoReportPath = Join-Path $ValidationDirectory "$OutputBaseName-video-report.json"
$StillReportPath = Join-Path $ValidationDirectory "$OutputBaseName-still-report.json"

$VideoReport = Invoke-ClassicCubeReplayExportRender `
    -UProjectPath $UProjectPath `
    -MapAssetPath $MapPath `
    -ReplayFilePath $ReplayPath `
    -OutputKind 'video' `
    -OutputBaseName $OutputBaseName `
    -OutputRoot $VideoDirectory `
    -SequenceAssetPath $LevelSequencePath `
    -FrameRate $OutputFrameRate `
    -Width $OutputWidth `
    -Height $OutputHeight `
    -CustomEndFrame $RenderEndFrame `
    -ReportPath $VideoReportPath `
    -AutoQuitTailSeconds $AutoQuitTailSeconds

$StillReport = Invoke-ClassicCubeReplayExportRender `
    -UProjectPath $UProjectPath `
    -MapAssetPath $MapPath `
    -ReplayFilePath $ReplayPath `
    -OutputKind 'still' `
    -OutputBaseName "$OutputBaseName-still" `
    -OutputRoot $StillDirectory `
    -SequenceAssetPath $LevelSequencePath `
    -FrameRate $OutputFrameRate `
    -Width $OutputWidth `
    -Height $OutputHeight `
    -CustomEndFrame $RenderEndFrame `
    -ReportPath $StillReportPath `
    -AutoQuitTailSeconds $AutoQuitTailSeconds

$VideoFile = @($VideoReport.files | Where-Object { $_ -like '*.mp4' } | Select-Object -First 1)[0]
if ([string]::IsNullOrWhiteSpace($VideoFile) -or -not (Test-Path -LiteralPath $VideoFile)) {
    throw "Replay export did not produce an MP4 output."
}
$RawVideoFile = $VideoFile
$CanonicalVideoFile = Join-Path $VideoDirectory "$OutputBaseName.mp4"
if ($RawVideoFile -ne $CanonicalVideoFile) {
    Copy-Item -LiteralPath $RawVideoFile -Destination $CanonicalVideoFile -Force
}
$VideoFile = $CanonicalVideoFile

$StillFrameFile = @($StillReport.files | Where-Object { $_ -like '*.png' } | Select-Object -Last 1)[0]
if ([string]::IsNullOrWhiteSpace($StillFrameFile) -or -not (Test-Path -LiteralPath $StillFrameFile)) {
    throw "Replay export did not produce a still-frame PNG output."
}
$RawStillFrameFile = $StillFrameFile
$CanonicalStillFrameFile = Join-Path $StillDirectory "$OutputBaseName-still.png"
if ($RawStillFrameFile -ne $CanonicalStillFrameFile) {
    Copy-Item -LiteralPath $RawStillFrameFile -Destination $CanonicalStillFrameFile -Force
}
$StillFrameFile = $CanonicalStillFrameFile

$ShareCardPath = Join-Path $ResolvedOutputDirectory "$OutputBaseName-share.png"
$ShareSubtitle = if ($ReplayInfo.ScrambleLength -gt 0) {
    '{0}-move scramble' -f $ReplayInfo.ScrambleLength
}
else {
    'scramble metadata unavailable'
}
$ShareFooter = if ([string]::IsNullOrWhiteSpace($ReplayInfo.ScrambleNotation)) {
    '{0} moves | {1:N2} TPS' -f $ReplayInfo.MoveCount, $ReplayInfo.TPS
}
else {
    '{0} | {1} moves | {2:N2} TPS' -f $ReplayInfo.ScrambleNotation, $ReplayInfo.MoveCount, $ReplayInfo.TPS
}

Write-HyperTwistShareCard `
    -BackgroundImagePath $StillFrameFile `
    -ShareCardPath $ShareCardPath `
    -Title $ReplayInfo.FinalTimeLabel `
    -Subtitle $ShareSubtitle `
    -Footer $ShareFooter

$AggregateReportPath = Join-Path $ValidationDirectory "$OutputBaseName-report.json"
$AggregateReport = [ordered]@{
    reportVersion = 'ht-classic-cube-media-export-report/v1'
    replayPath = (Resolve-Path -LiteralPath $ReplayPath).Path
    outputBaseName = $OutputBaseName
    outputWidth = $OutputWidth
    outputHeight = $OutputHeight
    outputFrameRate = $OutputFrameRate
    replayDurationMs = $ReplayInfo.ReplayDurationMs
    tailDurationMs = $TailDurationMs
    renderEndFrame = $RenderEndFrame
    videoPath = $VideoFile
    rawVideoPath = $RawVideoFile
    stillFramePath = $StillFrameFile
    rawStillFramePath = $RawStillFrameFile
    shareCardPath = $ShareCardPath
    videoReportPath = $VideoReportPath
    stillReportPath = $StillReportPath
    title = $ReplayInfo.FinalTimeLabel
    subtitle = $ShareSubtitle
    footer = $ShareFooter
}

$AggregateReport | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $AggregateReportPath -Encoding UTF8

Write-Host "Classic-cube media export succeeded."
Write-Host "Video: $VideoFile"
Write-Host "Share card: $ShareCardPath"
