globalThis.process ??= {};
globalThis.process.env ??= {};
import { a as __toESM } from "./rolldown-runtime_-UtK6b3T.mjs";
import { t as require_react } from "./react_BkIQlPcV.mjs";
import { h as require_jsx_runtime } from "./registry_CB_IcduS.mjs";
//#region ../../packages/uploads-react/dist/index.js
var import_react = /* @__PURE__ */ __toESM(require_react(), 1);
var import_jsx_runtime = require_jsx_runtime();
/**
* Typed boundary error when the host worker rejects an upload (validation/quota/size/auth).
* Carries a STRUCTURAL `isUploadRejectedError: true` tag so cross-package callers (the host, the
* useUpload hook) identify it WITHOUT `instanceof` — two deduped copies of this class break
* instanceof, and a `name`-only check is spoofable by a plain `{ name: 'UploadRejectedError' }`.
* Mirrors the same-wave `@platform-modules/uploads/storage` `isStorageError === true` convention
* (CLAUDE.md §6: cross-package error identity must use a structural type-guard, never instanceof).
*/
var UploadRejectedError = class extends Error {
	reason;
	status;
	name = "UploadRejectedError";
	isUploadRejectedError = true;
	constructor(reason, status) {
		super(`upload rejected (${status}): ${reason}`);
		this.reason = reason;
		this.status = status;
	}
};
/**
* Typed boundary error when `useUpload.upload` is called re-entrantly — a SECOND upload while one
* is already in flight. The hook tracks a single upload's state, so the second call cannot be
* coalesced (each must return its own MediaItem) and is rejected. DISTINCT from UploadRejectedError
* (a HOST-side rejection carrying reason+status) — a client-side re-entrancy conflict has neither,
* so it gets its own discriminant, never a synthetic-status reuse. Carries a STRUCTURAL
* `isUploadInFlightError: true` tag so callers identify it WITHOUT `instanceof` (CLAUDE.md §6).
*/
var UploadInFlightError = class extends Error {
	name = "UploadInFlightError";
	isUploadInFlightError = true;
	constructor() {
		super("an upload is already in progress");
	}
};
function useUpload(client) {
	const [uploading, setUploading] = (0, import_react.useState)(false);
	const [progress, setProgress] = (0, import_react.useState)(0);
	const [error, setError] = (0, import_react.useState)(null);
	const clientRef = (0, import_react.useRef)(client);
	clientRef.current = client;
	const inFlightRef = (0, import_react.useRef)(false);
	return {
		upload: (0, import_react.useCallback)(async (file, opts) => {
			if (inFlightRef.current) throw new UploadInFlightError();
			inFlightRef.current = true;
			setUploading(true);
			setProgress(0);
			setError(null);
			try {
				const item = await clientRef.current.upload(file, {
					signal: opts?.signal,
					onProgress: (f) => setProgress(f)
				});
				setProgress(1);
				return item;
			} catch (e) {
				const err = e instanceof Error ? e : new Error(String(e));
				setError(err);
				throw err;
			} finally {
				inFlightRef.current = false;
				setUploading(false);
			}
		}, []),
		uploading,
		progress,
		error,
		reset: (0, import_react.useCallback)(() => {
			setError(null);
			setProgress(0);
		}, [])
	};
}
function useMediaLibrary(client, opts) {
	const [items, setItems] = (0, import_react.useState)([]);
	const [loading, setLoading] = (0, import_react.useState)(true);
	const [error, setError] = (0, import_react.useState)(null);
	const [cursor, setCursor] = (0, import_react.useState)(void 0);
	const [hasMore, setHasMore] = (0, import_react.useState)(false);
	const clientRef = (0, import_react.useRef)(client);
	clientRef.current = client;
	const inFlightRef = (0, import_react.useRef)(false);
	const limit = opts?.limit;
	const fetchPage = (0, import_react.useCallback)(async (reset, fromCursor) => {
		if (inFlightRef.current) return;
		inFlightRef.current = true;
		setLoading(true);
		setError(null);
		try {
			const res = await clientRef.current.list({
				cursor: fromCursor,
				limit
			});
			setItems((prev) => reset ? res.items : [...prev, ...res.items]);
			setCursor(res.cursor);
			setHasMore(Boolean(res.cursor));
		} catch (e) {
			setError(e instanceof Error ? e : new Error(String(e)));
		} finally {
			inFlightRef.current = false;
			setLoading(false);
		}
	}, [limit]);
	(0, import_react.useEffect)(() => {
		fetchPage(true, void 0);
	}, [fetchPage]);
	return {
		items,
		loading,
		error,
		hasMore,
		loadMore: (0, import_react.useCallback)(async () => {
			if (!cursor) return;
			await fetchPage(false, cursor);
		}, [cursor, fetchPage]),
		refresh: (0, import_react.useCallback)(async () => {
			await fetchPage(true, void 0);
		}, [fetchPage]),
		remove: (0, import_react.useCallback)(async (key) => {
			try {
				await clientRef.current.remove(key);
				setItems((prev) => prev.filter((i) => i.key !== key));
			} catch (e) {
				setError(e instanceof Error ? e : new Error(String(e)));
			}
		}, [])
	};
}
function MediaLibrary({ client, onSelect, className, allowDelete = true }) {
	const lib = useMediaLibrary(client);
	const up = useUpload(client);
	const inputRef = (0, import_react.useRef)(null);
	const onFile = async (e) => {
		const file = e.target.files?.[0];
		if (!file) return;
		try {
			await up.upload(file);
			await lib.refresh();
		} catch {} finally {
			if (inputRef.current) inputRef.current.value = "";
		}
	};
	const onDelete = async (key) => {
		await lib.remove(key);
		inputRef.current?.focus();
	};
	return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
		"aria-label": "Media library",
		className,
		style: { containerType: "inline-size" },
		children: [
			/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Upload image" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
				ref: inputRef,
				type: "file",
				accept: "image/*",
				onChange: onFile,
				disabled: up.uploading
			})] }), up.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
				role: "alert",
				children: up.error.message
			}) : null] }),
			lib.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
				role: "alert",
				children: lib.error.message
			}) : null,
			!lib.loading && !lib.error && lib.items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: "No media yet. Upload an image to begin." }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", {
				style: {
					display: "grid",
					gridTemplateColumns: "repeat(auto-fill, minmax(min(12cqi, 100%), 1fr))",
					gap: "0.75rem",
					listStyle: "none",
					margin: 0,
					padding: 0
				},
				children: lib.items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
					type: "button",
					"aria-label": `Select ${item.key}`,
					onClick: () => onSelect(item),
					style: {
						display: "block",
						width: "100%",
						border: "none",
						padding: 0,
						background: "none",
						cursor: "pointer"
					},
					children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", {
						src: item.url,
						alt: "",
						width: item.width,
						height: item.height,
						style: {
							width: "100%",
							height: "auto",
							display: "block"
						}
					})
				}), allowDelete ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
					type: "button",
					"aria-label": `Delete ${item.key}`,
					onClick: () => void onDelete(item.key),
					children: "Delete"
				}) : null] }, item.key))
			}),
			lib.hasMore ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
				type: "button",
				onClick: () => void lib.loadMore(),
				disabled: lib.loading,
				children: "Load more"
			}) : null
		]
	});
}
//#endregion
//#region src/lib/media-client.ts
async function readError(res) {
	try {
		return (await res.json()).error?.code ?? `http_${res.status}`;
	} catch {
		return `http_${res.status}`;
	}
}
/** Same-origin client. CSRF is satisfied by the same-origin cookie + Origin header the browser sends. */
function createHostMediaClient() {
	return {
		async upload(file, opts) {
			const fd = new FormData();
			fd.set("file", file);
			const res = await fetch("/api/admin/media", {
				method: "POST",
				body: fd,
				signal: opts?.signal
			});
			if (!res.ok) throw new UploadRejectedError(await readError(res), res.status);
			return (await res.json()).item;
		},
		async list(opts) {
			const qs = new URLSearchParams();
			if (opts?.cursor) qs.set("cursor", opts.cursor);
			if (opts?.limit) qs.set("limit", String(opts.limit));
			const res = await fetch(`/api/admin/media?${qs.toString()}`);
			if (!res.ok) throw new Error(`list failed: ${res.status}`);
			return await res.json();
		},
		async remove(key) {
			const path = key.split("/").map(encodeURIComponent).join("/");
			const res = await fetch(`/api/admin/media/${path}`, { method: "DELETE" });
			if (!res.ok && res.status !== 404) throw new Error(`delete failed: ${res.status}`);
		}
	};
}
//#endregion
export { MediaLibrary as n, createHostMediaClient as t };
