export type BrandingFormValues = { primary: string; accent: string; background: string; surface: string; headingFont: string; bodyFont: string; fontSize: 's' | 'm' | 'l'; logoDataUrl: string; logoValue: string; logoMode: 'upload' | 'emoticon'; logoPosition: 'left' | 'center' | 'right'; logoSize: 's' | 'm' | 'l'; mode: 'light' | 'dark' | 'auto'; buttonStyle: 'filled' | 'outline'; buttonRadius: number; buttonPrimary: string; buttonSecondary: string; linkColor: string; }; export type BrandingFormDefaults = Pick< BrandingFormValues, | 'primary' | 'accent' | 'background' | 'surface' | 'headingFont' | 'bodyFont' | 'mode' | 'buttonStyle' | 'buttonRadius' | 'buttonPrimary' | 'buttonSecondary' | 'linkColor' | 'fontSize' | 'logoMode' | 'logoPosition' | 'logoSize' >; type BrandingRecord = Record; const isRecord = (value: unknown): value is BrandingRecord => Boolean(value) && typeof value === 'object' && !Array.isArray(value); const readHexColor = (value: unknown, fallback: string): string => { if (typeof value === 'string' && value.trim().startsWith('#')) { return value.trim(); } return fallback; }; const readEnum = (value: unknown, allowed: readonly T[], fallback: T): T => ( allowed.includes(value as T) ? (value as T) : fallback ); const readNumber = (value: unknown, fallback: number): number => ( typeof value === 'number' && !Number.isNaN(value) ? value : fallback ); const resolveAssetPreviewUrl = (value: string | null | undefined): string => { if (typeof value !== 'string') { return ''; } const trimmed = value.trim(); if (!trimmed) { return ''; } if (trimmed.startsWith('data:') || trimmed.startsWith('http://') || trimmed.startsWith('https://')) { return trimmed; } const normalized = trimmed.startsWith('/') ? trimmed.slice(1) : trimmed; if (normalized.startsWith('storage/')) { return `/${normalized}`; } if (normalized.startsWith('branding/') || normalized.startsWith('tenant-branding/')) { return `/storage/${normalized}`; } return trimmed.startsWith('/') ? trimmed : `/${trimmed}`; }; export function extractBrandingForm(settings: unknown, defaults: BrandingFormDefaults): BrandingFormValues { const settingsRecord = isRecord(settings) ? settings : {}; const branding = isRecord(settingsRecord.branding) ? (settingsRecord.branding as BrandingRecord) : settingsRecord; const palette = isRecord(branding.palette) ? (branding.palette as BrandingRecord) : {}; const typography = isRecord(branding.typography) ? (branding.typography as BrandingRecord) : {}; const buttons = isRecord(branding.buttons) ? (branding.buttons as BrandingRecord) : {}; const logo = isRecord(branding.logo) ? (branding.logo as BrandingRecord) : {}; const primary = readHexColor(palette.primary, readHexColor(branding.primary_color, defaults.primary)); const accent = readHexColor( palette.secondary, readHexColor(branding.secondary_color, readHexColor(branding.accent_color, defaults.accent)) ); const background = readHexColor(palette.background, readHexColor(branding.background_color, defaults.background)); const surface = readHexColor(palette.surface, readHexColor(branding.surface_color, background)); const headingFont = typeof typography.heading === 'string' ? typography.heading : (branding.heading_font as string | undefined); const bodyFont = typeof typography.body === 'string' ? typography.body : (branding.body_font as string | undefined); const mode = readEnum(branding.mode, ['light', 'dark', 'auto'], defaults.mode); const fontSize = readEnum(typography.size ?? branding.font_size, ['s', 'm', 'l'], defaults.fontSize); const logoMode = readEnum(logo.mode ?? branding.logo_mode, ['upload', 'emoticon'], defaults.logoMode); const logoValue = logoMode === 'emoticon' ? (typeof logo.value === 'string' ? logo.value : (branding.logo_value as string | undefined) ?? '') : ''; const logoPosition = readEnum(logo.position ?? branding.logo_position, ['left', 'center', 'right'], defaults.logoPosition); const logoSize = readEnum(logo.size ?? branding.logo_size, ['s', 'm', 'l'], defaults.logoSize); const logoUploadValue = typeof branding.logo_data_url === 'string' ? branding.logo_data_url : logoMode === 'upload' ? (typeof logo.value === 'string' ? logo.value : (branding.logo_url as string | undefined)) : undefined; const buttonStyle = readEnum(buttons.style ?? branding.button_style, ['filled', 'outline'], defaults.buttonStyle); const buttonRadius = readNumber(buttons.radius ?? branding.button_radius, defaults.buttonRadius); const buttonPrimary = readHexColor(buttons.primary, readHexColor(branding.button_primary_color, primary)); const buttonSecondary = readHexColor(buttons.secondary, readHexColor(branding.button_secondary_color, accent)); const linkColor = readHexColor(buttons.link_color ?? buttons.linkColor, readHexColor(branding.link_color, accent)); return { primary, accent, background, surface, headingFont: headingFont ?? defaults.headingFont ?? '', bodyFont: bodyFont ?? defaults.bodyFont ?? '', fontSize, logoDataUrl: resolveAssetPreviewUrl(logoUploadValue), logoValue, logoMode, logoPosition, logoSize, mode, buttonStyle, buttonRadius, buttonPrimary, buttonSecondary, linkColor, }; }