# Copyright (c) 2026 Progralink. Licensed under the DevCage License — see LICENSE.
#
# Volumes.ps1 — Volume building and environment file loading for the DevCage
# PowerShell launcher. Dot-sourced by devcage.ps1.
# Depends on: $ContainerEngine, $Cwd, $WorkspaceSlug, $SandboxTmpDir,
#             $GlobalConfigDir, $UserConfigDir, $ProjectConfigDir,
#             $script:ActiveProfiles, $HostAgentsConfig, $EmbedDocker,
#             $NoCacheVolumes, $CacheDirs, $CustomVolumes, $EnvVars,
#             $GitAuth, $versionsFiles, $MergedVersionsContent,
#             $script:EnvBlockedNames, $script:EnvBlockedPrefixes.

# ── Agent configuration volumes ───────────────────────────────────
# Sets $script:VolumeArgs (agent config dirs) and $env:AGENT_CONFIG_RO.
function Build-AgentConfigVolumes {
    $script:VolumeArgs = @()

    # AGENT_CONFIG_RO mapping: ro→1, rw→0, no/disabled→skip
    if ($HostAgentsConfig -eq "rw") {
        $env:AGENT_CONFIG_RO = "0"
    } elseif ($HostAgentsConfig -eq "no" -or $HostAgentsConfig -eq "disabled") {
        $env:AGENT_CONFIG_RO = "skip"
    } else {
        $env:AGENT_CONFIG_RO = "1"
    }

    # Skip agent config mounting entirely in no/disabled mode
    if ($HostAgentsConfig -eq "no" -or $HostAgentsConfig -eq "disabled") { return }

    # Each entry: HostDir, RoMount, RwMount, CacheVolume
    $agentConfigs = @(
        @{ HostDir = Join-Path $env:USERPROFILE ".claude";             RoMount = "/agent-config/claude";     RwMount = "/home/devcage/.claude";              Vol = "devcage-claude-config" }
        @{ HostDir = Join-Path $env:USERPROFILE ".config\gemini-cli";  RoMount = "/agent-config/gemini-cli"; RwMount = "/home/devcage/.config/gemini-cli";   Vol = "devcage-gemini-config" }
        @{ HostDir = Join-Path $env:USERPROFILE ".codex";              RoMount = "/agent-config/codex";      RwMount = "/home/devcage/.codex";               Vol = "devcage-codex-config" }
        @{ HostDir = Join-Path $env:USERPROFILE ".copilot";            RoMount = "/agent-config/copilot";    RwMount = "/home/devcage/.copilot";             Vol = "devcage-copilot-config" }
        @{ HostDir = Join-Path $env:USERPROFILE ".config\goose";       RoMount = "/agent-config/goose";      RwMount = "/home/devcage/.config/goose";        Vol = "devcage-goose-config" }
    )

    foreach ($cfg in $agentConfigs) {
        if (Test-Path $cfg.HostDir) {
            if ($HostAgentsConfig -eq "rw") {
                $script:VolumeArgs += @("-v", "$($cfg.HostDir):$($cfg.RwMount)")
            } else {
                $script:VolumeArgs += @("-v", "$($cfg.HostDir):$($cfg.RoMount):ro")
            }
        } else {
            if ($HostAgentsConfig -eq "rw") {
                # RW mode: mount fallback volume directly at home path
                $script:VolumeArgs += @("-v", "$($cfg.Vol):$($cfg.RwMount)")
            } else {
                # RO mode: mount under /agent-config/ so entrypoint can
                # persist state in workspace via symlinks
                $script:VolumeArgs += @("-v", "$($cfg.Vol):$($cfg.RoMount):ro")
            }
        }
    }

    # Claude: global config file (~/.claude.json) — stores onboarding state, theme, etc.
    $claudeJson = Join-Path $env:USERPROFILE ".claude.json"
    if (Test-Path $claudeJson) {
        if ($HostAgentsConfig -eq "rw") {
            $script:VolumeArgs += @("-v", "${claudeJson}:/home/devcage/.claude.json")
        } else {
            $script:VolumeArgs += @("-v", "${claudeJson}:/agent-config/claude.json:ro")
        }
    }

    # Aider: single file, not directory
    $aiderConf = Join-Path $env:USERPROFILE ".aider.conf.yml"
    if (Test-Path $aiderConf) {
        if ($HostAgentsConfig -eq "rw") {
            $script:VolumeArgs += @("-v", "${aiderConf}:/home/devcage/.aider.conf.yml")
        } else {
            $script:VolumeArgs += @("-v", "${aiderConf}:/agent-config/aider.conf.yml:ro")
        }
    }

    # Git config & credentials (--git-auth, volumes)
    if ($GitAuth) {
        $gitConfigFile = Join-Path $env:USERPROFILE ".gitconfig"
        if (Test-Path $gitConfigFile) {
            $script:VolumeArgs += @("-v", "${gitConfigFile}:/agent-config/gitconfig:ro")
        }
        $gitCredFile = Join-Path $env:USERPROFILE ".git-credentials"
        if (Test-Path $gitCredFile) {
            $script:VolumeArgs += @("-v", "${gitCredFile}:/agent-config/git-credentials:ro")
        }
    }
}

