functions/Get-EndjinGist.ps1

# <copyright file="Get-EndjinGist.ps1" company="Endjin Limited">
# Copyright (c) Endjin Limited. All rights reserved.
# </copyright>

<#
.SYNOPSIS
    Downloads gist content from a Git repository using vendir.
 
.DESCRIPTION
    The Get-EndjinGist function downloads gist content from a specified group and name
    using the vendir tool. It reads available gists from a YAML configuration file,
    generates a vendir configuration, and synchronizes the content to a local directory.
 
.PARAMETER Group
    The group name containing the gist. This must match a key in the Gist Map configuration file.
 
.PARAMETER Name
    The name of the specific gist to download within the specified group.
 
.PARAMETER DestinationBaseDir
    The destination path for the downloaded gist content. The remainder of the destination path is
    derived from the Gist 'Group' and 'Name'.
 
.PARAMETER GistMapPath
    The path to a 'Gist Map' configuration file. Defaults to the configuration file
    distributed with the module (gist-map.yml).
 
.EXAMPLE
    Get-EndjinGist -Group "azure" -Name "deploy-script"
 
    Downloads the 'deploy-script' gist from the 'azure' group to the default location.
 
.EXAMPLE
    Get-EndjinGist -Group "common" -Name "logging" -Verbose
 
    Downloads the 'logging' gist from the 'common' group with verbose output enabled.
 
.NOTES
    Requires the 'vendir' tool to be installed and available in the PATH.
    The function creates a temporary .endjin-gists.yml file which is cleaned up
    after successful execution (unless Verbose mode is enabled).
 
.LINK
    https://carvel.dev/vendir/
#>

