param(
    [Parameter(Mandatory = $true)][string]$InputFile,
    [string]$Server = 'http://127.0.0.1:8189',
    [int]$Width = 1728,
    [int]$Height = 736,
    [int]$Steps = 8,
    [double]$Denoise = 0.50,
    [int64]$Seed = 556589502035082,
    [double]$RtxScale = 1.5,
    [switch]$SkipRtx,
    [string]$PromptText = "Preserve the source video's composition, architecture, subject identity, camera path, timing and physical motion. Refine surfaces, atmospheric depth, water, mist and lighting with cinematic realism. Stable geometry, coherent temporal detail, no flicker, no deformation, no new large structures, no text, no logo.",
    [string]$OutputPrefix = 'video/H3_Redraw_ConcatAV_Turbo8_RTX_2K',
    [string]$SourceLatentPrefix = 'latents/H3_Redraw_Source_AV',
    [string]$SampledLatentPrefix = 'latents/H3_Redraw_Turbo8_Sampled',
    [string]$SourceLatentFile = '',
    [string]$SampledLatentFile = ''
)

$ErrorActionPreference = 'Stop'
if ($Width -le 0 -or $Height -le 0 -or $Width % 32 -ne 0 -or $Height % 32 -ne 0) {
    throw 'Width and height must be positive multiples of 32.'
}
if ($Steps -lt 4 -or $Steps -gt 8) {
    throw 'MiniMax H3 Turbo is designed for 4-8 steps.'
}
if ($Denoise -le 0 -or $Denoise -gt 1) {
    throw 'Denoise must be in (0, 1].'
}
if ($RtxScale -lt 1 -or $RtxScale -gt 4) {
    throw 'RTX scale must be between 1 and 4.'
}

$workspaceRoot = [System.IO.Path]::GetFullPath((Split-Path -Parent $PSScriptRoot))
$comfyOutput = [System.IO.Path]::GetFullPath((Join-Path $workspaceRoot 'ComfyUI\output'))
$comfyInput = [System.IO.Path]::GetFullPath((Join-Path $workspaceRoot 'ComfyUI\input'))
$inputPath = [System.IO.Path]::GetFullPath($InputFile)
if (-not (Test-Path -LiteralPath $inputPath -PathType Leaf)) {
    throw "Input video not found: $inputPath"
}

