<script lang="ts">
  import { onMount } from 'svelte';
  import { fly, fade } from 'svelte/transition';
  import { flip } from 'svelte/animate';
  import type { CommandPaletteAction } from './actions';

  export let items: CommandPaletteAction[] = [];
  export let open = false;

  let search = '';
  let filtered: (CommandPaletteAction & { id?: string })[] = [];
  let selectedIndex = -1;
  let inputRef: HTMLInputElement;
  let isVisible = false;
  let timeoutID: ReturnType<typeof setTimeout> | null = null;
  let itemIDs = new Map<string, number>();

  $: {
    items.forEach((item, index) => {
      if (!itemIDs.has(item.url)) itemIDs.set(item.url, index);
    });

    const doesActionMatch = (action: CommandPaletteAction) => {
      const doesActionIncludePattern = (query: string, action: string) => {
        const normalise = (input: string) => input.toLowerCase().replace(/\s+/g, '');

        return normalise(query).includes(normalise(action));
      };

      return (
        doesActionIncludePattern(action.name, search) ||
        action.tags?.some((tag) => doesActionIncludePattern(tag, search))
      );
    };

    filtered = [];

    items.forEach((action, idx) => {
      const actionMatches = doesActionMatch(action);
      let matchedParent = false;

      if (actionMatches) {
        filtered.push({ ...action, id: `action-${idx}` });

        matchedParent = true;
      }

      if (action.actions)
        action.actions.forEach((nestedAction, nestedIdx) => {
          if (doesActionMatch(nestedAction))
            filtered.push({
              ...nestedAction,
              id: `action-${idx}-nested-${nestedIdx}`,
              name: `${matchedParent ? '↳' : `${action.name} >`} ${nestedAction.name}`
            });
        });
    });

    filtered = filtered.slice(0, 10);
  }

  $: if (selectedIndex >= filtered.length) selectedIndex = filtered.length - 1;
  $: if (selectedIndex < 0 && filtered.length > 0) selectedIndex = 0;

  $: if (open && !isVisible) {
    isVisible = true;

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

      timeoutID = null;
    }
  } else if (!open && isVisible) {
    if (timeoutID === null) {
      timeoutID = setTimeout(() => {
        isVisible = false;
        timeoutID = null;
      }, 200);
    }
  }

  const executeItem = (item: CommandPaletteAction) => {
    if (item.onClick) item.onClick();
    if (!item.preventDefault) window.location.href = item.url;

    open = false;
  };

  const handleKey = (e: KeyboardEvent) => {
    if (e.key === 'ArrowDown') {
      e.preventDefault();

      selectedIndex = (selectedIndex + 1) % filtered.length;
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();

      selectedIndex = (selectedIndex - 1 + filtered.length) % filtered.length;
    } else if (e.key === 'Enter') {
      if (filtered.length === 1) {
        executeItem(filtered[0]);
      } else if (filtered.length > 1 && selectedIndex >= 0) {
        executeItem(filtered[selectedIndex]);
      }
    } else if (e.key === 'Escape') {
      open = false;
    }
  };

  onMount(() => {
    window.addEventListener('keydown', handleGlobalKey);

    return () => {
      window.removeEventListener('keydown', handleGlobalKey);

      if (timeoutID !== null) clearTimeout(timeoutID);
    };
  });

  const handleClickOutside = (event: MouseEvent) => {
    const target = event.target as HTMLElement;

    if (target.classList.contains('command-palette-overlay') || !target.closest('.dropdown'))
      open = false;
  };

  const handleGlobalKey = (e: KeyboardEvent) => {
    if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
      e.preventDefault();

      open = !open;

      if (open) requestAnimationFrame(() => inputRef?.focus());
    }
  };
</script>

<svelte:window on:click={handleClickOutside} />

