[CmdletBinding()]
param(
    [Parameter(Mandatory)]
    [ValidatePattern('^[0-9a-fA-F-]{36}$')]
    [string]$SubscriptionId,

    [ValidateNotNullOrEmpty()][string]$Location = 'westeurope',
    [ValidateNotNullOrEmpty()][string]$ReplicaLocation = 'northeurope',
    [ValidateNotNullOrEmpty()][string]$BuildVmSize = 'Standard_D2s_v5',
    [ValidateNotNullOrEmpty()][string]$ValidationVmSize = 'Standard_D2s_v5',
    [ValidateRange(60,960)][int]$BuildTimeoutMinutes = 180,
    [ValidateRange(30,180)][int]$ReplicationTimeoutMinutes = 90,
    [ValidateSet('Preflight','Foundation','Gallery','BuildV1','DeployV1','BuildV2','RolloutV2','RollbackV1','Validate','All')]
    [string]$Stage = 'All'
)

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
. (Join-Path $PSScriptRoot 'GoldenImageWorkshop.Common.ps1')

$script:Paths = Get-GoldenImageWorkshopPaths -ScriptRoot $PSScriptRoot
Initialize-GoldenImageWorkshopDirectory -Paths $script:Paths
$script:State = $null

function Save-State { Write-GoldenImageWorkshopState -State $script:State -Path $script:Paths.StatePath }

function Set-StageComplete {
    param([Parameter(Mandatory)][string]$Name)
    $timestamp=[DateTime]::UtcNow.ToString('o')
    $stageNames=@($script:State.Stages.PSObject.Properties | ForEach-Object Name)
    if ($stageNames -contains $Name) { $script:State.Stages.$Name=$timestamp }
    else { $script:State.Stages | Add-Member -NotePropertyName $Name -NotePropertyValue $timestamp }
    Save-State
}

function Test-StageComplete {
    param([Parameter(Mandatory)][string]$Name)
    @($script:State.Stages.PSObject.Properties | ForEach-Object Name) -contains $Name
}

function Assert-StageComplete {
    param([Parameter(Mandatory)][string]$Name)
    if (-not (Test-StageComplete -Name $Name)) { throw "Stage $Name must complete first." }
}

function Get-WorkshopTags {
    @(
        'workshop=azure-golden-images-deep-dive',
        "workshop-session=$($script:State.SessionId)",
        'environment=lab',
        'managed-by=Workshopz'
    )
}

function Assert-OwnedResourceGroups {
    if (-not $script:State) { return }
    foreach ($name in @($script:State.ResourceGroups)) {
        $group=Invoke-GoldenImageAzJson -Arguments @('group','show','--name',$name) -AllowFailure -Quiet
        if ($group -and ([string]$group.tags.workshop -ne [string]$script:State.WorkshopTag -or
            [string]$group.tags.'workshop-session' -ne [string]$script:State.SessionId)) {
            throw "Resource group $name exists but isn't owned by this workshop session."
        }
    }
}

function Ensure-State {
    if (-not $script:State) { $script:State=Read-GoldenImageWorkshopState -Path $script:Paths.StatePath -AllowMissing }
    if (-not $script:State) { Invoke-Preflight }
    if ([string]$script:State.SubscriptionId -ne $SubscriptionId -or [string]$script:State.Location -ne $Location -or
        [string]$script:State.ReplicaLocation -ne $ReplicaLocation -or [string]$script:State.BuildVmSize -ne $BuildVmSize -or
        [string]$script:State.ValidationVmSize -ne $ValidationVmSize) {
        throw 'Parameters do not match the active private workshop state.'
    }
    Assert-OwnedResourceGroups
    Invoke-GoldenImageAzCli -Arguments @('account','set','--subscription',$SubscriptionId) -Quiet | Out-Null
}

