param(
    [string]$Backend = "",
    [string]$CudaArch = "",
    [string]$RocmArch = "",
    [string]$BuildProfile = ""
)

$ErrorActionPreference = "Stop"

$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$repoRoot = [System.IO.Path]::GetFullPath((Join-Path $scriptDir ".."))
$llamaDir = if ($env:MESH_LLM_LLAMA_DIR) { $env:MESH_LLM_LLAMA_DIR } else { Join-Path $repoRoot ".deps\llama.cpp" }
$llamaBuildRoot = if ($env:MESH_LLM_LLAMA_BUILD_ROOT) { $env:MESH_LLM_LLAMA_BUILD_ROOT } else { Join-Path $repoRoot ".deps\llama-build" }
$buildDir = if ($env:LLAMA_STAGE_BUILD_DIR) { $env:LLAMA_STAGE_BUILD_DIR } else { Join-Path $llamaBuildRoot "build-stage-abi" }
$meshUiDir = Join-Path $repoRoot "crates\mesh-llm-ui"
$compilerLauncherArgs = @()
$compilerCacheBin = $null
$buildProfile = if ($BuildProfile) { $BuildProfile.ToLowerInvariant() } elseif ($env:MESH_LLM_BUILD_PROFILE) { $env:MESH_LLM_BUILD_PROFILE.ToLowerInvariant() } else { "debug" }

function Prepare-Llama {
    $pinFile = Join-Path $repoRoot "third_party\llama.cpp\upstream.txt"
    $patchDir = Join-Path $repoRoot "third_party\llama.cpp\patches"
    $upstreamUrl = if ($env:LLAMA_UPSTREAM_URL) { $env:LLAMA_UPSTREAM_URL } else { "https://github.com/ggml-org/llama.cpp.git" }
    $targetSha = if ($env:MESH_LLM_LLAMA_PIN_SHA) { $env:MESH_LLM_LLAMA_PIN_SHA } else { (Get-Content $pinFile -Raw).Trim() }

    if (-not (Test-Path $pinFile)) {
        throw "Missing llama.cpp upstream pin: $pinFile"
    }
    if (-not (Test-Path $patchDir)) {
        throw "Missing llama.cpp patch directory: $patchDir"
    }

    $llamaParent = Split-Path -Parent $llamaDir
    New-Item -ItemType Directory -Force -Path $llamaParent | Out-Null
    if (-not (Test-Path (Join-Path $llamaDir ".git"))) {
        if (Test-Path $llamaDir) {
            Remove-Item -Recurse -Force $llamaDir
        }
        Invoke-NativeCommand "git" @("clone", "--filter=blob:none", $upstreamUrl, $llamaDir)
    }

    Push-Location $llamaDir
    try {
        Invoke-NativeCommand "git" @("config", "user.name", "Mesh-LLM CI")
        Invoke-NativeCommand "git" @("config", "user.email", "ci@mesh-llm.local")
        try {
            & git am --abort *> $null
        } catch {
        }
        Invoke-NativeCommand "git" @("remote", "set-url", "origin", $upstreamUrl)
        Invoke-NativeCommand "git" @("fetch", "origin", "master", "--tags")
        Invoke-NativeCommand "git" @("-c", "advice.detachedHead=false", "checkout", "--detach", "--quiet", $targetSha)
        Invoke-NativeCommand "git" @("reset", "--hard", "--quiet", $targetSha)
        Invoke-NativeCommand "git" @("clean", "-fdx", "-e", "build/")

        $patches = Get-ChildItem -Path $patchDir -Filter "*.patch" | Sort-Object Name
        foreach ($patch in $patches) {
            Invoke-NativeCommand "git" @("am", "--3way", $patch.FullName)
        }

        $patchedSha = (& git rev-parse HEAD).Trim()
        Write-Host "prepared llama.cpp"
        Write-Host "  upstream: $targetSha"
        Write-Host "  patched:  $patchedSha"
        Write-Host "  workdir:  $llamaDir"
    } finally {
        Pop-Location
    }
}

function Add-ToPath {
    param([string]$Directory)

    if (-not $Directory -or -not (Test-Path $Directory)) {
        return
    }

    $pathEntries = @($env:PATH -split [System.IO.Path]::PathSeparator)
    if ($pathEntries -contains $Directory) {
        return
    }

    $env:PATH = "$Directory$([System.IO.Path]::PathSeparator)$env:PATH"
}

