(function ($) {
  const modal = $('<div id="stw-confirmation-modal" class="stw-modal"><div class="stw-modal-content"><div class="stw-modal-header"><span class="stw-close">&times;</span></div><div class="stw-modal-body"><p>' + stwlite_frontend_js_vars.i18n.added_to_wishlist + '</p></div><div class="stw-modal-footer"><button id="stw-confirm-button" class="stw-modal-button">' + stwlite_frontend_js_vars.i18n.view_wishlist + '</button></div></div></div>')
  $('body').append(modal)

  const modalElement = $('#stw-confirmation-modal')

  const span = modalElement.find('.stw-close')

  const confirmBtn = modalElement.find('#stw-confirm-button')
  const cancelBtn = modalElement.find('#stw-cancel-button')

  // Mobile: detect touch-primary devices (phones/tablets — not desktop)
  const isTouchPrimary = window.matchMedia('(hover: none) and (pointer: coarse)').matches
  const listsBackdrop = $('<div class="stw-mobile-lists-backdrop" aria-hidden="true"></div>')

  function closeMobileListsPopup () {
    const popup = $('.save-to-wishlist-multiple-lists-container.stw-multiple-lists-open')
    if (!popup.length) return
    popup.removeClass('stw-multiple-lists-open')
    listsBackdrop.removeClass('stw-active')
    // Restore the popup to its original DOM position
    const prevSibling = popup.data('stw-prev-sibling')
    const originalParent = popup.data('stw-original-parent')
    if (prevSibling && prevSibling.length) {
      popup.insertAfter(prevSibling)
    } else if (originalParent && originalParent.length) {
      originalParent.prepend(popup)
    }
  }

  function openMobileListsPopup (popup) {
    closeMobileListsPopup()
    // Store the popup's current DOM position so it can be restored on close
    popup.data('stw-prev-sibling', popup.prev())
    popup.data('stw-original-parent', popup.parent())
    // Move popup to <body> to escape any ancestor stacking context.
    // Placement containers (e.g. top_right) have position:absolute + z-index:1,
    // which caps everything inside at that stacking level — keeping the popup
    // below the backdrop (z-index:99998) no matter how high the popup's own
    // z-index is. Moving it to <body> puts it in the root stacking context.
    $('body').append(popup)
    popup.addClass('stw-multiple-lists-open')
    listsBackdrop.addClass('stw-active')
  }

  /**
   * Open the confirmation modal and bind confirm/cancel handlers.
   *
   * @since 1.0.0
   *
   * @param {Function|null} callback - Callback invoked with a boolean indicating whether the user confirmed.
   */
  function openModal(callback) {
    modalElement.show()

    if (callback !== null) {
      confirmBtn.off('click').on('click', function () {
        if (typeof callback === 'function') {
          callback(true)
        }
        closeModal()
      })

      cancelBtn.off('click').on('click', function () {
        if (typeof callback === 'function') {
          callback(false)
        }
        closeModal()
      })
    }
  }

  /**
   * Add a product to the wishlist for either a guest (localStorage) or logged-in user (AJAX).
   *
   * @since 1.0.0
   *
   * @param {jQuery} button         - The wishlist button element that was clicked.
   * @param {number} productId      - The product or variation ID to add.
   * @param {number} collectionId   - The target wishlist collection ID.
   * @param {string} [urlCode]      - Optional wishlist URL code; falls back to the default wishlist's code.
   * @param {string} [wishlistName] - Optional wishlist name used in fade notifications.
   */
  function addItemToWishlist(button, productId, collectionId, urlCode = '', wishlistName = '') {
    wishlistName = wishlistName || (stwlite_frontend_js_vars.stwOptions.defaultWishlist && stwlite_frontend_js_vars.stwOptions.defaultWishlist.name) || ''
    const urlProductCode = urlCode || stwlite_frontend_js_vars.stwOptions.defaultWishlist.url_code

    // Check if user is logged in.
    if (parseInt(stwlite_frontend_js_vars.user_id) === 0 && stwlite_frontend_js_vars.stwOptions.disableGuestWishlist === 'no') {
      if (!productId) {
        console.error('Save to Wishlist: Missing data-product-id attribute.')
        return
      }

      let wishlist = {}
      try {
        const storedWishlist = localStorage.getItem('stw_wishlist')
        if (storedWishlist) {
          wishlist = JSON.parse(storedWishlist)
        }
      } catch (e) {
        console.error('Save to Wishlist: Could not parse wishlist from localStorage.', e)
        wishlist = {}
      }

      const productIdStr = String(productId)

      let quantity = 1
      let showPopup = true
      if (wishlist.hasOwnProperty(productIdStr)) {
        if (!stwlite_frontend_js_vars.stwOptions.allowMultipleProductsInWishlist) {
          if (stwlite_frontend_js_vars.stwOptions.allowRemoveProductFromWishlist) {
            delete wishlist[productIdStr]
            setWishlistButtonState(button, false)
            showPopup = false
          }
        } else {
          quantity = wishlist[productIdStr].quantity + 1

          // Update only the quantity for the product in the wishlist.
          wishlist[productIdStr].quantity = quantity
        }
      } else {
        // Add product to wishlist.
        wishlist[productIdStr] = {
          productId,
          quantity,
          addedAt: new Date().toISOString()
        }
        setWishlistButtonState(button, true)
      }

      try {
        localStorage.setItem('stw_wishlist', JSON.stringify(wishlist))

        // Show popup if not removed.
        if (showPopup) {
          if (stwlite_frontend_js_vars.stwOptions.wishlistAddActionButton === 'fade') {
            showFadeNotification(button, wishlistName)
          } else {
            // Update modal content.
            let modalContent = '<p>' + stwlite_frontend_js_vars.i18n.added_to_wishlist + '</p>'
            if (stwlite_frontend_js_vars.stwOptions.showLoginCreateLink === 'yes') {
              modalContent += '<p><a href="' + stwlite_frontend_js_vars.stwOptions.myAccountUrl + '">' + stwlite_frontend_js_vars.i18n.guest_wishlist_description + '</a></p>'
            }
            modalElement.find('.stw-modal-body').html(modalContent)

            openModal(function (confirmed) {
              if (confirmed) {
                window.location.href = stwlite_frontend_js_vars.stwOptions.wishlistUrl
              } else {
                closeModal()
              }
            })
          }
        }
      } catch (e) {
        console.error('Save to Wishlist: Could not save wishlist to localStorage.', e)
      }

      return
    }

    if (button.hasClass('stw-active')) {
      if (stwlite_frontend_js_vars.stwOptions.allowRemoveProductFromWishlist) {
        button.prop('disabled', true)
        button.addClass('stw-removing')
        button.find('.stw-text-label').text(stwlite_frontend_js_vars.i18n.removing)

        jQuery.ajax({
          url: stwlite_frontend_js_vars.ajax_url,
          type: 'POST',
          data: {
            action: 'stwlite_remove_product_from_wishlist',
            nonce: stwlite_frontend_js_vars.nonce,
            product_id: productId,
            collection_id: collectionId
          },
          success: function (response) {
            if (response.status === 'success') {
              button.removeClass('stw-active')
              button.find('.stw-text-label').text(stwlite_frontend_js_vars.buttonLabels.shopWishlistLabel)
              button.removeAttr('disabled')
              button.removeClass('stw-removing')
            }
          }
        })

        return
      }

      if (!stwlite_frontend_js_vars.stwOptions.allowMultipleProductsInWishlist) {
        return
      }
    }

    button.prop('disabled', true)
    button.addClass('stw-adding')
    button.find('.stw-text-label').text(stwlite_frontend_js_vars.i18n.adding)

    jQuery.ajax({
      url: stwlite_frontend_js_vars.ajax_url,
      type: 'POST',
      data: {
        action: 'stwlite_add_to_wishlist',
        nonce: stwlite_frontend_js_vars.nonce,
        product_id: productId,
        collection_id: collectionId
      },
      success: function (response) {
        if (stwlite_frontend_js_vars.stwOptions.wishlistAddActionButton === 'redirect') {
          window.location.href = stwlite_frontend_js_vars.stwOptions.wishlistUrl + urlProductCode
        } else if (stwlite_frontend_js_vars.stwOptions.wishlistAddActionButton === 'fade') {
          resetButton(button)
          showFadeNotification(button, wishlistName)
        } else {
          openModal(function (confirmed) {
            if (confirmed) {
              window.location.href = stwlite_frontend_js_vars.stwOptions.wishlistUrl + urlProductCode
            } else {
              closeModal()
            }
          })

          resetButton(button)
        }
      }
    })
  }

  /**
   * Toggle the wishlist button's active state and update its label.
   *
   * @since 1.0.5
   *
   * @param {jQuery}  $button  - The wishlist button element.
   * @param {boolean} isActive - Whether the button should be marked as active (product in wishlist).
   */
  function setWishlistButtonState($button, isActive) {
    const label = isActive
      ? stwlite_frontend_js_vars.buttonLabels.selectedShopWishlistLabel
      : stwlite_frontend_js_vars.buttonLabels.shopWishlistLabel
    $button.toggleClass('stw-active', isActive)
    $button.find('.stw-text-label').text(label)
  }

  function disableIfCartItemButton(button) {
    if (button.closest('[class*="stw-wishlist-button-placement-cart_item"]').length) {
      button.prop('disabled', true)
      return true
    }
    return false
  }

  /**
   * Reset the wishlist button to its active state and remove loading classes.
   *
   * @since 1.0.4
   *
   * @param {jQuery} button - The wishlist button element to reset.
   */
  function resetButton(button) {
    setWishlistButtonState(button, true)
    button.removeClass('stw-adding')
    button.removeAttr('disabled')
  }

  /**
   * Hide the confirmation modal.
   *
   * @since 1.0.0
   */
  function closeModal() {
    modalElement.hide()
  }

  /**
   * Show a transient fade-in/out notification next to the wishlist button.
   *
   * @since 1.0.4
   *
   * @param {jQuery} button       - The wishlist button the notification is anchored to.
   * @param {string} wishlistName - The wishlist name rendered in the notification message.
   */
  function showFadeNotification(button, wishlistName) {
    const container = button.closest('.stw-wishlist-button-container')
    container.find('.stw-fade-notification').remove()
    const message = stwlite_frontend_js_vars.i18n.added_to_wishlist_name.replace('%s', wishlistName)
    const checkIcon = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg>'
    const notification = $('<span class="stw-fade-notification" role="status" aria-live="polite" aria-atomic="true">' + checkIcon + $('<span>').text(message).html() + '</span>')
    notification.css('display', 'block').hide()
    // Append to the container so the notification renders for guests too.
    // .save-to-wishlist-multiple-lists-container only exists for logged-in
    // users (gated by is_user_logged_in() in Buttons::wishlist_button_html),
    // so the prior .next().after() insertion was a no-op for guests.
    container.append(notification)
    notification.fadeIn(300, function () {
      setTimeout(function () {
        notification.fadeOut(400, function () {
          notification.remove()
        })
      }, 2000)
    })
  }

  span.on('click', function () {
    closeModal()
  })

  $(window).on('click', function (event) {
    if (event.target == modalElement[0]) {
      closeModal()
    }
  })

  if (isTouchPrimary) {
    $('body').append(listsBackdrop)
    listsBackdrop.on('click', closeMobileListsPopup)
  }

  if (parseInt(stwlite_frontend_js_vars.user_id) === 0 && stwlite_frontend_js_vars.stwOptions.disableGuestWishlist === 'no') {
    // Get guest wishlist products.
    const storedWishlist = localStorage.getItem('stw_wishlist')
    if (storedWishlist) {
      const wishlist = JSON.parse(storedWishlist)
      const productIds = Object.keys(wishlist)

      // Loop through all .save-to-wishlist-button buttons and add stw-active if productId matches
      $('.save-to-wishlist-button').each(function () {
        const $btn = $(this)
        const btnProductId = String($btn.data('product-id'))
        if (productIds.includes(btnProductId)) {
          setWishlistButtonState($btn, true)
          disableIfCartItemButton($btn)
        }
      })
    }
  }

  // Store wishlist from localStorage to database after account is created/logged in.
  const storedWishlist = localStorage.getItem('stw_wishlist')
  if (parseInt(stwlite_frontend_js_vars.user_id) > 0 && storedWishlist) {
    const wishlist = JSON.parse(storedWishlist)

    $.ajax({
      url: stwlite_frontend_js_vars.ajax_url,
      type: 'POST',
      data: {
        action: 'stwlite_store_wishlist_from_local_storage',
        wishlist,
        nonce: stwlite_frontend_js_vars.nonce
      },
      success: function (response) {
        if (response.status === 'success' && response.items.length > 0) {
          // Remove all items from localStorage.
          localStorage.removeItem('stw_wishlist')

          // Update wishlist items.
          response.items.forEach(function (itemId) {
            const button = $('.save-to-wishlist-button[data-product-id="' + itemId + '"]')
            button.addClass('stw-active')
            button.each(function () { disableIfCartItemButton($(this)) })
          })
        }
      }
    })
  }

  $(document).on('click', '.save-to-wishlist-button', function (e) {
    e.preventDefault()

    // On touch devices: first tap opens the choose-list bottom sheet instead of
    // adding immediately. If the popup is already open (user tapped again after
    // it was somehow still visible), fall through to normal add behaviour.
    if (isTouchPrimary) {
      const container = $(this).closest('.stw-wishlist-button-container')
      const popup = container.find('.save-to-wishlist-multiple-lists-container')
      if (popup.length && !popup.hasClass('stw-multiple-lists-open')) {
        openMobileListsPopup(popup)
        return
      }
    }

    const button = $(this)
    const collectionId = button.data('collection-id') ?? stwlite_frontend_js_vars.stwOptions.defaultWishlist?.id
    let productId = button.data('product-id')

    const variationId = getVariationId(button)
    if (variationId > 0) {
      productId = variationId
    }

    addItemToWishlist(button, productId, collectionId)
  })

  $(document).on('click', '.save-to-wishlist-multiple-lists-container', function (e) {
    // Stop propagation so clicks inside the popup sheet don't bubble up and
    // accidentally trigger other handlers. e.preventDefault() is intentionally
    // omitted — it has no effect on <div> clicks but can interfere with the
    // iOS native select picker opening inside the sheet.
    e.preventDefault()
    e.stopPropagation()
  })

  $(document).on('change', '.save-to-wishlist-multiple-lists-button-content select', function (e) {
    e.preventDefault()
    const select = $(this)
    let productId = select.data('product-id')
    const button = $('.save-to-wishlist-product-id-' + productId)
    const wishlistData = select.val()
    const collectionId = wishlistData.split(':')[0]
    const urlCode = wishlistData.split(':')[1]

    const variationId = getVariationId(button)
    if (variationId > 0) {
      productId = variationId
    }

    // Reset the select field to the first option.
    select.val(select.find('option:first').val())

    addItemToWishlist(button, productId, collectionId, urlCode)

    // Close the mobile bottom sheet after a list is selected.
    if (isTouchPrimary) {
      closeMobileListsPopup()
    }
  })

  /**
   * Resolve the currently selected variation ID for a wishlist button.
   *
   * @since 1.0.4
   *
   * @param {jQuery} button - The wishlist button element.
   * @return {number} The variation ID, or 0 when no variation is selected.
   */
  function getVariationId(button) {
    let variationId = button.data('variation-id')

    if (!variationId) {
      const variationsForm = button.closest('form.variations_form, .product').find('form.variations_form').first()
      if (variationsForm.length > 0) {
        const selectedVariationId = variationsForm.find('input[name="variation_id"]').val()
        if (selectedVariationId && selectedVariationId !== '') {
          variationId = selectedVariationId
        }
      }
    }

    return variationId ? parseInt(variationId) : 0
  }

  /**
   * Add cart items to the given wishlist collection for logged-in users.
   *
   * @since 1.0.0.1
   *
   * @param {jQuery}        button         - The cart wishlist button element.
   * @param {number}        collectionId   - The target wishlist collection ID.
   * @param {number[]|null} selectedIds    - Optional list of product IDs to add; when null/empty, all cart items are added.
   * @param {string}        [urlCode]      - Optional wishlist URL code; falls back to the default wishlist's code.
   * @param {string}        [wishlistName] - Optional wishlist name used in fade notifications.
   */
  function addItemToWishlistCart(button, collectionId, selectedIds, urlCode = '', wishlistName = '') {
    wishlistName = wishlistName || (stwlite_frontend_js_vars.stwOptions.defaultWishlist && stwlite_frontend_js_vars.stwOptions.defaultWishlist.name) || ''
    const urlProductCode = urlCode || stwlite_frontend_js_vars.stwOptions.defaultWishlist.url_code

    button.prop('disabled', true)
    button.addClass('stw-adding')
    button.find('.stw-text-label').text(stwlite_frontend_js_vars.i18n.adding)

    const postData = {
      action: 'stwlite_add_product_cart_to_wishlist',
      nonce: stwlite_frontend_js_vars.nonce,
      collection_id: collectionId
    }

    if (selectedIds && selectedIds.length > 0) {
      postData.product_ids = selectedIds
    }

    jQuery.ajax({
      url: stwlite_frontend_js_vars.ajax_url,
      type: 'POST',
      data: postData,
      success: function (response) {
        // If all submitted items were already in the wishlist, disable the button
        // and inform the user without triggering the normal post-add flow.
        if (response.all_in_wishlist) {
          button.prop('disabled', true)
          button.attr('title', stwlite_frontend_js_vars.i18n.all_cart_items_in_wishlist)
          button.find('.stw-text-label').text(button.find('.stw-text-label').attr('data-label'))
          button.removeClass('stw-adding')
          return
        }

        if (stwlite_frontend_js_vars.stwOptions.wishlistAddActionButton === 'redirect') {
          window.location.href = stwlite_frontend_js_vars.stwOptions.wishlistUrl + urlProductCode
        } else if (stwlite_frontend_js_vars.stwOptions.wishlistAddActionButton === 'fade') {
          button.fadeOut(400, function () {
            button.find('.stw-text-label').text(button.find('.stw-text-label').attr('data-label'))
            button.removeAttr('disabled')
            button.removeClass('stw-adding')
            button.fadeIn(400, function () {
              showFadeNotification(button, wishlistName)
            })
          })
        } else {
          // Update modal content.
          modalElement.find('.stw-modal-body').html('<p>' + response.message + '</p>')

          openModal(function (confirmed) {
            if (confirmed) {
              window.location.href = stwlite_frontend_js_vars.stwOptions.wishlistUrl + urlProductCode
            } else {
              closeModal()
            }
          })

          button.find('.stw-text-label').text(button.find('.stw-text-label').attr('data-label'))
          button.removeAttr('disabled')
          button.removeClass('stw-adding')
        }
      }
    })
  }

  $(document).on('change', '.save-to-wishlist-multiple-lists-cart-button-content select', function (e) {
    e.preventDefault()
    const select = $(this)
    const button = $('.save-to-wishlist-cart-button')
    const wishlistData = select.val()
    const collectionId = wishlistData.split(':')[0]
    const urlCode = wishlistData.split(':')[1]
    const wishlistName = select.find(':selected').text()

    addItemToWishlistCart(button, collectionId, null, urlCode, wishlistName)
  })

  $(document).on('click', '.save-to-wishlist-cart-button', function (e) {
    e.preventDefault()
    const button = $(this)
    const collectionId = stwlite_frontend_js_vars.stwOptions.defaultWishlist?.id

    addItemToWishlistCart(button, collectionId, null)
  })

  /**
   * Handle variation changes on single product page.
   * This method triggers when variations are changed in WooCommerce.
   *
   * @since 1.0.4
   *
   * @param {Object} event     - The jQuery event object.
   * @param {Object} variation - The variation data object from WooCommerce.
   */
  function handleVariationChange(event, variation) {
    // Get the wishlist button for this product.
    const variationsForm = $(event.target).closest('form.variations_form')
    const productId = variationsForm.find('input[name="product_id"]').val() || variationsForm.data('product_id')
    const button = $('.save-to-wishlist-button[data-product-id="' + productId + '"]')

    if (button.length === 0) {
      return
    }

    // Get the selected variation ID.
    const variationId = variationsForm.find('input[name="variation_id"]').val()

    // Update button data attribute with variation ID if available.
    if (variationId && parseInt(variationId) > 0) {
      button.data('variation-id', variationId)

      // Check if this variation is already in the wishlist.
      if (parseInt(stwlite_frontend_js_vars.user_id) > 0) {
        // For logged-in users, check via AJAX.
        jQuery.ajax({
          url: stwlite_frontend_js_vars.ajax_url,
          type: 'POST',
          data: {
            action: 'stwlite_check_product_in_wishlist',
            nonce: stwlite_frontend_js_vars.nonce,
            product_id: productId,
            variation_id: variationId
          },
          success: function (response) {
            if (response.status === 'success' && response.exists === true) {
              button.addClass('stw-active')
              button.find('.stw-text-label').text(stwlite_frontend_js_vars.buttonLabels.selectedShopWishlistLabel)
              button.removeAttr('disabled')
            } else {
              button.removeClass('stw-active')
              button.find('.stw-text-label').text(stwlite_frontend_js_vars.buttonLabels.shopWishlistLabel)
              button.removeAttr('disabled')
            }
          }
        })
      } else {
        // For guests, check localStorage.
        const storedWishlist = localStorage.getItem('stw_wishlist')
        if (storedWishlist) {
          const wishlist = JSON.parse(storedWishlist)
          const productKey = String(variationId)
          if (wishlist.hasOwnProperty(productKey)) {
            button.addClass('stw-active')
          } else {
            button.removeClass('stw-active')
          }
        } else {
          button.removeClass('stw-active')
        }
      }
    } else {
      // No variation selected, remove variation-id data attribute.
      button.removeData('variation-id')
      button.removeClass('stw-active')
    }

    // Trigger custom event for other scripts to hook into.
    $(document).trigger('stwlite_variation_changed', {
      productId: productId,
      variationId: variationId,
      variation: variation,
      button: button
    })
  }

  /**
   * Initialize variation change listeners on single product page.
   *
   * @since 1.0.4
   */
  function initVariationChangeListener() {
    // Listen to WooCommerce found_variation event — fires after all attributes are selected
    // and WooCommerce has resolved + set the variation_id hidden input.
    $(document.body).on('found_variation', '.variations_form', handleVariationChange)

    // Listen to reset_data event — fires when attributes are cleared or no matching variation exists.
    $(document.body).on('reset_data', '.variations_form', function (event) {
      handleVariationChange(event, null)
    })
  }

  // Initialize variation change listener when document is ready.
  $(document).ready(function () {
    initVariationChangeListener()
  })

  /**
   * Inject wishlist buttons into WooCommerce block cart line items.
   *
   * The block cart renders items client-side via React, so there is no PHP
   * hook equivalent to woocommerce_after_cart_item_name. PHP pre-generates the
   * button HTML for each cart item and passes it via blockCartItems. A
   * MutationObserver watches the block cart container and injects the button
   * after a.wc-block-cart-item__product-name once each row is rendered,
   * matching by the product permalink path.
   */
  ;(function () {
    const blockItems = stwlite_frontend_js_vars.blockCartItems
    if (!blockItems || !blockItems.length) return

    // Normalise a URL pathname so trailing-slash differences don't break matching.
    function normalisePath (raw) {
      try {
        return new URL(raw).pathname.replace(/\/$/, '')
      } catch (e) {
        return raw.replace(/\/$/, '')
      }
    }

    // Build normalised permalink path → button HTML lookup map
    const normalisedButtonMap = {}
    blockItems.forEach(function (item) {
      normalisedButtonMap[normalisePath(item.permalink)] = item.button_html
    })

    function injectBlockCartButtons () {
      document.querySelectorAll('.wc-block-cart-items__row').forEach(function (row) {
        // Skip rows that already have a button injected
        if (row.querySelector('.stw-wishlist-button-container')) return

        // In current WooCommerce Blocks, .wc-block-components-product-name is
        // applied directly to the <a>. Older versions wrapped the link in a
        // <div> carrying the class, so fall back to an inner <a> for compat.
        const nameWrapper = row.querySelector('.wc-block-components-product-name')
        if (!nameWrapper) return

        const nameLink = nameWrapper.tagName === 'A' ? nameWrapper : nameWrapper.querySelector('a')
        if (!nameLink) return

        const buttonHtml = normalisedButtonMap[normalisePath(nameLink.href)]
        if (!buttonHtml) return

        // Insert after the element carrying the product-name class so the button
        // sits below the name in both current (anchor) and legacy (wrapper div)
        // block-cart markup, mirroring the classic cart's woocommerce_after_cart_item_name position.
        const tmp = document.createElement('div')
        tmp.innerHTML = buttonHtml
        const buttonNode = tmp.firstElementChild
        if (buttonNode) {
          nameWrapper.after(buttonNode)
        }
      })
    }

    $(document).ready(function () {
      // Observe document.body for the full page lifetime. injectBlockCartButtons
      // is idempotent (skips rows already carrying .stw-wishlist-button-container),
      // so re-runs on unrelated mutations are cheap. This handles the cases where
      // .wc-block-cart is not yet mounted on DOMContentLoaded and where React
      // replaces the cart subtree during hydration — both without relying on a
      // timing-based setTimeout.
      const observer = new MutationObserver(injectBlockCartButtons)
      observer.observe(document.body, { childList: true, subtree: true })

      // Attempt an immediate injection for server-rendered rows already in the DOM.
      injectBlockCartButtons()
    })
  }())

  /**
   * Move the wishlist button to the top right of the product image.
   */
  if ($('#stw-button-placement-top_image_right').length > 0) {
    const button = $('#stw-button-placement-top_image_right')
    const productImage = $('.woocommerce-product-gallery')
    const clonedButton = button.clone(true)
    productImage.prepend(clonedButton)
    button.remove()
  }
})(jQuery)
