import { createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
//#region src/provider.tsx
const MenuContext = createContext(null);
function MenuProvider({ client, children }) {
	return /* @__PURE__ */ jsx(MenuContext.Provider, {
		value: client,
		children
	});
}
function useMenuClient() {
	const ctx = useContext(MenuContext);
	if (!ctx) throw new Error("useMenuClient must be used within <MenuProvider>");
	return ctx;
}
//#endregion
//#region src/hooks.ts
function toError(e) {
	return e instanceof Error ? e : new Error(String(e));
}
function removeNodeFromTree(items, itemId) {
	return items.filter((item) => item.id !== itemId).map((item) => ({
		...item,
		children: removeNodeFromTree(item.children, itemId)
	}));
}
function findNodeInTree(items, itemId) {
	for (const item of items) {
		if (item.id === itemId) return item;
		const found = findNodeInTree(item.children, itemId);
		if (found) return found;
	}
	return null;
}
function updateSubtreeDepth(node, parentDepth) {
	const depth = parentDepth + 1;
	return {
		...node,
		depth,
		children: node.children.map((child, index) => ({
			...updateSubtreeDepth(child, depth),
			position: index
		}))
	};
}
function applyReorderToTree(items, parentId, orderedIds) {
	if (parentId === null) {
		const byId = new Map(items.map((item) => [item.id, item]));
		return orderedIds.map((id, position) => {
			return {
				...byId.get(id),
				position,
				parentId: null,
				depth: 0
			};
		});
	}
	return items.map((item) => {
		if (item.id === parentId) {
			const byId = new Map(item.children.map((child) => [child.id, child]));
			const children = orderedIds.map((id, position) => {
				return {
					...byId.get(id),
					position,
					parentId,
					depth: item.depth + 1
				};
			});
			return {
				...item,
				children
			};
		}
		return {
			...item,
			children: applyReorderToTree(item.children, parentId, orderedIds)
		};
	});
}
function applyMoveToTree(items, itemId, newParentId, newPosition) {
	const node = findNodeInTree(items, itemId);
	if (!node) return items;
	const without = removeNodeFromTree(items, itemId);
	if (newParentId === null) {
		const roots = [...without];
		const moved = {
			...node,
			parentId: null,
			depth: 0,
			children: node.children.map((child, index) => ({
				...updateSubtreeDepth(child, 0),
				position: index
			}))
		};
		roots.splice(newPosition, 0, moved);
		return roots.map((item, position) => ({
			...item,
			position
		}));
	}
	return insertNodeUnderParent(without, newParentId, newPosition, node);
}
function insertNodeUnderParent(items, parentId, position, node) {
	return items.map((item) => {
		if (item.id === parentId) {
			const parentDepth = item.depth;
			const moved = {
				...node,
				parentId,
				depth: parentDepth + 1,
				children: node.children.map((child, index) => ({
					...updateSubtreeDepth(child, parentDepth + 1),
					position: index
				}))
			};
			const children = [...item.children];
			children.splice(position, 0, moved);
			return {
				...item,
				children: children.map((child, index) => ({
					...child,
					position: index
				}))
			};
		}
		return {
			...item,
			children: insertNodeUnderParent(item.children, parentId, position, node)
		};
	});
}
function useMenus(opts = {}) {
	const client = useMenuClient();
	const hasSeed = opts.initialData !== void 0;
	const [menus, setMenus] = useState(opts.initialData ?? []);
	const [loading, setLoading] = useState(!hasSeed);
	const [error, setError] = useState(null);
	const [reloadToken, setReloadToken] = useState(0);
	const clientRef = useRef(client);
	clientRef.current = client;
	const reload = useCallback(() => {
		setReloadToken((t) => t + 1);
	}, []);
	useEffect(() => {
		if (hasSeed && reloadToken === 0) return;
		let cancelled = false;
		setLoading(true);
		setError(null);
		clientRef.current.listMenus().then((next) => {
			if (!cancelled) {
				setMenus(next);
				setLoading(false);
			}
		}).catch((e) => {
			if (!cancelled) {
				setError(toError(e));
				setLoading(false);
			}
		});
		return () => {
			cancelled = true;
		};
	}, [hasSeed, reloadToken]);
	const deleteMenu = useCallback(async (menuId) => {
		const snapshot = menus;
		setError(null);
		setMenus((prev) => prev.filter((menu) => menu.id !== menuId));
		try {
			await clientRef.current.deleteMenu(menuId);
		} catch (e) {
			setMenus(snapshot);
			const err = toError(e);
			setError(err);
			throw err;
		}
	}, [menus]);
	return {
		menus,
		loading,
		error,
		reload,
		createMenu: useCallback(async (input) => {
			const row = await clientRef.current.createMenu(input);
			setMenus((prev) => [...prev, row]);
			return row;
		}, []),
		updateMenu: useCallback(async (menuId, patch) => {
			const row = await clientRef.current.updateMenu(menuId, patch);
			setMenus((prev) => prev.map((menu) => menu.id === menuId ? row : menu));
			return row;
		}, []),
		deleteMenu
	};
}
function useMenuTree(key, opts = {}) {
	const client = useMenuClient();
	const hasSeed = opts.initialData !== void 0;
	const [tree, setTree] = useState(opts.initialData ?? null);
	const [loading, setLoading] = useState(!hasSeed);
	const [error, setError] = useState(null);
	const [reloadToken, setReloadToken] = useState(0);
	const clientRef = useRef(client);
	clientRef.current = client;
	const menuKey = key;
	const prevMenuKey = useRef(menuKey);
	const reload = useCallback(() => {
		setReloadToken((t) => t + 1);
	}, []);
	useEffect(() => {
		const keyChanged = prevMenuKey.current !== menuKey;
		prevMenuKey.current = menuKey;
		if (hasSeed && reloadToken === 0 && !keyChanged) return;
		let cancelled = false;
		setLoading(true);
		setError(null);
		clientRef.current.getMenuTree(key).then((next) => {
			if (!cancelled) {
				setTree(next);
				setLoading(false);
			}
		}).catch((e) => {
			if (!cancelled) {
				setError(toError(e));
				setLoading(false);
			}
		});
		return () => {
			cancelled = true;
		};
	}, [
		menuKey,
		hasSeed,
		reloadToken
	]);
	const removeItem = useCallback(async (itemId) => {
		const snapshot = tree;
		setError(null);
		setTree((prev) => prev ? {
			...prev,
			items: removeNodeFromTree(prev.items, itemId)
		} : prev);
		try {
			await clientRef.current.removeItem(itemId);
		} catch (e) {
			setTree(snapshot);
			const err = toError(e);
			setError(err);
			throw err;
		}
	}, [tree]);
	return {
		tree,
		loading,
		error,
		reload,
		addItem: useCallback(async (menuId, input) => {
			const row = await clientRef.current.addItem(menuId, input);
			reload();
			return row;
		}, [reload]),
		updateItem: useCallback(async (itemId, patch) => {
			const row = await clientRef.current.updateItem(itemId, patch);
			reload();
			return row;
		}, [reload]),
		removeItem,
		moveItem: useCallback(async (itemId, input) => {
			const snapshot = tree;
			setError(null);
			setTree((prev) => prev ? {
				...prev,
				items: applyMoveToTree(prev.items, itemId, input.newParentId, input.newPosition)
			} : prev);
			try {
				return await clientRef.current.moveItem(itemId, input);
			} catch (e) {
				setTree(snapshot);
				const err = toError(e);
				setError(err);
				throw err;
			}
		}, [tree]),
		reorder: useCallback(async (menuId, parentId, orderedIds) => {
			const snapshot = tree;
			setError(null);
			setTree((prev) => prev ? {
				...prev,
				items: applyReorderToTree(prev.items, parentId, orderedIds)
			} : prev);
			try {
				await clientRef.current.reorder(menuId, parentId, orderedIds);
			} catch (e) {
				setTree(snapshot);
				const err = toError(e);
				setError(err);
				throw err;
			}
		}, [tree])
	};
}
//#endregion
//#region src/editor.tsx
function findNode(items, itemId) {
	for (const item of items) {
		if (item.id === itemId) return item;
		const found = findNode(item.children, itemId);
		if (found) return found;
	}
	return null;
}
function targetFromNode(item) {
	if (item.targetKind === "entity" && item.targetEntityType && item.targetEntityId) return `entity:${item.targetEntityType}:${item.targetEntityId}`;
	return "custom";
}
function parseTargetChoice(value, customUrl, availableTargets) {
	if (value === "custom") return {
		kind: "url",
		url: customUrl
	};
	if (value.startsWith("entity:")) {
		const [, entityType, entityId] = value.split(":");
		if (entityType && entityId) return {
			kind: "entity",
			entityType,
			entityId
		};
	}
	const match = availableTargets.find((t) => `entity:${t.entityType}:${t.entityId}` === value);
	if (match) return {
		kind: "entity",
		entityType: match.entityType,
		entityId: match.entityId
	};
	return {
		kind: "url",
		url: customUrl
	};
}
function MenuTreeItem({ item, siblings, index, availableTargets, actions, allItems }) {
	const [label, setLabel] = useState(item.label);
	const [targetChoice, setTargetChoice] = useState(targetFromNode(item));
	const [customUrl, setCustomUrl] = useState(item.url ?? "");
	const commitLabel = useCallback(() => {
		if (label !== item.label) actions.updateItem(item.id, { label }).catch(() => {});
	}, [
		actions,
		item.id,
		item.label,
		label
	]);
	const commitTarget = useCallback((nextChoice, nextUrl) => {
		actions.updateItem(item.id, { target: parseTargetChoice(nextChoice, nextUrl, availableTargets) }).catch(() => {});
	}, [
		actions,
		availableTargets,
		item.id
	]);
	const handleMoveUp = useCallback(() => {
		if (index === 0) return;
		const orderedIds = siblings.map((s) => s.id);
		[orderedIds[index - 1], orderedIds[index]] = [orderedIds[index], orderedIds[index - 1]];
		actions.reorder(actions.menuId, item.parentId, orderedIds).catch(() => {});
	}, [
		actions,
		index,
		item.parentId,
		siblings
	]);
	const handleMoveDown = useCallback(() => {
		if (index >= siblings.length - 1) return;
		const orderedIds = siblings.map((s) => s.id);
		[orderedIds[index], orderedIds[index + 1]] = [orderedIds[index + 1], orderedIds[index]];
		actions.reorder(actions.menuId, item.parentId, orderedIds).catch(() => {});
	}, [
		actions,
		index,
		item.parentId,
		siblings
	]);
	const handleIndent = useCallback(() => {
		if (index === 0) return;
		const prev = siblings[index - 1];
		actions.moveItem(item.id, {
			newParentId: prev.id,
			newPosition: prev.children.length
		}).catch(() => {});
	}, [
		actions,
		index,
		item.id,
		siblings
	]);
	const handleOutdent = useCallback(() => {
		if (item.parentId === null) return;
		const parent = findNode(allItems, item.parentId);
		if (!parent) return;
		actions.moveItem(item.id, {
			newParentId: parent.parentId,
			newPosition: parent.position + 1
		}).catch(() => {});
	}, [
		actions,
		allItems,
		item.id,
		item.parentId
	]);
	return /* @__PURE__ */ jsxs("li", {
		role: "treeitem",
		"aria-expanded": item.children.length > 0 ? true : void 0,
		className: "menus-editor__item",
		children: [
			/* @__PURE__ */ jsx("input", {
				type: "text",
				"aria-label": item.label,
				value: label,
				className: "menus-editor__label",
				onChange: (e) => setLabel(e.target.value),
				onBlur: commitLabel
			}),
			/* @__PURE__ */ jsxs("select", {
				"aria-label": `${item.label} target`,
				value: targetChoice,
				className: "menus-editor__target",
				onChange: (e) => {
					const next = e.target.value;
					setTargetChoice(next);
					commitTarget(next, customUrl);
				},
				children: [/* @__PURE__ */ jsx("option", {
					value: "custom",
					children: "Custom URL"
				}), availableTargets.map((target) => /* @__PURE__ */ jsx("option", {
					value: `entity:${target.entityType}:${target.entityId}`,
					children: target.label
				}, `${target.entityType}:${target.entityId}`))]
			}),
			/* @__PURE__ */ jsx("input", {
				type: "text",
				"aria-label": `${item.label} custom URL`,
				value: customUrl,
				className: "menus-editor__url",
				onChange: (e) => setCustomUrl(e.target.value),
				onBlur: () => commitTarget(targetChoice, customUrl)
			}),
			/* @__PURE__ */ jsx("button", {
				type: "button",
				"aria-label": `Move ${item.label} up`,
				"aria-disabled": index === 0,
				onClick: handleMoveUp,
				children: "Move up"
			}),
			/* @__PURE__ */ jsx("button", {
				type: "button",
				"aria-label": `Move ${item.label} down`,
				"aria-disabled": index === siblings.length - 1,
				onClick: handleMoveDown,
				children: "Move down"
			}),
			/* @__PURE__ */ jsx("button", {
				type: "button",
				"aria-label": `Indent ${item.label}`,
				"aria-disabled": index === 0,
				onClick: handleIndent,
				children: "Indent"
			}),
			/* @__PURE__ */ jsx("button", {
				type: "button",
				"aria-label": `Outdent ${item.label}`,
				"aria-disabled": item.parentId === null,
				onClick: handleOutdent,
				children: "Outdent"
			}),
			/* @__PURE__ */ jsx("button", {
				type: "button",
				"aria-label": `Remove ${item.label}`,
				onClick: () => void actions.removeItem(item.id).catch(() => {}),
				children: "Remove"
			}),
			item.children.length > 0 ? /* @__PURE__ */ jsx("ul", {
				role: "group",
				className: "menus-editor__children",
				children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx(MenuTreeItem, {
					item: child,
					siblings: item.children,
					index: childIndex,
					availableTargets,
					actions,
					allItems
				}, child.id))
			}) : null
		]
	});
}
function MenuTreeList({ items, availableTargets, actions, rootItems }) {
	return /* @__PURE__ */ jsx(Fragment, { children: items.map((item, index) => /* @__PURE__ */ jsx(MenuTreeItem, {
		item,
		siblings: items,
		index,
		availableTargets,
		actions,
		allItems: rootItems
	}, item.id)) });
}
function MenuEditor({ menuKey, availableTargets }) {
	const { tree, loading, error, addItem, updateItem, removeItem, moveItem, reorder } = useMenuTree(menuKey);
	const [newLabel, setNewLabel] = useState("");
	const [newTargetChoice, setNewTargetChoice] = useState("custom");
	const [newCustomUrl, setNewCustomUrl] = useState("");
	const actions = {
		menuId: tree?.menu.id ?? "",
		reorder,
		moveItem,
		removeItem,
		updateItem
	};
	const handleAddItem = useCallback((e) => {
		e.preventDefault();
		if (!tree || !newLabel.trim()) return;
		addItem(tree.menu.id, {
			label: newLabel.trim(),
			target: parseTargetChoice(newTargetChoice, newCustomUrl, availableTargets),
			parentId: null
		}).catch(() => {});
		setNewLabel("");
		setNewCustomUrl("");
		setNewTargetChoice("custom");
	}, [
		addItem,
		availableTargets,
		newCustomUrl,
		newLabel,
		newTargetChoice,
		tree
	]);
	if (loading) return /* @__PURE__ */ jsx("p", {
		className: "menus-editor__loading",
		children: "Loading menu…"
	});
	if (!tree) return /* @__PURE__ */ jsx("p", {
		className: "menus-editor__empty",
		children: "No menu."
	});
	return /* @__PURE__ */ jsxs("div", {
		className: "menus-editor",
		style: { containerType: "inline-size" },
		children: [
			error ? /* @__PURE__ */ jsx("div", {
				role: "alert",
				tabIndex: 0,
				className: "menus-editor__error",
				children: error.message
			}) : null,
			/* @__PURE__ */ jsx("ul", {
				role: "tree",
				className: "menus-editor__tree",
				children: /* @__PURE__ */ jsx(MenuTreeList, {
					items: tree.items,
					availableTargets,
					actions,
					rootItems: tree.items
				})
			}),
			/* @__PURE__ */ jsxs("form", {
				className: "menus-editor__add",
				onSubmit: handleAddItem,
				children: [
					/* @__PURE__ */ jsx("input", {
						type: "text",
						"aria-label": "New item label",
						value: newLabel,
						className: "menus-editor__new-label",
						onChange: (e) => setNewLabel(e.target.value)
					}),
					/* @__PURE__ */ jsxs("select", {
						"aria-label": "New item target",
						value: newTargetChoice,
						className: "menus-editor__new-target",
						onChange: (e) => setNewTargetChoice(e.target.value),
						children: [/* @__PURE__ */ jsx("option", {
							value: "custom",
							children: "Custom URL"
						}), availableTargets.map((target) => /* @__PURE__ */ jsx("option", {
							value: `entity:${target.entityType}:${target.entityId}`,
							children: target.label
						}, `new-${target.entityType}:${target.entityId}`))]
					}),
					/* @__PURE__ */ jsx("input", {
						type: "text",
						"aria-label": "New item custom URL",
						value: newCustomUrl,
						className: "menus-editor__new-url",
						onChange: (e) => setNewCustomUrl(e.target.value)
					}),
					/* @__PURE__ */ jsx("button", {
						type: "submit",
						className: "menus-editor__add-button",
						children: "Add menu item"
					})
				]
			})
		]
	});
}
//#endregion
export { MenuEditor, MenuProvider, useMenuClient, useMenuTree, useMenus };
