<#
.SYNOPSIS
    Generate or refresh the WinMD cache for the Agent Skill.

.DESCRIPTION
    Builds and runs the standalone cache generator to export cached JSON files
    from all WinMD metadata found in project NuGet packages and Windows SDK.

    The cache is per-package+version: if two projects reference the same
    package at the same version, the WinMD data is parsed once and shared.

    Supports single project or recursive scan of an entire repo.

.PARAMETER ProjectDir
    Path to a project directory (contains .csproj/.vcxproj), or a project file itself.
    Defaults to scanning the workspace root.

.PARAMETER Scan
    Recursively discover all .csproj/.vcxproj files under ProjectDir.

.PARAMETER OutputDir
    Path to the cache output directory. Defaults to "Generated Files\winmd-cache".

.EXAMPLE
    .\Update-WinMdCache.ps1
    .\Update-WinMdCache.ps1 -ProjectDir BlankWinUI
    .\Update-WinMdCache.ps1 -Scan -ProjectDir .
    .\Update-WinMdCache.ps1 -ProjectDir "src\MyApp\MyApp.csproj"
#>
[CmdletBinding()]
param(
    [string]$ProjectDir,
    [switch]$Scan,
    [string]$OutputDir = 'Generated Files\winmd-cache'
)

$ErrorActionPreference = 'Stop'

# Convention: skill lives at .github/skills/winmd-api-search/scripts/
# so workspace root is 4 levels up from $PSScriptRoot.
$root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..\..')).Path
$generatorProj = Join-Path (Join-Path $PSScriptRoot 'cache-generator') 'CacheGenerator.csproj'

# ---------------------------------------------------------------------------
# WinAppSDK version detection -- look only at the repo root folder (no recursion)
# ---------------------------------------------------------------------------

function Get-WinAppSdkVersionFromDirectoryPackagesProps {
    <#
    .SYNOPSIS
        Extract Microsoft.WindowsAppSDK version from a Directory.Packages.props
        (Central Package Management) at the repo root.
    #>
    param([string]$RepoRoot)
    $propsFile = Join-Path $RepoRoot 'Directory.Packages.props'
    if (-not (Test-Path $propsFile)) { return $null }
    try {
        [xml]$xml = Get-Content $propsFile -Raw
        $node = $xml.SelectNodes('//PackageVersion') |
            Where-Object { $_.Include -eq 'Microsoft.WindowsAppSDK' } |
            Select-Object -First 1
        if ($node) { return $node.Version }
    } catch {
        Write-Verbose "Could not parse $propsFile : $_"
    }
    return $null
}

function Get-WinAppSdkVersionFromPackagesConfig {
    <#
    .SYNOPSIS
        Extract Microsoft.WindowsAppSDK version from a packages.config at the repo root.
    #>
    param([string]$RepoRoot)
    $configFile = Join-Path $RepoRoot 'packages.config'
    if (-not (Test-Path $configFile)) { return $null }
    try {
        [xml]$xml = Get-Content $configFile -Raw
        $node = $xml.SelectNodes('//package') |
            Where-Object { $_.id -eq 'Microsoft.WindowsAppSDK' } |
            Select-Object -First 1
        if ($node) { return $node.version }
    } catch {
        Write-Verbose "Could not parse $configFile : $_"
    }
    return $null
}

# Try Directory.Packages.props first (CPM), then packages.config
$winAppSdkVersion = Get-WinAppSdkVersionFromDirectoryPackagesProps -RepoRoot $root
if (-not $winAppSdkVersion) {
    $winAppSdkVersion = Get-WinAppSdkVersionFromPackagesConfig -RepoRoot $root
}
if ($winAppSdkVersion) {
    Write-Host "Detected WinAppSDK version from repo: $winAppSdkVersion" -ForegroundColor Cyan
} else {
    Write-Host "No WinAppSDK version found at repo root; will use latest (Version=*)" -ForegroundColor Yellow
}

# Default: if no ProjectDir, scan the workspace root
if (-not $ProjectDir) {
    $ProjectDir = $root
    $Scan = $true
}

Push-Location $root

