internal/functions/locale/New-SldgAILocaleData.ps1

function New-SldgAILocaleData {
    <#
    .SYNOPSIS
        Uses AI to generate a complete locale data pack for any culture.
    .DESCRIPTION
        Sends a structured prompt to the configured AI provider requesting
        culturally-appropriate data pools. The AI generates names, addresses,
        phone formats, company data, etc. for the specified culture.
        Results are cached in $script:SldgState.AILocaleCache.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-zA-Z]{2}(-[a-zA-Z]{2,})?$')]
        [string]$Locale,

        [int]$PoolSize = 30,

        [switch]$Force
    )

    # Return cached AI locale if already generated (check TTL)
    if (-not $Force -and $script:SldgState.AILocaleCache.ContainsKey($Locale)) {
        if (-not (Test-SldgCacheExpired -CacheName 'AILocaleCache' -Key $Locale)) {
            Write-PSFMessage -Level Verbose -Message ($script:strings.'Locale.AICacheHit' -f $Locale)
            return $script:SldgState.AILocaleCache[$Locale]
        }
        # Expired — remove and regenerate
        $script:SldgState.AILocaleCache.Remove($Locale)
        $script:SldgState.CacheTimestamps.Remove("AILocaleCache$($script:CacheKeySeparator)$Locale")
    }

    $aiProvider = Get-PSFConfigValue -FullName 'SqlLabDataGenerator.AI.Provider'
    if ($aiProvider -eq 'None') {
        Stop-PSFFunction -Message ($script:strings.'Locale.AINotConfigured' -f $Locale) -EnableException $true
    }

    Write-PSFMessage -Level Host -Message ($script:strings.'Locale.AIGenerating' -f $Locale, $aiProvider)

    $systemPrompt = Resolve-SldgPromptTemplate -Purpose 'locale-data' -Variables @{
        Locale   = $Locale
        PoolSize = $PoolSize
    }

    if (-not $systemPrompt) {
        Stop-PSFFunction -String 'Locale.PromptResolveFailed' -StringValues $Locale -EnableException $true
    }

    $userMessage = "Generate culturally authentic test data for locale: $Locale. Return ONLY the JSON object, nothing else."

    $response = Invoke-SldgAIRequest -SystemPrompt $systemPrompt -UserMessage $userMessage -Purpose 'locale-data'

    if (-not $response) {
        Stop-PSFFunction -Message ($script:strings.'Locale.AIFailed' -f $Locale) -EnableException $true
    }

    # Extract JSON from response (handle markdown code blocks or extra text)
    $jsonText = $response
    if ($jsonText -match '```(?:json)?\s*\n([\s\S]*?)\n```') {
        $jsonText = $Matches[1]
    }
    elseif ($jsonText -match '(\{[\s\S]*\})') {
        $jsonText = $Matches[1]
    }

    try {
        $parsed = $jsonText | ConvertFrom-Json -ErrorAction Stop
    }
    catch {
        Stop-PSFFunction -Message ($script:strings.'Locale.AIParseFailed' -f $Locale, $_) -EnableException $true
    }

    # Validate basic structure - must be an object with at least MaleNames
    if (-not $parsed.MaleNames) {
        Stop-PSFFunction -Message ($script:strings.'Locale.AIParseFailed' -f $Locale, 'AI response missing required property "MaleNames"') -EnableException $true
    }

    # Convert PSObject to hashtable structure
    $localeData = @{}

    # Simple string arrays
    $arrayKeys = @('MaleNames', 'FemaleNames', 'LastNames', 'StreetNames', 'StreetTypes',
        'Countries', 'EmailDomains', 'CompanyPrefixes', 'CompanyCores', 'CompanySuffixes',
        'Departments', 'JobTitles', 'Industries', 'IBANCountries', 'Currencies',
        'Statuses', 'Genders', 'Categories')

    foreach ($key in $arrayKeys) {
        if ($parsed.$key) {
            $localeData[$key] = @($parsed.$key)
        }
        else {
            Write-PSFMessage -Level Warning -Message ($script:strings.'Locale.AIMissingKey' -f $Locale, $key)
            $localeData[$key] = @()
        }
    }

    # Validate critical arrays are not empty — downstream Get-Random will crash on @()
    $requiredArrays = @('MaleNames', 'FemaleNames', 'LastNames', 'Locations')
    foreach ($key in $requiredArrays) {
        if ($localeData[$key].Count -eq 0) {
            Stop-PSFFunction -Message ($script:strings.'Locale.AIParseFailed' -f $Locale, "AI returned empty array for required key '$key'") -EnableException $true
        }
    }

    # Simple string values
    $stringKeys = @('ZipFormat', 'AddressFormat', 'StateLabel', 'NationalIdFormat', 'TaxIdFormat')
    foreach ($key in $stringKeys) {
        if ($parsed.$key) {
            $localeData[$key] = [string]$parsed.$key
        }
        else {
            # Sensible defaults
            $defaults = @{
                ZipFormat        = '{Prefix}{Suffix:D2}'
                AddressFormat    = '{Number} {Street} {StreetType}'
                StateLabel       = 'Region'
                NationalIdFormat = '{Part1:D4}-{Part2:D4}'
                TaxIdFormat      = '{Part1:D3}-{Part2:D6}'
            }
            $localeData[$key] = $defaults[$key]
        }
    }

    # Locations - array of hashtables
    if ($parsed.Locations) {
        $localeData['Locations'] = @(foreach ($loc in $parsed.Locations) {
                @{
                    City      = [string]$loc.City
                    State     = [string]$loc.State
                    ZipPrefix = [string]$loc.ZipPrefix
                }
            })
    }
    else {
        $localeData['Locations'] = @(@{ City = 'Unknown'; State = 'Unknown'; ZipPrefix = '000' })
    }

    # PhoneFormat - nested hashtable
    if ($parsed.PhoneFormat) {
        $pf = $parsed.PhoneFormat
        $formats = @{}
        if ($pf.Formats) {
            foreach ($prop in $pf.Formats.PSObject.Properties) {
                $formats[$prop.Name] = [string]$prop.Value
            }
        }
        if (-not $formats.Count) {
            $formats = @{
                Standard      = '{Area}{Exchange}{Subscriber}'
                International = '+{Area}{Exchange}{Subscriber}'
                Simple        = '{Area}{Exchange}{Subscriber}'
            }
        }
        $phoneExchangeMin = 100; try { if ($pf.ExchangeMin) { $phoneExchangeMin = [int]$pf.ExchangeMin } } catch { Write-PSFMessage -Level Debug -Message "Non-numeric ExchangeMin '$($pf.ExchangeMin)', using default" }
        $phoneExchangeMax = 999; try { if ($pf.ExchangeMax) { $phoneExchangeMax = [int]$pf.ExchangeMax } } catch { Write-PSFMessage -Level Debug -Message "Non-numeric ExchangeMax '$($pf.ExchangeMax)', using default" }
        $phoneSubscriberMin = 1000; try { if ($pf.SubscriberMin) { $phoneSubscriberMin = [int]$pf.SubscriberMin } } catch { Write-PSFMessage -Level Debug -Message "Non-numeric SubscriberMin '$($pf.SubscriberMin)', using default" }
        $phoneSubscriberMax = 9999; try { if ($pf.SubscriberMax) { $phoneSubscriberMax = [int]$pf.SubscriberMax } } catch { Write-PSFMessage -Level Debug -Message "Non-numeric SubscriberMax '$($pf.SubscriberMax)', using default" }
        $localeData['PhoneFormat'] = @{
            AreaCodes     = @(if ($pf.AreaCodes) { $pf.AreaCodes } else { @('000') })
            Formats       = $formats
            ExchangeMin   = $phoneExchangeMin
            ExchangeMax   = $phoneExchangeMax
            SubscriberMin = $phoneSubscriberMin
            SubscriberMax = $phoneSubscriberMax
        }
    }
    else {
        $localeData['PhoneFormat'] = @{
            AreaCodes     = @('000')
            Formats       = @{ Standard = '{Area}{Exchange}{Subscriber}'; International = '+{Area}{Exchange}{Subscriber}'; Simple = '{Area}{Exchange}{Subscriber}' }
            ExchangeMin   = 100
            ExchangeMax   = 999
            SubscriberMin = 1000
            SubscriberMax = 9999
        }
    }

    # Cache the generated locale
    Invoke-SldgCacheEviction -Cache $script:SldgState.AILocaleCache -CacheName 'AILocaleCache'
    $script:SldgState.AILocaleCache[$Locale] = $localeData
    $script:SldgState.CacheTimestamps["AILocaleCache$($script:CacheKeySeparator)$Locale"] = [datetime]::UtcNow
    Write-PSFMessage -Level Verbose -Message ($script:strings.'Locale.AIGenerated' -f $Locale)

    $localeData
}