<script lang="ts">
	import { onMount } from "svelte";
	import { isTauri } from "./app.svelte.js";

	type Item = {
		label?: string;
		action?: () => void;
		items?: Item[];
		disabled?: boolean;
		type?: "separator";
	};

	let { x, y, items, onclose } = $props<{
		x: number;
		y: number;
		items: Item[];
		onclose: () => void;
	}>();

	let active = $state<number | null>(null),
		subActive = $state<number | null>(null),
		width = $state(0),
		height = $state(0),
		element = $state<HTMLElement>();

	async function showTauriMenu() {
		try {
			const { Menu, MenuItem, Submenu, PredefinedMenuItem } =
				await import("@tauri-apps/api/menu");

			const createItems = async (items: Item[]): Promise<any[]> => {
				return Promise.all(
					items.map(async (item) => {
						if (item.type === "separator") {
							return await PredefinedMenuItem.new({ item: "Separator" });
						}
						if (item.items) {
							return await Submenu.new({
								text: item.label || "",
								items: await createItems(item.items),
							});
						}
						return await MenuItem.new({
							text: item.label || "",
							enabled: !item.disabled,
							action: () => {
								item.action?.();
								onclose();
							},
						});
					}),
				);
			};

			const menu = await Menu.new({
				items: await createItems(items),
			});

			await menu.popup();
		} catch (err) {
			console.error("failed to show tauri menu:", err);
		} finally {
			onclose();
		}
	}

	onMount(() => {
		if (isTauri) {
			showTauriMenu();
			return;
		}

		const prevFocus = document.activeElement as HTMLElement | null;
		element?.focus();
		return () => prevFocus?.focus();
	});

	const exec = (action?: () => void) => {
		if (action) {
			action();
			onclose();
		}
	};

	const left = $derived(
		x + width > window.innerWidth ? Math.max(0, x - width) : x,
	);
	const top = $derived(
		y + height > window.innerHeight ? Math.max(0, y - height) : y,
	);

	function onKey(event: KeyboardEvent) {
		event.stopPropagation();
		const key = event.key;
		if (key === "Escape") {
			onclose();
		} else if (key === "ArrowDown") {
			event.preventDefault();
			if (subActive !== null && active !== null && items[active].items) {
				const subItems = items[active].items!;
				let next = (subActive + 1) % subItems.length;
				while (subItems[next]?.type === "separator")
					next = (next + 1) % subItems.length;
				subActive = next;
			} else {
				let next = active === null ? 0 : (active + 1) % items.length;
				while (items[next]?.type === "separator")
					next = (next + 1) % items.length;
				active = next;
				subActive = null;
			}
		} else if (key === "ArrowUp") {
			event.preventDefault();
			if (subActive !== null && active !== null && items[active].items) {
				const subItems = items[active].items!;
				let next = (subActive - 1 + subItems.length) % subItems.length;
				while (subItems[next]?.type === "separator")
					next = (next - 1 + subItems.length) % subItems.length;
				subActive = next;
			} else {
				let next =
					active === null
						? items.length - 1
						: (active - 1 + items.length) % items.length;
				while (items[next]?.type === "separator")
					next = (next - 1 + items.length) % items.length;
				active = next;
				subActive = null;
			}
		} else if (key === "ArrowRight") {
			if (active !== null && items[active].items) {
				event.preventDefault();
				subActive = 0;
			}
		} else if (key === "ArrowLeft") {
			if (subActive !== null) {
				event.preventDefault();
				subActive = null;
			}
		} else if (key === "Enter") {
			event.preventDefault();
			if (active !== null) {
				const item = items[active];
				if (item.items) {
					if (subActive === null) subActive = 0;
					else exec(item.items[subActive].action);
				} else exec(item.action);
			}
		}
	}
</script>

{#snippet menuButton(item: Item, isActive: boolean, isSub: boolean)}
	<button
		class:active={isActive}
		disabled={item.disabled}
		onmouseenter={() => {
			if (isSub) {
				if (active !== null && items[active].items) {
					subActive = items[active].items.indexOf(item);
				}
			} else {
				active = items.indexOf(item);
				subActive = null;
			}
		}}
		onclick={(event) => {
			event.stopPropagation();
			if (item.items) {
				active = items.indexOf(item);
				subActive = null;
			} else {
				exec(item.action);
			}
		}}
	>
		{item.label}
		{#if item.items}<span class="arrow">▶</span>{/if}
	</button>
{/snippet}

{#if !isTauri}
	<!-- svelte-ignore a11y_no_static_element_interactions -->
	<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
	<!-- svelte-ignore a11y_click_events_have_key_events -->
	<div
		class="overlay"
		onclick={onclose}
		oncontextmenu={(event) => {
			event.preventDefault();
			onclose();
		}}
	>
		<div
			bind:this={element}
			class="menu"
			style="top: {top}px; left: {left}px;"
			onclick={(event) => event.stopPropagation()}
			onkeydown={onKey}
			bind:clientWidth={width}
			bind:clientHeight={height}
			tabindex="0"
		>
			{#each items as item, index}
				{#if item.type === "separator"}
					<div class="separator"></div>
				{:else}
					<div
						class="row"
						onmouseleave={() => {
							if (subActive === null) active = null;
						}}
					>
						{@render menuButton(
							item,
							active === index && subActive === null,
							false,
						)}
						{#if item.items && active === index}
							<div
								class="submenu"
								class:flip-x={left + width + 160 > window.innerWidth}
								class:flip-y={top + index * 32 + item.items.length * 32 >
									window.innerHeight}
							>
								{#each item.items as sub, subIndex}
									{#if sub.type === "separator"}
										<div class="separator"></div>
									{:else}
										{@render menuButton(sub, subActive === subIndex, true)}
									{/if}
								{/each}
							</div>
						{/if}
					</div>
				{/if}
			{/each}
		</div>
	</div>
{/if}

<style>
	.overlay {
		position: fixed;
		inset: 0;
		z-index: 1000;
	}
	.menu,
	.submenu {
		position: absolute;
		background: Canvas;
		border: 1px solid var(--border);
		box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
		min-inline-size: 160px;
		display: flex;
		flex-direction: column;
	}
	.menu {
		color: CanvasText;
		outline: none;
	}
	.row {
		position: relative;
	}
	button {
		background: none;
		border: none;
		text-align: left;
		padding: 0.375rem 0.75rem;
		inline-size: 100%;
		display: flex;
		justify-content: space-between;
		align-items: center;
		white-space: nowrap;
		gap: 1rem;
		color: inherit;
	}
	button:hover,
	button.active {
		background: Highlight;
		color: HighlightText;
	}
	button:disabled {
		opacity: 0.5;
	}
	.separator {
		border-block-start: 1px solid var(--border-subtle);
	}
	.arrow {
		font-size: 0.7em;
		opacity: 0.5;
	}
	.submenu {
		inset-inline-start: 100%;
		inset-block-start: 0;
	}
	.submenu.flip-x {
		inset-inline-start: auto;
		inset-inline-end: 100%;
	}
	.submenu.flip-y {
		inset-block-start: auto;
		inset-block-end: 0;
	}
</style>
