<script lang="ts">
	import { onMount, tick, createEventDispatcher } from "svelte";
	import { fade } from "svelte/transition";
	import { debounce, portalToBody } from "../../utils/ViewUtils.js";

	export let classNames = "";
	export let anchorElement: HTMLElement;
	export let alignment: "start" | "center" | "end" | "auto" = "auto";
	export let placement: "top" | "bottom" | "auto" | "prefer-top" | "prefer-bottom" = "auto";
	export let waitForContent = false;
	export let size: "sm" | "md" = "md";
	export let invertedColors = false;
	export let touchOnly = false;

	let popoverElement: HTMLDivElement;

	/// sizes of the arrow and its padding, needed to position the popover position correctly
	const ARROW_PADDING = 24;
	const ARROW_SIZE = 10;

	/// to prevent the toast from being too close to the edge of the screen
	const HIT_ZONE_MARGIN = 80;

	const dispatch = createEventDispatcher<{ close: void }>();

	let computedAlignment = alignment === "auto" ? "center" : alignment;
	let computedPlacement = placement === "auto" ? "bottom" : placement;

	let left: number;
	let top: number;
	let width: number;
	let height: number;

	let popoverShift: number;
	let isTouchOnly = false;
	let isActive = true;

	function updatePlacement(anchorBbox: DOMRect, pageHeight: number) {
		if (pageHeight > 0) {
			if (placement === "auto") {
				/// check if the anchor is closer to the top or bottom of the page
				computedPlacement = anchorBbox.top > pageHeight / 2 ? "top" : "bottom";
			} else if (placement === "prefer-top") {
				/// check if the toast has enough space to be placed above the anchor
				const popoverHeight = popoverElement.getBoundingClientRect().height;
				computedPlacement = anchorBbox.top > popoverHeight + HIT_ZONE_MARGIN ? "top" : "bottom";
			} else if (placement === "prefer-bottom") {
				/// check if the toast has enough space to be placed below the anchor
				const popoverHeight = popoverElement.getBoundingClientRect().height;
				computedPlacement =
					anchorBbox.top + anchorBbox.height + popoverHeight + HIT_ZONE_MARGIN > pageHeight ? "top" : "bottom";
			}
		}
	}

	function updateAlignment(anchorBbox: DOMRect, pageWidth: number) {
		if (alignment === "auto" && pageWidth > 0) {
			const popoverWidth = popoverElement.getBoundingClientRect().width;
			if (anchorBbox.left + popoverWidth > pageWidth - HIT_ZONE_MARGIN) {
				computedAlignment = "end";
			} else {
				computedAlignment = "start";
			}
		}
	}

	async function updatePosition() {
		if (anchorElement && !waitForContent) {
			await tick();

			const bbox = anchorElement.getBoundingClientRect();
			updateAlignment(bbox, window.innerWidth);
			updatePlacement(bbox, window.innerHeight);

			left = bbox.left + window.scrollX;
			top = bbox.top + window.scrollY;
			width = bbox.width;
			height = bbox.height;

			/// shift the popover so the arrow is exaclty at the middle of the anchor
			popoverShift = width / 2 - ARROW_SIZE / 2 - ARROW_PADDING;
		}
	}

	const debouncedShow = debounce(() => (isActive = true), 250);

	function hide() {
		if (!popoverElement?.matches(":hover")) {
			isActive = false;
		}
	}
	const debouncedHide = debounce(hide, 250);

	onMount(() => {
		isTouchOnly = touchOnly && window.matchMedia("(any-hover: none)").matches;

		if (!isTouchOnly) {
			updatePosition();
			if (anchorElement) {
				anchorElement.addEventListener("mouseover", debouncedShow);
				anchorElement.addEventListener("mouseleave", debouncedHide);
				return () => {
					anchorElement.removeEventListener("mouseover", debouncedShow);
					anchorElement.removeEventListener("mouseleave", debouncedHide);
				};
			}
		}
	});
</script>

<svelte:window on:resize={() => dispatch("close")} on:scroll={() => dispatch("close")} />

<div class={isTouchOnly ? "hidden sm:contents" : "contents"} use:portalToBody>
	<div
		class="pointer-events-none absolute bg-transparent hidden"
		class:hidden={!isActive}
		style:top="{top}px"
		style:left="{left}px"
		style:width="{width}px"
		style:height="{height}px"
	>
		<div
			bind:this={popoverElement}
			in:fade={{ duration: 100 }}
			on:mouseleave={debouncedHide}
			class="pointer-events-auto absolute z-10 transform
				{computedPlacement === 'top' ? 'bottom-full -translate-y-3' : 'top-full translate-y-2.5'}
				{computedAlignment === 'start' ? 'left-0' : computedAlignment === 'end' ? 'right-0' : 'left-1/2 -translate-x-1/2'}
				{classNames}"
		>
			<div
				class="absolute z-0 rotate-45 transform
					{size === 'sm' ? 'h-2 w-2' : 'h-2.5 w-2.5 rounded-sm'}
					{invertedColors ? 'bg-black dark:bg-gray-800' : 'border bg-white shadow  dark:bg-gray-800'}
					{computedPlacement === 'top' ? 'top-full -translate-y-1' : 'bottom-full translate-y-1'}
					{computedAlignment === 'start' ? 'left-6' : computedAlignment === 'center' ? 'left-1/2' : 'right-6'}"
			/>
			<div
				class="shadow-alternate-xl relative z-5 border font-normal leading-tight transition-opacity
					{size === 'sm' ? 'rounded px-2 py-1.5' : 'rounded-xl p-4'}
					{invertedColors ? 'border-black bg-black text-white dark:bg-gray-800' : 'bg-white text-black dark:bg-gray-925'}
				"
			>
				<slot />
			</div>
		</div>
	</div>
</div>