function Test-CommandSuccess {
    param(
        [string]$Command,
        [string[]]$Arguments = @()
    )

    try {
        $null = & $Command @Arguments 2>$null
        return $LASTEXITCODE -eq 0
    } catch {
        return $false
    }
}

function Resolve-CommandPath {
    param([string]$Name)

    $command = Get-Command $Name -ErrorAction SilentlyContinue
    if ($command) {
        return $command.Source
    }
    return $null
}

function Show-LldInstallInstructions {
    throw @"
LLVM lld was not found for the Windows MSVC target.

lld is required for faster Rust builds (measured up to 26% faster locally).

Install one of these, then rerun the just command:
  rustup component add llvm-tools-preview

Or install LLVM lld-link:
  winget install LLVM.LLVM
  choco install llvm

The build requires lld. It looks for rust-lld.exe in the active Rust sysroot first, then falls
back to rust-lld.exe or lld-link.exe on PATH.
"@
}

function Resolve-LldLinker {
    try {
        $sysroot = (& rustc --print sysroot).Trim()
        if ($LASTEXITCODE -eq 0 -and $sysroot) {
            foreach ($target in @("x86_64-pc-windows-msvc", "aarch64-pc-windows-msvc")) {
                $candidate = Join-Path $sysroot "lib\rustlib\$target\bin\rust-lld.exe"
                if (Test-Path $candidate) {
                    return $candidate
                }
            }
        }
    } catch {
    }

    foreach ($name in @("rust-lld.exe", "lld-link.exe")) {
        $command = Resolve-CommandPath $name
        if ($command) {
            return $command
        }
    }

    return $null
}

function Configure-LldLinker {
    $linker = Resolve-LldLinker
    if (-not $linker) {
        Show-LldInstallInstructions
    }

    $env:CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = $linker
    $env:CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER = $linker
    Write-Host "Using Rust linker: $linker"
}

function Configure-CompilerCache {
    $script:compilerCacheBin = Resolve-CommandPath "sccache"
    if (-not $script:compilerCacheBin) {
        $script:compilerCacheBin = Resolve-CommandPath "ccache"
    }
    if (-not $script:compilerCacheBin) {
        $script:compilerLauncherArgs = @()
        return
    }

    Write-Host "Using compiler cache: $script:compilerCacheBin"
    $script:compilerLauncherArgs = @(
        "-DCMAKE_C_COMPILER_LAUNCHER=$script:compilerCacheBin",
        "-DCMAKE_CXX_COMPILER_LAUNCHER=$script:compilerCacheBin"
    )
}

function Test-Sccache {
    return $compilerCacheBin -and ((Split-Path -Leaf $compilerCacheBin).ToLowerInvariant() -like "sccache*")
}

function Reset-SccacheStats {
    if (-not (Test-Sccache)) {
        return
    }

    $previousErrorActionPreference = $ErrorActionPreference
    try {
        $ErrorActionPreference = "Continue"
        & $compilerCacheBin --zero-stats 2>&1 | Out-Null
        if ($LASTEXITCODE -ne 0) {
            Write-Warning "Unable to reset sccache stats before build."
        }
    } finally {
        $ErrorActionPreference = $previousErrorActionPreference
    }
}

function Get-SccacheStats {
    if (-not (Test-Sccache)) {
        return $null
    }

    $json = & $compilerCacheBin --show-stats --stats-format=json
    if ($LASTEXITCODE -ne 0) {
        Write-Warning "Unable to read sccache stats."
        return $null
    }

    try {
        return ($json | ConvertFrom-Json).stats
    } catch {
        Write-Warning "Unable to parse sccache stats JSON."
        return $null
    }
}

function Show-SccacheStats {
    if (-not (Test-Sccache)) {
        return
    }

    & $compilerCacheBin --show-stats
    if ($LASTEXITCODE -ne 0) {
        Write-Warning "Unable to show sccache stats."
    }
}

function Test-SccacheStatsContainCuda {
    param($Stats)

    if (-not $Stats) {
        return $false
    }

    foreach ($bucketName in @("cache_hits", "cache_misses")) {
        $bucket = $Stats.$bucketName
        if (-not $bucket -or -not $bucket.counts) {
            continue
        }
        foreach ($property in $bucket.counts.PSObject.Properties) {
            if ($property.Name -like "CUDA*" -and [int64]$property.Value -gt 0) {
                return $true
            }
        }
    }

    return $false
}

