// Support for swiper v8
(function () {
  function BreakdanceSwiper() {
    const { mergeObjects, matchMedia, getCurrentBreakpoint, is, isBuilder } = BreakdanceFrontend.utils;

    function isElementInDom(selector) {
      // An element can be "hidden" (aka not in the DOM) with Hide on breakpoint -> in builder preview
      // and Swiper needs the element to be part of the DOM to work on it
      // This code was removed in https://github.com/soflyy/breakdance-elements/pull/2184/files#diff-897333d33555b463d7a30b1328e025df240eeb851ed79e85b643c65c6c6b4bffL12
      // but that introduced the following bug: https://github.com/soflyy/breakdance/issues/5956
      // so we put this code back, since the element might be SSRing.
      const isElementInDom = document.querySelector(selector);
      return !!isElementInDom;
    }

    function setBreakpoint(swiper, settings) {
      const { BASE_BREAKPOINT_ID } = window.BreakdanceFrontend.data;
      const isLoop = settings.infinite === "enabled" && !isBuilder();
      const slidesPerView = settings.advanced.slides_per_view;
      const slidesPerGroup = settings.advanced.slides_per_group;
      const spaceBetween = settings.advanced.between_slides;

      const onePerViewAt = settings.advanced.one_per_view_at;
      const onePerViewAtDesktop = onePerViewAt === BASE_BREAKPOINT_ID;
      const alwaysOne = settings.effect === "fade" || settings.effect === "flip" || onePerViewAtDesktop;

      const { activeIndex, initialized, loopedSlides = 0 } = swiper;
      const needsReLoop = isLoop && ((slidesPerView !== swiper.params.slidesPerView) || (slidesPerGroup !== swiper.params.slidesPerGroup));

      if (alwaysOne || matchMedia(onePerViewAt)) {
        swiper.params.slidesPerView = 1;
        swiper.params.slidesPerGroup = 1;
      } else {
        setBreakpointProperty(swiper, "slidesPerView", slidesPerView);
        setBreakpointProperty(swiper, "slidesPerGroup", slidesPerGroup);
      }

      setBreakpointProperty(swiper, "spaceBetween", spaceBetween);

      // A reloop is necessary when changing `slidesPerView` or `direction`*
      // when swiper has already been initialized.
      // *cargo cult addition: when slidesPerGroup was implemented, it was added to the needsReLoop calculation without
      // taking into consideration whether it is required
      if (needsReLoop && initialized) {
        // Tip: This logic is different on >= v9.
        // See: https://github.com/nolimits4web/swiper/blob/master/src/core/breakpoints/setBreakpoint.js
        swiper.loopDestroy();
        swiper.loopCreate();
        swiper.updateSlides();
        swiper.slideTo(activeIndex - loopedSlides + swiper.loopedSlides, 0, false);
      } else {
        swiper.updateSlides();
        swiper.slideReset(0);
      }
    }

    function setBreakpointProperty(swiper, key, value) {
      if (Number.isFinite(value)) {
        // version >= 1.3 - backwards compatibility for non-breakpoint values.
        swiper.params[key] = value;
      } else if (is.obj(value)) {
        // The value is an object, but we don't know if it's a breakpoint yet.
        const availableBreakpoints = Object.keys(value);
        const currBreakpoint = getCurrentBreakpoint(availableBreakpoints);
        const bpValue = value[currBreakpoint.id];

        // Found a breakpoint value, use it.
        if (bpValue) {
          swiper.params[key] = isUnitValue(bpValue) ? bpValue.number : bpValue;
        } else if (isUnitValue(value) && !isResponsiveValue(value)) {
          // Otherwise, if the value is a unit value, use it.
          // Due to unknown reasons, the value is sometimes a breakpoint + unit value object, we ignore those.
          swiper.params[key] = value.number;
        }
      }
    }

    function isResponsiveValue(value) {
      const { breakpoints } = window.BreakdanceFrontend.data;
      const ids = breakpoints.map((bp) => bp.id);
      return Object.keys(value).some(key => ids.includes(key));
    }

    function isUnitValue(value) {
      return is.obj(value) && "number" in value;
    }

    function resetSlideAnimations(slide) {
      const customEvent = new Event("breakdance_reset_animations", { bubbles: true });
      slide.dispatchEvent(customEvent);
    }

    function playSlideAnimations(slide, delay) {
      const customEvent = new CustomEvent("breakdance_play_animations", {
        bubbles: true,
        detail: { delay: delay / 1000 }
      });
      slide.dispatchEvent(customEvent);
    }

    function supportEntranceAnimations(swiper, settings) {
      let lastVisibleSlides = swiper.visibleSlides || [];
      const playOn = settings.advanced.play_animations_on || "transition_end";

      const getNewestSlides = () => swiper.visibleSlides?.filter(x => !lastVisibleSlides.includes(x)) || [];
      const getHiddenSlides = () => swiper.slides?.filter((slide) => !swiper.visibleSlides.includes(slide)) || [];

      // This is a workaround for when the user drags the slider before slideChange is triggered.
      swiper.on("sliderFirstMove", () => {
        const hiddenSlides = getHiddenSlides();
        hiddenSlides.forEach((slide) => resetSlideAnimations(slide));
      });

      swiper.on("slideChange", () => {
        const delay = playOn === "transition_start" ? 0 : swiper.params.speed * 0.3;
        const newSlides = getNewestSlides();

        newSlides.forEach(slide => {
          resetSlideAnimations(slide);
          playSlideAnimations(slide, delay);
        });

        lastVisibleSlides = swiper.visibleSlides;
      });
    }

    // This prevents memory leak from event listeners
    function destroy(id) {
      if (
        window.swiperInstances &&
        window.swiperInstances[id] &&
        // if the element is not in the DOM, "el" will be the selector instead of the element reference
        typeof window.swiperInstances[id].el === "object"
      ) {
        window.swiperInstances[id].destroy(true, true);
        delete window.swiperInstances[id];
      }
    }

    function update({ id, selector, settings, paginationSettings, extras }) {

      if (!isElementInDom(`${selector} > .breakdance-swiper-wrapper > .swiper`)) return;

      destroy(id);

      const defaultOptions = {
        settings: {
          effect: "slide",
          coverflow: {
            rotate: {
              number: 50
            },
            depth: {
              number: 100
            },
            stretch: {
              number: 0
            }
          },
          speed: { number: 1000 },
          autoplay_settings: {
            speed: {
              number: 3000
            }
          },
          advanced: {
            between_slides: 0,
            slides_per_view: 1,
            slides_per_group: 1,
            initial_slide: 0,
          },
          direction: "horizontal",
        },
        pagination: {
          type: "bullets"
        }
      };

      settings = mergeObjects(defaultOptions.settings, settings);
      paginationSettings = mergeObjects(
        defaultOptions.pagination,
        paginationSettings
      );

      const advancedSettings = settings.advanced;
      const isBuilder = !!window?.BreakdanceFrontend.utils.isBuilder();

      const isCoverflowEffect = settings.effect === "coverflow";
      const coverFlowEffect = isCoverflowEffect
        ? {
          coverflowEffect: {
            rotate: settings.coverflow.rotate.number,
            slideShadows: !!settings.coverflow.shadow,
            depth: settings.coverflow.depth.number,
            stretch: settings.coverflow.stretch.number
          }
        }
        : {};

      const fadeEffect =
        settings.effect === "fade"
          ? {
            fadeEffect: {
              crossFade: true
            }
          }
          : {};

      const builderOnlySettings = isBuilder
        ? {
          // this prevents bugs caused by Swiper swallowing events
          preventClicksPropagation: false,
          preventClicks: false,
          simulateTouch: false,
          // doesn't play nice with our drag events
          allowTouchMove: false
        }
        : {};

      const forceAutoplay = extras && extras.autoplay === true;

      const swiperInstance = new Swiper(
        `${selector} > .breakdance-swiper-wrapper > .swiper`,
        {
          ...extras,
          speed: settings.speed.number,
          loop: settings.infinite === "enabled" && !isBuilder,
          autoplay:
            settings.autoplay === "enabled" && (!isBuilder || forceAutoplay)
              ? {
                delay: settings.autoplay_settings.speed.number,
                pauseOnMouseEnter:
                  !!settings.autoplay_settings.pause_on_hover,
                disableOnInteraction:
                  !!settings.autoplay_settings.stop_on_interaction,
                stopOnLastSlide: settings.infinite !== "enabled",
              }
              : false,

          effect: settings.effect,
          pagination: {
            el: `${selector} > .breakdance-swiper-wrapper > .swiper-pagination`,
            type: paginationSettings.type,
            clickable: true,
          },
          navigation: {
            nextEl: `${selector} > .breakdance-swiper-wrapper > .swiper-button-next`,
            prevEl: `${selector} > .breakdance-swiper-wrapper > .swiper-button-prev`,
          },
          keyboard: !advancedSettings.disable_keyboard_control,
          ...coverFlowEffect,
          ...fadeEffect,

          ...builderOnlySettings,

          // Advanced options
          mousewheel: advancedSettings.swipe_on_scroll
            ? {
              releaseOnEdges: true
            }
            : false,

          autoHeight: !!advancedSettings.auto_height,
          loopPreventsSlide: false,
          centeredSlides: isCoverflowEffect
            ? // We decided to make it always true because otherwise it looks ugly
            true
            : settings.center_slides,
          // Swiper docs advise to do this
          watchSlidesProgress: advancedSettings.slides_per_view !== 1,
          // doesn't do anything on its own, but enables elements to create cool effects with HTML
          parallax: true,

          direction: settings.direction,
          a11y: {
            slideRole: ""
          },
          initialSlide: settings.advanced.initial_slide,
        });

      setBreakpoint(swiperInstance, settings);
      supportEntranceAnimations(swiperInstance, settings);

      swiperInstance.on("resize", () => {
        setBreakpoint(swiperInstance, settings);
      });

      window.swiperInstances = {
        ...window.swiperInstances,
        [id]: swiperInstance
      };
    }

    function updateSliderFromChild(id) {
      const sliderId = document
        // select itself
        .querySelector(`[data-node-id="${id}"]`)
        // get parent (slider) node id
        .parentElement.closest("[data-node-id]").dataset.nodeId;

      const sliderIdNumber = sliderId && parseInt(sliderId);

      if (window.swiperInstances && window.swiperInstances[sliderIdNumber]) {
        window.swiperInstances[sliderIdNumber].update();
      }
    }

    function selectSlide(id) {
      const slideElement = document
        .querySelector(`[data-node-id="${id}"]`)
        .closest(".swiper-slide");

      if (slideElement) {
        const slideIndex = Array.from(
          slideElement.parentElement.children
        ).indexOf(slideElement);

        const sliderElement =
          slideElement.parentElement &&
          slideElement.parentElement.closest("[data-node-id]");

        const sliderId = sliderElement ? sliderElement.dataset.nodeId : null;

        if (
          sliderId &&
          slideIndex !== null &&
          window.swiperInstances &&
          window.swiperInstances[sliderId]
        ) {
          window.swiperInstances[sliderId].slideTo(slideIndex, 0);
        }
      }
    }

    return {
      update,
      destroy,
      updateSliderFromChild,
      selectSlide
    };
  }

  window.BreakdanceSwiper = BreakdanceSwiper;
})();