$outputBoundary = $comfyOutput.TrimEnd('\') + '\'
$inputBoundary = $comfyInput.TrimEnd('\') + '\'
if ($inputPath.StartsWith($outputBoundary, [System.StringComparison]::OrdinalIgnoreCase)) {
    $relative = $inputPath.Substring($outputBoundary.Length).Replace('\', '/')
    $comfyFile = "$relative [output]"
} elseif ($inputPath.StartsWith($inputBoundary, [System.StringComparison]::OrdinalIgnoreCase)) {
    $comfyFile = $inputPath.Substring($inputBoundary.Length).Replace('\', '/')
} else {
    throw 'Input video must be inside ComfyUI/input or ComfyUI/output.'
}

function Invoke-ComfyPrompt {
    param(
        [Parameter(Mandatory = $true)]$Prompt,
        [Parameter(Mandatory = $true)][string]$Stage,
        [int]$TimeoutSeconds = 7200
    )

    $request = @{
        prompt = $Prompt
        client_id = [guid]::NewGuid().ToString()
    }
    $body = $request | ConvertTo-Json -Depth 40 -Compress
    $queued = Invoke-RestMethod -Uri "$Server/prompt" -Method Post -ContentType 'application/json' -Body $body -TimeoutSec 30
    if (-not $queued.prompt_id) {
        throw "ComfyUI did not return a prompt_id: $($queued | ConvertTo-Json -Depth 10)"
    }

    $promptId = $queued.prompt_id
    Write-Host "${Stage}_PROMPT_ID=$promptId"
    $started = Get-Date
    $lastProgress = -20
    while ($true) {
        Start-Sleep -Seconds 2
        $history = Invoke-RestMethod -Uri "$Server/history/$promptId" -Method Get -TimeoutSec 30
        $entry = $history.$promptId
        if ($entry) {
            if ($entry.status.status_str -eq 'error') {
                $messages = $entry.status.messages | ConvertTo-Json -Depth 30
                throw "ComfyUI $Stage failed: $messages"
            }
            if ($entry.status.completed -and $entry.outputs) {
                $elapsed = ((Get-Date) - $started).TotalSeconds
                Write-Host ("${Stage}_ELAPSED_SECONDS=" + [math]::Round($elapsed, 2))
                return $entry
            }
        }

        $elapsed = ((Get-Date) - $started).TotalSeconds
        if ($elapsed -gt $TimeoutSeconds) {
            throw "Timed out after $TimeoutSeconds seconds waiting for $Stage."
        }
        if ($elapsed -ge ($lastProgress + 20)) {
            $lastProgress = [int]$elapsed
            Write-Host ("${Stage}_WAITING_SECONDS=" + [int]$elapsed)
        }
    }
}

function Clear-ComfyModels {
    $body = @{ unload_models = $true; free_memory = $true } | ConvertTo-Json -Compress
    Invoke-RestMethod -Uri "$Server/free" -Method Post -ContentType 'application/json' -Body $body -TimeoutSec 30 | Out-Null
    Start-Sleep -Seconds 3
}

function Get-LatentName {
    param([Parameter(Mandatory = $true)]$Entry, [Parameter(Mandatory = $true)][string]$NodeId)
    $record = $Entry.outputs.$NodeId.h3_latents | Select-Object -First 1
    if (-not $record) {
        throw "Node $NodeId returned no h3_latents record."
    }
    if ($record.subfolder) {
        return "$($record.subfolder)/$($record.filename)"
    }
    return $record.filename
}

Write-Host "INPUT=$inputPath"
Write-Host "COMFY_INPUT=$comfyFile"
Write-Host "REDRAW=${Width}x${Height} steps=$Steps denoise=$Denoise"
if ($SkipRtx) {
    Write-Host 'FINISH_ENGINE=UNIVERSAL'
} else {
    Write-Host "RTX_SCALE=$RtxScale quality=HIGHBITRATE_HIGH"
}

if ($SourceLatentFile) {
    $sourceLatent = $SourceLatentFile.Replace('\', '/')
    Write-Host "SOURCE_LATENT_REUSED=$sourceLatent"
} else {
    # Stage 1: encode the existing video's frames and audio before loading H3.
    Clear-ComfyModels
    $encodePrompt = @{
        '1' = @{ class_type = 'LoadVideo'; inputs = @{ file = $comfyFile } }
        '2' = @{ class_type = 'GetVideoComponents'; inputs = @{ video = @('1', 0) } }
        '3' = @{ class_type = 'ImageScale'; inputs = @{ image = @('2', 0); upscale_method = 'lanczos'; width = $Width; height = $Height; crop = 'center' } }
        '4' = @{ class_type = 'VAELoader'; inputs = @{ vae_name = 'minimax_h3_video_vae_fp16.safetensors' } }
        '5' = @{ class_type = 'VAEEncodeTiled'; inputs = @{ pixels = @('3', 0); vae = @('4', 0); tile_size = 512; overlap = 64; temporal_size = 64; temporal_overlap = 8 } }
        '6' = @{ class_type = 'VAELoader'; inputs = @{ vae_name = 'minimax_h3_audio_vae_fp32.safetensors' } }
        '7' = @{ class_type = 'VAEEncodeAudio'; inputs = @{ audio = @('2', 1); vae = @('6', 0) } }
        '8' = @{ class_type = 'PT_H3ConcatAVLatent'; inputs = @{ video_latent = @('5', 0); audio_latent = @('7', 0) } }
        '200' = @{ class_type = 'SaveMiniMaxH3Latent'; inputs = @{ samples = @('8', 0); filename_prefix = $SourceLatentPrefix } }
    }
    $encodeResult = Invoke-ComfyPrompt -Prompt $encodePrompt -Stage 'ENCODE_SOURCE_AV'
    $sourceLatent = Get-LatentName -Entry $encodeResult -NodeId '200'
    Write-Host "SOURCE_LATENT=$sourceLatent"
}

if ($SampledLatentFile) {
    $sampledLatent = $SampledLatentFile.Replace('\', '/')
    Write-Host "SAMPLED_LATENT_REUSED=$sampledLatent"
} else {
    # Stage 2: reload only the two anchor frames plus H3 and redraw the saved AV latent.
    Clear-ComfyModels
    $samplePrompt = @{
    '1' = @{ class_type = 'LoadVideo'; inputs = @{ file = $comfyFile } }
    '2' = @{ class_type = 'GetVideoComponents'; inputs = @{ video = @('1', 0) } }
    '3' = @{ class_type = 'ImageScale'; inputs = @{ image = @('2', 0); upscale_method = 'lanczos'; width = $Width; height = $Height; crop = 'center' } }
    '24' = @{ class_type = 'ImageFromBatch'; inputs = @{ image = @('3', 0); batch_index = 0; length = 1 } }
    '25' = @{ class_type = 'ImageFromBatch'; inputs = @{ image = @('3', 0); batch_index = -1; length = 1 } }
    '26' = @{ class_type = 'GetImageSize'; inputs = @{ image = @('3', 0) } }
    '6' = @{ class_type = 'UNETLoader'; inputs = @{ unet_name = 'minimax_h3_fl2va_pruned_int8_convrot.safetensors'; weight_dtype = 'default' } }
    '7' = @{ class_type = 'MiniMaxH3TurboLoRA'; inputs = @{ model = @('6', 0); lora_name = 'minimax_h3_turbo_v4_step600_ema.safetensors'; strength = 1.0; low_vram = $true } }
    '11' = @{ class_type = 'VAELoader'; inputs = @{ vae_name = 'minimax_h3_video_vae_fp16.safetensors' } }
    '13' = @{ class_type = 'CLIPLoader'; inputs = @{ clip_name = 'qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors'; type = 'minimax'; device = 'default' } }
    '104' = @{ class_type = 'MiniMaxH3ImageToVideo'; inputs = @{ clip = @('13', 0); vae = @('11', 0); first_frame = @('24', 0); last_frame = @('25', 0); prompt = $PromptText; width = @('26', 0); height = @('26', 1); length = @('26', 2) } }
    '9' = @{ class_type = 'BasicScheduler'; inputs = @{ model = @('7', 0); scheduler = 'simple'; steps = $Steps; denoise = $Denoise } }
    '14' = @{ class_type = 'SamplerCustomAdvanced'; inputs = @{ noise = @('15', 0); guider = @('16', 0); sampler = @('17', 0); sigmas = @('9', 0); latent_image = @('300', 0) } }
    '15' = @{ class_type = 'RandomNoise'; inputs = @{ noise_seed = $Seed } }
    '16' = @{ class_type = 'BasicGuider'; inputs = @{ model = @('7', 0); conditioning = @('104', 0) } }
    '17' = @{ class_type = 'MiniMaxH3TurboSampler'; inputs = @{} }
    '200' = @{ class_type = 'SaveMiniMaxH3Latent'; inputs = @{ samples = @('14', 0); filename_prefix = $SampledLatentPrefix } }
    '300' = @{ class_type = 'LoadMiniMaxH3Latent'; inputs = @{ filename = $sourceLatent } }
    }
    $sampleResult = Invoke-ComfyPrompt -Prompt $samplePrompt -Stage 'H3_REDRAW'
    $sampledLatent = Get-LatentName -Entry $sampleResult -NodeId '200'
    Write-Host "SAMPLED_LATENT=$sampledLatent"
}

# Stage 3: decode and mux. RTX profiles upscale here; universal profiles leave
# the source-size redraw for the vendor-neutral finishing stage in H3 Studio.
Clear-ComfyModels
$finishPrompt = @{
    '10' = @{ class_type = 'VAEDecode'; inputs = @{ samples = @('300', 0); vae = @('11', 0) } }
    '11' = @{ class_type = 'VAELoader'; inputs = @{ vae_name = 'minimax_h3_video_vae_fp16.safetensors' } }
    '23' = @{ class_type = 'VAEDecodeAudio'; inputs = @{ samples = @('300', 0); vae = @('24', 0) } }
    '24' = @{ class_type = 'VAELoader'; inputs = @{ vae_name = 'minimax_h3_audio_vae_fp32.safetensors' } }
    '21' = @{ class_type = 'CreateVideo'; inputs = @{ images = @('10', 0); audio = @('23', 0); fps = 24.0; bit_depth = 8 } }
    '22' = @{ class_type = 'SaveVideo'; inputs = @{ video = @('21', 0); filename_prefix = $OutputPrefix; format = 'auto'; codec = 'auto' } }
    '300' = @{ class_type = 'LoadMiniMaxH3Latent'; inputs = @{ filename = $sampledLatent } }
}
if (-not $SkipRtx) {
    $finishPrompt['20'] = @{ class_type = 'RTXVideoSuperResolution'; inputs = @{ images = @('10', 0); resize_type = 'scale by multiplier'; 'resize_type.scale' = $RtxScale; quality = 'HIGHBITRATE_HIGH' } }
    $finishPrompt['21'].inputs.images = @('20', 0)
}
$finishStage = if ($SkipRtx) { 'DECODE_MUX' } else { 'DECODE_RTX_MUX' }
$finishResult = Invoke-ComfyPrompt -Prompt $finishPrompt -Stage $finishStage
Write-Host 'VIDEO_OUTPUT_BEGIN'
$finishResult.outputs.'22' | ConvertTo-Json -Depth 30
Write-Host 'VIDEO_OUTPUT_END'
Clear-ComfyModels