try {
    # Detect installed .NET SDK -- require >= 8.0, prefer stable over preview
    $dotnetSdks = dotnet --list-sdks 2>$null
    $bestMajor = $dotnetSdks |
        Where-Object { $_ -notmatch 'preview|rc|alpha|beta' } |
        ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
        Where-Object { $_ -ge 8 } |
        Sort-Object -Descending |
        Select-Object -First 1

    # Fall back to preview SDKs if no stable SDK found
    if (-not $bestMajor) {
        $bestMajor = $dotnetSdks |
            ForEach-Object { if ($_ -match '^(\d+)\.') { [int]$Matches[1] } } |
            Where-Object { $_ -ge 8 } |
            Sort-Object -Descending |
            Select-Object -First 1
    }

    if (-not $bestMajor) {
        Write-Error "No .NET SDK >= 8.0 found. Install from https://dotnet.microsoft.com/download"
        exit 1
    }

    $targetFramework = "net$bestMajor.0"
    Write-Host "Using .NET SDK: $targetFramework" -ForegroundColor Cyan

    # Build MSBuild properties -- pass detected WinAppSDK version when available
    $sdkVersionProp = ''
    if ($winAppSdkVersion) {
        $sdkVersionProp = "-p:WinAppSdkVersion=$winAppSdkVersion"
    }

    Write-Host "Building cache generator..." -ForegroundColor Cyan
    $restoreArgs = @($generatorProj, "-p:TargetFramework=$targetFramework", '--nologo', '-v', 'q')
    if ($sdkVersionProp) { $restoreArgs += $sdkVersionProp }
    dotnet restore @restoreArgs
    if ($LASTEXITCODE -ne 0) {
        Write-Error "Restore failed"
        exit 1
    }
    $buildArgs = @($generatorProj, '-c', 'Release', '--nologo', '-v', 'q', "-p:TargetFramework=$targetFramework", '--no-restore')
    if ($sdkVersionProp) { $buildArgs += $sdkVersionProp }
    dotnet build @buildArgs
    if ($LASTEXITCODE -ne 0) {
        Write-Error "Build failed"
        exit 1
    }

    # Run the built executable directly (avoids dotnet run target framework mismatch issues)
    $generatorDir = Join-Path $PSScriptRoot 'cache-generator'
    $exePath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.exe"
    if (-not (Test-Path $exePath)) {
        # Fallback: try dll with dotnet
        $dllPath = Join-Path $generatorDir "bin\Release\$targetFramework\CacheGenerator.dll"
        if (Test-Path $dllPath) {
            $exePath = $null
        } else {
            Write-Error "Built executable not found at: $exePath"
            exit 1
        }
    }

    $runArgs = @()
    if ($Scan) {
        $runArgs += '--scan'
    }

    # Detect installed WinAppSDK runtime via Get-AppxPackage (the WindowsApps
    # folder is ACL-restricted so C# cannot enumerate it directly).
    # WinMD files are architecture-independent metadata, so pick whichever arch
    # matches the current OS to ensure the package is present.
    $osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString()
    $runtimePkg = Get-AppxPackage -Name 'Microsoft.WindowsAppRuntime.*' -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -notmatch 'CBS' -and $_.Architecture -eq $osArch } |
        Sort-Object -Property Version -Descending |
        Select-Object -First 1
    if ($runtimePkg -and $runtimePkg.InstallLocation -and (Test-Path $runtimePkg.InstallLocation)) {
        Write-Host "Detected WinAppSDK runtime: $($runtimePkg.Name) v$($runtimePkg.Version)" -ForegroundColor Cyan
        $runArgs += '--winappsdk-runtime'
        $runArgs += $runtimePkg.InstallLocation
    }

    $runArgs += $ProjectDir
    $runArgs += $OutputDir

    Write-Host "Exporting WinMD cache..." -ForegroundColor Cyan
    if ($exePath) {
        & $exePath @runArgs
    } else {
        dotnet $dllPath @runArgs
    }
    if ($LASTEXITCODE -ne 0) {
        Write-Error "Cache export failed"
        exit 1
    }

    Write-Host "Cache updated at: $OutputDir" -ForegroundColor Green
} finally {
    Pop-Location
}