{#if isVisible}
  <div
    class="command-palette-overlay {open ? 'fade-in' : 'fade-out'}"
    on:click={() => (open = false)}
  ></div>

  <div class="dropdown {open ? 'fade-in' : 'fade-out'}">
    <div class="dropdown-content card card-small">
      <input
        bind:this={inputRef}
        bind:value={search}
        class="command-input"
        placeholder="Search"
        on:keydown={handleKey}
      />

      <div class="results-container">
        {#each filtered as item, i (item.id || item.url)}
          <a
            href={item.url}
            class="header-item {selectedIndex === i ? 'selected' : ''}"
            in:fly={{ y: 20, duration: 150, delay: i * 30 }}
            out:fly={{ y: -20, duration: 150 }}
            animate:flip={{ duration: 200 }}
            on:click={(e) => {
              if (item.preventDefault) e.preventDefault();
              if (item.onClick) item.onClick();

              open = false;
            }}
          >
            {item.name}
          </a>
        {/each}

        {#if filtered.length === 0 && search !== ''}
          <div class="no-results opaque" in:fade={{ duration: 150 }}>No results found</div>
        {/if}
      </div>
    </div>
  </div>
{/if}

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

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

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

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

  .no-results {
    padding: 1em;
    text-align: center;
  }

  .results-container {
    max-height: 60vh;
    overflow-y: auto;
    overflow-x: hidden;
    scrollbar-width: none; /* Firefox */
    -ms-overflow-style: none; /* IE/Edge */
  }

  .results-container::-webkit-scrollbar {
    display: none; /* WebKit */
  }

  .command-palette-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.6);
    z-index: 100;
    backdrop-filter: blur(3px);
    cursor: pointer;
    opacity: 0;
  }

  .command-palette-overlay.fade-in {
    animation: overlayFadeIn 0.15s ease-in-out forwards;
  }

  .command-palette-overlay.fade-out {
    animation: overlayFadeOut 0.2s ease-in-out forwards;
  }

  @keyframes overlayFadeIn {
    from {
      opacity: 0;
      backdrop-filter: blur(0px);
    }

    to {
      opacity: 1;
      backdrop-filter: blur(3px);
    }
  }

  @keyframes overlayFadeOut {
    from {
      opacity: 1;
      backdrop-filter: blur(3px);
    }

    to {
      opacity: 0;
      backdrop-filter: blur(0px);
    }
  }

  .dropdown {
    position: fixed;
    top: 15%;
    left: 50%;
    z-index: 101;
    width: min(600px, 90%);
    font-size: 1.05em;
    opacity: 0;
    transform: translateX(-50%) translateY(-10px);
  }

  .dropdown.fade-in {
    animation: dropdownFadeIn 0.2s ease-in-out forwards;
  }

  .dropdown.fade-out {
    animation: dropdownFadeOut 0.2s ease-in-out forwards;
  }

  @keyframes dropdownFadeIn {
    from {
      opacity: 0;
      transform: translateX(-50%) translateY(-10px);
    }

    to {
      opacity: 1;
      transform: translateX(-50%) translateY(0);
    }
  }

  @keyframes dropdownFadeOut {
    from {
      opacity: 1;
      transform: translateX(-50%) translateY(0);
    }

    to {
      opacity: 0;
      transform: translateX(-50%) translateY(-10px);
    }
  }

  .command-input {
    width: calc(100% - 2rem);
    padding: 0.5em 0.75em;
    margin: 0.5rem 1rem;
    margin-bottom: 0.75rem;
    outline: none;
  }

  .dropdown-content {
    display: block;
    position: relative;
    min-width: max-content;
    padding: 0.5em 0;
  }

  .dropdown-content a {
    padding: 0.5em 0.75em;
    text-decoration: none;
    display: block;
    border-radius: 8px;
    margin: 0.5em 0.75em;
    backdrop-filter: blur(0px);
    font-weight: 450;
    transform-origin: center left;
  }

  .dropdown-content a:hover,
  .dropdown-content a.selected {
    background-color: var(--base01);
  }
</style>
