import { t as __exportAll } from "./chunk-ClPoSABd.mjs";
import { n as getI18nConfig, r as isI18nEnabled, t as getFallbackChain } from "./config-DkxPrM9l.mjs";
import { getRequestContext } from "./request-context.mjs";

//#region src/visual-editing/editable.ts
/**
* Create an editable proxy for an entry.
*
* Usage:
* - `{...entry.edit}` - entry-level annotation (includes status/hasDraft)
* - `{...entry.edit.title}` - field-level annotation
* - `{...entry.edit['nested.field']}` - nested field (bracket notation)
*/
function createEditable(collection, id, options) {
	const base = {
		collection,
		id,
		...options?.status && { status: options.status },
		...options?.hasDraft && { hasDraft: true }
	};
	return new Proxy({}, {
		get(_, prop) {
			if (prop === "toJSON") return () => ({ "data-emdash-ref": JSON.stringify(base) });
			if (typeof prop === "symbol") return void 0;
			if (prop === "data-emdash-ref") return JSON.stringify(base);
			return { "data-emdash-ref": JSON.stringify({
				...base,
				field: String(prop)
			}) };
		},
		ownKeys() {
			return ["data-emdash-ref"];
		},
		getOwnPropertyDescriptor(_, prop) {
			if (prop === "data-emdash-ref") return {
				configurable: true,
				enumerable: true,
				value: JSON.stringify(base)
			};
		}
	});
}
/**
* Create a noop proxy for production mode.
* Spreading this produces no attributes.
*/
function createNoop() {
	return new Proxy({}, {
		get(_, prop) {
			if (typeof prop === "symbol") return void 0;
		},
		ownKeys() {
			return [];
		},
		getOwnPropertyDescriptor() {}
	});
}

