@use "config" as *;
@use "functions" as *;
@use "mixins/border-radius" as *;
@use "mixins/transition" as *;
@use "mixins/mask-icon" as *;
@use "mixins/tokens" as *;

$carousel-tokens: () !default;

// stylelint-disable custom-property-no-missing-var-function
// scss-docs-start carousel-tokens
// stylelint-disable-next-line scss/dollar-variable-default
$carousel-tokens: defaults(
  (
    --carousel-gap: .75rem,
    --carousel-indicator-bg: var(--fg-3),
    --carousel-indicator-width: .75rem,
    --carousel-indicator-height: .75rem,
    --carousel-indicator-spacer: .25rem,
    --carousel-indicator-opacity-duration: .6s,
    --carousel-indicator-opacity-timing: ease,
    --carousel-indicator-width-duration: .3s,
    --carousel-indicator-width-timing: ease,
    --carousel-indicator-progress-bg: var(--carousel-indicator-bg),
    --carousel-control-icon-width: 1rem,
    --carousel-control-prev-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0'/></svg>"),
    --carousel-control-next-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708'/></svg>"),
    --carousel-control-pause-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='M5.5 3.5A1.5 1.5 0 0 1 7 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5m5 0A1.5 1.5 0 0 1 12 5v6a1.5 1.5 0 0 1-3 0V5a1.5 1.5 0 0 1 1.5-1.5'/></svg>"),
    --carousel-control-play-icon: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path d='m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393z'/></svg>"),
    // Scroll-snap engine. `gap` must carry a length unit: it feeds the
    // `.carousel-item` flex-basis `calc()`, and subtracting a unitless `0` from a
    // percentage is invalid CSS (it would drop the whole declaration and collapse
    // every slide to its content width). `peek` only feeds `padding-inline`/
    // `scroll-padding-inline`, so a bare `0` would be valid there, but we keep it
    // unit-bearing for consistency.
    --carousel-items: 1,
    --carousel-items-gap: 0px,
    --carousel-items-peek: 0px,
    --carousel-fade-duration: .6s,
    --carousel-fade-timing: ease,
  ),
  $carousel-tokens
);
// scss-docs-end carousel-tokens
// stylelint-enable custom-property-no-missing-var-function

