import { BaseComponent, exposeComponent, triggerEvent } from "../../utils/dom";

/**
 * External dependencies
 */
import Swiper from "swiper";

import {
  Navigation,
  Pagination,
  Scrollbar,
  A11y,
  Autoplay,
  Thumbs,
  EffectFade,
  EffectCube,
  EffectFlip,
  EffectCoverflow,
  EffectCreative,
  EffectCards,
} from "swiper/modules";

Swiper.use([
  Navigation,
  Pagination,
  Scrollbar,
  A11y,
  Autoplay,
  Thumbs,
  EffectFade,
  EffectCube,
  EffectFlip,
  EffectCoverflow,
  EffectCreative,
  EffectCards,
]);

/**
 * Internal dependencies
 */
import { getColorCSSValue, isValidColor } from "./utils-for-frontend";

class CarouselScript extends BaseComponent {
  constructor(element, options = {}) {
    // Get this._element and this._config
    super(element, options);

    // Bail if the element is not a DOM node
    if (!element) {
      return;
    }

    // Already initialized.
    if (element.dataset.cbbCarouselId) {
      return this;
    }

    // Store the main element
    this.sliderElement = this._element;

    // Store the original source
    this.originalSource = element.cloneNode(true);

    // Get the owner document
    this.document = element.ownerDocument;

    // Cache the config
    this.settings = this._config;
    const { datasetSelector } = this.settings;

    // Get the datasetElement
    this.datasetElement = datasetSelector
      ? element.querySelector(datasetSelector)
      : element;

    // Get slider options
    this.sliderOptions = this.getSliderOptions(options?.sliderOptions);

    // The slider instance
    this.slider = false;

    // Kick start
    this.init();
  }

  static get NAME() {
    return "carousel";
  }

  defaultConfig() {
    return {
      selector: ".js-carousel-layout",
      wrapperSelector: ".carousel__inner",
      itemSelector: ".is-carousel-item",
      containerClass: "swiper",
      wrapperClass: "swiper-wrapper",
      itemClass: "swiper-slide",
      paginationClass: "swiper-pagination",
      navigationNextClass: "swiper-button-next",
      navigationPrevClass: "swiper-button-prev",
      scrollbarClass: "swiper-scrollbar",
      preloadClass: "cbb-carousel-preload",
      instanceId: "cbb-carousel-1",
      datasetSelector: "",
    };
  }

  getSliderOptions(options) {
    // Slider default options
    let sliderOptions = {
      // Optional parameters
      direction: "horizontal",
      loop: false,
      grabCursor: true,
      pagination: true,
      navigation: false,
      scrollbar: false,

      // Slides per view
      slidesPerView: 1,
      slidesPerGroup: 1,
      spaceBetween: 0,
      watchSlidesProgress: true,
      on: {
        afterInit(swiper) {
          triggerEvent(window, "cbb.carousel.afterInit", { swiper });
        },
        slideChangeTransitionStart(swiper) {
          triggerEvent(window, "cbb.carousel.slideChangeTransitionStart", {
            swiper,
          });
        },
        slideChangeTransitionEnd(swiper) {
          triggerEvent(window, "cbb.carousel.slideChangeTransitionEnd", {
            swiper,
          });
        },
      },
    };

    // Parse settings from attribute
    const carouselSettings = this.datasetElement.dataset?.carouselSettings;
    const attributeOptions = carouselSettings
      ? JSON.parse(carouselSettings)
      : {};

    sliderOptions = Object.assign(sliderOptions, attributeOptions);

    if (options) {
      sliderOptions = Object.assign(sliderOptions, options);
    }

    // Get slider options
    return sliderOptions;
  }

  init() {
    // Page load
    if (!this.slider) {
      // Setup the slider.
      this.buildSlider();
    }
  }