function Test-SccacheStatsContainHits {
    param($Stats)

    if (-not $Stats -or -not $Stats.cache_hits -or -not $Stats.cache_hits.counts) {
        return $false
    }

    foreach ($property in $Stats.cache_hits.counts.PSObject.Properties) {
        if ([int64]$property.Value -gt 0) {
            return $true
        }
    }

    return $false
}

function Assert-RequiredSccacheUsage {
    param(
        [string]$BackendName,
        $Stats
    )

    if ($env:MESH_LLM_REQUIRE_SCCACHE -ne "1") {
        return
    }

    if (-not (Test-Sccache)) {
        throw "MESH_LLM_REQUIRE_SCCACHE=1 but sccache is not available."
    }
    if (-not $Stats -or [int64]$Stats.compile_requests -le 0) {
        throw "MESH_LLM_REQUIRE_SCCACHE=1 but sccache saw zero compile requests."
    }
    if ($env:MESH_LLM_REQUIRE_SCCACHE_HITS -eq "1" -and -not (Test-SccacheStatsContainHits $Stats)) {
        throw "MESH_LLM_REQUIRE_SCCACHE_HITS=1 but sccache did not record any cache hits."
    }
    if ($BackendName -eq "cuda" -and -not (Test-SccacheStatsContainCuda $Stats)) {
        throw "MESH_LLM_REQUIRE_SCCACHE=1 but sccache did not record CUDA compile hits or misses."
    }
}

function Import-CmdEnvironment {
    param(
        [Parameter(Mandatory = $true)]
        [string]$CommandLine
    )

    $output = & cmd.exe /s /c "$CommandLine && set"
    if ($LASTEXITCODE -ne 0) {
        throw "Failed to initialize Windows build environment with command: $CommandLine"
    }

    foreach ($line in $output) {
        if ($line -match '^(?<name>[^=]+)=(?<value>.*)$') {
            Set-Item -Path "env:$($Matches.name)" -Value $Matches.value
        }
    }
}

function Invoke-NativeCommand {
    param(
        [Parameter(Mandatory = $true)]
        [string]$Command,
        [string[]]$Arguments = @()
    )

    & $Command @Arguments
    if ($LASTEXITCODE -ne 0) {
        $argString = if ($Arguments.Count -gt 0) { " " + ($Arguments -join " ") } else { "" }
        throw "Command failed with exit code ${LASTEXITCODE}: $Command$argString"
    }
}

function Invoke-CmakeBuild {
    param(
        [Parameter(Mandatory = $true)]
        [string]$BuildDir,
        [Parameter(Mandatory = $true)]
        [int]$ParallelJobs
    )

    $arguments = @("--build", $BuildDir, "--config", "Release", "--parallel", "$ParallelJobs", "--target", "llama", "llama-common", "mtmd")
    try {
        Invoke-NativeCommand "cmake" $arguments
    } catch {
        if ($backendName -ne "cuda" -or -not (Test-Sccache) -or $env:MESH_LLM_RETRY_SCCACHE_CUDA_BUILD -eq "0") {
            throw
        }

        Write-Warning "CUDA ABI build failed while using sccache; restarting sccache and retrying once."
        # `sccache --stop-server` exits non-zero when the server is already dead
        # (which is precisely the case this retry path exists to handle). Tolerate
        # that instead of letting $ErrorActionPreference='Stop' turn it into a
        # terminating error that aborts the retry before cmake runs again.
        $previousErrorActionPreference = $ErrorActionPreference
        try {
            $ErrorActionPreference = "Continue"
            & $compilerCacheBin --stop-server 2>&1 | Out-Null
        } finally {
            $ErrorActionPreference = $previousErrorActionPreference
        }
        Start-Sleep -Seconds 2
        Reset-SccacheStats
        Invoke-NativeCommand "cmake" $arguments
    }
}