function Get-EndjinGist {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, Position=0)]
        [string] $Group,

        [Parameter(Mandatory, Position=1)]
        [string] $Name,

        [Parameter()]
        [string] $DestinationBaseDir = '.endjin',

        [Parameter()]
        [string] $GistMapPath = (Join-Path $PSScriptRoot '..' 'gist-map.yml')
    )

    begin {
        Set-StrictMode -Version Latest

        if (!(Get-Command 'vendir' -ErrorAction SilentlyContinue)) {
            throw "Please install 'vendir' to your PATH before using this tool."
        }

        $availableGists = Get-Content $GistMapPath | ConvertFrom-Yaml
        Write-Verbose "Available gists:`n$($availableGists | ConvertTo-Json -Depth 5)"

        if (!$availableGists.ContainsKey($Group)) {
            throw "Unknown gist group: $Group"
        }

        $vendirConfig = [ordered]@{
            apiVersion = 'vendir.k14s.io/v1alpha1'
            kind = 'Config'
            directories = @()
        }

        # Track gists that have movePaths for post-download processing
        $gistsWithMovePaths = [System.Collections.Generic.List[hashtable]]::new()
    }
    
    process {
        $gistConfig = $availableGists[$Group] | Where-Object { $_.name -eq $Name }
        if (!$gistConfig) {
            throw "Unknown gist '$Name' in group '$Group'"
        }

        Write-Information "⚙️ Processing '$Name' from group '$Group'"
        $directoryConfig =  [ordered]@{
            path = "$DestinationBaseDir/$Group/$Name"
            contents = @(
                @{
                    path = '.'
                    git = [ordered]@{
                        url = $gistConfig.source
                        ref = $gistConfig.ref
                    }
                    includePaths = [array]($gistConfig.includePaths)
                }
            )
        }

        if ($gistConfig.ContainsKey('sourceDirectory')) {
            $directoryConfig.contents[0] += @{ newRootPath = $gistConfig.sourceDirectory }
        }

        $vendirConfig.directories += [array]$directoryConfig

        # Track gists with movePaths for post-download processing
        if ($gistConfig.ContainsKey('movePaths')) {
            $gistsWithMovePaths.Add(@{
                Config = $gistConfig
                DestinationPath = "$DestinationBaseDir/$Group/$Name"
            })
        }
    }
    
    end {
        $vendirFilename = '.endjin-gists.yml'
        $outputPath = Join-Path $PWD $vendirFilename
        
        ConvertTo-Yaml -Data $vendirConfig -OutFile $outputPath -Force
        Write-Verbose "Generated vendir.yml:`n$(Get-Content $outputPath | ConvertFrom-Yaml -Ordered | ConvertTo-Json -Depth 5)"

        Write-Information "⌛ Downloading gists..."
        $PSNativeCommandUseErrorActionPreference = $false
        $result = Invoke-Command { & vendir sync --file $vendirFilename 2>&1 }
        $result | Write-Verbose

        if ($LASTEXITCODE -ne 0) {
            if ($result -imatch 'Access is denied') {
                Write-Host -f Red @"
Operation failed due to potential file contention with VSCode, please add the following to your VSCode settings:
`n`"files.watcherExclude`": {
`t`"**/.vendir-tmp-*/**`": true
}`n
"@

            }
            else {
                throw "Operation failed with exit code $LASTEXITCODE.`n$result"
            }
        }

        # Process movePaths for gists that have them
        if ($gistsWithMovePaths.Count -gt 0) {
            # Determine repo root for ${repoRoot} variable expansion
            $repoRoot = & git rev-parse --show-toplevel 2>$null
            if (-not $repoRoot) {
                $repoRoot = $PWD.Path
            }

            foreach ($gistInfo in $gistsWithMovePaths) {
                $gistDestPath = Join-Path $PWD $gistInfo.DestinationPath

                foreach ($movePath in $gistInfo.Config.movePaths) {
                    $fromPattern = $movePath.from
                    $toPath = $movePath.to -replace '\$\{repoRoot\}', $repoRoot

                    # Find the base directory from the pattern (part before wildcards)
                    $fromBase = ($fromPattern -split '[*?]')[0].TrimEnd('/', '\')
                    $fromBasePath = Join-Path $gistDestPath $fromBase

                    if (-not (Test-Path $fromBasePath)) {
                        Write-Verbose "Source path not found: $fromBasePath"
                        continue
                    }

                    # Get all files recursively from the base path
                    # Note: PowerShell doesn't support ** glob, so we use -Recurse instead
                    $filesToMove = Get-ChildItem -Path $fromBasePath -File -Recurse -ErrorAction SilentlyContinue

                    foreach ($file in $filesToMove) {
                        # Calculate relative path from the 'from' base to preserve structure
                        $relativePath = $file.FullName.Substring($fromBasePath.Length).TrimStart('\', '/')
                        $destFile = Join-Path $toPath $relativePath

                        # Ensure destination directory exists
                        $destDir = Split-Path -Parent $destFile
                        if (-not (Test-Path $destDir)) {
                            New-Item -ItemType Directory -Path $destDir -Force | Out-Null
                        }

                        # Move file (overwrites if exists)
                        Move-Item -Path $file.FullName -Destination $destFile -Force
                        Write-Verbose "Moved: $($file.FullName) -> $destFile"
                    }

                    # Clean up empty directories in source
                    if (Test-Path $fromBasePath) {
                        Get-ChildItem -Path $fromBasePath -Directory -Recurse |
                            Sort-Object { $_.FullName.Length } -Descending |
                            Where-Object { @(Get-ChildItem $_.FullName -Force).Count -eq 0 } |
                            Remove-Item -Force

                        # Remove base dir if empty
                        if (@(Get-ChildItem $fromBasePath -Force).Count -eq 0) {
                            Remove-Item $fromBasePath -Force
                        }
                    }
                }

                Write-Information "📁 Moved files for '$($gistInfo.Config.name)'"
            }
        }

        if ($VerbosePreference -eq 'SilentlyContinue') {
            Remove-Item $outputPath
            Remove-Item (Join-Path (Split-Path -Parent $outputPath) 'vendir.lock.yml')
        }

        Write-Information "✅ Completed successfully."
    }
}