  buildSlider() {
    const { sliderElement, sliderOptions } = this;
    const {
      itemSelector,
      itemClass,
      wrapperSelector,
      wrapperClass,
      containerClass,
      preloadClass,
      instanceId,
    } = this.settings;

    // Query slide items
    const sliderItems = Array.from(
      sliderElement.querySelectorAll(itemSelector),
    );

    // Cache it
    this.sliderItems = sliderItems;

    // Add item class
    sliderItems.forEach((item) => this.addClass(item, itemClass));

    // The wrapper element
    let sliderWrapper = sliderElement.querySelector(wrapperSelector);

    if (sliderWrapper) {
      this.hasSlideWrapper = true;
    } else {
      this.hasSlideWrapper = false;

      // Create wrapper element
      sliderWrapper = this.document.createElement("div");
    }

    // Cache it
    this.sliderWrapper = sliderWrapper;

    // Add swiper class
    this.addClass(sliderWrapper, wrapperClass);

    const {
      equalHeight,
      effect,
      centeredSlides,
      centeredSlidesSettings = {},
    } = sliderOptions;

    // Make all items have equal height
    if (equalHeight) {
      this.addClass(sliderWrapper, "is-equal-height");
    }

    // centeredSlides
    if (
      effect === "slide" &&
      centeredSlides &&
      centeredSlidesSettings?.enable
    ) {
      let inactiveSize = parseFloat(centeredSlidesSettings?.inactiveSize);

      if (!isNaN(inactiveSize)) {
        this.addClass(sliderWrapper, "centered-active-slide");
        sliderElement.style.setProperty(
          "--swiper-inactive-scale",
          inactiveSize,
        );
      }
    }

    if (!this.hasSlideWrapper) {
      // Attach items
      sliderWrapper.append(...sliderItems);
    }

    // Create the container
    const sliderContainer = Object.assign(this.document.createElement("div"), {
      className: containerClass,
    });

    // Cache it
    this.sliderContainer = sliderContainer;

    // Wrap the slider wrapper inside a container
    sliderContainer.appendChild(sliderWrapper);
    sliderElement.appendChild(sliderContainer);

    // Build navigation
    this.buildNavigation();

    // Build play/pause
    this.buildPlayPause();

    // Build pagination
    this.buildPagination();

    // Build scrollbar
    this.buildScrollbar();

    // Mark it as initialized.
    this.addClass(sliderElement, "slider-initialized");

    // Remove the preload class
    this.removeClass(sliderElement, preloadClass);

    // Initialize the slider.
    this.slider = new Swiper(sliderContainer, sliderOptions);

    // Save the instance to the element.
    sliderElement.setAttribute("data-cbb-carousel-id", instanceId);
  }

  dispose() {
    super.dispose();

    if (this.slider) {
      // Swiper's destroy
      this.slider.destroy();

      // Remove the instance
      this.slider = null;

      if (!this.settings?.isClone) {
        // Restore the original source
        this.sliderElement.replaceWith(this.originalSource);
      }
    }
  }

  /**
   * Build slider controls
   */
  buildSliderControls() {
    if (!this.sliderControls) {
      // Create the control wrapper
      const sliderControls = this.document.createElement("div");

      // Add id
      sliderControls.setAttribute("id", `${this.settings.instanceId}-controls`);

      // Add class
      sliderControls.classList.add("cbb-carousel-controls");

      // Cache it
      this.sliderControls = sliderControls;

      // Add the controls to the slider element
      this.sliderElement.appendChild(sliderControls);
    }

    return this.sliderControls;
  }

  hasCarouselControls(controlType = "") {
    const {
      pagination,
      direction,
      navigation,
      navigationSettings = {},
      playPause = {},
    } = this.sliderOptions;

    if (!pagination || direction !== "horizontal") {
      return false;
    }

    const withPagination = "with-pagination";
    const hasNavigation =
      navigation && navigationSettings?.position === withPagination;
    const hasPlayPause =
      playPause?.enable && playPause?.position === withPagination;

    if (!controlType) {
      return hasNavigation || hasPlayPause;
    } else {
      if (controlType === "navigation") {
        return hasNavigation;
      } else if (controlType === "playPause") {
        return hasPlayPause;
      }
    }
  }

  getSliderControls(controlType) {
    if (this.hasCarouselControls(controlType)) {
      return this.buildSliderControls();
    }

    return this.sliderElement;
  }

