src/functions/internal-commands.ps1

function Get-ConfigMapInternalCommands {
    param(
        [ValidateSet('build')]
        [string]$Language = 'build'
    )

    if ($Language -ne 'build') {
        return [ordered]@{}
    }

    return [ordered]@{
        'help' = @{
            description = 'Show qbuild usage and internal commands'
            exec = {
                param($_context)
                Write-InternalHelp -invocation $_context.Invocation
            }
        }
        'list' = @{
            description = 'List available build commands'
            exec = {
                param($_context)

                if (!$_context.UserMap) {
                    Write-Help -invocation $_context.Invocation -mapPath "./.build.map.ps1"
                    return
                }

                Write-MapHelp -map $_context.UserMap -invocation $_context.Invocation
            }
        }
        '!init' = @{
            description = 'Create a build map in the current directory'
            _internal = @{
                completeWhen = 'localMapMissing'
            }
            exec = {
                param($_context)

                if (Test-Path $_context.MapArgument) {
                    throw "map file '$($_context.MapArgument)' already exists"
                }
                Initialize-BuildMap -file $_context.MapArgument
            }
        }
        '!agent.init' = @{
            description = 'Install the qbuild discovery skill'
            exec = {
                param(
                    $_context,
                    [ValidateSet('user', 'project')]
                    [string]$Scope = 'project',
                    [ValidateSet('cursor', 'copilot', 'claude')]
                    [string]$Agent = 'cursor'
                )

                Initialize-QBuildAgent -Scope $Scope -Agent $Agent
            }
        }
        '!settings' = @{
            description = 'Show effective ConfigMap settings'
            exec = {
                param(
                    $_context,
                    [string]$Path
                )

                $remaining = @($_context.RemainingArguments | Where-Object { $_ })
                if ($Path -and $remaining.Count -gt 0 -and $remaining[0] -eq $Path) {
                    $remaining = @($remaining | Select-Object -Skip 1)
                }
                if ($remaining.Count -gt 0) {
                    if ($Path -or $remaining.Count -gt 1) {
                        throw "!settings accepts at most one command path."
                    }
                    $Path = $remaining[0]
                }

                if (!$_context.UserMap) {
                    return Get-ConfigMapSettings
                }

                Get-ConfigMapSettingsForPath -Map $_context.UserMap -Path $Path
            }
        }
        '!describe' = @{
            description = 'Describe commands in the build map'
            exec = {
                param(
                    $_context,
                    [string]$Path
                )

                $remaining = @($_context.RemainingArguments | Where-Object { $_ })
                if ($Path -and $remaining.Count -gt 0 -and $remaining[0] -eq $Path) {
                    $remaining = @($remaining | Select-Object -Skip 1)
                }
                if ($remaining.Count -gt 0) {
                    if ($Path -or $remaining.Count -gt 1) {
                        throw "!describe accepts at most one command path."
                    }
                    $Path = $remaining[0]
                }

                if (!$_context.UserMap) {
                    throw "No build map file found. Run 'qbuild !init' to create one, or provide -map."
                }

                if ($Path) {
                    return Get-ConfigMapCommandCatalog -Map $_context.UserMap -Path $Path -Language build
                }
                Get-ConfigMapCommandCatalog -Map $_context.UserMap -Language build
            }
        }
        '!cache' = @{
            description = 'Inspect or manage the discovery cache'
            exec = {
                param(
                    $_context,
                    [ValidateSet('status', 'clear', 'rebuild')]
                    [string]$Action = 'status'
                )

                $cacheAction = $Action
                $remaining = @($_context.RemainingArguments | Where-Object { $_ })
                if ($cacheAction -and $remaining.Count -gt 0 -and $remaining[0] -eq $cacheAction) {
                    $remaining = @($remaining | Select-Object -Skip 1)
                }
                if ($remaining.Count -gt 0) {
                    if ($PSBoundParameters.ContainsKey('Action') -or $remaining.Count -gt 1) {
                        throw "!cache accepts at most one action."
                    }
                    $cacheAction = $remaining[0]
                }
                if ($cacheAction -notin @('status', 'clear', 'rebuild')) {
                    throw "!cache accepts status, clear, or rebuild."
                }

                if (!$_context.UserMap) {
                    throw "No build map file found. Run 'qbuild !init' to create one, or provide -map."
                }

                Invoke-QBuildCacheCommand -Map $_context.UserMap -Language build -Action $cacheAction
            }
        }
    }
}

function Merge-ConfigMapCommands {
    param(
        [AllowNull()]
        [System.Collections.IDictionary]$Map,
        [ValidateSet('build')]
        [string]$Language = 'build'
    )

    $result = [ordered]@{}
    $commands = [ordered]@{}
    $internalCommands = Get-ConfigMapInternalCommands -Language $Language

    if ($Map) {
        foreach ($metadataKey in @('_baseDir', '_sourceFile', '_settings', '_dependsOn')) {
            if ($Map.Contains($metadataKey)) {
                $result[$metadataKey] = $Map[$metadataKey]
            }
        }
    }

    $userCommands = if ($Map -and $Map.Contains('list')) { $Map['list'] } else { $Map }

    # Precedence policy: the last write wins. Reverse these two loops to let user commands override internals.
    if ($userCommands -is [System.Collections.IDictionary]) {
        foreach ($item in $userCommands.GetEnumerator()) {
            if ($item.Key -notin @('_baseDir', '_sourceFile', '_settings', '_dependsOn')) {
                $commands[$item.Key] = $item.Value
            }
        }
    }
    foreach ($item in $internalCommands.GetEnumerator()) {
        $commands[$item.Key] = $item.Value
    }

    $result['list'] = $commands
    return $result
}

function Test-ConfigMapCommandCompletion {
    param(
        [System.Collections.IDictionary]$Entry,
        [bool]$LocalMapExists
    )

    if (!$Entry -or !$Entry._internal -or !$Entry._internal.completeWhen) {
        return $true
    }

    switch ($Entry._internal.completeWhen) {
        'localMapMissing' { return !$LocalMapExists }
        default { return $true }
    }
}

function Test-ConfigMapInternalCommand {
    param(
        [string]$Name,
        [ValidateSet('build')]
        [string]$Language = 'build'
    )

    if (!$Name) {
        return $false
    }

    return (Get-ConfigMapInternalCommands -Language $Language).Contains($Name)
}