import { useCallback, useEffect, useId, useRef, useState } from "react";
import { Checkbox, Field, Input, Radio, RadioGroup, Table, Textarea } from "@platform-modules/ui-primitives";
import { jsx, jsxs } from "react/jsx-runtime";
//#region src/errors.ts
var FormsClientError = class extends Error {
	code;
	constructor(message, code = "FORMS_CLIENT_ERROR") {
		super(message);
		this.name = "FormsClientError";
		this.code = code;
	}
};
function isFormsClientError(e) {
	return typeof e === "object" && e !== null && e.name === "FormsClientError";
}
//#endregion
//#region src/useFormSubmit.ts
function toError$1(e) {
	if (isFormsClientError(e)) return e;
	if (e instanceof Error) return new FormsClientError(e.message);
	return new FormsClientError(String(e));
}
function useFormSubmit(submit) {
	const [pending, setPending] = useState(false);
	const [fieldErrors, setFieldErrors] = useState([]);
	const submitRef = useRef(submit);
	submitRef.current = submit;
	return {
		submit: useCallback(async (formId, data, renderToken) => {
			setPending(true);
			try {
				const result = await submitRef.current(formId, data, renderToken);
				if (!result.ok) setFieldErrors(result.errors);
				else setFieldErrors([]);
				return result;
			} catch (e) {
				throw toError$1(e);
			} finally {
				setPending(false);
			}
		}, []),
		pending,
		fieldErrors
	};
}
//#endregion
//#region src/FormRenderer.tsx
function initialValue(field) {
	if (field.default !== void 0) return field.default;
	switch (field.type) {
		case "checkbox": return false;
		case "multiselect": return [];
		case "number": return "";
		case "hidden": return field.default ?? "";
		default: return "";
	}
}
function errorsByField(errors) {
	const map = {};
	for (const err of errors) map[err.field] = err;
	return map;
}
const honeypotStyle = {
	position: "absolute",
	left: "-9999px",
	width: 1,
	height: 1,
	overflow: "hidden"
};
const selectClassName = "w-full border border-border bg-surface text-fg rounded-md focus-visible:outline focus-visible:outline-accent disabled:opacity-50 disabled:cursor-not-allowed text-base px-3 py-2";
function FormRenderer({ form, submit, renderToken, onSuccess }) {
	const { submit: doSubmit, pending, fieldErrors } = useFormSubmit(submit);
	const [values, setValues] = useState(() => {
		const init = {};
		for (const field of form.fields) init[field.name] = initialValue(field);
		return init;
	});
	const [localErrors, setLocalErrors] = useState({});
	const [submitError, setSubmitError] = useState(null);
	const [summaryCount, setSummaryCount] = useState(0);
	const alertRef = useRef(null);
	const honeypotName = form.antispam?.honeypot;
	const [honeypot, setHoneypot] = useState("");
	const fieldErrorMap = {
		...errorsByField(fieldErrors),
		...localErrors
	};
	const setValue = useCallback((name, value) => {
		setValues((prev) => ({
			...prev,
			[name]: value
		}));
		setLocalErrors((prev) => {
			if (!prev[name]) return prev;
			const next = { ...prev };
			delete next[name];
			return next;
		});
	}, []);
	const renderField = (field) => {
		const error = fieldErrorMap[field.name]?.message;
		const value = values[field.name];
		if (field.type === "hidden") return /* @__PURE__ */ jsx("input", {
			type: "hidden",
			name: field.name,
			value: String(value ?? "")
		}, field.name);
		switch (field.type) {
			case "text":
			case "email":
			case "url":
			case "tel":
			case "number":
			case "date": return /* @__PURE__ */ jsx(Field, {
				label: field.label,
				required: field.required,
				error,
				children: /* @__PURE__ */ jsx(Input, {
					name: field.name,
					type: field.type,
					required: field.required,
					value: value === "" || value === void 0 ? "" : String(value),
					onChange: (e) => setValue(field.name, field.type === "number" ? e.target.value === "" ? "" : Number(e.target.value) : e.target.value)
				})
			}, field.name);
			case "textarea": return /* @__PURE__ */ jsx(Field, {
				label: field.label,
				required: field.required,
				error,
				children: /* @__PURE__ */ jsx(Textarea, {
					name: field.name,
					required: field.required,
					value: String(value ?? ""),
					onChange: (e) => setValue(field.name, e.target.value)
				})
			}, field.name);
			case "select": {
				const controlId = `field-${field.name}`;
				return /* @__PURE__ */ jsx(Field, {
					label: field.label,
					htmlFor: controlId,
					required: field.required,
					error,
					children: /* @__PURE__ */ jsxs("select", {
						id: controlId,
						name: field.name,
						required: field.required,
						className: selectClassName,
						value: String(value ?? ""),
						onChange: (e) => setValue(field.name, e.target.value),
						"aria-invalid": error ? true : void 0,
						"aria-describedby": error ? `${controlId}-error` : void 0,
						children: [/* @__PURE__ */ jsx("option", {
							value: "",
							children: "Select…"
						}), (field.options ?? []).map((opt) => /* @__PURE__ */ jsx("option", {
							value: opt.value,
							children: opt.label
						}, opt.value))]
					})
				}, field.name);
			}
			case "multiselect": {
				const selected = Array.isArray(value) ? value : [];
				const labelId = `field-${field.name}-label`;
				const errorId = error ? `field-${field.name}-error` : void 0;
				return /* @__PURE__ */ jsxs("div", {
					role: "group",
					"aria-labelledby": labelId,
					"aria-describedby": errorId,
					className: "flex flex-col gap-1",
					children: [
						/* @__PURE__ */ jsxs("span", {
							id: labelId,
							className: "text-sm font-medium text-fg",
							children: [field.label, field.required ? /* @__PURE__ */ jsx("span", {
								"aria-hidden": "true",
								children: " *"
							}) : null]
						}),
						/* @__PURE__ */ jsx("div", {
							className: "flex flex-col gap-2",
							children: (field.options ?? []).map((opt) => {
								const optId = `${field.name}-${opt.value}`;
								const checked = selected.includes(opt.value);
								return /* @__PURE__ */ jsxs("label", {
									htmlFor: optId,
									className: "flex items-center gap-2 text-sm",
									children: [/* @__PURE__ */ jsx(Checkbox, {
										id: optId,
										checked,
										onCheckedChange: (v) => {
											const next = v === true ? [...selected, opt.value] : selected.filter((s) => s !== opt.value);
											setValue(field.name, next);
										}
									}), opt.label]
								}, opt.value);
							})
						}),
						error ? /* @__PURE__ */ jsx("span", {
							id: errorId,
							role: "alert",
							className: "text-sm text-danger",
							children: error
						}) : null
					]
				}, field.name);
			}
			case "checkbox": return /* @__PURE__ */ jsx(Field, {
				label: field.label,
				required: field.required,
				error,
				children: /* @__PURE__ */ jsx(Checkbox, {
					checked: Boolean(value),
					onCheckedChange: (v) => setValue(field.name, v === true)
				})
			}, field.name);
			case "radio": {
				const labelId = `field-${field.name}-label`;
				const errorId = error ? `field-${field.name}-error` : void 0;
				return /* @__PURE__ */ jsxs("div", {
					className: "flex flex-col gap-1",
					children: [
						/* @__PURE__ */ jsxs("span", {
							id: labelId,
							className: "text-sm font-medium text-fg",
							children: [field.label, field.required ? /* @__PURE__ */ jsx("span", {
								"aria-hidden": "true",
								children: " *"
							}) : null]
						}),
						/* @__PURE__ */ jsx(RadioGroup, {
							name: field.name,
							value: String(value ?? ""),
							onValueChange: (v) => setValue(field.name, v),
							"aria-labelledby": labelId,
							"aria-describedby": errorId,
							children: (field.options ?? []).map((opt) => {
								const optId = `${field.name}-${opt.value}`;
								return /* @__PURE__ */ jsxs("label", {
									htmlFor: optId,
									className: "flex items-center gap-2 text-sm",
									children: [/* @__PURE__ */ jsx(Radio, {
										id: optId,
										value: opt.value
									}), opt.label]
								}, opt.value);
							})
						}),
						error ? /* @__PURE__ */ jsx("span", {
							id: errorId,
							role: "alert",
							className: "text-sm text-danger",
							children: error
						}) : null
					]
				}, field.name);
			}
			default: return field.type;
		}
	};
	const handleSubmit = async (e) => {
		e.preventDefault();
		setSubmitError(null);
		setLocalErrors({});
		setSummaryCount(0);
		const data = { ...values };
		if (honeypotName) data[honeypotName] = honeypot;
		try {
			const result = await doSubmit(form.id, data, renderToken);
			if (result.ok) onSuccess?.(result.id);
			else {
				setLocalErrors(errorsByField(result.errors));
				setSummaryCount(result.errors.length);
				requestAnimationFrame(() => alertRef.current?.focus());
			}
		} catch (err) {
			const message = err instanceof Error ? err.message : "Submission failed";
			setSubmitError(message);
			requestAnimationFrame(() => alertRef.current?.focus());
		}
	};
	const tokenInputId = useId();
	return /* @__PURE__ */ jsxs("form", {
		onSubmit: (e) => void handleSubmit(e),
		noValidate: true,
		children: [
			renderToken ? /* @__PURE__ */ jsx("input", {
				type: "hidden",
				name: "_renderToken",
				id: tokenInputId,
				value: renderToken,
				readOnly: true
			}) : null,
			honeypotName ? /* @__PURE__ */ jsx("input", {
				type: "text",
				name: honeypotName,
				value: honeypot,
				onChange: (e) => setHoneypot(e.target.value),
				"aria-hidden": "true",
				tabIndex: -1,
				autoComplete: "off",
				style: honeypotStyle
			}) : null,
			form.fields.map(renderField),
			submitError || summaryCount > 0 ? /* @__PURE__ */ jsx("div", {
				ref: alertRef,
				role: "alert",
				tabIndex: -1,
				className: "text-sm text-danger mt-2",
				children: submitError ?? `Please fix ${summaryCount} field${summaryCount === 1 ? "" : "s"} below.`
			}) : null,
			/* @__PURE__ */ jsx("button", {
				type: "submit",
				disabled: pending,
				className: "mt-4 px-4 py-2 border rounded",
				children: pending ? "Submitting…" : "Submit"
			})
		]
	});
}
//#endregion
//#region src/useSubmissions.ts
function toError(e) {
	if (e instanceof FormsClientError) return e;
	if (e instanceof Error) return new FormsClientError(e.message);
	return new FormsClientError(String(e));
}
function useSubmissions(client, formId, opts) {
	const [items, setItems] = useState([]);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);
	const [hasMore, setHasMore] = useState(false);
	const [reloadToken, setReloadToken] = useState(0);
	const clientRef = useRef(client);
	clientRef.current = client;
	const nextCursorRef = useRef(void 0);
	const optsKey = JSON.stringify(opts ?? {});
	const reload = useCallback(() => {
		setReloadToken((t) => t + 1);
	}, []);
	useEffect(() => {
		let cancelled = false;
		setLoading(true);
		setError(null);
		nextCursorRef.current = void 0;
		clientRef.current.list(formId, opts).then((page) => {
			if (!cancelled) {
				setItems(page.items);
				nextCursorRef.current = page.nextCursor;
				setHasMore(Boolean(page.nextCursor));
				setLoading(false);
			}
		}).catch((e) => {
			if (!cancelled) {
				setError(toError(e));
				setLoading(false);
			}
		});
		return () => {
			cancelled = true;
		};
	}, [
		formId,
		optsKey,
		reloadToken
	]);
	return {
		items,
		loading,
		error,
		hasMore,
		loadMore: useCallback(async () => {
			const cursor = nextCursorRef.current;
			if (!cursor) return;
			try {
				const page = await clientRef.current.list(formId, {
					...opts,
					cursor
				});
				nextCursorRef.current = page.nextCursor;
				setHasMore(Boolean(page.nextCursor));
				setItems((prev) => [...prev, ...page.items]);
			} catch (e) {
				setError(toError(e));
			}
		}, [formId, optsKey]),
		remove: useCallback(async (id) => {
			const snapshot = items;
			setItems((prev) => prev.filter((item) => item.id !== id));
			try {
				await clientRef.current.delete(formId, id);
			} catch (e) {
				setItems(snapshot);
				const err = toError(e);
				setError(err);
				throw err;
			}
		}, [formId, items]),
		reload
	};
}
//#endregion
//#region src/SubmissionsTable.tsx
function defaultFormatDate(iso) {
	try {
		return new Date(iso).toLocaleString();
	} catch {
		return iso;
	}
}
function previewData(data) {
	const email = data.email;
	if (typeof email === "string" && email) return email;
	const first = Object.values(data).find((v) => typeof v === "string" && v);
	return typeof first === "string" ? first : "—";
}
function SubmissionsTable({ client, formId, formatDate = defaultFormatDate }) {
	const [includeSpam, setIncludeSpam] = useState(false);
	const { items, loading, error, hasMore, loadMore, remove } = useSubmissions(client, formId, { includeSpam });
	return /* @__PURE__ */ jsxs("div", { children: [
		/* @__PURE__ */ jsxs("label", {
			className: "flex items-center gap-2 mb-4 text-sm",
			children: [/* @__PURE__ */ jsx("input", {
				type: "checkbox",
				checked: includeSpam,
				onChange: (e) => setIncludeSpam(e.target.checked),
				"aria-label": "Show spam"
			}), "Show spam"]
		}),
		error ? /* @__PURE__ */ jsx("div", {
			role: "alert",
			className: "text-sm text-danger mb-2",
			children: error.message
		}) : null,
		loading && items.length === 0 ? /* @__PURE__ */ jsx("p", { children: "Loading…" }) : null,
		/* @__PURE__ */ jsxs(Table, {
			caption: "Form submissions",
			children: [/* @__PURE__ */ jsx(Table.Head, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
				/* @__PURE__ */ jsx(Table.Th, {
					scope: "col",
					children: "Created"
				}),
				/* @__PURE__ */ jsx(Table.Th, {
					scope: "col",
					children: "Data"
				}),
				/* @__PURE__ */ jsx(Table.Th, {
					scope: "col",
					children: "Status"
				}),
				/* @__PURE__ */ jsx(Table.Th, {
					scope: "col",
					children: "Actions"
				})
			] }) }), /* @__PURE__ */ jsx(Table.Body, { children: items.map((row) => /* @__PURE__ */ jsxs(Table.Row, { children: [
				/* @__PURE__ */ jsx(Table.Td, { children: formatDate(row.createdAt) }),
				/* @__PURE__ */ jsx(Table.Td, { children: previewData(row.data) }),
				/* @__PURE__ */ jsx(Table.Td, { children: row.spam ? /* @__PURE__ */ jsx("span", { children: "Spam" }) : /* @__PURE__ */ jsx("span", { children: "OK" }) }),
				/* @__PURE__ */ jsx(Table.Td, { children: /* @__PURE__ */ jsx("button", {
					type: "button",
					onClick: () => void remove(row.id),
					children: "Delete"
				}) })
			] }, row.id)) })]
		}),
		hasMore ? /* @__PURE__ */ jsx("button", {
			type: "button",
			onClick: () => void loadMore(),
			className: "mt-4",
			children: "Load more"
		}) : null
	] });
}
//#endregion
export { FormRenderer, FormsClientError, SubmissionsTable, isFormsClientError, useFormSubmit, useSubmissions };
