function Zip-Includes {
    param (
        [string]$IncludesFile,
        [string]$BuildNumber,
        [string]$BuildFolder,
        [string]$AppRoot
    )

    # Read all paths from .includes
    $itemsToZip = @()
    Get-Content $IncludesFile | ForEach-Object {
        $relPath = $_.Trim()
        if ($relPath -ne "") {
            $fullPath = Join-Path $AppRoot $relPath
            if (Test-Path $fullPath) {
                $itemsToZip += [PSCustomObject]@{
                    FullPath = $fullPath
                    RelativePath = $relPath
                }
            } else {
                Write-Warning "Path not found: $fullPath"
            }
        }
    }

    if (-Not $itemsToZip) {
        Write-Warning "No valid files or folders to zip."
        return
    }

    $zipFileName = Join-Path $BuildFolder "controllers-$BuildNumber.zip"

    if (Test-Path $zipFileName) {
        Remove-Item $zipFileName
    }

    # Create zip preserving relative paths
    $tempFolder = Join-Path $env:TEMP "zip-temp-$BuildNumber"
    if (Test-Path $tempFolder) { Remove-Item $tempFolder -Recurse -Force }
    New-Item -Path $tempFolder -ItemType Directory | Out-Null

    foreach ($item in $itemsToZip) {
        $dest = Join-Path $tempFolder $item.RelativePath
        $destDir = Split-Path $dest
        if (-Not (Test-Path $destDir)) { New-Item -Path $destDir -ItemType Directory | Out-Null }

        if ((Get-Item $item.FullPath).PSIsContainer) {
            Copy-Item -Path $item.FullPath -Destination $dest -Recurse
        } else {
            Copy-Item -Path $item.FullPath -Destination $dest
        }
    }

    Compress-Archive -Path (Join-Path $tempFolder "*") -DestinationPath $zipFileName

    # Cleanup temp folder
    Remove-Item $tempFolder -Recurse -Force

    Write-Output "Created zip: $zipFileName"
}
