src/functions/map-entries.ps1

function New-BuildAllEntry {
    return [ordered]@{ __buildAll = $true }
}

function Test-BuildAllEntry {
    param($Entry)

    return $Entry -is [System.Collections.IDictionary] -and $Entry.__buildAll
}

function Test-IsInvokableBuildEntry {
    param(
        $Entry,
        [ValidateSet('build', 'conf')]$Language
    )

    $reservedKeys = (Get-MapLanguage $Language).reservedKeys

    if (Test-BuildAllEntry $Entry) { return $false }
    if ($Entry -is [scriptblock]) { return $true }
    if ($Entry -is [System.Collections.IDictionary]) {
        if (Test-IsParentEntry $Entry -reservedKeys $reservedKeys) {
            return $false
        }

        return $Entry.exec -is [scriptblock]
    }

    return $false
}

function Get-BuildAllChildren {
    param(
        [System.Collections.IDictionary]$ParentEntry,
        [ValidateSet('build', 'conf')]$Language,
        [string]$ParentKey = '',
        [string]$Separator = '.'
    )

    $reservedKeys = (Get-MapLanguage $Language).reservedKeys
    $result = [ordered]@{}

    foreach ($kvp in $ParentEntry.GetEnumerator()) {
        if ($kvp.Key -in $reservedKeys -or $kvp.Key -eq 'all') {
            continue
        }

        if (!(Test-IsInvokableBuildEntry $kvp.Value -Language $Language)) {
            continue
        }

        $childKey = if ($ParentKey) { "$ParentKey$Separator$($kvp.Key)" } else { $kvp.Key }
        $result[$childKey] = $kvp.Value
    }

    return $result
}

function Get-MapEntry(
    [ValidateScript({
            $_ -is [System.Collections.IDictionary] -or $_ -is [array]
        })]
    $map,
    $key,
    $separator = ".",
    $language = $null
) {
    return (Get-MapEntries $map $key -separator $separator -language $language).Value
}

function Get-MapEntries(
    [ValidateScript({
            $_ -is [System.Collections.IDictionary] -or $_ -is [array]
        })]
    $map,
    $keys,
    [switch][bool]$flatten = $false,
    [switch][bool]$leafsOnly = $false,
    $separator = ".",
    $language = $null
) {
    $results = @()

    $completions = Get-CompletionList $map -flatten:$flatten -leafsOnly:$leafsOnly -separator:$separator -language $language

    foreach ($key in @($keys)) {
        $found = @($completions.GetEnumerator() | Where-Object { $_.Key -eq $key })
        if ($found.Count -eq 0) { continue }

        $target = $found[0]
        if ((Test-BuildAllEntry $target.Value) -and $language -eq 'build') {
            $parentKey = if ($key -match '^(.*)\.all$') { $Matches[1] } else { '' }
            $parentEntry = if ($parentKey) {
                (Get-MapEntries $map $parentKey -separator $separator -language $language).Value
            }
            else {
                $map
            }

            $children = Get-BuildAllChildren $parentEntry -Language $language -ParentKey $parentKey -Separator $separator
            foreach ($child in $children.GetEnumerator()) {
                $results += [ordered]@{ Key = $child.Key; Value = $child.Value }
            }
            continue
        }

        $results += $target
    }

    if (!$results) {
        $completions = Get-CompletionList $map -flatten:$flatten -leafsOnly:$leafsOnly -separator:$separator -language $language
        Write-Verbose "entry '$keys' not found in ($($completions.Keys))"
    }

    return $results
}

# TODO: key should be a hidden property of $entry
function Get-EntryHasExec {
    param($Entry)

    if ($Entry -isnot [System.Collections.IDictionary]) { return $false }
    $exec = $Entry.exec
    if ($null -eq $exec) { return $false }
    if ($exec -is [array]) { return $exec.Count -gt 0 }
    return $true
}

function Get-EntryCommand(
    [ValidateScript({
            $_ -is [System.Collections.IDictionary] -or $_ -is [array] -or $_ -is [scriptblock]
        })]
    [Parameter(Mandatory = $true)]
    $entry,
    [Parameter(Mandatory = $true)]
    $commandKey
) {
    if (!$entry) { throw "entry is NULL" }
    if ($entry -is [scriptblock]) { return $entry }

    if ($entry -is [System.Collections.IDictionary] -or $entry -is [System.Collections.Hashtable]) {
        if (!$entry.$commandKey) {
            throw "Command '$commandKey' not found"
            return $null
        }
        return $entry.$commandKey
    }

    throw "Entry of type $($entry.GetType().Name) is not supported"
    return $null
}

function Test-IsParentEntry {
    <#
    .SYNOPSIS
        Determines if an entry is a parent container (has nested commands) or a leaf (executable command)
    .PARAMETER Entry
        The map entry to test
    .PARAMETER ReservedKeys
        Array of reserved keys that should be skipped during processing
    .OUTPUTS
        [bool] $true if the entry is a parent container
    #>

    param(
        $Entry,
        $ReservedKeys = @("options", "exec", "list")
    )

    # If entry is not a hashtable, it's a leaf (scriptblock or other)
    if ($Entry -isnot [System.Collections.IDictionary]) {
        return $false
    }

    # Check for explicit list key (traditional nested structure)
    if ($Entry.list) {
        return $true
    }

    # Check if entry contains nested commands (hashtables or scriptblocks)
    foreach ($subKvp in $Entry.GetEnumerator()) {
        if ($subKvp.Key -in $reservedKeys) {
            continue
        }
        if ($subKvp.Value -is [System.Collections.IDictionary] -or $subKvp.Value -is [scriptblock]) {
            return $true
        }
    }

    return $false
}