globalThis.process ??= {};
globalThis.process.env ??= {};
import { r as __exportAll } from "./rolldown-runtime_-UtK6b3T.mjs";
import { Tt as sql } from "./d1_DTqC4o_t.mjs";
import { F as readFlatContentEntries, d as mapContentError, u as jsonOk } from "./crypto_CY2zBtG3.mjs";
import { r as canonicalContentMigrationHash, v as planContentModelMigration } from "./dist_YU938xCs.mjs";
import { n as getFullDb } from "./db_BQ9NAt8x.mjs";
import { I as readFlatFieldValues, ht as planFieldsRecursiveEavMigration, pt as canonicalFieldsMigrationHash } from "./dist_BBLvfyU8.mjs";
import { i as requireAdmin } from "./auth_B0DWM0G6.mjs";
import { env } from "cloudflare:workers";
//#region src/lib/content-model-migration.ts
var ContentModelAdoptionError = class extends Error {
	code;
	detail;
	name = "ContentModelAdoptionError";
	constructor(code, detail) {
		super(`content model adoption ${code}: ${detail}`);
		this.code = code;
		this.detail = detail;
	}
};
var fail = (code, detail) => {
	throw new ContentModelAdoptionError(code, detail);
};
function parseJson(value, field) {
	if (typeof value !== "string") return value;
	try {
		return JSON.parse(value);
	} catch {
		return fail("integrity", `${field} contains invalid JSON`);
	}
}
function iso(value) {
	if (value === null || value === void 0) return null;
	const date = value instanceof Date ? value : new Date(String(value));
	if (Number.isNaN(date.getTime())) return String(value);
	return date.toISOString();
}
function bool(value) {
	return value === true || value === 1 || value === "1";
}
function normalizeValue(value) {
	if (value instanceof Date) return value.toISOString();
	if (Array.isArray(value)) return value.map(normalizeValue);
	if (typeof value === "object" && value !== null) {
		const result = {};
		for (const key of Object.keys(value).sort()) {
			const child = value[key];
			if (child !== void 0) result[key] = normalizeValue(child);
		}
		return result;
	}
	return value;
}
async function checkpoint(rows) {
	const normalized = rows.map(normalizeValue);
	return Object.freeze({
		count: normalized.length,
		hash: await canonicalContentMigrationHash(normalized)
	});
}
async function readLegacyGroups(reader) {
	const rows = await reader.execute(sql`SELECT entity_type AS "entityType",sub_type AS "subType",key,label,fields,position FROM field_groups ORDER BY entity_type,key`);
	return Object.freeze(rows.map((row) => {
		const fields = parseJson(row.fields, `field group ${row.key}`);
		if (!Array.isArray(fields)) fail("integrity", `field group ${row.key} fields are malformed`);
		return Object.freeze({
			key: String(row.key),
			label: String(row.label),
			location: Object.freeze({
				entityType: String(row.entityType),
				...row.subType === null ? {} : { subType: String(row.subType) }
			}),
			fields,
			position: Number(row.position)
		});
	}));
}
async function readTerms(reader) {
	return reader.execute(sql`SELECT id,taxonomy,slug,name,parent_id AS "parentId",depth,created_at AS "createdAt" FROM content_terms ORDER BY taxonomy,depth,id`);
}
async function readAssignments(reader) {
	return reader.execute(sql`SELECT entry_id AS "entryId",term_id AS "termId" FROM content_entry_terms ORDER BY entry_id,term_id`);
}
async function readRevisions(reader) {
	return (await reader.execute(sql`SELECT id,entry_id AS "entryId",seq,title,body,slug,type,term_ids AS "termIds",snapshot,editor,created_at AS "createdAt" FROM content_revisions ORDER BY entry_id,seq,id`)).map((row) => Object.freeze({
		...row,
		termIds: parseJson(row.termIds, "revision.termIds"),
		snapshot: row.snapshot === null ? null : parseJson(row.snapshot, "revision.snapshot"),
		createdAt: iso(row.createdAt)
	}));
}
async function readComments(reader) {
	return reader.execute(sql`SELECT seq,id,target_type AS "targetType",target_id AS "targetId",parent_id AS "parentId",depth,author_kind AS "authorKind",author_user_id AS "authorUserId",author_name AS "authorName",author_email AS "authorEmail",author_url AS "authorUrl",body,body_html AS "bodyHtml",status,created_at_ms AS "createdAtMs",created_at AS "createdAt",edited_at AS "editedAt" FROM comments ORDER BY seq,id`);
}
async function readTranslations(reader) {
	return (await reader.execute(sql`SELECT id,entity_type AS "entityType",entity_id AS "entityId",field_key AS "fieldKey",locale,value,source_hash AS "sourceHash",manual_override AS "manualOverride",model_id AS "modelId",status,translated_at AS "translatedAt",created_at AS "createdAt",updated_at AS "updatedAt" FROM translation_value ORDER BY entity_type,entity_id,field_key,locale`)).map((row) => Object.freeze({
		...row,
		manualOverride: bool(row.manualOverride),
		translatedAt: iso(row.translatedAt),
		createdAt: iso(row.createdAt),
		updatedAt: iso(row.updatedAt)
	}));
}
async function readLanguages(reader) {
	return (await reader.execute(sql`SELECT code,name_native AS "nameNative",name_english AS "nameEnglish",direction,search_config AS "searchConfig",is_active AS "isActive",is_default AS "isDefault",sort_order AS "sortOrder",created_at AS "createdAt",updated_at AS "updatedAt" FROM languages ORDER BY code`)).map((row) => Object.freeze({
		...row,
		isActive: bool(row.isActive),
		isDefault: bool(row.isDefault),
		sortOrder: Number(row.sortOrder),
		createdAt: iso(row.createdAt),
		updatedAt: iso(row.updatedAt)
	}));
}
async function readMedia(reader) {
	return (await reader.execute(sql`SELECT key,url,content_type AS "contentType",size,width,height,uploader_id AS "uploaderId",created_at AS "createdAt" FROM media_assets ORDER BY key`)).map((row) => Object.freeze({
		...row,
		size: Number(row.size),
		width: row.width === null ? null : Number(row.width),
		height: row.height === null ? null : Number(row.height),
		createdAt: iso(row.createdAt)
	}));
}
function contentRoutes(entries) {
	return Object.freeze([...entries].sort((a, b) => a.id.localeCompare(b.id)).map((entry) => Object.freeze({
		id: entry.id,
		type: entry.type,
		slug: entry.slug
	})));
}
function searchIdentity(entries) {
	return Object.freeze([...entries].sort((a, b) => a.id.localeCompare(b.id)).map((entry) => Object.freeze({
		id: entry.id,
		type: entry.type,
		slug: entry.slug,
		title: entry.title,
		body: entry.body,
		status: entry.status,
		visibility: entry.visibility
	})));
}
function validateSatelliteReferences(input) {
	const entryIds = new Set(input.entries.map((entry) => entry.id)), typeKeys = new Set(input.entries.map((entry) => entry.type));
	const termIds = new Set(input.terms.map((row) => String(row.id))), commentIds = new Set(input.comments.map((row) => String(row.id))), mediaIds = new Set(input.media.map((row) => String(row.key)));
	const entryType = new Map(input.entries.map((entry) => [entry.id, entry.type])), termTaxonomy = new Map(input.terms.map((row) => [String(row.id), String(row.taxonomy)]));
	const typeTaxonomies = /* @__PURE__ */ new Map();
	for (const term of input.terms) if (term.parentId !== null && term.parentId !== void 0 && !termIds.has(String(term.parentId))) fail("integrity", `term ${String(term.id)} references missing parent`);
	for (const assignment of input.assignments) {
		const entryId = String(assignment.entryId), termId = String(assignment.termId);
		if (!entryIds.has(entryId)) fail("integrity", `term assignment references missing entry ${entryId}`);
		if (!termIds.has(termId)) fail("integrity", `term assignment references missing term ${termId}`);
		const type = entryType.get(entryId);
		const set = typeTaxonomies.get(type) ?? /* @__PURE__ */ new Set();
		set.add(termTaxonomy.get(termId));
		typeTaxonomies.set(type, set);
	}
	for (const revision of input.revisions) {
		const entryId = String(revision.entryId);
		if (!entryIds.has(entryId)) fail("integrity", `revision ${String(revision.id)} references missing entry ${entryId}`);
		const ids = revision.termIds;
		if (Array.isArray(ids)) {
			for (const id of ids) if (!termIds.has(String(id))) fail("integrity", `revision ${String(revision.id)} references missing term ${String(id)}`);
		}
	}
	for (const comment of input.comments) {
		if (String(comment.targetType) === "content_entry" && !entryIds.has(String(comment.targetId))) fail("integrity", `comment ${String(comment.id)} references missing content entry`);
		if (comment.parentId !== null && comment.parentId !== void 0 && !commentIds.has(String(comment.parentId))) fail("integrity", `comment ${String(comment.id)} references missing parent comment`);
	}
	for (const translation of input.translations) if (String(translation.entityType) === "content" && !entryIds.has(String(translation.entityId))) fail("integrity", `translation ${String(translation.id)} references missing content entry`);
	for (const value of input.fields) {
		if (value.entityType === "content" && !entryIds.has(value.entityId)) fail("integrity", `field value ${value.id} references missing content entry ${value.entityId}`);
		if ((value.refType === "content" || value.refType === "content_entry") && value.refId !== null && !entryIds.has(value.refId)) fail("integrity", `field value ${value.id} references missing related content entry ${value.refId}`);
		if ((value.refType === "media" || value.refType === "media_asset") && value.refId !== null && !mediaIds.has(value.refId)) fail("integrity", `field value ${value.id} references missing media ${value.refId}`);
	}
	for (const group of input.groups) if (group.location.entityType === "content" && group.location.subType !== void 0 && !typeKeys.has(group.location.subType)) fail("integrity", `field group ${group.key} references missing content subtype ${group.location.subType}`);
	return Object.freeze(Object.fromEntries([...typeTaxonomies].sort(([a], [b]) => a.localeCompare(b)).map(([type, set]) => [type, Object.freeze([...set].sort())])));
}
async function enrichContentDefinitions(plan, typeTaxonomies) {
	const definitions = [];
	for (const definition of plan.definitions) {
		if (definition.kind !== "type") {
			definitions.push(definition);
			continue;
		}
		const raw = definition.definition, taxonomies = typeTaxonomies[definition.key] ?? [];
		const next = Object.freeze({
			...raw,
			taxonomies: Object.freeze(taxonomies)
		});
		definitions.push(Object.freeze({
			...definition,
			canonicalHash: await canonicalContentMigrationHash(next),
			definition: next
		}));
	}
	return Object.freeze(definitions);
}
async function satelliteCheckpoints(reader, entries, rows) {
	const data = rows ?? {
		terms: await readTerms(reader),
		assignments: await readAssignments(reader),
		revisions: await readRevisions(reader),
		comments: await readComments(reader),
		translations: await readTranslations(reader),
		languages: await readLanguages(reader),
		media: await readMedia(reader)
	};
	return Object.freeze({
		terms: await checkpoint(data.terms),
		assignments: await checkpoint(data.assignments),
		revisions: await checkpoint(data.revisions),
		comments: await checkpoint(data.comments),
		translations: await checkpoint(data.translations),
		languages: await checkpoint(data.languages),
		media: await checkpoint(data.media),
		routes: await checkpoint(contentRoutes(entries)),
		search: await checkpoint(searchIdentity(entries))
	});
}
async function planLegacyContentModelAdoption(reader, options) {
	const defaultTemplateKey = options.defaultTemplateKey.trim();
	if (!defaultTemplateKey || defaultTemplateKey.length > 128) fail("validation", "defaultTemplateKey must be bounded and non-empty");
	const entries = await readFlatContentEntries({
		adapter: reader.adapter,
		execute: reader.execute
	}), fields = await readFlatFieldValues({
		adapter: reader.adapter,
		execute: reader.execute
	}), groups = await readLegacyGroups(reader);
	const rows = {
		terms: await readTerms(reader),
		assignments: await readAssignments(reader),
		revisions: await readRevisions(reader),
		comments: await readComments(reader),
		translations: await readTranslations(reader),
		languages: await readLanguages(reader),
		media: await readMedia(reader)
	};
	const typeTaxonomies = validateSatelliteReferences({
		entries,
		fields,
		groups,
		...rows
	});
	const content = await planContentModelMigration(entries, { defaultTemplateKey }), fieldPlan = await planFieldsRecursiveEavMigration(fields, groups), contentDefinitions = await enrichContentDefinitions(content, typeTaxonomies), satellites = await satelliteCheckpoints(reader, entries, rows), fieldGroupsHash = await canonicalFieldsMigrationHash(groups);
	const rest = Object.freeze({
		adapter: reader.adapter,
		defaultTemplateKey,
		content,
		contentDefinitions,
		fields: fieldPlan,
		fieldGroupsHash,
		typeTaxonomies,
		satellites
	});
	return Object.freeze({
		planHash: await canonicalContentMigrationHash(rest),
		...rest
	});
}
function summarizeLegacyContentModelAdoption(plan) {
	return Object.freeze({
		planHash: plan.planHash,
		adapter: plan.adapter,
		defaultTemplateKey: plan.defaultTemplateKey,
		content: Object.freeze({
			source: plan.content.source,
			target: plan.content.target,
			definitions: plan.contentDefinitions.length
		}),
		fields: Object.freeze({
			source: plan.fields.source,
			target: plan.fields.target
		}),
		satellites: plan.satellites,
		readyForEnforcement: false
	});
}
//#endregion
//#region src/pages/api/admin/content-model/adoption.ts
var adoption_exports = /* @__PURE__ */ __exportAll({
	GET: () => GET,
	prerender: () => false
});
var GET = async ({ request, cookies }) => {
	try {
		await requireAdmin(request, env, cookies);
		const { db, dialect } = getFullDb(env);
		const execute = async (query) => {
			const value = await db.execute(query);
			if (Array.isArray(value)) return value;
			const rows = value?.rows;
			return Array.isArray(rows) ? rows : [];
		};
		const plan = await planLegacyContentModelAdoption({
			adapter: dialect === "postgres" ? "postgres" : "d1",
			execute
		}, { defaultTemplateKey: "default" });
		return jsonOk(summarizeLegacyContentModelAdoption(plan));
	} catch (error) {
		return mapContentError(error);
	}
};
//#endregion
//#region \0virtual:astro:page:src/pages/api/admin/content-model/adoption@_@ts
var page = () => adoption_exports;
//#endregion
export { page };