# ── Profile init/crontab/agents helper ───────────────────────────
function _Mount-ProfileFiles {
    param([string]$ParentDir, [string]$Prefix, [string]$FileName,
          [string]$DestDir, [string]$DestSuffix, [ref]$VolumeList, [ref]$ProjectFlag)
    if (-not $script:ActiveProfiles -or $script:ActiveProfiles.Count -eq 0) { return }
    $pidx = 0
    foreach ($pdir in (Get-ProfileDirs -ParentDir $ParentDir)) {
        $pidx++
        $pfile = Join-Path $pdir $FileName
        if (Test-Path $pfile) {
            $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'  # skip "config."
            $pidxStr = $pidx.ToString("D2")
            $VolumeList.Value += @("-v", "${pfile}:${DestDir}/${Prefix}p${pidxStr}-${pname}.${DestSuffix}:ro")
            if ($Prefix -eq "03" -and $ProjectFlag) {
                $ProjectFlag.Value = $true
            }
        }
    }
}

# ── Init script volumes ───────────────────────────────────────────
# Sets $script:InitVolumeArgs and $script:SandboxProjectInit.
function Build-InitVolumes {
    $script:InitVolumeArgs = @()
    $script:SandboxProjectInit = $false

    $globalInit  = Join-Path $GlobalConfigDir "init.sh"
    $userInit    = if ($UserConfigDir)   { Join-Path $UserConfigDir "init.sh" }   else { "" }
    $projectInit = if ($ProjectConfigDir) { Join-Path $ProjectConfigDir "init.sh" } else { "" }

    if (Test-Path $globalInit) {
        $script:InitVolumeArgs += @("-v", "${globalInit}:/sandbox-init.d/01-global.sh:ro")
    }
    if ($script:ActiveProfiles.Count -gt 0) {
        $pidx = 0
        foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $GlobalConfigDir -Parent))) {
            $pidx++
            $pfile = Join-Path $pdir "init.sh"
            if (Test-Path $pfile) {
                $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                $script:InitVolumeArgs += @("-v", "${pfile}:/sandbox-init.d/01p$($pidx.ToString("D2"))-${pname}.sh:ro")
            }
        }
    }

    if ($UserConfigDir) {
        if (Test-Path $userInit) {
            $script:InitVolumeArgs += @("-v", "${userInit}:/sandbox-init.d/02-user.sh:ro")
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $UserConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "init.sh"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:InitVolumeArgs += @("-v", "${pfile}:/sandbox-init.d/02p$($pidx.ToString("D2"))-${pname}.sh:ro")
                }
            }
        }
    }

    if ($ProjectConfigDir) {
        if (Test-Path $projectInit) {
            $script:InitVolumeArgs += @("-v", "${projectInit}:/sandbox-init.d/03-project.sh:ro")
            $script:SandboxProjectInit = $true
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $ProjectConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "init.sh"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:InitVolumeArgs += @("-v", "${pfile}:/sandbox-init.d/03p$($pidx.ToString("D2"))-${pname}.sh:ro")
                    $script:SandboxProjectInit = $true
                }
            }
        }
    }

    # Extra init script (--script flag or EXTRA_SCRIPT key) — sourced last
    if ($ExtraInitScript) {
        $resolvedScript = $ExtraInitScript
        if (-not [System.IO.Path]::IsPathRooted($resolvedScript)) {
            $resolvedScript = Join-Path (Get-Location).Path $resolvedScript
        }
        if (Test-Path $resolvedScript) {
            $resolvedScript = (Resolve-Path $resolvedScript).Path
            $script:InitVolumeArgs += @("-v", "${resolvedScript}:/sandbox-init.d/04-custom.sh:ro")
        }
    }
}

