import { t as validateIdentifier } from "./validate-VPnKoIzW.mjs";
import "./dialect-helpers-DRI5pyY3.mjs";
import "./base64-CmWvODNW.mjs";
import "./types-CuW0zWDA.mjs";
import { n as chunks, t as SQL_BATCH_SIZE } from "./chunks-B2isWQhr.mjs";
import { n as isMissingTableError } from "./db-errors-CK46D-ly.mjs";
import "./content-refresh-CA4uKwzH.mjs";
import "./fts-manager-BbFaF_V8.mjs";
import { n as SchemaRegistry } from "./registry-B65mUtPa.mjs";
import { n as generateZodSchema } from "./zod-generator-CacrR-i8.mjs";
import { sql } from "kysely";

//#region src/api/handlers/validation.ts
/**
* Field-level validation for content create / update.
*
* Wires the existing `generateZodSchema()` pipeline (`schema/zod-generator.ts`)
* into the handler boundary so REST and MCP both get the same enforcement:
*
*  - required fields must be present and non-empty
*  - select / multiSelect values must match the configured options
*  - reference fields must resolve to a real, non-trashed target
*
* Errors surface as `{ code: "VALIDATION_ERROR", message }` with all
* offending fields listed in one message so callers can fix everything in
* a single round trip.
*/
/** Treat `undefined`, `null`, and `""` as "not set". */
function isMissing(value) {
	return value === void 0 || value === null || value === "";
}
/**
* Resolve the target collection slug for a reference field.
*
* Schema-defined reference fields (the static `reference()` factory in
* `fields/reference.ts`) put the target in `options.collection`. The MCP
* `schema_create_field` tool also puts it there. Tests and some admin paths
* stash it inside `validation.collection` directly; we accept both.
*/
function getReferenceTargetCollection(field) {
	const fromOptions = field.options?.collection;
	if (typeof fromOptions === "string" && fromOptions.length > 0) return fromOptions;
	const validation = field.validation;
	if (validation && "collection" in validation) {
		const fromValidation = validation.collection;
		if (typeof fromValidation === "string" && fromValidation.length > 0) return fromValidation;
	}
}
/**
* Format a Zod issue path into a human-readable field reference, e.g.
* `tags`, `tags.1`, `image.alt`.
*/
function formatIssuePath(path) {
	if (path.length === 0) return "(root)";
	return path.map((seg) => String(seg)).join(".");
}
/**
* Validate `data` against the collection's field definitions.
*
* `partial: true` switches Zod into partial mode so updates can include
* only the fields being changed without tripping required-field errors on
* fields the caller didn't touch. Required fields that ARE present in
* partial-mode data still get the empty-string check below.
*/
async function validateContentData(db, collection, data, options = {}) {
	const collectionWithFields = await new SchemaRegistry(db).getCollectionWithFields(collection);
	if (!collectionWithFields) return {
		ok: false,
		error: {
			code: "COLLECTION_NOT_FOUND",
			message: `Collection '${collection}' not found`
		}
	};
	const issues = [];
	const knownFields = new Set(collectionWithFields.fields.map((f) => f.slug));
	for (const key of Object.keys(data)) {
		if (key.startsWith("_")) continue;
		if (!knownFields.has(key)) issues.push(`${key}: unknown field on collection '${collection}'`);
	}
	const baseSchema = generateZodSchema(collectionWithFields);
	const parsed = (options.partial ? baseSchema.partial() : baseSchema).safeParse(data);
	if (!parsed.success) for (const issue of parsed.error.issues) issues.push(`${formatIssuePath(issue.path)}: ${issue.message}`);
	for (const field of collectionWithFields.fields) {
		if (!field.required) continue;
		const present = Object.hasOwn(data, field.slug);
		if (options.partial && !present) continue;
		if (data[field.slug] === "") issues.push(`${field.slug}: required (empty value not allowed)`);
	}
	const refsByTarget = /* @__PURE__ */ new Map();
	for (const field of collectionWithFields.fields) {
		if (field.type !== "reference") continue;
		if (options.partial && !Object.hasOwn(data, field.slug)) continue;
		const value = data[field.slug];
		if (isMissing(value)) continue;
		if (typeof value !== "string") continue;
		const target = getReferenceTargetCollection(field);
		if (!target) continue;
		const list = refsByTarget.get(target) ?? [];
		list.push({
			field: field.slug,
			id: value
		});
		refsByTarget.set(target, list);
	}
	for (const [target, refs] of refsByTarget) {
		try {
			validateIdentifier(target, "reference target collection");
		} catch {
			for (const ref of refs) issues.push(`${ref.field}: invalid reference target collection '${target}'`);
			continue;
		}
		const ids = [...new Set(refs.map((r) => r.id))];
		const tableName = `ec_${target}`;
		const found = /* @__PURE__ */ new Set();
		let targetTableMissing = false;
		for (const idChunk of chunks(ids, SQL_BATCH_SIZE)) try {
			const rows = await sql`
					SELECT id FROM ${sql.ref(tableName)}
					WHERE id IN (${sql.join(idChunk)})
					AND deleted_at IS NULL
				`.execute(db);
			for (const row of rows.rows) found.add(row.id);
		} catch (error) {
			if (isMissingTableError(error)) {
				targetTableMissing = true;
				break;
			}
			throw error;
		}
		if (targetTableMissing) {
			for (const ref of refs) issues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);
			continue;
		}
		for (const ref of refs) if (!found.has(ref.id)) issues.push(`${ref.field}: target '${ref.id}' not found in collection '${target}'`);
	}
	if (issues.length === 0) return { ok: true };
	return {
		ok: false,
		error: {
			code: "VALIDATION_ERROR",
			message: issues.join("; ")
		}
	};
}

//#endregion
export { validateContentData };
//# sourceMappingURL=validation-c8WjhH8z.mjs.map