import { A as contentEntries, S as ContentAuthzError, b as normalizeContentInput, m as termsForEntry, v as ContentValidationError } from "./taxonomy-CmOYaoVZ.js";
import * as drizzle from "drizzle-orm";
//#region src/migrate.ts
var ContentMigrateError = class extends Error {
	detail;
	name = "ContentMigrateError";
	constructor(detail) {
		super(`content migrate: ${detail}`);
		this.detail = detail;
	}
};
const STATUS_LITERALS = [
	"draft",
	"pending",
	"scheduled",
	"published",
	"trashed"
];
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const MAX_BODY_LENGTH = 1e6;
const MAX_ENTRIES = 1e5;
async function toEntry(db, row) {
	return {
		id: row.id,
		slug: row.slug,
		type: row.type,
		title: row.title,
		body: row.body,
		status: row.status,
		visibility: row.visibility,
		publishedAt: row.publishedAt ?? null,
		author: row.author,
		terms: await termsForEntry(db, row.id),
		createdAt: row.createdAt,
		updatedAt: row.updatedAt,
		parentId: row.parentId ?? null,
		menuOrder: row.menuOrder,
		templateKey: row.templateKey ?? null,
		excerpt: row.excerpt,
		featuredMedia: row.featuredMedia ?? null,
		commentStatus: row.commentStatus,
		pingStatus: row.pingStatus,
		passwordProtected: false,
		sticky: row.sticky,
		format: row.format ?? null,
		deletedAt: row.deletedAt ?? null,
		lastEditedBy: row.lastEditedBy,
		typeDefinitionRevision: row.typeDefinitionRevision,
		statusDefinitionRevision: row.statusDefinitionRevision
	};
}
function assertSanitize(sanitize) {
	if (typeof sanitize !== "function") throw new ContentMigrateError("sanitize required");
}
function assertCanImport(actor) {
	if (actor?.canEditAny !== true || actor?.canPublish !== true) throw new ContentAuthzError("update", actor?.id ?? "<anonymous>", "import requires canEditAny and canPublish");
}
function assertCanExport(actor) {
	if (actor?.canEditAny !== true) throw new ContentAuthzError("read", actor?.id ?? "<anonymous>", "export requires canEditAny");
}
function assertTransactional(db) {
	if (typeof db.transaction !== "function") throw new ContentMigrateError("replace mode requires a TransactionalDatabase");
}
function isUniqueViolation(e) {
	let cur = e;
	while (cur) {
		const code = cur?.code;
		const msg = cur instanceof Error ? cur.message : String(cur);
		if (code === "23505" || /content_entries_type_slug_uq|duplicate key|unique constraint/i.test(msg)) return true;
		cur = cur instanceof Error ? cur.cause : void 0;
	}
	return false;
}
function toContentInput(entry) {
	return {
		id: entry.id,
		slug: entry.slug,
		type: entry.type,
		title: entry.title,
		body: entry.body,
		visibility: entry.visibility,
		termIds: entry.terms.map((t) => t.id)
	};
}
function prepareEntries(archive, sanitize) {
	const prepared = [];
	const conflicts = [];
	let skipped = 0;
	for (const entry of archive.entries) {
		const ref = `${entry?.type ?? "?"}/${entry?.slug ?? "?"}`;
		try {
			if (entry.id !== void 0 && entry.id !== null && (typeof entry.id !== "string" || !UUID_RE.test(entry.id))) {
				conflicts.push(`${ref}: id — must be a valid UUID`);
				skipped++;
				continue;
			}
			if (!STATUS_LITERALS.includes(entry.status)) {
				conflicts.push(`${ref}: status — must be draft|pending|scheduled|published|trashed`);
				skipped++;
				continue;
			}
			if (typeof entry.body === "string" && entry.body.length > MAX_BODY_LENGTH) {
				conflicts.push(`${ref}: body — exceeds ${MAX_BODY_LENGTH} chars`);
				skipped++;
				continue;
			}
			const normalized = normalizeContentInput(toContentInput(entry));
			const safeBody = sanitize(normalized.body);
			const safeVisibility = normalized.visibility ?? "private";
			prepared.push({
				source: entry,
				normalized,
				safeBody,
				safeStatus: entry.status,
				safeVisibility
			});
		} catch (e) {
			if (e instanceof ContentValidationError) {
				conflicts.push(`${ref}: ${e.field} — ${e.detail}`);
				skipped++;
				continue;
			}
			throw new ContentMigrateError("validation failed");
		}
	}
	return {
		prepared,
		skipped,
		conflicts
	};
}
function insertValues(p) {
	const { source, normalized, safeBody, safeStatus, safeVisibility } = p;
	return {
		id: source.id,
		slug: normalized.slug,
		type: normalized.type,
		title: normalized.title,
		body: safeBody,
		status: safeStatus,
		visibility: safeVisibility,
		publishedAt: source.publishedAt,
		author: source.author,
		createdAt: source.createdAt,
		updatedAt: source.updatedAt,
		parentId: source.parentId ?? null,
		menuOrder: source.menuOrder ?? 0,
		templateKey: source.templateKey ?? null,
		excerpt: source.excerpt ?? "",
		featuredMedia: source.featuredMedia ?? null,
		commentStatus: source.commentStatus ?? "open",
		pingStatus: source.pingStatus ?? "open",
		sticky: source.sticky ?? false,
		format: source.format ?? null,
		deletedAt: source.deletedAt ?? null,
		lastEditedBy: source.lastEditedBy ?? source.author,
		typeDefinitionRevision: source.typeDefinitionRevision ?? 1,
		statusDefinitionRevision: source.statusDefinitionRevision ?? 1
	};
}
async function exportContent(db, opts) {
	assertCanExport(opts?.actor);
	const rows = opts?.types?.length ? await db.select().from(contentEntries).where(drizzle.inArray(contentEntries.type, opts.types)) : await db.select().from(contentEntries);
	const archive = {
		version: 1,
		exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
		entries: await Promise.all(rows.map((row) => toEntry(db, row)))
	};
	if (opts?.includeSettings) archive.settings = {};
	return archive;
}
async function writePrepared(db, prepared, mode) {
	let imported = 0;
	let skipped = 0;
	for (const p of prepared) {
		if (mode === "merge") {
			const [existing] = await db.select().from(contentEntries).where(drizzle.and(drizzle.eq(contentEntries.type, p.normalized.type), drizzle.eq(contentEntries.slug, p.normalized.slug))).limit(1);
			if (existing && p.source.updatedAt <= existing.updatedAt) {
				skipped++;
				continue;
			}
		}
		try {
			await db.insert(contentEntries).values(insertValues(p)).onConflictDoUpdate({
				target: [contentEntries.type, contentEntries.slug],
				set: {
					title: p.normalized.title,
					body: p.safeBody,
					status: p.safeStatus,
					visibility: p.safeVisibility,
					publishedAt: p.source.publishedAt,
					author: p.source.author,
					updatedAt: p.source.updatedAt
				},
				where: drizzle.sql`${contentEntries.updatedAt} < excluded.updated_at`
			});
			imported++;
		} catch (e) {
			if (isUniqueViolation(e)) {
				skipped++;
				continue;
			}
			throw new ContentMigrateError("write failed");
		}
	}
	return {
		imported,
		skipped
	};
}
async function importContent(db, archive, opts) {
	assertCanImport(opts?.actor);
	assertSanitize(opts.sanitize);
	if (!archive || !Array.isArray(archive.entries)) throw new ContentMigrateError("archive.entries must be an array");
	if (archive.entries.length > MAX_ENTRIES) throw new ContentMigrateError(`archive exceeds ${MAX_ENTRIES} entries`);
	const { prepared, skipped: invalidSkipped, conflicts } = prepareEntries(archive, opts.sanitize);
	if (opts.mode === "replace") {
		assertTransactional(db);
		if (conflicts.length > 0) throw new ContentMigrateError(`replace aborted: ${conflicts.length} invalid entries`);
		let imported = 0;
		await db.transaction(async (tx) => {
			await tx.delete(contentEntries);
			imported = (await writePrepared(tx, prepared, "replace")).imported;
		});
		return {
			imported,
			skipped: invalidSkipped,
			conflicts
		};
	}
	const result = await writePrepared(db, prepared, "merge");
	return {
		imported: result.imported,
		skipped: invalidSkipped + result.skipped,
		conflicts
	};
}
//#endregion
export { ContentMigrateError, exportContent, importContent };
