Private/SwaggerCodeText.ps1

# Helpers for New-FunctionsFromSwagger that turn swagger text into safe PowerShell code text (quoting, comments,
# names, attributes, sample values and verbs). They have no state.

# Quotes text as a PowerShell single-quoted string literal (single quotes are doubled), so text from the
# swagger file is always data in generated code
function ConvertTo-QuotedLiteral {
    param([AllowNull()][AllowEmptyString()][string] $Text)
    if ($null -eq $Text) { $Text = '' }
    # EscapeSingleQuotedStringContent also doubles the curly quotes that PowerShell accepts as single quotes
    return "'" + [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent($Text) + "'"
}


# Makes text safe for comment-based help and line comments: one line, and no sequence that ends or starts a
# block comment
function ConvertTo-SafeCommentText {
    param([AllowNull()][AllowEmptyString()][string] $Text)
    if ([string]::IsNullOrEmpty($Text)) { return $Text }
    return (($Text -replace '[\r\n]+', ' ') -replace '#>', '# >' -replace '<#', '< #')
}


# Replaces every {{NAME}} placeholder in one pass (unknown placeholders are left as they are), so placeholder-like
# text inside values is never expanded
function Expand-CodeTemplate {
    param(
        [Parameter(Mandatory = $true)][string] $Template,
        [Parameter(Mandatory = $true)][hashtable] $Values
    )
    $evaluator = {
        param($placeholder)
        $name = $placeholder.Groups[1].Value
        if ($Values.ContainsKey($name)) { return [string]$Values[$name] }
        return $placeholder.Value
    }
    return [regex]::Replace($Template, '\{\{([A-Z_]+)\}\}', [System.Text.RegularExpressions.MatchEvaluator]$evaluator)
}


# First 8 hex characters of the SHA1 of the text (used to make colliding function names unique)
function Get-ShortHash {
    param([Parameter(Mandatory = $true)][string] $Text)
    $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
    $sha = [System.Security.Cryptography.SHA1]::Create()
    try {
        $hash = $sha.ComputeHash($bytes)
    }
    finally {
        $sha.Dispose()
    }
    $hex = ([System.BitConverter]::ToString($hash)).Replace('-', '').ToLowerInvariant()
    return $hex.Substring(0, 8)
}


function ConvertTo-PowerShellLiteral {
    param([object]$Value)

    if ($null -eq $Value) { return '$null' }
    if ($Value -is [bool]) {
        if ($Value) { return '$true' } else { return '$false' }
    }
    if ($Value -is [string]) {
        return ConvertTo-QuotedLiteral $Value
    }
    if ($Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
        return $Value.ToString([System.Globalization.CultureInfo]::InvariantCulture)
    }
    if ($Value -is [datetime]) {
        return "(Get-Date '" + $Value.ToString('o') + "')"
    }
    if ($Value -is [array]) {
        $items = @()
        foreach ($item in $Value) { $items += ConvertTo-PowerShellLiteral $item }
        return '@(' + ($items -join ', ') + ')'
    }
    # Objects become ConvertFrom-Json of a quoted JSON string, so they are always data
    try { return '(' + (ConvertTo-QuotedLiteral ($Value | ConvertTo-Json -Compress -Depth 20)) + ' | ConvertFrom-Json)' } catch { return ConvertTo-QuotedLiteral ([string]$Value) }
}


function Get-ValidateSetAttributeText {
    param(
        [object[]]$Values,
        [string]$PSType
    )

    if (-not $Values -or $Values.Count -eq 0) { return $null }
    $escaped = @()
    foreach ($val in $Values) {
        $escaped += ConvertTo-QuotedLiteral ([string]$val)
    }
    $ignoreSegment = if ($PSType -like '`[string*') { ', IgnoreCase=$true' } else { '' }
    return ' [ValidateSet(' + ($escaped -join ', ') + $ignoreSegment + ')]'
}


function Get-ValidateScriptAttributeText {
    param(
        [string]$Format,
        [string]$PSType
    )

    if (-not $Format) { return $null }
    if ($PSType -ne '[string]') { return $null }

    $formatLower = $Format.ToLowerInvariant()
    switch ($formatLower) {
        'uuid' { $formatLower = 'guid' }
    }

    switch ($formatLower) {
        'guid' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' $parsed = [guid]::Empty',
                ' if ([guid]::TryParse($value, [ref]$parsed)) { return $true }',
                ' throw "Invalid guid format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        'date-time' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' $parsed = [datetime]::MinValue',
                ' if ([datetime]::TryParse($value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::RoundtripKind, [ref]$parsed)) { return $true }',
                ' throw "Invalid date-time format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        'date' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' $parsed = [datetime]::MinValue',
                ' if ([datetime]::TryParse($value, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::AssumeUniversal, [ref]$parsed)) { return $true }',
                ' throw "Invalid date format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        'email' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' if ($value -match ''^([^\s@]+)@([^\s@]+)\.([^\s@]+)$'') { return $true }',
                ' throw "Invalid email format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        'uri' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' if ([uri]::IsWellFormedUriString($value, [System.UriKind]::Absolute)) { return $true }',
                ' throw "Invalid uri format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        'hostname' {
            $lines = @(
                ' [ValidateScript({',
                ' param($value)',
                ' if ($null -eq $value) { return $true }',
                ' if ($value -match ''^(?=.{1,253}$)(?!-)([A-Za-z0-9-]{0,62}[A-Za-z0-9]\.)+[A-Za-z]{2,63}$'') { return $true }',
                ' throw "Invalid hostname format: $value"',
                ' })]'
            )
            return ($lines -join "`n")
        }
        default { return $null }
    }
}


