import { useEffect, useMemo, useRef, useState } from "@wordpress/element";
import { decodeEntities } from "@wordpress/html-entities";
import { Button, Card, CardBody, Notice, Spinner, __experimentalConfirmDialog as ConfirmDialog, __experimentalHeading as Heading } from "@wordpress/components";
import MenusHeader from "./MenusHeader";
import CreateMenuModal from "./CreateMenuModal";
import AddItemsPanel from "./AddItemsPanel";
import MenuTree from "./MenuTree";
import MetaSettingsModal from "./MetaSettingsModal";
import { buildTree, flattenWithOrder } from "./tree";
import { ajaxPostJetMenu, buildControlData, getCfg, normalizeContentTypeValue, prepareNavItemSettingsForSave } from "./MetaModalHelpers";
import {
	setupApiFetch,
	createMenu,
	deleteMenu,
	createMenuItem,
	deleteMenuItem,
	getMenuItems,
	getMenus,
	updateMenuItem,
} from "./api";

function mapMenuItem(it) {
	return {
		id: it.id,
		parent: it.parent || 0,
		menu_order: it.menu_order || 0,
		title: decodeEntities(it?.title?.rendered ?? ""),
		type: it.type,
		object: it.object,
		object_id: it.object_id || 0,
		url: it.url || "",
		meta: it.meta || {},
	};
}

