import { encode } from "blurhash";
import { imageSize } from "image-size";

//#region src/media/normalize.ts
const INTERNAL_MEDIA_PREFIX = "/_emdash/api/media/file/";
const URL_PATTERN = /^https?:\/\//;
/**
* Normalize a media field value into a consistent MediaValue shape.
*
* - `null`/`undefined` → `null`
* - Bare URL string → `{ provider: "external", id: "", src: url }`
* - Bare internal media URL → resolved via local provider's `get()`
* - Object with `provider` + `id` → enriched with missing fields from provider
*/
async function normalizeMediaValue(value, getProvider) {
	if (value == null) return null;
	if (typeof value === "string") return normalizeStringUrl(value, getProvider);
	if (!isRecord(value)) return null;
	if (!("id" in value) && !("src" in value)) return null;
	const provider = (typeof value.provider === "string" ? value.provider : void 0) || "local";
	const id = typeof value.id === "string" ? value.id : "";
	if (provider === "external") return recordToMediaValue(value);
	const result = {
		...recordToMediaValue(value),
		provider
	};
	if (provider === "local") delete result.src;
	const needsDimensions = result.width == null || result.height == null;
	const needsStorageKey = provider === "local" && !result.meta?.storageKey;
	const needsFileInfo = !result.mimeType || !result.filename;
	if (!(needsDimensions || needsStorageKey || needsFileInfo) || !id) return result;
	const mediaProvider = getProvider(provider);
	if (!mediaProvider?.get) return result;
	let providerItem;
	try {
		providerItem = await mediaProvider.get(id);
	} catch {
		return result;
	}
	if (!providerItem) return result;
	return mergeProviderData(result, providerItem);
}
function normalizeStringUrl(url, getProvider) {
	if (url.startsWith(INTERNAL_MEDIA_PREFIX)) return resolveInternalUrl(url, getProvider);
	if (URL_PATTERN.test(url)) return Promise.resolve({
		provider: "external",
		id: "",
		src: url
	});
	return Promise.resolve({
		provider: "external",
		id: "",
		src: url
	});
}
async function resolveInternalUrl(url, getProvider) {
	const storageKey = url.slice(24);
	const localProvider = getProvider("local");
	if (!localProvider?.get) return {
		provider: "external",
		id: "",
		src: url
	};
	let item;
	try {
		item = await localProvider.get(storageKey);
	} catch {
		return {
			provider: "external",
			id: "",
			src: url
		};
	}
	if (!item) return {
		provider: "external",
		id: "",
		src: url
	};
	return {
		provider: "local",
		id: item.id,
		filename: item.filename,
		mimeType: item.mimeType,
		width: item.width,
		height: item.height,
		alt: item.alt,
		meta: item.meta
	};
}
/**
* Merge provider data into an existing MediaValue, preserving caller-supplied fields.
* Caller `alt` takes priority over provider `alt` (per-usage, not per-image).
*/
function mergeProviderData(existing, item) {
	const result = { ...existing };
	if (result.width == null && item.width != null) result.width = item.width;
	if (result.height == null && item.height != null) result.height = item.height;
	if (!result.filename && item.filename) result.filename = item.filename;
	if (!result.mimeType && item.mimeType) result.mimeType = item.mimeType;
	if (!result.alt && item.alt) result.alt = item.alt;
	if (item.meta) result.meta = {
		...item.meta,
		...result.meta
	};
	return result;
}
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Extract known MediaValue fields from a runtime-checked record.
* Avoids unsafe `as MediaValue` cast by reading each property explicitly.
*/
function recordToMediaValue(obj) {
	const result = { id: typeof obj.id === "string" ? obj.id : "" };
	if (typeof obj.provider === "string") result.provider = obj.provider;
	if (typeof obj.src === "string") result.src = obj.src;
	if (typeof obj.previewUrl === "string") result.previewUrl = obj.previewUrl;
	if (typeof obj.filename === "string") result.filename = obj.filename;
	if (typeof obj.mimeType === "string") result.mimeType = obj.mimeType;
	if (typeof obj.width === "number") result.width = obj.width;
	if (typeof obj.height === "number") result.height = obj.height;
	if (typeof obj.alt === "string") result.alt = obj.alt;
	if (isRecord(obj.meta)) result.meta = obj.meta;
	return result;
}

