import { createContext, use, useCallback, useEffect, useMemo, useState } from "react";
import { jsx } from "react/jsx-runtime";
import Color from "colorjs.io";
//#region ../core/src/app-definition-api-context.ts
const AppDefinitionApiContext = createContext(null);
const AppDefinitionApiProvider = AppDefinitionApiContext.Provider;
function useAppDefinitionApi() {
	const api = use(AppDefinitionApiContext);
	if (!api) throw new Error("useAppDefinitionApi must be used within an AppDefinitionApiProvider");
	return api;
}
//#endregion
//#region ../../platform/theme-engine/src/types.ts
const SEMANTIC_COLOR_NAMES = [
	"background",
	"foreground",
	"primary",
	"secondary",
	"accent",
	"muted",
	"destructive"
];
const FONT_SIZE_KEYS = [
	"extraSmall",
	"small",
	"regular",
	"large",
	"extraLarge",
	"giant"
];
const FONT_FAMILY_KEYS = ["header", "body"];
const RADIUS_KEYS = [
	"small",
	"medium",
	"large",
	"extraLarge"
];
//#endregion
//#region ../../platform/theme-engine/src/color-engine.ts
const BARE_HEX_RE = /^[0-9a-fA-F]{6}$/;
/**
* Attempt to convert any string into a Color using colorjs.io.
* If the string is exactly 6 hex digits it is assumed to be a bare hex value
* (e.g. "3b82f6") and a "#" prefix is added before parsing. Six-letter
* named colours like "orange" or "maroon" are left untouched.
*
* @returns the parsed Color, or a neutral gray (`oklch(0.5 0 0)`) on failure
*/
function parseColor(value) {
	if (BARE_HEX_RE.test(value)) value = `#${value}`;
	try {
		return new Color(value);
	} catch (error) {
		console.warn("[theme] Failed to parse color:", value, error);
		return new Color("oklch", [
			.5,
			0,
			0
		]);
	}
}
/**
* Returns either the original foreground or a corrected lightness variant,
* whichever provides better contrast against `color`.
* Inversion triggers when the |APCA contrast| is below 50 — APCA is signed
* (negative for dark-on-light, positive for light-on-dark), so comparing the
* absolute value avoids flipping dark text that already contrasts well on a
* medium background.
*/
function getForegroundColor(foreground, color) {
	if (foreground.oklch.l == null || color.oklch.l == null) return foreground;
	const contrast = color.contrastAPCA(foreground);
	if (Math.abs(contrast) < 50) return new Color("oklch", [
		color.oklch.l < .7 ? .95 : .15,
		foreground.oklch.c || 0,
		foreground.oklch.h || 0
	]);
	return foreground;
}
const DARK_DERIVATION_CONFIG = {
	background: {
		baseLightness: .15,
		fgLightness: .93
	},
	foreground: {
		baseLightness: .93,
		fgLightness: .15
	},
	muted: {
		baseLightness: .22,
		fgLightness: .75
	},
	primary: {
		baseLightness: "invert",
		fgLightness: .95,
		chromaScale: .9
	},
	secondary: {
		baseLightness: "invert",
		fgLightness: .93,
		chromaScale: .85
	},
	accent: {
		baseLightness: "invert",
		fgLightness: .95,
		chromaScale: .9
	},
	destructive: {
		baseLightness: "invert",
		fgLightness: .95,
		chromaScale: .95
	}
};
/** Invert OKLCH lightness (1 - l), clamped to [0.35, 0.75] to avoid extremes. */
function invertLightness(l) {
	const inverted = 1 - l;
	return Math.max(.35, Math.min(.75, inverted));
}
/**
* Derive a dark-mode ThemeColorInput from its light-mode counterpart.
*/
function deriveDarkVariant(name, light) {
	const config = DARK_DERIVATION_CONFIG[name];
	const chromaScale = config.chromaScale ?? 1;
	const baseLightness = config.baseLightness === "invert" ? invertLightness(light.base.oklch.l ?? 0) : config.baseLightness;
	const fgLightness = config.fgLightness === "invert" ? invertLightness(light.foreground.oklch.l ?? 0) : config.fgLightness;
	return {
		base: new Color("oklch", [
			baseLightness,
			(light.base.oklch.c || 0) * chromaScale,
			light.base.oklch.h || 0
		]),
		foreground: new Color("oklch", [
			fgLightness,
			(light.foreground.oklch.c || 0) * chromaScale,
			light.foreground.oklch.h || 0
		])
	};
}
/**
* Merge auto-derived dark colors with any user-specified overrides.
* For each semantic color, if the user has fully overridden both base and
* foreground those are used; otherwise the missing channels are derived.
*/
function mergeDarkOverrides(def) {
	const darkColors = {};
	for (const name of SEMANTIC_COLOR_NAMES) {
		const lightInput = def.light[name];
		const darkOverride = def.dark[name];
		if (darkOverride?.base && darkOverride?.foreground) darkColors[name] = darkOverride;
		else if (darkOverride) {
			const base = darkOverride.base ?? deriveDarkVariant(name, lightInput).base;
			darkColors[name] = {
				base,
				foreground: darkOverride.foreground ?? getForegroundColor(def.light.foreground.base, base)
			};
		} else darkColors[name] = deriveDarkVariant(name, lightInput);
	}
	return darkColors;
}
function resolveColorSet(colors) {
	const resolved = {};
	for (const name of SEMANTIC_COLOR_NAMES) {
		const input = colors[name];
		resolved[name] = {
			base: input.base.clone(),
			foreground: input.foreground.clone()
		};
	}
	return resolved;
}
/**
* Resolve a ThemeDefinition into a complete ResolvedTheme.
* Dark mode colors are derived from light where not overridden.
*/
function resolveTheme(def) {
	return {
		id: def.id,
		name: def.name,
		light: resolveColorSet(def.light),
		dark: resolveColorSet(mergeDarkOverrides(def)),
		fontSizes: { ...def.fontSizes },
		fontFamilies: { ...def.fontFamilies },
		spacing: def.spacing,
		radii: { ...def.radii }
	};
}
//#endregion
//#region ../../platform/ui-primitives/src/tokens/manifest.ts
const TOKEN_MANIFEST = {
	colors: [
		"background",
		"foreground",
		"card",
		"card-foreground",
		"popover",
		"popover-foreground",
		"primary",
		"primary-foreground",
		"secondary",
		"secondary-foreground",
		"muted",
		"muted-foreground",
		"accent",
		"accent-foreground",
		"destructive",
		"destructive-foreground",
		"border",
		"input",
		"ring",
		"chart-1",
		"chart-2",
		"chart-3",
		"chart-4",
		"chart-5",
		"sidebar",
		"sidebar-foreground",
		"sidebar-primary",
		"sidebar-primary-foreground",
		"sidebar-accent",
		"sidebar-accent-foreground",
		"sidebar-border",
		"sidebar-ring",
		"contrast"
	],
	radii: [
		"",
		"sm",
		"md",
		"lg",
		"xl"
	],
	merchantCustomizable: [
		"background",
		"foreground",
		"primary",
		"secondary",
		"accent",
		"muted",
		"destructive"
	]
};
//#endregion
//#region ../../platform/theme-engine/src/tailwind-overrides.ts
const OVERRIDES = {
	"--color-gray-50": "var(--color-muted)",
	"--color-gray-100": "color-mix(in oklch, var(--color-muted), var(--color-foreground) 15%)",
	"--color-gray-200": "var(--color-border)"
};
/**
* Map Tailwind built-in color names to semantic theme colors using color-mix
* for shade interpolation. Each shade maps to a percentage of the semantic
* color mixed with transparent, so shades naturally adapt to both light and
* dark modes without inversion logic.
*/
function emitTailwindOverrides() {
	const TAILWIND_COLOR_MAP = {
		gray: "foreground",
		red: "destructive",
		blue: "primary",
		green: "accent"
	};
	const TAILWIND_SHADES = [
		50,
		100,
		200,
		300,
		400,
		500,
		600,
		700,
		800,
		900,
		950
	];
	const lines = [];
	for (const [twName, semantic] of Object.entries(TAILWIND_COLOR_MAP)) for (const shade of TAILWIND_SHADES) {
		const override = OVERRIDES[`--color-${twName}-${shade}`];
		if (override) lines.push(`--color-${twName}-${shade}: ${override};`);
		else {
			const percent = Math.max(10, Math.min(Math.round(shade / 10), 100));
			lines.push(`--color-${twName}-${shade}: color-mix(in oklch, var(--color-${semantic}) ${percent}%, transparent);`);
		}
	}
	lines.push("--color-white: var(--color-background);");
	lines.push("--color-black: var(--color-foreground);");
	return lines;
}
//#endregion
//#region ../../platform/theme-engine/src/css-generator.ts
function colorToCSS(color) {
	const result = color.toString({ format: "oklch" });
	if (result.includes("NaN")) {
		console.warn("[theme] colorToCSS produced NaN, using neutral fallback:", result);
		return "oklch(0.5 0 0)";
	}
	return result;
}
function camelToKebab(str) {
	return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
}
/**
* Emit --color-{name} and --color-{name}-foreground vars.
* Uses --color- prefix to match portal-widgets/tailwind.config.ts.
*/
function emitColorVars(colors) {
	const lines = [];
	for (const name of SEMANTIC_COLOR_NAMES) {
		const color = colors[name];
		lines.push(`--color-${name}: ${colorToCSS(color.base)};`);
		lines.push(`--color-${name}-foreground: ${colorToCSS(color.foreground)};`);
	}
	return lines;
}
/**
* Format a font family value for CSS output.
* - If the value starts with "var(" (legacy), pass through as-is
* - If the value already contains a comma (has fallback), pass through as-is
* - Otherwise, wrap in quotes and append a generic sans-serif fallback
*/
function formatFontFamily(value) {
	if (value.startsWith("var(")) return value;
	if (value.includes(",")) return value;
	return `'${value}', sans-serif`;
}
/**
* Emit non-color CSS variables (font sizes, families, spacing, radii).
*/
function emitNonColorVars(theme) {
	const lines = [];
	for (const key of FONT_SIZE_KEYS) lines.push(`--font-size-${camelToKebab(key)}: ${theme.fontSizes[key]};`);
	for (const key of FONT_FAMILY_KEYS) lines.push(`--font-${key}: ${formatFontFamily(theme.fontFamilies[key])};`);
	lines.push(`--spacing: ${theme.spacing};`);
	for (const key of RADIUS_KEYS) lines.push(`--radius-${camelToKebab(key)}: ${theme.radii[key]};`);
	return lines;
}
const MERCHANT_CUSTOMIZABLE = new Set(TOKEN_MANIFEST.merchantCustomizable);
/**
* Names that theme-engine emits via emitColorVars (the merchant-customizable
* base set) and their derived `-foreground` siblings. Used to skip duplicate
* emission of `--color-{name}` from the manifest derivation pass.
*
* Typed as `Set<string>` because the synthesized `${name}-foreground` strings
* include `"background-foreground"` / `"foreground-foreground"`, which are not
* members of `SemanticTokenName`. Membership checks against the manifest still
* work because `Set.has(string)` accepts the wider type.
*/
const EMITTED_BY_COLOR_VARS = new Set([...SEMANTIC_COLOR_NAMES, ...SEMANTIC_COLOR_NAMES.map((n) => `${n}-foreground`)]);
/**
* Resolve the alias expression for a non-merchant-customizable token.
*
* Returns `null` for tokens that should fall through to the canonical default
* defined in `ui-primitives.css` (chart palette is intentionally not merchant-themed).
*
* The exhaustive switch is the fail-fast guarantee: TypeScript rejects this
* file if a non-customizable token in the manifest is missing a case here.
*/
function aliasFor(token) {
	switch (token) {
		case "card": return "var(--color-muted)";
		case "card-foreground": return "var(--color-muted-foreground)";
		case "popover": return "var(--color-background)";
		case "popover-foreground": return "var(--color-foreground)";
		case "border": return "color-mix(in oklch, var(--color-background), var(--color-foreground) 15%)";
		case "input": return "var(--color-border)";
		case "ring": return "var(--color-primary)";
		case "chart-1":
		case "chart-2":
		case "chart-3":
		case "chart-4":
		case "chart-5": return null;
		case "sidebar": return "var(--color-muted)";
		case "sidebar-foreground": return "var(--color-muted-foreground)";
		case "sidebar-primary": return "var(--color-primary)";
		case "sidebar-primary-foreground": return "var(--color-primary-foreground)";
		case "sidebar-accent": return "var(--color-accent)";
		case "sidebar-accent-foreground": return "var(--color-accent-foreground)";
		case "sidebar-border": return "var(--color-border)";
		case "sidebar-ring": return "var(--color-primary)";
		case "contrast": return "var(--color-foreground)";
	}
}
/**
* Build the alias block emitted at the top of every `[data-theme]` rule.
*
* For each canonical token in `TOKEN_MANIFEST.colors`:
* - merchant-customizable tokens: `--color-{name}` is emitted by `emitColorVars`
*   from the resolved theme; this function only emits the short-name alias
*   (`--{name}: var(--color-{name})`) for compatibility with consumers that
*   reference short names.
* - non-customizable tokens: emit both `--color-{name}: <alias>` and the
*   short-name alias, unless `aliasFor` returns null. Returning null is the
*   intentional pass-through signal: ui-primitives.css already maps `--color-{name}`
*   to `var(--{name})` via `@theme inline`, so emitting `--{name}: var(--color-{name})`
*   inside `[data-theme]` would create a circular var() reference and resolve
*   to an invalid value (chart-1..5 would render transparent under any theme).
*
* Two static aliases (`--color-background-foreground`, `--color-foreground-foreground`)
* are not regular `-foreground` siblings; they are emitted explicitly because
* they swap the bg/fg pair rather than reading from a paired field.
*/
function buildAliasBlock() {
	const lines = ["--color-background-foreground: var(--color-foreground);", "--color-foreground-foreground: var(--color-background);"];
	for (const token of TOKEN_MANIFEST.colors) {
		if (MERCHANT_CUSTOMIZABLE.has(token)) {
			lines.push(`--${token}: var(--color-${token});`);
			continue;
		}
		if (EMITTED_BY_COLOR_VARS.has(token)) {
			lines.push(`--${token}: var(--color-${token});`);
			continue;
		}
		const alias = aliasFor(token);
		if (alias === null) continue;
		lines.push(`--color-${token}: ${alias};`);
		lines.push(`--${token}: var(--color-${token});`);
	}
	return lines;
}
/**
* Static alias variables emitted at the top of every theme rule.
* Derived from the canonical token manifest so new canonical tokens reach
* merchant theming automatically.
*/
const globalCSSOverride = [
	...buildAliasBlock(),
	"--radius-sm: var(--radius-small);",
	"--radius-md: var(--radius-medium);",
	"--radius-lg: var(--radius-large);",
	"--radius-xl: var(--radius-extra-large);",
	"--text-xs: var(--font-size-extra-small);",
	"--text-sm: var(--font-size-small);",
	"--text-base: var(--font-size-regular);",
	"--text-lg: var(--font-size-large);",
	"--text-xl: var(--font-size-extra-large);",
	"--text-2xl: var(--font-size-giant);"
];
/**
* Overrides for global tailwindcss for specifically dark mode.
*/
const globalDarkCSSOverride = ["--border: color-mix(in oklch, var(--color-background), var(--color-foreground) 15%);"];
/**
* Generate a complete CSS string for a resolved theme.
* Outputs 2–3 blocks: light default, dark explicit via `[data-theme-mode="dark"]`,
* and (unless `disableAutoTheme`) a `prefers-color-scheme: dark` media query block.
*/
function generateThemeCSS(theme, options = {}) {
	const sel = `[data-theme="${theme.id}"]`;
	const tw = options.mapTailwindColors ?? true;
	const blocks = [];
	blocks.push(`${sel} {`);
	blocks.push(...globalCSSOverride);
	blocks.push(...emitNonColorVars(theme));
	blocks.push(...emitColorVars(theme.light));
	if (tw) blocks.push(...emitTailwindOverrides());
	blocks.push(`}`);
	blocks.push(`${sel}[data-theme-mode="dark"] {`);
	blocks.push(...globalDarkCSSOverride);
	blocks.push(...emitColorVars(theme.dark));
	if (tw) blocks.push(...emitTailwindOverrides());
	blocks.push(`}`);
	if (!options.disableAutoTheme) {
		blocks.push(`@media (prefers-color-scheme: dark) {`);
		blocks.push(`${sel}:not([data-theme-mode]) {`);
		blocks.push(...globalDarkCSSOverride);
		blocks.push(...emitColorVars(theme.dark).map((l) => `${l}`));
		if (tw) blocks.push(...emitTailwindOverrides().map((l) => `${l}`));
		blocks.push(`}`);
		blocks.push(`}`);
	}
	return blocks.join("\n");
}
//#endregion
//#region ../../platform/theme-engine/src/defaults.ts
const DEFAULT_FONT_SIZES = {
	extraSmall: "0.75rem",
	small: "0.875rem",
	regular: "1rem",
	large: "1.125rem",
	extraLarge: "1.25rem",
	giant: "1.5rem"
};
const DEFAULT_FONT_FAMILIES = {
	header: "Inter",
	body: "Inter"
};
const DEFAULT_SPACING = "0.25rem";
const DEFAULT_RADII = {
	small: "0.25rem",
	medium: "0.5rem",
	large: "0.75rem",
	extraLarge: "1rem"
};
const DEFAULT_COLORS = {
	background: "#ffffff",
	foreground: "#1a1a1a",
	primary: "#3b82f6",
	secondary: "#6b7280",
	accent: "#10b981",
	muted: "#f3f4f6",
	destructive: "#ef4444",
	mutedForeground: "#6b7280"
};
const DEFAULT_THEME_ID = "default";
const DEFAULT_THEME_NAME = "Default Theme";
/**
* Build a fresh ThemeDefinition populated with all defaults.
* Returns a new object each call because Color instances are mutable — do not cache the result.
*/
function getDefaultThemeDefinition() {
	const bg = new Color(DEFAULT_COLORS.background);
	const fg = new Color(DEFAULT_COLORS.foreground);
	const primary = new Color(DEFAULT_COLORS.primary);
	const secondary = new Color(DEFAULT_COLORS.secondary);
	const accent = new Color(DEFAULT_COLORS.accent);
	const muted = new Color(DEFAULT_COLORS.muted);
	const destructive = new Color(DEFAULT_COLORS.destructive);
	const mutedFg = new Color(DEFAULT_COLORS.mutedForeground);
	const darkBg = new Color("#0a0a0a");
	const darkFg = new Color("#fafafa");
	const darkMuted = new Color("#171717");
	const darkMutedForeground = new Color("#dddddd");
	return {
		id: DEFAULT_THEME_ID,
		name: DEFAULT_THEME_NAME,
		light: {
			background: {
				base: bg,
				foreground: fg
			},
			foreground: {
				base: fg,
				foreground: bg
			},
			primary: {
				base: primary,
				foreground: getForegroundColor(fg, primary)
			},
			secondary: {
				base: secondary,
				foreground: getForegroundColor(fg, secondary)
			},
			accent: {
				base: accent,
				foreground: getForegroundColor(fg, accent)
			},
			muted: {
				base: muted,
				foreground: mutedFg
			},
			destructive: {
				base: destructive,
				foreground: getForegroundColor(fg, destructive)
			}
		},
		dark: {
			background: {
				base: darkBg,
				foreground: darkFg
			},
			foreground: {
				base: darkFg,
				foreground: darkBg
			},
			muted: {
				base: darkMuted,
				foreground: darkMutedForeground
			}
		},
		fontSizes: { ...DEFAULT_FONT_SIZES },
		fontFamilies: { ...DEFAULT_FONT_FAMILIES },
		spacing: DEFAULT_SPACING,
		radii: { ...DEFAULT_RADII }
	};
}
//#endregion
//#region ../../platform/theme-engine/src/serialisation.ts
function colorToPlain(color) {
	return {
		l: color.oklch.l ?? 0,
		c: color.oklch.c ?? 0,
		h: color.oklch.h ?? 0
	};
}
function plainToColor(plain) {
	return new Color("oklch", [
		plain.l,
		plain.c,
		plain.h
	]);
}
/**
* Serialise a ThemeDefinition (with Color objects) to a plain JSON payload
* suitable for backend storage.
*/
function serialiseTheme(def) {
	const light = {};
	for (const name of SEMANTIC_COLOR_NAMES) light[name] = {
		base: colorToPlain(def.light[name].base),
		foreground: colorToPlain(def.light[name].foreground)
	};
	const dark = {};
	for (const [name, value] of Object.entries(def.dark)) {
		if (!value) continue;
		dark[name] = {
			...value.base ? { base: colorToPlain(value.base) } : {},
			...value.foreground ? { foreground: colorToPlain(value.foreground) } : {}
		};
	}
	return {
		id: def.id,
		name: def.name,
		light,
		dark,
		fontSizes: { ...def.fontSizes },
		fontFamilies: { ...def.fontFamilies },
		spacing: def.spacing,
		radii: { ...def.radii },
		...def.syncWithBrandColors ? { syncWithBrandColors: true } : {}
	};
}
/**
* Deserialise a backend payload into a ThemeDefinition with Color objects.
* Accepts `Record<string, unknown>` because API data is untyped at the boundary.
* Falls back to default colors for any missing light-mode entries.
*/
function deserialiseTheme(payload) {
	const lightRaw = payload.light ?? {};
	const darkRaw = payload.dark ?? {};
	const defaults = getDefaultThemeDefinition();
	const light = {};
	for (const name of SEMANTIC_COLOR_NAMES) {
		const entry = lightRaw[name];
		if (entry) light[name] = {
			base: plainToColor(entry.base),
			foreground: plainToColor(entry.foreground)
		};
		else {
			console.warn(`[theme] deserialiseTheme: missing light color "${name}", using default`);
			light[name] = defaults.light[name];
		}
	}
	const dark = {};
	for (const [name, value] of Object.entries(darkRaw)) {
		if (!value) continue;
		dark[name] = {
			...value.base ? { base: plainToColor(value.base) } : {},
			...value.foreground ? { foreground: plainToColor(value.foreground) } : {}
		};
	}
	return {
		id: payload.id,
		name: payload.name,
		light,
		dark,
		fontSizes: payload.fontSizes ?? DEFAULT_FONT_SIZES,
		fontFamilies: payload.fontFamilies ?? DEFAULT_FONT_FAMILIES,
		spacing: payload.spacing ?? "0.25rem",
		radii: payload.radii ?? DEFAULT_RADII,
		...payload.syncWithBrandColors === true ? { syncWithBrandColors: true } : {}
	};
}
//#endregion
//#region ../../platform/theme-engine/src/transforms.ts
/**
* Check if a theme config uses the new structured format (has a `light` key
* that is an object) vs the legacy flat format.
*/
function isNewThemeFormat(config) {
	return config.light != null && typeof config.light === "object";
}
/**
* Convert a legacy flat config to a ThemeDefinition.
* Legacy format: { base: "#fff", text: "#000", primary: "oklch(0.6 0.2 250)", ... }
*/
function legacyConfigToDefinition(id, name, config) {
	const bg = parseColor(config.base ?? config.background ?? DEFAULT_COLORS.background);
	const fg = parseColor(config.text ?? config.foreground ?? DEFAULT_COLORS.foreground);
	const primary = parseColor(config.primary ?? DEFAULT_COLORS.primary);
	const secondary = parseColor(config.secondary ?? DEFAULT_COLORS.secondary);
	const accent = parseColor(config.accent ?? DEFAULT_COLORS.accent);
	const muted = parseColor(config.muted ?? DEFAULT_COLORS.muted);
	const destructive = parseColor(config.destructive ?? DEFAULT_COLORS.destructive);
	const mutedFg = parseColor(config.mutedForeground ?? DEFAULT_COLORS.mutedForeground);
	return {
		id: String(id),
		name,
		light: {
			background: {
				base: bg,
				foreground: fg
			},
			foreground: {
				base: fg,
				foreground: bg
			},
			primary: {
				base: primary,
				foreground: getForegroundColor(fg, primary)
			},
			secondary: {
				base: secondary,
				foreground: getForegroundColor(fg, secondary)
			},
			accent: {
				base: accent,
				foreground: getForegroundColor(fg, accent)
			},
			muted: {
				base: muted,
				foreground: mutedFg
			},
			destructive: {
				base: destructive,
				foreground: getForegroundColor(fg, destructive)
			}
		},
		dark: {},
		fontSizes: {
			extraSmall: config.extraSmall ?? DEFAULT_FONT_SIZES.extraSmall,
			small: config.small ?? DEFAULT_FONT_SIZES.small,
			regular: config.regular ?? DEFAULT_FONT_SIZES.regular,
			large: config.large ?? DEFAULT_FONT_SIZES.large,
			extraLarge: config.extraLarge ?? DEFAULT_FONT_SIZES.extraLarge,
			giant: config.giant ?? DEFAULT_FONT_SIZES.giant
		},
		fontFamilies: {
			header: config.headerFont ?? DEFAULT_FONT_FAMILIES.header,
			body: config.bodyFont ?? DEFAULT_FONT_FAMILIES.body
		},
		spacing: config.globalSpacing ?? "0.25rem",
		radii: {
			small: config.radiusSmall ?? DEFAULT_RADII.small,
			medium: config.radiusMedium ?? DEFAULT_RADII.medium,
			large: config.radiusLarge ?? DEFAULT_RADII.large,
			extraLarge: config.radiusExtraLarge ?? DEFAULT_RADII.extraLarge
		}
	};
}
/**
* Build a ThemeDefinition from a single API theme object.
* Handles both new structured format and legacy flat format.
*/
function buildThemeDefinition(theme) {
	const config = theme.config ?? {};
	if (isNewThemeFormat(config)) return deserialiseTheme({
		...config,
		id: String(theme.id),
		name: theme.name ?? "Untitled Theme"
	});
	return legacyConfigToDefinition(theme.id, theme.name ?? "Untitled Theme", config);
}
/**
* Transform raw API themes to ThemeDefinition[].
* Catches and logs errors per theme (graceful degradation).
*/
function transformThemes(themes) {
	return themes.flatMap((theme) => {
		try {
			return [buildThemeDefinition(theme)];
		} catch (error) {
			console.error(`[theme] Failed to build theme id=${theme.id}:`, error);
			return [];
		}
	});
}
/**
* Get the active theme ID from a list of raw API themes.
* Falls back to the first theme if none is marked active.
*/
function getActiveThemeId(themes) {
	const active = themes.find((t) => t.active) ?? themes[0];
	return active ? String(active.id) : void 0;
}
//#endregion
//#region ../../platform/theme-engine/src/theme-applicator.ts
const STYLE_PREFIX = "theme-style-";
const FONT_LINK_PREFIX = "theme-font-";
const SYSTEM_FONTS = new Set([
	"sans-serif",
	"serif",
	"monospace",
	"cursive",
	"fantasy",
	"system-ui",
	"ui-sans-serif",
	"ui-serif",
	"ui-monospace"
]);
/** Build a Google Fonts CSS2 URL for a given font family with all weights. */
function buildGoogleFontUrl(family) {
	return `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family).replace(/%20/g, "+")}:wght@100;200;300;400;500;600;700;800;900&display=swap`;
}
/** Check if a font family value needs to be loaded (i.e. is not a CSS var or system font). */
function isLoadableFont(value) {
	if (!value) return false;
	if (value.startsWith("var(")) return false;
	return !SYSTEM_FONTS.has(value.toLowerCase());
}
/** Deterministic link element ID for a font family. */
function fontLinkId(family) {
	return `${FONT_LINK_PREFIX}${family.replace(/\s+/g, "-").toLowerCase()}`;
}
/**
* Inject or update `<link>` elements for Google Fonts used by the theme.
* Removes links for fonts that are no longer referenced.
*/
function loadThemeFonts(theme) {
	if (typeof document === "undefined") return;
	const fontsToLoad = /* @__PURE__ */ new Set();
	for (const key of FONT_FAMILY_KEYS) {
		const value = theme.fontFamilies[key];
		if (isLoadableFont(value)) fontsToLoad.add(value);
	}
	document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((link) => {
		const owners = link.getAttribute("data-font-theme-ids")?.split(",") ?? [];
		if (!owners.includes(theme.id)) return;
		const fontName = link.getAttribute("data-font-family");
		if (fontName && !fontsToLoad.has(fontName)) {
			const remaining = owners.filter((id) => id !== theme.id);
			if (remaining.length === 0) link.remove();
			else link.setAttribute("data-font-theme-ids", remaining.join(","));
		}
	});
	for (const family of fontsToLoad) {
		const id = fontLinkId(family);
		const existing = document.getElementById(id);
		if (existing) {
			const owners = existing.getAttribute("data-font-theme-ids")?.split(",") ?? [];
			if (!owners.includes(theme.id)) existing.setAttribute("data-font-theme-ids", [...owners, theme.id].join(","));
		} else {
			const link = document.createElement("link");
			link.id = id;
			link.rel = "stylesheet";
			link.href = buildGoogleFontUrl(family);
			link.setAttribute("data-font-family", family);
			link.setAttribute("data-font-theme-ids", theme.id);
			document.head.appendChild(link);
		}
	}
}
/** Remove all font `<link>` elements injected by the theme system. */
function removeAllFontLinks() {
	if (typeof document === "undefined") return;
	document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((el) => el.remove());
}
/**
* Inject or update a `<style>` element in `<head>` for the given theme.
* The element ID is deterministic (`theme-style-{themeId}`) so repeated calls
* for the same theme are idempotent — the existing element is updated in place.
* Also loads Google Fonts referenced by the theme's font families.
* No-op when `document` is unavailable (SSR).
*/
function applyTheme(theme, options) {
	if (typeof document === "undefined") return;
	try {
		loadThemeFonts(theme);
		const styleId = `${STYLE_PREFIX}${theme.id}`;
		let el = document.getElementById(styleId);
		if (!el) {
			el = document.createElement("style");
			el.id = styleId;
			document.head.appendChild(el);
		}
		el.textContent = generateThemeCSS(theme, options);
	} catch (error) {
		console.error(`[theme] applyTheme failed for "${theme.id}":`, error);
	}
}
/** Remove an injected theme stylesheet and clean up font link ownership. No-op during SSR. */
function removeTheme(themeId) {
	if (typeof document === "undefined") return;
	document.getElementById(`${STYLE_PREFIX}${themeId}`)?.remove();
	document.querySelectorAll(`link[id^="${FONT_LINK_PREFIX}"]`).forEach((link) => {
		const remaining = (link.getAttribute("data-font-theme-ids")?.split(",") ?? []).filter((id) => id !== themeId);
		if (remaining.length === 0) link.remove();
		else link.setAttribute("data-font-theme-ids", remaining.join(","));
	});
}
/** Remove all injected theme stylesheets and font links. No-op during SSR. */
function removeAllThemes() {
	if (typeof document === "undefined") return;
	document.querySelectorAll(`style[id^="${STYLE_PREFIX}"]`).forEach((el) => el.remove());
	removeAllFontLinks();
}
//#endregion
//#region src/providers/FluidThemeProvider.tsx
/**
* Theme Provider for Fluid SDK
* Handles CSS variable injection using portal-core's theme engine.
* Accepts ThemeDefinition objects and resolves them into CSS via generateThemeCSS.
*/
const ThemeContext = createContext(null);
/**
* Apply a theme to the DOM using the shared portal-core pipeline.
* Also sets data attributes on the container for CSS selector targeting.
*/
function applyThemeToDOM(theme, mode, container) {
	const target = container ?? document.documentElement;
	applyTheme(resolveTheme(theme));
	target.dataset.theme = theme.id;
	if (mode) target.dataset.themeMode = mode;
	else delete target.dataset.themeMode;
}
function FluidThemeProvider({ children, initialTheme, container }) {
	const [currentTheme, setCurrentTheme] = useState(initialTheme ?? null);
	const [mode, setMode] = useState(void 0);
	useEffect(() => {
		if (currentTheme) applyThemeToDOM(currentTheme, mode, container ?? null);
		return () => {
			if (currentTheme) removeTheme(currentTheme.id);
		};
	}, [
		currentTheme,
		mode,
		container
	]);
	const setTheme = useCallback((theme) => {
		setCurrentTheme(theme);
	}, []);
	const setThemeMode = useCallback((newMode) => {
		setMode(newMode);
	}, []);
	const value = useMemo(() => ({
		currentTheme,
		setTheme,
		setThemeMode,
		mode
	}), [
		currentTheme,
		setTheme,
		setThemeMode,
		mode
	]);
	return /* @__PURE__ */ jsx(ThemeContext.Provider, {
		value,
		children
	});
}
/**
* Hook to access theme context
* Must be used within a FluidThemeProvider
*/
function useThemeContext() {
	const context = use(ThemeContext);
	if (!context) throw new Error("useThemeContext must be used within a FluidThemeProvider");
	return context;
}
//#endregion
export { useAppDefinitionApi as A, parseColor as C, RADIUS_KEYS as D, FONT_SIZE_KEYS as E, SEMANTIC_COLOR_NAMES as O, mergeDarkOverrides as S, FONT_FAMILY_KEYS as T, DEFAULT_THEME_NAME as _, removeTheme as a, deriveDarkVariant as b, transformThemes as c, DEFAULT_COLORS as d, DEFAULT_FONT_FAMILIES as f, DEFAULT_THEME_ID as g, DEFAULT_SPACING as h, removeAllThemes as i, AppDefinitionApiProvider as k, deserialiseTheme as l, DEFAULT_RADII as m, useThemeContext as n, buildThemeDefinition as o, DEFAULT_FONT_SIZES as p, applyTheme as r, getActiveThemeId as s, FluidThemeProvider as t, serialiseTheme as u, getDefaultThemeDefinition as v, resolveTheme as w, getForegroundColor as x, generateThemeCSS as y };

//# sourceMappingURL=FluidThemeProvider-D_O4NV2b.mjs.map