<script lang="ts">
import tooltipPosition from "$stores/tooltipPosition";
import { fade } from "svelte/transition";
import { onDestroy } from "svelte";

export let id: string | undefined = undefined;
export let pin: string | undefined = undefined;
export let content: string;
export let disable = false;
export let pinPosition: "top" | "bottom" | "left" | "right" = "top";
export let offset = 10;
export let tooltipTransitionTime = 200;
export let tooltipHideDelay = 10;
export let debounceDelay = 100;
export let tooltipOpacityTransitionTime = 200;
export let relative = false;
export let ignoreAnchorStyling = false;

let tooltipDiv: HTMLDivElement | null = null;
let hideTimeout: number | null = null;
let debounceTimer: number | null = null;
let opacity = 0;

onDestroy(() => {
	if (hideTimeout !== null) clearTimeout(hideTimeout);

	if (debounceTimer !== null) clearTimeout(debounceTimer);

	if (tooltipDiv && tooltipDiv.parentNode === document.body) {
		document.body.removeChild(tooltipDiv);
		tooltipDiv = null;
	}
});

const createTooltip = () => {
	if (!tooltipDiv) {
		tooltipDiv = document.createElement("div");
		tooltipDiv.style.position = "absolute";
		tooltipDiv.style.left = "-9999px";
		tooltipDiv.style.top = "-9999px";
		tooltipDiv.style.visibility = "hidden";
		tooltipDiv.style.zIndex = "1000";
		opacity = 0;
		tooltipDiv.style.pointerEvents = "none";
		tooltipDiv.style.whiteSpace = "nowrap";

		tooltipDiv.classList.add("card");
		tooltipDiv.classList.add("card-small");
		document.body.appendChild(tooltipDiv);
	}
};

const updateTooltipPosition = (x: number, y: number) => {
	if (tooltipDiv) {
		if (pin) {
			const pinnedElement = document.getElementById(pin);

			if (pinnedElement) {
				const rectangle = pinnedElement.getBoundingClientRect();
				const parentRectangle =
					pinnedElement.offsetParent?.getBoundingClientRect();
				const tooltipWidth = tooltipDiv.offsetWidth;
				const tooltipHeight = tooltipDiv.offsetHeight;
				let top = 0;
				let left = 0;

				switch (pinPosition) {
					case "top":
						if (relative && parentRectangle) {
							top =
								rectangle.top - tooltipHeight - offset - parentRectangle.top;
							left =
								rectangle.left +
								rectangle.width / 2 -
								tooltipWidth / 2 -
								parentRectangle.left;
						} else {
							top = rectangle.top - tooltipHeight - offset;
							left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
						}

						break;
					case "bottom":
						if (relative && parentRectangle) {
							top =
								rectangle.top + rectangle.height + offset - parentRectangle.top;
							left =
								rectangle.left +
								rectangle.width / 2 -
								tooltipWidth / 2 -
								parentRectangle.left;
						} else {
							top = rectangle.top + rectangle.height + offset;
							left = rectangle.left + rectangle.width / 2 - tooltipWidth / 2;
						}

						break;
					case "left":
						if (relative && parentRectangle) {
							top =
								rectangle.top +
								rectangle.height / 2 -
								tooltipHeight / 2 -
								parentRectangle.top;
							left =
								rectangle.left - tooltipWidth - offset - parentRectangle.left;
						} else {
							top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
							left = rectangle.left - tooltipWidth - offset;
						}

						break;
					case "right":
						if (relative && parentRectangle) {
							top =
								rectangle.top +
								rectangle.height / 2 -
								tooltipHeight / 2 -
								parentRectangle.top;
							left =
								rectangle.left +
								rectangle.width +
								offset -
								parentRectangle.left;
						} else {
							top = rectangle.top + rectangle.height / 2 - tooltipHeight / 2;
							left = rectangle.left + rectangle.width + offset;
						}

						break;
				}

				if (relative && parentRectangle) {
					if (left + parentRectangle.left < 0)
						left = offset - parentRectangle.left;
					if (left + tooltipWidth + parentRectangle.left > window.innerWidth)
						left =
							window.innerWidth - tooltipWidth - offset - parentRectangle.left;
					if (top + parentRectangle.top < 0)
						top =
							rectangle.top + rectangle.height + offset - parentRectangle.top;
					if (top + tooltipHeight + parentRectangle.top > window.innerHeight)
						top =
							window.innerHeight - tooltipHeight - offset - parentRectangle.top;
				} else {
					if (left < 0) left = offset;
					if (left + tooltipWidth > window.innerWidth)
						left = window.innerWidth - tooltipWidth - offset;
					if (top < 0) top = rectangle.top + rectangle.height + offset;
					if (top + tooltipHeight > window.innerHeight)
						top = window.innerHeight - tooltipHeight - offset;
				}

				tooltipPosition.set({
					x: left,
					y: top + (relative ? 0 : window.scrollY),
				});

				return;
			}
		}

		const tooltipWidth = tooltipDiv.offsetWidth;
		const tooltipHeight = tooltipDiv.offsetHeight;
		let top = y - tooltipHeight - offset;
		let left = x - tooltipWidth / 2;

		if (left < 0) left = offset;
		if (left + tooltipWidth > window.innerWidth)
			left = window.innerWidth - tooltipWidth - offset;
		if (top < 0) top = y + offset;

		tooltipPosition.set({
			x: left,
			y: top,
		});
	}
};