# ── Crontab volumes ───────────────────────────────────────────────
# Sets $script:CrontabVolumeArgs and $script:SandboxProjectCrontab.
function Build-CrontabVolumes {
    $script:CrontabVolumeArgs = @()
    $script:SandboxProjectCrontab = $false

    $globalCrontab  = Join-Path $GlobalConfigDir "crontab"
    $userCrontab    = if ($UserConfigDir)   { Join-Path $UserConfigDir "crontab" }   else { "" }
    $projectCrontab = if ($ProjectConfigDir) { Join-Path $ProjectConfigDir "crontab" } else { "" }

    if (Test-Path $globalCrontab) {
        $script:CrontabVolumeArgs += @("-v", "${globalCrontab}:/sandbox-crontab.d/01-global.crontab:ro")
    }
    if ($script:ActiveProfiles.Count -gt 0) {
        $pidx = 0
        foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $GlobalConfigDir -Parent))) {
            $pidx++
            $pfile = Join-Path $pdir "crontab"
            if (Test-Path $pfile) {
                $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                $script:CrontabVolumeArgs += @("-v", "${pfile}:/sandbox-crontab.d/01p$($pidx.ToString("D2"))-${pname}.crontab:ro")
            }
        }
    }

    if ($UserConfigDir) {
        if (Test-Path $userCrontab) {
            $script:CrontabVolumeArgs += @("-v", "${userCrontab}:/sandbox-crontab.d/02-user.crontab:ro")
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $UserConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "crontab"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:CrontabVolumeArgs += @("-v", "${pfile}:/sandbox-crontab.d/02p$($pidx.ToString("D2"))-${pname}.crontab:ro")
                }
            }
        }
    }

    if ($ProjectConfigDir) {
        if (Test-Path $projectCrontab) {
            $script:CrontabVolumeArgs += @("-v", "${projectCrontab}:/sandbox-crontab.d/03-project.crontab:ro")
            $script:SandboxProjectCrontab = $true
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $ProjectConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "crontab"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:CrontabVolumeArgs += @("-v", "${pfile}:/sandbox-crontab.d/03p$($pidx.ToString("D2"))-${pname}.crontab:ro")
                    $script:SandboxProjectCrontab = $true
                }
            }
        }
    }
}

# ── AGENTS.md volumes ─────────────────────────────────────────────
# Sets $script:AgentsVolumeArgs.
function Build-AgentsVolumes {
    $script:AgentsVolumeArgs = @()

    $globalAgents  = Join-Path $GlobalConfigDir "AGENTS.md"
    $userAgents    = if ($UserConfigDir)   { Join-Path $UserConfigDir "AGENTS.md" }   else { "" }
    $projectAgents = if ($ProjectConfigDir) { Join-Path $ProjectConfigDir "AGENTS.md" } else { "" }

    if (Test-Path $globalAgents) {
        $script:AgentsVolumeArgs += @("-v", "${globalAgents}:/sandbox-agents.d/01-global.md:ro")
    }
    if ($script:ActiveProfiles.Count -gt 0) {
        $pidx = 0
        foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $GlobalConfigDir -Parent))) {
            $pidx++
            $pfile = Join-Path $pdir "AGENTS.md"
            if (Test-Path $pfile) {
                $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                $script:AgentsVolumeArgs += @("-v", "${pfile}:/sandbox-agents.d/01p$($pidx.ToString("D2"))-${pname}.md:ro")
            }
        }
    }

    if ($UserConfigDir) {
        if (Test-Path $userAgents) {
            $script:AgentsVolumeArgs += @("-v", "${userAgents}:/sandbox-agents.d/02-user.md:ro")
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $UserConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "AGENTS.md"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:AgentsVolumeArgs += @("-v", "${pfile}:/sandbox-agents.d/02p$($pidx.ToString("D2"))-${pname}.md:ro")
                }
            }
        }
    }

    if ($ProjectConfigDir) {
        if (Test-Path $projectAgents) {
            $script:AgentsVolumeArgs += @("-v", "${projectAgents}:/sandbox-agents.d/03-project.md:ro")
        }
        if ($script:ActiveProfiles.Count -gt 0) {
            $pidx = 0
            foreach ($pdir in (Get-ProfileDirs -ParentDir (Split-Path $ProjectConfigDir -Parent))) {
                $pidx++
                $pfile = Join-Path $pdir "AGENTS.md"
                if (Test-Path $pfile) {
                    $pname = (Split-Path $pdir -Leaf).Substring(7) -replace '\.', '-'
                    $script:AgentsVolumeArgs += @("-v", "${pfile}:/sandbox-agents.d/03p$($pidx.ToString("D2"))-${pname}.md:ro")
                }
            }
        }
    }
}

