import { a as __exportAll } from "./runner-K8XK101B.mjs";
import { n as getI18nConfig } from "./config-CVssduLe.mjs";
import { r as RevisionRepository, t as ContentRepository } from "./content-DhgEth4k.mjs";
import { t as MediaRepository } from "./media-BrXIaMFN.mjs";
import { t as TaxonomyRepository } from "./taxonomy-BxGxZZdu.mjs";
import { t as withTransaction } from "./transaction-qfqpPVpu.mjs";
import { t as RedirectRepository } from "./redirect-IyfNirgS.mjs";
import { t as BylineRepository } from "./byline-Bm8ZgUP9.mjs";
import { a as markContentMediaUsageCollectionStaleSafely } from "./content-refresh-CA4uKwzH.mjs";
import { t as FTSManager } from "./fts-manager-BbFaF_V8.mjs";
import { n as SchemaRegistry } from "./registry-B65mUtPa.mjs";
import { s as setSiteSettings } from "./settings-zhmdKyJQ.mjs";
import { a as validateExternalUrl, r as ssrfSafeFetch } from "./ssrf-1g7usGBg.mjs";
import { t as validateSeed } from "./validate-CWSnKVIJ.mjs";
import { ulid } from "ulidx";
import { imageSize } from "image-size";
import mime from "mime/lite";

