src/handlers/qconf/entry-types.ps1

#requires -version 7.0

function Get-ConfigEntryTemplate {
    param(
        $Entry
    )

    if ($Entry -isnot [System.Collections.IDictionary]) { return $null }
    if (-not $Entry.Contains('_template')) { return $null }

    $template = $Entry._template
    if ($template -is [System.Collections.IDictionary]) {
        return $template
    }

    if ($template -is [string]) {
        # Short form: "appsettings:{filepath}" → @{ type = 'appconfig'; file = ... }
        if ($template -match '^(?i)appsettings:(.+)$') {
            $file = $Matches[1].Trim()
            if ([string]::IsNullOrWhiteSpace($file)) {
                throw "Config entry template 'appsettings:' requires a file path."
            }
            return @{
                type = 'appconfig'
                file = $file
            }
        }

        throw "Unknown config entry template '$template'."
    }

    return $null
}

function Resolve-ConfigEntryFile {
    param(
        $Entry,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$Template,
        [Parameter(Mandatory = $true)]
        [string]$TypeName
    )

    $file = $Template.file
    if ($file -isnot [string] -or [string]::IsNullOrWhiteSpace($file)) {
        throw "Config entry type '$TypeName' requires a file path."
    }

    if ([System.IO.Path]::IsPathRooted($file)) {
        return $file
    }

    $baseDir = if ($Entry._baseDir) { [string]$Entry._baseDir } else { (Get-Location).Path }
    return [System.IO.Path]::GetFullPath((Join-Path $baseDir $file))
}

function Set-AppConfigOption {
    param(
        [Parameter(Mandatory = $true)]
        [string]$File,
        $Values
    )

    if ($Values -isnot [System.Collections.IDictionary]) {
        throw "appconfig option value must be a hashtable of setting keys."
    }

    $directory = Split-Path -Parent $File
    foreach ($key in @($Values.Keys)) {
        if ("$key".StartsWith('__')) { continue }

        $settingValue = resolve-value $Values[$key] -dir $directory
        set-appsettings -file $File -path $key -value $settingValue
    }
}

function New-BoundTypeCommand {
    param(
        [Parameter(Mandatory = $true)]
        [string]$File,
        [Parameter(Mandatory = $true)]
        [string]$Body
    )

    # Unbound scriptblocks run in the caller's module scope, so they can see
    # the private type helpers. GetNewClosure would hide those functions.
    $literal = $File.Replace("'", "''")
    [scriptblock]::Create(($Body -replace '\{file\}', $literal))
}

function Add-AppConfigCommands {
    param(
        [Parameter(Mandatory = $true)]
        $Entry,
        [Parameter(Mandatory = $true)]
        [System.Collections.IDictionary]$Template,
        [switch]$NeedsGet,
        [switch]$NeedsSet
    )

    $settingsFile = Resolve-ConfigEntryFile -Entry $Entry -Template $Template -TypeName 'appconfig'

    if ($NeedsGet) {
        $Entry.get = New-BoundTypeCommand -File $settingsFile -Body @'
param($ctx, $options)
Find-ActiveOption -file '{file}' -options $options
'@

    }

    if ($NeedsSet) {
        $Entry.set = New-BoundTypeCommand -File $settingsFile -Body @'
param($ctx, $key, $value)
Set-AppConfigOption -File '{file}' -Values $value
'@

    }
}

function Add-ConfigEntryTypeCommands {
    <#
    .SYNOPSIS
        Fill in get and set from Entry._template when the map did not supply them.
 
    .DESCRIPTION
        Accepts `_template` as a hashtable (`type`/`file`) or short string
        (`appsettings:{filepath}`). Leaves an author-supplied get or set in place.
    #>

    param(
        $Entry
    )

    if ($Entry -isnot [System.Collections.IDictionary]) { return }

    $template = Get-ConfigEntryTemplate -Entry $Entry
    if ($null -eq $template -or -not $template.Contains('type') -or -not $template.type) { return }

    $needsGet = $Entry.get -isnot [scriptblock]
    $needsSet = $Entry.set -isnot [scriptblock]
    if (-not $needsGet -and -not $needsSet) { return }

    $typeName = [string]$template.type
    switch ($typeName) {
        'appconfig' {
            Add-AppConfigCommands -Entry $Entry -Template $template -NeedsGet:$needsGet -NeedsSet:$needsSet
        }
        default {
            throw "Unknown config entry type '$typeName'."
        }
    }
}