src/public/Configuration/Resolve-AitherServiceUrl.ps1

#Requires -Version 7.0

<#
.SYNOPSIS
    Resolves an AitherOS service URL dynamically from the service registry.
 
.DESCRIPTION
    Looks up service port and URL from AitherZero/config/services.psd1 (which mirrors
    AitherOS/config/services.yaml). Supports service names, aliases, and Docker mode
    hostname resolution. Eliminates hardcoded port assumptions.
 
    In Docker mode (AITHER_DOCKER_MODE=true), resolves to container hostnames instead
    of localhost. Supports multi-node deployments via environment variable overrides.
 
.PARAMETER Name
    Service name or alias (e.g., 'Genesis', 'Node', 'Pulse', 'MicroScheduler').
 
.PARAMETER PathOnly
    Return just the port number instead of the full URL.
 
.PARAMETER HealthEndpoint
    Append the service's health check path to the URL.
 
.EXAMPLE
    Resolve-AitherServiceUrl -Name Genesis
    # Returns: http://localhost:8001
 
.EXAMPLE
    Resolve-AitherServiceUrl -Name MicroScheduler
    # Returns: http://localhost:8150
 
.EXAMPLE
    Resolve-AitherServiceUrl -Name Node -HealthEndpoint
    # Returns: http://localhost:8080/health
 
.EXAMPLE
    $env:AITHER_DOCKER_MODE = 'true'
    Resolve-AitherServiceUrl -Name Genesis
    # Returns: http://aitheros-genesis:8001
 
.NOTES
    Category: Configuration
    Platform: Windows, Linux, macOS
#>

function Resolve-AitherServiceUrl {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter()]
        [switch]$PortOnly,

        [Parameter()]
        [switch]$HealthEndpoint
    )

    # Known aliases (matching AitherPorts.py)
    $aliases = @{
        'Orchestrator'  = 'Genesis'
        'LLM'           = 'MicroScheduler'
        'Scheduler'     = 'MicroScheduler'
        'Brain'         = 'Genesis'
        'MCP'           = 'Node'
        'Events'        = 'Pulse'
        'Logs'          = 'Chronicle'
        'Vault'         = 'Secrets'
        'Ingestion'     = 'Strata'
        'Dashboard'     = 'Veil'
        'UI'            = 'Veil'
    }

    $resolvedName = if ($aliases.ContainsKey($Name)) { $aliases[$Name] } else { $Name }

    # Try loading from services.psd1
    $port = $null
    $healthPath = '/health'
    $containerName = $null

    if (-not $script:_ServiceRegistryCache) {
        $moduleRoot = Get-AitherModuleRoot -ErrorAction SilentlyContinue
        if (-not $moduleRoot) {
            $fallbackRoot = Get-AitherProjectRoot -ErrorAction SilentlyContinue
            if ($fallbackRoot) { $moduleRoot = Join-Path $fallbackRoot "AitherZero" }
        }
        $registryPath = Join-Path $moduleRoot "config/services.psd1"

        if (Test-Path $registryPath) {
            try {
                $script:_ServiceRegistryCache = Import-PowerShellDataFile $registryPath -ErrorAction Stop
            }
            catch {
                Write-Verbose "Could not load services.psd1: $_"
            }
        }
    }

    if ($script:_ServiceRegistryCache -and $script:_ServiceRegistryCache.Services) {
        $services = $script:_ServiceRegistryCache.Services

        # Direct name match
        $svcKey = $services.Keys | Where-Object {
            $_ -eq $resolvedName -or
            $_ -eq "Aither$resolvedName" -or
            $_ -like "*$resolvedName*"
        } | Select-Object -First 1

        if ($svcKey) {
            $svc = $services[$svcKey]
            $port = $svc.Port
            if ($svc.HealthPath) { $healthPath = $svc.HealthPath }
            $containerName = "aitheros-$($svcKey.ToLower() -replace '^aither', '')"
        }

        # Try aliases within services.psd1
        if (-not $port) {
            foreach ($key in $services.Keys) {
                $svc = $services[$key]
                if ($svc.Aliases -and ($svc.Aliases -contains $resolvedName)) {
                    $port = $svc.Port
                    if ($svc.HealthPath) { $healthPath = $svc.HealthPath }
                    $containerName = "aitheros-$($key.ToLower() -replace '^aither', '')"
                    break
                }
            }
        }
    }

    # Fallback: well-known ports
    if (-not $port) {
        $wellKnown = @{
            'Genesis'        = 8001;  'Node'           = 8080
            'Pulse'          = 8081;  'Watch'          = 8082
            'Secrets'        = 8111;  'Chronicle'      = 8121
            'Strata'         = 8136;  'MicroScheduler' = 8150
            'Veil'           = 3000;  'Compute'        = 8168
            'Mesh'           = 8125;  'Comet'          = 8126
            'MCPGateway'     = 8180;  'Directory'      = 8190
        }

        if ($wellKnown.ContainsKey($resolvedName)) {
            $port = $wellKnown[$resolvedName]
            $containerName = "aitheros-$($resolvedName.ToLower())"
        }
        else {
            Write-Warning "Service '$Name' not found in registry or well-known ports."
            return $null
        }
    }

    if ($PortOnly) {
        return $port
    }

    # Determine hostname
    $isDocker = $env:AITHER_DOCKER_MODE -eq 'true'
    $host_ = if ($isDocker -and $containerName) { $containerName } else { 'localhost' }

    # Check for environment override (e.g., AITHER_GENESIS_URL)
    $envKey = "AITHER_$($resolvedName.ToUpper())_URL"
    $envOverride = [Environment]::GetEnvironmentVariable($envKey)
    if ($envOverride) {
        return $envOverride
    }

    $url = "http://${host_}:${port}"
    if ($HealthEndpoint) {
        $url += $healthPath
    }

    return $url
}