//#endregion
//#region src/query.ts
/**
* Query functions for EmDash content
*
* These wrap Astro's getLiveCollection/getLiveEntry with type filtering.
* Use these instead of calling Astro's functions directly.
*
* Error handling follows Astro's pattern - returns { entries/entry, error }
* so callers can gracefully handle errors (including 404s).
*
* Preview mode is handled implicitly via ALS request context —
* no parameters needed. The middleware verifies the preview token
* and sets the context; query functions read it automatically.
*/
var query_exports = /* @__PURE__ */ __exportAll({
	getEditMeta: () => getEditMeta,
	getEmDashCollection: () => getEmDashCollection,
	getEmDashEntry: () => getEmDashEntry,
	getTranslations: () => getTranslations,
	invalidateUrlPatternCache: () => invalidateUrlPatternCache,
	resolveEmDashPath: () => resolveEmDashPath
});
const COLLECTION_NAME = "_emdash";
/** Symbol key for edit metadata on PT arrays — avoids collision with user data */
const EMDASH_EDIT = Symbol.for("__emdash");
/** Type guard for EditFieldMeta */
function isEditFieldMeta(value) {
	if (typeof value !== "object" || value === null) return false;
	if (!("collection" in value) || !("id" in value) || !("field" in value)) return false;
	const { collection, id, field } = value;
	return typeof collection === "string" && typeof id === "string" && typeof field === "string";
}
/**
* Read edit metadata from a value (returns undefined if not tagged).
* Uses Object.getOwnPropertyDescriptor to access Symbol-keyed property
* without an unsafe type assertion.
*/
function getEditMeta(value) {
	if (value && typeof value === "object") {
		const meta = Object.getOwnPropertyDescriptor(value, EMDASH_EDIT)?.value;
		if (isEditFieldMeta(meta)) return meta;
	}
}
/**
* Tag PT-like arrays in entry data with edit metadata (non-enumerable).
* A PT array is identified by: is an array, first element has _type property.
*/
function tagEditableFields(data, collection, id) {
	for (const [field, value] of Object.entries(data)) if (Array.isArray(value) && value.length > 0 && value[0] && typeof value[0] === "object" && "_type" in value[0]) Object.defineProperty(value, EMDASH_EDIT, {
		value: {
			collection,
			id,
			field
		},
		enumerable: false,
		configurable: true
	});
}
/** Safely read a string field from a Record, with optional fallback */
function dataStr(data, key, fallback = "") {
	const val = data[key];
	return typeof val === "string" ? val : fallback;
}
/** Type guard for Record<string, unknown> */
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
/** Extract data as Record from an Astro entry (which is any-typed) */
function entryData(entry) {
	return isRecord(entry.data) ? entry.data : {};
}
/** Extract the database ID from entry data (data.id is the ULID, entry.id is the slug) */
function entryDatabaseId(entry) {
	return dataStr(entryData(entry), "id") || entry.id;
}
/** Extract edit options from entry data for the proxy */
function entryEditOptions(entry) {
	const data = entryData(entry);
	const status = dataStr(data, "status", "draft");
	const draftRevisionId = dataStr(data, "draftRevisionId") || void 0;
	const liveRevisionId = dataStr(data, "liveRevisionId") || void 0;
	return {
		status,
		hasDraft: !!draftRevisionId && draftRevisionId !== liveRevisionId
	};
}
/**
* Get all entries of a content type
*
* Returns { entries, error } for graceful error handling.
*
* When emdash-env.d.ts is generated, the collection name will be
* type-checked and the return type will be inferred automatically.
*
* @example
* ```ts
* import { getEmDashCollection } from "emdash";
*
* const { entries: posts, error } = await getEmDashCollection("posts");
* if (error) {
*   console.error("Failed to load posts:", error);
*   return;
* }
* // posts[0].data.title is typed (if emdash-env.d.ts exists)
*
* // With filters
* const { entries: drafts } = await getEmDashCollection("posts", { status: "draft" });
* ```
*/
async function getEmDashCollection(type, filter) {
	const { getLiveCollection } = await import("astro:content");
	const ctx = getRequestContext();
	const i18nConfig = getI18nConfig();
	const resolvedLocale = filter?.locale ?? ctx?.locale ?? (isI18nEnabled() ? i18nConfig.defaultLocale : void 0);
	const result = await getLiveCollection(COLLECTION_NAME, {
		type,
		status: filter?.status,
		limit: filter?.limit,
		cursor: filter?.cursor,
		where: filter?.where,
		orderBy: filter?.orderBy,
		locale: resolvedLocale
	});
	const { entries, error, cacheHint } = result;
	const rawCursor = Object.getOwnPropertyDescriptor(result, "nextCursor")?.value;
	const nextCursor = typeof rawCursor === "string" ? rawCursor : void 0;
	if (error) return {
		entries: [],
		error,
		cacheHint: {}
	};
	const isEditMode = ctx?.editMode ?? false;
	const entriesWithEdit = entries.map((entry) => {
		const dbId = entryDatabaseId(entry);
		if (isEditMode) tagEditableFields(entryData(entry), type, dbId);
		return {
			...entry,
			edit: isEditMode ? createEditable(type, dbId, entryEditOptions(entry)) : createNoop()
		};
	});
	await hydrateEntryBylines(type, entriesWithEdit);
	return {
		entries: entriesWithEdit,
		nextCursor,
		cacheHint: cacheHint ?? {}
	};
}
/**
* Get a single entry by type and ID/slug
*
* Returns { entry, error, isPreview } for graceful error handling.
* - entry is null if not found (not an error)
* - error is set only for actual errors (db issues, etc.)
*
* Preview mode is detected automatically from request context (ALS).
* When the URL has a valid `_preview` token, the middleware sets preview
* context and this function serves draft revision data if available.
*
* @example
* ```ts
* import { getEmDashEntry } from "emdash";
*
* // Simple usage — preview just works via middleware
* const { entry: post, isPreview, error } = await getEmDashEntry("posts", "my-slug");
* if (!post) return Astro.redirect("/404");
* ```
*/
async function getEmDashEntry(type, id, options) {
	const { getLiveEntry } = await import("astro:content");
	const ctx = getRequestContext();
	const preview = ctx?.preview;
	const isEditMode = ctx?.editMode ?? false;
	const isPreviewMode = !!preview && preview.collection === type;
	const serveDrafts = isPreviewMode || isEditMode;
	const requestedLocale = options?.locale ?? ctx?.locale;
	/** Wrap a raw Astro entry with edit proxy, tagging editable fields if needed */
	function wrapEntry(raw) {
		const dbId = entryDatabaseId(raw);
		if (isEditMode) tagEditableFields(entryData(raw), type, dbId);
		return {
			...raw,
			edit: isEditMode ? createEditable(type, dbId, entryEditOptions(raw)) : createNoop()
		};
	}
	/** Check if an entry is publicly visible (published or scheduled past its time) */
	function isVisible(entry) {
		const data = entryData(entry);
		const status = dataStr(data, "status");
		const scheduledAt = dataStr(data, "scheduledAt") || void 0;
		return status === "published" || !!(status === "scheduled" && scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date());
	}
	const localeChain = requestedLocale && isI18nEnabled() ? getFallbackChain(requestedLocale) : [requestedLocale];
	/** Return a successful EntryResult with bylines hydrated */
	async function successResult(wrapped, opts) {
		await hydrateEntryBylines(type, [wrapped]);
		return {
			entry: wrapped,
			isPreview: opts.isPreview,
			fallbackLocale: opts.fallbackLocale,
			cacheHint: opts.cacheHint
		};
	}
	if (serveDrafts) {
		for (let i = 0; i < localeChain.length; i++) {
			const locale = localeChain[i];
			const fallbackLocale = i > 0 ? locale : void 0;
			const { entry: baseEntry, error: baseError, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
				type,
				id,
				locale
			});
			if (baseError) return {
				entry: null,
				error: baseError,
				isPreview: serveDrafts,
				cacheHint: {}
			};
			if (!baseEntry) continue;
			if (isPreviewMode && !isEditMode) {
				const dbId = entryDatabaseId(baseEntry);
				if (preview.id !== dbId && preview.id !== id) {
					if (isVisible(baseEntry)) return successResult(wrapEntry(baseEntry), {
						isPreview: false,
						fallbackLocale,
						cacheHint: cacheHint ?? {}
					});
					continue;
				}
			}
			const draftRevisionId = dataStr(entryData(baseEntry), "draftRevisionId") || void 0;
			if (draftRevisionId) {
				const { entry: draftEntry, error: draftError } = await getLiveEntry(COLLECTION_NAME, {
					type,
					id,
					revisionId: draftRevisionId,
					locale
				});
				if (!draftError && draftEntry) return successResult(wrapEntry(draftEntry), {
					isPreview: serveDrafts,
					fallbackLocale,
					cacheHint: cacheHint ?? {}
				});
			}
			return successResult(wrapEntry(baseEntry), {
				isPreview: serveDrafts,
				fallbackLocale,
				cacheHint: cacheHint ?? {}
			});
		}
		return {
			entry: null,
			isPreview: serveDrafts,
			cacheHint: {}
		};
	}
	for (let i = 0; i < localeChain.length; i++) {
		const locale = localeChain[i];
		const fallbackLocale = i > 0 ? locale : void 0;
		const { entry, error, cacheHint } = await getLiveEntry(COLLECTION_NAME, {
			type,
			id,
			locale
		});
		if (error) return {
			entry: null,
			error,
			isPreview: false,
			cacheHint: {}
		};
		if (entry && isVisible(entry)) return successResult(wrapEntry(entry), {
			isPreview: false,
			fallbackLocale,
			cacheHint: cacheHint ?? {}
		});
	}
	return {
		entry: null,
		isPreview: false,
		cacheHint: {}
	};
}
/**
* Eagerly hydrate byline data onto entry.data for one or more entries.
*
* Attaches `bylines` (array of ContentBylineCredit) and `byline`
* (primary BylineSummary or null) to each entry's data object.
* Uses batch queries to avoid N+1.
*
* Fails silently if the byline tables don't exist yet (pre-migration).
*/
async function hydrateEntryBylines(type, entries) {
	if (entries.length === 0) return;
	try {
		const { getBylinesForEntries } = await import("./bylines-C_Wsnz4L.mjs").then((n) => n.t);
		const ids = entries.map((e) => dataStr(entryData(e), "id")).filter(Boolean);
		if (ids.length === 0) return;
		const bylinesMap = await getBylinesForEntries(type, ids);
		for (const entry of entries) {
			const data = entryData(entry);
			const dbId = dataStr(data, "id");
			if (!dbId) continue;
			const credits = bylinesMap.get(dbId) ?? [];
			data.bylines = credits;
			data.byline = credits[0]?.byline ?? null;
		}
	} catch (err) {
		const msg = err instanceof Error ? err.message : "";
		if (!msg.includes("no such table")) console.warn("[emdash] Failed to hydrate bylines:", msg);
	}
}
/**
* Get all translations of a content item.
*
* Given a content entry, returns all locale variants that share the same
* translation group. This is useful for building language switcher UI.
*
* @example
* ```ts
* import { getEmDashEntry, getTranslations } from "emdash";
*
* const { entry: post } = await getEmDashEntry("posts", "hello-world", { locale: "en" });
* const { translations } = await getTranslations("posts", post.data.id);
* // translations = [{ id: "...", locale: "en", slug: "hello-world", status: "published" }, ...]
* ```
*/
async function getTranslations(type, id) {
	try {
		const db = (await import("./loader-BYzwzORf.mjs").then((n) => n.r)).getDb;
		const dbInstance = await db();
		const { ContentRepository } = await import("./content-BsBoyj8G.mjs").then((n) => n.n);
		const repo = new ContentRepository(dbInstance);
		const item = await repo.findByIdOrSlug(type, id);
		if (!item) return {
			translationGroup: "",
			translations: [],
			error: /* @__PURE__ */ new Error(`Content item not found: ${id}`)
		};
		const group = item.translationGroup || item.id;
		return {
			translationGroup: group,
			translations: (await repo.findTranslations(type, group)).map((t) => ({
				id: t.id,
				locale: t.locale || "en",
				slug: t.slug,
				status: t.status
			}))
		};
	} catch (error) {
		return {
			translationGroup: "",
			translations: [],
			error: error instanceof Error ? error : new Error(String(error))
		};
	}
}
/** Matches `{paramName}` placeholders in URL patterns */
const URL_PARAM_PATTERN = /\{(\w+)\}/g;
/** Convert a URL pattern like "/blog/{slug}" to a regex and param name list */
function patternToRegex(pattern) {
	const paramNames = [];
	const regexStr = pattern.replace(URL_PARAM_PATTERN, (_match, name) => {
		paramNames.push(name);
		return "([^/]+)";
	});
	return {
		regex: new RegExp(`^${regexStr}$`),
		paramNames
	};
}
let cachedUrlPatterns = null;
/**
* Invalidate the cached URL patterns used by resolveEmDashPath.
* Call when collection URL patterns change (schema updates).
*/
function invalidateUrlPatternCache() {
	cachedUrlPatterns = null;
}
/**
* Resolve a URL path to a content entry by matching against collection URL patterns.
*
* Loads all collections with a `urlPattern` set, converts each pattern to a regex,
* and tests the given path. On match, extracts the slug and fetches the entry.
*
* @example
* ```ts
* import { resolveEmDashPath } from "emdash";
*
* // Given pages with urlPattern "/{slug}" and posts with "/blog/{slug}":
* const result = await resolveEmDashPath("/blog/hello-world");
* if (result) {
*   console.log(result.collection); // "posts"
*   console.log(result.params.slug); // "hello-world"
*   console.log(result.entry.data); // post data
* }
* ```
*/
async function resolveEmDashPath(path) {
	if (!cachedUrlPatterns) {
		const { getDb } = await import("./loader-BYzwzORf.mjs").then((n) => n.r);
		const { SchemaRegistry } = await import("./registry-BgnP3ysR.mjs").then((n) => n.r);
		const collections = await new SchemaRegistry(await getDb()).listCollections();
		cachedUrlPatterns = [];
		for (const collection of collections) {
			if (!collection.urlPattern) continue;
			const { regex, paramNames } = patternToRegex(collection.urlPattern);
			cachedUrlPatterns.push({
				slug: collection.slug,
				regex,
				paramNames
			});
		}
	}
	for (const pattern of cachedUrlPatterns) {
		const match = path.match(pattern.regex);
		if (!match) continue;
		const params = {};
		for (let i = 0; i < pattern.paramNames.length; i++) params[pattern.paramNames[i]] = match[i + 1];
		const slug = params.slug;
		if (!slug) continue;
		const { entry } = await getEmDashEntry(pattern.slug, slug);
		if (entry) return {
			entry,
			collection: pattern.slug,
			params
		};
	}
	return null;
}

//#endregion
export { invalidateUrlPatternCache as a, createEditable as c, getTranslations as i, createNoop as l, getEmDashCollection as n, query_exports as o, getEmDashEntry as r, resolveEmDashPath as s, getEditMeta as t };
//# sourceMappingURL=query-B6Vu0d2i.mjs.map