<svelte:options customElement="ui-modal" />

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

  export let title = '';

  let rootEl: HTMLElement;
  let panelEl: HTMLElement;
  let hostEl: HTMLElement;

  function isOpen(): boolean {
    return !!hostEl?.hasAttribute('open');
  }

  function closeModal() {
    hostEl?.removeAttribute('open');
    hostEl?.dispatchEvent(
      new CustomEvent('ui:close', { bubbles: true, composed: true })
    );
  }

  function onKeyDown(event: KeyboardEvent) {
    if (!isOpen()) return;

    if (event.key === 'Escape') {
      event.preventDefault();
      closeModal();
      return;
    }

    if (event.key === 'Tab') {
      trapFocus(event);
    }
  }

  function trapFocus(event: KeyboardEvent) {
    const focusables = panelEl?.querySelectorAll<HTMLElement>(
      'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])'
    );
    if (!focusables || focusables.length === 0) {
      event.preventDefault();
      panelEl?.focus();
      return;
    }

    const first = focusables[0];
    const last = focusables[focusables.length - 1];
    const active = document.activeElement as HTMLElement | null;

    if (event.shiftKey && active === first) {
      event.preventDefault();
      last.focus();
      return;
    }

    if (!event.shiftKey && active === last) {
      event.preventDefault();
      first.focus();
    }
  }

  function onBackdropClick(event: MouseEvent) {
    if (event.target === event.currentTarget) {
      closeModal();
    }
  }

  onMount(() => {
    hostEl = (rootEl.getRootNode() as ShadowRoot).host as HTMLElement;

    const observer = new MutationObserver(() => {
      if (isOpen()) {
        setTimeout(() => {
          const autofocus = panelEl?.querySelector<HTMLElement>('[autofocus]');
          (autofocus || panelEl)?.focus();
        }, 0);
      }
    });

    observer.observe(hostEl, { attributes: true, attributeFilter: ['open'] });

    if (isOpen()) {
      setTimeout(() => {
        (panelEl as HTMLElement | undefined)?.focus();
      }, 0);
    }

    return () => observer.disconnect();
  });
</script>

<div class="overlay" on:click={onBackdropClick} on:keydown={onKeyDown} role="presentation" bind:this={rootEl}>
  <div class="panel" role="dialog" aria-modal="true" tabindex="-1" bind:this={panelEl}>
    <header class="header">
      <h3>{title}</h3>
      <button type="button" class="close" on:click={closeModal} aria-label="Close">×</button>
    </header>
    <section class="body">
      <slot />
    </section>
  </div>
</div>

<style>
  :host {
    display: block;
  }

  .overlay {
    position: fixed;
    inset: 0;
    background: var(--ui-overlay);
    display: none;
    place-items: center;
    z-index: 1000;
  }

  :host([open]) .overlay {
    display: grid;
  }

  .panel {
    width: min(640px, calc(100vw - 32px));
    max-height: calc(100vh - 48px);
    overflow: auto;
    background: var(--ui-panel);
    border-radius: var(--ui-radius);
    box-shadow: var(--ui-shadow);
    outline: none;
  }

  .header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 16px;
    border-bottom: 1px solid var(--ui-border);
  }

  h3 {
    margin: 0;
    font-size: 1rem;
  }

  .close {
    border: 0;
    background: transparent;
    font-size: 1.4rem;
    cursor: pointer;
  }

  .body {
    padding: 12px 16px 16px;
  }
</style>
