import { defaultRuntimeTheme, type RuntimeTheme } from "./contract" import { applyRuntimeTheme, isRuntimeTheme, resetRuntimeTheme } from "./runtime" import { beginThemeRequest, isCurrentThemeRequest } from "./requests" /** Fetch/validate only. No DOM access, fixed URL, storage or framework dependency. */ export async function fetchRuntimeTheme(url: string | URL, request?: RequestInit): Promise { const response = await fetch(url, request) if (!response.ok) throw new Error(`Theme request failed: HTTP ${response.status}`) const theme: unknown = await response.json() if (!isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document") return { schemaVersion: 1, tokens: { ...theme.tokens } } } /** Resolve a supplied default + partial override using the existing v1 contract. */ export function resolveRuntimeTheme(theme: unknown, defaults: unknown = defaultRuntimeTheme): RuntimeTheme { if (!isRuntimeTheme(defaults) || !isRuntimeTheme(theme)) throw new Error("Invalid Runtime Theme v1 document") return { schemaVersion: 1, tokens: { ...defaultRuntimeTheme.tokens, ...defaults.tokens, ...theme.tokens } } } export type RuntimeThemeLoadOptions = { request?: RequestInit; defaults?: RuntimeTheme } /** Convenience path: CDN/settings URL -> validate -> optional merge -> apply. * Failure releases this API's override to the host/HM CSS fallback. * Superseded requests never apply or reset the newer owner's theme. */ export async function loadRuntimeTheme(root: HTMLElement, url: string | URL, options: RuntimeThemeLoadOptions = {}): Promise { const request = beginThemeRequest(root) try { const theme = await fetchRuntimeTheme(url, options.request) if (!isCurrentThemeRequest(root, request)) return false return applyRuntimeTheme(root, options.defaults ? resolveRuntimeTheme(theme, options.defaults) : theme) } catch { if (isCurrentThemeRequest(root, request)) resetRuntimeTheme(root) return false } }