import { t as after } from "./after-B1IIdH3Y.mjs";
import { l as invalidateObjectCache, n as cachedQuery } from "./object-cache-Bok5j2ae.mjs";
import { t as MediaRepository } from "./media-BrXIaMFN.mjs";
import { t as OptionsRepository } from "./options-DTTML-Tx.mjs";
import { n as peekRequestCache, r as requestCached } from "./request-cache-KCNHp_RJ.mjs";
import { a as getDb } from "./loader-CB6YHeul.mjs";
import { n as invalidateSingleFlightCache, r as singleFlightCached, t as createSingleFlightCache } from "./single-flight-cache-BG3B_GhN.mjs";

//#region src/settings/index.ts
/** Object-cache namespace for site settings. */
const SETTINGS_CACHE_NAMESPACE = "settings";
/** Prefix for site settings in the options table */
const SETTINGS_PREFIX = "site:";
/**
* Worker-isolate cache for the resolved `site:*` settings.
*
* Site settings (title, logo, SEO defaults) change rarely but are read on
* every public request. Caching across the isolate's lifetime drops the
* `options WHERE name LIKE 'site:%'` prefix scan from once-per-request to
* once-per-isolate. Cross-isolate staleness is bounded by isolate lifetime
* (workerd typically recycles within minutes); acceptable for chrome.
*
* Backed by single-flight-cache.ts: concurrent cold reads coalesce onto one
* query via a reclaimable single-flight lock and the resolved *value* is
* cached — never a shared in-flight promise, so a cancelled request can't
* poison the isolate (see that file's header). Stored on globalThis with a
* Symbol.for key so Vite SSR chunk duplication doesn't produce two
* independent caches (same pattern as request-context.ts).
*/
const SITE_SETTINGS_CACHE_KEY = Symbol.for("emdash:site-settings");
const g = globalThis;
const settingsCache = g[SITE_SETTINGS_CACHE_KEY] ?? (() => {
	const c = createSingleFlightCache();
	g[SITE_SETTINGS_CACHE_KEY] = c;
	return c;
})();
/**
* Bump the isolate-wide site-settings cache version, forcing the next
* `getSiteSettings()` to re-query the database.
*
* Called from every `site:*` write path. Other isolates still serve their
* own cached copy until they expire — staleness bounded by isolate lifetime.
*/
function invalidateSiteSettingsCache() {
	invalidateSingleFlightCache(settingsCache);
	invalidateObjectCache(SETTINGS_CACHE_NAMESPACE);
}
/**
* Type guard for MediaReference values
*/
function isMediaReference(value) {
	return typeof value === "object" && value !== null && "mediaId" in value;
}
/**
* Resolve a media reference to include the full URL plus content metadata.
*
* Pulls `mimeType` and intrinsic dimensions from the media row so callers
* can emit correct head tags (e.g. `<link rel="icon" type="image/svg+xml">`,
* which Chromium requires when the URL has no `.svg` extension) without
* a second round-trip to the media table.
*/
async function resolveMediaReference(mediaRef, db, _storage) {
	if (!mediaRef?.mediaId) return mediaRef;
	try {
		const media = await new MediaRepository(db).findById(mediaRef.mediaId);
		if (media) return {
			...mediaRef,
			url: `/_emdash/api/media/file/${media.storageKey}`,
			contentType: media.mimeType,
			...media.width !== null ? { width: media.width } : {},
			...media.height !== null ? { height: media.height } : {}
		};
	} catch {}
	return mediaRef;
}
/**
* Get a single site setting by key
*
* Returns `undefined` if the setting has not been configured.
* For media settings (logo, favicon), the URL is resolved automatically.
*
* @param key - The setting key (e.g., "title", "logo", "social")
* @returns The setting value, or undefined if not set
*
* @example
* ```ts
* import { getSiteSetting } from "emdash";
*
* const title = await getSiteSetting("title");
* const logo = await getSiteSetting("logo");
* console.log(logo?.url); // Resolved URL
* ```
*/
async function getSiteSetting(key) {
	const primed = peekRequestCache("siteSettings");
	if (primed) return (await primed)[key];
	return requestCached(`siteSetting:${key}`, async () => {
		return getSiteSettingWithDb(key, await getDb());
	});
}
/**
* Get a single site setting by key (with explicit db)
*
* @internal Use `getSiteSetting()` in templates. This variant is for admin routes
* that already have a database handle.
*/
async function getSiteSettingWithDb(key, db, storage = null) {
	const value = await new OptionsRepository(db).get(`${SETTINGS_PREFIX}${key}`);
	if (!value) return;
	if ((key === "logo" || key === "favicon") && isMediaReference(value)) return await resolveMediaReference(value, db, storage);
	if (key === "seo" && value && typeof value === "object") {
		const seo = value;
		if (seo.defaultOgImage) return {
			...seo,
			defaultOgImage: await resolveMediaReference(seo.defaultOgImage, db, storage)
		};
	}
	return value;
}
/**
* Get all site settings
*
* Returns all configured settings. Unset values are undefined.
* Media references (logo/favicon) are resolved to include URLs.
*
* @example
* ```ts
* import { getSiteSettings } from "emdash";
*
* const settings = await getSiteSettings();
* console.log(settings.title); // "My Site"
* console.log(settings.logo?.url); // "/_emdash/api/media/file/abc123"
* ```
*/
function getSiteSettings() {
	return requestCached("siteSettings", () => singleFlightCached(settingsCache, () => cachedQuery({
		namespace: SETTINGS_CACHE_NAMESPACE,
		key: "all",
		load: async () => {
			return getSiteSettingsWithDb(await getDb());
		}
	}), {
		anchor: (promise) => after(() => promise),
		ownerTimeoutMs: 3e4
	}));
}
/**
* Get all site settings (with explicit db)
*
* @internal Use `getSiteSettings()` in templates. This variant is for admin routes
* that already have a database handle.
*/
async function getSiteSettingsWithDb(db, storage = null) {
	const allOptions = await new OptionsRepository(db).getByPrefix(SETTINGS_PREFIX);
	const settings = {};
	for (const [key, value] of allOptions) {
		const settingKey = key.replace(SETTINGS_PREFIX, "");
		settings[settingKey] = value;
	}
	const typedSettings = settings;
	if (typedSettings.logo) typedSettings.logo = await resolveMediaReference(typedSettings.logo, db, storage);
	if (typedSettings.favicon) typedSettings.favicon = await resolveMediaReference(typedSettings.favicon, db, storage);
	if (typedSettings.seo?.defaultOgImage) typedSettings.seo = {
		...typedSettings.seo,
		defaultOgImage: await resolveMediaReference(typedSettings.seo.defaultOgImage, db, storage)
	};
	return typedSettings;
}
/**
* Set site settings (internal function used by admin API)
*
* Merges provided settings with existing ones. Only provided fields are updated.
* Media references should include just the mediaId; URLs are resolved on read.
*
* @param settings - Partial settings object with values to update
* @param db - Kysely database instance
* @returns Promise that resolves when settings are saved
*
* @internal
*
* @example
* ```ts
* // Update multiple settings at once
* await setSiteSettings({
*   title: "My Site",
*   tagline: "Welcome",
*   logo: { mediaId: "med_123", alt: "Logo" }
* }, db);
* ```
*/
async function setSiteSettings(settings, db) {
	const options = new OptionsRepository(db);
	const updates = {};
	for (const [key, value] of Object.entries(settings)) if (value !== void 0) updates[`${SETTINGS_PREFIX}${key}`] = value;
	try {
		await options.setMany(updates);
	} finally {
		invalidateSiteSettingsCache();
	}
}
/**
* Get a single plugin setting by key.
*
* Plugin settings are stored in the options table under
* `plugin:<pluginId>:settings:<key>`.
*/
async function getPluginSetting(pluginId, key) {
	return getPluginSettingWithDb(pluginId, key, await getDb());
}
/**
* Get a single plugin setting by key (with explicit db).
*
* @internal Use `getPluginSetting()` in templates and plugin rendering code.
*/
async function getPluginSettingWithDb(pluginId, key, db) {
	return await new OptionsRepository(db).get(`plugin:${pluginId}:settings:${key}`) ?? void 0;
}
/**
* Get all persisted plugin settings for a plugin.
*
* Defaults declared in `admin.settingsSchema` are not materialized
* automatically; callers should apply their own fallback defaults.
*/
async function getPluginSettings(pluginId) {
	return getPluginSettingsWithDb(pluginId, await getDb());
}
/**
* Get all persisted plugin settings for a plugin (with explicit db).
*
* @internal Use `getPluginSettings()` in templates and plugin rendering code.
*/
async function getPluginSettingsWithDb(pluginId, db) {
	const prefix = `plugin:${pluginId}:settings:`;
	const allOptions = await new OptionsRepository(db).getByPrefix(prefix);
	const settings = {};
	for (const [key, value] of allOptions) {
		if (!key.startsWith(prefix)) continue;
		settings[key.slice(prefix.length)] = value;
	}
	return settings;
}

//#endregion
export { getSiteSettingsWithDb as a, getSiteSettings as i, getPluginSettings as n, invalidateSiteSettingsCache as o, getSiteSetting as r, setSiteSettings as s, getPluginSetting as t };
//# sourceMappingURL=settings-zhmdKyJQ.mjs.map