function Test-UiBuildRequired {
    param(
        [Parameter(Mandatory = $true)]
        [string]$UiDirectory
    )

    $distDir = Join-Path $UiDirectory "dist"
    if (-not (Test-Path $distDir)) {
        return $true
    }

    $distFiles = Get-ChildItem -Path $distDir -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1
    if (-not $distFiles) {
        return $true
    }

    $distTimestampUtc = (Get-Item $distDir).LastWriteTimeUtc
    $uiBuildInputs = @(
        (Join-Path $UiDirectory "package.json"),
        (Join-Path $UiDirectory "pnpm-lock.yaml"),
        (Join-Path $UiDirectory "vite.config.ts"),
        (Join-Path $UiDirectory "tsconfig.json"),
        (Join-Path $UiDirectory "tsconfig.app.json"),
        (Join-Path $UiDirectory "tsconfig.node.json"),
        (Join-Path $UiDirectory "biome.json"),
        (Join-Path $UiDirectory "index.html"),
        (Join-Path $UiDirectory "src"),
        (Join-Path $UiDirectory "public")
    )

    foreach ($path in $uiBuildInputs) {
        if (-not (Test-Path $path)) {
            continue
        }

        $item = Get-Item $path
        if ($item.PSIsContainer) {
            $newerInput = Get-ChildItem -Path $path -File -Recurse -ErrorAction SilentlyContinue |
                Where-Object { $_.LastWriteTimeUtc -gt $distTimestampUtc } |
                Select-Object -First 1
            if ($newerInput) {
                return $true
            }
            continue
        }

        if ($item.LastWriteTimeUtc -gt $distTimestampUtc) {
            return $true
        }
    }

    return $false
}

function Test-PnpmInstallRequired {
    param(
        [Parameter(Mandatory = $true)]
        [string]$UiDirectory
    )

    $nodeModulesDir = Join-Path $UiDirectory "node_modules"
    if (-not (Test-Path $nodeModulesDir)) {
        return $true
    }

    $nodeModulesTimestampUtc = (Get-Item $nodeModulesDir).LastWriteTimeUtc
    foreach ($manifestName in @("package.json", "pnpm-lock.yaml")) {
        $manifestPath = Join-Path $UiDirectory $manifestName
        if (-not (Test-Path $manifestPath)) {
            continue
        }

        if ((Get-Item $manifestPath).LastWriteTimeUtc -gt $nodeModulesTimestampUtc) {
            return $true
        }
    }

    return $false
}

function Normalize-RecipeArgument {
    param(
        [AllowEmptyString()]
        [string]$Value,
        [string[]]$KnownNames = @()
    )

    if ($null -eq $Value) {
        return $Value
    }

    $normalized = $Value.Trim()
    if (-not $normalized) {
        return ""
    }

    if ($normalized -match '^(?<name>[A-Za-z_][A-Za-z0-9_-]*)=(?<value>.*)$') {
        $matchedName = $Matches.name
        $isKnownName = $KnownNames.Count -eq 0
        foreach ($knownName in $KnownNames) {
            if ($matchedName.Equals($knownName, [System.StringComparison]::OrdinalIgnoreCase)) {
                $isKnownName = $true
                break
            }
        }

        if ($isKnownName) {
            $normalized = $Matches.value
        }
    }

    if ($normalized.Length -ge 2) {
        $first = $normalized[0]
        $last = $normalized[$normalized.Length - 1]
        if (($first -eq '"' -and $last -eq '"') -or ($first -eq "'" -and $last -eq "'")) {
            $normalized = $normalized.Substring(1, $normalized.Length - 2)
        }
    }

    return $normalized.Trim()
}