  /**
   * Build pagination
   */
  buildPagination() {
    if (this.sliderOptions.pagination) {
      const sliderControls = this.getSliderControls();
      const {
        sliderElement,
        sliderOptions,
        settings: { instanceId, paginationClass } = {},
      } = this;

      // Create pagination wrapper
      const sliderPagination = this.document.createElement("div");

      // Cache it
      this.sliderPagination = sliderPagination;

      // Add id
      sliderPagination.setAttribute("id", `${instanceId}-pagination`);

      // Add class
      sliderPagination.classList.add(paginationClass);

      const { direction, paginationSettings = {} } = sliderOptions;

      const {
        size, // Deprecated
        bulletWidth = size,
        bulletHeight = size,
        spacing,
        color,
        inactiveColor,
        opacity,
        borderColor,
        horizontalGap,
        dynamicBullets = false,
      } = paginationSettings;

      if (bulletWidth === bulletHeight) {
        if (bulletWidth) {
          sliderElement.style.setProperty(
            "--swiper-pagination-bullet-size",
            bulletWidth,
          );
        }
      } else {
        if (bulletWidth) {
          sliderElement.style.setProperty(
            "--swiper-pagination-bullet-width",
            bulletWidth,
          );
        }
        if (bulletHeight) {
          sliderElement.style.setProperty(
            "--swiper-pagination-bullet-height",
            bulletHeight,
          );
        }
      }

      if (spacing) {
        const offsetProperty =
          direction === "vertical"
            ? "--swiper-pagination-right"
            : "--swiper-pagination-bottom";
        sliderElement.style.setProperty(offsetProperty, spacing);
      }

      if (isValidColor(color)) {
        sliderElement.style.setProperty(
          "--swiper-pagination-color",
          getColorCSSValue(color),
        );
      }

      if (isValidColor(inactiveColor)) {
        sliderElement.style.setProperty(
          "--swiper-pagination-bullet-inactive-color",
          getColorCSSValue(inactiveColor),
        );
      }

      if (isValidColor(borderColor)) {
        sliderElement.style.setProperty(
          "--swiper-pagination-bullet-border-color",
          getColorCSSValue(borderColor),
        );
      }

      if (opacity) {
        sliderElement.style.setProperty(
          "--swiper-pagination-bullet-inactive-opacity",
          opacity,
        );
      }

      if (horizontalGap) {
        sliderElement.style.setProperty(
          "--swiper-pagination-bullet-horizontal-gap",
          horizontalGap,
        );
      }

      // Add pagination to the options
      this.sliderOptions.pagination = {
        el: sliderPagination,
        renderBullet(i) {
          return `<span class="swiper-pagination-bullet" tabindex="0" role="button" aria-label="Go to slide ${
            i + 1
          }"><span class="swiper-pagination-bullet__dot"></span></span>`;
        },
        clickable: true,
        dynamicBullets,
      };

      // Add the pagination to the slider element
      sliderControls.appendChild(sliderPagination);
    }
  }

  /**
   * Build navigation
   */
  buildNavigation() {
    if (this.sliderOptions.navigation) {
      const {
        sliderElement,
        settings: { instanceId, navigationNextClass, navigationPrevClass } = {},
      } = this;

      const {
        nextIcon = {},
        prevIcon = {},
        size,
        position,
        xOffset,
        yOffset,
        spacing,
        color,
        ...otherProps
      } = this.sliderOptions.navigationSettings;

      const sliderControls = this.getSliderControls("navigation");

      // Create naviation next button
      const sliderNavigationNext = this.document.createElement("div");

      // Cache it
      this.sliderNavigationNext = sliderNavigationNext;

      // Add id
      sliderNavigationNext.setAttribute("id", `${instanceId}-navigation-next`);

      // Add class
      sliderNavigationNext.classList.add(navigationNextClass);

      if (nextIcon?.value) {
        sliderNavigationNext.classList.add("has-custom-icon");
        sliderNavigationNext.insertAdjacentHTML("afterbegin", nextIcon.value);
      }

      // Create naviation prev button
      const sliderNavigationPrev = this.document.createElement("div");

      // Cache it
      this.sliderNavigationPrev = sliderNavigationPrev;

      // Add id
      sliderNavigationPrev.setAttribute("id", `${instanceId}-navigation-prev`);

      // Add class
      sliderNavigationPrev.classList.add(navigationPrevClass);

      if (prevIcon?.value) {
        sliderNavigationPrev.classList.add("has-custom-icon");
        sliderNavigationPrev.insertAdjacentHTML("afterbegin", prevIcon.value);
      }

      // Add custom variables
      if (size) {
        sliderElement.style.setProperty("--swiper-navigation-size", size);
      }

      // Add position settings
      if (position) {
        sliderNavigationNext.classList.add(
          "nav-custom-position",
          `is-${position}`,
        );
        sliderNavigationPrev.classList.add(
          "nav-custom-position",
          `is-${position}`,
        );

        if (xOffset) {
          sliderElement.style.setProperty("--cbb--nav-x-offset", xOffset);
        }
        if (yOffset) {
          sliderElement.style.setProperty("--cbb--nav-y-offset", yOffset);
        }
      } else {
        if (spacing) {
          sliderElement.style.setProperty(
            "--swiper-navigation-sides-offset",
            spacing,
          );
        }
      }

      if (isValidColor(color)) {
        sliderElement.style.setProperty(
          "--swiper-navigation-color",
          getColorCSSValue(color),
        );
      }

      // Add navigation to the options
      this.sliderOptions.navigation = {
        ...otherProps,
        nextEl: sliderNavigationNext,
        prevEl: sliderNavigationPrev,
      };

      // Add the naviation button to the slider element
      sliderControls.appendChild(sliderNavigationNext);

      // Add the naviation button to the slider element
      sliderControls.appendChild(sliderNavigationPrev);

      // Add class to mark the slider have navigation buttons
      sliderElement.classList.add("swiper--has-navigation");
    }
  }

