globalThis.process ??= {};
globalThis.process.env ??= {};
import { Tt as sql, f as getTransactionIdentity } from "./d1_DTqC4o_t.mjs";
import { X as listTerms, Z as termsForEntry, q as authorizeContentAction } from "./crypto_CY2zBtG3.mjs";
import { A as resolveContentTypes, I as transitionContent, L as updateContentEntry, M as restoreContentRevision, O as resolveContentStatuses, h as normalizeContentTypeDefinition, m as normalizeContentStatusDefinition, p as getContentRevision, r as canonicalContentMigrationHash } from "./dist_B03Lmokr.mjs";
import { n as getFullDb, o as getMedia, r as getMutationDb, s as listMedia } from "./db_DCN9iNkm.mjs";
import { $ as createFieldTypeRegistry, C as getFieldsRevision, L as renderBlockDocument, R as resolveFieldBlocks, V as restoreFieldsRevision, W as setBlockDocument, Y as validateBlockDocument, Z as authorizeRelationshipReplacement, c as assertFieldsTransactionIdentity, ct as redactFieldValueForRevision, et as decodeFieldValueNodes, f as createLocationResolverRegistry, ft as validateFieldValue, l as captureFieldsRevision, lt as resolveFieldDefinitionVersion, m as createReusableBlockResolver, pt as canonicalFieldsMigrationHash, t as DEFAULT_BLOCK_POLICY, tt as encodeFieldValueNodes, ut as resolveFinalFieldGroups, x as getBlockDocument } from "./dist_zF1UfK-y.mjs";
import { t as sanitizeContentBody } from "./sanitize_CLHlWQbC.mjs";
import { r as loadPublicComments } from "./comments-render_sVi9oim7.mjs";
import { i as contentModelAdminFieldsContext } from "./content-model-admin_GNZxNm19.mjs";
import { n as resolveContentTranslations } from "./content-i18n_B2RsyQw6.mjs";
import { t as loadPublicContentSearch } from "./content-search_ByAiHuPJ.mjs";
//#region src/lib/content-model-adapters.ts
function rowsOf$1(value) {
	if (Array.isArray(value)) return value;
	const rows = value?.rows;
	return Array.isArray(rows) ? rows : [];
}
function createContentModelHostAdapters(db) {
	const contentDb = db, mediaDb = db, commentsDb = db, translationDb = db;
	const contentExists = async (id) => rowsOf$1(await db.execute(sql`SELECT id FROM content_entries WHERE id=${id} LIMIT 1`)).length === 1;
	const userExists = async (id) => rowsOf$1(await db.execute(sql`SELECT id FROM auth_users WHERE id=${id} LIMIT 1`)).length === 1;
	const reusableBlockExists = async (id) => rowsOf$1(await db.execute(sql`SELECT id FROM field_reusable_blocks WHERE id=${id} AND deleted_at IS NULL LIMIT 1`)).length === 1;
	const remappers = Object.freeze({
		entity: async (ref) => ref.entityType === "content" ? await contentExists(ref.entityId) ? ref : null : ref,
		user: async (id) => await userExists(id) ? id : null,
		media: async (ref) => {
			const row = await getMedia(mediaDb, ref.id);
			if (!row) return null;
			if (ref.mimeType !== void 0 && row.contentType !== ref.mimeType) return null;
			return ref;
		},
		term: async (ref) => {
			return (await listTerms(contentDb, ref.taxonomy)).some((term) => term.id === ref.id) ? ref : null;
		},
		reusableBlock: async (id) => await reusableBlockExists(id) ? id : null
	});
	return Object.freeze({
		media: Object.freeze({
			get: (key) => getMedia(mediaDb, key),
			list: (input) => listMedia(mediaDb, input)
		}),
		taxonomy: Object.freeze({
			list: (taxonomy) => listTerms(contentDb, taxonomy),
			forEntry: (entryId) => termsForEntry(contentDb, entryId)
		}),
		comments: Object.freeze({ forEntry: (entryId) => loadPublicComments(commentsDb, {
			type: "content_entry",
			id: entryId
		}) }),
		translations: Object.freeze({ resolve: (input) => resolveContentTranslations(translationDb, {
			entityType: "content",
			...input
		}) }),
		search: Object.freeze({ public: (query, cursor) => loadPublicContentSearch(contentDb, query, cursor) }),
		fieldsImportRemappers: remappers
	});
}
//#endregion
//#region src/lib/content-model-editor.ts
var ContentModelEditorError = class extends Error {
	code;
	detail;
	name = "ContentModelEditorError";
	constructor(code, detail) {
		super(`content model editor ${code}: ${detail}`);
		this.code = code;
		this.detail = detail;
	}
};
function rowsOf(value) {
	if (Array.isArray(value)) return value;
	const rows = value?.rows;
	if (Array.isArray(rows)) return rows;
	throw new ContentModelEditorError("integrity", "database returned an invalid row envelope");
}
async function execute(db, query) {
	return rowsOf(await db.execute(query));
}
function jsonValue(value) {
	if (typeof value !== "string") return value;
	try {
		return JSON.parse(value);
	} catch {
		return value;
	}
}
function mediaValue(value) {
	const parsed = jsonValue(value);
	if (parsed === null || parsed === void 0) return null;
	if (typeof parsed !== "object" || Array.isArray(parsed)) throw new ContentModelEditorError("integrity", "featured media is malformed");
	const row = parsed;
	if (typeof row.id !== "string") throw new ContentModelEditorError("integrity", "featured media is malformed");
	return Object.freeze({
		id: row.id,
		...typeof row.kind === "string" ? { kind: row.kind } : {}
	});
}
function dateValue(value, field) {
	const date = value instanceof Date ? new Date(value) : new Date(String(value));
	if (Number.isNaN(date.getTime())) throw new ContentModelEditorError("integrity", `${field} is malformed`);
	return date;
}
function nullableDate(value, field) {
	return value === null || value === void 0 ? null : dateValue(value, field);
}
async function readEntry(db, id) {
	const row = (await execute(db, sql`SELECT id,slug,type,title,body,status,visibility,published_at AS "publishedAt",author,created_at AS "createdAt",updated_at AS "updatedAt",parent_id AS "parentId",menu_order AS "menuOrder",template_key AS "templateKey",excerpt,featured_media AS "featuredMedia",comment_status AS "commentStatus",ping_status AS "pingStatus",sticky,format,deleted_at AS "deletedAt",last_edited_by AS "lastEditedBy",type_definition_revision AS "typeDefinitionRevision",status_definition_revision AS "statusDefinitionRevision",EXISTS(SELECT 1 FROM content_password_credentials c WHERE c.entry_id=content_entries.id) AS "passwordProtected" FROM content_entries WHERE id=${id} LIMIT 1`))[0];
	if (!row) throw new ContentModelEditorError("access-denied", "content is unavailable");
	const terms = await execute(db, sql`SELECT t.id,t.taxonomy,t.slug,t.name,t.parent_id AS "parentId",t.depth FROM content_entry_terms et JOIN content_terms t ON t.id=et.term_id WHERE et.entry_id=${id} ORDER BY t.depth,t.name,t.id`);
	return Object.freeze({
		id: String(row.id),
		slug: String(row.slug),
		type: String(row.type),
		title: String(row.title),
		body: String(row.body),
		status: String(row.status),
		visibility: row.visibility,
		publishedAt: nullableDate(row.publishedAt, "publishedAt"),
		author: String(row.author),
		terms: terms.map((term) => Object.freeze({
			id: term.id,
			taxonomy: term.taxonomy,
			slug: term.slug,
			name: term.name,
			parentId: term.parentId,
			depth: Number(term.depth)
		})),
		createdAt: dateValue(row.createdAt, "createdAt"),
		updatedAt: dateValue(row.updatedAt, "updatedAt"),
		parentId: row.parentId === null ? null : String(row.parentId),
		menuOrder: Number(row.menuOrder),
		templateKey: row.templateKey === null ? null : String(row.templateKey),
		excerpt: String(row.excerpt ?? ""),
		featuredMedia: mediaValue(row.featuredMedia),
		commentStatus: row.commentStatus,
		pingStatus: row.pingStatus,
		passwordProtected: Boolean(row.passwordProtected),
		sticky: Boolean(row.sticky),
		format: row.format === null ? null : String(row.format),
		deletedAt: nullableDate(row.deletedAt, "deletedAt"),
		lastEditedBy: String(row.lastEditedBy),
		typeDefinitionRevision: Number(row.typeDefinitionRevision),
		statusDefinitionRevision: Number(row.statusDefinitionRevision)
	});
}
function adminContentPrincipal(userId, types) {
	const capabilities = /* @__PURE__ */ new Set();
	for (const type of types) for (const capability of Object.values(type.capabilities)) capabilities.add(capability);
	return Object.freeze({
		id: userId,
		capabilities,
		system: false
	});
}
function fieldsContext(userId) {
	const base = contentModelAdminFieldsContext(userId);
	return Object.freeze({
		...base,
		fieldTypeRegistry: createFieldTypeRegistry(),
		sanitizer: Object.freeze({ sanitizeHtml: sanitizeContentBody })
	});
}
function matchingStatuses(type, statuses) {
	const allowed = type.statusKeys ? new Set(type.statusKeys) : null;
	return Object.freeze(statuses.filter((status) => status.active && (!allowed || allowed.has(status.key))).sort((a, b) => (a.position ?? 0) - (b.position ?? 0) || a.key.localeCompare(b.key)));
}
function locationContext(entry, roles) {
	const taxonomy = {};
	for (const term of entry.terms) (taxonomy[term.taxonomy] ??= []).push(term.id);
	return Object.freeze({
		entityType: "content",
		subType: entry.type,
		entityId: entry.id,
		status: entry.status,
		template: entry.templateKey ?? void 0,
		parentId: entry.parentId ?? void 0,
		taxonomy: Object.freeze(Object.fromEntries(Object.entries(taxonomy).map(([key, ids]) => [key, Object.freeze(ids.sort())]))),
		format: entry.format ?? void 0,
		authorId: entry.author,
		actorRoles: Object.freeze([...roles]),
		surface: "entry"
	});
}
function containsPrivateField(fields, kind) {
	for (const field of fields) {
		if (field.type === kind) return true;
		if (field.type === "group" || field.type === "repeater") {
			if (containsPrivateField(field.settings.fields, kind)) return true;
		}
		if (field.type === "flexible") {
			for (const layout of field.settings.layouts) if (containsPrivateField(layout.fields, kind)) return true;
		}
	}
	return false;
}
function nodeFromRow(row) {
	return Object.freeze({
		nodeId: String(row.nodeId),
		entityType: String(row.entityType),
		entityId: String(row.entityId),
		groupKey: String(row.groupKey),
		definitionRevision: Number(row.definitionRevision),
		fieldKey: String(row.fieldKey),
		path: String(row.path),
		parentNodeId: row.parentNodeId === null ? null : String(row.parentNodeId),
		rowId: row.rowId === null ? null : String(row.rowId),
		layoutKey: row.layoutKey === null ? null : String(row.layoutKey),
		nodeKind: row.nodeKind,
		ordinal: Number(row.ordinal),
		isNull: row.isNull === true || row.isNull === 1,
		valueText: row.valueText,
		valueNumber: row.valueNumber === null ? null : Number(row.valueNumber),
		valueBoolean: row.valueBoolean === null ? null : row.valueBoolean === true || row.valueBoolean === 1,
		valueDateTime: row.valueDateTime,
		valueRef: row.valueRef,
		valueJson: row.valueJson
	});
}
function storage(value) {
	return JSON.parse(JSON.stringify(value));
}
function revisionRedact(definitions, values, context) {
	const registry = context.fieldTypeRegistry ?? createFieldTypeRegistry(), out = {};
	for (const definition of definitions) {
		const value = values[definition.key];
		if (value === void 0) continue;
		if (definition.type === "password") {
			out[definition.key] = storage({ configured: Boolean(value?.configured) });
			continue;
		}
		if (definition.type === "message" || definition.type === "accordion" || definition.type === "tab") continue;
		if (definition.type === "group") {
			out[definition.key] = storage(revisionRedact(definition.settings.fields, value ?? {}, context));
			continue;
		}
		if (definition.type === "repeater") {
			const fields = definition.settings.fields;
			out[definition.key] = storage(value.map((row) => ({
				rowId: row.rowId,
				values: revisionRedact(fields, row.values, context)
			})));
			continue;
		}
		if (definition.type === "flexible") {
			const layouts = definition.settings.layouts;
			out[definition.key] = storage(value.map((row) => {
				const layout = layouts.find((item) => item.key === row.layoutKey);
				if (!layout) throw new ContentModelEditorError("integrity", "field snapshot references an unknown flexible layout");
				return {
					rowId: row.rowId,
					layoutKey: row.layoutKey,
					values: revisionRedact(layout.fields, row.values, context)
				};
			}));
			continue;
		}
		if (definition.type === "clone") throw new ContentModelEditorError("unsupported-capability", "clone-bearing field values require a host clone resolver");
		const redacted = redactFieldValueForRevision(definition, value, registry);
		if (redacted !== "omit") out[definition.key] = storage(redacted);
	}
	return Object.freeze(out);
}
async function currentFieldsState(db, ref, context) {
	const rows = await execute(db, sql`SELECT node_id AS "nodeId",entity_type AS "entityType",entity_id AS "entityId",group_key AS "groupKey",definition_revision AS "definitionRevision",field_key AS "fieldKey",path,parent_node_id AS "parentNodeId",row_id AS "rowId",layout_key AS "layoutKey",node_kind AS "nodeKind",ordinal,is_null AS "isNull",value_text AS "valueText",value_number AS "valueNumber",value_boolean AS "valueBoolean",value_date_time AS "valueDateTime",value_ref AS "valueRef",value_json AS "valueJson" FROM field_value_nodes WHERE entity_type=${ref.entityType} AND entity_id=${ref.entityId} ORDER BY group_key,path`);
	const byGroup = /* @__PURE__ */ new Map();
	for (const row of rows) {
		const bucket = byGroup.get(row.groupKey) ?? [];
		bucket.push(row);
		byGroup.set(row.groupKey, bucket);
	}
	const definitionVersions = {}, values = {}, definitions = /* @__PURE__ */ new Map();
	for (const [groupKey, bucket] of [...byGroup].sort(([a], [b]) => a.localeCompare(b))) {
		const revisions = [...new Set(bucket.map((row) => Number(row.definitionRevision)))];
		if (revisions.length !== 1) throw new ContentModelEditorError("integrity", `current field group ${groupKey} mixes immutable revisions`);
		const revision = revisions[0], resolved = await resolveFieldDefinitionVersion(db, groupKey, revision), decoded = decodeFieldValueNodes(resolved.definition, bucket.map(nodeFromRow), { definitionRevision: revision });
		definitionVersions[groupKey] = revision;
		values[groupKey] = Object.freeze(decoded);
		definitions.set(groupKey, resolved.definition);
	}
	const blockDocument = await getBlockDocument(db, ref, context);
	const redacted = {};
	for (const [groupKey, value] of Object.entries(values)) {
		const definition = definitions.get(groupKey);
		if (!definition) throw new ContentModelEditorError("integrity", `definition ${groupKey} disappeared`);
		redacted[groupKey] = storage(revisionRedact(definition.fields, value, context));
	}
	const version = await canonicalFieldsMigrationHash({
		definitionVersions,
		values: redacted,
		blockDocument: blockDocument?.document ?? null
	});
	return Object.freeze({
		version,
		definitionVersions: Object.freeze(definitionVersions),
		values: Object.freeze(values),
		definitions,
		blockDocument
	});
}
async function matchedFieldGroups(db, entry, sources, context, roles, stateInput) {
	const groups = await resolveFinalFieldGroups(db, { codeGroups: sources.fieldGroups }), resolver = createLocationResolverRegistry(), location = locationContext(entry, roles), state = stateInput ?? await currentFieldsState(db, {
		entityType: "content",
		entityId: entry.id
	}, context), output = [];
	for (const group of groups) {
		if (!group.active || !await resolver.evaluate(group.location, location)) continue;
		const valueRevision = state.definitionVersions[group.key] ?? group.revision, pinned = state.definitions.get(group.key), values = state.values[group.key] ?? Object.freeze({}), stale = pinned !== void 0 && valueRevision !== group.revision, hasPassword = containsPrivateField((pinned ?? group).fields, "password"), hasClone = containsPrivateField((pinned ?? group).fields, "clone"), definition = pinned ? Object.freeze({
			...pinned,
			origin: group.origin,
			version: group.version,
			revision: valueRevision,
			active: group.active,
			...group.shadowedDbVersion === void 0 ? {} : { shadowedDbVersion: group.shadowedDbVersion }
		}) : group;
		output.push(Object.freeze({
			definition,
			valueRevision,
			values,
			writable: !stale && !hasPassword && !hasClone,
			...stale ? { disabledReason: "stale-definition" } : hasPassword ? { disabledReason: "private-credential" } : hasClone ? { disabledReason: "clone-resolver" } : {}
		}));
	}
	return Object.freeze(output);
}
async function editorFieldsVersion(groups, blockDocument, context) {
	const definitionVersions = {}, values = {};
	for (const group of [...groups].sort((a, b) => a.definition.key.localeCompare(b.definition.key))) {
		definitionVersions[group.definition.key] = group.valueRevision;
		values[group.definition.key] = storage(revisionRedact(group.definition.fields, group.values, context));
	}
	return canonicalFieldsMigrationHash({
		definitionVersions,
		values,
		blockDocument: blockDocument?.document ?? null
	});
}
async function editorDefinitions(db, sources) {
	const [types, statuses] = await Promise.all([resolveContentTypes(db, { codeTypes: sources.contentTypes }), resolveContentStatuses(db, { codeStatuses: sources.contentStatuses })]);
	return {
		types,
		statuses
	};
}
async function linkedRevisionSummaries(db, entryId) {
	const rows = await execute(db, sql`SELECT r.id,r.seq,r.editor,r.created_at AS "createdAt",(SELECT fr.id FROM field_revisions fr WHERE fr.parent_revision_id=CAST(r.id AS TEXT) AND fr.entity_type='content' AND fr.entity_id=${entryId} AND fr.kind='revision' ORDER BY fr.created_at DESC,fr.id DESC LIMIT 1) AS "fieldsRevisionId" FROM content_revisions r WHERE r.entry_id=${entryId} ORDER BY r.seq DESC,r.id DESC`);
	return Object.freeze(rows.map((row) => Object.freeze({
		id: row.id,
		seq: Number(row.seq),
		editor: row.editor,
		createdAt: dateValue(row.createdAt, "revision.createdAt").toISOString(),
		...row.fieldsRevisionId ? { fieldsRevisionId: row.fieldsRevisionId } : {}
	})));
}
async function pinnedTypeStatus(db, entry, currentTypes, currentStatuses) {
	const typeCurrent = currentTypes.find((candidate) => candidate.key === entry.type), statusCurrent = currentStatuses.find((candidate) => candidate.key === entry.status);
	if (!typeCurrent || !statusCurrent) throw new ContentModelEditorError("integrity", "entry references unavailable type or status");
	const versions = await execute(db, sql`SELECT definition_kind AS kind,definition_key AS key,revision,canonical_hash AS "canonicalHash",definition,origin FROM content_definition_versions WHERE (definition_kind='type' AND definition_key=${entry.type} AND revision=${entry.typeDefinitionRevision}) OR (definition_kind='status' AND definition_key=${entry.status} AND revision=${entry.statusDefinitionRevision})`);
	const typeVersion = versions.find((row) => row.kind === "type"), statusVersion = versions.find((row) => row.kind === "status");
	if (!typeVersion || !statusVersion) throw new ContentModelEditorError("integrity", "entry immutable type/status definition is unavailable");
	const typeDefinition = normalizeContentTypeDefinition(jsonValue(typeVersion.definition)), statusDefinition = normalizeContentStatusDefinition(jsonValue(statusVersion.definition));
	return Object.freeze({
		type: Object.freeze({
			...typeDefinition,
			origin: typeVersion.origin === "code" ? "code" : "db",
			version: typeCurrent.version,
			revision: Number(typeVersion.revision),
			canonicalHash: typeVersion.canonicalHash,
			...typeCurrent.shadowedDbVersion === void 0 ? {} : { shadowedDbVersion: typeCurrent.shadowedDbVersion }
		}),
		status: Object.freeze({
			...statusDefinition,
			origin: statusVersion.origin === "code" ? "code" : "db",
			version: statusCurrent.version,
			revision: Number(statusVersion.revision),
			active: statusCurrent.active,
			canonicalHash: statusVersion.canonicalHash,
			...statusCurrent.shadowedDbVersion === void 0 ? {} : { shadowedDbVersion: statusCurrent.shadowedDbVersion }
		})
	});
}
function requireAdmin(principal) {
	if (!principal.userId || !principal.roles.includes("admin")) throw new ContentModelEditorError("access-denied", "content model editor is unavailable");
}
async function loadContentModelEditorSnapshot(env, principal, entryId, sources = {}) {
	requireAdmin(principal);
	const db = getFullDb(env).db, entry = await readEntry(db, entryId), definitions = await editorDefinitions(db, sources), pinned = await pinnedTypeStatus(db, entry, definitions.types, definitions.statuses), contentPrincipal = adminContentPrincipal(principal.userId, [...definitions.types, pinned.type]);
	authorizeContentAction({
		principal: contentPrincipal,
		type: pinned.type,
		status: pinned.status,
		operation: "edit",
		entry
	});
	const context = fieldsContext(principal.userId), state = await currentFieldsState(db, Object.freeze({
		entityType: "content",
		entityId: entry.id
	}), context), fields = await matchedFieldGroups(db, entry, sources, context, principal.roles, state), fieldsVersion = await editorFieldsVersion(fields, state.blockDocument, context), blocksEnabled = pinned.type.supports.includes("blocks");
	let blocks = Object.freeze({ enabled: false });
	if (blocksEnabled) {
		const registry = await resolveFieldBlocks(db, { codeBlocks: sources.blocks }), document = state.blockDocument;
		if (document) await validateBlockDocument(document.document, registry, createReusableBlockResolver(db), context, DEFAULT_BLOCK_POLICY);
		blocks = Object.freeze({
			enabled: true,
			registryVersion: registry.version,
			definitions: Object.freeze([...registry.blocks.values()].filter((block) => block.active)),
			document
		});
	}
	return Object.freeze({
		entry,
		type: pinned.type,
		statuses: matchingStatuses(pinned.type, definitions.statuses),
		fieldsVersion,
		fields,
		blocks,
		revisions: await linkedRevisionSummaries(db, entry.id)
	});
}
function templatesFor(types) {
	const descriptors = types.map((type) => Object.freeze({
		key: type.defaultTemplateKey,
		label: type.labels.singularName,
		typeKeys: Object.freeze([type.key]),
		active: true,
		version: String(type.revision)
	}));
	return Object.freeze({
		version: descriptors.map((item) => `${item.typeKeys[0]}:${item.key}:${item.version}`).sort().join("|") || "none",
		resolve: (typeKey, key) => descriptors.find((item) => item.key === key && item.typeKeys.includes(typeKey)),
		list: (typeKey) => Object.freeze(descriptors.filter((item) => item.typeKeys.includes(typeKey)))
	});
}
function contentOutbox() {
	const enqueue = async (tx, event) => {
		await tx.execute(sql`INSERT INTO content_model_admin_outbox (id,kind,payload,created_at) VALUES (${crypto.randomUUID()},${`editor:${event.kind}`},${JSON.stringify({
			...event,
			occurredAt: event.occurredAt.toISOString()
		})},${event.occurredAt.toISOString()})`);
	};
	return Object.freeze({ enqueue });
}
async function contentWriteDeps(tx, types) {
	return Object.freeze({
		sanitize: sanitizeContentBody,
		templates: templatesFor(types),
		authors: Object.freeze({
			assertAssign: async (_principal, authorId) => {
				if (!(await execute(tx, sql`SELECT id FROM auth_users WHERE id=${authorId} LIMIT 1`))[0]) throw new ContentModelEditorError("validation", "author is unavailable");
			},
			assertTransfer: async (_principal, _from, to) => {
				if (!(await execute(tx, sql`SELECT id FROM auth_users WHERE id=${to} LIMIT 1`))[0]) throw new ContentModelEditorError("validation", "author is unavailable");
			}
		}),
		capabilities: Object.freeze({ validateMediaRef: async (ref) => {
			if (!(await execute(tx, sql`SELECT key FROM media_assets WHERE key=${ref.id} LIMIT 1`))[0]) throw new ContentModelEditorError("validation", "featured media is unavailable");
		} }),
		outbox: contentOutbox()
	});
}
function boundedOperationId(value) {
	const normalized = value.trim();
	if (!normalized || normalized.length > 180) throw new ContentModelEditorError("validation", "operationId must contain 1..180 characters");
	return normalized;
}
function editorPrincipalScope(principal) {
	return `${principal.tenantId ?? ""}:${principal.userId}`;
}
function receiptPayload(value) {
	const parsed = jsonValue(value);
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new ContentModelEditorError("integrity", "editor receipt is malformed");
	const row = parsed;
	if (typeof row.requestHash !== "string" || typeof row.principalScope !== "string" || row.result === void 0) throw new ContentModelEditorError("integrity", "editor receipt is malformed");
	return row;
}
function serializeEntry(entry) {
	return storage({
		...entry,
		publishedAt: entry.publishedAt?.toISOString() ?? null,
		createdAt: entry.createdAt.toISOString(),
		updatedAt: entry.updatedAt.toISOString(),
		deletedAt: entry.deletedAt?.toISOString() ?? null
	});
}
function deserializeEntry(value) {
	const row = jsonValue(value);
	if (typeof row !== "object" || row === null || Array.isArray(row)) throw new ContentModelEditorError("integrity", "editor receipt entry is malformed");
	const item = row;
	const terms = Array.isArray(item.terms) ? item.terms : [];
	return Object.freeze({
		id: String(item.id),
		slug: String(item.slug),
		type: String(item.type),
		title: String(item.title),
		body: String(item.body),
		status: String(item.status),
		visibility: item.visibility,
		publishedAt: nullableDate(item.publishedAt, "receipt.publishedAt"),
		author: String(item.author),
		terms: terms.map((term) => Object.freeze({
			...term,
			depth: Number(term.depth)
		})),
		createdAt: dateValue(item.createdAt, "receipt.createdAt"),
		updatedAt: dateValue(item.updatedAt, "receipt.updatedAt"),
		parentId: item.parentId === null ? null : String(item.parentId),
		menuOrder: Number(item.menuOrder),
		templateKey: item.templateKey === null ? null : String(item.templateKey),
		excerpt: String(item.excerpt ?? ""),
		featuredMedia: mediaValue(item.featuredMedia),
		commentStatus: item.commentStatus,
		pingStatus: item.pingStatus,
		passwordProtected: Boolean(item.passwordProtected),
		sticky: Boolean(item.sticky),
		format: item.format === null ? null : String(item.format),
		deletedAt: nullableDate(item.deletedAt, "receipt.deletedAt"),
		lastEditedBy: String(item.lastEditedBy),
		typeDefinitionRevision: Number(item.typeDefinitionRevision),
		statusDefinitionRevision: Number(item.statusDefinitionRevision)
	});
}
function serializeSaveResult(result) {
	return storage({
		entry: serializeEntry(result.entry),
		parentRevisionId: result.parentRevisionId ?? null,
		fieldsRevisionId: result.fieldsRevisionId ?? null,
		blockDocument: result.blockDocument ?? null
	});
}
function deserializeSaveResult(value) {
	const parsed = jsonValue(value);
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new ContentModelEditorError("integrity", "editor receipt result is malformed");
	const row = parsed;
	return Object.freeze({
		entry: deserializeEntry(row.entry),
		...typeof row.parentRevisionId === "string" ? { parentRevisionId: row.parentRevisionId } : {},
		...typeof row.fieldsRevisionId === "string" ? { fieldsRevisionId: row.fieldsRevisionId } : {},
		...row.blockDocument === null || row.blockDocument === void 0 ? {} : { blockDocument: row.blockDocument }
	});
}
function serializeRestoreResult(result) {
	return storage({
		entry: serializeEntry(result.entry),
		contentRevisionId: result.contentRevisionId,
		fieldsRevisionId: result.fieldsRevisionId,
		undoParentRevisionId: result.undoParentRevisionId,
		undoFieldsRevisionId: result.undoFieldsRevisionId,
		blockDocument: result.blockDocument ?? null
	});
}
function deserializeRestoreResult(value) {
	const parsed = jsonValue(value);
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new ContentModelEditorError("integrity", "editor restore receipt result is malformed");
	const row = parsed;
	for (const key of [
		"contentRevisionId",
		"fieldsRevisionId",
		"undoParentRevisionId",
		"undoFieldsRevisionId"
	]) if (typeof row[key] !== "string") throw new ContentModelEditorError("integrity", "editor restore receipt result is malformed");
	return Object.freeze({
		entry: deserializeEntry(row.entry),
		contentRevisionId: row.contentRevisionId,
		fieldsRevisionId: row.fieldsRevisionId,
		undoParentRevisionId: row.undoParentRevisionId,
		undoFieldsRevisionId: row.undoFieldsRevisionId,
		...row.blockDocument === null || row.blockDocument === void 0 ? {} : { blockDocument: row.blockDocument }
	});
}
function serializeTransitionResult(result) {
	return storage({
		entry: serializeEntry(result.entry),
		parentRevisionId: result.parentRevisionId ?? null,
		fieldsRevisionId: result.fieldsRevisionId ?? null
	});
}
function deserializeTransitionResult(value) {
	const parsed = jsonValue(value);
	if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new ContentModelEditorError("integrity", "editor transition receipt result is malformed");
	const row = parsed;
	return Object.freeze({
		entry: deserializeEntry(row.entry),
		...typeof row.parentRevisionId === "string" ? { parentRevisionId: row.parentRevisionId } : {},
		...typeof row.fieldsRevisionId === "string" ? { fieldsRevisionId: row.fieldsRevisionId } : {}
	});
}
async function readEditorReceipt(tx, operationId) {
	const rows = await execute(tx, sql`SELECT payload FROM content_lifecycle_journal WHERE operation_id=${operationId} AND kind='content:model-editor:save' LIMIT 1`);
	return rows[0] ? receiptPayload(rows[0].payload) : void 0;
}
async function writeEditorReceipt(tx, operationId, requestHash, principal, result) {
	const now = (/* @__PURE__ */ new Date()).toISOString(), payload = {
		requestHash,
		principalScope: editorPrincipalScope(principal),
		result: serializeSaveResult(result)
	};
	await tx.execute(sql`INSERT INTO content_lifecycle_journal (id,operation_id,kind,entry_id,state,payload,created_at,updated_at) VALUES (${crypto.randomUUID()},${operationId},'content:model-editor:save',${result.entry.id},'committed',${JSON.stringify(payload)},${now},${now})`);
}
async function readEditorRestoreReceipt(tx, operationId) {
	const rows = await execute(tx, sql`SELECT payload FROM content_lifecycle_journal WHERE operation_id=${operationId} AND kind='content:model-editor:restore' LIMIT 1`);
	return rows[0] ? receiptPayload(rows[0].payload) : void 0;
}
async function writeEditorRestoreReceipt(tx, operationId, requestHash, principal, result) {
	const now = (/* @__PURE__ */ new Date()).toISOString(), payload = {
		requestHash,
		principalScope: editorPrincipalScope(principal),
		result: serializeRestoreResult(result)
	};
	await tx.execute(sql`INSERT INTO content_lifecycle_journal (id,operation_id,kind,entry_id,state,payload,created_at,updated_at) VALUES (${crypto.randomUUID()},${operationId},'content:model-editor:restore',${result.entry.id},'committed',${JSON.stringify(payload)},${now},${now})`);
}
async function readEditorTransitionReceipt(tx, operationId) {
	const rows = await execute(tx, sql`SELECT payload FROM content_lifecycle_journal WHERE operation_id=${operationId} AND kind='content:model-editor:transition' LIMIT 1`);
	return rows[0] ? receiptPayload(rows[0].payload) : void 0;
}
async function writeEditorTransitionReceipt(tx, operationId, requestHash, principal, result) {
	const now = (/* @__PURE__ */ new Date()).toISOString(), payload = {
		requestHash,
		principalScope: editorPrincipalScope(principal),
		result: serializeTransitionResult(result)
	};
	await tx.execute(sql`INSERT INTO content_lifecycle_journal (id,operation_id,kind,entry_id,state,payload,created_at,updated_at) VALUES (${crypto.randomUUID()},${operationId},'content:model-editor:transition',${result.entry.id},'committed',${JSON.stringify(payload)},${now},${now})`);
}
function textPieces(value, out, depth = 0) {
	if (depth > 16) return;
	if (typeof value === "string") {
		const trimmed = value.trim();
		if (trimmed) out.push(trimmed);
		return;
	}
	if (typeof value === "number") {
		out.push(String(value));
		return;
	}
	if (value === null || typeof value === "boolean") return;
	if (Array.isArray(value)) {
		for (const item of value) textPieces(item, out, depth + 1);
		return;
	}
	if (typeof value === "object") {
		const row = value;
		if (Object.keys(row).length === 1 && typeof row.configured === "boolean") return;
		for (const key of Object.keys(row).sort()) textPieces(row[key], out, depth + 1);
	}
}
async function blockBodyFallback(document, registry, resolver, context) {
	const rendered = await renderBlockDocument(document, registry, resolver, { render: (node) => {
		const pieces = [];
		textPieces(node.attributes, pieces);
		for (const group of node.fieldGroups) textPieces(group.values, pieces);
		return pieces.join(" ");
	} }, context);
	return sanitizeContentBody(rendered.filter(Boolean).join("\n").replace(/\s+/g, " ").trim());
}
async function fieldValueContext(tx, context) {
	const authorization = context.authorization, principal = context.principal;
	if (!principal || !authorization) throw new ContentModelEditorError("access-denied", "fields authorization is unavailable");
	const adapters = createContentModelHostAdapters(tx);
	return Object.freeze({
		sanitizer: context.sanitizer,
		fieldTypeRegistry: context.fieldTypeRegistry,
		authorization: Object.freeze({ assertTarget: (ref, action) => authorization.assert(principal, action, {
			entityType: ref.entityType,
			entityId: ref.id
		}) }),
		mediaMetadata: Object.freeze({ resolve: async (ref) => {
			const media = await adapters.media.get(ref.id);
			return media ? Object.freeze({
				mimeType: media.contentType,
				bytes: media.size,
				...media.width === null ? {} : { width: media.width },
				...media.height === null ? {} : { height: media.height }
			}) : void 0;
		} })
	});
}
async function validateGroupMap(definitions, input, before, registry, valueContext, path) {
	const known = new Map(definitions.map((definition) => [definition.key, definition]));
	for (const key of Object.keys(input)) if (!known.has(key)) throw new ContentModelEditorError("validation", `${path}.${key} is not defined`);
	const output = {};
	for (const definition of definitions) {
		if (definition.type === "message" || definition.type === "accordion" || definition.type === "tab") continue;
		if (definition.type === "password") throw new ContentModelEditorError("unsupported-capability", `${path}.${definition.key} requires a private credential adapter`);
		if (definition.type === "clone") throw new ContentModelEditorError("unsupported-capability", `${path}.${definition.key} requires a clone resolver`);
		if (!Object.prototype.hasOwnProperty.call(input, definition.key)) {
			if (definition.required) throw new ContentModelEditorError("validation", `${path}.${definition.key} is required`);
			continue;
		}
		const raw = input[definition.key];
		if (definition.type === "group") {
			if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new ContentModelEditorError("validation", `${path}.${definition.key} must be an object`);
			output[definition.key] = await validateGroupMap(definition.settings.fields, raw, before[definition.key] ?? {}, registry, valueContext, `${path}.${definition.key}`);
			continue;
		}
		if (definition.type === "repeater") {
			const parsed = await validateFieldValue(definition, raw, registry, valueContext), rows = [];
			const beforeRows = Array.isArray(before[definition.key]) ? before[definition.key] : [];
			for (const row of parsed) {
				const previous = beforeRows.find((candidate) => candidate.rowId === row.rowId)?.values ?? {};
				rows.push(Object.freeze({
					rowId: row.rowId,
					values: await validateGroupMap(definition.settings.fields, row.values, previous, registry, valueContext, `${path}.${definition.key}.${row.rowId}`)
				}));
			}
			output[definition.key] = Object.freeze(rows);
			continue;
		}
		if (definition.type === "flexible") {
			const parsed = await validateFieldValue(definition, raw, registry, valueContext), layouts = definition.settings.layouts, rows = [], beforeRows = Array.isArray(before[definition.key]) ? before[definition.key] : [];
			for (const row of parsed) {
				const layout = layouts.find((candidate) => candidate.key === row.layoutKey);
				if (!layout) throw new ContentModelEditorError("validation", `${path}.${definition.key} references an unknown layout`);
				const previous = beforeRows.find((candidate) => candidate.rowId === row.rowId && candidate.layoutKey === row.layoutKey)?.values ?? {};
				rows.push(Object.freeze({
					rowId: row.rowId,
					layoutKey: row.layoutKey,
					values: await validateGroupMap(layout.fields, row.values, previous, registry, valueContext, `${path}.${definition.key}.${row.rowId}`)
				}));
			}
			output[definition.key] = Object.freeze(rows);
			continue;
		}
		const parsed = await validateFieldValue(definition, raw, registry, valueContext);
		if (definition.type === "relationship") await authorizeRelationshipReplacement(definition, Array.isArray(before[definition.key]) ? before[definition.key] : [], parsed, valueContext.authorization);
		output[definition.key] = parsed;
	}
	return Object.freeze(output);
}
async function writeGroupNodes(tx, ref, group, values) {
	await tx.execute(sql`DELETE FROM field_value_nodes WHERE entity_type=${ref.entityType} AND entity_id=${ref.entityId} AND group_key=${group.key}`);
	const ids = /* @__PURE__ */ new Map(), nodeIdForPath = (path) => {
		let id = ids.get(path);
		if (!id) {
			id = crypto.randomUUID();
			ids.set(path, id);
		}
		return id;
	}, rows = encodeFieldValueNodes(group, values, {
		entityType: ref.entityType,
		entityId: ref.entityId,
		definitionRevision: group.revision,
		nodeIdForPath
	});
	const now = (/* @__PURE__ */ new Date()).toISOString();
	for (const row of rows) await tx.execute(sql`INSERT INTO field_value_nodes (node_id,entity_type,entity_id,group_key,definition_revision,field_key,path,parent_node_id,row_id,layout_key,node_kind,ordinal,is_null,value_text,value_number,value_boolean,value_date_time,value_ref,value_json,created_at,updated_at) VALUES (${row.nodeId},${row.entityType},${row.entityId},${row.groupKey},${row.definitionRevision},${row.fieldKey},${row.path},${row.parentNodeId},${row.rowId},${row.layoutKey},${row.nodeKind},${row.ordinal},${row.isNull},${row.valueText},${row.valueNumber},${row.valueBoolean},${row.valueDateTime},${row.valueRef},${row.valueJson},${now},${now})`);
}
async function editorTransaction(env, callback) {
	const mutation = getMutationDb(env);
	if (mutation.dialect !== "postgres") throw new ContentModelEditorError("unsupported-capability", "combined content editor mutations require a callback PostgreSQL transaction");
	return mutation.db.transaction((tx) => callback(tx));
}
async function newestRevisionSeq(tx, entryId) {
	const rows = await execute(tx, sql`SELECT MAX(seq) AS seq FROM content_revisions WHERE entry_id=${entryId}`);
	return rows[0]?.seq === null || rows[0]?.seq === void 0 ? 0 : Number(rows[0].seq);
}
async function createdParentRevision(tx, entryId, afterSeq) {
	const rows = await execute(tx, sql`SELECT id,seq FROM content_revisions WHERE entry_id=${entryId} AND seq>${afterSeq} ORDER BY seq,id`);
	if (rows.length !== 1) throw new ContentModelEditorError("integrity", "content update did not create exactly one parent revision");
	return rows[0].id;
}
function fieldChanges(input) {
	return input.fieldValues !== void 0 || input.blockDocument !== void 0;
}
function ensurePatchForRevision(entry, type, patch, needsParent) {
	if (Object.keys(patch).length > 0) return patch;
	if (!needsParent) throw new ContentModelEditorError("validation", "save contains no content changes");
	if (type.supports.includes("title")) return Object.freeze({ title: entry.title });
	if (type.supports.includes("editor") || type.supports.includes("blocks")) return Object.freeze({ body: entry.body });
	return Object.freeze({ sticky: entry.sticky });
}
async function saveContentModelEditorInTransaction(tx, principal, entryId, input, sources = {}) {
	requireAdmin(principal);
	const operationId = boundedOperationId(input.operationId), expectedUpdatedAt = dateValue(input.expectedUpdatedAt, "expectedUpdatedAt"), requestHash = await canonicalContentMigrationHash({
		entryId,
		principalScope: editorPrincipalScope(principal),
		input: {
			...input,
			operationId: void 0
		}
	}), hasAuxChanges = fieldChanges(input);
	const entry = await readEntry(tx, entryId), definitions = await editorDefinitions(tx, sources), pinned = await pinnedTypeStatus(tx, entry, definitions.types, definitions.statuses), contentPrincipal = adminContentPrincipal(principal.userId, [...definitions.types, pinned.type]);
	authorizeContentAction({
		principal: contentPrincipal,
		type: pinned.type,
		status: pinned.status,
		operation: "edit",
		entry
	});
	const ref = Object.freeze({
		entityType: "content",
		entityId: entry.id
	}), context = fieldsContext(principal.userId), fieldsPrincipal = context.principal, fieldsAuth = context.authorization;
	if (hasAuxChanges) {
		if (!fieldsPrincipal || !fieldsAuth) throw new ContentModelEditorError("access-denied", "fields authorization is unavailable");
		await fieldsAuth.assert(fieldsPrincipal, "write", ref);
	}
	const replay = await readEditorReceipt(tx, operationId);
	if (replay) {
		if (replay.requestHash !== requestHash || replay.principalScope !== editorPrincipalScope(principal)) throw new ContentModelEditorError("validation", "operationId was already used for different editor input");
		return deserializeSaveResult(replay.result);
	}
	if (entry.updatedAt.getTime() !== expectedUpdatedAt.getTime()) throw new ContentModelEditorError("stale-version", "content changed since the editor snapshot");
	const txIdentity = getTransactionIdentity(tx), beforeFields = await currentFieldsState(tx, ref, context), beforeGroups = hasAuxChanges ? await matchedFieldGroups(tx, entry, sources, context, principal.roles, beforeFields) : Object.freeze([]), revisionsEnabled = pinned.type.supports.includes("revisions"), blocksEnabled = pinned.type.supports.includes("blocks");
	assertFieldsTransactionIdentity(tx, txIdentity);
	if (hasAuxChanges) {
		if (typeof input.expectedFieldsVersion !== "string" || !input.expectedFieldsVersion || input.expectedFieldsVersion.length > 128) throw new ContentModelEditorError("validation", "expectedFieldsVersion is required for field or block updates");
		if (await editorFieldsVersion(beforeGroups, beforeFields.blockDocument, context) !== input.expectedFieldsVersion) throw new ContentModelEditorError("stale-version", "fields changed since the editor snapshot");
	}
	let validatedBlock, blockRegistry, bodyFallback;
	if (blocksEnabled) {
		if (Object.prototype.hasOwnProperty.call(input.patch, "body")) throw new ContentModelEditorError("validation", "body is derived from the canonical block document for this content type");
		if (input.blockDocument !== void 0) {
			if (!revisionsEnabled) throw new ContentModelEditorError("unsupported-capability", "block editing requires content revisions for linked transaction identity");
			if (typeof input.expectedBlockVersion !== "string" || !input.expectedBlockVersion) throw new ContentModelEditorError("validation", "expectedBlockVersion is required for block updates");
			blockRegistry = await resolveFieldBlocks(tx, { codeBlocks: sources.blocks });
			const resolver = createReusableBlockResolver(tx);
			validatedBlock = await validateBlockDocument(input.blockDocument, blockRegistry, resolver, context, DEFAULT_BLOCK_POLICY);
			bodyFallback = await blockBodyFallback(validatedBlock, blockRegistry, resolver, context);
		}
	} else if (input.blockDocument !== void 0 || input.expectedBlockVersion !== void 0) throw new ContentModelEditorError("validation", "blocks are disabled for this content type");
	const proposedPatch = Object.freeze({
		...input.patch,
		...bodyFallback === void 0 ? {} : { body: bodyFallback }
	});
	const patch = ensurePatchForRevision(entry, pinned.type, proposedPatch, hasAuxChanges), beforeSeq = revisionsEnabled ? await newestRevisionSeq(tx, entry.id) : 0, deps = await contentWriteDeps(tx, [...definitions.types, pinned.type]);
	const updated = await updateContentEntry(tx, entry.id, {
		patch,
		expectedUpdatedAt,
		operationId: `${operationId}:content`
	}, contentPrincipal, deps), parentRevisionId = revisionsEnabled ? await createdParentRevision(tx, entry.id, beforeSeq) : void 0;
	let fieldsRevisionId;
	if (hasAuxChanges && revisionsEnabled) fieldsRevisionId = (await captureFieldsRevision(tx, ref, {
		expectedVersion: beforeFields.version,
		operationId: `${operationId}:fields-revision`,
		expectedTransactionIdentity: txIdentity,
		parentRevisionId
	}, context)).id;
	if (input.fieldValues !== void 0) {
		const available = await matchedFieldGroups(tx, updated, sources, context, principal.roles), byKey = new Map(available.map((group) => [group.definition.key, group])), registry = context.fieldTypeRegistry ?? createFieldTypeRegistry(), valueContext = await fieldValueContext(tx, context);
		for (const [groupKey, raw] of Object.entries(input.fieldValues)) {
			const groupState = byKey.get(groupKey);
			if (!groupState) throw new ContentModelEditorError("validation", `field group ${groupKey} does not match the updated content context`);
			if (!groupState.writable) throw new ContentModelEditorError("unsupported-capability", `field group ${groupKey} is read-only: ${groupState.disabledReason}`);
			const validated = await validateGroupMap(groupState.definition.fields, raw, beforeFields.values[groupKey] ?? Object.freeze({}), registry, valueContext, `fields.${groupKey}`);
			await writeGroupNodes(tx, ref, groupState.definition, validated);
		}
	}
	let blockDocument = beforeFields.blockDocument;
	if (validatedBlock && blockRegistry) blockDocument = await setBlockDocument(tx, ref, validatedBlock, {
		expectedVersion: input.expectedBlockVersion,
		operationId: `${operationId}:block`,
		expectedTransactionIdentity: txIdentity,
		parentRevisionId
	}, blockRegistry, context);
	const result = Object.freeze({
		entry: updated,
		...parentRevisionId ? { parentRevisionId } : {},
		...fieldsRevisionId ? { fieldsRevisionId } : {},
		...blocksEnabled ? { blockDocument } : {}
	});
	await writeEditorReceipt(tx, operationId, requestHash, principal, result);
	return result;
}
async function saveContentModelEditor(env, principal, entryId, input, sources = {}) {
	return editorTransaction(env, (tx) => saveContentModelEditorInTransaction(tx, principal, entryId, input, sources));
}
async function restoreContentModelEditorInTransaction(tx, principal, entryId, input, sources = {}) {
	requireAdmin(principal);
	const operationId = boundedOperationId(input.operationId), expectedUpdatedAt = dateValue(input.expectedUpdatedAt, "expectedUpdatedAt");
	const requestHash = await canonicalContentMigrationHash({
		entryId,
		principalScope: editorPrincipalScope(principal),
		input: {
			...input,
			operationId: void 0
		}
	});
	const entry = await readEntry(tx, entryId), definitions = await editorDefinitions(tx, sources), pinned = await pinnedTypeStatus(tx, entry, definitions.types, definitions.statuses), contentPrincipal = adminContentPrincipal(principal.userId, [...definitions.types, pinned.type]);
	authorizeContentAction({
		principal: contentPrincipal,
		type: pinned.type,
		status: pinned.status,
		operation: "edit",
		entry
	});
	if (!pinned.type.supports.includes("revisions")) throw new ContentModelEditorError("unsupported-capability", "content type does not support linked revisions");
	const ref = Object.freeze({
		entityType: "content",
		entityId: entry.id
	}), context = fieldsContext(principal.userId), fieldsPrincipal = context.principal, auth = context.authorization;
	if (!fieldsPrincipal || !auth) throw new ContentModelEditorError("access-denied", "fields authorization is unavailable");
	await auth.assert(fieldsPrincipal, "write", ref);
	const replay = await readEditorRestoreReceipt(tx, operationId);
	if (replay) {
		if (replay.requestHash !== requestHash || replay.principalScope !== editorPrincipalScope(principal)) throw new ContentModelEditorError("validation", "operationId was already used for different editor restore input");
		return deserializeRestoreResult(replay.result);
	}
	if (entry.updatedAt.getTime() !== expectedUpdatedAt.getTime()) throw new ContentModelEditorError("stale-version", "content changed since the editor snapshot");
	if (typeof input.expectedFieldsVersion !== "string" || !input.expectedFieldsVersion || input.expectedFieldsVersion.length > 128) throw new ContentModelEditorError("validation", "expectedFieldsVersion is required for linked restore");
	const beforeFields = await currentFieldsState(tx, ref, context);
	if (await editorFieldsVersion(await matchedFieldGroups(tx, entry, sources, context, principal.roles, beforeFields), beforeFields.blockDocument, context) !== input.expectedFieldsVersion) throw new ContentModelEditorError("stale-version", "fields changed since the editor snapshot");
	const sourceContent = await getContentRevision(tx, entry.id, input.contentRevisionId, contentPrincipal), sourceFields = await getFieldsRevision(tx, ref, input.fieldsRevisionId, context);
	if (!sourceFields || sourceFields.parentRevisionId !== sourceContent.id) throw new ContentModelEditorError("validation", "linked revision pair is unavailable");
	const txIdentity = getTransactionIdentity(tx);
	assertFieldsTransactionIdentity(tx, txIdentity);
	const beforeSeq = await newestRevisionSeq(tx, entry.id), deps = await contentWriteDeps(tx, [...definitions.types, pinned.type]);
	const restoredEntry = await restoreContentRevision(tx, entry.id, sourceContent.id, {
		expectedUpdatedAt,
		operationId: `${operationId}:content-restore`
	}, contentPrincipal, deps);
	const undoParentRevisionId = await createdParentRevision(tx, entry.id, beforeSeq);
	const undoFields = await captureFieldsRevision(tx, ref, {
		expectedVersion: beforeFields.version,
		operationId: `${operationId}:undo-fields`,
		expectedTransactionIdentity: txIdentity,
		parentRevisionId: undoParentRevisionId
	}, context);
	const restoredFields = await restoreFieldsRevision(tx, ref, sourceFields.id, {
		expectedVersion: beforeFields.version,
		operationId: `${operationId}:fields-restore`,
		expectedTransactionIdentity: txIdentity,
		parentRevisionId: sourceContent.id
	}, context);
	const blockDocument = pinned.type.supports.includes("blocks") ? await getBlockDocument(tx, ref, context) : void 0;
	const result = Object.freeze({
		entry: restoredEntry,
		contentRevisionId: sourceContent.id,
		fieldsRevisionId: restoredFields.revision.id,
		undoParentRevisionId,
		undoFieldsRevisionId: undoFields.id,
		...blockDocument === void 0 ? {} : { blockDocument }
	});
	await writeEditorRestoreReceipt(tx, operationId, requestHash, principal, result);
	return result;
}
async function restoreContentModelEditor(env, principal, entryId, input, sources = {}) {
	return editorTransaction(env, (tx) => restoreContentModelEditorInTransaction(tx, principal, entryId, input, sources));
}
function defaultTransitionRules(statuses) {
	return Object.freeze(statuses.filter((status) => status.active).map((status) => Object.freeze({
		from: "*",
		to: status.key,
		capability: status.published ? "publish" : "editOwn",
		...status.transitionInput === "scheduleAt" ? { requiresScheduleAt: true } : {}
	})));
}
function exactRegistry(current, exact) {
	return Object.freeze([...current.filter((item) => item.key !== exact.key), exact]);
}
async function transitionContentModelEditorInTransaction(tx, principal, entryId, input, sources = {}) {
	requireAdmin(principal);
	const operationId = boundedOperationId(input.operationId), expectedUpdatedAt = dateValue(input.expectedUpdatedAt, "expectedUpdatedAt"), scheduleAt = input.scheduleAt === void 0 ? void 0 : dateValue(input.scheduleAt, "scheduleAt"), publishedAt = input.publishedAt === void 0 ? void 0 : dateValue(input.publishedAt, "publishedAt"), expectedFieldsVersion = typeof input.expectedFieldsVersion === "string" && input.expectedFieldsVersion.length <= 128 ? input.expectedFieldsVersion : "";
	if (!expectedFieldsVersion) throw new ContentModelEditorError("validation", "expectedFieldsVersion is required for status transition");
	const requestHash = await canonicalContentMigrationHash({
		entryId,
		principalScope: editorPrincipalScope(principal),
		input: {
			...input,
			operationId: void 0
		}
	});
	const entry = await readEntry(tx, entryId), definitions = await editorDefinitions(tx, sources), pinned = await pinnedTypeStatus(tx, entry, definitions.types, definitions.statuses), contentPrincipal = adminContentPrincipal(principal.userId, [...definitions.types, pinned.type]);
	authorizeContentAction({
		principal: contentPrincipal,
		type: pinned.type,
		status: pinned.status,
		operation: "edit",
		entry
	});
	const ref = Object.freeze({
		entityType: "content",
		entityId: entry.id
	}), context = fieldsContext(principal.userId), fieldsPrincipal = context.principal, fieldsAuth = context.authorization;
	if (!fieldsPrincipal || !fieldsAuth) throw new ContentModelEditorError("access-denied", "fields authorization is unavailable");
	await fieldsAuth.assert(fieldsPrincipal, "write", ref);
	const replay = await readEditorTransitionReceipt(tx, operationId);
	if (replay) {
		if (replay.requestHash !== requestHash || replay.principalScope !== editorPrincipalScope(principal)) throw new ContentModelEditorError("validation", "operationId was already used for different editor transition input");
		return deserializeTransitionResult(replay.result);
	}
	if (entry.updatedAt.getTime() !== expectedUpdatedAt.getTime()) throw new ContentModelEditorError("stale-version", "content changed since the editor snapshot");
	const txIdentity = getTransactionIdentity(tx), beforeFields = await currentFieldsState(tx, ref, context);
	if (await editorFieldsVersion(await matchedFieldGroups(tx, entry, sources, context, principal.roles, beforeFields), beforeFields.blockDocument, context) !== expectedFieldsVersion) throw new ContentModelEditorError("stale-version", "fields changed since the editor snapshot");
	const revisionsEnabled = pinned.type.supports.includes("revisions"), beforeSeq = revisionsEnabled ? await newestRevisionSeq(tx, entry.id) : 0;
	assertFieldsTransactionIdentity(tx, txIdentity);
	const types = exactRegistry(definitions.types, pinned.type), statuses = exactRegistry(definitions.statuses, pinned.status), rules = sources.transitionRules ?? defaultTransitionRules(definitions.statuses), deps = Object.freeze({
		types,
		statuses,
		rules,
		outbox: contentOutbox()
	});
	const transitioned = await transitionContent(tx, entry.id, {
		to: input.to,
		...scheduleAt ? { scheduleAt } : {},
		...publishedAt ? { publishedAt } : {},
		expectedUpdatedAt,
		operationId: `${operationId}:content-transition`
	}, contentPrincipal, deps), parentRevisionId = revisionsEnabled ? await createdParentRevision(tx, entry.id, beforeSeq) : void 0;
	let fieldsRevisionId;
	if (parentRevisionId) fieldsRevisionId = (await captureFieldsRevision(tx, ref, {
		expectedVersion: beforeFields.version,
		operationId: `${operationId}:fields-revision`,
		expectedTransactionIdentity: txIdentity,
		parentRevisionId
	}, context)).id;
	const result = Object.freeze({
		entry: transitioned,
		...parentRevisionId ? { parentRevisionId } : {},
		...fieldsRevisionId ? { fieldsRevisionId } : {}
	});
	await writeEditorTransitionReceipt(tx, operationId, requestHash, principal, result);
	return result;
}
async function transitionContentModelEditor(env, principal, entryId, input, sources = {}) {
	return editorTransaction(env, (tx) => transitionContentModelEditorInTransaction(tx, principal, entryId, input, sources));
}
//#endregion
export { saveContentModelEditor as a, restoreContentModelEditor as i, adminContentPrincipal as n, transitionContentModelEditor as o, loadContentModelEditorSnapshot as r, ContentModelEditorError as t };