const showTooltip = (content: string, x: number, y: number) => {
	if (hideTimeout !== null) {
		clearTimeout(hideTimeout);

		hideTimeout = null;
	}

	createTooltip();

	if (tooltipDiv) {
		tooltipDiv.innerHTML = content.replace(/\n/g, "<br>");
		tooltipDiv.style.opacity = "0";

		updateTooltipPosition(x, y);
		setTimeout(() => {
			if (tooltipDiv) {
				opacity = 1;
			}
		}, 10);
	}
};

const hideTooltip = () => {
	setTimeout(() => {
		if (tooltipDiv) {
			opacity = 0;

			hideTimeout = window.setTimeout(() => {
				if (tooltipDiv) {
					document.body.removeChild(tooltipDiv);

					tooltipDiv = null;
				}
			}, tooltipTransitionTime);
		}
	}, tooltipHideDelay);
};

const handleMouseEnter = (event: MouseEvent) => {
	if (disable) return;

	if (hideTimeout !== null) {
		clearTimeout(hideTimeout);

		hideTimeout = null;
	}

	if (!tooltipDiv) showTooltip(content, event.pageX, event.pageY);
};

const handleMouseMove = (event: MouseEvent) => {
	if (debounceTimer !== null) clearTimeout(debounceTimer);

	debounceTimer = window.setTimeout(() => {
		if (tooltipDiv && opacity === 1)
			updateTooltipPosition(event.pageX, event.pageY);
	}, debounceDelay);
};

const handleMouseLeave = () => {
	hideTooltip();
};
</script>

<span
  {id}
  onmouseenter={handleMouseEnter}
  onmousemove={handleMouseMove}
  onmouseleave={handleMouseLeave}
  role="tooltip"
>
  <slot />
</span>

{#if tooltipDiv}
  <div
    class="tooltip card card-small"
    style={`left: ${$tooltipPosition.x}px; top: ${$tooltipPosition.y}px; opacity: ${opacity}; --tooltip-opacity-transition-time: ${tooltipOpacityTransitionTime}ms;`}
  >
    {#key content}
      <span
        in:fade={{ duration: tooltipTransitionTime }}
        out:fade={{ duration: tooltipTransitionTime }}
        class:ignore-anchor-styling={ignoreAnchorStyling}
      >
        {@html content.replace(/\n/g, '<br>')}
      </span>
    {/key}
  </div>
{/if}

<style>
  .tooltip {
    position: absolute;
    z-index: 1000;
    transition: opacity var(--tooltip-opacity-transition-time) ease-in-out;
    pointer-events: none;
    white-space: nowrap;
  }

  /* fix line break slot */

  .ignore-anchor-styling {
    color: var(--fg);
  }
</style>