//#endregion
//#region src/media/placeholder.ts
/**
* Image Placeholder Generation
*
* Generates blurhash and dominant color from image buffers for LQIP support.
* Decodes images via jpeg-js (pure JS) and upng-js (pure JS, uses pako for
* deflate). No Node-specific dependencies — works in Workers and Node SSR.
*/
const SUPPORTED_TYPES = {
	"image/jpeg": "jpeg",
	"image/jpg": "jpeg",
	"image/png": "png"
};
/** Max width for blurhash input. Encode is O(w*h*components), so downsample first. */
const MAX_ENCODE_WIDTH = 32;
/** Max decoded RGBA size (32 MB). Images exceeding this skip placeholder generation. */
const MAX_DECODED_BYTES = 32 * 1024 * 1024;
/**
* Decode a JPEG buffer into raw RGBA pixel data.
*/
async function decodeJpeg(buffer) {
	const { decode } = await import("jpeg-js");
	const result = decode(buffer, { useTArray: true });
	return {
		width: result.width,
		height: result.height,
		data: result.data
	};
}
/**
* Decode a PNG buffer into raw RGBA pixel data.
* Uses upng-js (pure JS with pako deflate) — no Node zlib dependency.
*/
async function decodePng(buffer) {
	const UPNG = (await import("upng-js")).default;
	const img = UPNG.decode(buffer.buffer);
	const frames = UPNG.toRGBA8(img);
	const rgba = new Uint8Array(frames[0]);
	return {
		width: img.width,
		height: img.height,
		data: rgba
	};
}
/**
* Extract the dominant color from RGBA pixel data.
* Simple average of all non-transparent pixels.
*/
function extractDominantColor(data, width, height) {
	let r = 0;
	let g = 0;
	let b = 0;
	let count = 0;
	const len = width * height * 4;
	for (let i = 0; i < len; i += 4) {
		if (data[i + 3] < 128) continue;
		r += data[i];
		g += data[i + 1];
		b += data[i + 2];
		count++;
	}
	if (count === 0) return "rgb(0,0,0)";
	return `rgb(${Math.round(r / count)},${Math.round(g / count)},${Math.round(b / count)})`;
}
/**
* Read image dimensions from headers without decoding pixel data.
*/
function getImageDimensions(buffer) {
	try {
		const result = imageSize(buffer);
		if (result.width != null && result.height != null) return {
			width: result.width,
			height: result.height
		};
		return null;
	} catch {
		return null;
	}
}
/**
* Generate blurhash and dominant color from an image buffer.
* Returns null for non-image MIME types or on failure.
*
* @param dimensions - Optional pre-known dimensions. Used as a fallback when
*   image-size cannot parse the buffer (e.g. truncated headers). When the
*   decoded size (width * height * 4) exceeds MAX_DECODED_BYTES, placeholder
*   generation is skipped to avoid OOM on memory-constrained runtimes.
*/
async function generatePlaceholder(buffer, mimeType, dimensions) {
	const format = SUPPORTED_TYPES[mimeType];
	if (!format) return null;
	try {
		const dims = getImageDimensions(buffer) ?? dimensions;
		if (dims && dims.width * dims.height * 4 > MAX_DECODED_BYTES) return null;
		const { width, height, data } = format === "jpeg" ? await decodeJpeg(buffer) : await decodePng(buffer);
		if (width === 0 || height === 0) return null;
		let encodePixels;
		let encodeWidth;
		let encodeHeight;
		if (width > MAX_ENCODE_WIDTH) {
			const scale = MAX_ENCODE_WIDTH / width;
			encodeWidth = MAX_ENCODE_WIDTH;
			encodeHeight = Math.max(1, Math.round(height * scale));
			encodePixels = downsample(data, width, height, encodeWidth, encodeHeight);
		} else {
			encodeWidth = width;
			encodeHeight = height;
			encodePixels = new Uint8ClampedArray(data.buffer, data.byteOffset, data.byteLength);
		}
		return {
			blurhash: encode(encodePixels, encodeWidth, encodeHeight, 4, 3),
			dominantColor: extractDominantColor(data, width, height)
		};
	} catch {
		return null;
	}
}
/**
* Nearest-neighbor downsample of RGBA pixel data.
*/
function downsample(src, srcW, srcH, dstW, dstH) {
	const dst = new Uint8ClampedArray(dstW * dstH * 4);
	for (let y = 0; y < dstH; y++) {
		const srcY = Math.floor(y * srcH / dstH);
		for (let x = 0; x < dstW; x++) {
			const srcX = Math.floor(x * srcW / dstW);
			const srcIdx = (srcY * srcW + srcX) * 4;
			const dstIdx = (y * dstW + x) * 4;
			dst[dstIdx] = src[srcIdx];
			dst[dstIdx + 1] = src[srcIdx + 1];
			dst[dstIdx + 2] = src[srcIdx + 2];
			dst[dstIdx + 3] = src[srcIdx + 3];
		}
	}
	return dst;
}

//#endregion
export { normalizeMediaValue as n, generatePlaceholder as t };
//# sourceMappingURL=placeholder-aiCD8aSZ.mjs.map