  /**
   * Build play/pause
   */
  buildPlayPause() {
    const { autoplay, playPause = {}, on = {} } = this.sliderOptions;
    if (playPause?.enable) {
      const {
        size,
        position = "bottom-right",
        xOffset,
        yOffset,
        color,
        trackColor,
        progressColor,
      } = playPause;

      const sliderControls = this.getSliderControls("playPause");

      // Create play/pause button
      const sliderPlayPause = this.document.createElement("div");

      // Set the markup
      sliderPlayPause.innerHTML =
        '<svg viewBox="0 0 48 48"><circle class="track" cx="24" cy="24" r="20"></circle><circle cx="24" cy="24" r="20"></circle></svg>';

      // Add attributes
      sliderPlayPause.setAttribute(
        "id",
        `${this.settings.instanceId}-play-pause`,
      );
      sliderPlayPause.setAttribute("tabindex", 0);
      sliderPlayPause.setAttribute("role", "button");
      window.addEventListener(
        "cbb.carousel.afterInit",
        ({ detail: { swiper } }) =>
          sliderPlayPause.setAttribute(
            "aria-controls",
            swiper.wrapperEl.getAttribute("id"),
          ),
      );

      // Add class
      sliderPlayPause.classList.add(
        "cbb-play-pause",
        "cbb-carousel-play-pause",
        `is-${position}`,
      );

      // Inject custom properties
      if (size) {
        sliderPlayPause.style.setProperty("--cbb--pp-size", size);
      }
      if (xOffset) {
        sliderPlayPause.style.setProperty("--cbb--pp-x-offset", xOffset);
      }
      if (yOffset) {
        sliderPlayPause.style.setProperty("--cbb--pp-y-offset", yOffset);
      }
      if (isValidColor(color)) {
        sliderPlayPause.style.setProperty(
          "--cbb--pp-color",
          getColorCSSValue(color),
        );
      }
      if (isValidColor(trackColor)) {
        sliderPlayPause.style.setProperty(
          "--cbb--progress-track-color",
          getColorCSSValue(trackColor),
        );
      }
      if (isValidColor(progressColor)) {
        sliderPlayPause.style.setProperty(
          "--cbb--progress-color",
          getColorCSSValue(progressColor),
        );
      }

      const updatePlayPause = (sliderPlayPause, autoplay) => {
        if (autoplay) {
          sliderPlayPause.classList.remove("is-paused");
          sliderPlayPause.classList.add("is-playing");
          sliderPlayPause.setAttribute("aria-label", "Pause");
        } else {
          sliderPlayPause.classList.add("is-paused");
          sliderPlayPause.classList.remove("is-playing");
          sliderPlayPause.setAttribute("aria-label", "Play");
        }
      };
      const setProgress = (progress) =>
        sliderPlayPause.style.setProperty(
          "--cbb--progress",
          progress.toFixed(5),
        );

      const handlePlayPause = () => {
        const { running, start, resume, pause } = this.slider.autoplay;
        if (!running) {
          start();
          resume();
          updatePlayPause(sliderPlayPause, true);
          this.autoplaying = true;
        } else {
          if (!this.autoplaying) {
            resume();
          } else {
            pause();
          }
          this.autoplaying = !this.autoplaying;
          updatePlayPause(sliderPlayPause, this.autoplaying);
        }
      };

      // Add event listener
      sliderPlayPause.addEventListener("click", handlePlayPause);
      sliderPlayPause.addEventListener("keydown", (e) => {
        if (e.keyCode !== 13 && e.keyCode !== 32) return;
        e.preventDefault();
        handlePlayPause();
      });

      // Play by default
      this.autoplaying = !!autoplay;
      updatePlayPause(sliderPlayPause, !!autoplay);

      on.autoplayStop = () => {
        updatePlayPause(sliderPlayPause, false);
        setProgress(0);
      };
      on.autoplayResume = () => updatePlayPause(sliderPlayPause, true);
      on.slideChange = (s) => {
        if (!this.autoplaying) {
          s.autoplay.stop();
        }
      };
      on.lock = (s) => sliderPlayPause.classList.add("swiper-button-lock");
      on.unlock = (s) => sliderPlayPause.classList.remove("swiper-button-lock");
      on.autoplayTimeLeft = (s, time, progress) => {
        if (s.isLocked || s.autoplay.paused) {
          return;
        }

        const activeSlideEl = s.slides[s.activeIndex];
        const currentSlideDelay = parseInt(
          activeSlideEl.getAttribute("data-swiper-autoplay"),
          10,
        );

        setProgress(
          1 - (currentSlideDelay > 0 ? time / currentSlideDelay : progress),
        );
      };

      // Cache it
      this.sliderPlayPause = sliderPlayPause;

      // Add the play/pause button to the slider element
      sliderControls.appendChild(sliderPlayPause);
    }
  }

