src/functions/resolve-map.ps1

# in order to make imports from the map file work globally, we have to call dot-source from top-level scope.
# hence this pattern:
# $map = Resolve-ConfigMap $map | % { if ($_.source -eq "file") { $_.map = . $_.sourceFile | Add-BaseDir -baseDir $_.sourceFile }; $_ } | % { $_.map }
function Resolve-ConfigMap {
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        # we want to allow passing objects or strings as map
        # somehow validateScript is throwing an error when $map is null
        # [ValidateScript({ $null -eq $_ -or $_ -is [string] -or $_ -is [System.Collections.IDictionary] })]
        $map,
        [Parameter(Mandatory = $false)]
        $fallback,
        [switch][bool]$lookUp = $true
    )

    if ($map -is [System.Collections.IDictionary]) {
        return [PSCustomObject]@{
            source     = "object"
            sourceFile = $null
            map        = $map
        }
    }

    $sourceFile = Resolve-ConfigMapFile $map $fallback
    if (!$sourceFile) {
        throw "No map provided and fallback '$fallback' not found"
    }
    return [PSCustomObject]@{
        source     = "file"
        sourceFile = $sourceFile
        map        = $null
    }
}


function Resolve-ConfigMapFile {
    [OutputType([string])]
    param(
        [Parameter(Mandatory = $false)]
        [AllowNull()]
        [string]$mapFile,
        [Parameter(Mandatory = $false)]
        [string]$fallback
    )

    # Set default map file if null
    if (!$map) {
        if (!$fallback) {
            throw "map is null and defaultMapFile is not provided"
            return $null
        }
        $map = $fallback
    }

    # Load map from file if it's a string path
    $fullPath = [System.IO.Path]::IsPathRooted($map) ? $map : (Join-Path $PWD.Path $map)
    $file = Split-Path $fullPath -Leaf
    $dir = Split-Path $fullPath -Parent

    do {
        $fullPath = Join-Path $dir $file
        if (Test-Path $fullPath) {
            return $fullPath
        }
        $dir = Split-Path $dir -Parent
    } while ($lookUp -and $dir)

    throw "map file '$map' not found"
    return $null
}

function Assert-ConfigMap {
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
        $map
    )
    # Validate that we have a loaded map
    if (!$map) {
        throw "failed to load map"
        return $null
    }

    if ($map -isnot [System.Collections.IDictionary]) {
        throw "map is not a dictionary"
    }

    return $map
}

function Add-BaseDir {
    <#
    .SYNOPSIS
        Recursively injects _baseDir property into map entries
    .DESCRIPTION
        Adds _baseDir to dictionary entries (directly).
        Wraps bare scriptblock leaf entries in @{ exec = scriptblock, _baseDir = ... } dictionaries
        so they can carry the _baseDir metadata needed for directory switching.
        Skips reserved keys like exec, set, get, description, etc.
        If baseDir is a file path, automatically extracts the parent directory.
    #>

    param(
        [Parameter(ValueFromPipeline = $true)]
        [System.Collections.IDictionary]$map,
        [string]$baseDir
    )

    if (!$map -or !$baseDir) {
        return $map
    }

    # If baseDir is a file, get its parent directory
    if ((Test-Path $baseDir -PathType Leaf) -or [System.IO.Path]::GetExtension($baseDir)) {
        $baseDir = Split-Path $baseDir -Parent
    }

    $map._baseDir = $baseDir

    $reservedKeys = @("exec", "set", "get", "options", "list", "description", "#include")
    
    foreach ($key in @($map.Keys)) {
        $value = $map[$key]
        
        # Skip reserved keys
        if ($key -in $reservedKeys) {
            continue
        }
        
        # If value is a bare scriptblock (leaf entry), wrap it with _baseDir
        if ($value -is [scriptblock]) {
            $map[$key] = @{
                exec = $value
                _baseDir = $baseDir
            }
            continue
        }
        
        # If value is a dictionary, add _baseDir and recurse
        if ($value -is [System.Collections.IDictionary]) {
            $value._baseDir = $baseDir
            Add-BaseDir $value $baseDir | Out-Null
        }
    }
    
    return $map
}