# ── Versions config volume ────────────────────────────────────────
# Writes merged versions.conf to temp dir and mounts it.
# Sets $script:VersionsVolumeArgs.
function Build-VersionsVolume {
    $script:VersionsVolumeArgs = @()
    if ($versionsFiles.Count -gt 0) {
        $mergedVersionsFile = Join-Path $SandboxTmpDir "merged-versions.conf"
        # Write with BOM-less UTF-8 and Unix line endings (LF only).
        $utf8NoBom = New-Object System.Text.UTF8Encoding $false
        [System.IO.File]::WriteAllText($mergedVersionsFile, $MergedVersionsContent, $utf8NoBom)
        $script:VersionsVolumeArgs = @("-v", "${mergedVersionsFile}:/etc/devcage/versions.conf:ro")
    }
}

# ── Package cache volumes ─────────────────────────────────────────
# Sets $script:CacheVolumeArgs.
function Build-CacheVolumes {
    $script:CacheVolumeArgs = @()
    if (-not $NoCacheVolumes) {
        $script:CacheVolumeArgs = @(
            "-v", "devcage_${WorkspaceSlug}_cache:/cache",
            "-v", "devcage_${WorkspaceSlug}_local:/home/devcage/.local"
        )
    }
}

# ── Directory cache volumes ───────────────────────────────────────
# Sets $script:DirCacheVolumeArgs, $script:DirCacheCount, $script:DirCachePaths,
# $script:hasSubpath, $script:dcPaths.
function Build-DirCacheVolumes {
    $script:DirCacheVolumeArgs = @()
    $script:DirCachePaths = ""
    $script:DirCacheCount = 0
    $script:hasSubpath = $false
    $script:dcPaths = @()

    if ($NoCacheVolumes -or $CacheDirs -eq "") { return }

    # Detect engine version for volume-subpath support (Docker >= 26, Podman >= 5)
    $engineMajor = 0
    $engineVerRaw = & $ContainerEngine version --format '{{.Server.Version}}' 2>$null
    if ($engineVerRaw -match '^(\d+)') { $engineMajor = [int]$Matches[1] }
    if ($ContainerEngine -eq "docker" -and $engineMajor -ge 26) { $script:hasSubpath = $true }
    elseif ($ContainerEngine -eq "podman" -and $engineMajor -ge 5) { $script:hasSubpath = $true }

    $cacheDirNames = $CacheDirs -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
    foreach ($dcName in $cacheDirNames) {
        if ($dcName -eq "node_modules") {
            # Special case: scan for package.json to discover all locations
            $pjFiles = Get-ChildItem -Path $Cwd -Filter "package.json" -Recurse -Depth 5 -File -ErrorAction SilentlyContinue |
                Where-Object { $_.FullName -notmatch '[/\\]node_modules[/\\]' -and $_.FullName -notmatch '[/\\]\.devcage[/\\]' } |
                Sort-Object FullName
            foreach ($pj in $pjFiles) {
                $pdir = $pj.DirectoryName
                $wsNorm = $Cwd.TrimEnd('\', '/')
                $pdirNorm = $pdir.TrimEnd('\', '/')
                if ($pdirNorm -eq $wsNorm) {
                    $script:dcPaths += "node_modules"
                } else {
                    $relDir = $pdirNorm.Substring($wsNorm.Length + 1).Replace('\', '/')
                    $script:dcPaths += "${relDir}/node_modules"
                }
            }
        } else {
            # Generic: scan for existing directories with that name
            $foundDirs = Get-ChildItem -Path $Cwd -Filter $dcName -Recurse -Depth 3 -Directory -ErrorAction SilentlyContinue |
                Where-Object { $_.FullName -notmatch '[/\\]node_modules[/\\]' -and $_.FullName -notmatch '[/\\]\.devcage[/\\]' } |
                Sort-Object FullName
            foreach ($fd in $foundDirs) {
                $wsNorm = $Cwd.TrimEnd('\', '/')
                $fdNorm = $fd.FullName.TrimEnd('\', '/')
                if ($fdNorm -eq $wsNorm) {
                    $script:dcPaths += $dcName
                } else {
                    $relDir = $fdNorm.Substring($wsNorm.Length + 1).Replace('\', '/')
                    $script:dcPaths += $relDir
                }
            }
        }
    }

    # Build volume args for each detected path (skip paths with spaces)
    foreach ($dcPath in $script:dcPaths) {
        if ($dcPath -match '\s') {
            Write-Host "Warning: skipping dircache path '$dcPath' -- paths with spaces not supported" -ForegroundColor Yellow
            continue
        }
        if ($script:hasSubpath) {
            $script:DirCacheVolumeArgs += @("--mount", "type=volume,source=devcage_${WorkspaceSlug}_cache,target=/workspace/${dcPath},volume-subpath=dircache/${dcPath}")
        } else {
            # Fallback: separate named volume per path
            $sanitized = ($dcPath -replace '[/\\]', '-').ToLower() -replace '[^a-z0-9-]', '-'
            $script:DirCacheVolumeArgs += @("-v", "devcage_${WorkspaceSlug}_dircache_${sanitized}:/workspace/${dcPath}")
        }
    }

    $script:DirCacheCount = $script:dcPaths.Count
    if ($script:DirCacheCount -gt 0) {
        $script:DirCachePaths = $script:dcPaths -join ','
        # Mount workspace read-only for entrypoint seeding
        $script:DirCacheVolumeArgs += @("-v", "${Cwd}:/workspace-host:ro")
        $script:DirCacheVolumeArgs += @("-e", "DIR_CACHE_PATHS=${script:DirCachePaths}")
    }
}

# ── Custom volume mounts ──────────────────────────────────────────
# Sets $script:CustomVolumeArgs.
function Build-CustomVolumes {
    $script:CustomVolumeArgs = @()
    foreach ($volSpec in $CustomVolumes) {
        $validated = Test-VolumeSpec $volSpec
        if (-not $validated) { Remove-SandboxTmpDir; exit 1 }
        $script:CustomVolumeArgs += @("-v", $validated)
    }
}

# ── Env files loading ─────────────────────────────────────────────
# Appends --env and --env-file args to $script:RunArgs.
# Also adds git-auth token env file, no-DinD docker env, and HOST_OS.
# Must be called after Build-RunArgs initializes RunArgs.
function Load-EnvFiles {
    # User-specified --env args
    foreach ($envVar in $EnvVars) {
        $script:RunArgs += @("-e", $envVar)
    }

    # Collect .env files per scope (base + profiles)
    $allEnvFiles = @()
    foreach ($f in (Get-ScopeFiles -ScopeDir $GlobalConfigDir -FileName ".env")) { $allEnvFiles += @{ Scope = "global"; Path = $f } }
    if ($UserConfigDir) {
        foreach ($f in (Get-ScopeFiles -ScopeDir $UserConfigDir -FileName ".env")) { $allEnvFiles += @{ Scope = "user"; Path = $f } }
    }
    if ($ProjectConfigDir) {
        foreach ($f in (Get-ScopeFiles -ScopeDir $ProjectConfigDir -FileName ".env")) { $allEnvFiles += @{ Scope = "project"; Path = $f } }
    }

    $projectEnvCounter = 0
    foreach ($envEntry in $allEnvFiles) {
        $envFilePath = $envEntry.Path
        $envScope = $envEntry.Scope
        if (Test-Path $envFilePath) {
            $hasActiveVars = Get-Content $envFilePath | Where-Object { $_ -match '^[^#\s]' } | Select-Object -First 1
            if ($hasActiveVars) {
                if ($envScope -eq "project") {
                    # Filter project-level .env to prevent privilege escalation via env vars
                    $projectEnvCounter++
                    $filteredPath = Join-Path $SandboxTmpDir ".project-env-filtered-$projectEnvCounter"
                    $droppedCount = 0
                    $filteredLines = @()
                    foreach ($line in Get-Content $envFilePath) {
                        if (-not $line -or $line.StartsWith("#")) {
                            $filteredLines += $line
                            continue
                        }
                        $varName = if ($line -match '^([^=]+)=') { $Matches[1].Trim() } else { "" }
                        $blocked = $false
                        if ($varName) {
                            $upperVar = $varName.ToUpperInvariant()
                            if ($EnvBlockedNames -contains $upperVar) { $blocked = $true }
                            if (-not $blocked) {
                                foreach ($prefix in $EnvBlockedPrefixes) {
                                    if ($upperVar.StartsWith($prefix)) { $blocked = $true; break }
                                }
                            }
                        }
                        if ($blocked) {
                            $droppedCount++
                        } else {
                            $filteredLines += $line
                        }
                    }
                    if ($droppedCount -gt 0) {
                        [Console]::Error.WriteLine("Warning: filtered $droppedCount blocked variable(s) from project .env ($(Split-Path (Split-Path $envFilePath -Parent) -Leaf))")
                    }
                    if ($filteredLines.Count -gt 0) {
                        $hasActive = $filteredLines | Where-Object { $_ -match '^[^#\s]' } | Select-Object -First 1
                        if ($hasActive) {
                            $utf8NoBom = New-Object System.Text.UTF8Encoding $false
                            [System.IO.File]::WriteAllText($filteredPath, ($filteredLines -join "`n") + "`n", $utf8NoBom)
                            $script:RunArgs += @("--env-file", $filteredPath)
                        }
                    }
                } else {
                    $script:RunArgs += @("--env-file", $envFilePath)
                }
            }
        }
    }

    # Git auth env vars (token passthrough via env-file to avoid ps aux exposure)
    if ($GitAuth) {
        $gitEnvFile = Join-Path $SandboxTmpDir ".git-env"
        $gitEnvLines = @()
        if ($env:GH_TOKEN) { $gitEnvLines += "GH_TOKEN=$($env:GH_TOKEN)" }
        if ($env:GITHUB_TOKEN) { $gitEnvLines += "GITHUB_TOKEN=$($env:GITHUB_TOKEN)" }
        if ($gitEnvLines.Count -gt 0) {
            $utf8NoBom = New-Object System.Text.UTF8Encoding $false
            [System.IO.File]::WriteAllText($gitEnvFile, ($gitEnvLines -join "`n") + "`n", $utf8NoBom)
            # Restrict file permissions to current user only
            $acl = New-Object System.Security.AccessControl.FileSecurity
            $acl.SetAccessRuleProtection($true, $false)
            $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
            $acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($currentUser, "FullControl", "Allow")))
            try { Set-Acl -Path $gitEnvFile -AclObject $acl } catch { Write-Host "Warning: could not restrict git env file ACLs: $_" -ForegroundColor Yellow }
            $script:RunArgs += @("--env-file", $gitEnvFile)
        }
    }

    if (-not $EmbedDocker) {
        # Docker env vars disabled (no DinD sidecar)
        $script:RunArgs += @("-e", "DOCKER_HOST=tcp://disabled:0")
        $script:RunArgs += @("-e", "DOCKER_TLS_VERIFY=")
        $script:RunArgs += @("-e", "DOCKER_CERT_PATH=/nonexistent")
    }

    # Host OS for entrypoint platform-specific optimizations
    $script:RunArgs += @("-e", "HOST_OS=windows")
}

# ── Build all volumes ─────────────────────────────────────────────
# Calls all volume builders in order.
# Note: Load-EnvFiles is NOT called here — it must be called after
# Build-RunArgs initializes RunArgs.
function Build-AllVolumes {
    Build-AgentConfigVolumes
    Build-InitVolumes
    Build-CrontabVolumes
    Build-AgentsVolumes
    Build-VersionsVolume
    Build-CacheVolumes
    Build-DirCacheVolumes
    Build-CustomVolumes
}