//#region src/seed/apply.ts
/**
* Seed engine - applies seed files to database
*
* This is the core implementation that bootstraps an EmDash site from a seed file.
* Apply order is critical for foreign keys and references.
*/
var apply_exports = /* @__PURE__ */ __exportAll({ applySeed: () => applySeed });
const FILE_EXTENSION_PATTERN = /\.([a-z0-9]+)(?:\?|$)/i;
/** Pattern to remove file extensions */
const EXTENSION_PATTERN = /\.[^.]+$/;
/** Pattern to remove query parameters */
const QUERY_PARAM_PATTERN = /\?.*$/;
/** Pattern to remove non-alphanumeric characters (except dash and underscore) */
const SANITIZE_PATTERN = /[^a-zA-Z0-9_-]/g;
/** Pattern to collapse multiple hyphens */
const MULTIPLE_HYPHENS_PATTERN = /-+/g;
/**
* Apply a seed file to the database
*
* This function is idempotent - safe to run multiple times.
*
* @param db - Kysely database instance
* @param seed - Seed file to apply
* @param options - Application options
* @returns Result summary
*/
async function applySeed(db, seed, options = {}) {
	const validation = validateSeed(seed);
	if (!validation.valid) throw new Error(`Invalid seed file:\n${validation.errors.join("\n")}`);
	const { includeContent = false, storage, skipMediaDownload = false, onConflict = "skip" } = options;
	const result = {
		collections: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		fields: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		taxonomies: {
			created: 0,
			terms: 0
		},
		bylines: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		menus: {
			created: 0,
			items: 0
		},
		redirects: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		widgetAreas: {
			created: 0,
			widgets: 0
		},
		sections: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		settings: { applied: 0 },
		content: {
			created: 0,
			skipped: 0,
			updated: 0
		},
		media: {
			created: 0,
			skipped: 0
		}
	};
	const mediaContext = {
		db,
		storage: storage ?? null,
		skipMediaDownload,
		mediaCache: /* @__PURE__ */ new Map()
	};
	const seedIdMap = /* @__PURE__ */ new Map();
	const seedBylineIdMap = /* @__PURE__ */ new Map();
	const staleMarkedContentCollections = /* @__PURE__ */ new Set();
	const failedStaleContentCollections = /* @__PURE__ */ new Set();
	const defaultLocale = getI18nConfig()?.defaultLocale ?? seed.defaultLocale ?? "en";
	const markSeedContentCollectionStale = async (collectionSlug) => {
		if (staleMarkedContentCollections.has(collectionSlug)) return;
		if (await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_STALE")) {
			staleMarkedContentCollections.add(collectionSlug);
			failedStaleContentCollections.delete(collectionSlug);
		} else failedStaleContentCollections.add(collectionSlug);
	};
	const retryFailedSeedContentStaleMarks = async () => {
		for (const collectionSlug of failedStaleContentCollections) if (await markContentMediaUsageCollectionStaleSafely(db, collectionSlug, "CONTENT_USAGE_STALE")) {
			staleMarkedContentCollections.add(collectionSlug);
			failedStaleContentCollections.delete(collectionSlug);
		}
	};
	if (seed.settings) {
		await setSiteSettings(seed.settings, db);
		result.settings.applied = Object.keys(seed.settings).length;
	}
	if (seed.collections) {
		const registry = new SchemaRegistry(db);
		for (const collection of seed.collections) {
			if (await registry.getCollection(collection.slug)) {
				if (onConflict === "error") throw new Error(`Conflict: collection "${collection.slug}" already exists`);
				if (onConflict === "update") {
					await registry.updateCollection(collection.slug, {
						label: collection.label,
						labelSingular: collection.labelSingular,
						description: collection.description,
						icon: collection.icon,
						supports: collection.supports || [],
						urlPattern: collection.urlPattern,
						commentsEnabled: collection.commentsEnabled
					});
					result.collections.updated++;
					for (const field of collection.fields) if (await registry.getField(collection.slug, field.slug)) {
						await registry.updateField(collection.slug, field.slug, {
							label: field.label,
							type: field.type,
							required: field.required || false,
							unique: field.unique || false,
							searchable: field.searchable || false,
							defaultValue: field.defaultValue,
							validation: field.validation,
							widget: field.widget,
							options: field.options
						});
						result.fields.updated++;
					} else {
						await registry.createField(collection.slug, {
							slug: field.slug,
							label: field.label,
							type: field.type,
							required: field.required || false,
							unique: field.unique || false,
							searchable: field.searchable || false,
							defaultValue: field.defaultValue,
							validation: field.validation,
							widget: field.widget,
							options: field.options
						});
						result.fields.created++;
					}
					continue;
				}
				result.collections.skipped++;
				result.fields.skipped += collection.fields.length;
				continue;
			}
			await registry.createCollection({
				slug: collection.slug,
				label: collection.label,
				labelSingular: collection.labelSingular,
				description: collection.description,
				icon: collection.icon,
				supports: collection.supports || [],
				source: "seed",
				urlPattern: collection.urlPattern,
				commentsEnabled: collection.commentsEnabled
			});
			result.collections.created++;
			for (const field of collection.fields) {
				await registry.createField(collection.slug, {
					slug: field.slug,
					label: field.label,
					type: field.type,
					required: field.required || false,
					unique: field.unique || false,
					searchable: field.searchable || false,
					defaultValue: field.defaultValue,
					validation: field.validation,
					widget: field.widget,
					options: field.options
				});
				result.fields.created++;
			}
		}
	}
	if (seed.taxonomies) {
		const defSeedIdMap = /* @__PURE__ */ new Map();
		const termSeedIdMap = /* @__PURE__ */ new Map();
		for (const taxonomy of seed.taxonomies) {
			const defLocale = taxonomy.locale ?? defaultLocale;
			const existingDef = await db.selectFrom("_emdash_taxonomy_defs").selectAll().where("name", "=", taxonomy.name).where("locale", "=", defLocale).executeTakeFirst();
			let defId;
			let defTranslationGroup;
			if (existingDef) {
				defId = existingDef.id;
				defTranslationGroup = existingDef.translation_group ?? existingDef.id;
				if (onConflict === "error") throw new Error(`Conflict: taxonomy "${taxonomy.name}" (${defLocale}) already exists`);
				if (onConflict === "update") await db.updateTable("_emdash_taxonomy_defs").set({
					label: taxonomy.label,
					label_singular: taxonomy.labelSingular ?? null,
					hierarchical: taxonomy.hierarchical ? 1 : 0,
					collections: JSON.stringify(taxonomy.collections)
				}).where("id", "=", existingDef.id).execute();
			} else {
				defId = ulid();
				defTranslationGroup = defId;
				if (taxonomy.translationOf) {
					const source = defSeedIdMap.get(taxonomy.translationOf);
					if (source) defTranslationGroup = source.translationGroup;
					else console.warn(`taxonomy "${taxonomy.name}" (${defLocale}): translationOf "${taxonomy.translationOf}" not found yet; minting a fresh group.`);
				}
				await db.insertInto("_emdash_taxonomy_defs").values({
					id: defId,
					name: taxonomy.name,
					label: taxonomy.label,
					label_singular: taxonomy.labelSingular ?? null,
					hierarchical: taxonomy.hierarchical ? 1 : 0,
					collections: JSON.stringify(taxonomy.collections),
					locale: defLocale,
					translation_group: defTranslationGroup
				}).execute();
				result.taxonomies.created++;
			}
			if (taxonomy.id) defSeedIdMap.set(taxonomy.id, {
				id: defId,
				translationGroup: defTranslationGroup
			});
			if (includeContent && taxonomy.terms && taxonomy.terms.length > 0) {
				const termRepo = new TaxonomyRepository(db);
				if (taxonomy.hierarchical) await applyHierarchicalTerms(termRepo, taxonomy.name, defLocale, taxonomy.terms, termSeedIdMap, result, onConflict);
				else for (const term of taxonomy.terms) {
					const termLocale = term.locale ?? defLocale;
					const existing = await termRepo.findBySlug(taxonomy.name, term.slug, termLocale);
					if (existing) {
						if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomy.name}" (${termLocale}) already exists`);
						if (onConflict === "update") {
							await termRepo.update(existing.id, {
								label: term.label,
								data: term.description ? { description: term.description } : {}
							});
							result.taxonomies.terms++;
						}
						if (term.id) termSeedIdMap.set(term.id, existing.id);
					} else {
						const translationOf = term.translationOf ? termSeedIdMap.get(term.translationOf) : void 0;
						const created = await termRepo.create({
							name: taxonomy.name,
							slug: term.slug,
							label: term.label,
							data: term.description ? { description: term.description } : void 0,
							locale: termLocale,
							translationOf
						});
						if (term.id) termSeedIdMap.set(term.id, created.id);
						result.taxonomies.terms++;
					}
				}
			}
		}
		const { invalidateTaxonomyDefsCache } = await import("./taxonomies-Cnb4F-Pb.mjs").then((n) => n.d);
		invalidateTaxonomyDefsCache();
	}
	if (includeContent && seed.bylines) {
		const bylineRepo = new BylineRepository(db);
		for (const byline of seed.bylines) {
			const existing = await bylineRepo.findBySlug(byline.slug);
			if (existing) {
				if (onConflict === "error") throw new Error(`Conflict: byline "${byline.slug}" already exists`);
				if (onConflict === "update") {
					const avatar = byline.avatar ? await resolveSeedBylineAvatar(db, byline.avatar) : null;
					try {
						if (!await bylineRepo.update(existing.id, {
							displayName: byline.displayName,
							bio: byline.bio ?? null,
							websiteUrl: byline.websiteUrl ?? null,
							isGuest: byline.isGuest,
							...avatar ? { avatarMediaId: avatar.id } : {}
						})) throw new Error(`Byline "${byline.slug}" disappeared during update`);
					} catch (error) {
						if (avatar?.created) await deleteMediaRow(db, avatar.id);
						throw error;
					}
					seedBylineIdMap.set(byline.id, existing.id);
					result.bylines.updated++;
					if (avatar?.created) result.media.created++;
					continue;
				}
				seedBylineIdMap.set(byline.id, existing.id);
				result.bylines.skipped++;
				continue;
			}
			const avatar = byline.avatar ? await resolveSeedBylineAvatar(db, byline.avatar) : null;
			let createdId;
			try {
				createdId = (await bylineRepo.create({
					slug: byline.slug,
					displayName: byline.displayName,
					bio: byline.bio ?? null,
					websiteUrl: byline.websiteUrl ?? null,
					isGuest: byline.isGuest,
					avatarMediaId: avatar?.id ?? null
				})).id;
			} catch (error) {
				if (avatar?.created) await deleteMediaRow(db, avatar.id);
				throw error;
			}
			seedBylineIdMap.set(byline.id, createdId);
			result.bylines.created++;
			if (avatar?.created) result.media.created++;
		}
	}
	if (includeContent && seed.content) {
		const contentRepo = new ContentRepository(db);
		try {
			for (const [collectionSlug, entries] of Object.entries(seed.content)) for (const entry of entries) {
				const entryLocale = entry.locale ?? defaultLocale;
				const existing = await contentRepo.findBySlug(collectionSlug, entry.slug, entryLocale);
				if (existing) {
					if (onConflict === "error") throw new Error(`Conflict: content "${entry.slug}" in "${collectionSlug}" already exists`);
					if (onConflict === "update") {
						const resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);
						const status = entry.status || "published";
						let contentMutated = false;
						try {
							await withTransaction(db, async (trx) => {
								const trxContentRepo = new ContentRepository(trx);
								const trxBylineRepo = new BylineRepository(trx);
								const trxRevisionRepo = new RevisionRepository(trx);
								await trxContentRepo.update(collectionSlug, existing.id, {
									status,
									data: resolvedData
								});
								contentMutated = true;
								await applyContentBylines(trxBylineRepo, collectionSlug, existing.id, entry, seedBylineIdMap, true);
								await applyContentTaxonomies(trx, collectionSlug, existing.id, entry, true);
								if (status === "published") {
									const draft = await trxRevisionRepo.create({
										collection: collectionSlug,
										entryId: existing.id,
										data: resolvedData
									});
									await trxContentRepo.setDraftRevision(collectionSlug, existing.id, draft.id);
									await trxContentRepo.publish(collectionSlug, existing.id);
								}
							});
						} catch (error) {
							if (contentMutated) await markSeedContentCollectionStale(collectionSlug);
							throw error;
						}
						seedIdMap.set(entry.id, existing.id);
						result.content.updated++;
						await markSeedContentCollectionStale(collectionSlug);
						continue;
					}
					result.content.skipped++;
					seedIdMap.set(entry.id, existing.id);
					continue;
				}
				const resolvedData = await resolveReferences(entry.data, seedIdMap, mediaContext, result);
				let translationOf;
				if (entry.translationOf) {
					const sourceId = seedIdMap.get(entry.translationOf);
					if (!sourceId) console.warn(`content.${collectionSlug}: translationOf "${entry.translationOf}" not found (not yet created or missing). Skipping translation link.`);
					else translationOf = sourceId;
				}
				const status = entry.status || "published";
				let contentMutated = false;
				let created;
				try {
					created = await withTransaction(db, async (trx) => {
						const trxContentRepo = new ContentRepository(trx);
						const trxBylineRepo = new BylineRepository(trx);
						const item = await trxContentRepo.create({
							type: collectionSlug,
							slug: entry.slug,
							status,
							data: resolvedData,
							locale: entryLocale,
							translationOf,
							publishedAt: status === "published" ? (/* @__PURE__ */ new Date()).toISOString() : null
						});
						contentMutated = true;
						await applyContentBylines(trxBylineRepo, collectionSlug, item.id, entry, seedBylineIdMap);
						await applyContentTaxonomies(trx, collectionSlug, item.id, entry, false);
						if (status === "published") await trxContentRepo.publish(collectionSlug, item.id);
						return item;
					});
				} catch (error) {
					if (contentMutated) await markSeedContentCollectionStale(collectionSlug);
					throw error;
				}
				seedIdMap.set(entry.id, created.id);
				result.content.created++;
				await markSeedContentCollectionStale(collectionSlug);
			}
		} finally {
			await retryFailedSeedContentStaleMarks();
		}
	}
	if (seed.menus) {
		const menuSeedIdMap = /* @__PURE__ */ new Map();
		const itemSeedIdMap = /* @__PURE__ */ new Map();
		for (const menu of seed.menus) {
			const locale = menu.locale ?? defaultLocale;
			const existingMenu = await db.selectFrom("_emdash_menus").selectAll().where("name", "=", menu.name).where("locale", "=", locale).executeTakeFirst();
			let menuId;
			let translationGroup;
			if (existingMenu) {
				menuId = existingMenu.id;
				translationGroup = existingMenu.translation_group ?? existingMenu.id;
				await db.deleteFrom("_emdash_menu_items").where("menu_id", "=", menuId).execute();
			} else {
				menuId = ulid();
				translationGroup = menuId;
				if (menu.translationOf) {
					const source = menuSeedIdMap.get(menu.translationOf);
					if (source) translationGroup = source.translationGroup;
					else console.warn(`menu "${menu.name}" (${locale}): translationOf "${menu.translationOf}" not found yet; minting a fresh group.`);
				}
				await db.insertInto("_emdash_menus").values({
					id: menuId,
					name: menu.name,
					label: menu.label,
					created_at: (/* @__PURE__ */ new Date()).toISOString(),
					updated_at: (/* @__PURE__ */ new Date()).toISOString(),
					locale,
					translation_group: translationGroup
				}).execute();
				result.menus.created++;
			}
			if (menu.id) menuSeedIdMap.set(menu.id, {
				id: menuId,
				translationGroup
			});
			const itemCount = await applyMenuItems(db, menuId, locale, menu.items, null, 0, seedIdMap, itemSeedIdMap);
			result.menus.items += itemCount;
		}
	}
	if (seed.redirects) {
		const redirectRepo = new RedirectRepository(db);
		for (const redirect of seed.redirects) {
			const existing = await redirectRepo.findBySource(redirect.source);
			if (existing) {
				if (onConflict === "error") throw new Error(`Conflict: redirect "${redirect.source}" already exists`);
				if (onConflict === "update") {
					await redirectRepo.update(existing.id, {
						destination: redirect.destination,
						type: redirect.type,
						enabled: redirect.enabled,
						groupName: redirect.groupName
					});
					result.redirects.updated++;
					continue;
				}
				result.redirects.skipped++;
				continue;
			}
			await redirectRepo.create({
				source: redirect.source,
				destination: redirect.destination,
				type: redirect.type,
				enabled: redirect.enabled,
				groupName: redirect.groupName
			});
			result.redirects.created++;
		}
	}
	if (seed.widgetAreas) for (const area of seed.widgetAreas) {
		const existingArea = await db.selectFrom("_emdash_widget_areas").selectAll().where("name", "=", area.name).executeTakeFirst();
		let areaId;
		if (existingArea) {
			areaId = existingArea.id;
			await db.deleteFrom("_emdash_widgets").where("area_id", "=", areaId).execute();
		} else {
			areaId = ulid();
			await db.insertInto("_emdash_widget_areas").values({
				id: areaId,
				name: area.name,
				label: area.label,
				description: area.description ?? null
			}).execute();
			result.widgetAreas.created++;
		}
		for (let i = 0; i < area.widgets.length; i++) {
			const widget = area.widgets[i];
			await applyWidget(db, areaId, widget, i);
			result.widgetAreas.widgets++;
		}
	}
	if (seed.sections) for (const section of seed.sections) {
		const existing = await db.selectFrom("_emdash_sections").select("id").where("slug", "=", section.slug).executeTakeFirst();
		if (existing) {
			if (onConflict === "error") throw new Error(`Conflict: section "${section.slug}" already exists`);
			if (onConflict === "update") {
				await db.updateTable("_emdash_sections").set({
					title: section.title,
					description: section.description ?? null,
					keywords: section.keywords ? JSON.stringify(section.keywords) : null,
					content: JSON.stringify(section.content),
					source: section.source || "theme",
					updated_at: (/* @__PURE__ */ new Date()).toISOString()
				}).where("id", "=", existing.id).execute();
				result.sections.updated++;
				continue;
			}
			result.sections.skipped++;
			continue;
		}
		const id = ulid();
		const now = (/* @__PURE__ */ new Date()).toISOString();
		await db.insertInto("_emdash_sections").values({
			id,
			slug: section.slug,
			title: section.title,
			description: section.description ?? null,
			keywords: section.keywords ? JSON.stringify(section.keywords) : null,
			content: JSON.stringify(section.content),
			preview_media_id: null,
			source: section.source || "theme",
			theme_id: section.source === "theme" ? section.slug : null,
			created_at: now,
			updated_at: now
		}).execute();
		result.sections.created++;
	}
	if (seed.collections) {
		const ftsManager = new FTSManager(db);
		for (const collection of seed.collections) if (collection.supports?.includes("search")) {
			if ((await ftsManager.getSearchableFields(collection.slug)).length > 0) try {
				await ftsManager.enableSearch(collection.slug);
			} catch (err) {
				console.warn(`Failed to enable search for ${collection.slug}:`, err);
			}
		}
	}
	const { invalidateBylineCache } = await import("./bylines-Woq87129.mjs").then((n) => n.t);
	const { invalidateRedirectCache } = await import("./cache-CEBAZjir.mjs").then((n) => n.t);
	const { invalidateUrlPatternCache } = await import("./query-ByGq6ver.mjs").then((n) => n.o);
	invalidateBylineCache();
	invalidateRedirectCache();
	invalidateUrlPatternCache();
	return result;
}
/**
* Apply hierarchical taxonomy terms (parents before children)
*/
async function applyHierarchicalTerms(termRepo, taxonomyName, defLocale, terms, termSeedIdMap, result, onConflict = "skip") {
	const slugToId = /* @__PURE__ */ new Map();
	let remaining = [...terms];
	let maxPasses = 10;
	while (remaining.length > 0 && maxPasses > 0) {
		const processedThisPass = [];
		for (const term of remaining) {
			const termLocale = term.locale ?? defLocale;
			const parentReady = !term.parent || slugToId.has(`${termLocale}::${term.parent}`);
			const translationReady = !term.translationOf || termSeedIdMap.has(term.translationOf);
			if (!parentReady || !translationReady) continue;
			const parentId = term.parent ? slugToId.get(`${termLocale}::${term.parent}`) : void 0;
			const translationOf = term.translationOf ? termSeedIdMap.get(term.translationOf) : void 0;
			const existing = await termRepo.findBySlug(taxonomyName, term.slug, termLocale);
			if (existing) {
				if (onConflict === "error") throw new Error(`Conflict: taxonomy term "${term.slug}" in "${taxonomyName}" (${termLocale}) already exists`);
				if (onConflict === "update") {
					await termRepo.update(existing.id, {
						label: term.label,
						parentId,
						data: term.description ? { description: term.description } : {}
					});
					result.taxonomies.terms++;
				}
				slugToId.set(`${termLocale}::${term.slug}`, existing.id);
				if (term.id) termSeedIdMap.set(term.id, existing.id);
			} else {
				const created = await termRepo.create({
					name: taxonomyName,
					slug: term.slug,
					label: term.label,
					parentId,
					data: term.description ? { description: term.description } : void 0,
					locale: termLocale,
					translationOf
				});
				slugToId.set(`${termLocale}::${term.slug}`, created.id);
				if (term.id) termSeedIdMap.set(term.id, created.id);
				result.taxonomies.terms++;
			}
			processedThisPass.push(term.slug + "::" + termLocale);
		}
		remaining = remaining.filter((t) => !processedThisPass.includes(t.slug + "::" + (t.locale ?? defLocale)));
		maxPasses--;
	}
	if (remaining.length > 0) console.warn(`Could not process ${remaining.length} terms due to missing parents/translations`);
}
/**
* Apply byline credits to a content entry.
* In update mode, clears existing credits even if the seed has none.
*/
async function applyContentBylines(bylineRepo, collectionSlug, contentId, entry, seedBylineIdMap, isUpdate = false) {
	if (!entry.bylines || entry.bylines.length === 0) {
		if (isUpdate) await bylineRepo.setContentBylines(collectionSlug, contentId, []);
		return;
	}
	const credits = entry.bylines.map((credit) => {
		const bylineId = seedBylineIdMap.get(credit.byline);
		if (!bylineId) return null;
		return {
			bylineId,
			roleLabel: credit.roleLabel ?? null
		};
	}).filter((credit) => Boolean(credit));
	if (credits.length !== entry.bylines.length) console.warn(`content.${collectionSlug}.${entry.slug}: one or more byline refs could not be resolved`);
	if (credits.length > 0 || isUpdate) await bylineRepo.setContentBylines(collectionSlug, contentId, credits);
}
/**
* Apply taxonomy term assignments to a content entry.
* In update mode, clears existing assignments before re-attaching.
*/
async function applyContentTaxonomies(db, collectionSlug, contentId, entry, isUpdate) {
	if (isUpdate) await db.deleteFrom("content_taxonomies").where("collection", "=", collectionSlug).where("entry_id", "=", contentId).execute();
	if (!entry.taxonomies) {
		if (isUpdate) {
			const { invalidateTermCache } = await import("./taxonomies-Cnb4F-Pb.mjs").then((n) => n.d);
			invalidateTermCache();
		}
		return;
	}
	for (const [taxonomyName, termSlugs] of Object.entries(entry.taxonomies)) {
		const termRepo = new TaxonomyRepository(db);
		for (const termSlug of termSlugs) {
			const term = await termRepo.findBySlug(taxonomyName, termSlug);
			if (term) await termRepo.attachToEntry(collectionSlug, contentId, term.id);
		}
	}
	const { invalidateTermCache } = await import("./taxonomies-Cnb4F-Pb.mjs").then((n) => n.d);
	invalidateTermCache();
}
/**
* Apply menu items recursively.
*
* When a `SeedMenuItem` carries `id`/`translationOf`, the import resolves the
* source item's `translation_group` so cross-locale "same nav entry" links
* survive export → apply. Items without `translationOf` get a fresh group
* (= their own id).
*/
async function applyMenuItems(db, menuId, locale, items, parentId, startOrder, seedIdMap, itemSeedIdMap) {
	let count = 0;
	let order = startOrder;
	for (const item of items) {
		const itemId = ulid();
		const itemLocale = item.locale ?? locale;
		let referenceId = null;
		let referenceCollection = null;
		if (item.type === "page" || item.type === "post") {
			if (item.ref && seedIdMap.has(item.ref)) {
				referenceId = seedIdMap.get(item.ref);
				referenceCollection = item.collection || `${item.type}s`;
			}
		}
		let translationGroup = itemId;
		if (item.translationOf) {
			const source = itemSeedIdMap.get(item.translationOf);
			if (source) translationGroup = source.translationGroup;
			else console.warn(`menu item "${item.label ?? item.url ?? item.ref ?? "(unlabeled)"}" (${itemLocale}): translationOf "${item.translationOf}" not found yet; minting a fresh group.`);
		}
		await db.insertInto("_emdash_menu_items").values({
			id: itemId,
			menu_id: menuId,
			parent_id: parentId,
			sort_order: order,
			type: item.type,
			reference_collection: referenceCollection,
			reference_id: referenceId,
			custom_url: item.url ?? null,
			label: item.label || "",
			title_attr: item.titleAttr ?? null,
			target: item.target ?? null,
			css_classes: item.cssClasses ?? null,
			created_at: (/* @__PURE__ */ new Date()).toISOString(),
			locale: itemLocale,
			translation_group: translationGroup
		}).execute();
		if (item.id) itemSeedIdMap.set(item.id, {
			id: itemId,
			translationGroup
		});
		count++;
		order++;
		if (item.children && item.children.length > 0) {
			const childCount = await applyMenuItems(db, menuId, itemLocale, item.children, itemId, 0, seedIdMap, itemSeedIdMap);
			count += childCount;
		}
	}
	return count;
}
/**
* Apply a widget
*/
async function applyWidget(db, areaId, widget, sortOrder) {
	await db.insertInto("_emdash_widgets").values({
		id: ulid(),
		area_id: areaId,
		sort_order: sortOrder,
		type: widget.type,
		title: widget.title ?? null,
		content: widget.content ? JSON.stringify(widget.content) : null,
		menu_name: widget.menuName ?? null,
		component_id: widget.componentId ?? null,
		component_props: widget.props ? JSON.stringify(widget.props) : null
	}).execute();
}
/**
* Type guard for $media reference
*/
function isSeedMediaReference(value) {
	if (typeof value !== "object" || value === null || !("$media" in value)) return false;
	const media = value.$media;
	return typeof media === "object" && media !== null && "url" in media && typeof media.url === "string";
}
/**
* Resolve $ref: and $media references in content data
*/
async function resolveReferences(data, seedIdMap, mediaContext, result) {
	const resolved = {};
	for (const [key, value] of Object.entries(data)) resolved[key] = await resolveValue(value, seedIdMap, mediaContext, result);
	return resolved;
}
/**
* Resolve a single value recursively
*/
async function resolveValue(value, seedIdMap, mediaContext, result) {
	if (typeof value === "string" && value.startsWith("$ref:")) {
		const seedId = value.slice(5);
		return seedIdMap.get(seedId) ?? value;
	}
	if (isSeedMediaReference(value)) return resolveMedia(value, mediaContext, result);
	if (Array.isArray(value)) return Promise.all(value.map((item) => resolveValue(item, seedIdMap, mediaContext, result)));
	if (typeof value === "object" && value !== null) {
		const resolved = {};
		for (const [k, v] of Object.entries(value)) resolved[k] = await resolveValue(v, seedIdMap, mediaContext, result);
		return resolved;
	}
	return value;
}
/**
* Resolve a seeded byline avatar to a `media` row id. The file is assumed to
* already exist in storage (the caller supplies its `storageKey`), so nothing
* is downloaded or uploaded.
*
* Idempotent: if a media row with the same `storageKey` already exists it is
* reused rather than duplicated, so re-applying a seed in `update` mode does
* not leak rows. `created` reports whether a new row was inserted, so the
* caller can both account for it and delete it if the subsequent byline write
* fails (the only cross-dialect way to avoid an orphan — `withTransaction`
* is a no-op on D1).
*/
async function resolveSeedBylineAvatar(db, avatar) {
	const existing = await db.selectFrom("media").select("id").where("storage_key", "=", avatar.storageKey).orderBy("id", "asc").executeTakeFirst();
	if (existing) return {
		id: existing.id,
		created: false
	};
	const basename = avatar.storageKey.split("/").pop();
	const filename = avatar.filename ?? (basename && basename.length > 0 ? basename : avatar.storageKey);
	return {
		id: (await new MediaRepository(db).create({
			filename,
			mimeType: avatar.mimeType ?? "image/jpeg",
			storageKey: avatar.storageKey,
			alt: avatar.alt,
			width: avatar.width,
			height: avatar.height,
			status: "ready"
		})).id,
		created: true
	};
}
/**
* Delete a media row by id. Best-effort cleanup for a failed byline write: a
* failure here must not mask the original error that triggered the cleanup, so
* it is logged and swallowed rather than thrown.
*/
async function deleteMediaRow(db, id) {
	try {
		await db.deleteFrom("media").where("id", "=", id).execute();
	} catch (error) {
		console.warn(`[seed] failed to clean up orphaned avatar media ${id}:`, error);
	}
}
/**
* Resolve a $media reference by downloading and uploading the media
*/
async function resolveMedia(ref, ctx, result) {
	const { url, alt, filename, caption } = ref.$media;
	const cached = ctx.mediaCache.get(url);
	if (cached) {
		result.media.skipped++;
		return {
			...cached,
			alt: alt ?? cached.alt
		};
	}
	if (ctx.skipMediaDownload) {
		const mediaValue = {
			provider: "external",
			id: ulid(),
			src: url,
			alt: alt ?? void 0,
			filename: filename ?? void 0
		};
		ctx.mediaCache.set(url, mediaValue);
		result.media.created++;
		return mediaValue;
	}
	if (!ctx.storage) {
		console.warn(`Skipping $media reference (no storage configured): ${url}`);
		result.media.skipped++;
		return null;
	}
	try {
		validateExternalUrl(url);
		console.log(`  📥 Downloading: ${url}`);
		const response = await ssrfSafeFetch(url, { headers: { "User-Agent": "EmDash-CMS/1.0" } });
		if (!response.ok) {
			console.warn(`  ⚠️ Failed to download ${url}: ${response.status}`);
			result.media.skipped++;
			return null;
		}
		const contentType = response.headers.get("content-type") || "application/octet-stream";
		const ext = getExtensionFromContentType(contentType) || getExtensionFromUrl(url) || ".bin";
		const id = ulid();
		const finalFilename = filename || generateFilename(url, ext);
		const storageKey = `${id}${ext}`;
		const arrayBuffer = await response.arrayBuffer();
		const body = new Uint8Array(arrayBuffer);
		let width;
		let height;
		if (contentType.startsWith("image/")) {
			const dimensions = getImageDimensions(body);
			width = dimensions?.width;
			height = dimensions?.height;
		}
		await ctx.storage.upload({
			key: storageKey,
			body,
			contentType
		});
		await new MediaRepository(ctx.db).create({
			filename: finalFilename,
			mimeType: contentType,
			size: body.length,
			width,
			height,
			alt,
			caption,
			storageKey,
			status: "ready"
		});
		const mediaValue = {
			provider: "local",
			id,
			alt: alt ?? void 0,
			width,
			height,
			mimeType: contentType,
			filename: finalFilename,
			meta: { storageKey }
		};
		ctx.mediaCache.set(url, mediaValue);
		result.media.created++;
		console.log(`  ✅ Uploaded: ${finalFilename}`);
		return mediaValue;
	} catch (error) {
		console.warn(`  ⚠️ Error processing $media ${url}:`, error instanceof Error ? error.message : error);
		result.media.skipped++;
		return null;
	}
}
/**
* Get file extension from content type
*/
function getExtensionFromContentType(contentType) {
	const baseMime = contentType.split(";")[0].trim();
	const ext = mime.getExtension(baseMime);
	return ext ? `.${ext}` : null;
}
/**
* Get file extension from URL
*/
function getExtensionFromUrl(url) {
	try {
		const match = new URL(url).pathname.match(FILE_EXTENSION_PATTERN);
		return match ? `.${match[1]}` : null;
	} catch {
		return null;
	}
}
/**
* Generate a filename from URL
*/
function generateFilename(url, ext) {
	try {
		return `${(new URL(url).pathname.split("/").pop() || "media").replace(EXTENSION_PATTERN, "").replace(QUERY_PARAM_PATTERN, "").replace(SANITIZE_PATTERN, "-").replace(MULTIPLE_HYPHENS_PATTERN, "-") || "media"}${ext}`;
	} catch {
		return `media${ext}`;
	}
}
/**
* Get image dimensions from buffer using image-size.
* Supports PNG, JPEG, GIF, WebP, AVIF, SVG, TIFF, and more.
*/
function getImageDimensions(buffer) {
	try {
		const result = imageSize(buffer);
		if (result.width != null && result.height != null) return {
			width: result.width,
			height: result.height
		};
		return null;
	} catch {
		return null;
	}
}

//#endregion
export { apply_exports as n, applySeed as t };
//# sourceMappingURL=apply-Bu-8nYHL.mjs.map