functions/_Get-GistMapData.ps1

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

<#
.SYNOPSIS
    Internal helper function to read and parse the gist-map configuration.

.DESCRIPTION
    This private function handles reading and parsing the gist-map configuration.
    It supports a remote-first loading strategy with local fallback:
    1. HTTP fetch via Invoke-RestMethod (fast, works for public repos)
    2. Git fetch via vendir (handles private repos with SSH/credential helpers)
    3. Local file fallback from the module's gist-map.yml

    Results are cached for 30 seconds to improve tab-completion performance.

.PARAMETER ScriptRoot
    The script root directory to use for locating the local gist-map.yml file.
    Used as a fallback when remote fetching fails.

.PARAMETER GistMapUrl
    The URL to fetch the gist-map.yml file from via HTTP. When provided, the function
    attempts an HTTP fetch before falling back to other methods.

.PARAMETER GistMapGitSource
    A hashtable containing 'url', 'ref', and 'path' keys for fetching the gist-map
    via vendir git sync. Used as a fallback when HTTP fetch fails.

.PARAMETER NoCache
    When specified, bypasses the cache and forces a fresh read.

.OUTPUTS
    Returns a hashtable containing the parsed gist-map data, or $null if all
    sources fail.

.EXAMPLE
    _Get-GistMapData -ScriptRoot $PSScriptRoot

.EXAMPLE
    _Get-GistMapData -ScriptRoot $PSScriptRoot -GistMapUrl 'https://raw.githubusercontent.com/endjin/endjin-gists/refs/heads/main/module/gist-map.yml'

.EXAMPLE
    _Get-GistMapData -ScriptRoot $PSScriptRoot -NoCache

.NOTES
    This is a private helper function (prefixed with '_') and is not exported from the module.
    Results are cached for 30 seconds to improve tab-completion performance.
#>

$script:cachedMap = $null
$script:cacheTimestamp = [datetime]::MinValue
$script:cacheTtlSeconds = 30

function _Get-GistMapData {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string] $ScriptRoot,

        [Parameter()]
        [string] $GistMapUrl,

        [Parameter()]
        [hashtable] $GistMapGitSource,

        [Parameter()]
        [switch] $NoCache
    )

    # Return cached data if within TTL
    if (-not $NoCache -and $script:cachedMap -and
        ([datetime]::UtcNow - $script:cacheTimestamp).TotalSeconds -lt $script:cacheTtlSeconds) {
        return $script:cachedMap
    }

    $map = $null

    # 1. Try HTTP fetch
    if ($GistMapUrl) {
        try {
            Write-Verbose "Attempting HTTP fetch of gist-map from: $GistMapUrl"
            $yamlContent = Invoke-RestMethod -Uri $GistMapUrl -TimeoutSec 5
            $map = $yamlContent | ConvertFrom-Yaml
            Write-Verbose "Successfully loaded gist-map via HTTP"
        }
        catch {
            Write-Verbose "HTTP fetch failed: $_"
        }
    }

    # 2. Try vendir git fetch
    if (-not $map -and $GistMapGitSource) {
        try {
            Write-Verbose "Attempting vendir git fetch of gist-map"
            $yamlContent = _Get-RemoteGistMap -GitSource $GistMapGitSource
            if ($yamlContent) {
                $map = $yamlContent | ConvertFrom-Yaml
                Write-Verbose "Successfully loaded gist-map via vendir"
            }
        }
        catch {
            Write-Verbose "Vendir git fetch failed: $_"
        }
    }

    # 3. Fallback to local file
    if (-not $map -and $ScriptRoot) {
        $mapPath = Join-Path $ScriptRoot 'gist-map.yml'
        if (Test-Path $mapPath) {
            Write-Verbose "Loading gist-map from local file: $mapPath"
            $map = Get-Content -Path $mapPath -Raw | ConvertFrom-Yaml
            Write-Verbose "Successfully loaded gist-map from local file"
        }
    }

    if (-not $map) {
        return $null
    }

    # Validate schema: every gist entry must have required fields
    $requiredFields = @('name', 'source', 'ref', 'includePaths')
    foreach ($groupName in $map.Keys) {
        foreach ($gist in $map[$groupName]) {
            foreach ($field in $requiredFields) {
                if (-not $gist.ContainsKey($field)) {
                    throw "Gist '$($gist.name)' in group '$groupName' is missing required field: $field"
                }
            }
        }
    }

    $script:cachedMap = $map
    $script:cacheTimestamp = [datetime]::UtcNow
    return $map
}