<#
.SYNOPSIS
Build and package PowerToys (CmdPal and installer) for a specific platform and configuration LOCALLY.

.DESCRIPTION
Builds and packages PowerToys (CmdPal and installer) locally. Handles solution build, signing, and WiX installer generation.

.PARAMETER Platform
Specifies the target platform for the build (e.g., 'arm64', 'x64'). Default is 'x64'.

.PARAMETER Configuration
Specifies the build configuration (e.g., 'Debug', 'Release'). Default is 'Release'.

.PARAMETER PerUser
Specifies whether to build a per-user installer (true) or machine-wide installer (false). Default is true (per-user).

.EXAMPLE
.\build-installer.ps1
Runs the installer build pipeline for x64 Release.

.EXAMPLE
.\build-installer.ps1 -Platform x64 -Configuration Release
Runs the pipeline for x64 Release.

.EXAMPLE
.\build-installer.ps1 -Platform x64 -Configuration Release -PerUser false
Runs the pipeline for x64 Release with machine-wide installer.

.NOTES
- Generated MSIX files will be signed using cert-sign-package.ps1.
- If the working tree is not clean, the script will prompt before continuing (use -Force to skip the prompt).
- Use the -Clean parameter to clean build outputs (bin/obj) and MSBuild outputs.
- The built installer will be placed under: installer/PowerToysSetupVNext/[Platform]/[Configuration]/User[Machine]Setup 
  relative to the solution root directory.
- To run the full installation in other machines, call "./cert-management.ps1" to export the cert used to sign the packages.
  And trust the cert in the target machine.
#>

param (
    [string]$Platform = 'x64',
    [string]$Configuration = 'Release',
    [string]$PerUser = 'true',
    [string]$Version,
    [switch]$Force,
    [switch]$EnableCmdPalAOT,
    [switch]$Clean,
    [switch]$SkipBuild,
    [switch]$Help
)

if ($Help) {
    Write-Host "Usage: .\build-installer.ps1 [-Platform <x64|arm64>] [-Configuration <Release|Debug>] [-PerUser <true|false>] [-Version <0.0.1>] [-Force] [-EnableCmdPalAOT] [-Clean] [-SkipBuild]"
    Write-Host "  -Platform       Target platform (default: auto-detect or x64)"
    Write-Host "  -Configuration  Build configuration (default: Release)"
    Write-Host "  -PerUser        Build per-user installer (default: true)"
    Write-Host "  -Version        Sets the PowerToys version (default: from src\Version.props)"
    Write-Host "  -Force          Continue even if the git working tree is not clean (skips the interactive prompt)."
    Write-Host "  -EnableCmdPalAOT Enable AOT compilation for CmdPal (slower build)"
    Write-Host "  -Clean          Clean output directories before building"
    Write-Host "  -SkipBuild      Skip building the main solution and tools (assumes they are already built)"
    Write-Host "  -Help           Show this help message"
    exit 0
}

# Ensure helpers are available
. "$PSScriptRoot\build-common.ps1"

# Initialize Visual Studio dev environment
if (-not (Ensure-VsDevEnvironment)) { exit 1 }

# Auto-detect platform when not provided
if (-not $Platform -or $Platform -eq '') {
    try {
        $Platform = Get-DefaultPlatform
        Write-Host ("[AUTO-PLATFORM] Detected platform: {0}" -f $Platform)
    } catch {
        Write-Warning "Failed to auto-detect platform; defaulting to x64"
        $Platform = 'x64'
    }
}

# Find the PowerToys repository root automatically
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$repoRoot = $scriptDir

# Navigate up from the script location to find the repo root
# Script is typically in tools\build, so go up two levels
while ($repoRoot -and -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx"))) {
    $parentDir = Split-Path -Parent $repoRoot
    if ($parentDir -eq $repoRoot) {
        # Reached the root of the drive, PowerToys.slnx not found
        Write-Error "Could not find PowerToys repository root. Make sure this script is in the PowerToys repository."
        exit 1
    }
    $repoRoot = $parentDir
}

if (-not $repoRoot -or -not (Test-Path (Join-Path $repoRoot "PowerToys.slnx"))) {
    Write-Error "Could not locate PowerToys.slnx. Please ensure this script is run from within the PowerToys repository."
    exit 1
}

Write-Host "PowerToys repository root detected: $repoRoot"