function Ensure-MsvcToolchain {
    if ((Resolve-CommandPath "cl") -and (Resolve-CommandPath "link") -and (Resolve-CommandPath "lib")) {
        return
    }

    $programFilesX86 = ${env:ProgramFiles(x86)}
    $vswhereCandidates = @()
    if ($programFilesX86) {
        $vswhereCandidates += (Join-Path $programFilesX86 "Microsoft Visual Studio\Installer\vswhere.exe")
    }
    if ($env:ProgramFiles) {
        $vswhereCandidates += (Join-Path $env:ProgramFiles "Microsoft Visual Studio\Installer\vswhere.exe")
    }
    $vswhereFromPath = Resolve-CommandPath "vswhere"
    if ($vswhereFromPath) {
        $vswhereCandidates += $vswhereFromPath
    }

    $vcvars64 = $null
    $vswhere = $vswhereCandidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -Unique -First 1
    if ($vswhere) {
        $installationPathOutput = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath | Select-Object -First 1
        $installationPath = if ($installationPathOutput) { $installationPathOutput.Trim() } else { "" }
        if ($installationPath) {
            $candidate = Join-Path $installationPath "VC\Auxiliary\Build\vcvars64.bat"
            if (Test-Path $candidate) {
                $vcvars64 = $candidate
            }
        }
    }

    if (-not $vcvars64) {
        $searchRoots = @($programFilesX86, $env:ProgramFiles) | Where-Object { $_ } | Select-Object -Unique
        foreach ($searchRoot in $searchRoots) {
            $candidate = Get-ChildItem -Path $searchRoot -Filter vcvars64.bat -Recurse -ErrorAction SilentlyContinue |
                Where-Object { $_.FullName -like '*Microsoft Visual Studio*VC\Auxiliary\Build\vcvars64.bat' } |
                Select-Object -First 1
            if ($candidate) {
                $vcvars64 = $candidate.FullName
                break
            }
        }
    }

    if (-not (Test-Path $vcvars64)) {
        throw "Visual Studio Build Tools with vcvars64.bat were not found on this Windows runner."
    }

    Import-CmdEnvironment "`"$vcvars64`" >nul"

    if (-not (Resolve-CommandPath "cl")) {
        throw "MSVC toolchain initialization completed, but cl.exe is still not available in PATH."
    }
}

function Resolve-HipPackageRoot {
    $roots = @()
    if ($env:HIP_PATH) {
        $roots += $env:HIP_PATH
    }
    if ($env:ROCM_PATH) {
        $roots += $env:ROCM_PATH
    }

    $roots = $roots | Where-Object { $_ } | Select-Object -Unique

    foreach ($root in $roots) {
        if (-not (Test-Path $root)) {
            continue
        }

        $directConfig = Join-Path $root "lib\cmake\hip\hip-config.cmake"
        if (Test-Path $directConfig) {
            return [PSCustomObject]@{
                Root   = $root
                HipDir = Split-Path -Parent $directConfig
            }
        }

        $versionedRoot = Get-ChildItem -Path $root -Directory -ErrorAction SilentlyContinue |
            Where-Object {
                Test-Path (Join-Path $_.FullName "lib\cmake\hip\hip-config.cmake")
            } |
            Sort-Object Name -Descending |
            Select-Object -First 1

        if ($versionedRoot) {
            $configPath = Join-Path $versionedRoot.FullName "lib\cmake\hip\hip-config.cmake"
            return [PSCustomObject]@{
                Root   = $versionedRoot.FullName
                HipDir = Split-Path -Parent $configPath
            }
        }
    }

    return $null
}

function Resolve-RocmRoot {
    $hipPackage = Resolve-HipPackageRoot
    if ($hipPackage) {
        return $hipPackage.Root
    }
    if ($env:ProgramFiles) {
        foreach ($candidate in @(
            (Join-Path $env:ProgramFiles "AMD\ROCm"),
            (Join-Path $env:ProgramFiles "AMD\HIP")
        )) {
            if (Test-Path $candidate) {
                return $candidate
            }
        }
    }
    return $null
}

function Resolve-VulkanSdkRoot {
    if ($env:VULKAN_SDK -and (Test-Path $env:VULKAN_SDK)) {
        return $env:VULKAN_SDK
    }

    if ($env:ProgramFiles) {
        $sdkBase = Join-Path $env:ProgramFiles "VulkanSDK"
        if (Test-Path $sdkBase) {
            $latest = Get-ChildItem -Path $sdkBase -Directory | Sort-Object Name -Descending | Select-Object -First 1
            if ($latest) {
                return $latest.FullName
            }
        }
    }

    return $null
}

function Resolve-Backend {
    param([string]$Requested)

    if ($Requested) {
        $normalized = $Requested.ToLowerInvariant()
        switch ($normalized) {
            "hip" { return "rocm" }
            "rocm" { return "rocm" }
            default { return $normalized }
        }
    }

    if (Test-CommandSuccess "nvidia-smi" @("--query-gpu=name", "--format=csv,noheader")) {
        return "cuda"
    }

    if (Resolve-RocmRoot) {
        return "rocm"
    }

    if ((Resolve-CommandPath "hipInfo") -or (Resolve-CommandPath "hipconfig")) {
        return "rocm"
    }

    if (Test-CommandSuccess "vulkaninfo" @("--summary")) {
        return "vulkan"
    }

    if (Resolve-VulkanSdkRoot) {
        return "vulkan"
    }

    return "cpu"
}

function Ensure-CudaToolchain {
    if (Resolve-CommandPath "nvcc") {
        return
    }

    $candidates = @()
    if ($env:CUDA_PATH) {
        $candidates += (Join-Path $env:CUDA_PATH "bin")
    }
    if ($env:ProgramFiles) {
        $toolkitRoot = Join-Path $env:ProgramFiles "NVIDIA GPU Computing Toolkit\CUDA"
        if (Test-Path $toolkitRoot) {
            $candidates += Get-ChildItem -Path $toolkitRoot -Directory | Sort-Object Name -Descending | ForEach-Object {
                Join-Path $_.FullName "bin"
            }
        }
    }

    foreach ($candidate in $candidates) {
        if (Test-Path (Join-Path $candidate "nvcc.exe")) {
            Add-ToPath $candidate
            return
        }
    }

    throw "nvcc not found. Install the CUDA toolkit and ensure nvcc.exe is in PATH."
}

function Ensure-RocmToolchain {
    $rocmRoot = Resolve-RocmRoot
    $hipPackage = Resolve-HipPackageRoot
    if ($rocmRoot) {
        $binDir = Join-Path $rocmRoot "bin"
        $llvmBinDir = Join-Path $rocmRoot "llvm\bin"
        Add-ToPath $binDir
        Add-ToPath $llvmBinDir
        $env:ROCM_PATH = $rocmRoot
        $env:HIP_PATH = $rocmRoot
        $env:CMAKE_PREFIX_PATH = if ($env:CMAKE_PREFIX_PATH) {
            "$rocmRoot;$env:CMAKE_PREFIX_PATH"
        } else {
            $rocmRoot
        }
        if ($hipPackage) {
            $env:hip_DIR = $hipPackage.HipDir
        }
        if (-not $env:HIPCC -or -not $env:HIPCXX) {
            foreach ($candidate in @(
                (Join-Path $llvmBinDir "clang++.exe"),
                (Join-Path $binDir "clang++.exe"),
                (Join-Path $llvmBinDir "clang.exe"),
                (Join-Path $binDir "clang.exe")
            )) {
                if ($candidate -like "*clang++.exe" -and -not $env:HIPCXX -and (Test-Path $candidate)) {
                    $env:HIPCXX = $candidate
                } elseif ($candidate -like "*clang.exe" -and -not $env:HIPCC -and (Test-Path $candidate)) {
                    $env:HIPCC = $candidate
                }

                if ($env:HIPCC -and $env:HIPCXX) {
                    break
                }
            }
        }
    }

    $hipConfig = Resolve-CommandPath "hipconfig"
    if ($hipConfig) {
        try {
            $hipCompilerRoot = (& $hipConfig -l).Trim()
            if ($hipCompilerRoot) {
                $clangxx = Join-Path $hipCompilerRoot "clang++.exe"
                $clang = Join-Path $hipCompilerRoot "clang.exe"
                if (Test-Path $clangxx) {
                    $env:HIPCXX = $clangxx
                }
                if (Test-Path $clang) {
                    $env:HIPCC = $clang
                    if (-not $env:HIPCXX) {
                        $env:HIPCXX = $clang
                    }
                }
            }
        } catch {
        }

        try {
            $hipRoot = (& $hipConfig -R).Trim()
            if ($hipRoot -and (Test-Path $hipRoot)) {
                $env:HIP_PATH = $hipRoot
                $env:ROCM_PATH = $hipRoot
                if (-not $hipPackage) {
                    $hipPackage = Resolve-HipPackageRoot
                    if ($hipPackage) {
                        $env:hip_DIR = $hipPackage.HipDir
                    }
                }
            }
        } catch {
        }
    }

    if (-not (Resolve-CommandPath "hipconfig") -and -not (Resolve-CommandPath "hipInfo") -and -not $rocmRoot) {
        throw "ROCm/HIP toolchain not found. Install ROCm on Windows and ensure hipconfig or hipInfo is available."
    }

    if (-not $hipPackage) {
        $hipPackage = Resolve-HipPackageRoot
    }
    if (-not $hipPackage) {
        throw "HIP package config not found. Expected hip-config.cmake under the HIP SDK installation."
    }
    $env:hip_DIR = $hipPackage.HipDir
    if (-not $env:HIPCC) {
        throw "HIP C compiler not found. Expected clang.exe in the HIP SDK installation."
    }
    if (-not $env:HIPCXX) {
        throw "HIP C++ compiler not found. Expected clang++.exe in the HIP SDK installation."
    }
}

function Ensure-VulkanToolchain {
    $sdkRoot = Resolve-VulkanSdkRoot
    if ($sdkRoot) {
        Add-ToPath (Join-Path $sdkRoot "Bin")
        Add-ToPath (Join-Path $sdkRoot "Bin32")
        if (-not $env:VULKAN_SDK) {
            $env:VULKAN_SDK = $sdkRoot
        }
        $env:CMAKE_PREFIX_PATH = if ($env:CMAKE_PREFIX_PATH) {
            "$sdkRoot;$env:CMAKE_PREFIX_PATH"
        } else {
            $sdkRoot
        }
    }

    $hasVulkanHeaders =
        ($env:VULKAN_SDK -and (Test-Path (Join-Path $env:VULKAN_SDK "Include\vulkan\vulkan.h"))) -or
        ($sdkRoot -and (Test-Path (Join-Path $sdkRoot "Include\vulkan\vulkan.h")))
    if (-not $hasVulkanHeaders) {
        throw "Vulkan SDK/development files not found. Install the Vulkan SDK and ensure VULKAN_SDK is configured."
    }

    if (-not (Resolve-CommandPath "glslc")) {
        throw "glslc not found. Install the Vulkan SDK and ensure its Bin directory is in PATH."
    }
}

function Invoke-InRepo {
    param(
        [scriptblock]$Script
    )

    Push-Location $repoRoot
    try {
        & $Script
    } finally {
        Pop-Location
    }
}

$Backend = Normalize-RecipeArgument $Backend @("backend")
$CudaArch = Normalize-RecipeArgument $CudaArch @("cuda_arch", "cudaarch")
$RocmArch = Normalize-RecipeArgument $RocmArch @("rocm_arch", "rocmarch", "amd_arch", "amdarch")

$backendName = Resolve-Backend $Backend
$buildDir = if ($env:LLAMA_STAGE_BUILD_DIR) { $env:LLAMA_STAGE_BUILD_DIR } else { Join-Path $llamaBuildRoot "build-stage-abi-$backendName" }
Write-Host "Using Windows backend: $backendName"

Ensure-MsvcToolchain
Configure-LldLinker
Configure-CompilerCache
if ((Test-Sccache) -and -not $env:RUSTC_WRAPPER) {
    $env:RUSTC_WRAPPER = $script:compilerCacheBin
    Write-Host "Using sccache for Rust compilation: $env:RUSTC_WRAPPER"
}

switch ($backendName) {
    "cuda" {
        Ensure-CudaToolchain
        if ($CudaArch) {
            Write-Host "Using CUDA architectures: $CudaArch"
        } else {
            Write-Host "Using CUDA toolkit at: $(Split-Path -Parent (Resolve-CommandPath 'nvcc'))"
        }
    }
    "rocm" {
        Ensure-RocmToolchain
        if ($RocmArch) {
            Write-Host "Using AMDGPU targets: $RocmArch"
        }
    }
    "vulkan" {
        Ensure-VulkanToolchain
    }
    "cpu" {
        Write-Host "Building Windows backend: CPU only"
    }
    default {
        throw "Unsupported backend '$backendName'. Use one of: cuda, rocm, hip, vulkan, cpu."
    }
}

Invoke-InRepo {
    Prepare-Llama

    $pathMaxDefine = if ($backendName -eq "rocm") { "-DPATH_MAX=4096" } else { "/DPATH_MAX=4096" }
    Reset-SccacheStats

    $cmakeArgs = @(
        "-B", $buildDir,
        "-S", $llamaDir,
        "-DCMAKE_BUILD_TYPE=Release",
        "-DCMAKE_CXX_FLAGS=$pathMaxDefine",
        "-DGGML_METAL=OFF",
        "-DGGML_CUDA=OFF",
        "-DGGML_HIP=OFF",
        "-DGGML_VULKAN=OFF",
        "-DGGML_OPENMP=ON",
        "-DBUILD_SHARED_LIBS=OFF",
        "-DLLAMA_CURL=OFF",
        "-DLLAMA_BUILD_EXAMPLES=OFF",
        "-DLLAMA_BUILD_TESTS=OFF",
        "-DGGML_BUILD_TESTS=OFF"
    )

    if (Resolve-CommandPath "ninja") {
        $cmakeArgs = @("-G", "Ninja") + $cmakeArgs
    }

    switch ($backendName) {
        "cuda" {
            $cmakeArgs += "-DGGML_CUDA=ON"
            if (Test-Sccache) {
                $cmakeArgs += "-DCMAKE_CUDA_COMPILER_LAUNCHER=$compilerCacheBin"
            }
            if ($CudaArch) {
                $cmakeArgs += "-DCMAKE_CUDA_ARCHITECTURES=$CudaArch"
            }
        }
        "rocm" {
            $cmakeArgs += "-DGGML_HIP=ON"
            if ($compilerCacheBin) {
                $cmakeArgs += "-DCMAKE_HIP_COMPILER_LAUNCHER=$compilerCacheBin"
            }
            if ($env:HIPCC) {
                $cmakeArgs += "-DCMAKE_C_COMPILER=$env:HIPCC"
            }
            if ($env:HIPCXX) {
                $cmakeArgs += "-DCMAKE_CXX_COMPILER=$env:HIPCXX"
            }
            if ($env:hip_DIR) {
                $cmakeArgs += "-Dhip_DIR=$env:hip_DIR"
            }
            if ($env:ROCM_PATH) {
                $cmakeArgs += "-DCMAKE_PREFIX_PATH=$env:ROCM_PATH"
            }
            if ($RocmArch) {
                $cmakeArgs += "-DAMDGPU_TARGETS=$RocmArch"
            }
        }
        "vulkan" {
            $cmakeArgs += "-DGGML_VULKAN=ON"
        }
        "cpu" {
        }
    }

    $cmakeArgs += $compilerLauncherArgs

    $parallelJobs = if ($env:MESH_LLM_WINDOWS_BUILD_JOBS) {
        [int]$env:MESH_LLM_WINDOWS_BUILD_JOBS
    } elseif ($backendName -eq "cuda" -and (Test-Sccache)) {
        [Math]::Min([Environment]::ProcessorCount, 2)
    } else {
        [Environment]::ProcessorCount
    }
    Invoke-NativeCommand "cmake" $cmakeArgs
    Invoke-CmakeBuild -BuildDir $buildDir -ParallelJobs $parallelJobs
    Write-Host "Patched llama.cpp ABI build complete: $buildDir"
    $sccacheStats = Get-SccacheStats
    Show-SccacheStats
    Assert-RequiredSccacheUsage $backendName $sccacheStats

    if ($env:MESH_LLM_SKIP_UI -eq "1") {
        Write-Host "Skipping mesh-llm UI build because MESH_LLM_SKIP_UI=1."
    } elseif (Test-Path $meshUiDir) {
        if (Test-UiBuildRequired -UiDirectory $meshUiDir) {
            Write-Host "Building mesh-llm UI..."
            Push-Location $meshUiDir
            try {
                if (Test-PnpmInstallRequired -UiDirectory $meshUiDir) {
                    Invoke-NativeCommand "pnpm" @("install", "--frozen-lockfile")
                }
                Invoke-NativeCommand "pnpm" @("run", "build")
            } finally {
                Pop-Location
            }
        } else {
            Write-Host "Skipping mesh-llm UI build; dist is up to date."
        }
    }

    Write-Host "Building mesh-llm..."
    $env:LLAMA_STAGE_BUILD_DIR = $buildDir
    $cargoFeatureArgs = @()
    switch ($backendName) {
        "cuda" { $cargoFeatureArgs = @("--features", "gpu-bench-cuda") }
        "rocm" { $cargoFeatureArgs = @("--features", "gpu-bench-hip") }
    }
    switch ($buildProfile) {
        "dev" {
            Invoke-NativeCommand "cargo" (@("build", "-p", "mesh-llm", "--bin", "mesh-llm") + $cargoFeatureArgs)
            Write-Host "Mesh binary: target\debug\mesh-llm.exe"
        }
        "debug" {
            Invoke-NativeCommand "cargo" (@("build", "-p", "mesh-llm", "--bin", "mesh-llm") + $cargoFeatureArgs)
            Write-Host "Mesh binary: target\debug\mesh-llm.exe"
        }
        "release" {
            Invoke-NativeCommand "cargo" (@("build", "--release", "--locked", "-p", "mesh-llm") + $cargoFeatureArgs)
            Write-Host "Mesh binary: target\release\mesh-llm.exe"
        }
        default {
            throw "Unsupported MESH_LLM_BUILD_PROFILE/BuildProfile '$buildProfile'. Expected debug, dev, or release."
        }
    }
}
