param(
    [int]$Steps = 6,
    [bool]$Turbo = $true,
    [ValidateSet('', 'true', 'false', '1', '0')]
    [string]$TurboMode = '',
    [string]$TurboLora = 'minimax_h3_turbo_v4_step600_ema.safetensors',
    [double]$TurboStrength = 1.0,
    [bool]$TurboLowVram = $true,
    [double]$Duration = 0.5,
    [int]$Width = 1280,
    [int]$Height = 736,
    [int64]$Seed = 556589502035082,
    [string]$Server = 'http://127.0.0.1:8189',
    [string]$Latent = '',
    [string]$FirstFrame = '',
    [string]$LastFrame = '',
    [string]$PromptFile = '',
    [string]$PromptText = 'A cinematic sunrise over a calm misty lake. A deep red paper airplane glides smoothly above the water while the camera tracks beside it. Warm natural light, detailed reflections, subtle wind and quiet stereo ambience, no dialogue, no text, no logo.',
    [string]$OutputPrefix = 'video/H3_736p_Maxine_Source',
    [string]$LatentPrefix = 'latents/H3_736p_Maxine_Latent'
)

$ErrorActionPreference = 'Stop'
if ($TurboMode) {
    $Turbo = $TurboMode -in @('true', '1')
}
if ($Duration -le 0) {
    throw 'Duration must be greater than zero.'
}
if ($Turbo -and ($Steps -lt 4 -or $Steps -gt 8)) {
    throw 'Turbo LoRA is designed for 4-8 steps; use 6-8 for final-quality clips.'
}
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 ($PromptFile) {
    if (-not (Test-Path -LiteralPath $PromptFile -PathType Leaf)) {
        throw "Prompt file not found: $PromptFile"
    }
    $PromptText = Get-Content -LiteralPath $PromptFile -Raw -Encoding UTF8
}
if ([string]::IsNullOrWhiteSpace($PromptText)) {
    throw 'Prompt text must not be empty.'
}
$frameLength = [math]::Max(5, [math]::Round($Duration * 24))
$frameLength += (5 - ($frameLength % 17) + 17) % 17

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

    $request = @{
        prompt = $Prompt
        client_id = [guid]::NewGuid().ToString()
    }
    $body = $request | ConvertTo-Json -Depth 30 -Compress
    $queued = Invoke-RestMethod -Uri "$Server/prompt" -Method Post -ContentType 'application/json' -Body $body
    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
        $entry = $history.$promptId
        if ($entry) {
            if ($entry.status.status_str -eq 'error') {
                $messages = $entry.status.messages | ConvertTo-Json -Depth 20
                throw "ComfyUI $Stage failed: $messages"
            }
            if ($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 3600) {
            throw "Timed out after 60 minutes 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 | Out-Null
    Start-Sleep -Seconds 3
}

$latentName = $Latent
$modelOutput = @('6', 0)
$samplerNode = @{
    class_type = 'KSamplerSelect'
    inputs = @{ sampler_name = 'res_multistep' }
}
if ($Turbo) {
    $modelOutput = @('7', 0)
    $samplerNode = @{
        class_type = 'MiniMaxH3TurboSampler'
        inputs = @{}
    }
    Write-Host "TURBO_LORA=$TurboLora"
    Write-Host "TURBO_STRENGTH=$TurboStrength"
    Write-Host "TURBO_LOW_VRAM=$TurboLowVram"
} else {
    Write-Host 'TURBO_LORA=disabled'
}
$h3Inputs = @{
    clip = @('13', 0)
    vae = @('11', 0)
    prompt = $PromptText
    width = $Width
    height = $Height
    length = $frameLength
}
if ($FirstFrame) {
    $h3Inputs.first_frame = @('100', 0)
}
if ($LastFrame) {
    $h3Inputs.last_frame = @('101', 0)
}

# Stage 1: H3 sampling only. Persist the joint video/audio latent so the 20 GB
# diffusion model can be released before either VAE is loaded for decoding.
$samplePrompt = @{
    '6' = @{
        class_type = 'UNETLoader'
        inputs = @{
            unet_name = 'minimax_h3_fl2va_pruned_int8_convrot.safetensors'
            weight_dtype = 'default'
        }
    }
    '9' = @{
        class_type = 'BasicScheduler'
        inputs = @{
            model = $modelOutput
            scheduler = 'simple'
            steps = $Steps
            denoise = 1.0
        }
    }
    '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'
        }
    }
    '14' = @{
        class_type = 'SamplerCustomAdvanced'
        inputs = @{
            noise = @('15', 0)
            guider = @('16', 0)
            sampler = @('17', 0)
            sigmas = @('9', 0)
            latent_image = @('104', 1)
        }
    }
    '15' = @{
        class_type = 'RandomNoise'
        inputs = @{ noise_seed = $Seed }
    }
    '16' = @{
        class_type = 'BasicGuider'
        inputs = @{
            model = $modelOutput
            conditioning = @('104', 0)
        }
    }
    '17' = $samplerNode
    '104' = @{
        class_type = 'MiniMaxH3ImageToVideo'
        inputs = $h3Inputs
    }
    '200' = @{
        class_type = 'SaveMiniMaxH3Latent'
        inputs = @{
            samples = @('14', 0)
            filename_prefix = $LatentPrefix
        }
    }
}
if ($Turbo) {
    $samplePrompt.'7' = @{
        class_type = 'MiniMaxH3TurboLoRA'
        inputs = @{
            model = @('6', 0)
            lora_name = $TurboLora
            strength = $TurboStrength
            low_vram = $TurboLowVram
        }
    }
}
if ($FirstFrame) {
    $samplePrompt.'100' = @{
        class_type = 'LoadImage'
        inputs = @{ image = $FirstFrame }
    }
}
if ($LastFrame) {
    $samplePrompt.'101' = @{
        class_type = 'LoadImage'
        inputs = @{ image = $LastFrame }
    }
}