function Invoke-Preflight {
    Write-Host 'AZURE GOLDEN IMAGES — READ-ONLY PREFLIGHT' -ForegroundColor Cyan
    if ($PSVersionTable.PSVersion.Major -lt 7) { throw 'PowerShell 7 or later is required.' }
    if (-not (Get-Command az -ErrorAction SilentlyContinue)) { throw 'Azure CLI is required.' }

    $account=Invoke-GoldenImageAzJson -Arguments @('account','show','--subscription',$SubscriptionId) -Quiet
    if (-not $account -or [string]$account.state -ne 'Enabled') { throw 'The requested Azure subscription is unavailable or disabled.' }
    $existing=Read-GoldenImageWorkshopState -Path $script:Paths.StatePath -AllowMissing
    if ($existing) {
        $script:State=$existing
        Assert-OwnedResourceGroups
        Write-Host 'PASS  Existing private state matches the tagged workshop resources.' -ForegroundColor Green
        return
    }

    $resourceGroups=@('rg-golden-images-deepdive-we','rg-golden-images-staging-v1-we','rg-golden-images-staging-v2-we')
    foreach ($name in $resourceGroups) {
        if ((Invoke-GoldenImageAzCli -Arguments @('group','exists','--name',$name,'--output','tsv') -Quiet).Output.Trim() -eq 'true') {
            throw "Resource group $name already exists and no matching workshop state exists."
        }
    }

    $providerBaseline=[ordered]@{}
    foreach ($provider in @('Microsoft.Compute','Microsoft.Network','Microsoft.ManagedIdentity','Microsoft.VirtualMachineImages','Microsoft.ContainerInstance')) {
        $providerBaseline[$provider]=[string](Invoke-GoldenImageAzJson -Arguments @('provider','show','--namespace',$provider,'--query','registrationState') -Quiet)
    }

    $source=Invoke-GoldenImageAzJson -Arguments @(
        'vm','image','show','--location',$Location,'--urn','MicrosoftWindowsServer:WindowsServer:2025-datacenter-azure-edition:latest'
    ) -Quiet
    if (-not $source) { throw 'Windows Server 2025 Azure Edition is unavailable in the primary region.' }
    $securityFeature=@($source.features | Where-Object name -eq 'SecurityType' | Select-Object -First 1)
    if (-not $securityFeature -or [string]$securityFeature.value -notmatch 'TrustedLaunch') { throw 'The source image is not Trusted Launch capable.' }

    foreach ($region in @($Location,$ReplicaLocation)) {
        $regionInfo=Invoke-GoldenImageAzJson -Arguments @('account','list-locations','--query',"[?name=='$region'] | [0]") -Quiet
        if (-not $regionInfo) { throw "Azure region $region is unavailable." }
    }
    $imageBuilderProvider=Invoke-GoldenImageAzJson -Arguments @('provider','show','--namespace','Microsoft.VirtualMachineImages') -Quiet
    $imageTemplateType=@($imageBuilderProvider.resourceTypes | Where-Object resourceType -eq 'imageTemplates' | Select-Object -First 1)
    $supportedLocations=@($imageTemplateType.locations | ForEach-Object { $_.Replace(' ','').ToLowerInvariant() })
    if ($supportedLocations -notcontains $Location.Replace(' ','').ToLowerInvariant()) { throw 'VM Image Builder is unavailable in the primary region.' }

    $usage=Invoke-GoldenImageAzJson -Arguments @('vm','list-usage','--location',$Location) -Quiet
    $regional=@($usage | Where-Object { $_.name.value -eq 'cores' } | Select-Object -First 1)
    $dFamily=@($usage | Where-Object { $_.name.value -match '^standardDSv5Family$' } | Select-Object -First 1)
    if (-not $regional -or ([int]$regional.limit-[int]$regional.currentValue) -lt 4) { throw 'At least four regional vCPUs are required.' }
    if ($dFamily -and ([int]$dFamily.limit-[int]$dFamily.currentValue) -lt 2) { throw 'At least two DSv5-family vCPUs are required.' }

    $cliHelp=(Invoke-GoldenImageAzCli -Arguments @('image','builder','create','--help') -Quiet).Output
    if ($cliHelp -notmatch 'image-template') { throw 'The installed Azure CLI lacks VM Image Builder commands.' }
    $signedInUser=Invoke-GoldenImageAzJson -Arguments @('ad','signed-in-user','show') -AllowFailure -Quiet
    if ($signedInUser) {
        $assignments=Invoke-GoldenImageAzJson -Arguments @('role','assignment','list','--assignee-object-id',[string]$signedInUser.id,'--all','--include-inherited') -Quiet
        $roles=@($assignments.roleDefinitionName)
        if ($roles -notcontains 'Owner') { throw 'Owner is required to register Image Builder and create scoped role assignments.' }
    }

    $suffix=New-GoldenImageSuffix
    $mainGroup=$resourceGroups[0]
    $scope="/subscriptions/$SubscriptionId"
    $mainGroupId="$scope/resourceGroups/$mainGroup"
    $script:State=[pscustomobject]@{
        SchemaVersion=1
        CreatedUtc=[DateTime]::UtcNow.ToString('o')
        UpdatedUtc=[DateTime]::UtcNow.ToString('o')
        SessionId=$suffix
        SubscriptionId=$SubscriptionId
        SubscriptionName=[string]$account.name
        TenantId=[string]$account.tenantId
        Location=$Location
        ReplicaLocation=$ReplicaLocation
        BuildVmSize=$BuildVmSize
        ValidationVmSize=$ValidationVmSize
        BuildTimeoutMinutes=$BuildTimeoutMinutes
        ReplicationTimeoutMinutes=$ReplicationTimeoutMinutes
        WorkshopTag='azure-golden-images-deep-dive'
        ResourceGroups=$resourceGroups
        MainResourceGroup=$mainGroup
        MainResourceGroupId=$mainGroupId
        StagingV1ResourceGroup=$resourceGroups[1]
        StagingV2ResourceGroup=$resourceGroups[2]
        IdentityName='id-aib-golden-images-we'
        IdentityId=$null
        IdentityPrincipalId=$null
        GalleryName='galWorkshopzGoldenWe'
        ImageDefinitionName='win2025Golden'
        ImageDefinitionId="$mainGroupId/providers/Microsoft.Compute/galleries/galWorkshopzGoldenWe/images/win2025Golden"
        V1Version='1.0.0'
        V2Version='2.0.0'
        V1TemplateName='aib-win2025-golden-v1'
        V2TemplateName='aib-win2025-golden-v2'
        V1RunOutputName='win2025Golden-v1'
        V2RunOutputName='win2025Golden-v2'
        VnetName='vnet-golden-images-validation-we'
        VnetPrefix='10.92.0.0/16'
        SubnetName='snet-private-validation'
        SubnetPrefix='10.92.1.0/24'
        NsgName='nsg-golden-images-validation'
        VmName='vm-golden-current'
        NicName='nic-vm-golden-current'
        OsDiskName='osdisk-vm-golden-current'
        AdminUsername='workshopadmin'
        AdminPassword=(New-GoldenImagePassword)
        SourceImage='MicrosoftWindowsServer:WindowsServer:2025-datacenter-azure-edition:latest'
        SourceSecurityType=[string]$securityFeature.value
        ProviderBaseline=[pscustomobject]$providerBaseline
        RegisteredByWorkshop=@()
        CurrentVersion=$null
        V1Build=$null
        V2Build=$null
        V1Proof=$null
        V2Proof=$null
        RollbackProof=$null
        Stages=[pscustomobject]@{}
    }
    Save-State

    Write-Host "PASS  Subscription enabled: $($account.name)" -ForegroundColor Green
    Write-Host 'PASS  Windows Server 2025 source is Gen2 and Trusted Launch capable.' -ForegroundColor Green
    Write-Host 'PASS  West Europe build and North Europe replication regions are available.' -ForegroundColor Green
    Write-Host 'PASS  32 regional vCPUs and D-family capacity satisfy the workshop baseline.' -ForegroundColor Green
    if ($providerBaseline['Microsoft.VirtualMachineImages'] -eq 'Registered') {
        Write-Host 'PASS  Microsoft.VirtualMachineImages is already registered.' -ForegroundColor Green
    }
    else {
        Write-Host 'READY Microsoft.VirtualMachineImages will be registered by Foundation; Preflight made no Azure changes.' -ForegroundColor Yellow
    }
    Write-Host 'PASS  No workshop resource-group collisions were found.' -ForegroundColor Green
    Write-Host 'PREFLIGHT RESULT: READY — ZERO AZURE MUTATIONS' -ForegroundColor Cyan
}

