'use client'
import { cn } from "../cva.js";
import { useCallback, useId, useMemo, useRef, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { RichTextEditor, createDefaultExtensions } from "@platform-modules/ui-editor";
//#region src/editor-widget/errors.ts
function createEditorWidgetError(code, message) {
	return Object.assign(new Error(message), {
		name: "EditorWidgetError",
		code
	});
}
function isEditorWidgetError(value) {
	if (typeof value !== "object" || value === null) return false;
	const candidate = value;
	return candidate.name === "EditorWidgetError" && (candidate.code === "invalid_media" || candidate.code === "media_insert_failed") && typeof candidate.message === "string";
}
//#endregion
//#region src/editor-widget/preview.ts
const PREVIEW_STYLE = `
:root { color-scheme: light dark; }
html, body { min-block-size: 100%; }
html { overflow: auto; }
body {
  box-sizing: border-box;
  margin: 0;
  padding: 1rem;
  overflow-wrap: anywhere;
  font: 1rem/1.6 sans-serif;
}
body > :first-child { margin-block-start: 0; }
body > :last-child { margin-block-end: 0; }
h2 { margin-block: 1.5rem 0.75rem; font-size: 1.5rem; line-height: 1.25; }
p, ul { margin-block: 0 1rem; }
ul { padding-inline-start: 1.5rem; list-style: disc; }
li + li { margin-block-start: 0.5rem; }
img { max-inline-size: 100%; block-size: auto; }
`;
const DROP_WITH_CONTENT = /* @__PURE__ */ new Set([
	"SCRIPT",
	"STYLE",
	"IFRAME",
	"OBJECT",
	"EMBED",
	"TEMPLATE",
	"NOSCRIPT",
	"SVG",
	"MATH"
]);
const DROP_ELEMENT = /* @__PURE__ */ new Set([
	"INPUT",
	"BUTTON",
	"SELECT",
	"TEXTAREA",
	"OPTION"
]);
const SAFE_CONTAINER = /* @__PURE__ */ new Set([
	"P",
	"H1",
	"H2",
	"STRONG",
	"EM",
	"UL",
	"LI",
	"BR"
]);
function safeImageSource(value) {
	try {
		const url = new URL(value);
		return url.protocol === "https:" && url.username === "" && url.password === "";
	} catch {
		return false;
	}
}
function positiveFiniteAttribute(value) {
	if (value === null || value.trim() === "") return null;
	const parsed = Number(value);
	return Number.isFinite(parsed) && parsed > 0 ? value : null;
}
function copyDirection(source, target) {
	const dir = source.getAttribute("dir");
	if (dir === "ltr" || dir === "rtl") target.setAttribute("dir", dir);
}
function safeNode(source, targetDocument) {
	if (source.nodeType === Node.TEXT_NODE) return targetDocument.createTextNode(source.textContent ?? "");
	if (source.nodeType !== Node.ELEMENT_NODE) return targetDocument.createDocumentFragment();
	const element = source;
	const tagName = element.tagName.toUpperCase();
	if (DROP_WITH_CONTENT.has(tagName) || DROP_ELEMENT.has(tagName)) return targetDocument.createDocumentFragment();
	if (tagName === "IMG") {
		const src = element.getAttribute("src") ?? "";
		if (!safeImageSource(src)) return targetDocument.createDocumentFragment();
		const image = targetDocument.createElement("img");
		image.setAttribute("src", src);
		image.setAttribute("alt", element.getAttribute("alt") ?? "");
		const width = positiveFiniteAttribute(element.getAttribute("width"));
		const height = positiveFiniteAttribute(element.getAttribute("height"));
		if (width !== null) image.setAttribute("width", width);
		if (height !== null) image.setAttribute("height", height);
		copyDirection(element, image);
		return image;
	}
	const normalizedTag = tagName === "B" ? "STRONG" : tagName === "I" ? "EM" : tagName;
	const target = SAFE_CONTAINER.has(normalizedTag) ? targetDocument.createElement(normalizedTag.toLowerCase()) : targetDocument.createDocumentFragment();
	if (target.nodeType === Node.ELEMENT_NODE) copyDirection(element, target);
	for (const child of Array.from(element.childNodes)) target.appendChild(safeNode(child, targetDocument));
	return target;
}
function constrainPreviewHtml(sourceHtml) {
	if (typeof DOMParser === "undefined") return sourceHtml.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
	const sourceDocument = new DOMParser().parseFromString(sourceHtml, "text/html");
	const targetDocument = document.implementation.createHTMLDocument("");
	for (const node of Array.from(sourceDocument.body.childNodes)) targetDocument.body.appendChild(safeNode(node, targetDocument));
	return targetDocument.body.innerHTML;
}
function buildPreviewDocument(sourceHtml, dir) {
	const html = constrainPreviewHtml(sourceHtml);
	return `<!doctype html>
<html dir="${dir}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="referrer" content="no-referrer">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https:; style-src 'unsafe-inline'; base-uri 'none'; form-action 'none'; object-src 'none'">
<style>${PREVIEW_STYLE}</style>
</head>
<body>${html}</body>
</html>`;
}
//#endregion
//#region src/editor-widget/EditorWidget.tsx
const FORMAT_BUTTON_CLASSES = "EditorWidget-formatButton inline-flex min-h-[var(--mod-touch-min)] min-w-[var(--mod-touch-min)] flex-none items-center justify-center border border-[var(--mod-color-border)] bg-[var(--mod-color-surface)] px-3 text-sm text-[var(--mod-color-fg)] hover:bg-[var(--mod-color-surface-raised)] active:border-[var(--mod-color-border-strong)] active:bg-[var(--mod-color-surface-raised)] aria-[pressed=true]:border-[var(--mod-color-border-strong)] aria-[pressed=true]:bg-[var(--mod-color-surface-raised)] aria-[pressed=true]:outline aria-[pressed=true]:outline-current focus-visible:z-10 focus-visible:outline-[var(--mod-focus-ring-width)] focus-visible:outline-[var(--mod-focus-ring-color)] focus-visible:outline-offset-[var(--mod-focus-ring-offset)] disabled:cursor-not-allowed disabled:opacity-[var(--mod-opacity-disabled)]";
const TAB_CLASSES = "EditorWidget-tab inline-flex min-h-[var(--mod-touch-min)] items-center justify-center rounded-md border border-transparent px-3 text-sm text-[var(--mod-color-fg)] data-[state=inactive]:bg-[var(--mod-color-surface)] data-[state=inactive]:hover:bg-[var(--mod-color-surface-raised)] data-[state=active]:bg-[var(--mod-color-accent)] data-[state=active]:text-[var(--mod-color-accent-fg)] forced-colors:data-[state=active]:border-current forced-colors:data-[state=active]:outline forced-colors:data-[state=active]:outline-2 focus-visible:outline-[var(--mod-focus-ring-width)] focus-visible:outline-[var(--mod-focus-ring-color)] focus-visible:outline-offset-[var(--mod-focus-ring-offset)]";
const EDITOR_SURFACE_CLASSES = "EditorWidget-surface min-w-0 rounded-md border border-[var(--mod-color-border)] bg-[var(--mod-color-surface)] text-[var(--mod-color-fg)] focus-within:outline-[var(--mod-focus-ring-width)] focus-within:outline-[var(--mod-focus-ring-color)] focus-within:outline-offset-[var(--mod-focus-ring-offset)] [&_[role=textbox]]:min-h-[calc(var(--mod-touch-min)*6)] [&_[role=textbox]]:[overflow-wrap:anywhere] [&_[role=textbox]]:p-4 [&_[role=textbox]]:outline-none [&_[role=textbox]>*:first-child]:mt-0 [&_[role=textbox]>*:last-child]:mb-0 [&_[role=textbox]_h2]:my-4 [&_[role=textbox]_h2]:text-xl [&_[role=textbox]_h2]:font-semibold [&_[role=textbox]_h2]:leading-tight [&_[role=textbox]_p]:mb-3 [&_[role=textbox]_ul]:mb-3 [&_[role=textbox]_ul]:list-disc [&_[role=textbox]_ul]:ps-6 [&_[role=textbox]_li+li]:mt-1 [&_[role=textbox]_img]:h-auto [&_[role=textbox]_img]:max-w-full";
const FORMATTING_ITEMS = [
	{
		label: "Bold",
		text: "B",
		shortcut: "Control+B Meta+B"
	},
	{
		label: "Italic",
		text: "I",
		shortcut: "Control+I Meta+I"
	},
	{
		label: "Heading 2",
		text: "H2"
	},
	{
		label: "Bullet list",
		text: "List"
	}
];
function positiveFinite(value) {
	return value === void 0 || Number.isFinite(value) && value > 0;
}
function safeMediaSource(value) {
	if (typeof value !== "string" || value.trim() !== value || value.length === 0) return false;
	try {
		const url = new URL(value);
		return url.protocol === "https:" && url.username === "" && url.password === "";
	} catch {
		return false;
	}
}
function validMediaItem(value) {
	if (typeof value !== "object" || value === null) return false;
	const item = value;
	return safeMediaSource(item.src) && typeof item.alt === "string" && positiveFinite(item.width) && positiveFinite(item.height);
}
function FormattingToolbar({ state, ariaLabel, dir, media, reportError, reportStatus }) {
	const buttons = useRef([]);
	const [tabStop, setTabStop] = useState(0);
	const editor = state.editor;
	const items = [
		{
			...FORMATTING_ITEMS[0],
			pressed: state.isBold,
			run: () => {
				if (editor?.can().toggleBold()) editor.chain().focus().toggleBold().run();
			}
		},
		{
			...FORMATTING_ITEMS[1],
			pressed: state.isItalic,
			run: () => {
				if (editor?.can().toggleItalic()) editor.chain().focus().toggleItalic().run();
			}
		},
		{
			...FORMATTING_ITEMS[2],
			pressed: state.isH2,
			run: () => {
				if (editor?.can().toggleHeading({ level: 2 })) editor.chain().focus().toggleHeading({ level: 2 }).run();
			}
		},
		{
			...FORMATTING_ITEMS[3],
			pressed: state.isBullet,
			run: () => {
				if (editor?.can().toggleBulletList()) editor.chain().focus().toggleBulletList().run();
			}
		}
	];
	function moveFocus(event) {
		const forward = dir === "rtl" ? -1 : 1;
		let next = tabStop;
		if (event.key === "ArrowRight") next = (tabStop + forward + items.length) % items.length;
		else if (event.key === "ArrowLeft") next = (tabStop - forward + items.length) % items.length;
		else if (event.key === "Home") next = 0;
		else if (event.key === "End") next = items.length - 1;
		else return;
		event.preventDefault();
		setTabStop(next);
		buttons.current[next]?.focus();
	}
	function preserveEditorFocus(event) {
		event.preventDefault();
	}
	function activateWithKeyboard(event, run) {
		if (event.key !== "Enter" && event.key !== " ") return;
		event.preventDefault();
		run();
	}
	function insertMedia(item) {
		if (!validMediaItem(item)) {
			reportError(createEditorWidgetError("invalid_media", "The selected image is invalid."));
			return false;
		}
		if (!editor || editor.schema.nodes.image === void 0) {
			reportError(createEditorWidgetError("media_insert_failed", "The selected image could not be inserted."));
			return false;
		}
		try {
			if (!editor.chain().insertContent({
				type: "image",
				attrs: {
					src: item.src,
					alt: item.alt,
					...item.width === void 0 ? {} : { width: item.width },
					...item.height === void 0 ? {} : { height: item.height }
				}
			}).run()) {
				reportError(createEditorWidgetError("media_insert_failed", "The selected image could not be inserted."));
				return false;
			}
			reportStatus("Image inserted.");
			return true;
		} catch {
			reportError(createEditorWidgetError("media_insert_failed", "The selected image could not be inserted."));
			return false;
		}
	}
	const mediaDisabled = editor === null || editor.schema.nodes.image === void 0;
	return /* @__PURE__ */ jsxs("div", {
		className: "EditorWidget-toolbarRow flex min-w-0 flex-col gap-2 @md:flex-row @md:items-start @md:justify-between",
		"data-editor-widget-part": "toolbar-row",
		"data-editor-ready": editor === null ? "false" : "true",
		children: [/* @__PURE__ */ jsx("div", {
			className: "EditorWidget-toolbar flex max-w-full overflow-x-auto rounded-md",
			"data-editor-widget-part": "formatting-toolbar",
			role: "toolbar",
			"aria-label": `${ariaLabel} formatting`,
			"aria-orientation": "horizontal",
			onKeyDown: moveFocus,
			children: items.map((item, index) => /* @__PURE__ */ jsx("button", {
				ref: (node) => {
					buttons.current[index] = node;
				},
				className: FORMAT_BUTTON_CLASSES,
				"data-editor-widget-part": "format-button",
				type: "button",
				"aria-label": item.label,
				"aria-keyshortcuts": item.shortcut,
				"aria-pressed": item.pressed,
				disabled: editor === null,
				tabIndex: tabStop === index ? 0 : -1,
				onFocus: () => setTabStop(index),
				onMouseDown: preserveEditorFocus,
				onPointerDown: preserveEditorFocus,
				onKeyDown: (event) => activateWithKeyboard(event, item.run),
				onClick: item.run,
				children: item.text
			}, item.label))
		}), media ? /* @__PURE__ */ jsx("div", {
			className: "EditorWidget-media min-w-0",
			"data-editor-widget-part": "media-picker",
			children: media.renderPicker({
				disabled: mediaDisabled,
				onSelect: insertMedia
			})
		}) : null]
	});
}
function sameOptionValue(left, right, seen = /* @__PURE__ */ new WeakMap()) {
	if (Object.is(left, right)) return true;
	if (typeof left !== "object" || left === null || typeof right !== "object" || right === null) return false;
	if (Object.getPrototypeOf(left) !== Object.getPrototypeOf(right)) return false;
	if (seen.get(left) === right) return true;
	seen.set(left, right);
	const leftKeys = Object.keys(left);
	const rightKeys = Object.keys(right);
	if (leftKeys.length !== rightKeys.length) return false;
	return leftKeys.every((key) => Object.prototype.hasOwnProperty.call(right, key) && sameOptionValue(left[key], right[key], seen));
}
function sameExtensionImplementation(left, right) {
	const leftKeys = Object.keys(left);
	const rightKeys = Object.keys(right);
	if (leftKeys.length !== rightKeys.length) return false;
	return leftKeys.every((key) => {
		if (!Object.prototype.hasOwnProperty.call(right, key)) return false;
		const leftValue = Reflect.get(left, key);
		const rightValue = Reflect.get(right, key);
		if (typeof leftValue === "function" || typeof rightValue === "function") return typeof leftValue === "function" && typeof rightValue === "function" && Function.prototype.toString.call(leftValue) === Function.prototype.toString.call(rightValue);
		return sameOptionValue(leftValue, rightValue);
	});
}
function sameExtensionConfig(left, right) {
	if (left === right) return true;
	if (!left || !right || left.extensions.length !== right.extensions.length) return false;
	return left.extensions.every((extension, index) => {
		const candidate = right.extensions[index];
		return candidate !== void 0 && extension.name === candidate.name && sameOptionValue(extension.options, candidate.options) && sameExtensionImplementation(extension.config, candidate.config);
	});
}
function useStableExtensionConfig(config) {
	const stable = useRef(config);
	if (!sameExtensionConfig(stable.current, config)) stable.current = config;
	return stable.current;
}
function editorExtensions(mediaExtensions) {
	const extensionsByName = new Map(createDefaultExtensions().map((extension) => [extension.name, extension]));
	for (const extension of mediaExtensions?.extensions ?? []) extensionsByName.set(extension.name, extension);
	return { extensions: [...extensionsByName.values()] };
}
function EditorWidget({ value, onChange, dir = "ltr", readOnly = false, ariaLabel, ariaDescribedBy, media, onError, className }) {
	const instanceId = useId();
	const labelId = `${instanceId}-label`;
	const writeTabId = `${instanceId}-write-tab`;
	const previewTabId = `${instanceId}-preview-tab`;
	const writePanelId = `${instanceId}-write-panel`;
	const previewPanelId = `${instanceId}-preview-panel`;
	const writeTab = useRef(null);
	const previewTab = useRef(null);
	const [activeTab, setActiveTab] = useState("write");
	const announcementId = useRef(0);
	const [announcement, setAnnouncement] = useState(null);
	const mediaExtensions = useStableExtensionConfig(media?.extensions);
	const extensions = useMemo(() => editorExtensions(mediaExtensions), [mediaExtensions]);
	const previewSource = useMemo(() => activeTab === "preview" ? buildPreviewDocument(value, dir) : void 0, [
		activeTab,
		value,
		dir
	]);
	const announce = useCallback((message) => {
		announcementId.current += 1;
		setAnnouncement({
			id: announcementId.current,
			message
		});
	}, []);
	const reportError = useCallback((error) => {
		announce(error.message);
		onError?.(error);
	}, [announce, onError]);
	function onTabKeyDown(event) {
		if (![
			"ArrowLeft",
			"ArrowRight",
			"Home",
			"End"
		].includes(event.key)) return;
		event.preventDefault();
		const order = dir === "rtl" ? ["preview", "write"] : ["write", "preview"];
		const current = order.indexOf(activeTab);
		const next = (event.key === "Home" ? "write" : event.key === "End" ? "preview" : event.key === "ArrowRight" ? order[(current + 1) % order.length] : order[(current - 1 + order.length) % order.length]) ?? activeTab;
		setActiveTab(next);
		(next === "write" ? writeTab : previewTab).current?.focus();
	}
	const writeInactive = activeTab !== "write";
	const previewInactive = activeTab !== "preview";
	return /* @__PURE__ */ jsxs("section", {
		className: cn("EditorWidget @container grid min-w-0 gap-2 text-[var(--mod-color-fg)]", className),
		"data-editor-widget": "",
		"data-read-only": readOnly ? "true" : "false",
		dir,
		"aria-labelledby": labelId,
		children: [
			/* @__PURE__ */ jsx("span", {
				id: labelId,
				className: "sr-only",
				children: ariaLabel
			}),
			!readOnly ? /* @__PURE__ */ jsx("p", {
				className: "EditorWidget-hint m-0 text-sm text-[var(--mod-color-fg-muted)]",
				children: "Shortcuts: Control/Command+B and Control/Command+I."
			}) : null,
			/* @__PURE__ */ jsxs("div", {
				className: "EditorWidget-tabs grid min-w-0 gap-2",
				"data-editor-widget-part": "tabs",
				children: [
					/* @__PURE__ */ jsxs("div", {
						className: "EditorWidget-tabList inline-flex w-fit max-w-full gap-1 rounded-md bg-[var(--mod-color-surface)] p-1",
						role: "tablist",
						"aria-label": `${ariaLabel} view`,
						children: [/* @__PURE__ */ jsx("button", {
							ref: writeTab,
							id: writeTabId,
							className: TAB_CLASSES,
							"data-editor-widget-part": "tab",
							"data-editor-widget-view": "write",
							"data-state": activeTab === "write" ? "active" : "inactive",
							type: "button",
							role: "tab",
							"aria-selected": activeTab === "write",
							"aria-controls": writePanelId,
							tabIndex: activeTab === "write" ? 0 : -1,
							onClick: () => setActiveTab("write"),
							onKeyDown: onTabKeyDown,
							children: "Write"
						}), /* @__PURE__ */ jsx("button", {
							ref: previewTab,
							id: previewTabId,
							className: TAB_CLASSES,
							"data-editor-widget-part": "tab",
							"data-editor-widget-view": "preview",
							"data-state": activeTab === "preview" ? "active" : "inactive",
							type: "button",
							role: "tab",
							"aria-selected": activeTab === "preview",
							"aria-controls": previewPanelId,
							tabIndex: activeTab === "preview" ? 0 : -1,
							onClick: () => setActiveTab("preview"),
							onKeyDown: onTabKeyDown,
							children: "Preview"
						})]
					}),
					/* @__PURE__ */ jsx("div", {
						ref: (node) => {
							node?.toggleAttribute("inert", writeInactive);
						},
						id: writePanelId,
						className: "EditorWidget-panel EditorWidget-panel--write min-w-0",
						"data-editor-widget-part": "write-panel",
						role: "tabpanel",
						"aria-labelledby": writeTabId,
						"aria-hidden": writeInactive || void 0,
						hidden: writeInactive,
						children: /* @__PURE__ */ jsx(RichTextEditor, {
							value,
							onChange,
							dir,
							"aria-label": ariaLabel,
							"aria-describedby": ariaDescribedBy,
							readOnly,
							className: EDITOR_SURFACE_CLASSES,
							extensions,
							renderToolbar: readOnly ? () => null : (state) => /* @__PURE__ */ jsx(FormattingToolbar, {
								state,
								ariaLabel,
								dir,
								media,
								reportError,
								reportStatus: announce
							})
						})
					}),
					/* @__PURE__ */ jsx("div", {
						ref: (node) => {
							node?.toggleAttribute("inert", previewInactive);
						},
						id: previewPanelId,
						className: "EditorWidget-panel EditorWidget-panel--preview min-w-0",
						"data-editor-widget-part": "preview-panel",
						role: "tabpanel",
						"aria-labelledby": previewTabId,
						"aria-hidden": previewInactive || void 0,
						hidden: previewInactive,
						children: previewSource !== void 0 ? /* @__PURE__ */ jsx("iframe", {
							className: "EditorWidget-preview block min-h-[calc(var(--mod-touch-min)*8)] w-full rounded-md border border-[var(--mod-color-border)] bg-[var(--mod-color-surface)] focus-visible:outline-[var(--mod-focus-ring-width)] focus-visible:outline-[var(--mod-focus-ring-color)] focus-visible:outline-offset-[var(--mod-focus-ring-offset)]",
							"data-editor-widget-part": "preview",
							title: `${ariaLabel} preview`,
							sandbox: "",
							referrerPolicy: "no-referrer",
							tabIndex: 0,
							srcDoc: previewSource
						}) : null
					})
				]
			}),
			/* @__PURE__ */ jsx("p", {
				className: "sr-only",
				role: "status",
				"aria-live": "polite",
				"aria-atomic": "true",
				children: announcement ? /* @__PURE__ */ jsx("span", { children: announcement.message }, announcement.id) : null
			})
		]
	});
}
//#endregion
export { EditorWidget, isEditorWidgetError };
