<!-- @component `PhotoCarousel` [Read the docs.](https://reuters-graphics.github.io/graphics-components/?path=/docs/components-multimedia-photocarousel--docs) -->
<script lang="ts">
  // Utils
  import '@splidejs/svelte-splide/css/core';
  import { fly } from 'svelte/transition';
  import { Splide, SplideSlide, SplideTrack } from '@splidejs/svelte-splide';

  // Icons
  import Fa from 'svelte-fa/src/fa.svelte';
  import {
    faChevronLeft,
    faChevronRight,
  } from '@fortawesome/free-solid-svg-icons';

  // Components
  import Block from '../Block/Block.svelte';
  import PaddingReset from '../PaddingReset/PaddingReset.svelte';

  // Types
  import type { MoveEventDetail } from '@splidejs/svelte-splide/types';
  import type { Snippet } from 'svelte';
  import type { PhotoCarouselImage } from '../@types/global';

  type ContainerWidth = 'normal' | 'wide' | 'wider' | 'widest' | 'fluid';

  type ObjectFit = 'cover' | 'contain';

  interface Props {
    /** Array of photos. */
    photos: PhotoCarouselImage[];
    /** Width of the component within the text well: normal, wide, wider, widest, fluid */
    width?: ContainerWidth;
    /**
     * Set a different width for captions within the text well, e.g. "normal," to keep captions inline with the rest of the text well. Can't ever be wider than `width`.
     */
    textWidth?: ContainerWidth;
    /** Add an ID to target with SCSS. */
    id?: string;
    /** Add a class to target with SCSS. */
    cls?: string;
    /** Max height of the carousel */
    maxHeight?: number;
    /** Default Image object-fit style: cover, contain */
    defaultImageObjectFit?: ObjectFit;
    /** Default image object-position style, e.g., `center center` or `50% 50%`. */
    defaultImageObjectPosition?: string;
    /** ARIA label for the carousel. */
    carouselAriaLabel?: string;
    /** Set height of the carousel as a ratio of its width as long as its below whatever you set in `maxHeight`. */
    heightRatio?: number;
    /** Number of images to preload ahead of the active image. */
    preloadImages?: number;
    /** Optional custom credit format as a snippet, which takes the argument `photo`. */
    credit?: Snippet<[PhotoCarouselImage]>;
    /** Optional custom caption format as a snippet, which takes the argument `photo`. */
    caption?: Snippet<[PhotoCarouselImage]>;
  }

  let {
    width = 'wider',
    textWidth = 'normal',
    id = '',
    cls = '',
    photos,
    maxHeight = 660,
    defaultImageObjectFit = 'cover',
    defaultImageObjectPosition = 'center center',
    carouselAriaLabel = 'Photo gallery',
    heightRatio = 0.68,
    preloadImages = 1,
    credit,
    caption,
  }: Props = $props();

  let containerWidth: number | undefined = $state(undefined);
  let activeImageIndex = $state(0);

  // Derive carousel height based on container width
  let carouselHeight = $derived(
    containerWidth ?
      Math.min(containerWidth * heightRatio, maxHeight)
    : maxHeight
  );

  const handleActiveChange = (e?: CustomEvent<MoveEventDetail>) => {
    if (!e) return;
    activeImageIndex = e.detail.dest;
  };
</script>

<Block {width} {id} class="photo-carousel fmy-6 {cls}">
  <div class="carousel-container" bind:clientWidth={containerWidth}>
    <Splide
      hasTrack={false}
      options={{
        height: carouselHeight,
        fixedHeight: carouselHeight,
        lazyLoad: 'nearby',
        preloadPages: preloadImages,
      }}
      aria-label={carouselAriaLabel}
      on:move={handleActiveChange}
    >
      <div class="image-container">
        <SplideTrack>
          {#each photos as photo}
            <SplideSlide>
              <div class="photo-slide w-full h-full relative">
                <figure
                  class="fm-0 w-full relative"
                  style="height: {carouselHeight}px;"
                >
                  <img
                    class="w-full h-full fmy-0"
                    data-splide-lazy={photo.src}
                    alt={photo.altText}
                    style:object-fit={photo.objectFit || defaultImageObjectFit}
                    style:object-position={photo.objectPosition ||
                      defaultImageObjectPosition}
                  />
                  {#if credit}
                    <!-- Render custom credit snippet -->
                    {@render credit(photo)}
                  {:else if photo.credit}
                    <span
                      class="credit absolute fmb-1 fml-1 leading-tighter font-note text-xxs"
                      class:contain-fit={photo.objectFit === 'contain' ||
                        defaultImageObjectFit === 'contain'}
                      >{photo.credit}</span
                    >
                  {/if}
                </figure>
              </div>
            </SplideSlide>
          {/each}
        </SplideTrack>

        {#if photos[activeImageIndex].caption}
          {@const activePhoto = photos[activeImageIndex]}
          <PaddingReset containerIsFluid={width === 'fluid'}>
            <Block width={textWidth}>
              {#if caption && activePhoto.caption}
                {#key activeImageIndex}
                  {@render caption(activePhoto)}
                {/key}
              {:else if typeof activePhoto.caption}
                {#key activeImageIndex}
                  <p
                    class="caption body-caption text-center"
                    in:fly|local={{ x: 20, duration: 175 }}
                  >
                    {activePhoto.caption}
                  </p>
                {/key}
              {/if}
            </Block>
          </PaddingReset>
        {/if}

        <div class="splide__progress">
          <div class="splide__progress__bar"></div>
        </div>

        <div class="splide__arrows fp-1">
          <button class="splide__arrow splide__arrow--prev">
            <Fa icon={faChevronLeft} fw />
          </button>
          <button class="splide__arrow splide__arrow--next">
            <Fa icon={faChevronRight} fw />
          </button>
        </div>
      </div>
    </Splide>
  </div>
</Block>

<style lang="scss">
  @use '../../scss/mixins' as mixins;

  .carousel-container {
    .photo-slide {
      figure {
        span.credit {
          bottom: 0;
          left: 0;
          color: white;
          text-shadow:
            -1px -1px 0 #333,
            1px -1px 0 #333,
            -1px 1px 0 #333,
            1px 1px 0 #333;
          &.contain-fit {
            left: 50%;
            transform: translate(-50%, 0%);
          }
        }
      }
    }
    :global(.splide) {
      max-height: 100%;
    }
    :global(li) {
      padding: 0;
    }
    :global(.splide__arrows) {
      width: 275px;
      margin: 0 auto;
      display: flex;
      justify-content: space-between;
    }
    :global(.splide__arrows button) {
      display: flex;
      font-size: 14px;
      height: 30px;
      width: 30px;
      justify-content: center;
      align-items: center;
      border: 1px solid transparent;
      border-radius: 50%;
      background-color: transparent;
      cursor: pointer;
      @include mixins.text-secondary;
      opacity: 0.4;
    }
    :global(.splide__arrows button.splide__arrow--prev) {
      padding-right: 7px;
    }
    :global(.splide__arrows button.splide__arrow--next) {
      padding-left: 7px;
    }
    :global(.splide__arrows button:hover) {
      opacity: 1;
      border-color: mixins.$theme-colour-text-secondary;
      @include mixins.text-secondary;
    }
    :global(.splide__arrows button:disabled) {
      opacity: 0.4;
    }
    :global(.splide__arrows button:disabled:hover) {
      border-color: transparent;
    }
    :global(ul.splide__pagination) {
      width: 200px;
      padding: 0;
      text-align: center;
      margin: -26px auto 0;
      display: flex;
      flex-wrap: nowrap;
    }
    :global(ul.splide__pagination li) {
      flex-grow: 1;
      margin-top: -9px;
    }
    :global(ul.splide__pagination li button) {
      width: 100%;
      height: 7px;
      border-radius: 0;
      padding: 0;
      border: 1px solid mixins.$theme-colour-background;
      background: mixins.$theme-colour-text-secondary;
      opacity: 0.4;
    }
    :global(ul.splide__pagination li button.is-active) {
      opacity: 1;
    }
  }
</style>
