<script>
  const gallery = document.querySelector('.gallery');
  const track = gallery.querySelector('.track');
  const images = gallery.querySelectorAll('.track > *');
  const nav = gallery.querySelector('.gallery-nav');
  const prevButton = gallery.querySelector('button.prev');
  const nextButton = gallery.querySelector('button.next');

  const TOLERANCE = 2;

  const debounce = (fn, delay = 300) => {
    let timer = 0;
    const debounced = (...args) => {
      if (!args) args = [];
      clearTimeout(timer);

      timer = setTimeout(() => {
        fn.apply(fn, args);
      }, delay);
    };

    return debounced;
  };

  const isAtStart = () => Math.floor(track.scrollLeft) <= TOLERANCE;
  const isAtEnd = () => {
    const { scrollLeft, offsetWidth, scrollWidth } = track;
    return Math.abs(scrollLeft + offsetWidth - scrollWidth) <= TOLERANCE;
  };

  const updateAriaCurrent = (element, index) => {
    currentIndex === index
      ? element.setAttribute('aria-current', 'true')
      : element.removeAttribute('aria-current');
  };

  const updateActiveStates = (index) => {
    images.forEach(updateAriaCurrent);
    nav.querySelectorAll('.indicator-btn').forEach(updateAriaCurrent);
    prevButton.disabled = isAtStart();
    nextButton.disabled = isAtEnd();
  };

  let currentIndex = 0;

  const scrollToIndex = (index) => {
    if (index < 0 || index >= images.length || index === currentIndex) return;

    currentIndex = index;
    images[index].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });

    updateActiveStates(index);
  };

  const prev = () => scrollToIndex(currentIndex - 1);
  const next = () => scrollToIndex(currentIndex + 1);

  const resizeObserver = new ResizeObserver(() => debounce(scrollToIndex(currentIndex)));
  resizeObserver.observe(track);

  const galleryObserver = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && entry.intersectionRatio >= 0.75) {
          const index = [...images].indexOf(entry.target);
          if (index !== -1) {
            currentIndex = index;
            updateActiveStates(currentIndex);
          }
        }
      });
    },
    { root: track, threshold: 0.75, rootMargin: '0px' }
  );

  const initializeGallery = () => {
    images.forEach((el, i) => {
      const btn = document.createElement('button');

      if (i === 0) btn.setAttribute('aria-current', 'true');
      btn.classList.add('indicator-btn');
      btn.dataset.index = i;
      btn.innerText = i + 1;
      btn.setAttribute('aria-label', `Go to image ${i + 1} of ${images.length}`);
      btn.addEventListener('click', () => scrollToIndex(Number(btn.dataset.index)));

      if (i === 0) el.setAttribute('aria-current', 'true');
      el.setAttribute('aria-label', `Image ${i + 1} of ${images.length}`);
      el.setAttribute('tabindex', '0');
      el.setAttribute('role', 'group');

      nav.appendChild(btn);
    });

    updateActiveStates(0);
  };

  images.forEach((el) => galleryObserver.observe(el));
  prevButton.addEventListener('click', prev);
  nextButton.addEventListener('click', next);
  track.addEventListener('scroll', () => debounce(updateActiveStates(currentIndex)));
  document.addEventListener('DOMContentLoaded', () => initializeGallery());
</script>

<section class="gallery" aria-label="Image gallery">
  <button class="prev" aria-label="Previous Image">
    <icon icon="chevron-left" webc:nokeep></icon>
  </button>
  <button class="next" aria-label="Next Image">
    <icon icon="chevron-right" webc:nokeep></icon>
  </button>

  <div class="track">
    <slot></slot>
  </div>

  <nav class="gallery-nav" aria-label="Gallery"></nav>
</section>

<style>
  .gallery {
    display: grid;
    grid-auto-columns: auto 1fr auto;
    grid-template-areas:
      'prev track next'
      '. progress .';
    align-items: center;
    gap: 1em;

    & .prev,
    & .next,
    & .indicator-btn {
      position: relative;

      font-size: 1.25em;
      color: var(--clr-text);
      cursor: pointer;

      background: linear-gradient(
        to bottom right,
        var(--clr-box-gradient-start) 0%,
        var(--clr-box-gradient-end) 50%
      );

      box-shadow: 0.125em 0.125em 0.5em var(--clr-box-shadow);

      border: none;
      border-radius: 100%;
      padding: 0.875em;

      z-index: 1;

      &:active::after {
        background: linear-gradient(
          to top left,
          var(--clr-quick-info-bg-start) 0%,
          var(--clr-quick-info-bg-end) 50%
        );
      }

      &::after {
        content: '';
        position: absolute;
        inset: var(--border-thin);
        background: linear-gradient(
          to bottom right,
          var(--clr-quick-info-bg-start) 0%,
          var(--clr-quick-info-bg-end) 50%
        );
        border-radius: inherit;
        z-index: -1;
      }
    }

    & .prev,
    & .next {
      grid-row: track;

      &:disabled {
        cursor: not-allowed;
        opacity: 0.5;
      }

      & svg {
        position: absolute;
        inset: 0;
        padding: 0.375em;
      }
    }

    & .prev {
      grid-column: prev;
      left: 0.5em;
    }

    & .next {
      grid-column: next;
      right: 0.5em;
    }

    & .track {
      grid-row: track;
      grid-column: prev / next;
      display: grid;
      grid-auto-flow: column;
      grid-auto-columns: 100%;
      gap: 2em;

      border-radius: 1em;

      overflow-x: auto;
      scroll-snap-type: x mandatory;
      scroll-behavior: smooth;
      scrollbar-width: none;

      & > * {
        scroll-snap-align: center;
      }

      &::-webkit-scrollbar {
        display: none;
      }
    }

    & .ref-image {
      & :first-child {
        grid-row: image / caption;
      }

      & img {
        width: 100%;
        height: 80vw;
        object-fit: cover;
      }

      & .caption {
        font-size: 0.875em;
        text-wrap: balance;

        background: oklch(from var(--clr-box-background) l c h / 0.75);

        padding-block: 0.5em;
        padding-inline: 1em;
      }
    }

    & .gallery-nav {
      --indicator-size: 2.5em;

      grid-row: progress;
      grid-column: prev / next;

      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(min(1em, 100%), var(--indicator-size)));
      gap: 0.5em;
      place-content: center;
      place-items: center;
      font-size: 0.875em;
    }

    & .indicator-btn {
      font-size: 1em;
      width: var(--indicator-size);
      height: var(--indicator-size);
      padding: 0;
      aspect-ratio: 1 / 1;
    }

    & .indicator-btn[aria-current='true']::after {
      background: linear-gradient(
        to top left,
        var(--clr-quick-info-bg-start) 0%,
        var(--clr-quick-info-bg-end) 50%
      );
    }
  }
</style>
