import * as drizzle from "drizzle-orm";
import { desc, sql } from "drizzle-orm";
import { getTransactionIdentity } from "@platform-modules/db";
import { bigserial, boolean, check, index, integer, jsonb, pgTable, primaryKey, text, timestamp, uniqueIndex, uuid } from "drizzle-orm/pg-core";
//#region src/schema.ts
const definitionColumns = () => ({
	key: text("key").primaryKey(),
	origin: text("origin").$type().notNull(),
	version: integer("version").notNull().default(1),
	active: boolean("active").notNull().default(true),
	currentRevision: integer("current_revision").notNull().default(1),
	canonicalHash: text("canonical_hash").notNull(),
	definition: jsonb("definition").$type().notNull(),
	shadowedDbVersion: integer("shadowed_db_version"),
	hostSessionId: text("host_session_id"),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
});
const contentTypeDefinitions = pgTable("content_type_definitions", definitionColumns(), (t) => [
	check("content_type_definitions_version_positive", sql`${t.version} > 0`),
	check("content_type_definitions_revision_positive", sql`${t.currentRevision} > 0`),
	index("content_type_definitions_active_idx").on(t.active, t.key)
]);
const contentStatusDefinitions = pgTable("content_status_definitions", definitionColumns(), (t) => [
	check("content_status_definitions_version_positive", sql`${t.version} > 0`),
	check("content_status_definitions_revision_positive", sql`${t.currentRevision} > 0`),
	index("content_status_definitions_active_idx").on(t.active, t.key)
]);
const contentDefinitionVersions = pgTable("content_definition_versions", {
	definitionKind: text("definition_kind").$type().notNull(),
	definitionKey: text("definition_key").notNull(),
	revision: integer("revision").notNull(),
	canonicalHash: text("canonical_hash").notNull(),
	definition: jsonb("definition").$type().notNull(),
	origin: text("origin").$type().notNull(),
	hostSessionId: text("host_session_id"),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [
	primaryKey({ columns: [
		t.definitionKind,
		t.definitionKey,
		t.revision
	] }),
	check("content_definition_versions_revision_positive", sql`${t.revision} > 0`),
	index("content_definition_versions_hash_idx").on(t.canonicalHash)
]);
/** Private credential bytes never join the public entry projection. */
const contentPasswordCredentials = pgTable("content_password_credentials", {
	entryId: uuid("entry_id").primaryKey(),
	credentialVersion: text("credential_version").notNull(),
	credential: text("credential").notNull(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [index("content_password_credentials_version_idx").on(t.credentialVersion)]);
const contentLifecycleJournal = pgTable("content_lifecycle_journal", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	operationId: text("operation_id").notNull(),
	kind: text("kind").notNull(),
	entryId: uuid("entry_id"),
	definitionKind: text("definition_kind").$type(),
	definitionKey: text("definition_key"),
	definitionRevision: integer("definition_revision"),
	state: text("state").notNull(),
	payload: jsonb("payload").$type(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [uniqueIndex("content_lifecycle_journal_operation_uq").on(t.operationId), index("content_lifecycle_journal_entry_idx").on(t.entryId, t.createdAt)]);
const contentImportJournal = pgTable("content_import_journal", {
	importId: uuid("import_id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	hostSessionId: text("host_session_id").notNull(),
	manifestHash: text("manifest_hash").notNull(),
	planHash: text("plan_hash").notNull(),
	state: text("state").notNull(),
	version: integer("version").notNull().default(0),
	nextSection: text("next_section"),
	nextOffset: integer("next_offset"),
	highWaterMark: text("high_water_mark"),
	payload: jsonb("payload").$type(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [uniqueIndex("content_import_journal_session_uq").on(t.hostSessionId), check("content_import_journal_version_nonnegative", sql`${t.version} >= 0`)]);
const contentTombstones = pgTable("content_tombstones", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	entryId: uuid("entry_id").notNull(),
	operationId: text("operation_id").notNull(),
	typeKey: text("type_key").notNull(),
	typeDefinitionRevision: integer("type_definition_revision").notNull(),
	statusKey: text("status_key").notNull(),
	statusDefinitionRevision: integer("status_definition_revision").notNull(),
	snapshot: jsonb("snapshot").$type().notNull(),
	deletedAt: timestamp("deleted_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [uniqueIndex("content_tombstones_operation_entry_uq").on(t.operationId, t.entryId), index("content_tombstones_entry_idx").on(t.entryId, t.deletedAt)]);
const contentMigrationCheckpoints = pgTable("content_migration_checkpoints", {
	migrationId: text("migration_id").notNull(),
	adapter: text("adapter").$type().notNull(),
	phase: text("phase").$type().notNull(),
	batchOrdinal: integer("batch_ordinal").notNull().default(0),
	version: integer("version").notNull().default(0),
	lastKey: text("last_key"),
	sourceCount: integer("source_count").notNull(),
	targetCount: integer("target_count"),
	sourceHash: text("source_hash").notNull(),
	targetHash: text("target_hash"),
	highWaterMark: text("high_water_mark"),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [
	primaryKey({ columns: [
		t.migrationId,
		t.adapter,
		t.phase,
		t.batchOrdinal
	] }),
	check("content_migration_checkpoint_batch_nonnegative", sql`${t.batchOrdinal} >= 0`),
	check("content_migration_checkpoint_version_nonnegative", sql`${t.version} >= 0`)
]);
const contentMigrationWriteJournal = pgTable("content_migration_write_journal", {
	seq: bigserial("seq", { mode: "number" }).primaryKey(),
	migrationId: text("migration_id").notNull(),
	operation: text("operation").$type().notNull(),
	entryId: text("entry_id").notNull(),
	beforeValue: jsonb("before_value").$type(),
	afterValue: jsonb("after_value").$type(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [index("content_migration_write_journal_migration_seq_idx").on(t.migrationId, t.seq)]);
const contentEntries = pgTable("content_entries", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	slug: text("slug").notNull(),
	type: text("type").notNull(),
	title: text("title").notNull(),
	body: text("body").notNull().default(""),
	status: text("status").$type().notNull().default("draft"),
	visibility: text("visibility").$type().notNull().default("public"),
	publishedAt: timestamp("published_at", {
		withTimezone: true,
		precision: 3
	}),
	author: text("author").notNull(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	updatedAt: timestamp("updated_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
	parentId: uuid("parent_id"),
	menuOrder: integer("menu_order").notNull().default(0),
	templateKey: text("template_key"),
	excerpt: text("excerpt").notNull().default(""),
	featuredMedia: jsonb("featured_media").$type(),
	commentStatus: text("comment_status").$type().notNull().default("open"),
	pingStatus: text("ping_status").$type().notNull().default("open"),
	sticky: boolean("sticky").notNull().default(false),
	format: text("format"),
	deletedAt: timestamp("deleted_at", {
		withTimezone: true,
		precision: 3
	}),
	lastEditedBy: text("last_edited_by").notNull(),
	typeDefinitionRevision: integer("type_definition_revision").notNull().default(1),
	statusDefinitionRevision: integer("status_definition_revision").notNull().default(1)
}, (t) => [
	uniqueIndex("content_entries_type_slug_uq").on(t.type, t.slug),
	index("content_entries_type_status_pub_idx").on(t.type, t.status, desc(t.publishedAt)),
	index("content_entries_type_status_vis_pub_idx").on(t.type, t.status, t.visibility, desc(t.publishedAt)),
	index("content_entries_parent_order_idx").on(t.type, t.parentId, t.menuOrder, t.id),
	index("content_entries_deleted_idx").on(t.deletedAt),
	index("content_entries_type_definition_idx").on(t.type, t.typeDefinitionRevision),
	index("content_entries_status_definition_idx").on(t.status, t.statusDefinitionRevision)
]);
const contentTerms = pgTable("content_terms", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	taxonomy: text("taxonomy").notNull(),
	slug: text("slug").notNull(),
	name: text("name").notNull(),
	parentId: uuid("parent_id").references(() => contentTerms.id, { onDelete: "restrict" }),
	depth: integer("depth").notNull().default(0),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [uniqueIndex("content_terms_sibling_slug_uq").on(t.taxonomy, sql`COALESCE(${t.parentId}, '00000000-0000-0000-0000-000000000000'::uuid)`, t.slug), index("content_terms_taxonomy_parent_idx").on(t.taxonomy, t.parentId)]);
const contentEntryTerms = pgTable("content_entry_terms", {
	entryId: uuid("entry_id").notNull().references(() => contentEntries.id, { onDelete: "cascade" }),
	termId: uuid("term_id").notNull().references(() => contentTerms.id, { onDelete: "cascade" })
}, (t) => [primaryKey({ columns: [t.entryId, t.termId] }), index("content_entry_terms_term_idx").on(t.termId)]);
const contentRevisions = pgTable("content_revisions", {
	id: uuid("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
	entryId: uuid("entry_id").notNull().references(() => contentEntries.id, { onDelete: "cascade" }),
	seq: bigserial("seq", { mode: "number" }).notNull(),
	title: text("title").notNull(),
	body: text("body").notNull(),
	slug: text("slug").notNull(),
	type: text("type").notNull(),
	termIds: jsonb("term_ids").$type().notNull().$defaultFn(() => []),
	snapshot: jsonb("snapshot").$type(),
	editor: text("editor").notNull(),
	createdAt: timestamp("created_at", {
		withTimezone: true,
		precision: 3
	}).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
}, (t) => [index("content_revisions_entry_seq_idx").on(t.entryId, desc(t.seq))]);
const contentSchema = {
	contentEntries,
	contentTerms,
	contentEntryTerms,
	contentRevisions,
	contentTypeDefinitions,
	contentStatusDefinitions,
	contentDefinitionVersions,
	contentPasswordCredentials,
	contentLifecycleJournal,
	contentImportJournal,
	contentTombstones,
	contentMigrationCheckpoints,
	contentMigrationWriteJournal
};
/** Runtime guard: only an active callback-minted database transaction may enter T4 lifecycle seams. */
function assertActiveContentTransaction(tx) {
	getTransactionIdentity(tx);
}
//#endregion
//#region src/authz.ts
var ContentAuthorizationError = class extends Error {
	operation;
	principalId;
	detail;
	name = "ContentAuthorizationError";
	constructor(operation, principalId, detail) {
		super(`content authorization denied: ${operation} by ${principalId} — ${detail}`);
		this.operation = operation;
		this.principalId = principalId;
		this.detail = detail;
	}
};
function requireEntryStatus(input) {
	if (!input.entry) return input.status;
	if (!input.status || input.status.key !== input.entry.status) throw new ContentAuthorizationError(input.operation, input.principal.id, "entry status metadata is missing or mismatched");
	return input.status;
}
/**
* Normative capability-key resolution from the complete content-type contract.
* Returned keys are conjunctive; callers must never select their own ownership/status branches.
*/
function resolveRequiredContentCapabilities(input) {
	const status = requireEntryStatus(input);
	const capabilities = [];
	const own = input.entry?.author === input.principal.id;
	switch (input.operation) {
		case "manageType":
			capabilities.push("manageType");
			break;
		case "migrateTypeDefinition":
			capabilities.push("manageType", "migrateAll");
			break;
		case "manageTerms":
			capabilities.push("manageTerms");
			if (input.entry) capabilities.push(own ? "editOwn" : "editOthers");
			break;
		case "create":
			capabilities.push("create");
			break;
		case "read":
			capabilities.push("read");
			if (input.entry?.visibility === "private") capabilities.push("readPrivate");
			break;
		case "readProtected":
			capabilities.push("read");
			if (input.entry?.visibility === "private") capabilities.push("readPrivate");
			capabilities.push("readProtected");
			break;
		case "edit":
			if (!input.entry) throw new ContentAuthorizationError(input.operation, input.principal.id, "edit requires exact entry metadata");
			capabilities.push(own ? "editOwn" : "editOthers");
			if (input.entry.visibility === "private") capabilities.push("editPrivate");
			if (status?.published) capabilities.push("editPublished");
			break;
		case "publish":
			if (!input.entry) throw new ContentAuthorizationError(input.operation, input.principal.id, "publish requires exact entry metadata");
			capabilities.push(own ? "editOwn" : "editOthers");
			if (input.entry.visibility === "private") capabilities.push("editPrivate");
			if (status?.published) capabilities.push("editPublished");
			capabilities.push("publish");
			break;
		case "delete":
			if (!input.entry) throw new ContentAuthorizationError(input.operation, input.principal.id, "delete requires exact entry metadata");
			capabilities.push(own ? "deleteOwn" : "deleteOthers");
			if (input.entry.visibility === "private") capabilities.push("deletePrivate");
			if (status?.published) capabilities.push("deletePublished");
	}
	return Object.freeze([...new Set(capabilities)]);
}
function authorizeContentAction(input) {
	const required = resolveRequiredContentCapabilities(input);
	for (const capabilityKey of required) {
		const mapped = input.type.capabilities[capabilityKey];
		if (typeof mapped !== "string" || mapped.length === 0 || !input.principal.capabilities.has(mapped)) throw new ContentAuthorizationError(input.operation, input.principal.id, `missing required capability ${capabilityKey}`);
	}
}
var ContentAuthzError = class extends Error {
	action;
	actorId;
	detail;
	entryId;
	name = "ContentAuthzError";
	constructor(action, actorId, detail, entryId) {
		super(`content authz denied: ${action} by ${actorId} — ${detail}`);
		this.action = action;
		this.actorId = actorId;
		this.detail = detail;
		this.entryId = entryId;
	}
};
function assertCanModify(actor, action, ownerAuthor, entryId) {
	if (actor.canEditAny || ownerAuthor === actor.id) return;
	throw new ContentAuthzError(action, actor.id, "actor is not the author and lacks canEditAny", entryId);
}
function assertCanPublish(actor, action, entryId) {
	if (!actor.canPublish) throw new ContentAuthzError(action, actor.id, "actor lacks canPublish", entryId);
}
function assertCanManageTaxonomy(actor) {
	if (!actor.canManageTaxonomy) throw new ContentAuthzError("update", actor.id, "actor lacks canManageTaxonomy");
}
//#endregion
//#region src/model.ts
/** Typed, contextful boundary error — carries the offending field + why (coding-standard §4). */
var ContentValidationError = class extends Error {
	field;
	detail;
	name = "ContentValidationError";
	constructor(field, detail) {
		super(`content input invalid: ${field} — ${detail}`);
		this.field = field;
		this.detail = detail;
	}
};
/** Thrown when a write reaches the store without a usable `sanitize` function (fail-closed). */
var ContentSanitizationError = class extends Error {
	name = "ContentSanitizationError";
	constructor(message) {
		super(message);
	}
};
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
const MAX_SLUG = 200;
const MAX_TYPE = 64;
const MAX_TITLE = 300;
const VISIBILITY_LITERALS = [
	"public",
	"private",
	"members"
];
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
function normTermIds(raw) {
	if (raw === void 0) return void 0;
	if (!Array.isArray(raw)) throw new ContentValidationError("termIds", "must be an array of UUID strings");
	const out = [];
	for (const id of raw) {
		if (typeof id !== "string" || !UUID_RE.test(id)) throw new ContentValidationError("termIds", "must contain only well-formed UUID strings");
		out.push(id);
	}
	return [...new Set(out)];
}
function normVisibility(raw, isUpdate) {
	if (raw === void 0 || raw === null) return isUpdate ? null : "public";
	if (typeof raw !== "string" || !VISIBILITY_LITERALS.includes(raw)) throw new ContentValidationError("visibility", "must be public|private|members");
	return raw;
}
/**
* Validator-agnostic boundary normalization (north-star: never bundle a validator).
* Hand-rolled required-field + format checks; the adopter's own validator runs at the route.
*/
function normalizeContentInput(raw) {
	if (typeof raw !== "object" || raw === null) throw new ContentValidationError("input", "must be an object");
	const slug = String(raw.slug ?? "").trim();
	if (!SLUG_RE.test(slug)) throw new ContentValidationError("slug", "must be lowercase kebab-case [a-z0-9-]");
	if (slug.length > MAX_SLUG) throw new ContentValidationError("slug", `exceeds ${MAX_SLUG} chars`);
	const type = String(raw.type ?? "").trim();
	if (!type) throw new ContentValidationError("type", "required");
	if (type.length > MAX_TYPE) throw new ContentValidationError("type", `exceeds ${MAX_TYPE} chars`);
	const title = String(raw.title ?? "").trim();
	if (!title) throw new ContentValidationError("title", "required");
	if (title.length > MAX_TITLE) throw new ContentValidationError("title", `exceeds ${MAX_TITLE} chars`);
	const body = typeof raw.body === "string" ? raw.body : "";
	const id = raw.id !== void 0 && raw.id !== null ? String(raw.id).trim() : null;
	if (id !== null && id.length === 0) throw new ContentValidationError("id", "must be non-empty when present");
	return {
		id,
		slug,
		type,
		title,
		body,
		visibility: normVisibility(raw.visibility, id !== null),
		termIds: normTermIds(raw.termIds)
	};
}
//#endregion
//#region src/taxonomy.ts
const MAX_TERM_DEPTH = 6;
var TermConflictError = class extends Error {
	taxonomy;
	slug;
	parentId;
	name = "TermConflictError";
	constructor(taxonomy, slug, parentId) {
		super(`term slug conflict: taxonomy=${taxonomy} slug=${slug} parentId=${parentId ?? "root"}`);
		this.taxonomy = taxonomy;
		this.slug = slug;
		this.parentId = parentId;
	}
};
var TermCycleError = class extends Error {
	termId;
	newParentId;
	name = "TermCycleError";
	constructor(termId, newParentId) {
		super(`term move would create cycle: term=${termId} newParent=${newParentId}`);
		this.termId = termId;
		this.newParentId = newParentId;
	}
};
var TermHasChildrenError = class extends Error {
	termId;
	name = "TermHasChildrenError";
	constructor(termId) {
		super(`term has children: id=${termId}`);
		this.termId = termId;
	}
};
var TermNotFoundError = class extends Error {
	selector;
	name = "TermNotFoundError";
	constructor(selector) {
		super(`term not found: ${selector}`);
		this.selector = selector;
	}
};
var TermValidationError = class extends Error {
	field;
	detail;
	name = "TermValidationError";
	constructor(field, detail) {
		super(`term invalid: ${field} — ${detail}`);
		this.field = field;
		this.detail = detail;
	}
};
/** Idempotent additive DDL for taxonomy tables (spec §3 / §5 step 1). */
const contentTaxonomyMigrationSql = () => `
CREATE TABLE IF NOT EXISTS content_terms (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  taxonomy   text NOT NULL,
  slug       text NOT NULL,
  name       text NOT NULL,
  parent_id  uuid REFERENCES content_terms(id) ON DELETE RESTRICT,
  depth      integer NOT NULL DEFAULT 0,
  created_at timestamptz(3) NOT NULL DEFAULT NOW(),
  host_session_id text
);
ALTER TABLE content_terms ADD COLUMN IF NOT EXISTS host_session_id text;
CREATE UNIQUE INDEX IF NOT EXISTS content_terms_sibling_slug_uq
  ON content_terms (taxonomy, COALESCE(parent_id, '00000000-0000-0000-0000-000000000000'::uuid), slug);
CREATE INDEX IF NOT EXISTS content_terms_taxonomy_parent_idx ON content_terms (taxonomy, parent_id);
CREATE TABLE IF NOT EXISTS content_entry_terms (
  entry_id uuid NOT NULL REFERENCES content_entries(id) ON DELETE CASCADE,
  term_id  uuid NOT NULL REFERENCES content_terms(id) ON DELETE CASCADE,
  PRIMARY KEY (entry_id, term_id)
);
CREATE INDEX IF NOT EXISTS content_entry_terms_term_idx ON content_entry_terms (term_id);
ALTER TABLE content_revisions ADD COLUMN IF NOT EXISTS term_ids jsonb NOT NULL DEFAULT '[]'::jsonb;
`.trim();
function toTermRef(row) {
	return {
		id: row.id,
		taxonomy: row.taxonomy,
		slug: row.slug,
		name: row.name,
		parentId: row.parentId ?? null,
		depth: row.depth
	};
}
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_terms_sibling_slug_uq|duplicate key|unique constraint/i.test(msg)) return true;
		cur = cur instanceof Error ? cur.cause : void 0;
	}
	return false;
}
async function createTerm(db, input, actor) {
	assertCanManageTaxonomy(actor);
	const taxonomy = input.taxonomy.trim();
	const slug = input.slug.trim();
	const name = input.name.trim();
	if (!taxonomy) throw new TermValidationError("taxonomy", "required");
	if (!slug) throw new TermValidationError("slug", "required");
	if (!name) throw new TermValidationError("name", "required");
	const parentId = input.parentId ?? null;
	let depth = 0;
	if (parentId) {
		const [parent] = await db.select().from(contentTerms).where(drizzle.eq(contentTerms.id, parentId)).limit(1);
		if (!parent) throw new TermNotFoundError(`parent id=${parentId}`);
		if (parent.taxonomy !== taxonomy) throw new TermValidationError("parentId", "parent must be same taxonomy");
		depth = parent.depth + 1;
		if (depth > 6) throw new TermValidationError("depth", `exceeds max depth 6`);
	}
	try {
		const [row] = await db.insert(contentTerms).values({
			taxonomy,
			slug,
			name,
			parentId,
			depth
		}).returning();
		return toTermRef(row);
	} catch (e) {
		if (isUniqueViolation(e)) throw new TermConflictError(taxonomy, slug, parentId);
		throw e;
	}
}
async function updateTerm(db, id, patch, actor) {
	assertCanManageTaxonomy(actor);
	const [existing] = await db.select().from(contentTerms).where(drizzle.eq(contentTerms.id, id)).limit(1);
	if (!existing) throw new TermNotFoundError(`id=${id}`);
	const name = patch.name !== void 0 ? patch.name.trim() : existing.name;
	const slug = patch.slug !== void 0 ? patch.slug.trim() : existing.slug;
	if (!name) throw new TermValidationError("name", "required");
	if (!slug) throw new TermValidationError("slug", "required");
	try {
		const [row] = await db.update(contentTerms).set({
			name,
			slug
		}).where(drizzle.eq(contentTerms.id, id)).returning();
		return toTermRef(row);
	} catch (e) {
		if (isUniqueViolation(e)) throw new TermConflictError(existing.taxonomy, slug, existing.parentId ?? null);
		throw e;
	}
}
async function listTerms(db, taxonomy) {
	return (await db.select().from(contentTerms).where(drizzle.eq(contentTerms.taxonomy, taxonomy)).orderBy(drizzle.asc(contentTerms.depth), drizzle.asc(contentTerms.name))).map(toTermRef);
}
/**
* `Querier` is `Database | PostgresTransaction`, and the two disagree on `execute()`'s result shape:
* a transaction hands back a plain row array, while the non-transactional handle still returns the
* driver's `{ rows }` envelope. Normalize instead of casting — a cast picks one shape and silently
* reads `undefined` on the other, which is how the cycle and depth guards came to no-op.
*/
function rowsOf(result) {
	if (Array.isArray(result)) return result;
	const rows = result?.rows;
	return Array.isArray(rows) ? rows : [];
}
async function isInSubtree(db, rootId, nodeId) {
	if (rootId === nodeId) return true;
	return rowsOf(await db.execute(drizzle.sql`
    WITH RECURSIVE subtree AS (
      SELECT id, 1 AS lvl FROM content_terms WHERE id = ${rootId}
      UNION ALL
      SELECT t.id, s.lvl + 1 FROM content_terms t
      INNER JOIN subtree s ON t.parent_id = s.id
      WHERE s.lvl < 64
    )
    SELECT 1 AS hit FROM subtree WHERE id = ${nodeId} LIMIT 1
  `)).length > 0;
}
async function moveTerm(db, id, newParentId, actor) {
	assertCanManageTaxonomy(actor);
	return db.transaction(async (tx) => {
		const [termTax] = await tx.select({ taxonomy: contentTerms.taxonomy }).from(contentTerms).where(drizzle.eq(contentTerms.id, id)).limit(1);
		if (!termTax) throw new TermNotFoundError(`id=${id}`);
		const taxonomy = termTax.taxonomy;
		await tx.execute(drizzle.sql`SELECT pg_advisory_xact_lock(hashtext(${"content_terms_move:" + taxonomy}))`);
		const [term] = await tx.select().from(contentTerms).where(drizzle.eq(contentTerms.id, id)).limit(1);
		if (!term) throw new TermNotFoundError(`id=${id}`);
		if (newParentId === id) throw new TermCycleError(id, newParentId);
		let newDepth = 0;
		if (newParentId) {
			const [parent] = await tx.select().from(contentTerms).where(drizzle.eq(contentTerms.id, newParentId)).limit(1);
			if (!parent) throw new TermNotFoundError(`parent id=${newParentId}`);
			if (parent.taxonomy !== term.taxonomy) throw new TermValidationError("newParentId", "parent must be same taxonomy");
			if (await isInSubtree(tx, id, newParentId)) throw new TermCycleError(id, newParentId);
			newDepth = parent.depth + 1;
		}
		const delta = newDepth - term.depth;
		if (rowsOf(await tx.execute(drizzle.sql`
      WITH RECURSIVE subtree AS (
        SELECT id, depth, 1 AS lvl FROM content_terms WHERE id = ${id}
        UNION ALL
        SELECT t.id, t.depth, s.lvl + 1 FROM content_terms t
        INNER JOIN subtree s ON t.parent_id = s.id
        WHERE s.lvl < 64
      )
      SELECT 1 AS hit FROM subtree WHERE depth + ${delta} > ${6} LIMIT 1
    `)).length > 0) throw new TermValidationError("depth", `exceeds max depth 6`);
		await tx.update(contentTerms).set({ parentId: newParentId }).where(drizzle.eq(contentTerms.id, id));
		await tx.execute(drizzle.sql`
      WITH RECURSIVE subtree AS (
        SELECT id, 1 AS lvl FROM content_terms WHERE id = ${id}
        UNION ALL
        SELECT t.id, s.lvl + 1 FROM content_terms t
        INNER JOIN subtree s ON t.parent_id = s.id
        WHERE s.lvl < 64
      )
      UPDATE content_terms SET depth = depth + ${delta}
      WHERE id IN (SELECT id FROM subtree)
    `);
		const [moved] = await tx.select().from(contentTerms).where(drizzle.eq(contentTerms.id, id)).limit(1);
		return toTermRef(moved);
	});
}
async function deleteTerm(db, id, actor) {
	assertCanManageTaxonomy(actor);
	const [term] = await db.select().from(contentTerms).where(drizzle.eq(contentTerms.id, id)).limit(1);
	if (!term) throw new TermNotFoundError(`id=${id}`);
	const [child] = await db.select().from(contentTerms).where(drizzle.eq(contentTerms.parentId, id)).limit(1);
	if (child) throw new TermHasChildrenError(id);
	await db.delete(contentTerms).where(drizzle.eq(contentTerms.id, id));
}
async function validateTermIdsExist(db, termIds) {
	if (termIds.length === 0) return;
	for (const id of termIds) if (typeof id !== "string" || !UUID_RE.test(id)) throw new TermValidationError("termIds", "must contain only well-formed UUID strings");
	const found = await db.select({ id: contentTerms.id }).from(contentTerms).where(drizzle.inArray(contentTerms.id, termIds));
	if (found.length !== termIds.length) {
		const foundSet = new Set(found.map((r) => r.id));
		throw new TermNotFoundError(`id=${termIds.find((id) => !foundSet.has(id))}`);
	}
}
async function replaceEntryTerms(db, entryId, termIds) {
	await db.delete(contentEntryTerms).where(drizzle.eq(contentEntryTerms.entryId, entryId));
	for (const termId of termIds) await db.insert(contentEntryTerms).values({
		entryId,
		termId
	});
}
async function termsForEntry(db, entryId) {
	return (await db.select({ term: contentTerms }).from(contentEntryTerms).innerJoin(contentTerms, drizzle.eq(contentEntryTerms.termId, contentTerms.id)).where(drizzle.eq(contentEntryTerms.entryId, entryId)).orderBy(drizzle.asc(contentTerms.depth), drizzle.asc(contentTerms.name))).map((r) => toTermRef(r.term));
}
async function assignTerms(db, entryId, termIds, actor) {
	const [entry] = await db.select({ author: contentEntries.author }).from(contentEntries).where(drizzle.eq(contentEntries.id, entryId)).limit(1);
	if (!entry) {
		const { ContentNotFoundError } = await import("./store-D_2h-weW.js").then((n) => n.b);
		throw new ContentNotFoundError(`id=${entryId}`);
	}
	assertCanModify(actor, "update", entry.author, entryId);
	await validateTermIdsExist(db, termIds);
	await replaceEntryTerms(db, entryId, termIds);
	return termsForEntry(db, entryId);
}
//#endregion
export { contentEntries as A, assertCanManageTaxonomy as C, resolveRequiredContentCapabilities as D, authorizeContentAction as E, contentRevisions as F, contentSchema as I, contentStatusDefinitions as L, contentLifecycleJournal as M, contentMigrationCheckpoints as N, assertActiveContentTransaction as O, contentMigrationWriteJournal as P, contentTombstones as R, ContentAuthzError as S, assertCanPublish as T, ContentSanitizationError as _, TermNotFoundError as a, normalizeContentInput as b, contentTaxonomyMigrationSql as c, listTerms as d, moveTerm as f, validateTermIdsExist as g, updateTerm as h, TermHasChildrenError as i, contentImportJournal as j, contentDefinitionVersions as k, createTerm as l, termsForEntry as m, TermConflictError as n, TermValidationError as o, replaceEntryTerms as p, TermCycleError as r, assignTerms as s, MAX_TERM_DEPTH as t, deleteTerm as u, ContentValidationError as v, assertCanModify as w, ContentAuthorizationError as x, UUID_RE as y, contentTypeDefinitions as z };