  /**
   * Build scrollbar
   */
  buildScrollbar() {
    if (this.sliderOptions.scrollbar) {
      const { sliderElement, settings: { instanceId, scrollbarClass } = {} } =
        this;

      // Create scrollbar element
      const sliderScrollbar = this.document.createElement("div");

      // Cache it
      this.sliderScrollbar = sliderScrollbar;

      // Add id
      sliderScrollbar.setAttribute("id", `${instanceId}-scrollbar`);

      // Add class
      sliderScrollbar.classList.add(scrollbarClass);

      const { direction, scrollbarSettings = {} } = this.sliderOptions;
      const {
        trackSize,
        spacing,
        trackColor,
        sliderColor,
        trackOpacity,
        ...otherProps
      } = scrollbarSettings;

      // Add custom variables
      if (trackSize) {
        sliderElement.style.setProperty("--swiper-scrollbar-size", trackSize);
      }

      if (spacing) {
        const offsetProperty =
          direction === "vertical"
            ? "--swiper-scrollbar-right"
            : "--swiper-scrollbar-bottom";
        sliderElement.style.setProperty(offsetProperty, spacing);
      }

      if (isValidColor(trackColor)) {
        sliderElement.style.setProperty(
          "--swiper-scrollbar-track-color",
          getColorCSSValue(trackColor),
        );
      }

      if (isValidColor(sliderColor)) {
        sliderElement.style.setProperty(
          "--swiper-scrollbar-slider-color",
          getColorCSSValue(sliderColor),
        );
      }

      if (trackOpacity) {
        sliderElement.style.setProperty(
          "--swiper-scrollbar-track-opacity",
          trackOpacity,
        );
      }

      // Add scrollbar to the options
      this.sliderOptions.scrollbar = {
        ...otherProps,
        el: sliderScrollbar,
      };

      // Add the scrollbar to the slider element
      this.sliderContainer.appendChild(sliderScrollbar);
    }
  }

  addClass(element, classname) {
    if (!element.classList.contains(classname)) {
      element.classList.add(classname);
    }
  }

  removeClass(element, classname) {
    if (classname) {
      element.classList.remove(classname);
    }
  }
}

/**
 * Get carousel manager object
 *
 * @returns {Array}
 */
export const getCarouselManager = () => {
  if (!window?.CBBCarousels) {
    window.CBBCarousels = {};
  }

  return window.CBBCarousels;
};

export const addCarousel = (element, args, carouselElements = []) => {
  const manager = getCarouselManager();
  const { instanceId } = args;
  if (!manager[instanceId]) {
    if (carouselElements.length > 1) {
      const thumbs = buildThumbs(element, carouselElements);
      if (thumbs) {
        args.sliderOptions = { thumbs: { swiper: thumbs.slider } };
      }
    }

    if (element.classList.contains("wp-block-query")) {
      args["wrapperSelector"] = "ul";
      args["itemSelector"] = "li";
    }

    manager[instanceId] = new CarouselScript(element, args);
  }

  return manager[instanceId];
};

const buildThumbs = (element, carouselElements) => {
  let carouselSettings = element?.dataset?.carouselSettings;
  if (!carouselSettings) {
    return null;
  }

  carouselSettings = JSON.parse(carouselSettings) || {};

  const thumbSelector = carouselSettings?.thumbsSettings?.enable
    ? carouselSettings.thumbsSettings?.selector
    : "";

  if (!thumbSelector) {
    return null;
  }

  const thumbElement = element.ownerDocument.querySelector(thumbSelector);
  if (!thumbElement) {
    return null;
  }

  const thumbsIndex = carouselElements.findIndex((element) =>
    element.isSameNode(thumbElement),
  );

  if (thumbsIndex === -1) {
    return null;
  }

  return addCarousel(
    thumbElement,
    {
      instanceId: `cbb-carousel-${thumbsIndex + 1}`,
    },
    carouselElements,
  );
};

exposeComponent("CarouselScript", CarouselScript);

export default CarouselScript;