export default function App() {
	const [menus, setMenus] = useState([]);
	const [menuId, setMenuId] = useState(0);
	const [items, setItems] = useState([]);
	const [selectedId, setSelectedId] = useState(null);

	const [busy, setBusy] = useState(false);
	const [loadingMenus, setLoadingMenus] = useState(true);
	const [loadingItems, setLoadingItems] = useState(false);
	const [createOpen, setCreateOpen] = useState(false);

	const [settingsOpen, setSettingsOpen] = useState(false);
	const [editorVisible, setEditorVisible] = useState(false);
	const [currentEditorUrl, setCurrentEditorUrl] = useState("");
	const [savingMeta, setSavingMeta] = useState(false);

	const [orderSaving, setOrderSaving] = useState(false);
	const orderSaveChainRef = useRef(Promise.resolve());
	const [settingsLoading, setSettingsLoading] = useState(false);

	const [ deleteConfirmOpen, setDeleteConfirmOpen ] = useState( false );
	const [ deletePendingId, setDeletePendingId ] = useState( null );

	const [ deleteMenuConfirmOpen, setDeleteMenuConfirmOpen ] = useState( false );
	const [ deleteMenuPendingId, setDeleteMenuPendingId ] = useState( null );

	const selectedItem = useMemo(
		() => items.find((i) => String(i.id) === String(selectedId)) || null,
		[items, selectedId]
	);

	// unset selected ID on click outside of the selected item
	useEffect(() => {
		if (!selectedId || settingsOpen) {
			return;
		}

		const onClick = (e) => {
			if (!e.target.closest(".jet-menu-tree__item.is-selected")) {
				setSelectedId(null);
			}
		};

		document.addEventListener("click", onClick);

		return () => {
			document.removeEventListener("click", onClick);
		};
	}, [selectedId, settingsOpen]);

	const [controlData, setControlData] = useState(null);
	const setControlValue = (key, value) => {
		setControlData((prev) => ({
			...(prev || {}),
			[key]: { ...((prev && prev[key]) || {}), value },
		}));
	};

	const reloadMenus = async () => {
		setLoadingMenus(true);
		try {
			const list = await getMenus();
			setMenus(list || []);
			if (!menuId && list?.[0]?.id) setMenuId(Number(list[0].id));
		} finally {
			setLoadingMenus(false);
		}
	};

	const reloadItems = async (mid) => {
		if (!mid) return;
		setLoadingItems(true);
		try {
			const list = await getMenuItems(mid);
			setItems((list || []).map(mapMenuItem));
			setSelectedId(null);
		} finally {
			setLoadingItems(false);
		}
	};

	useEffect(() => {
		setupApiFetch();
		reloadMenus();
	}, []);

	useEffect(() => {
		if (menuId) reloadItems(menuId);
	}, [menuId]);

	const onCreateNewMenu = async ({ name, description }) => {
		const created = await createMenu({ name, description });
		await reloadMenus();
		setMenuId(Number(created.id));
	};

	const onDeleteMenu = () => {
		if (!menuId) return;

		setDeleteMenuPendingId(menuId);
		setDeleteMenuConfirmOpen(true);
	};

	const cancelDeleteMenu = () => {
		setDeleteMenuConfirmOpen(false);
		setDeleteMenuPendingId(null);
	};

	const confirmDeleteMenu = async () => {
		if (!deleteMenuPendingId) return;

		setDeleteMenuConfirmOpen(false);
		setBusy(true);

		try {
			await deleteMenu(deleteMenuPendingId);

			const list = await getMenus();
			setMenus(list || []);

			const nextId = list?.[0]?.id ? Number(list[0].id) : 0;
			setMenuId(nextId);

			if (!nextId) {
				setItems([]);
				setSelectedId(null);
			}
		} catch (e) {
			alert(e?.message || "Failed to delete menu");
		} finally {
			setBusy(false);
			setDeleteMenuPendingId(null);
		}
	};

	const onAddItems = async (added) => {
		if (!menuId) return;
		setBusy(true);
		try {
			for (const a of added) {
				const payload =
					a.type === "custom"
						? {
							menus: menuId,
							parent: Number(a.parent || 0),
							title: a.title || a.url,
							type: "custom",
							url: a.url,
						}
						: a.type === "taxonomy"
							? {
								menus: menuId,
								parent: Number(a.parent || 0),
								title: a.title,
								type: "taxonomy",
								object: a.object,
								object_id: a.object_id,
							}
							: {
								menus: menuId,
								parent: Number(a.parent || 0),
								title: a.title,
								type: "post_type",
								object: a.object,
								object_id: a.object_id,
							};

				const created = await createMenuItem(payload);
				setItems((prev) => [mapMenuItem(created), ...prev]);
				setSelectedId(created.id);
			}
		} catch (e) {
			alert(e?.message || "Failed to add items");
		} finally {
			setBusy(false);
		}
	};

	const onPatchItem = (id, patch) => {
		setItems((prev) =>
			prev.map((x) => (String(x.id) === String(id) ? { ...x, ...patch } : x))
		);
	};

	const onSaveItem = async (id) => {
		const it = items.find((x) => String(x.id) === String(id));
		if (!it || !menuId) return;

		setBusy(true);
		try {
			const payload =
				it.type === "custom"
					? {
						menus: menuId,
						title: it.title,
						type: "custom",
						url: it.url,
						parent: it.parent,
						menu_order: it.menu_order,
					}
					: { menus: menuId, title: it.title, parent: it.parent, menu_order: it.menu_order };

			await updateMenuItem(it.id, payload);
			await reloadItems(menuId);
			setSelectedId(it.id);
		} catch (e) {
			alert(e?.message || "Failed to save item");
		} finally {
			setBusy(false);
		}
	};

	const confirmDeleteItem = async () => {
		if (!deletePendingId) return;

		setDeleteConfirmOpen(false);
		setBusy(true);

		try {
			await deleteMenuItem(deletePendingId);
			await reloadItems(menuId);
		} catch (e) {
			alert(e?.message || "Failed to delete");
		} finally {
			setBusy(false);
			setDeletePendingId(null);
		}
	};

	const cancelDeleteItem = () => {
		setDeleteConfirmOpen(false);
		setDeletePendingId(null);
	};

	const onDeleteItem = (id) => {
		const it = items.find((x) => String(x.id) === String(id));
		if (!it) return;

		setDeletePendingId(it.id);
		setDeleteConfirmOpen(true);
	};

	const loadItemSettings = async ( menuItemId, depth ) => {
		setSettingsLoading(true);

		try {
			const response = await ajaxPostJetMenu({
				action: "jet_get_nav_item_settings",
				data: {
					itemId: menuItemId,
					itemDepth: depth,
				},
			});

			if (response?.success && response?.data?.settings) {
				setControlData(buildControlData(response.data.settings));
			} else {
				setControlData(buildControlData({}));
			}
		} finally {
			setSettingsLoading(false);
		}
	};

	const onOpenSettingsModal = async (id) => {
		setSelectedId(id);
		setSettingsOpen(true);
		setEditorVisible(false);
		setControlData(null);

		const depth = getItemDepthById(id, items);
		await loadItemSettings(id, depth);
	};

	const persistMenuOrder = async (nextItems) => {
		if (!menuId) return;

		const ordered = flattenWithOrder(buildTree(nextItems));

		setOrderSaving(true);
		try {
			for (let i = 0; i < ordered.length; i++) {
				const row = ordered[i];

				await updateMenuItem(row.id, {
					menus: menuId,
					parent: Number(row.parent || 0),
					menu_order: Number(row.menu_order || (i + 1)),
				});
			}
		} finally {
			setOrderSaving(false);
		}
	};

	const onReorder = ({ dragId, dropId, mode }) => {
		setItems((prev) => {
			const dragKey = String(dragId);
			const dropKey = String(dropId);

			const prevTree = buildTree(prev);
			const prevFlat = flattenWithOrder(prevTree); // [{id,parent,menu_order} in current visible order]
			const ids = prevFlat.map((x) => String(x.id));

			const fromIndex = ids.indexOf(dragKey);
			const toIndex = ids.indexOf(dropKey);
			if (fromIndex === -1 || toIndex === -1 || fromIndex === toIndex) return prev;

			const next = prev.map((x) => ({ ...x }));
			const dragItem = next.find((x) => String(x.id) === dragKey);
			const dropItem = next.find((x) => String(x.id) === dropKey);
			if (!dragItem || !dropItem) return prev;

			// Remove drag id from current order
			ids.splice(fromIndex, 1);

			if (mode === "child") {
				dragItem.parent = Number(dropItem.id);

				// Insert drag right after drop item (as first child area approximation)
				const dropPos = ids.indexOf(dropKey);
				ids.splice(dropPos + 1, 0, dragKey);
			} else {
				dragItem.parent = Number(dropItem.parent || 0);

				const dropPos = ids.indexOf(dropKey);
				const insertAt = mode === "before" ? dropPos : dropPos + 1;
				ids.splice(insertAt, 0, dragKey);
			}

			// Renumber menu_order for a stable buildTree sort
			const orderMap = new Map(ids.map((id, idx) => [id, idx + 1]));
			for (const it of next) {
				const k = String(it.id);
				if (orderMap.has(k)) it.menu_order = orderMap.get(k);
			}

			// Persist in background (no reloadItems here to avoid flicker)
			orderSaveChainRef.current = orderSaveChainRef.current
				.then(() => persistMenuOrder(next))
				.catch((e) => alert(e?.message || "Failed to save order"));

			return next;
		});
	};

	const buildEditorUrl = (idForEdit) => {
		const cfg = getCfg();
		const it = items.find((x) => String(x.id) === String(idForEdit));
		if (!it) return "";

		const contentType = normalizeContentTypeValue(
			controlData?.content_type?.value || "default"
		);

		const adminUrl = cfg.adminUrl || (window.location.origin + "/wp-admin/");
		const base = new URL(adminUrl);

		base.pathname = base.pathname.replace(/\/?$/, "/") + "themes.php";
		base.searchParams.set("page", "jet-menus");

		base.searchParams.set("jet-open-editor", "1");
		base.searchParams.set("item", String(it.id));
		base.searchParams.set("menu", String(menuId));
		base.searchParams.set("content", String(contentType));
		base.searchParams.set("_nonce", cfg.editorNonce || "");

		return base.toString();
	};

	const editorUrl = useMemo(() => {

		if ( ! settingsOpen || ! selectedId ) return "";

		return buildEditorUrl( selectedId );

	}, [settingsOpen, selectedId, menuId, controlData?.content_type?.value]);

	const openEditor = () => {
		if (!selectedId || !menuId) return;
		const url = buildEditorUrl(selectedId);
		if (!url) return;
		setCurrentEditorUrl(url);
		setEditorVisible(true);
	};

	const openEditorInNewTab = () => {
		if ( ! editorUrl ) return;

		window.open( editorUrl, "_blank", "noopener,noreferrer" );
	};

	function getItemDepthById(id, items) {
		const map = new Map(items.map((it) => [String(it.id), it]));
		let depth = 0;
		let cur = map.get(String(id));

		while (cur && cur.parent && Number(cur.parent) !== 0) {
			depth++;
			cur = map.get(String(cur.parent));
			if (depth > 50) break;
		}

		return depth;
	}

	const saveItemSettings = async () => {
		if (!selectedId || !controlData) return;

		setSavingMeta(true);
		try {
			const settings = prepareNavItemSettingsForSave( controlData );

			const depth = getItemDepthById(selectedId, items);

			const response = await ajaxPostJetMenu({
				action: "jet_save_nav_item_settings",
				data: {
					itemId: selectedId,
					itemDepth: depth,
					itemSettings: settings,
				},
			});

			if (!response?.success) {
				throw new Error(response?.data?.message || "Failed to save item settings");
			}

			await loadItemSettings(selectedId, depth);

		} catch (e) {
			alert(e?.message || "Failed to save item settings");
		} finally {
			setSavingMeta(false);
		}
	};

	return (
		<div className="jet-menu-nav-settings-root">
			{loadingMenus ? (
				<Spinner />
			) : (
				<MenusHeader
					menus={menus}
					menuId={menuId}
					onMenuChange={setMenuId}
					onCreateClick={() => setCreateOpen(true)}
					onReloadClick={reloadMenus}
					onDeleteClick={onDeleteMenu}
					busy={busy}
				/>
			)}

			<CreateMenuModal
				isOpen={createOpen}
				onRequestClose={() => setCreateOpen(false)}
				onCreate={onCreateNewMenu}
			/>

			<MetaSettingsModal
				isOpen={settingsOpen}
				onClose={() => {
					setSettingsOpen(false);
					setEditorVisible(false);
				}}
				editorVisible={editorVisible}
				currentEditorUrl={currentEditorUrl}
				loading={settingsLoading}
				saving={savingMeta}
				controlData={controlData}
				setControlValue={setControlValue}
				onSave={saveItemSettings}
				onOpenEditor={openEditor}
				onOpenEditorInNewTab={openEditorInNewTab}
				editorUrl={editorUrl}
			/>

			<ConfirmDialog
				isOpen={ deleteConfirmOpen }
				onConfirm={ confirmDeleteItem }
				onCancel={ cancelDeleteItem }
				confirmButtonText="Delete"
				cancelButtonText="Cancel"
			>
				Delete this menu item?
			</ConfirmDialog>

			<ConfirmDialog
				isOpen={ deleteMenuConfirmOpen }
				onConfirm={ confirmDeleteMenu }
				onCancel={ cancelDeleteMenu }
				confirmButtonText="Delete"
				cancelButtonText="Cancel"
			>
				Delete this menu?
			</ConfirmDialog>


			<div className="jet-menu-nav-settings">
				<div className="jet-menu-nav-settings__col">
					<AddItemsPanel menuId={menuId} onAddItems={onAddItems} />
				</div>

				<div className="jet-menu-nav-settings__col">
					<Card className="jet-menu-card">
						<CardBody className="jet-menu-card__body">
							<div className="jet-menu-card__head">
								<Heading className="jet-menu-card__title" level={4}>
									Menu structure
									{orderSaving ? <span style={{fontSize: 12, opacity: 0.7}}>Saving…</span> : null}
								</Heading>

								<Button
									variant="secondary"
									disabled={!menuId || busy}
									onClick={() => onAddItems([{type: "custom", title: "New item", url: "https://"}])}
								>
									+ New item
								</Button>
							</div>

							<div className="jet-menu-card__content">
								{!menuId ? (
									<Notice status="warning" isDismissible={false}>
										Select or create a menu.
									</Notice>
								) : loadingItems ? (
									<Spinner/>
								) : items.length === 0 ? (
									<Notice status="info" isDismissible={false}>
										No menu items yet. Add some on the left.
									</Notice>
								) : (
									<MenuTree
										items={items}
										selectedId={selectedId}
										onSelect={setSelectedId}
										onAddChild={(parent) =>
											onAddItems([{type: "custom", title: "Child item", url: "https://", parent}])
										}
										onReorder={onReorder}
										onPatchItem={onPatchItem}
										onSaveItem={onSaveItem}
										onDeleteItem={onDeleteItem}
										onOpenSettingsModal={onOpenSettingsModal}
										busy={busy}
									/>
								)}
							</div>
						</CardBody>
					</Card>
				</div>
			</div>
		</div>
	);
}