<script lang="ts">
import { tick } from "svelte";

interface Item {
	name: string;
	url: string;
	onClick?: () => void;
	preventDefault?: boolean;
}

export let items: Item[] = [];
export let title: string | undefined = undefined;
export let header = true;
export let center = false;

let open = false;
let toggleRef: HTMLSpanElement;
let itemRefs: HTMLAnchorElement[] = [];
const instanceId = `dropdown-${Math.random().toString(36).slice(2, 10)}`;
const toggleId = `${instanceId}-toggle`;
const menuId = `${instanceId}-menu`;

const handleClickOutside = (event: MouseEvent) => {
	if (!(event.target as HTMLElement).closest(".dropdown")) open = false;
};

const focusItem = async (index: number) => {
	if (!items.length) return;

	const wrapped = ((index % items.length) + items.length) % items.length;

	await tick();
	itemRefs[wrapped]?.focus();
};

const handleToggleKey = async (e: KeyboardEvent) => {
	if (e.key === "Enter" || e.key === " ") {
		e.preventDefault();
		open = !open;

		if (open) await focusItem(0);
	} else if (e.key === "ArrowDown") {
		e.preventDefault();

		if (!open) open = true;

		await focusItem(0);
	} else if (e.key === "ArrowUp") {
		e.preventDefault();

		if (!open) open = true;

		await focusItem(-1);
	} else if (e.key === "Escape" && open) {
		e.preventDefault();
		open = false;
	}
};

const handleItemKey = async (e: KeyboardEvent, index: number) => {
	if (e.key === "Escape") {
		e.preventDefault();
		open = false;
		toggleRef?.focus();
	} else if (e.key === "ArrowDown") {
		e.preventDefault();
		await focusItem(index + 1);
	} else if (e.key === "ArrowUp") {
		e.preventDefault();
		await focusItem(index - 1);
	} else if (e.key === "Home") {
		e.preventDefault();
		await focusItem(0);
	} else if (e.key === "End") {
		e.preventDefault();
		await focusItem(-1);
	}
};
</script>

<svelte:window onclick={handleClickOutside} />

<div
  class="dropdown"
  id="dropdown"
  style={`--dropdown-left: ${center ? '50%' : 'unset'}; --dropdown-transform: ${
    center ? 'translateX(-50%)' : 'unset'
  };`}
>
  <span
    bind:this={toggleRef}
    class={`${header ? 'header-item' : ''} dropdown-toggle`}
    id={toggleId}
    onclick={(e) => {
      e.preventDefault();

      open = !open;
    }}
    onkeydown={handleToggleKey}
    role="button"
    tabindex="0"
    aria-haspopup="menu"
    aria-expanded={open}
    aria-controls={menuId}
  >
    {#if title}
      {title}
    {:else}
      <slot name="title" />
    {/if}
  </span>

  <div
    class={`dropdown-content card card-small card-glass ${open ? 'dropdown-open' : ''}`}
    id={menuId}
    role="menu"
    aria-labelledby={toggleId}
  >
    {#each items as item, i}
      <a
        bind:this={itemRefs[i]}
        href={item.url}
        class="header-item"
        role="menuitem"
        onclick={(e) => {
          if (item.preventDefault) e.preventDefault();
          if (item.onClick) item.onClick();
          open = false;
        }}
        onkeydown={(e) => handleItemKey(e, i)}
      >
        {item.name}
      </a>
    {/each}
  </div>
</div>

<style lang="scss">
  a {
    color: var(--base06);
  }

  .header-item {
    margin: 0 0.5rem;
  }

  .header-item:hover {
    text-decoration: none;
  }

  .header-item:active {
    outline: none;
  }

  .dropdown {
    position: relative;
    display: inline-block;
  }

  .dropdown-content {
    display: block;
    position: absolute;
    min-width: max-content;
    padding: 0.5em 0;
    opacity: 0;
    transform: translateY(-20px);
    visibility: hidden;
    $delay: 0.25s;
    transition:
      opacity $delay ease,
      transform $delay ease,
      visibility 0s linear $delay;
    left: var(--dropdown-left);
    transform: var(--dropdown-transform);
    z-index: 1;
  }

  .dropdown-open {
    opacity: 1;
    transform: translateY(0);
    visibility: visible;
    transition-delay: 0s, 0s, 0s;
    left: var(--dropdown-left);
    transform: var(--dropdown-transform);
  }

  .dropdown:hover .dropdown-content {
    opacity: 1;
    transform: translateY(0);
    visibility: visible;
    transition-delay: 0s, 0s, 0s;
    left: var(--dropdown-left);
    transform: var(--dropdown-transform);
  }

  .dropdown-content a {
    padding: 0.5em 0.75em;
    text-decoration: none;
    display: block;
  }

  .dropdown-content a:focus-visible {
    outline: 2px solid var(--base0D);
    outline-offset: 2px;
  }

  .dropdown-content a:hover {
    border-radius: 8px;
    background-color: var(--base01);
  }
</style>