function Test-IsGuidString {
    param([object]$Value)

    if ($null -eq $Value) { return $false }
    if ($Value -isnot [string]) { return $false }
    $parsed = [guid]::Empty
    return [guid]::TryParse($Value, [ref]$parsed)
}


function Test-UseDefaultLiteral {
    param(
        [string]$PSType,
        [string]$DefaultLiteral
    )

    if ([string]::IsNullOrEmpty($DefaultLiteral)) { return $false }
    if ($DefaultLiteral -eq "''") { return $false }
    if ($DefaultLiteral -eq '$null') { return $false }

    if ($PSType -match '\[\]') {
        if ($DefaultLiteral -match '^@\(') { return $true } else { return $false }
    }

    if ($PSType -eq '[hashtable]') {
        if ($DefaultLiteral -match '^@\{' -or $DefaultLiteral -match '^\{') { return $true } else { return $false }
    }

    if ($PSType -eq '[hashtable[]]' -or $PSType -eq '[object[]]') {
        if ($DefaultLiteral -match '^@\(') { return $true } else { return $false }
    }

    return $true
}


function Format-HelpTextSegment {
    param([string]$Text)

    if ([string]::IsNullOrWhiteSpace($Text)) { return $Text }

    $normalized = ($Text -replace '\s+', ' ').Trim()
    if (-not $normalized) { return $normalized }

    $normalized = $normalized.Trim('"')
    $normalized = $normalized.Trim("'")
    $normalized = $normalized.Trim()

    while ($normalized.EndsWith(',')) {
        $normalized = $normalized.TrimEnd(',').Trim()
    }
    while ($normalized.EndsWith(';')) {
        $normalized = $normalized.TrimEnd(';').Trim()
    }

    return $normalized
}


