import { SPECIAL_GROUP_KEY_PATTERN } from '../common/special-group-key'; export const WEBSITE_STATIC_IMAGE_CATALOG_PATH = '/meshkee/static-image-slots'; export type WebsiteStaticImageCatalogSlot = { key: string; label: string; kind: 'single' | 'list'; pageKey: string; aspectRatio: string | null; recommendedWidth: number | null; itemCount: number | null; }; const ASPECT_RATIO_PATTERN = /^[0-9]{1,3}:[0-9]{1,3}$/; function asRecord(value: unknown): Record | null { return value && typeof value === 'object' && !Array.isArray(value) ? (value as Record) : null; } function readString(value: unknown): string { return typeof value === 'string' ? value.trim() : ''; } function readOptionalInt(value: unknown, min: number, max: number): number | null { if (value == null || value === '') return null; const parsed = typeof value === 'number' ? value : Number(value); if (!Number.isInteger(parsed) || parsed < min || parsed > max) return null; return parsed; } export function parseWebsiteStaticImageCatalog( payload: unknown, ): WebsiteStaticImageCatalogSlot[] { const root = asRecord(payload); const rawSlots = Array.isArray(root?.slots) ? root.slots : Array.isArray(payload) ? payload : null; if (!rawSlots) { throw new Error('Slot catalog must be { slots: [...] }'); } const slots: WebsiteStaticImageCatalogSlot[] = []; const seen = new Set(); for (const entry of rawSlots) { const row = asRecord(entry); if (!row) continue; const key = readString(row.key); if (!key || !SPECIAL_GROUP_KEY_PATTERN.test(key) || seen.has(key)) continue; const kind = readString(row.kind) === 'single' ? 'single' : 'list'; const label = readString(row.label) || key; const pageKey = readString(row.pageKey) || 'home'; if (!SPECIAL_GROUP_KEY_PATTERN.test(pageKey)) continue; const aspectRatioRaw = readString(row.aspectRatio); const aspectRatio = aspectRatioRaw && ASPECT_RATIO_PATTERN.test(aspectRatioRaw) ? aspectRatioRaw : null; const itemCount = kind === 'single' ? 1 : readOptionalInt(row.itemCount, 1, 24); seen.add(key); slots.push({ key, label: label.slice(0, 255), kind, pageKey, aspectRatio, recommendedWidth: readOptionalInt(row.recommendedWidth, 1, 8000), itemCount, }); } if (slots.length === 0) { throw new Error('Slot catalog did not contain any valid slots'); } return slots; } export function websiteCatalogUrls(hosts: string[]): string[] { const uniqueHosts = [...new Set(hosts.map((host) => host.toLowerCase().trim()).filter(Boolean))]; const urls: string[] = []; for (const host of uniqueHosts) { const bare = host.replace(/^www\./, ''); const variants = [bare, `www.${bare}`]; for (const candidate of variants) { urls.push(`https://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}`); urls.push(`http://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}`); urls.push(`https://${candidate}${WEBSITE_STATIC_IMAGE_CATALOG_PATH}.json`); } } return [...new Set(urls)]; }