@layer components {
  .carousel {
    @include tokens($carousel-tokens);

    position: relative;
    display: flex;
    flex-direction: column;
    gap: var(--carousel-gap);
  }

  // The scroll viewport
  .carousel-inner {
    display: flex;
    gap: var(--carousel-items-gap);
    width: 100%;
    padding-inline: var(--carousel-items-peek);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--carousel-items-peek);
    scrollbar-width: none; // Hide the scrollbar without losing scrollability

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

  // Smooth programmatic/keyboard scrolling, disabled under reduced-motion
  @media (prefers-reduced-motion: no-preference) {
    .carousel-inner {
      scroll-behavior: smooth;
    }
  }

  .carousel-item {
    // `100%` here is `.carousel-inner`'s content box, which `padding-inline`
    // has already inset by the peek on each side, so the peek must NOT be
    // subtracted again — doing so makes every slide `2 * peek` too narrow and
    // the peek lopsided. Only the inter-slide gaps need removing.
    flex: 0 0 calc((100% - (var(--carousel-items) - 1) * var(--carousel-items-gap)) / var(--carousel-items));
    min-width: 0;
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }

  //
  // Layout variants
  //

  // Center the active slide in the viewport (pairs well with `--carousel-items-peek`)
  .carousel-center {
    .carousel-item {
      scroll-snap-align: center;
    }
  }

  // Let each slide size itself; snap points still land on every item
  .carousel-auto {
    .carousel-item {
      flex-basis: auto;
    }
  }

  //
  // Alternate transitions
  //

  // Fade can't ride scroll-snap (it stacks slides instead of scrolling), so it
  // becomes a JavaScript-driven mode: every slide is stacked and the active one
  // is faded in via a CSS opacity transition.
  .carousel-fade {
    .carousel-inner {
      display: grid;
      overflow: hidden;
      scroll-snap-type: none;
    }

    .carousel-item {
      grid-area: 1 / 1;
      width: 100%;
      visibility: hidden;
      opacity: 0;
      @include transition(opacity var(--carousel-fade-duration) var(--carousel-fade-timing), visibility 0s linear var(--carousel-fade-duration));
    }

    .carousel-item.active {
      visibility: visible;
      opacity: 1;
      @include transition(opacity var(--carousel-fade-duration) var(--carousel-fade-timing));
    }
  }

  // Icons for within, rendered via CSS mask so they inherit the current text
  // color (white on the overlay controls, the button color inside `.btn-*`).
  .carousel-icon-prev,
  .carousel-icon-next,
  .carousel-icon-pause,
  .carousel-icon-play {
    display: inline-block;
    width: var(--carousel-control-icon-width);
    height: var(--carousel-control-icon-width);
    background-color: currentcolor;
    @include mask-icon($size: 100% 100%, $position: 50%);
  }

  .carousel-icon-prev {
    mask-image: var(--carousel-control-prev-icon);
  }

  .carousel-icon-next {
    mask-image: var(--carousel-control-next-icon);
  }

  [dir="rtl"] .carousel-icon-prev,
  [dir="rtl"] .carousel-icon-next {
    transform: scaleX(-1);
  }

  .carousel-icon-pause {
    mask-image: var(--carousel-control-pause-icon);
  }

  .carousel-icon-play {
    mask-image: var(--carousel-control-play-icon);
  }

  // Optional play/pause control
  //
  // A discoverable toggle so users can stop an autoplaying carousel, as required
  // by WCAG 2.2.2 (Pause, Stop, Hide). `.carousel-control-play-pause` is only a
  // behavior hook—JS toggles `.paused` on it and its appearance comes from the
  // wrapping button (e.g. `.btn-icon`). The button holds both glyphs and we show
  // whichever `.carousel-icon-*` matches the current state.
  .carousel-control-play-pause .carousel-icon-play {
    display: none;
  }

  .carousel-control-play-pause.paused {
    .carousel-icon-pause {
      display: none;
    }

    .carousel-icon-play {
      display: inline-block;
    }
  }

  .carousel-indicators {
    display: flex;
    gap: var(--carousel-indicator-spacer);
    justify-content: center;

    [data-bs-target] {
      flex: 0 1 auto;
      width: var(--carousel-indicator-width);
      height: var(--carousel-indicator-height);
      padding: 0;
      cursor: pointer;
      background-color: transparent;
      border: 1px solid var(--carousel-indicator-bg);
      @include border-radius(var(--carousel-indicator-width));
      @include transition(opacity var(--carousel-indicator-opacity-duration) var(--carousel-indicator-opacity-timing), width var(--carousel-indicator-width-duration) var(--carousel-indicator-width-timing));
    }

    .active {
      width: calc(var(--carousel-indicator-width) * 2.5);
      background-color: var(--carousel-indicator-bg);
      border-color: var(--carousel-indicator-bg);
    }
  }

  // Autoplay progress: fill the active indicator like a progress bar over the
  // current slide's interval. The JS adds `.carousel-playing` and sets
  // `--carousel-interval` (shipped as `--bs-carousel-interval`) while autoplay is
  // running. The fill restarts on its own each slide because `.active` moves to a
  // fresh indicator, so its `::after` animation begins from scratch.
  @if $enable-transitions {
    @keyframes carousel-indicator-progress {
      from { inline-size: 0; }
      to { inline-size: 100%; }
    }

    .carousel-playing .carousel-indicators .active {
      @media (prefers-reduced-motion: no-preference) {
        position: relative;
        overflow: hidden;
        // Empty the pill so it reads as a track that the fill grows across.
        background-color: transparent;

        &::after {
          position: absolute;
          inset-block: 0;
          inset-inline-start: 0;
          inline-size: 0;
          content: "";
          background-color: var(--carousel-indicator-progress-bg);
          animation: carousel-indicator-progress var(--carousel-interval, 5000ms) linear forwards;
        }
      }
    }
  }

  // Overlay layout
  //
  // Overlays the prev/next controls, play/pause button, and indicators on top of
  // the slides (the classic carousel look) instead of stacking them in the flow.

  .carousel-overlay {
    --carousel-indicator-bg: light-dark(var(--white), var(--black));

    .carousel-overlay-controls {
      position: absolute;
      inset-block-end: 1rem;
      inset-inline: 1rem;
      z-index: 1;
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
  }
}