# Safety check: avoid mixing build outputs with existing local changes unless the user confirms.
if (-not $Force) {
    Push-Location $repoRoot
    try {
        $gitStatus = $null
        $gitRelevantStatus = @()
        try {
            $gitStatus = git status --porcelain=v1 --untracked-files=all --ignore-submodules=all
        } catch {
            Write-Warning ("[GIT] Failed to query git status: {0}" -f $_.Exception.Message)
        }

        if ($gitStatus -and $gitStatus.Length -gt 0) {
            foreach ($line in $gitStatus) {
                if (-not $line) { continue }

                # Porcelain v1 format: XY <path>
                # We only care about changes that affect the working tree (Y != ' ') or untracked files (??).
                # Index-only changes (staged, Y == ' ') are ignored per user request.
                if ($line.StartsWith('??')) {
                    $gitRelevantStatus += $line
                    continue
                }

                if ($line.StartsWith('!!')) {
                    continue
                }

                if ($line.Length -ge 2) {
                    $workTreeStatus = $line[1]
                    if ($workTreeStatus -ne ' ') {
                        $gitRelevantStatus += $line
                    }
                }
            }
        }

        if ($gitRelevantStatus.Count -gt 0) {
            Write-Warning "[GIT] Working tree is NOT clean."
            Write-Warning "[GIT] This build will generate untracked files and may modify tracked files, which can mix with your current changes."
            Write-Host "[GIT] Unstaged/untracked status (first 50 lines):"
            $gitRelevantStatus | Select-Object -First 50 | ForEach-Object { Write-Host ("  {0}" -f $_) }

            $shouldContinue = $false
            try {
                $choices = [System.Management.Automation.Host.ChoiceDescription[]]@(
                    (New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Continue the build."),
                    (New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Cancel the build.")
                )
                $decision = $Host.UI.PromptForChoice("Working tree not clean", "Continue anyway?", $choices, 1)
                $shouldContinue = ($decision -eq 0)
            } catch {
                Write-Warning "[GIT] Interactive prompt not available."
                Write-Error "Refusing to proceed with a dirty working tree. Re-run with -Force to continue anyway."
                exit 1
            }

            if (-not $shouldContinue) {
                Write-Host "[GIT] Cancelled by user."
                exit 1
            }
        }
    } finally {
        Pop-Location
    }
}

$cmdpalOutputPath = Join-Path $repoRoot "$Platform\$Configuration\WinUI3Apps\CmdPal"
$buildOutputPath = Join-Path $repoRoot "$Platform\$Configuration"

# Clean should be done first before any other steps
if ($Clean) {
    if (Test-Path $cmdpalOutputPath) {
        Write-Host "[CLEAN] Removing previous output: $cmdpalOutputPath"
        Remove-Item $cmdpalOutputPath -Recurse -Force -ErrorAction Ignore
    }
    if (Test-Path $buildOutputPath) {
        Write-Host "[CLEAN] Removing previous build output: $buildOutputPath"
        Remove-Item $buildOutputPath -Recurse -Force -ErrorAction Ignore
    }
    
    Write-Host "[CLEAN] Cleaning solution (msbuild /t:Clean)..."
    RunMSBuild 'PowerToys.slnx' '/t:Clean' $Platform $Configuration
}

try {
    if ($Version) {
        Write-Host "[VERSION] Setting PowerToys version to $Version using versionSetting.ps1..."
        $versionScript = Join-Path $repoRoot ".pipelines\versionSetting.ps1"
        if (Test-Path $versionScript) {
            & $versionScript -versionNumber $Version -DevEnvironment 'Local'
            if (-not $?) {
                Write-Error "versionSetting.ps1 failed"
                exit 1
            }
        } else {
            Write-Error "Could not find versionSetting.ps1 at: $versionScript"
            exit 1
        }
    }

    Write-Host "[VERSION] Setting up versioning using Microsoft.Windows.Terminal.Versioning..."

    # Check for nuget.exe - download to AppData if not available
    $nugetDownloaded = $false
    $nugetPath = $null
    if (-not (Get-Command nuget -ErrorAction SilentlyContinue)) {
        Write-Warning "nuget.exe not found in PATH. Attempting to download..."
        $nugetUrl = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
        $nugetDir = Join-Path $env:LOCALAPPDATA "PowerToys\BuildTools"
        if (-not (Test-Path $nugetDir)) { New-Item -ItemType Directory -Path $nugetDir -Force | Out-Null }
        $nugetPath = Join-Path $nugetDir "nuget.exe"
        if (-not (Test-Path $nugetPath)) {
            try {
                Invoke-WebRequest $nugetUrl -OutFile $nugetPath
                $nugetDownloaded = $true
            } catch {
                Write-Error "Failed to download nuget.exe. Please install it manually and add to PATH."
                exit 1
            }
        }
        $env:Path += ";$nugetDir"
    }

    # Install Terminal versioning package to AppData
    $versioningDir = Join-Path $env:LOCALAPPDATA "PowerToys\BuildTools\.versioning"
    if (-not (Test-Path $versioningDir)) { New-Item -ItemType Directory -Path $versioningDir -Force | Out-Null }

    $configFile = Join-Path $repoRoot ".pipelines\release-nuget.config"

    # Install the package
    # Use -ExcludeVersion to make the path predictable
    nuget install Microsoft.Windows.Terminal.Versioning -ConfigFile $configFile -OutputDirectory $versioningDir -ExcludeVersion -NonInteractive

    $versionRoot = Join-Path $versioningDir "Microsoft.Windows.Terminal.Versioning"
    $setupScript = Join-Path $versionRoot "build\Setup.ps1"

    if (Test-Path $setupScript) {
        & $setupScript -ProjectDirectory (Join-Path $repoRoot "src\modules\cmdpal") -Verbose
    } else {
        Write-Error "Could not find Setup.ps1 in $versionRoot"
    }

    # WiX v5 projects use WixToolset.Sdk via NuGet/MSBuild; no separate WiX installation is required.
    Write-Host ("[PIPELINE] Start | Platform={0} Configuration={1} PerUser={2}" -f $Platform, $Configuration, $PerUser)
    Write-Host ''

    $commonArgs = '/p:CIBuild=true /p:IsPipeline=true'

    if ($EnableCmdPalAOT) {
        $commonArgs += " /p:EnableCmdPalAOT=true"
    }

    # No local projects found (or continuing) - build full solution and tools
    if (-not $SkipBuild) {
        RestoreThenBuild 'PowerToys.slnx' $commonArgs $Platform $Configuration
    }

    $msixSearchRoot = Join-Path $repoRoot "$Platform\$Configuration"
    $msixFiles = Get-ChildItem -Path $msixSearchRoot -Recurse -Filter *.msix |
    Select-Object -ExpandProperty FullName

    if ($msixFiles.Count) {
        Write-Host ("[SIGN] .msix file(s): {0}" -f ($msixFiles -join '; '))
        & (Join-Path $PSScriptRoot "cert-sign-package.ps1") -TargetPaths $msixFiles
    }
    else {
        Write-Warning "[SIGN] No .msix files found in $msixSearchRoot"
    }

    # Generate DSC v2 manifests (PowerToys.Settings.DSC.Schema.Generator)
    # The csproj PostBuild event is skipped on ARM64, so we run it manually here if needed.
    if ($Platform -eq 'arm64') {
        Write-Host "[DSC] Manually generating DSC v2 manifests for ARM64..."

        # 1. Get Version
        $versionPropsPath = Join-Path $repoRoot "src\Version.props"
        [xml]$versionProps = Get-Content $versionPropsPath
        $ptVersion = $versionProps.Project.PropertyGroup.Version
        # Directory.Build.props appends .0 to the version for .csproj files
        $ptVersionFull = "$ptVersion.0"
        
        # 2. Build the Generator
        $generatorProj = Join-Path $repoRoot "src\dsc\PowerToys.Settings.DSC.Schema.Generator\PowerToys.Settings.DSC.Schema.Generator.csproj"
        RunMSBuild $generatorProj "/t:Build" $Platform $Configuration

        # 3. Define paths
        # The generator output path is in the project's bin folder
        $generatorExe = Join-Path $repoRoot "src\dsc\PowerToys.Settings.DSC.Schema.Generator\bin\$Platform\$Configuration\PowerToys.Settings.DSC.Schema.Generator.exe"
        
        if (-not (Test-Path $generatorExe)) {
            Write-Warning "Could not find generator at expected path: $generatorExe"
            Write-Warning "Searching in build output..."
            $found = Get-ChildItem -Path (Join-Path $repoRoot "$Platform\$Configuration") -Filter "PowerToys.Settings.DSC.Schema.Generator.exe" -Recurse | Select-Object -First 1
            if ($found) {
                $generatorExe = $found.FullName
            }
        }

        $settingsLibDll = Join-Path $repoRoot "$Platform\$Configuration\WinUI3Apps\PowerToys.Settings.UI.Lib.dll"
        
        $dscGenDir = Join-Path $repoRoot "src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$ptVersionFull"
        if (-not (Test-Path $dscGenDir)) {
            New-Item -ItemType Directory -Path $dscGenDir -Force | Out-Null
        }
        
        $outPsm1 = Join-Path $dscGenDir "Microsoft.PowerToys.Configure.psm1"
        $outPsd1 = Join-Path $dscGenDir "Microsoft.PowerToys.Configure.psd1"

        # 4. Run Generator
        if (Test-Path $generatorExe) {
            Write-Host "[DSC] Executing: $generatorExe"
            
            $generatorDir = Split-Path -Parent $generatorExe
            $winUI3AppsDir = Join-Path $repoRoot "$Platform\$Configuration\WinUI3Apps"
            
            # Copy dependencies from WinUI3Apps to Generator directory to satisfy WinRT/WinUI3 dependencies
            # This avoids "Class not registered" errors without polluting the WinUI3Apps directory which is used for packaging.
            if (Test-Path $winUI3AppsDir) {
                Write-Host "[DSC] Copying dependencies from $winUI3AppsDir to $generatorDir"
                Get-ChildItem -Path $winUI3AppsDir -Filter "*.dll" | ForEach-Object {
                    $destPath = Join-Path $generatorDir $_.Name
                    if (-not (Test-Path $destPath)) {
                        Copy-Item -Path $_.FullName -Destination $destPath -Force
                    }
                }
                # Also copy resources.pri if it exists, as it might be needed for resource lookup
                $priFile = Join-Path $winUI3AppsDir "resources.pri"
                if (Test-Path $priFile) {
                    Copy-Item -Path $priFile -Destination $generatorDir -Force
                }
            }

            Push-Location $generatorDir
            try {
                # Now we can use the local DLLs
                $localSettingsLibDll = Join-Path $generatorDir "PowerToys.Settings.UI.Lib.dll"
                
                if (Test-Path $localSettingsLibDll) {
                    Write-Host "[DSC] Using local DLL: $localSettingsLibDll"
                    & $generatorExe $localSettingsLibDll $outPsm1 $outPsd1
                } else {
                    # Fallback (shouldn't happen if copy succeeded or build was correct)
                    Write-Warning "[DSC] Local DLL not found, falling back to: $settingsLibDll"
                    & $generatorExe $settingsLibDll $outPsm1 $outPsd1
                }

                if ($LASTEXITCODE -ne 0) {
                    Write-Error "DSC v2 generation failed with exit code $LASTEXITCODE"
                    exit 1
                }
            } finally {
                Pop-Location
            }
            
            Write-Host "[DSC] DSC v2 manifests generated successfully."
        } else {
            Write-Error "Could not find generator executable at $generatorExe"
            exit 1
        }
    }

    # Generate DSC manifest files
    Write-Host '[DSC] Generating DSC manifest files...'
    $dscScriptPath = Join-Path $repoRoot '.\tools\build\generate-dsc-manifests.ps1'
    if (Test-Path $dscScriptPath) {
        & $dscScriptPath -BuildPlatform $Platform -BuildConfiguration $Configuration -RepoRoot $repoRoot
        if ($LASTEXITCODE -ne 0) {
            Write-Error "DSC manifest generation failed with exit code $LASTEXITCODE"
            exit 1
        }
        Write-Host '[DSC] DSC manifest files generated successfully'
    } else {
        Write-Warning "[DSC] DSC manifest generator script not found at: $dscScriptPath"
    }

    if (-not $SkipBuild) {
        RestoreThenBuild 'tools\BugReportTool\BugReportTool.sln' $commonArgs $Platform $Configuration
        RestoreThenBuild 'tools\StylesReportTool\StylesReportTool.sln' $commonArgs $Platform $Configuration
    }

    # Set NUGET_PACKAGES environment variable if not set, to help wixproj find heat.exe
    if (-not $env:NUGET_PACKAGES) {
        $env:NUGET_PACKAGES = Join-Path $env:USERPROFILE ".nuget\packages"
        Write-Host "[ENV] Set NUGET_PACKAGES to $env:NUGET_PACKAGES"
    }

    RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /t:restore /p:RestorePackagesConfig=true" $Platform $Configuration

    RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /m /t:PowerToysInstallerVNext /p:PerUser=$PerUser" $Platform $Configuration

    # Fix: WiX v5 locally puts the MSI in an 'en-us' subfolder, but the Bootstrapper expects it in the root of UserSetup/MachineSetup.
    # We move it up one level to match expectations.
    $setupType = if ($PerUser -eq 'true') { 'UserSetup' } else { 'MachineSetup' }
    $msiParentDir = Join-Path $repoRoot "installer\PowerToysSetupVNext\$Platform\$Configuration\$setupType"
    $msiEnUsDir = Join-Path $msiParentDir "en-us"

    if (Test-Path $msiEnUsDir) {
        Write-Host "[FIX] Moving MSI files from $msiEnUsDir to $msiParentDir"
        Get-ChildItem -Path $msiEnUsDir -Filter *.msi | Move-Item -Destination $msiParentDir -Force
    }

    RunMSBuild 'installer\PowerToysSetup.slnx' "$commonArgs /m /t:PowerToysBootstrapperVNext /p:PerUser=$PerUser" $Platform $Configuration

} finally {
    # No git cleanup; leave workspace state as-is.
}

Write-Host '[PIPELINE] Completed'