if (-not $latentName) {
    Clear-ComfyModels
    $sampleResult = Invoke-ComfyPrompt -Prompt $samplePrompt -Stage 'SAMPLE'
    $latentOutput = $sampleResult.outputs.'200'
    $latentRecord = $latentOutput.h3_latents | Select-Object -First 1
    if (-not $latentRecord) {
        throw "SaveMiniMaxH3Latent returned no latent file: $($latentOutput | ConvertTo-Json -Depth 20)"
    }
    $latentName = if ($latentRecord.subfolder) {
        "$($latentRecord.subfolder)/$($latentRecord.filename)"
    } else {
        $latentRecord.filename
    }
}
Write-Output "LATENT=$latentName"

# Stage 2: unload H3, then decode the saved joint latent with only the audio and
# video VAEs resident. This avoids severe dynamic-VRAM paging on a 24 GB GPU.
Clear-ComfyModels
$decodePrompt = @{
    '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' }
    }
    '91' = @{
        class_type = 'CreateVideo'
        inputs = @{
            images = @('10', 0)
            audio = @('23', 0)
            fps = 24.0
            bit_depth = 8
        }
    }
    '92' = @{
        class_type = 'SaveVideo'
        inputs = @{
            video = @('91', 0)
            filename_prefix = $OutputPrefix
            format = 'auto'
            codec = 'auto'
        }
    }
    '300' = @{
        class_type = 'LoadMiniMaxH3Latent'
        inputs = @{ filename = $latentName }
    }
}

$decodeResult = Invoke-ComfyPrompt -Prompt $decodePrompt -Stage 'DECODE'
$videoOutput = $decodeResult.outputs.'92'
Write-Output 'VIDEO_OUTPUT_BEGIN'
$videoOutput | ConvertTo-Json -Depth 30
Write-Output 'VIDEO_OUTPUT_END'
Clear-ComfyModels