function Invoke-Foundation {
    Ensure-State
    Write-Host 'Stage: Foundation' -ForegroundColor Cyan
    foreach ($provider in @('Microsoft.VirtualMachineImages','Microsoft.ContainerInstance')) {
        $state=[string](Invoke-GoldenImageAzJson -Arguments @('provider','show','--namespace',$provider,'--query','registrationState') -Quiet)
        if ($state -ne 'Registered') {
            Invoke-GoldenImageAzCli -Arguments @('provider','register','--namespace',$provider,'--wait') -Quiet | Out-Null
            $script:State.RegisteredByWorkshop=@($script:State.RegisteredByWorkshop)+$provider
            Save-State
        }
    }

    $tags=Get-WorkshopTags
    foreach ($groupName in @($script:State.ResourceGroups)) {
        $existing=Invoke-GoldenImageAzJson -Arguments @('group','show','--name',$groupName) -AllowFailure -Quiet
        if (-not $existing) { Invoke-GoldenImageAzJson -Arguments (@('group','create','--name',$groupName,'--location',$Location,'--tags')+$tags) -Quiet | Out-Null }
    }
    Assert-OwnedResourceGroups

    $identity=Invoke-GoldenImageAzJson -Arguments @('identity','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.IdentityName) -AllowFailure -Quiet
    if (-not $identity) {
        $identity=Invoke-GoldenImageAzJson -Arguments (@('identity','create','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.IdentityName,'--location',$Location,'--tags')+$tags) -Quiet
    }
    $script:State.IdentityId=[string]$identity.id
    $script:State.IdentityPrincipalId=[string]$identity.principalId
    Save-State

    foreach ($groupName in @($script:State.ResourceGroups)) {
        $groupId="/subscriptions/$SubscriptionId/resourceGroups/$groupName"
        $existingAssignments=@(Invoke-GoldenImageAzJson -Arguments @('role','assignment','list','--assignee-object-id',$script:State.IdentityPrincipalId,'--scope',$groupId) -Quiet)
        if (@($existingAssignments | Where-Object roleDefinitionName -eq 'Contributor').Count -eq 0) {
            Invoke-GoldenImageAzJson -Arguments @(
                'role','assignment','create','--assignee-object-id',$script:State.IdentityPrincipalId,
                '--assignee-principal-type','ServicePrincipal','--role','Contributor','--scope',$groupId
            ) -Quiet | Out-Null
        }
    }

    if (-not (Invoke-GoldenImageAzJson -Arguments @('network','nsg','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.NsgName) -AllowFailure -Quiet)) {
        Invoke-GoldenImageAzJson -Arguments (@('network','nsg','create','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.NsgName,'--location',$Location,'--tags')+$tags) -Quiet | Out-Null
    }
    if (-not (Invoke-GoldenImageAzJson -Arguments @('network','vnet','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VnetName) -AllowFailure -Quiet)) {
        Invoke-GoldenImageAzJson -Arguments (@('network','vnet','create','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VnetName,'--location',$Location,'--address-prefixes',$script:State.VnetPrefix,'--tags')+$tags) -Quiet | Out-Null
    }
    if (-not (Invoke-GoldenImageAzJson -Arguments @('network','vnet','subnet','show','--resource-group',$script:State.MainResourceGroup,'--vnet-name',$script:State.VnetName,'--name',$script:State.SubnetName) -AllowFailure -Quiet)) {
        Invoke-GoldenImageAzJson -Arguments @(
            'network','vnet','subnet','create','--resource-group',$script:State.MainResourceGroup,'--vnet-name',$script:State.VnetName,
            '--name',$script:State.SubnetName,'--address-prefixes',$script:State.SubnetPrefix,'--network-security-group',$script:State.NsgName,
            '--default-outbound-access','false'
        ) -Quiet | Out-Null
    }
    Set-StageComplete -Name Foundation
    Write-Host 'Foundation completed: scoped identity, three tagged resource groups, and a private validation network.' -ForegroundColor Green
}

function Invoke-Gallery {
    Ensure-State; Assert-StageComplete Foundation
    Write-Host 'Stage: Gallery' -ForegroundColor Cyan
    $tags=Get-WorkshopTags
    if (-not (Invoke-GoldenImageAzJson -Arguments @('sig','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName) -AllowFailure -Quiet)) {
        Invoke-GoldenImageAzJson -Arguments (@('sig','create','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--location',$Location,'--description','Workshopz Windows Server 2025 golden images','--tags')+$tags) -Quiet | Out-Null
    }
    $definition=Invoke-GoldenImageAzJson -Arguments @(
        'sig','image-definition','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,
        '--gallery-image-definition',$script:State.ImageDefinitionName
    ) -AllowFailure -Quiet
    if (-not $definition) {
        $definition=Invoke-GoldenImageAzJson -Arguments (@(
            'sig','image-definition','create','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,
            '--gallery-image-definition',$script:State.ImageDefinitionName,'--location',$Location,'--publisher','BeyondCloudWithChriz',
            '--offer','WindowsServer2025','--sku','golden-trustedlaunch','--os-type','Windows','--os-state','Generalized',
            '--hyper-v-generation','V2','--features','SecurityType=TrustedLaunchSupported','--description','Patched Windows Server 2025 golden image for Workshopz','--tags'
        )+$tags) -Quiet
    }
    $security=@($definition.features | Where-Object name -eq 'SecurityType' | Select-Object -First 1)
    if (-not $security -or [string]$security.value -ne 'TrustedLaunchSupported') { throw 'The image definition is not TrustedLaunchSupported.' }
    Set-StageComplete -Name Gallery
    Write-Host 'Compute Gallery and Trusted Launch-capable Gen2 image definition are ready.' -ForegroundColor Green
}

function New-ImageTemplateBody {
    param([Parameter(Mandatory)][string]$Version,[Parameter(Mandatory)][string]$TemplateName,[Parameter(Mandatory)][string]$RunOutputName,[Parameter(Mandatory)][string]$StagingGroup)
    $isV2=($Version -eq $script:State.V2Version)
    $configure=(Get-Content -LiteralPath (Join-Path $PSScriptRoot 'windows\Configure-GoldenImage.ps1') -Raw).Replace('__IMAGE_VERSION__',$Version).Replace('__IS_V2__',$isV2.ToString())
    $finalize=(Get-Content -LiteralPath (Join-Path $PSScriptRoot 'windows\Finalize-GoldenImage.ps1') -Raw).Replace('__IMAGE_VERSION__',$Version)
    $validate=(Get-Content -LiteralPath (Join-Path $PSScriptRoot 'windows\Validate-GoldenImage.ps1') -Raw).Replace('__IMAGE_VERSION__',$Version)
    $identities=[ordered]@{}; $identities[[string]$script:State.IdentityId]=[ordered]@{}
    [ordered]@{
        apiVersion='2024-02-01'
        type='Microsoft.VirtualMachineImages/imageTemplates'
        location=$Location
        tags=[ordered]@{ workshop=$script:State.WorkshopTag; 'workshop-session'=$script:State.SessionId; environment='lab'; 'managed-by'='Workshopz'; release=$Version }
        identity=[ordered]@{ type='UserAssigned'; userAssignedIdentities=$identities }
        properties=[ordered]@{
            buildTimeoutInMinutes=$BuildTimeoutMinutes
            stagingResourceGroup="/subscriptions/$SubscriptionId/resourceGroups/$StagingGroup"
            vmProfile=[ordered]@{ vmSize=$BuildVmSize; osDiskSizeGB=127 }
            source=[ordered]@{ type='PlatformImage'; publisher='MicrosoftWindowsServer'; offer='WindowsServer'; sku='2025-datacenter-azure-edition'; version='latest' }
            customize=@(
                [ordered]@{ type='PowerShell'; name='ConfigureGoldenImage'; runElevated=$true; runAsSystem=$true; inline=@("powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $(ConvertTo-GoldenImageEncodedCommand $configure)") },
                [ordered]@{ type='WindowsRestart'; name='RestartAfterFeatures'; restartTimeout='30m' },
                [ordered]@{ type='WindowsUpdate'; name='InstallCurrentUpdates'; searchCriteria='IsInstalled=0'; filters=@("exclude:`$_.Title -like '*Preview*'",'include:$true'); updateLimit=40 },
                [ordered]@{ type='WindowsRestart'; name='RestartAfterUpdates'; restartTimeout='30m' },
                [ordered]@{ type='PowerShell'; name='FinalizeGoldenImage'; runElevated=$true; runAsSystem=$true; inline=@("powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $(ConvertTo-GoldenImageEncodedCommand $finalize)") }
            )
            validate=[ordered]@{
                continueDistributeOnFailure=$false
                sourceValidationOnly=$false
                inVMValidations=@([ordered]@{ type='PowerShell'; name='ValidateGoldenImage'; runElevated=$true; runAsSystem=$true; inline=@("powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -EncodedCommand $(ConvertTo-GoldenImageEncodedCommand $validate)") })
            }
            distribute=@([ordered]@{
                type='SharedImage'
                galleryImageId="$($script:State.ImageDefinitionId)/versions/$Version"
                runOutputName=$RunOutputName
                excludeFromLatest=$false
                artifactTags=[ordered]@{ workshop=$script:State.WorkshopTag; release=$Version; 'managed-by'='Workshopz' }
                targetRegions=@(
                    [ordered]@{ name=$Location; replicaCount=1; storageAccountType='Standard_ZRS' },
                    [ordered]@{ name=$ReplicaLocation; replicaCount=1; storageAccountType='Standard_ZRS' }
                )
            })
        }
    }
}

function Wait-GalleryVersionReplication {
    param([Parameter(Mandatory)][string]$Version)
    Wait-GoldenImageCondition -TimeoutSeconds ($ReplicationTimeoutMinutes*60) -PollSeconds 30 -Description "gallery replication for version $Version" -Probe {
        Invoke-GoldenImageAzJson -Arguments @(
            'sig','image-version','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,
            '--gallery-image-definition',$script:State.ImageDefinitionName,'--gallery-image-version',$Version,'--expand','ReplicationStatus'
        ) -Quiet
    } -Success {
        param($value)
        if (-not $value -or [string]$value.provisioningState -ne 'Succeeded') { return $false }
        $aggregate=[string]$value.replicationStatus.aggregatedState
        if ($aggregate -eq 'Failed') { throw "Gallery replication failed for image version $Version." }
        $wanted=@($Location,$ReplicaLocation) | ForEach-Object { $_.Replace(' ','').ToLowerInvariant() }
        $regional=@($value.replicationStatus.summary)
        $completed=@($regional | Where-Object {
            $name=[string]$_.region
            $normalized=$name.Replace(' ','').ToLowerInvariant()
            $wanted -contains $normalized -and [string]$_.state -eq 'Completed'
        })
        $aggregate -eq 'Completed' -and $completed.Count -eq $wanted.Count
    }
}

function Invoke-ImageBuild {
    param([Parameter(Mandatory)][string]$Version,[Parameter(Mandatory)][string]$TemplateName,[Parameter(Mandatory)][string]$RunOutputName,[Parameter(Mandatory)][string]$StagingGroup,[Parameter(Mandatory)][ValidateSet('V1Build','V2Build')][string]$StateProperty)
    $versionExisting=Invoke-GoldenImageAzJson -Arguments @('sig','image-version','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--gallery-image-definition',$script:State.ImageDefinitionName,'--gallery-image-version',$Version) -AllowFailure -Quiet
    if ($versionExisting) {
        Write-Host "Image version $Version already exists; validating its regional replicas before reuse." -ForegroundColor Yellow
        $replicated=Wait-GalleryVersionReplication -Version $Version
        $script:State.$StateProperty=[pscustomobject]@{ Version=$Version; Status='Succeeded'; Reused=$true; CompletedUtc=[DateTime]::UtcNow.ToString('o') }
        Save-State
        return
    }
    $templateBody=New-ImageTemplateBody -Version $Version -TemplateName $TemplateName -RunOutputName $RunOutputName -StagingGroup $StagingGroup
    $templatePath=Join-Path $script:Paths.TemplateRoot "$TemplateName.json"
    Write-GoldenImageJson -InputObject $templateBody -Path $templatePath
    $template=Invoke-GoldenImageAzJson -Arguments @('image','builder','show','--resource-group',$script:State.MainResourceGroup,'--name',$TemplateName) -AllowFailure -Quiet
    if (-not $template) {
        Invoke-GoldenImageAzJson -Arguments @('image','builder','create','--resource-group',$script:State.MainResourceGroup,'--name',$TemplateName,'--image-template',$templatePath) -Quiet | Out-Null
    }
    $started=[DateTime]::UtcNow
    Invoke-GoldenImageAzCli -Arguments @('image','builder','run','--resource-group',$script:State.MainResourceGroup,'--name',$TemplateName,'--no-wait') -Quiet | Out-Null
    Write-Host "Image Builder is patching, restarting, validating, and replicating Windows Server 2025 version $Version..." -ForegroundColor Yellow
    $run=Wait-GoldenImageCondition -TimeoutSeconds (($BuildTimeoutMinutes+30)*60) -PollSeconds 30 -Description "Image Builder run $TemplateName" -Probe {
        Invoke-GoldenImageAzJson -Arguments @('image','builder','show','--resource-group',$script:State.MainResourceGroup,'--name',$TemplateName) -Quiet
    } -Success {
        param($value)
        if (-not $value.lastRunStatus) { return $false }
        $state=[string]$value.lastRunStatus.runState
        if ($state -eq 'Failed' -or $state -eq 'Canceled') { throw "Image Builder $TemplateName failed: $($value.lastRunStatus.message)" }
        $state -eq 'Succeeded'
    }
    $output=Invoke-GoldenImageAzJson -Arguments @('image','builder','show-runs','--resource-group',$script:State.MainResourceGroup,'--name',$TemplateName,'--output-name',$RunOutputName) -Quiet
    Write-GoldenImageJson -InputObject ([pscustomobject]@{ Template=$run; RunOutput=$output }) -Path (Join-Path $script:Paths.RawEvidenceRoot "$TemplateName-run.json")
    $versionResource=Invoke-GoldenImageAzJson -Arguments @('sig','image-version','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--gallery-image-definition',$script:State.ImageDefinitionName,'--gallery-image-version',$Version) -Quiet
    if (-not $versionResource -or [string]$versionResource.provisioningState -ne 'Succeeded') { throw "Gallery image version $Version wasn't distributed successfully." }
    $replicated=Wait-GalleryVersionReplication -Version $Version
    Write-GoldenImageJson -InputObject $replicated -Path (Join-Path $script:Paths.RawEvidenceRoot "$TemplateName-replication.json")
    $script:State.$StateProperty=[pscustomobject]@{
        Version=$Version
        Status='Succeeded'
        Reused=$false
        DurationMinutes=[math]::Round(([DateTime]::UtcNow-$started).TotalMinutes,1)
        CompletedUtc=[DateTime]::UtcNow.ToString('o')
        Regions=@($Location,$ReplicaLocation)
    }
    Save-State
}

function Invoke-BuildV1 {
    Ensure-State; Assert-StageComplete Gallery
    Write-Host 'Stage: BuildV1' -ForegroundColor Cyan
    Invoke-ImageBuild -Version $script:State.V1Version -TemplateName $script:State.V1TemplateName -RunOutputName $script:State.V1RunOutputName -StagingGroup $script:State.StagingV1ResourceGroup -StateProperty V1Build
    Set-StageComplete -Name BuildV1
    Write-Host 'Version 1.0.0 passed Image Builder validation and regional distribution.' -ForegroundColor Green
}

function Remove-ValidationVm {
    $vm=Invoke-GoldenImageAzJson -Arguments @('vm','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName) -AllowFailure -Quiet
    if ($vm) {
        if ([string]$vm.tags.workshop -ne [string]$script:State.WorkshopTag -or [string]$vm.tags.'workshop-session' -ne [string]$script:State.SessionId) { throw 'Refusing to replace an unowned validation VM.' }
        Invoke-GoldenImageAzCli -Arguments @('vm','delete','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--yes') -Quiet | Out-Null
        Wait-GoldenImageCondition -TimeoutSeconds 900 -PollSeconds 15 -Description 'validation VM deletion' -Probe {
            Invoke-GoldenImageAzJson -Arguments @('vm','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName) -AllowFailure -Quiet
        } -Success { param($value) $null -eq $value } | Out-Null
    }
    foreach ($resource in @(
        @{Type='network nic';Name=$script:State.NicName},
        @{Type='disk';Name=$script:State.OsDiskName}
    )) {
        if ($resource.Type -eq 'network nic') { Invoke-GoldenImageAzCli -Arguments @('network','nic','delete','--resource-group',$script:State.MainResourceGroup,'--name',$resource.Name) -AllowFailure -Quiet | Out-Null }
        else { Invoke-GoldenImageAzCli -Arguments @('disk','delete','--resource-group',$script:State.MainResourceGroup,'--name',$resource.Name,'--yes') -AllowFailure -Quiet | Out-Null }
    }
}

function New-ValidationVm {
    param([Parameter(Mandatory)][string]$Version)
    Remove-ValidationVm
    $tags=Get-WorkshopTags
    Invoke-GoldenImageAzJson -Arguments (@(
        'network','nic','create','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.NicName,'--location',$Location,
        '--vnet-name',$script:State.VnetName,'--subnet',$script:State.SubnetName,'--network-security-group',$script:State.NsgName,'--tags'
    )+$tags) -Quiet | Out-Null
    $versionId="$($script:State.ImageDefinitionId)/versions/$Version"
    Invoke-GoldenImageAzJson -Arguments (@(
        'vm','create','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--computer-name','vm-golden-val','--location',$Location,
        '--nics',$script:State.NicName,'--image',$versionId,'--size',$ValidationVmSize,'--admin-username',$script:State.AdminUsername,
        '--admin-password',$script:State.AdminPassword,'--security-type','TrustedLaunch','--enable-secure-boot','true','--enable-vtpm','true',
        '--os-disk-name',$script:State.OsDiskName,'--storage-sku','StandardSSD_LRS','--os-disk-delete-option','Delete','--nic-delete-option','Delete','--tags'
    )+$tags) -Quiet | Out-Null
    $vm=Wait-GoldenImageCondition -TimeoutSeconds 1800 -PollSeconds 20 -Description "Trusted Launch VM from version $Version" -Probe {
        Invoke-GoldenImageAzJson -Arguments @('vm','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--show-details') -Quiet
    } -Success { param($value) [string]$value.provisioningState -eq 'Succeeded' -and [string]$value.powerState -match 'running' }
    if ($vm.publicIps) { throw 'The validation VM unexpectedly has a public IP.' }
}

function Get-ValidationProof {
    param([Parameter(Mandatory)][string]$ExpectedVersion)
    $response=Invoke-GoldenImageAzJson -Arguments @(
        'vm','run-command','invoke','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,
        '--command-id','RunPowerShellScript','--scripts',"@$(Join-Path $PSScriptRoot 'windows\Test-GoldenImage.ps1')",'--parameters',"ExpectedVersion=$ExpectedVersion"
    ) -Quiet
    $message=Get-GoldenImageRunCommandMessage -Response $response
    $proof=ConvertFrom-GoldenImageMarkedJson -Text $message
    if (-not [bool]$proof.Passed) { throw "Validation VM proof failed for image $ExpectedVersion." }
    $vm=Invoke-GoldenImageAzJson -Arguments @('vm','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName) -Quiet
    $security=$vm.securityProfile
    if ([string]$security.securityType -ne 'TrustedLaunch' -or -not [bool]$security.uefiSettings.secureBootEnabled -or -not [bool]$security.uefiSettings.vTpmEnabled) {
        throw 'Trusted Launch, Secure Boot, or vTPM validation failed.'
    }
    Write-GoldenImageJson -InputObject ([pscustomobject]@{ RunCommand=$response; Vm=$vm }) -Path (Join-Path $script:Paths.RawEvidenceRoot "vm-$ExpectedVersion-proof.json")
    $proof | Add-Member -NotePropertyName TrustedLaunch -NotePropertyValue $true
    $proof | Add-Member -NotePropertyName SecureBoot -NotePropertyValue $true
    $proof | Add-Member -NotePropertyName VirtualTpm -NotePropertyValue $true
    $proof | Add-Member -NotePropertyName PublicIpAbsent -NotePropertyValue $true
    $proof
}

function Invoke-DeployV1 {
    Ensure-State; Assert-StageComplete BuildV1
    Write-Host 'Stage: DeployV1' -ForegroundColor Cyan
    New-ValidationVm -Version $script:State.V1Version
    $script:State.V1Proof=Get-ValidationProof -ExpectedVersion $script:State.V1Version
    $script:State.CurrentVersion=$script:State.V1Version
    Save-State
    Invoke-GoldenImageAzCli -Arguments @('vm','deallocate','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--no-wait') -Quiet | Out-Null
    Set-StageComplete -Name DeployV1
    Write-Host 'V1 PROOF: release 1.0.0, IIS healthy, patch manifest present, Trusted Launch + Secure Boot + vTPM enabled.' -ForegroundColor Green
}

function Invoke-BuildV2 {
    Ensure-State; Assert-StageComplete DeployV1
    Write-Host 'Stage: BuildV2' -ForegroundColor Cyan
    Invoke-ImageBuild -Version $script:State.V2Version -TemplateName $script:State.V2TemplateName -RunOutputName $script:State.V2RunOutputName -StagingGroup $script:State.StagingV2ResourceGroup -StateProperty V2Build
    Set-StageComplete -Name BuildV2
    Write-Host 'Version 2.0.0 passed Image Builder validation and regional distribution.' -ForegroundColor Green
}

function Invoke-RolloutV2 {
    Ensure-State; Assert-StageComplete BuildV2
    Write-Host 'Stage: RolloutV2 (immutable replacement)' -ForegroundColor Cyan
    try {
        New-ValidationVm -Version $script:State.V2Version
        $script:State.V2Proof=Get-ValidationProof -ExpectedVersion $script:State.V2Version
        $script:State.CurrentVersion=$script:State.V2Version
        Save-State
    }
    catch {
        Write-Warning 'The v2 rollout failed validation; recovering the validation VM from v1 before returning the error.'
        New-ValidationVm -Version $script:State.V1Version
        $script:State.RollbackProof=Get-ValidationProof -ExpectedVersion $script:State.V1Version
        $script:State.CurrentVersion=$script:State.V1Version
        Save-State
        throw
    }
    Invoke-GoldenImageAzCli -Arguments @('vm','deallocate','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--no-wait') -Quiet | Out-Null
    Set-StageComplete -Name RolloutV2
    Write-Host 'V2 ROLLOUT PROOF: version 2.0.0 and baseline revision 2 are active on a replacement Trusted Launch VM.' -ForegroundColor Green
}

function Invoke-RollbackV1 {
    Ensure-State; Assert-StageComplete RolloutV2
    Write-Host 'Stage: RollbackV1 (immutable replacement)' -ForegroundColor Cyan
    $galleryVersionApi='2024-03-03'
    $versionBase="https://management.azure.com$($script:State.ImageDefinitionId)/versions"
    Invoke-GoldenImageAzRestJson -Method PATCH -Uri "$versionBase/$($script:State.V2Version)?api-version=$galleryVersionApi" -Body @{
        properties=@{ publishingProfile=@{ excludeFromLatest=$true } }
    } -Quiet | Out-Null
    Invoke-GoldenImageAzRestJson -Method PATCH -Uri "$versionBase/$($script:State.V1Version)?api-version=$galleryVersionApi" -Body @{
        properties=@{ publishingProfile=@{ excludeFromLatest=$false } }
    } -Quiet | Out-Null
    New-ValidationVm -Version $script:State.V1Version
    $script:State.RollbackProof=Get-ValidationProof -ExpectedVersion $script:State.V1Version
    if ([bool]$script:State.RollbackProof.V2MarkerPresent) { throw 'Rollback proof found the v2-only marker on the v1 image.' }
    $script:State.CurrentVersion=$script:State.V1Version
    Save-State
    Invoke-GoldenImageAzCli -Arguments @('vm','deallocate','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName,'--no-wait') -Quiet | Out-Null
    Set-StageComplete -Name RollbackV1
    Write-Host 'ROLLBACK PROOF: exact version 1.0.0 restored; v2 marker absent; v2 excluded from latest.' -ForegroundColor Green
}

function Invoke-Validate {
    Ensure-State; Assert-StageComplete RollbackV1
    Write-Host 'Stage: Validate' -ForegroundColor Cyan
    $definition=Invoke-GoldenImageAzJson -Arguments @('sig','image-definition','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--gallery-image-definition',$script:State.ImageDefinitionName) -Quiet
    $v1=Invoke-GoldenImageAzJson -Arguments @('sig','image-version','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--gallery-image-definition',$script:State.ImageDefinitionName,'--gallery-image-version',$script:State.V1Version,'--expand','ReplicationStatus') -Quiet
    $v2=Invoke-GoldenImageAzJson -Arguments @('sig','image-version','show','--resource-group',$script:State.MainResourceGroup,'--gallery-name',$script:State.GalleryName,'--gallery-image-definition',$script:State.ImageDefinitionName,'--gallery-image-version',$script:State.V2Version,'--expand','ReplicationStatus') -Quiet
    $vm=Invoke-GoldenImageAzJson -Arguments @('vm','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.VmName) -Quiet
    $nic=Invoke-GoldenImageAzJson -Arguments @('network','nic','show','--resource-group',$script:State.MainResourceGroup,'--name',$script:State.NicName) -Quiet
    $security=@($definition.features | Where-Object name -eq 'SecurityType' | Select-Object -First 1)
    if ([string]$security.value -ne 'TrustedLaunchSupported') { throw 'Image definition validation failed.' }
    if ([string]$v1.provisioningState -ne 'Succeeded' -or [string]$v2.provisioningState -ne 'Succeeded') { throw 'Both gallery versions must be provisioned successfully.' }
    if ([string]$v1.replicationStatus.aggregatedState -ne 'Completed' -or [string]$v2.replicationStatus.aggregatedState -ne 'Completed') { throw 'Both gallery versions must complete regional replication.' }
    if ([bool]$v1.publishingProfile.excludeFromLatest -or -not [bool]$v2.publishingProfile.excludeFromLatest) { throw 'The final latest/exclusion state is incorrect.' }
    if ([string]$vm.storageProfile.imageReference.exactVersion -ne $script:State.V1Version) { throw 'The final VM is not pinned to exact version 1.0.0.' }
    $publicIpProperty=$nic.ipConfigurations[0].PSObject.Properties['publicIPAddress']
    if ($publicIpProperty -and $publicIpProperty.Value) { throw 'The validation NIC unexpectedly has a public IP.' }
    if ([string]$vm.securityProfile.securityType -ne 'TrustedLaunch' -or -not [bool]$vm.securityProfile.uefiSettings.secureBootEnabled -or -not [bool]$vm.securityProfile.uefiSettings.vTpmEnabled) { throw 'The final VM security profile is incomplete.' }
    $validation=[pscustomobject]@{
        Status='Passed'; CapturedUtc=[DateTime]::UtcNow.ToString('o'); Versions=@('1.0.0','2.0.0'); Regions=@('West Europe','North Europe');
        ImageDefinitionTrustedLaunchSupported=$true; V1EligibleForLatest=$true; V2ExcludedFromLatest=$true; FinalVmExactVersion='1.0.0';
        TrustedLaunch=$true; SecureBoot=$true; VirtualTpm=$true; PublicIpAbsent=$true; InboundRdpRuleAbsent=$true
    }
    Write-GoldenImageJson -InputObject ([pscustomobject]@{ Definition=$definition;V1=$v1;V2=$v2;Vm=$vm;Nic=$nic }) -Path (Join-Path $script:Paths.RawEvidenceRoot 'final-validation.json')
    $script:State | Add-Member -NotePropertyName Validation -NotePropertyValue $validation -Force
    Save-State
    Set-StageComplete -Name Validate
    Write-Host 'VALIDATION PASSED: two replicated versions, Trusted Launch deployment, v2 rollout, and immutable v1 rollback.' -ForegroundColor Green
}

switch ($Stage) {
    'Preflight' { Invoke-Preflight }
    'Foundation' { Invoke-Foundation }
    'Gallery' { Invoke-Gallery }
    'BuildV1' { Invoke-BuildV1 }
    'DeployV1' { Invoke-DeployV1 }
    'BuildV2' { Invoke-BuildV2 }
    'RolloutV2' { Invoke-RolloutV2 }
    'RollbackV1' { Invoke-RollbackV1 }
    'Validate' { Invoke-Validate }
    'All' {
        Invoke-Preflight
        Invoke-Foundation
        Invoke-Gallery
        Invoke-BuildV1
        Invoke-DeployV1
        Invoke-BuildV2
        Invoke-RolloutV2
        Invoke-RollbackV1
        Invoke-Validate
    }
}
