import type { ConfiguredDefaultServerDefinition, DefaultEndpointTemplate, DefaultServerDefinition, ServerEndpoint } from '../models/server-directory.model'; export function sanitiseServerBaseUrl(rawUrl: string): string { let cleaned = rawUrl.trim().replace(/\/+$/, ''); if (cleaned.toLowerCase().endsWith('/api')) { cleaned = cleaned.slice(0, -4); } return cleaned; } export function normaliseConfiguredServerUrl( rawUrl: string, defaultProtocol: 'http' | 'https' ): string { let cleaned = rawUrl.trim(); if (!cleaned) { return ''; } if (cleaned.toLowerCase().startsWith('ws://')) { cleaned = `http://${cleaned.slice(5)}`; } else if (cleaned.toLowerCase().startsWith('wss://')) { cleaned = `https://${cleaned.slice(6)}`; } else if (cleaned.startsWith('//')) { cleaned = `${defaultProtocol}:${cleaned}`; } else if (!/^[a-z][a-z\d+.-]*:\/\//i.test(cleaned)) { cleaned = `${defaultProtocol}://${cleaned}`; } return sanitiseServerBaseUrl(cleaned); } export function buildFallbackDefaultServerUrl( configuredUrl: string | undefined, defaultProtocol: 'http' | 'https' ): string { if (configuredUrl?.trim()) { return normaliseConfiguredServerUrl(configuredUrl, defaultProtocol); } return `${defaultProtocol}://localhost:3001`; } export function buildDefaultServerDefinitions( configuredDefaults: ConfiguredDefaultServerDefinition[] | undefined, configuredUrl: string | undefined, defaultProtocol: 'http' | 'https' ): DefaultServerDefinition[] { const seenKeys = new Set(); const seenUrls = new Set(); const definitions = (configuredDefaults ?? []) .map((server, index) => { const key = server.key?.trim() || `default-${index + 1}`; const url = normaliseConfiguredServerUrl(server.url ?? '', defaultProtocol); if (!key || !url || seenKeys.has(key) || seenUrls.has(url)) { return null; } seenKeys.add(key); seenUrls.add(url); return { key, name: server.name?.trim() || (index === 0 ? 'Default Server' : `Default Server ${index + 1}`), url } satisfies DefaultServerDefinition; }) .filter((definition): definition is DefaultServerDefinition => definition !== null); if (definitions.length > 0) { return definitions; } return [ { key: 'default', name: 'Default Server', url: buildFallbackDefaultServerUrl(configuredUrl, defaultProtocol) } ]; } export function buildDefaultEndpointTemplates( definitions: DefaultServerDefinition[] ): DefaultEndpointTemplate[] { return definitions.map((definition) => ({ name: definition.name, url: definition.url, isActive: true, isDefault: true, defaultKey: definition.key, status: 'unknown' })); } export function hasEndpointForDefault( endpoints: ServerEndpoint[], defaultEndpoint: DefaultEndpointTemplate ): boolean { return endpoints.some((endpoint) => endpoint.defaultKey === defaultEndpoint.defaultKey || sanitiseServerBaseUrl(endpoint.url) === defaultEndpoint.url ); } export function matchDefaultEndpointTemplate( defaultEndpoints: DefaultEndpointTemplate[], endpoint: ServerEndpoint, sanitisedUrl: string, claimedDefaultKeys: Set ): DefaultEndpointTemplate | null { if (endpoint.defaultKey) { return defaultEndpoints.find( (candidate) => candidate.defaultKey === endpoint.defaultKey && !claimedDefaultKeys.has(candidate.defaultKey) ) ?? null; } if (!endpoint.isDefault) { return null; } const matchingCurrentDefault = defaultEndpoints.find( (candidate) => candidate.url === sanitisedUrl && !claimedDefaultKeys.has(candidate.defaultKey) ); if (matchingCurrentDefault) { return matchingCurrentDefault; } return defaultEndpoints.find( (candidate) => !claimedDefaultKeys.has(candidate.defaultKey) ) ?? null; } export function findDefaultEndpointKeyByUrl( defaultEndpoints: DefaultEndpointTemplate[], url: string ): string | null { const sanitisedUrl = sanitiseServerBaseUrl(url); return defaultEndpoints.find((endpoint) => endpoint.url === sanitisedUrl)?.defaultKey ?? null; } export function ensureAnyActiveEndpoint(endpoints: ServerEndpoint[]): ServerEndpoint[] { if (endpoints.length === 0 || endpoints.some((endpoint) => endpoint.isActive)) { return endpoints; } const nextEndpoints = [...endpoints]; nextEndpoints[0] = { ...nextEndpoints[0], isActive: true }; return nextEndpoints; } export function ensureCompatibleActiveEndpoint(endpoints: ServerEndpoint[]): ServerEndpoint[] { if (endpoints.length === 0 || endpoints.some((endpoint) => endpoint.isActive)) { return endpoints; } const fallbackIndex = endpoints.findIndex((endpoint) => endpoint.status !== 'incompatible'); if (fallbackIndex < 0) { return endpoints; } const nextEndpoints = [...endpoints]; nextEndpoints[fallbackIndex] = { ...nextEndpoints[fallbackIndex], isActive: true }; return nextEndpoints; }