(function ($) {
  const debounce = function (callback, wait, immediate) {
    let timeout;
    return function () {
      const context = this,
        args = arguments;
      const later = function () {
        timeout = null;
        if (!immediate) callback.apply(context, args);
      };
      const callNow = immediate && !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(later, wait);
      if (callNow) callback.apply(context, args);
    };
  };

  const updateCartItem = async (input) => {
    const url = window.BreakdanceFrontend.data.ajaxUrl;
    const hash = input.getAttribute("name").replace(/cart\[(\w+)]\[qty]/g, "$1");
    const newQty = parseFloat(input.value);

    const data = new FormData();
    data.append("action", "breakdance_cart_update_qty");
    data.append("hash", hash);
    data.append("quantity", newQty);

    const payload = {
      method: "POST",
      credentials: "same-origin",
      body: data
    };

    const { makeAjaxRequest } = window.BreakdanceFrontend.utils;
    await makeAjaxRequest(url, payload);

    $(document.body).trigger("wc_fragment_refresh");
    $(document.body).trigger("wc_update_cart");
  };

  const delayedUpdateCartItem = debounce((input) => {
    updateCartItem(input);
    blockUI(input);
  }, 300);

  const afterInputChange = (input) => {
    // Works for Mini Cart, Single Product Page, Shop and Cart Page.
    if (isLoopProduct(input)) {
      getAddToCartButton(input)
        ?.setAttribute("data-quantity", input.value);
    } else if (isSingleProduct(input)) {
      // Do nothing. WooCommerce automatically picks up the input.
    } else {
      delayedUpdateCartItem(input);
    }
  };

  const isIncrement = (target) => {
    return target.matches(".bde-quantity-button--inc");
  };

  const isSingleProduct = (input) => {
    return !!input.closest("form.cart");
  };

  const isLoopProduct = (input) => {
    return !!input.closest("li.product");
  };

  const blockUI = (input) => {
    const row = input.closest(".woocommerce-mini-cart-item");

    $(row).block({
      message: null,
      overlayCSS: {
        opacity: 0.6
      }
    });
  };

  const getAddToCartButton = (input) => {
    return input.closest("li.product").querySelector(".add_to_cart_button");
  };

  const getQuantityInput = (event) => {
    return event.target.parentElement.querySelector(".qty");
  };

  const onMaybeButtonClick = (event) => {
    const isButton = event.target.matches(".bde-quantity-button");

    if (!isButton) return;

    event.preventDefault();

    const input = getQuantityInput(event);
    const action = isIncrement(event.target) ? "stepUp" : "stepDown";

    input[action](); // Increment or decrement input

    // Manually trigger change event.
    const customEvent = new Event("change", {
      bubbles: true,
      cancelable: true
    });

    input.dispatchEvent(customEvent);
  };

  const onMaybeInputChange = (event) => {
    const isInput = event.target.matches("input.qty");

    if (!isInput) return;
    afterInputChange(event.target);
  }

  const maybeReloadCart = () => {
    const containsEl = document.querySelector(".bde-cart-contents");
    if (containsEl) location.reload();
  }

  addEventListener("DOMContentLoaded", () => {
    addEventListener("change", onMaybeInputChange);
    addEventListener("click", onMaybeButtonClick);
    $(document.body).on("wc_cart_emptied", maybeReloadCart);
  });
}(jQuery));