function Get-DescriptionHint {
    param([string]$DescriptionText)

    $hints = @{}
    if ([string]::IsNullOrWhiteSpace($DescriptionText)) { return $hints }

    $normalized = $DescriptionText -replace "`r", ''
    $regex = [regex]"(?im)[`"'']?(?<name>[A-Za-z0-9_]+)[`"'']?\s*[:\-\u2013]\s*(?<value>[^`r`n]+)"
    foreach ($match in $regex.Matches($normalized)) {
        $name = $match.Groups['name'].Value
        $value = $match.Groups['value'].Value.Trim()
        if (-not $name) { continue }
        $key = $name.ToLowerInvariant()
        if (-not $hints.ContainsKey($key)) {
            $normalizedValue = Format-HelpTextSegment $value
            if (-not [string]::IsNullOrWhiteSpace($normalizedValue)) { $hints[$key] = $normalizedValue }
        }
    }

    return $hints
}


function ConvertTo-SafeNameComponent {
    param([string]$Name)

    $sanitized = $Name -replace '[^0-9A-Za-z]', '_'
    if ([string]::IsNullOrEmpty($sanitized)) { $sanitized = 'Property' }
    if ($sanitized.Length -gt 1) {
        $sanitized = $sanitized.Substring(0, 1).ToUpper() + $sanitized.Substring(1)
    }
    else {
        $sanitized = $sanitized.ToUpper()
    }
    return ($sanitized -replace '_+', '_')
}


function Get-UniqueBodyParameterName {
    param(
        [string[]]$Segments,
        [hashtable]$UsedNames
    )

    $components = @()
    foreach ($segment in $Segments) {
        $components += (ConvertTo-SafeNameComponent -Name $segment)
    }
    if ($components.Count -eq 0) { $components = @('Property') }
    $baseName = ($components -join '')
    if ([string]::IsNullOrEmpty($baseName)) { $baseName = 'Property' }

    $candidate = $baseName
    $suffix = 1
    while ($UsedNames.ContainsKey($candidate.ToLowerInvariant())) {
        $candidate = "${baseName}${suffix}"
        $suffix++
    }
    $UsedNames[$candidate.ToLowerInvariant()] = $true
    return $candidate
}


function Get-SanitizedIdentifier {
    param(
        [string]$Name,
        [string]$Fallback = 'Value'
    )

    $candidate = $Name
    if ([string]::IsNullOrWhiteSpace($candidate)) { $candidate = $Fallback }

    $segments = @($candidate -split '[^0-9A-Za-z]+') | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
    if ($segments.Count -eq 0) { $segments = @($Fallback) }

    $parts = @()
    foreach ($segment in $segments) {
        $parts += (ConvertTo-SafeNameComponent -Name $segment)
    }
    if ($parts.Count -eq 0) { $parts = @($Fallback) }

    $identifier = ($parts -join '')
    if ([string]::IsNullOrWhiteSpace($identifier)) { $identifier = $Fallback }
    if ($identifier[0] -match '\d') { $identifier = '_' + $identifier }
    return $identifier
}


function Get-UniqueIdentifier {
    param(
        [string]$BaseName,
        [hashtable]$UsedNames
    )

    $normalized = if ([string]::IsNullOrWhiteSpace($BaseName)) { 'Value' } else { $BaseName }
    $candidate = $normalized
    $suffix = 1
    while ($UsedNames.ContainsKey($candidate.ToLowerInvariant())) {
        $candidate = "${normalized}${suffix}"
        $suffix++
    }
    $UsedNames[$candidate.ToLowerInvariant()] = $true
    return $candidate
}


function Get-HintForPath {
    param(
        [hashtable]$DescriptionHints,
        [string[]]$PathSegments,
        [string]$ParameterName
    )

    if (-not $DescriptionHints) { return $null }
    $candidates = @()
    if ($PathSegments.Count -gt 0) {
        $candidates += ($PathSegments -join '.')
        $candidates += $PathSegments[-1]
    }
    foreach ($segment in $PathSegments) { $candidates += $segment }
    if ($ParameterName) { $candidates += $ParameterName }

    foreach ($candidate in $candidates) {
        if ([string]::IsNullOrWhiteSpace($candidate)) { continue }
        $key = $candidate.ToLowerInvariant()
        if ($DescriptionHints.ContainsKey($key)) { return $DescriptionHints[$key] }
    }

    return $null
}


function Add-ParameterDescriptor {
    param(
        [hashtable]$DescriptorMap,
        [System.Collections.Generic.List[object]]$DescriptorList,
        [pscustomobject]$Descriptor,
        [string]$ParameterSetName
    )

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

    if (-not ($Descriptor.PSObject.Properties.Name -contains 'ParameterSets')) {
        $Descriptor | Add-Member -NotePropertyName ParameterSets -NotePropertyValue (New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)) -Force
    }

    if ($ParameterSetName) { $null = $Descriptor.ParameterSets.Add($ParameterSetName) }

    $key = $Descriptor.Name.ToLowerInvariant()
    if ($DescriptorMap.ContainsKey($key)) {
        $existing = $DescriptorMap[$key]
        foreach ($set in $Descriptor.ParameterSets) { $null = $existing.ParameterSets.Add($set) }
        if ($Descriptor.Required -and -not $existing.Required) { $existing.Required = $true }
        if (-not $existing.Description -and $Descriptor.Description) { $existing.Description = $Descriptor.Description }
        if (-not $existing.HelpSummary -and $Descriptor.HelpSummary) { $existing.HelpSummary = $Descriptor.HelpSummary }
        return [pscustomobject]@{ Descriptor = $existing; IsNew = $false }
    }

    [void]$DescriptorList.Add($Descriptor)
    $DescriptorMap[$key] = $Descriptor
    return [pscustomobject]@{ Descriptor = $Descriptor; IsNew = $true }
}


function Get-BodyAssignmentLine {
    param([pscustomobject]$Definition, [string]$OperationKey)

    $pathSegments = $Definition.PathSegments
    if (-not $pathSegments -or $pathSegments.Count -eq 0) { return @() }

    $lines = [System.Collections.Generic.List[string]]::new()
    $parentExpr = '$bodyPayload'
    for ($i = 0; $i -lt $pathSegments.Count - 1; $i++) {
        $segment = $pathSegments[$i]
        $segmentLiteral = ConvertTo-QuotedLiteral $segment
        $lines.Add([string]::Format(' if (-not {0}.ContainsKey({1}) -or $null -eq {0}[{1}]) {{ {0}[{1}] = @{{}} }}', $parentExpr, $segmentLiteral))
        $parentExpr = "$parentExpr[$segmentLiteral]"
    }

    $lastSegment = $pathSegments[-1]
    $targetExpr = "$parentExpr[$(ConvertTo-QuotedLiteral $lastSegment)]"
    $paramName = $Definition.Name
    $hasChildProps = [bool]$Definition.HasChildProperties

    $needsConversion = $false
    if ($OperationKey -and $Definition.Type -and ($Definition.Type -match 'int|long|Int32|Int64|integer')) { $needsConversion = $true }

    if ($Definition.Required) {
        if ($needsConversion) {
            # Use $PSBoundParameters[...] to safely reference parameter values (avoid evaluating $() during generation)
            $lines.Add((' {0} = Convert-SwaggerParamValue -OperationKey ''{1}'' -ParamName ''{2}'' -Value $PSBoundParameters[''{3}''] -TargetType ''int''' -f $targetExpr, $OperationKey, $paramName, $paramName))
        }
        else {
            $lines.Add((' {0} = $PSBoundParameters[''{1}'']' -f $targetExpr, $paramName))
        }
    }
    elseif ($Definition.HasEffectiveDefault) {
        # If the parameter has an effective default from the swagger metadata, prefer the caller's provided
        # value when present; otherwise emit the swagger default into the request body so the server sees it.
        $defaultLiteral = if ($null -ne $Definition.DefaultLiteral) { $Definition.DefaultLiteral } else { $null }
        if ($needsConversion) {
            if ($defaultLiteral) {
                $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'') -and ($PSBoundParameters[''{0}''] -ne $null)) {{ {1} = Convert-SwaggerParamValue -OperationKey ''{2}'' -ParamName ''{3}'' -Value $PSBoundParameters[''{0}''] -TargetType ''int'' }} else {{ {1} = {4} }}' -f $paramName, $targetExpr, $OperationKey, $paramName, $defaultLiteral))
            }
            else {
                $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'') -and ($PSBoundParameters[''{0}''] -ne $null)) {{ {1} = Convert-SwaggerParamValue -OperationKey ''{2}'' -ParamName ''{3}'' -Value $PSBoundParameters[''{0}''] -TargetType ''int'' }}' -f $paramName, $targetExpr, $OperationKey, $paramName))
            }
        }
        else {
            if ($defaultLiteral) {
                $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'') -and ($PSBoundParameters[''{0}''] -ne $null)) {{ {1} = $PSBoundParameters[''{0}''] }} else {{ {1} = {2} }}' -f $paramName, $targetExpr, $defaultLiteral))
            }
            else {
                $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'') -and ($PSBoundParameters[''{0}''] -ne $null)) {{ {1} = $PSBoundParameters[''{0}''] }}' -f $paramName, $targetExpr))
            }
        }
    }
    elseif ($Definition.AllowsNull -and -not $hasChildProps) {
        # Option B enhancement: For optional/nullable properties, OMIT the key entirely unless caller supplied a value.
        # This avoids emitting explicit nulls that can conflict with conversion schemas when swagger marks default:null but lacks nullable:true.
        # Previous behavior set else { prop = $null }; we now drop that branch.
        if ($needsConversion) {
            $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'')) {{ {1} = Convert-SwaggerParamValue -OperationKey ''{2}'' -ParamName ''{3}'' -Value $PSBoundParameters[''{0}''] -TargetType ''int'' }}' -f $paramName, $targetExpr, $OperationKey, $paramName))
        }
        else {
            $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'')) {{ {1} = $PSBoundParameters[''{0}''] }}' -f $paramName, $targetExpr))
        }
    }
    else {
        if ($needsConversion) {
            $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'')) {{ {1} = Convert-SwaggerParamValue -OperationKey ''{2}'' -ParamName ''{3}'' -Value $PSBoundParameters[''{0}''] -TargetType ''int'' }}' -f $paramName, $targetExpr, $OperationKey, $paramName))
        }
        else {
            $lines.Add((' if ($PSBoundParameters.ContainsKey(''{0}'')) {{ {1} = $PSBoundParameters[''{0}''] }}' -f $paramName, $targetExpr))
        }
    }

    return @($lines)
}


# Picks a PowerShell verb from the HTTP method and the path, summary and operationId
function Select-OperationVerb([string]$method, [string]$path, [string]$summary, [string]$operationId) {
    if (-not $method) { return 'Invoke' }
    $m = $method.ToUpper()

    # Build tokens from path/operationId (for ordering) and a separate token list from the summary (for tiebreaking)
    $rawPath = ($path + ' ' + $(if ($null -ne $operationId) { $operationId } else { '' }))
    $tokens = ($rawPath -split '[^A-Za-z0-9]+' | Where-Object { $_ -ne '' })
    $subtokens = @()
    for ($i = 0; $i -lt $tokens.Count; $i++) {
        $t = $tokens[$i]
        $tokMatches = [regex]::Matches($t, '[A-Z][a-z0-9]*') | ForEach-Object { $_.Value }
        foreach ($s in $tokMatches) { $subtokens += [pscustomobject]@{ Token = $s; Index = $subtokens.Count } }
    }

    $summarySubtokens = @()
    if ($null -ne $summary -and $summary -ne '') {
        $rawSummary = $summary
        $sTokens = ($rawSummary -split '[^A-Za-z0-9]+' | Where-Object { $_ -ne '' })
        foreach ($st in $sTokens) {
            $smatches = [regex]::Matches($st, '[A-Z][a-z0-9]*') | ForEach-Object { $_.Value }
            foreach ($ss in $smatches) { $summarySubtokens += $ss }
        }
    }

    # helper to choose among candidate matches: prefer one that appears earliest in the summary if present,
    # otherwise prefer the earliest token occurrence in the path (lowest Index)
    function Resolve-Match([array]$candidates) {
        if ($candidates.Count -eq 0) { return $null }
        if ($candidates.Count -eq 1) { return $candidates[0].Verb }
        # try to find earliest in summary
        $best = $null
        $bestSummaryPos = [int]::MaxValue
        foreach ($c in $candidates) {
            $tok = $c.Token
            $pos = $summarySubtokens.IndexOf($tok)
            if ($pos -ge 0 -and $pos -lt $bestSummaryPos) { $bestSummaryPos = $pos; $best = $c }
        }
        if ($best) { return $best.Verb }

        # fallback: earliest token index in path
        $candidates = $candidates | Sort-Object -Property Index
        return $candidates[0].Verb
    }

    switch ($m) {
        'GET' {
            # If the summary explicitly uses Get/Read/Retrieve/Fetch, prefer a plain Get verb
            $priorityGetWords = @('Get', 'Retrieve', 'Fetch', 'Read')
            foreach ($w in $priorityGetWords) { if ($summarySubtokens -contains $w) { return 'Get' } }

            # collect search-like matches
            $candidates = @()
            $findList = @('Search', 'Find', 'Matching', 'List', 'Lookup', 'Download', 'GetAll', 'GetBy')
            for ($i = 0; $i -lt $subtokens.Count; $i++) {
                $t = $subtokens[$i].Token
                if ($findList -contains $t) { $candidates += [pscustomobject]@{ Token = $t; Verb = 'Find'; Index = $subtokens[$i].Index } }
            }
            $resolved = Resolve-Match $candidates
            if ($resolved) { return $resolved }
            return 'Get'
        }
        'POST' {
            # Token-ordered heuristics for POST: collect all matches then resolve using summary as tiebreaker
            $mapping = @{
                'Insert' = 'New'; 'Create' = 'New'; 'Add' = 'New'; 'New' = 'New'; 'Upload' = 'New'; 'Register' = 'New'; 'Submit' = 'New'
                'Get' = 'Get'; 'GetBy' = 'Get'; 'GetAll' = 'Get'
                'Update' = 'Update'; 'Set' = 'Update'; 'Change' = 'Update'; 'Patch' = 'Update'; 'Replace' = 'Update'
                'Delete' = 'Remove'; 'Remove' = 'Remove'
                'Approve' = 'Approve'; 'Authorize' = 'Approve'; 'Permit' = 'Approve'
                'Enable' = 'Enable'; 'Disable' = 'Disable'
                'Import' = 'Import'; 'Export' = 'Publish'; 'Download' = 'Publish'
                'Search' = 'Find'; 'ByParameters' = 'Find'
            }

            $candidates = @()
            for ($i = 0; $i -lt $subtokens.Count; $i++) {
                $t = $subtokens[$i].Token
                if ($mapping.ContainsKey($t)) { $candidates += [pscustomobject]@{ Token = $t; Verb = $mapping[$t]; Index = $subtokens[$i].Index } }
            }

            $resolved = Resolve-Match $candidates
            if ($resolved) { return $resolved }
            # default for POST is Invoke (action-oriented)
            return 'Invoke'
        }
        'PUT' { return 'Update' }
        'PATCH' { return 'Update' }
        'DELETE' { return 'Remove' }
        default { return 'Invoke' }
    }
}


function Get-SampleFromHint {
    param(
        [string]$psType,
        [string]$hint,
        [int]$depth = 0
    )

    if ([string]::IsNullOrWhiteSpace($hint)) { return $null }
    if ($depth -gt 3) { return $null }

    if ($psType -match '^\[(?<base>[a-z]+)\[\]\]$') {
        $baseType = "[{0}]" -f $matches['base']
        $elementLiteral = Get-SampleFromHint -psType $baseType -hint $hint -depth ($depth + 1)
        if ($elementLiteral) { return "@($elementLiteral)" }
    }

    $normalizedSegments = ($hint -split '\|') | ForEach-Object { $_.Trim() } | Where-Object { $_ }
    if (-not $normalizedSegments -or $normalizedSegments.Count -eq 0) { $normalizedSegments = @($hint.Trim()) }

    $entries = @()
    foreach ($segment in $normalizedSegments) {
        $label = $segment
        $value = $segment
        if ($segment -match '^(?<label>[^=]+?)\s*=\s*(?<value>.+)$') {
            $label = $matches['label']
            $value = $matches['value']
        }
        $entries += [pscustomobject]@{
            Label = ($label -replace '"|''', '').Trim()
            Value = ($value -replace '"|''', '').Trim()
        }
    }

    if ($psType -eq '[bool]') {
        foreach ($entry in $entries) {
            if ($entry.Label -match '(?i)true' -or $entry.Value -match '(?i)true') { return '$true' }
            if ($entry.Label -match '(?i)false' -or $entry.Value -match '(?i)false') { return '$false' }
        }
        if ($hint -match '(?i)true') { return '$true' }
        if ($hint -match '(?i)false') { return '$false' }
        return $null
    }

    if ($psType -in @('[int]', '[long]', '[double]')) {
        foreach ($entry in $entries) {
            foreach ($candidate in @($entry.Value, $entry.Label)) {
                $trimmed = ($candidate -replace '[^0-9\.-]', '').Trim()
                if (-not $trimmed) { continue }
                if ($psType -eq '[double]') {
                    $doubleVal = 0.0
                    if ([double]::TryParse($trimmed, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref]$doubleVal)) {
                        return ([string]::Format([System.Globalization.CultureInfo]::InvariantCulture, '{0}', $doubleVal))
                    }
                }
                else {
                    $longVal = 0
                    if ([long]::TryParse($trimmed, [ref]$longVal)) {
                        return [string]$longVal
                    }
                }
            }
        }
        return $null
    }

    if ($psType -eq '[string]') {
        foreach ($entry in $entries) {
            if (-not [string]::IsNullOrWhiteSpace($entry.Label)) { return ConvertTo-PowerShellLiteral $entry.Label }
            if (-not [string]::IsNullOrWhiteSpace($entry.Value)) { return ConvertTo-PowerShellLiteral $entry.Value }
        }
        return $null
    }

    return $null
}


# Sample argument for the generated .EXAMPLE
function Get-SampleValue([string]$psType, [object[]]$enumValues = @(), [bool]$hasDefault = $false, [object]$defaultValue = $null, [string]$hint = $null) {
    if ($enumValues -and $enumValues.Count -gt 0) {
        $sampleEnum = ConvertTo-PowerShellLiteral $enumValues[0]
        if ($psType -match '\[\]') { return '@(' + $sampleEnum + ')' }
        return $sampleEnum
    }
    if ($hasDefault) {
        $defaultLiteral = ConvertTo-PowerShellLiteral $defaultValue
        if (Test-UseDefaultLiteral -PSType $psType -DefaultLiteral $defaultLiteral) { return $defaultLiteral }
    }
    $hintLiteral = Get-SampleFromHint -psType $psType -hint $hint
    if ($hintLiteral) { return $hintLiteral }
    switch ($psType) {
        '[string]' { return "'example'" }
        '[int]' { return '123' }
        '[long]' { return '123' }
        '[double]' { return '3.14' }
        '[bool]' { return '$true' }
        '[switch]' { return '$true' }
        '[hashtable]' { return "@{ Key = 'Value' }" }
        '[string[]]' { return "@('example1','example2')" }
        '[int[]]' { return '@(1,2)' }
        '[long[]]' { return '@(1,2)' }
        '[double[]]' { return '@(1.1,2.2)' }
        '[bool[]]' { return '@($true,$false)' }
        '[hashtable[]]' { return "@(@{ Key = 'Value' })" }
        '[object[]]' { return "@('a','b')" }
        default { return "'example'" }
    }
}