(function ($) {
	"use strict"

    $.fn.wpcbConfigurator = function( wpcb_vars ) {

		/**
		 * BASE CONFIG
		 */
		let wpcb_canvasses,
			wpcb_canvas_layers,
			wpcb_canvas_swiper,
			wpcb_model_viewer,
			wpcb_scene_sym,
			wpcb_model_objects,
			wpcb_model_object_sizes;
		// wpcb_notice_timer;

		let wpcb_wishlist_items = [];
		let wpcb_wishlist_item_ids = [];
		let wpcb_pending_calls = [];
		let wpcb_visual_open_calls = 0;
		let wpcb_repeater_items = [];
		let wpcb_visible_nodes = wpcb_vars.base_nodes;

		let xValues = []; let yValues = []; let zValues = [];

		// let wpcb_hidden_nodes = [];
		// let wpcb_hidden_hotspots = [];
		let wpcb_visible_hotspots = wpcb_vars.base_hotspots;
		let wpcb_model_object_pos = {};

		let wpcb_base_configuration = {
			configurator_id: parseInt(wpcb_vars.configurator_id),
			product_id: wpcb_vars.product_id,
			product_stock: wpcb_vars.product_stock,
			allow_backorder: wpcb_vars.product_backorder,
			name: wpcb_vars.product_name,
			renders: [],
			canvasses: {},
			selection: {},
			totals: {
				product_price: 0,
				product_qty: 1,
				option_price: 0,
				total_price: 0,
			},
			step: {
				visited: 1,
				active: 1,
			}
		};

		let wpcb_configuration = {
			...wpcb_base_configuration
		};

		let wpcb_id = wpcb_configuration.configurator_id;

		let controlSettings = {
			transparentCorners: false,
			cornerColor: wpcb_vars.base_background,
			cornerStrokeColor: wpcb_vars.base_color,
			borderColor: wpcb_vars.base_color,
			cornerSize: 12,
			padding: 9,
			cornerStyle: wpcb_vars.border_circle ? 'circle' : 'box',
			borderDashArray: [3, 9]
		};

		let slickConfiguration = {
			slidesToShow: 1,
			slidesToScroll: 1,
			infinite: false,
			draggable: false,
			swipe: false,
			swipeToSlide: false,
			touchMove: false,
			fade: true,
			arrows: true,
			prevArrow: '<button type="button" class="slick-prev"><img src="' + wpcb_vars.icon_arrow + '" alt="slick arrow prev" width="24" height="24"></button>',
			nextArrow: '<button type="button" class="slick-next"><img src="' + wpcb_vars.icon_arrow + '" alt="slick arrow next" width="24" height="24"></button>'
		};
		if ($('.wpcb-is-rtl').length) {
			slickConfiguration.rtl = true;
		}

		let initialSize = {
			width: $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').data('width'),
			height: $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').data('height'),
			preserveObjectStacking: true,
			snapAngle: 15,
			selection: false,
			skipOffscreen: true,
		};

		$(document).ready(function () {

			let iOS = !window.MSStream && /iPad|iPhone|iPod/.test(navigator.userAgent);
			if (iOS) {
				$('body').addClass('wpcb-ios');
			}
			
			_showLoader();

			if ($('.wpcb-template-popup [data-wpcb-id="' + wpcb_id + '"]').length && $('.site-main').length && !$('.wpcb-template-popup.alignfull [data-wpcb-id="' + wpcb_id + '"]').length && $(window).width() > 991) {
				$('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').width($('.site-main').width());

				let resizePopup = () => {
					$('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').width($('.site-main').width());
				}
				window.addEventListener('resize', resizePopup, false);
			}

			$('.wpcb-delete-form').remove();

			if (wpcb_vars.settings.show_leave_popup) {
				document.addEventListener('mouseout', function (e) {
					if (e.clientY < 30) {
						e.preventDefault();
						$('#wpcb-leave-popup-' + wpcb_id).addClass('active');
						$('body').addClass('wpcb-panel-active');
					}
				});
			}

			// Append WPCB ID on the fly.
			if ( $('[data-wpcb-id="' + wpcb_id + '"]').parents('form.cart').length ) {
				$('[data-wpcb-id="' + wpcb_id + '"]').parents('form.cart').attr('data-wpcb-id', wpcb_id);
			}
			if ( $('[data-wpcb-id="' + wpcb_id + '"] form.cart').length ) {
				$('[data-wpcb-id="' + wpcb_id + '"] form.cart').attr('data-wpcb-id', wpcb_id);
			}

			/**
			 * INIT OPTIONS
			 */

			let queryString = window.location.search.replace('?', '');

			if (localStorage.getItem('wpcb_wishlist_item_ids')) {
				wpcb_wishlist_item_ids = JSON.parse(localStorage.getItem('wpcb_wishlist_item_ids'));

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_wishlist',
						nonce: wpcb_vars.nonce,
						wishlist_items: wpcb_wishlist_item_ids
					},
					success: function (result) {
						// Refresh table.
						wpcb_wishlist_items = result.data.map(item => JSON.parse(item));
						_setWishlistTableHtml();
					}
				});
			}

			let waitForConfig;

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').length) {

				/**
				 * Init 3D Model Viewer
				 */

				wpcb_model_viewer = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').get(0);

				waitForConfig = new Promise(function (resolve, reject) {

					/**
					 * Wait for 3D model to load to perform our option changes.
					 */

					wpcb_model_viewer.addEventListener("load", () => {

						let center = wpcb_model_viewer.getBoundingBoxCenter();
						let size = wpcb_model_viewer.getDimensions();

						let x2 = size.x / 2;
						let y2 = size.y / 2;
						let z2 = size.z / 2;

						wpcb_scene_sym = Object.getOwnPropertySymbols(wpcb_model_viewer).find(x => x.description === "scene");
						wpcb_model_objects = [];
						wpcb_model_object_sizes = {};
						wpcb_model_viewer[wpcb_scene_sym].traverse((child) => {
							if (child.type && child.type == "Mesh") {
								wpcb_model_objects.push(child);
							}
							if (child.type && child.type == "Group") {
								wpcb_model_objects.push(child);
							}
						});

						if (wpcb_vars.base_nodes.length) {
							_updateViewerNodes();
						}

						if (wpcb_vars.base_hotspots.length) {
							_updateViewerHotspots();
						}

						if ($('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+X-Y"]').length) {
							setTimeout(function () {
								_updateViewerDimLines(center, size, x2, y2, z2);
								_renderDimLinesSVG();
							}, 500);

							wpcb_model_viewer.addEventListener('camera-change', _renderDimLinesSVG);
						}

						if (queryString && (queryString.includes('wpcb_configuration=') || queryString.includes('wpcb_result=') || queryString.includes('wpcb_wi='))) {
							let optionsParts = queryString.split('&');
							let filename = '';
							for (let index in optionsParts) {
								if (optionsParts[index].includes('wpcb_configuration=')) {
									filename = optionsParts[index].replace('wpcb_configuration=', '');
								}
								if (optionsParts[index].includes('wpcb_wi=')) {
									filename = optionsParts[index].replace('wpcb_wi=', '');
								}
								if (optionsParts[index].includes('wpcb_result=')) {
									filename = optionsParts[index].replace('wpcb_result=', '');
								}
							}

							$.ajax({
								type: 'post',
								url: wpcb_vars.ajax_url,
								data: {
									action: 'wpcb_get_configuration_file',
									nonce: wpcb_vars.nonce,
									filename: filename,
								},
								success: function (configuration) {
									$(wpcb_model_viewer).removeClass("wpcb-loading");

									if (queryString.includes('wpcb_result=')) {
										$('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').remove();
									}

									resolve(configuration);
								}
							});
							// } else if (queryString && queryString.includes('wpcb_wi=')) {

							// 	let optionsPart = queryString.split('&')[0];
							// 	let current_item_id = optionsPart.replace('?wpcb_wi=', '');
							//  let filename = optionsPart.replace('?wpcb_wi=', '');

							// 	let configuration = wpcb_wishlist_items.find((wi) => wi.wishlist_id === current_item_id );

							// 	$(wpcb_model_viewer).removeClass("wpcb-loading");

							// 	if ( configuration ) {
							// 		resolve( JSON.stringify( configuration ) );
							// 	} else {
							// 		resolve( JSON.stringify( wpcb_configuration ) );
							// 	}
						} else {
							$(wpcb_model_viewer).removeClass("wpcb-loading");

							resolve(JSON.stringify(wpcb_configuration));
						}
					});
				});

			} else if (queryString && (queryString.includes('wpcb_configuration=') || queryString.includes('wpcb_result=') || queryString.includes('wpcb_wi='))) {

				let optionsParts = queryString.split('&');
				let filename = '';
				for (let index in optionsParts) {
					if (optionsParts[index].includes('wpcb_configuration=')) {
						filename = optionsParts[index].replace('wpcb_configuration=', '');
					}
					if (optionsParts[index].includes('wpcb_wi=')) {
						filename = optionsParts[index].replace('wpcb_wi=', '');
					}
					if (optionsParts[index].includes('wpcb_result=')) {
						filename = optionsParts[index].replace('wpcb_result=', '');
					}
				}

				waitForConfig = $.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_configuration_file',
						nonce: wpcb_vars.nonce,
						filename: filename,
					}
				});

				// } else if ( queryString && queryString.includes('wpcb_wi=')) {

				// 	let optionsPart = queryString.split('&')[0];
				// 	let current_item_id = optionsPart.replace('?wpcb_wi=', '');
				// 	let configuration = wpcb_wishlist_items.find((wi) => wi.wishlist_id === current_item_id );

				// 	if ( configuration ) {
				// 		let waitForConfig = Promise.resolve(
				// 			JSON.stringify( configuration )
				// 		);
				// 	} else {
				// 		let waitForConfig = Promise.resolve(
				// 			JSON.stringify( wpcb_configuration )
				// 		);
				// 	}

			} else {

				/**
				 * Init 2D viewer
				 */
				waitForConfig = Promise.resolve(
					JSON.stringify(wpcb_configuration)
				);
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length) {

				/**
				 * Init 2D canvasses
				 */

				wpcb_canvasses = {};
				wpcb_canvas_layers = new Array();

				for (let wpcb_index = 1; wpcb_index <= $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length; wpcb_index++) {

					wpcb_canvasses[wpcb_index] = new fabric.Canvas('wpcb-configurator-gallery-canvas-' + wpcb_id + '-' + wpcb_index, initialSize);
					wpcb_canvas_layers[wpcb_index] = {};

					// Load base image
					let parent_config = {
						id: 'base-image',
						objectType: 'baseimage',
						selectable: false,
						hoverCursor: wpcb_canvasses[wpcb_index].defaultCursor,
						// hasControls: false,
						// hasBorders: false,
						// lockMovementX: true,
						// lockMovementY: true,
						// lockScalingX: true,
						// lockScalingY: true,
						// ...controlSettings
					};

					_addCanvasImage($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image[data-slot="' + wpcb_index + '"]').data('image'), parent_config, wpcb_index, 0);
				}

				_resizeGalleryCanvas();

				window.addEventListener('resize', _resizeGalleryCanvas, false);

				wpcb_canvas_swiper = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__slick').slick(slickConfiguration);
			}

			_renderConfigurationGallery();

			_renderAdvancedFields();

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-cart-hidden').remove();

			/**
			 * Initial load
			 */

			waitForConfig.then(function (config) {
				if ('' == config || 'null' == config.data || '' == config.data) {
					// No data received. Reload 'normal' configurator.
					window.location.href = window.location.origin + window.location.pathname;
				}

				if (config.data) {
					wpcb_configuration = JSON.parse(config.data);
				} else {
					wpcb_configuration = JSON.parse(config);
				}

				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-repeater-main').length) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-repeater-main').each(function (index, repeater) {
						_initializeRepeater($(repeater));
					});
				}

				if (Object.keys(wpcb_configuration.selection).length) {
					_setSelectedConfigurationOptions(wpcb_configuration.selection)
				} else {
					_setDefaultConfigurationOptions(true);
				}

				_updateFormFieldDisplay();

				_setActiveStep( true );

				$(document).find('.wpcb-configurator-tab-list').each(function (index, tablist) {
					// Make first tab active
					$(tablist).find('li:first-child a').click();
				});

				_removeLoader();

				let queryString = window.location.search;
				if (queryString && queryString.includes('wpcb_result=')) {
					_refreshTotals();
					if ($('.wpcb-configurator-review.wpcb-template-popup[data-wpcb-id="' + wpcb_id + '"]').length) {
						$('.wpcb-configurator-review.wpcb-template-popup[data-wpcb-id="' + wpcb_id + '"]').addClass('active');
					}
				}
			});

			/**
			 * Change handlers
			 */

			$(document).on('keydown', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-option[aria-role="button"]', function (e) {
				if (e.key === 'Enter' || e.key === ' ') {
					e.preventDefault();
					$(e.target).click();
				}
			});

			$(document).on('change', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-option-form-select .wpcb-select', function (e) {
				e.preventDefault();

				let attributes = {};
				$(this).closest('.wpcb-option-form').find('select').each(function (key, select) {
					attributes[$(select).attr('name')] = $(select).val();
				});

				let $option = $(this).closest('.wpcb-option');
				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_variation_product',
						nonce: wpcb_vars.nonce,
						product: $(this).closest('.wpcb-option-form').data('product_id'),
						attributes: attributes,
					},
					success: function (response) {
						let result = JSON.parse(response);
						_updateProductVariationDisplay(result, $option);
					},
					error: function (error) {
						console.log(error);
					}
				});
			});

			$(document).on('change', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-option:not(.wpcb-option-type-template) input, [data-wpcb-id="' + wpcb_id + '"] .wpcb-dropdown:not(.wpcb-option-type-template) select', function (e) {
				if ( $(this).is(':disabled') ) {
					return;
				}

				$(this).addClass('wpcb-change');

				_updateConfiguration($(this).closest('.wpcb-configurator-field'), 'option');

				_updateFormFieldDisplay();

				if (!wpcb_vars.settings.disable_urls && !_hasLoader()) {
					let option = false;

					if ($(this).closest('.wpcb-dropdown').length) {
						option = $(this).find('option:selected');
					} else {
						option = $(this).is(':checked') ? $(this) : false;
					}

					if (option && $(option).data('url')) {
						if (wpcb_vars.settings.new_tab_urls) {
							window.open($(option).data('url'), 'blank');
						} else {
							window.location.href = $(option).data('url');
						}
					}
				}

				if ($(this).closest('.wpcb-dropdown').length) {
					_updateCanvasView($(this).closest('.wpcb-configurator-field'), $(this).find('option:selected'));
					_setOptionSummary($(this).find('option:selected'));

					if (wpcb_vars.settings.track_options) {
						$(this).find('option').attr('data-selected', '');
						$(this).find('option:selected').attr('data-selected', 'selected');
					}
				} else {
					_updateCanvasView($(this).closest('.wpcb-configurator-field'), $(this));
				}

				_validateConfigurationField($(this).closest('.wpcb-configurator-field'), 'option', $(this));

				if ($(this).siblings('.wpcb-option-summary').length) {
					_setOptionSummary($(this));
				}

				if ($(this).closest('.wpcb-collapsible').length) {
					let group_id = 'field-' + $(this).closest('.wpcb-configurator-field').data('id');

					if ($(this).parents('.wpcb-configurator-field-group[data-single-price]').length || $(this).parents('.wpcb-configurator-field-group[data-single-value]').length) {
						let $parent_wrapper = $(this).parents('.wpcb-configurator-field-group');
						group_id = 'group-' + $parent_wrapper.data('id');
					}

					let value_html = _getConfigurationCollapsibleOptionSummary($(this), group_id);
					$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').html(value_html);

					if (wpcb_vars.settings.collapse_attributes && !_hasLoader()) {
						// Auto collapse on value select.
						$(this).closest('.wpcb-collapsible').addClass('wpcb-collapsed');
					}
				}
				else if ($(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').length) {

					let group_id = 'field-' + $(this).closest('.wpcb-configurator-field').data('id');
					let field_id = false;

					if ($(this).parents('.wpcb-configurator-field-group[data-single-price]').length || $(this).parents('.wpcb-configurator-field-group[data-single-value]').length) {
						let $parent_wrapper = $(this).parents('.wpcb-configurator-field-group');
						group_id = 'group-' + $parent_wrapper.data('id');
						field_id = $(this).closest('.wpcb-configurator-field').data('id');
					} else if ($(this).closest('[data-repeater-list]').length) {
						group_id = 'repeater-' + $(this).closest('[data-repeater-list]').data('repeater-list');
						field_id = $(this).closest('.wpcb-configurator-field').data('id');
					}

					let value_html = _getConfigurationFieldSummary($(this), group_id, field_id);

					$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').html(value_html);

				}

				if (wpcb_vars.settings.track_options) {
					if (!$(this).closest('.wpcb-dropdown').length) {
						if ($(this).is(':checked')) {
							$(this).attr('checked', 'checked');
						} else {
							$(this).attr('checked', false);
						}
					}
				}

				if ($(this).parents('.wpcb-configurator-panel').length) {
					if ($(this).is(':checked')) {
						let thumbnail = '';
						if ($(this).closest('.wpcb-option').find('.wpcb-swatch').length) {
							thumbnail = $(this).closest('.wpcb-option').find('.wpcb-swatch').html();
						}
						if ($(this).closest('.wpcb-option').find('.wpcb-option-thumbnail').length) {
							thumbnail = $(this).closest('.wpcb-option').find('.wpcb-option-thumbnail').html();
						}
						let note = $(this).closest('.wpcb-option').find('.wpcb-option-note').length ? $(this).closest('.wpcb-option').find('.wpcb-option-note').html() : '';

						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-thumbnail').html(thumbnail);
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-label').html($(this).data('label'));
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-note').html(note);
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-totals').html($(this).closest('.wpcb-option').find('.wpcb-option-totals').html());
					} else {
						_handleConfiguratorFilterResetAction($(this).closest('.wpcb-configurator-field-filter-main'));
					}

					$(this).closest('.wpcb-configurator-field-filter').find('.wpcb-configurator-panel').removeClass('active');
					$('body').removeClass('wpcb-panel-active');
				}

				if ($(this).parents('.wpcb-configurator-popup').length) {
					if ($(this).is(':checked')) {
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-thumbnail').html($(this).closest('.wpcb-option').find('.wpcb-swatch').html());
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-label').html($(this).data('label'));
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-note').html($(this).closest('.wpcb-option').find('.wpcb-option-note').html());
						$(this).closest('.wpcb-configurator-field-filter-main').find('.wpcb-filter-item-totals').html($(this).closest('.wpcb-option').find('.wpcb-option-totals').html());
					}

					$(this).parents('.wpcb-configurator-popup').removeClass('active');
					$('body').removeClass('wpcb-panel-active');
				}

				if ($(this).closest('.wpcb-configurator-field-filter').length && $(this).is(':checked')) {
					$(this).closest('.wpcb-configurator-field-filter').find('.wpcb-configurator-filter-clear-action').removeClass('hidden');
				}

				$(this).removeClass('wpcb-change');
			});

			$(document).on('change', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-option-type-template input, [data-wpcb-id="' + wpcb_id + '"] .wpcb-option-type-template select', function (e) {
				if ( $(this).is(':disabled') ) {
					return;
				}

				if ( $(this).is(':checked') ) {
					var template = $(this).closest('label').data('template').toString().replaceAll("'", '"');
					if ( template ) {
						var template = JSON.parse(template);
						template.forEach(function(select_group, key) {
							_setSelectedFieldOptions(select_group);
						});
					} else {
						_handleConfiguratorResetAction();
					}

					_updateConfiguration($(this).closest('.wpcb-configurator-field'), 'option');

					_updateFormFieldDisplay();

					if ($(this).closest('.wpcb-dropdown').length) {
						_setOptionSummary($(this).find('option:selected'));

						if (wpcb_vars.settings.track_options) {
							$(this).find('option').attr('data-selected', '');
							$(this).find('option:selected').attr('data-selected', 'selected');
						}
					}

					if ($(this).siblings('.wpcb-option-summary').length) {
						_setOptionSummary($(this));
					}

					if ($(this).closest('.wpcb-collapsible').length) {
						let group_id = 'field-' + $(this).closest('.wpcb-configurator-field').data('id');

						if ($(this).parents('.wpcb-configurator-field-group[data-single-price]').length || $(this).parents('.wpcb-configurator-field-group[data-single-value]').length) {
							let $parent_wrapper = $(this).parents('.wpcb-configurator-field-group');
							group_id = 'group-' + $parent_wrapper.data('id');
						}

						let value_html = _getConfigurationCollapsibleOptionSummary($(this), group_id);
						$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').html(value_html);

						if (wpcb_vars.settings.collapse_attributes && !_hasLoader()) {
							// Auto collapse on value select.
							$(this).closest('.wpcb-collapsible').addClass('wpcb-collapsed');
						}
					}
					else if ($(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').length) {
						let group_id = 'field-' + $(this).closest('.wpcb-configurator-field').data('id');
						let field_id = false;

						if ($(this).parents('.wpcb-configurator-field-group[data-single-price]').length || $(this).parents('.wpcb-configurator-field-group[data-single-value]').length) {
							let $parent_wrapper = $(this).parents('.wpcb-configurator-field-group');
							group_id = 'group-' + $parent_wrapper.data('id');
							field_id = $(this).closest('.wpcb-configurator-field').data('id');
						} else if ($(this).closest('[data-repeater-list]').length) {
							group_id = 'repeater-' + $(this).closest('[data-repeater-list]').data('repeater-list');
							field_id = $(this).closest('.wpcb-configurator-field').data('id');
						}

						let value_html = _getConfigurationFieldSummary($(this), group_id, field_id);

						$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').html(value_html);
					}
				}
			});

			if (wpcb_vars.settings.uncheck_options) {
				// Allow uncheck optional options
				$(document).on('click', '.wpcb-option:not(.wpcb-option-type-product):not(.wpcb-option-out-of-stock)', function () {
					if ($(this).find('input').is(':checked')) {
						$(this).find('input').prop('checked', false);
						$(this).find('input').attr('checked', false);
					} else {
						$(this).find('input').prop('checked', true);
						$(this).find('input').attr('checked', true);
					}
					$(this).find('input').trigger('change');
					return false;
				});
			}

			let quantitySelector = _getConfigurationQuantitySelector();
			$(document).on('change', quantitySelector, function () {
				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-table') || $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-option input[data-price-table]').length) {
					if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-option input[data-price-table]').length) {
						let fieldIds = [];
						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-option input[data-price-table]').each(function (index, input) {
							fieldIds[$(input).closest('.wpcb-configurator-field').data('id')] = input;
						});

						fieldIds.forEach((input, id) => {
							if ($(input).attr('type') == 'radio' || $(input).attr('type') == 'checkbox') {
								_updateConfiguration($('.wpcb-configurator-field[data-id="' + id + '"]'), 'option');
							} else {
								_updateConfiguration($('.wpcb-configurator-field[data-id="' + id + '"]'), 'input');
							}
						});
					}

					_updateConfiguration($(this), 'input');
				} else {
					_refreshTotals();
				}
			});

			$(document).on('input', '.wpcb-option-product-input input[type=number]', function () {
				if ($(this).val() && $(this).val() > 0) {
					$(this).closest('.wpcb-option').find('input:checkbox').prop('checked', true);
					$(this).closest('.wpcb-option').find('input:checkbox').trigger('change');

					$(this).closest('.wpcb-option').addClass('active');
				} else {
					$(this).closest('.wpcb-option').find('input:checkbox').prop('checked', false);
					$(this).closest('.wpcb-option').find('input:checkbox').trigger('change');

					$(this).closest('.wpcb-option').removeClass('active');
				}

				if (wpcb_vars.settings.track_options) {
					$(this).attr('value', $(this).val());
				}

				if ($(this).closest('.wpcb-collapsible').length) {
					let group_id = 'field-' + $(this).closest('.wpcb-configurator-field').data('id');
					
					if ($(this).parents('.wpcb-configurator-field-group[data-single-price]').length || $(this).parents('.wpcb-configurator-field-group[data-single-value]').length) {
						let $parent_wrapper = $(this).parents('.wpcb-configurator-field-group');
						group_id = 'group-' + $parent_wrapper.data('id');
					}

					let value_html = _getConfigurationCollapsibleOptionSummary(group_id);
					$(this).closest('.wpcb-collapsible').find('.wpcb-configurator-field-value').html(value_html);
				}
			});

			$(document).on('click', '.wpcb-option-product-input', function (e) {
				e.preventDefault();
			});

			$(document).on('click', '.wpcb-configurator-text-input input, .wpcb-configurator-number-input input, .wpcb-option-product-input input', function () {
				$(this).select();
			});

			$(document).on('click', '.wpcb-configurator-gallery model-viewer .hotspot', function () {
				$(this).toggleClass("active");
			});

			/**
			 * Input handlers
			 */

			let wpcbTypingTimer;
			let wpcbTypingInterval = 200;

			$(document).on('input', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field input, [data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field textarea', function () {
				let is_resetting = false;
				if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-reset-action').length && $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-reset-action').hasClass('loading') ) {
					is_resetting = true;
				}

				if ( _hasLoader() || is_resetting ) {
					// On load, directly parse changes
					_handleInputChange( $(this) );

				} else {
					// Loaded, wait for user input
					clearTimeout(wpcbTypingTimer);

					let $this = $(this);

					wpcbTypingTimer = setTimeout(function () {

						_handleInputChange($this);

					}, wpcbTypingInterval);
				}
			});

			$(document).on('input', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-range-slider input[type="range"]', function (e) {
				_handleRangeInputChange(e);
			});

			$(document).on('change', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-file-upload input', async function (e) {
				// Sync image previews.
				let input = this;

				if (input.files && input.files[0]) {
					// File uploaded
					if ($(input).closest('.wpcb-configurator-field').find('.wpcb-error-note').length) {
						$(input).closest('.wpcb-configurator-field').find('.wpcb-error-note').remove();
					}

					if ($(input).data('max-size')) {
						let mb = parseFloat($(input).data('max-size'));
						let allowedMB = mb * 1024 * 1024;
						let filesize = input.files[0].size;

						if (filesize > allowedMB) {
							$(input).attr('value', ''); // undo file.
							$(input).closest('.wpcb-configurator-field').append(
								'<small class="wpcb-error-note">' + wpcb_vars.error_file.replace('{size}', mb) + '</small>'
							);
							return false;
						}
					}

					if ($(input).data('min-width') || $(input).data('max-width') || $(input).data('min-height') || $(input).data('max-height')) {
						let minWidth = $(input).data('min-width') ?? 0;
						let maxWidth = $(input).data('max-width') ?? 999999;
						let minHeight = $(input).data('min-height') ?? 0;
						let maxHeight = $(input).data('max-height') ?? 999999;

						let result = await _validateImageDimensions(input.files[0], minWidth, maxWidth, minHeight, maxHeight);
						if (!result.ok) {
							input.value = "";
							$(input).closest('.wpcb-configurator-field').append(
								'<small class="wpcb-error-note">' + wpcb_vars.error_file_dims + '</small>'
							);
							return false;
						}
					}

					let reader = new FileReader();
					reader.onload = function (e) {
						$(input).siblings('.wpcb-input-file').find('.wpcb-input-file-image').html(`<img src="${e.target.result}" alt="${input.files[0].name}">`);
						$(input).siblings('.wpcb-input-file').find('.wpcb-input-file-name').html(`${input.files[0].name}`);
						$(input).siblings('.wpcb-input-file').addClass('active');

						_updateConfiguration($(input).closest('.wpcb-configurator-field'), 'input');

						_updateFormFieldDisplay();

						if ($(input).data('has-preview')) {
							_updateCanvasInputImage($(input), e.target.result);
						}
					}
					reader.readAsDataURL(input.files[0]);
				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-file-upload .wpcb-input-clear-file', function (e) {
				e.preventDefault();

				$(this).closest('.wpcb-input-content').find('input').val('');
				$(this).closest('.wpcb-input-content').find('.wpcb-input-file').removeClass('active');
				$(this).closest('.wpcb-input-content').find('.wpcb-input-file-image').html('');
				$(this).closest('.wpcb-input-content').find('.wpcb-input-file-name').html('');

				_updateConfiguration($(this).closest('.wpcb-configurator-field'), 'input');

				_updateFormFieldDisplay();

				_updateCanvasInputImage($(this).closest('.wpcb-input-content').find('input'), '');

				if ($(this).closest('.wpcb-input-content').find('.wpcb-button-customize-popup').length) {
					$(this).closest('.wpcb-input-content').find('.wpcb-button-customize-popup').removeClass('active');
				}
			});

			/**
			 * Variation product support
			 */

			$( document ).on( "show_variation", ".variations_form", function ( event, variation ) {
				if ( $('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').length ) {
					$('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').val( variation.variation_id );
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] form.cart').prepend('<input type="hidden" name="variation_id" value="' + variation.variation_id + '">');
				}
				wpcb_vars.product_price = variation.display_price;
				wpcb_configuration.variation_id = variation.variation_id;
				wpcb_configuration.product_stock = variation.stock_qty;
				wpcb_configuration.allow_backorder = variation.backorders;

				_renderConfigurationGallery();
				_refreshTotals();
			});

			$( document ).on( "hide_variation", ".variations_form", function (e) {
				if ( $('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').length ) {
					$('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').val(0); // clear variation id
				}
				wpcb_vars.product_price = wpcb_vars.product_base_price;
				wpcb_configuration.product_stock = wpcb_vars.product_stock;
				wpcb_configuration.allow_backorder = wpcb_vars.product_backorder;

				_renderConfigurationGallery();
				_refreshTotals();
			});

			/**
			 * Click handlers
			 */

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-prev', function (e) {
				e.preventDefault();

				if (wpcb_configuration.step.active > 1) {
					wpcb_configuration.step.active--;
				}
				_setActiveStep();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-next', function (e) {
				e.preventDefault();

				if (wpcb_configuration.step.active >= $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').length) {
					return;
				}

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				wpcb_configuration.step.visited++;
				wpcb_configuration.step.active++;

				_setActiveStep();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button', function (e) {
				e.preventDefault();

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				wpcb_configuration.step.active = $(this).data('step-index');

				_setActiveStep();
			});

			$(document).on('click', '.wpcb-configurator-accordeon-summary', function (e) {
				$(this).closest('.wpcb-configurator-accordeon').toggleClass('wpcb-collapsed');
			});

			$(document).on('click', '.wpcb-configurator-field.wpcb-collapsible .wpcb-configurator-field-header', function (e) {
				if ($(e.target).hasClass('.wpcb-view-field-details') || $(e.target).parents('.wpcb-view-field-details').length) {
					return;
				}

				$(this).closest('.wpcb-configurator-field').toggleClass('wpcb-collapsed');

				if ($(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').length) {
					$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').toggleClass('active');
				}
			});

			$(document).on('click', '.wpcb-configurator-field-filter.wpcb-collapsible .wpcb-configurator-field-filter-header', function (e) {
				$(this).closest('.wpcb-configurator-field-filter').toggleClass('wpcb-collapsed');
			});

			$(document).on('click', '.wpcb-configurator-field-group.wpcb-collapsible .wpcb-configurator-field-group-column-header', function (e) {
				if ($(e.target).hasClass('.wpcb-view-field-group-details') || $(e.target).parents('.wpcb-view-field-group-details').length) {
					return;
				}
				$(this).closest('.wpcb-configurator-field-group').toggleClass('wpcb-collapsed');
			});

			$(document).on('click', '.wpcb-configurator-repeater-item-action a', function (e) {
				$(this).closest('.wpcb-configurator-repeater-item').toggleClass('wpcb-collapsed');
			});

			$(document).on('click', '.wpcb-configurator-tab-list a', function (e) {
				e.preventDefault();

				$(this).closest('.wpcb-configurator-tab-list').find('li').each(function (index, tabitem) {
					$(tabitem).find('a').removeClass('active');
					$($(tabitem).find('a').attr('href')).removeClass('active');
				});

				$(this).addClass('active');
				$($(this).attr('href')).addClass('active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item', function (e) {
				e.preventDefault();

				if (wpcb_model_viewer) {

					if ($(this).data("model-target")) {
						$(wpcb_model_viewer).attr("camera-target", $(this).data("model-target"));
					} else {
						$(wpcb_model_viewer).attr("camera-target", $(wpcb_model_viewer).data("target") ?? "");
					}

					if ($(this).data("model-orbit")) {
						$(wpcb_model_viewer).attr("camera-orbit", $(this).data("model-orbit"));
					} else {
						$(wpcb_model_viewer).attr("camera-orbit", $(wpcb_model_viewer).data("orbit") ?? "");
					}

					if ($(this).data("model-view")) {
						$(wpcb_model_viewer).attr("field-of-view", $(this).data("model-view"));
					} else {
						$(wpcb_model_viewer).attr("field-of-view", $(wpcb_model_viewer).data("view") ?? "");
					}

					if ($(this).data("env")) {
						$(wpcb_model_viewer).attr("environment-image", $(this).data("env"));
						if ($(wpcb_model_viewer).data("skybox-image")) {
							$(wpcb_model_viewer).attr("skybox-image", $(this).data("env"));
						}
					} else {
						$(wpcb_model_viewer).attr("environment-image", $(wpcb_model_viewer).data("env-image") ?? "");
						$(wpcb_model_viewer).attr("skybox-image", $(wpcb_model_viewer).data("skybox-image") ?? "");
					}

					if ($(this).data("exposure")) {
						$(wpcb_model_viewer).attr("exposure", $(this).data("exposure"));
					} else {
						$(wpcb_model_viewer).attr("exposure", "1");
					}

				} else {

					let index = $(this).attr('data-slot');
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__slick').slick('slickGoTo', (index - 1));

				}

				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').removeClass('active').addClass('disabled');
				$(this).addClass('active');

				setTimeout(function () {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').removeClass('disabled');
				}, 500);

			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery .slick-arrow', function (e) {
				let currentSlide = wpcb_canvas_swiper.slick('slickCurrentSlide');
				let slot = (currentSlide ? currentSlide : 0) + 1;

				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').removeClass('active');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item[data-slot="' + slot + '"]').addClass('active');
			});

			/**
			 * Gallery actions
			 */

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.fullscreen', function (e) {
				e.preventDefault();
				toggleFullScreen($('.wpcb-configurator-visuals[data-wpcb-id="' + wpcb_id + '"]').get(0));
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.capture-image', function (e) {
				e.preventDefault();

				let dataUrl = '';
				if (typeof wpcb_canvasses !== 'undefined' && $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length) {
					let slide = 1;
					if ($('[data-wpcb-id="' + wpcb_id + '"] .slick-current').length) {
						slide = $('[data-wpcb-id="' + wpcb_id + '"] .slick-current').data('slot');
					}
					dataUrl = _getCanvasDataUrl(slide);
				}
				if (typeof wpcb_model_viewer !== 'undefined') {
					if ('jpg' == wpcb_vars.settings.image_export) {
						dataUrl = wpcb_model_viewer.toDataURL('jpg');
					} else {
						dataUrl = wpcb_model_viewer.toDataURL('png');
					}
				}

				if ('' == dataUrl) {
					return;
				}

				let title = wpcb_vars.product_name.toString().replaceAll(' ', '-').toLowerCase();
				let anchor = document.createElement('a');
				anchor.style.display = 'none';
				document.body.appendChild(anchor);
				anchor.href = dataUrl;
				anchor.download = title + '.png';
				anchor.click();
				window.setTimeout(() => {
					document.body.removeChild(anchor);
				}, 500);

				_updateConfigurationAnalytics('capture');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.show-viewer-info', function (e) {
				e.preventDefault();
				$('#wpcb-viewer-info-popup-' + wpcb_id).addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.show-qr-code', function (e) {
				e.preventDefault();
				_handleConfiguratorShareAction(wpcb_configuration, true);
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.reset-view', function (e) {
				e.preventDefault();
				if ( wpcb_model_viewer ) {
					$(wpcb_model_viewer).addClass("view-changed");
					_updateViewerCameraSettings( $(e.target) );

					if ($('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+X-Y"]').length) {
						let center = wpcb_model_viewer.getBoundingBoxCenter();
						let size = wpcb_model_viewer.getDimensions();
						let x2 = size.x / 2;
						let y2 = size.y / 2;
						let z2 = size.z / 2;

						setTimeout(function () {
							_updateViewerDimLines(center, size, x2, y2, z2);
							_renderDimLinesSVG();
						}, 100);
					}
				} else {
					if (!_hasLoader()) {
						let show_slide = 0;

						if (wpcb_canvas_swiper) {
							wpcb_canvas_swiper.slick('slickGoTo', show_slide);
						}

						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').removeClass('active');
						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item[data-slot="' + (show_slide + 1) + '"]').addClass('active');
					}
				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.toggle-dims', function (e) {
				e.preventDefault();
				if ( wpcb_model_viewer ) {
					// 3D dimensions
					$(wpcb_model_viewer).find('.wpcb-dim').toggle();
					$(wpcb_model_viewer).find('.wpcb-dim-lines').toggle();
				} else {
					// 2D dimensions layer
					$(this).toggleClass('active');

					$('[data-wpcb-id="' + wpcb_id + '"] input[data-is-dim]').each(function(index,dim) {
						if ($(dim).data('has-preview')) {
							_updateCanvasView($(dim).closest('.wpcb-configurator-field'), $(dim));
						}
					});

					_renderConfigurationGallery();
				}
			});

			$(document).on('click', '.wpcb-configurator-filter-clear-action', function (e) {
				e.preventDefault();
				let filter = $(this).closest('.wpcb-configurator-field-filter-main');
				_handleConfiguratorFilterResetAction(filter);
				_updateConfiguration($(filter).closest('.wpcb-configurator-field-filter').find('.wpcb-configurator-field'), 'field');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-reset-action', function (e) {
				e.preventDefault();
				$(this).addClass('loading');
				_handleConfiguratorResetAction();
				$(this).removeClass('loading');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-save-action', function (e) {
				e.preventDefault();
				_handleConfiguratorShareAction(wpcb_configuration);
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-leave-popup-save-action', function (e) {
				e.preventDefault();
				_handleConfiguratorShareAction(wpcb_configuration);
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-pdf-action', async function (e) {
				e.preventDefault();

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				_showLoader();

				let queryString = window.location.search.replace('?', '');
				let optionsParts = queryString.split('&');
				let filename = '';
				for (let index in optionsParts) {
					if (optionsParts[index].includes('wpcb_configuration=')) {
						filename = optionsParts[index].replace('wpcb_configuration=', '');
					}
				}
				let product_url = window.location.origin + window.location.pathname;

				if ($('#wpcb-mail-pdf-popup-' + wpcb_id).length) {

					let $button = $(this);
					let configuration = structuredClone(wpcb_configuration);
					configuration.url = product_url;

					// Capture image
					configuration = await _getCanvasRenders(configuration, false, true);
					configuration.totals = _calculateTotals();

					let config_data = JSON.stringify(configuration);
					let config_utf8 = new TextEncoder().encode(config_data);
					let binary = "";
					for (let i = 0; i < config_utf8.length; i++) {
						binary += String.fromCharCode(config_utf8[i]);
					}
					config_data = btoa(binary);

					$.ajax({
						type: 'post',
						url: wpcb_vars.ajax_url,
						data: {
							action: 'wpcb_get_pdf_file_path',
							nonce: wpcb_vars.nonce,
							filename: filename,
							configuration: config_data,
						},
						success: function (data) {
							let pdf_url = data.file_url;
							let pdfname = data.filename;

							if ($('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').length) {
								$('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').attr('href', pdf_url);
								$('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').html('<img src="' + wpcb_vars.icon_pdf + '"><span>' + pdfname + '</span>');
							}

							$('#wpcb-mail-pdf-popup-' + wpcb_id).find('input[name="pdf_link"]').val(pdf_url);
							$('#wpcb-mail-pdf-popup-' + wpcb_id).addClass('active');
							$('body').addClass('wpcb-panel-active');

							if ($button && !$button.hasClass('wpcb-tracked')) {
								// Prevent excessive duplicate tracking
								_updateConfigurationAnalytics('pdf', { url: pdf_url });
								_updateConfigurationStatistics(configuration.selection);

								$button.addClass('wpcb-tracked');
							}

							_removeLoader();
						}
					});

				} else {

					_handleConfiguratorPdfDownloadAction( $(this) );

				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-wishlist-action', function (e) {
				e.preventDefault();
				// Show table.
				_setWishlistTableHtml();
				$('#wpcb-wishlist-' + wpcb_id).addClass('active');
				$('#wpcb-account-wishlist-' + wpcb_id).addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-add-to-wishlist', function (e) {
				e.preventDefault();

				_handleConfiguratorWishlistAction();

				if ($('#wpcb-account-wishlist-' + wpcb_id).length) {
					// Add to account wishlist.
					$.ajax({
						type: 'post',
						url: wpcb_vars.ajax_url,
						data: {
							action: 'wpcb_save_wishlist_for_user',
							nonce: wpcb_vars.nonce,
							wishlist: JSON.stringify(wpcb_wishlist_item_ids),
						},
						success: function (response) {
							// done.
						},
						error: function (error) {
							console.error(error);
						}
					});
				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-wishlist-table .wpcb-wishlist-load-item', function (e) {
				e.preventDefault();

				let current_item_id = $(this).data('id');

				_showLoader();
				// let configuration = wpcb_wishlist_items.find((wi) => wi.wishlist_id === current_item_id );

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_configuration_file',
						nonce: wpcb_vars.nonce,
						filename: current_item_id,
					},
					success: function (result) {
						if (result.data) {
							let configuration = JSON.parse(result.data);

							let current_page_url = location.protocol + '//' + location.host + location.pathname;
							if (current_page_url !== configuration.wishlist_page_url) {
								window.location.href = configuration.wishlist_page_url + '?wpcb_wi=' + configuration.wishlist_id;
								return;
							}

							wpcb_configuration = structuredClone(configuration);
							if (Object.keys(configuration.selection).length) {
								_setSelectedConfigurationOptions(configuration.selection);
							}

							// Close panel
							$('#wpcb-wishlist-' + wpcb_id).removeClass('active');
							$('#wpcb-account-wishlist-' + wpcb_id).removeClass('active');
							$('body').removeClass('wpcb-panel-active');

							_removeLoader();
						}
					},
					error: function (error) {
						console.error(error);
					}
				});
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-wishlist-table .wpcb-wishlist-remove-item', function (e) {
				e.preventDefault();

				let current_item_id = $(this).data('id');
				wpcb_wishlist_item_ids = wpcb_wishlist_item_ids.filter((wi_id) => wi_id !== current_item_id);
				wpcb_wishlist_items = wpcb_wishlist_items.filter((item) => item.wishlist_id !== current_item_id);

				localStorage.setItem('wpcb_wishlist_item_ids', JSON.stringify(wpcb_wishlist_item_ids));

				// Add to account wishlist.
				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_save_wishlist_for_user',
						nonce: wpcb_vars.nonce,
						wishlist: JSON.stringify(wpcb_wishlist_item_ids),
						remove_id: current_item_id
					},
					success: function (response) {

					},
					error: function (error) {
						console.error(error);
					}
				});

				_setWishlistTableHtml();
			});

			/**
			 * Popup mail actions
			 */

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-popup-form-link .wpcb-button', function (e) {
				e.preventDefault();

				let $form = $(this).closest('.wpcb-popup-form-link');
				if ($form.find('.wpcb-form-notice').length) {
					$form.find('.wpcb-form-notice').remove();
				}

				let error_email = wpcb_vars.email_invalid;
				let error_privacy = wpcb_vars.privacy_invalid;
				let success_delivery = wpcb_vars.email_success;
				let error_delivery = wpcb_vars.email_failed;
				let email = $form.find('input[type=email]').val();
				if (!email) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_email + '</p></div>');
					return;
				}
				if ($form.find('input[type=checkbox]').length && !$form.find('input[type=checkbox]:checked').length) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_privacy + '</p></div>');
					return;
				}

				const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
				let valid = re.test(String(email).toLowerCase());
				if (!valid) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_email + '</p></div>');
					return;
				}

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_mail_configuration_link',
						nonce: wpcb_vars.nonce,
						id: wpcb_configuration.configurator_id,
						link: $form.data('link'),
						email: email,
					},
					success: function (response) {
						if ($form.find('.wpcb-form-notice').length) {
							$form.find('.wpcb-form-notice').remove();
						}
						$form.append('<div class="wpcb-form-notice wpcb-form-notice-success"><p>' + success_delivery + '</p></div>');
					},
					error: function (error) {
						if ($form.find('.wpcb-form-notice').length) {
							$form.find('.wpcb-form-notice').remove();
						}
						$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_delivery + '</p></div>');
					}
				});
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-popup-form-pdf .wpcb-button', function (e) {
				e.preventDefault();

				let $form = $(this).closest('.wpcb-popup-form-pdf');
				if ($form.find('.wpcb-form-notice').length) {
					$form.find('.wpcb-form-notice').remove();
				}

				let error_email = wpcb_vars.email_invalid;
				let success_delivery = wpcb_vars.email_success;
				let error_delivery = wpcb_vars.email_failed;
				let email = $form.find('input[type=email]').val();
				if (!email) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_email + '</p></div>');
					return;
				}
				if ($form.find('input[type=checkbox]').length && !$form.find('input[type=checkbox]:checked').length) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_privacy + '</p></div>');
					return;
				}

				const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
				let valid = re.test(String(email).toLowerCase());
				if (!valid) {
					$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_email + '</p></div>');
					return;
				}

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_mail_pdf_link',
						nonce: wpcb_vars.nonce,
						id: wpcb_configuration.configurator_id,
						link: $form.find('input[name="pdf_link"]').val(),
						email: email,
					},
					success: function (response) {
						if ($form.find('.wpcb-form-notice').length) {
							$form.find('.wpcb-form-notice').remove();
						}
						$form.append('<div class="wpcb-form-notice wpcb-form-notice-success"><p>' + success_delivery + '</p></div>');
					},
					error: function (error) {
						if ($form.find('.wpcb-form-notice').length) {
							$form.find('.wpcb-form-notice').remove();
						}
						$form.append('<div class="wpcb-form-notice wpcb-form-notice-error"><p>' + error_delivery + '</p></div>');
					}
				});
			});

			/**
			 * Configurator actions
			 */

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_review_configuration_button', async function (e) {
				e.preventDefault();

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				_showLoader();

				let configuration = structuredClone(wpcb_configuration);
				// configuration = await _getCanvasRenders(configuration, true);
				// configuration.totals = _calculateTotals();

				let configuration_name = '';
				if (sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id)) {
					configuration_name = sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id);
				}

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_save_configuration_file',
						nonce: wpcb_vars.nonce,
						configuration: configuration,
						configuration_name: configuration_name
					},
					success: function (data) {
						let response = JSON.parse(data);
						let product_base_url = window.location.origin + window.location.pathname;
						let options = '?wpcb_result=' + response.filename;

						_removeLoader();

						window.location.href = product_base_url + options;
					}
				});
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_download_pdf_button', async function (e) {
				e.preventDefault();

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				_showLoader();

				let queryString = window.location.search.replace('?', '');
				let optionsParts = queryString.split('&');
				let filename = '';
				for (let index in optionsParts) {
					if (optionsParts[index].includes('wpcb_configuration=')) {
						filename = optionsParts[index].replace('wpcb_configuration=', '');
					}
				}
				let product_url = window.location.origin + window.location.pathname;

				if ($('#wpcb-mail-pdf-popup-' + wpcb_id).length) {

					let $button = $(this);
					let configuration = structuredClone(wpcb_configuration);
					configuration.url = product_url;

					// No filename. Capture image
					configuration = await _getCanvasRenders(configuration, false, true);
					configuration.totals = _calculateTotals();

					let config_data = JSON.stringify(configuration);
					let config_utf8 = new TextEncoder().encode(config_data);
					let binary = "";
					for (let i = 0; i < config_utf8.length; i++) {
						binary += String.fromCharCode(config_utf8[i]);
					}
					config_data = btoa(binary);

					$.ajax({
						type: 'post',
						url: wpcb_vars.ajax_url,
						data: {
							action: 'wpcb_get_pdf_file_path',
							nonce: wpcb_vars.nonce,
							filename: filename,
							configuration: config_data,
						},
						success: function (data) {
							let pdf_url = data.file_url;
							let pdfname = data.filename;

							if ($('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').length) {
								$('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').attr('href', pdf_url);
								$('#wpcb-mail-pdf-popup-' + wpcb_id + ' .wpcb_download_pdf_link').html('<img src="' + wpcb_vars.icon_pdf + '"><span>' + pdfname + '</span>');
							}

							$('#wpcb-mail-pdf-popup-' + wpcb_id + ' input[name="pdf_link"]').val(pdf_url);
							$('#wpcb-mail-pdf-popup-' + wpcb_id).addClass('active');
							$('body').addClass('wpcb-panel-active');

							if ($button && !$button.hasClass('wpcb-tracked')) {
								// Prevent excessive duplicate tracking
								_updateConfigurationAnalytics('pdf', { url: pdf_url });
								_updateConfigurationStatistics(configuration.selection);

								$button.addClass('wpcb-tracked');
							}

							_removeLoader();
						}
					});

				} else {

					_handleConfiguratorPdfDownloadAction( $(this) );

				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_request_quote_button', async function (e) {
				e.preventDefault();

				let valid = _validateForm();
				if (!valid) {
					return;
				}

				_handleConfiguratorQuoteAction($(this).data('quote-type').split(','), $(this));
			});

			$(document).on('click', '.wpcb-configurator-form-totals .single_add_to_cart_button', async function (e) {
				let valid = _validateForm();
				if (!valid) {
					e.preventDefault();
					return false;
				}

				let configuration = structuredClone(wpcb_configuration);
				configuration = await _handleConfiguratorCartAction(configuration);

				let options = _getSimplifiedConfigurationOptions(configuration.selection);
				_updateConfigurationAnalytics('cart', {
					options: options,
					quantity: configuration.totals.product_qty,
					price: configuration.totals.total_price,
					vat: configuration.totals.total_tax
				});
			});

			var processedEvent = false;
			$('.add-request-quote-button').on('click', function (e) {
				if ( $( e.target ).parents('[data-wpcb-id="' + wpcb_id + '"]').length ) {
					if ( ! $( e.target ).hasClass('processing') ) {
						let valid = _validateForm();
						if (!valid) {
							e.preventDefault();
							return false;
						}
						if (processedEvent || target === null) {
							return false;
						}

						var target = e.target;
						target.disabled = true;
						processedEvent = true;
						e.stopPropagation();
						$(e.target).addClass('processing');
						
						let configuration = structuredClone(wpcb_configuration);
						_handleConfiguratorCartAction(configuration).then(function() {
							redispatchEvent(e, target);
						});

						let options = _getSimplifiedConfigurationOptions(configuration.selection);
						_updateConfigurationAnalytics('quote', {
							options: options,
							quantity: configuration.totals.product_qty,
							price: configuration.totals.total_price,
							vat: configuration.totals.total_tax
						});
					}
				}
			});

			var redispatchEvent = (event, target) => {
				processedEvent = false;
				target.disabled = false;
				$(target).trigger('click');
				$(target).removeClass('processing');
			};

			$('.wpcb-configurator-form-totals[data-wpcb-id="' + wpcb_id + '"] a.afrfqbt_single_page').off('click').on('click', async function (e) {
				let valid = _validateForm();
				if (!valid) {
					e.preventDefault();
					return false;
				}

				let configuration = structuredClone(wpcb_configuration);
				configuration = _handleConfiguratorCartAction(configuration);

				let options = _getSimplifiedConfigurationOptions(configuration.selection);
				_updateConfigurationAnalytics('quote', {
					options: options,
					quantity: configuration.totals.product_qty,
					price: configuration.totals.total_price,
					vat: configuration.totals.total_tax
				});
			});

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button.single_send_email_button').on('click', function () {
				if (!$(this).hasClass('wpcb-tracked')) {
					// Prevent excessive duplicate tracking
					_updateConfigurationAnalytics('email');
					_updateConfigurationStatistics(wpcb_configuration.selection);

					$(this).addClass('wpcb-tracked');
				}
			});

			/**
			 * Panels
			 */

			$(document).on('click', '.wpcb-view-option-details', function (e) {
				e.preventDefault();
				let optionId = $(this).attr('data-id');
				if ( $(this).closest('.wpcb-configurator-field').length ) {
					_setOptionPanelHtml(optionId, $(this));
					$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-panel').addClass('active');
					$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-popup').addClass('active');
				} else {
					_setOptionPanelHtml(optionId, $('input#' + optionId));
					$('input#' + optionId).closest('.wpcb-configurator-field').find('.wpcb-configurator-panel').addClass('active');
					$('input#' + optionId).closest('.wpcb-configurator-field').find('.wpcb-configurator-popup').addClass('active');
				}
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '.wpcb-view-field-details', function (e) {
				e.preventDefault();
				_setFieldPanelHtml($(this));
				$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-panel').addClass('active');
				$(this).closest('.wpcb-configurator-field').find('.wpcb-configurator-popup').addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '.wpcb-view-field-group-details', function (e) {
				e.preventDefault();
				_setFieldPanelHtml($(this));
				$(this).closest('.wpcb-configurator-field-group').find('.wpcb-configurator-field-group-panel').addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '.wpcb-view-field-filter-details', function (e) {
				if ( ! $(e.target).closest('.wpcb-view-option-details').length ) {
					e.preventDefault();
					let id = $(this).data('id');
					$(this).closest('.wpcb-configurator-field-filter').find('#' + id).addClass('active');
					$('body').addClass('wpcb-panel-active');
				}
			});

			$(document).on('click', '.wpcb-configurator-panel-close', function (e) {
				e.preventDefault();
				$(this).closest('.wpcb-configurator-panel').removeClass('active');
				if (!$('.wpcb-configurator-panel.active').length) {
					$('body').removeClass('wpcb-panel-active');
				}
			});

			$(document).on('click', '.wpcb-configurator-popup-close', function (e) {
				e.preventDefault();
				$(this).closest('.wpcb-configurator-popup').removeClass('active');
				if (!$('.wpcb-configurator-popup.active').length) {
					$('body').removeClass('wpcb-panel-active');
				}
			});

			$(document).on('click', '.wpcb_show_configuration_popup', function (e) {
				e.preventDefault();

				let id = $(this).data('wpcb-id');
				$('#wpcb-configuration-' + id).addClass('active');
				$('body').addClass('wpcb-popup-active');

				if ($('[data-wpcb-id="' + id + '"] .wpcb-configurator-gallery__slick').length) {
					// reinit slider.
					$('[data-wpcb-id="' + id + '"] .wpcb-configurator-gallery__slick').slick('unslick');
					wpcb_canvas_swiper = $('[data-wpcb-id="' + id + '"] .wpcb-configurator-gallery__slick').slick(slickConfiguration);

					// reinit canvas
					_resizeGalleryCanvas();
				}
			});

			$(document).on('click', '#wpcb_close_configuration_popup', function (e) {
				e.preventDefault();
				$('.wpcb-template-popup').removeClass('active');
				$('body').removeClass('wpcb-popup-active');
			});

			$(document.body).on('added_to_cart', function () {
				// Ajax added to cart. hide popup.
				$('.wpcb-template-popup').removeClass('active');
				$('body').removeClass('wpcb-popup-active');
				// Force fragment refresh.
				$(document.body).trigger('wc_fragment_refresh');
			});

			// Panel open / popup click.
			$(document).mouseup(function (e) {
				let panel_container = $('.wpcb-configurator-panel-inner');
				let popup_container = $('.wpcb-configurator-popup-inner');
				if (!panel_container.is(e.target) && panel_container.has(e.target).length === 0 &&
					!popup_container.is(e.target) && popup_container.has(e.target).length === 0) {
					$('.wpcb-configurator-panel').removeClass('active');
					$('.wpcb-configurator-popup').removeClass('active');
					$('body').removeClass('wpcb-panel-active');
				}
			});

			$(document).keyup(function (e) {
				if (e.key === "Escape") { // escape key maps to keycode `27`
					$('.wpcb-configurator-panel').removeClass('active');
					$('.wpcb-configurator-popup').removeClass('active');
					$('body').removeClass('wpcb-panel-active');
				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-customize-popup', function (e) {
				e.preventDefault();
				let id = $(this).attr('href');
				if ($(id).length) {
					$(id).addClass('active');
					$('body').addClass('wpcb-panel-active');

					// scale canvas to width
					_resizePanelCanvas(
						$(id).attr('id').replace('_popup', ''),
						$(id).find('.wpcb-input-field-canvas').data('width'),
						$(id).find('.wpcb-input-field-canvas').data('height'),
						$(id).find('.wpcb-input-field-canvas').width()
					);
				}
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas .wpcb-canvas-plus', function () {
				let canvasId = $(this).closest('.wpcb-input-field-canvas').find('canvas').attr('id');
				if (!wpcb_canvasses[canvasId]) {
					return;
				}
				let objects = wpcb_canvasses[canvasId].getObjects();
				if (!objects[0]) {
					return;
				}
				objects[0].scaleX = objects[0].scaleX + 0.1;
				objects[0].scaleY = objects[0].scaleY + 0.1;
				wpcb_canvasses[canvasId].renderAll();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas .wpcb-canvas-minus', function () {
				let canvasId = $(this).closest('.wpcb-input-field-canvas').find('canvas').attr('id');
				if (!wpcb_canvasses[canvasId]) {
					return;
				}
				let objects = wpcb_canvasses[canvasId].getObjects();
				if (!objects[0]) {
					return;
				}
				objects[0].scaleX = objects[0].scaleX - 0.1;
				objects[0].scaleY = objects[0].scaleY - 0.1;
				wpcb_canvasses[canvasId].renderAll();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas .wpcb-canvas-rotate', function () {
				let canvasId = $(this).closest('.wpcb-input-field-canvas').find('canvas').attr('id');
				if (!wpcb_canvasses[canvasId]) {
					return;
				}
				let objects = wpcb_canvasses[canvasId].getObjects();
				if (!objects[0]) {
					return;
				}
				objects[0].angle = objects[0].angle - 5;
				wpcb_canvasses[canvasId].renderAll();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas .wpcb-canvas-rotate-alt', function () {
				let canvasId = $(this).closest('.wpcb-input-field-canvas').find('canvas').attr('id');
				if (!wpcb_canvasses[canvasId]) {
					return;
				}
				let objects = wpcb_canvasses[canvasId].getObjects();
				if (!objects[0]) {
					return;
				}
				objects[0].angle = objects[0].angle + 5;
				wpcb_canvasses[canvasId].renderAll();
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas-actions .wpcb-button-primary', function () {
				let canvasId = $(this).closest('.wpcb-panel-content').find('.wpcb-input-field-canvas canvas').attr('id');
				let dataURL = wpcb_canvasses[canvasId].toDataURL();
				let inputId = canvasId.replace('canvas_', '');
				let $input = $('input[id="' + inputId + '"]');

				if ($input.data('material-name')) {
					// 3D model
					_createAndApplyViewerTexture($input.data('material-name'), $input.data('material-type'), dataURL);
				} else {
					// 2D image
					let positions = [1];

					if ($input.closest('.wpcb-configurator-field').data('slide-positions')) {
						positions = $input.closest('.wpcb-configurator-field').data('slide-positions').toString().split(',');
						positions = positions.map(pos => parseInt(pos));
					} else if ($input.data('slide-positions')) {
						positions = $input.data('slide-positions').toString().split(',');
						positions = positions.map(pos => parseInt(pos));
					}

					for (let pos_index in positions) {
						_updateCanvasImage(dataURL, inputId, positions[pos_index]);
					}
				}

				$(this).closest('.wpcb-canvas-popup').removeClass('active');
				$('body').removeClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-input-field-canvas-actions .wpcb-button-alt', function () {
				$(this).closest('.wpcb-canvas-popup').removeClass('active');
				$('body').removeClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] #wpcb-view-step-progress', function (e) {
				e.preventDefault();
				$('#wpcb-configurator-step-progress').addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button a', function (e) {
				e.preventDefault();
				// Change step
				wpcb_configuration.step.active = $(this).closest('.wpcb-configurator-step-index-button').data('step-index');
				_setActiveStep();
				// Close panel
				$('#wpcb-configurator-step-progress').removeClass('active');
				$('body').removeClass('wpcb-panel-active');
			});

			$(document).on('click', '[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-view-price-details', function (e) {
				e.preventDefault();
				var id = $(this).closest('[data-wpcb-id]').data('wpcb-id');
				$('#wpcb-configurator-price-details-' + id).addClass('active');
				$('body').addClass('wpcb-panel-active');
			});

			// $(document).on('click', '#wpcb-configurator-button-view-summary', function (e) {
			// 	e.preventDefault();
			// 	_handleConfiguratorSummaryAction();
			// 	$('#wpcb-configurator-finish').addClass('active');
			// });

			if (queryString.includes('wpcb_configuration') && queryString.includes('cart_key') && $('.wpcb_show_configuration_popup').length) {
				// Open popup when user comes from cart page.
				$('.wpcb_show_configuration_popup').trigger('click');
			}

			/**
			 * Event handlers
			 */

			// $(document).on('wpcb_save_configuration', function() {
			// 	if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-save-action').length ) {
			// 		_handleConfiguratorShareAction(wpcb_configuration);
			// 	}
			// });

		});

		/**
		 * Helper functions
		 * 
		 */

		function _handleInputChange($input) {
			if (!_hasLoader()) {
				if ($input.data('value') && $input.data('value').toString().includes('{')) {
					$input.addClass('wpcb-modified');
				}
			}

			_updateConfiguration($input.closest('.wpcb-configurator-field'), 'input');

			// Update linked configuration fields.
			if ($('[data-wpcb-id="' + wpcb_id + '"] input[data-value*="{"]:not(.wpcb-modified)').length) {
				$('[data-wpcb-id="' + wpcb_id + '"] input[data-value*="{"]:not(.wpcb-modified)').each(function (index, input) {
					let input_id = $(input).data('value').replace('{', '').replace('}', '');
					let input_value = false;

					for (let groupId in wpcb_configuration.selection) {
						if (wpcb_configuration.selection[groupId].type === 'group') {
							wpcb_configuration.selection[groupId].values.forEach(function (subfield, index) {
								if (subfield.id == input_id) {
									input_value = subfield.value
								}
							});
						} else if (wpcb_configuration.selection[groupId].id == input_id) {
							input_value = wpcb_configuration.selection[groupId].value;
						}
					}

					if (input_value) {
						$(input).val(input_value);
						_updateConfiguration($(input).closest('.wpcb-configurator-field'), 'input');
					}
				});
			}

			_updateFormFieldDisplay();

			if ($input.data('has-preview') || $input.closest('.wpcb-configurator-field').data('reference') || $input.data('reference')) {
				_updateCanvasView($input.closest('.wpcb-configurator-field'), $input);
			}

			if ($input.data('model-node') && $input.data('property')) {
				let value = $input.val();
				if ($input.data('multiplier')) {
					value = value * $input.data('multiplier');
				}
				_updateViewerNodeSize($input.data('model-node'), $input.data('property'), value);
			}

			if ($input.find('textarea').length) {
				_validateConfigurationField($input.closest('.wpcb-configurator-field'), 'textarea', $input);
			} else {
				_validateConfigurationField($input.closest('.wpcb-configurator-field'), 'input', $input);
			}

			if (wpcb_vars.settings.track_options) {
				$input.attr('value', $input.val());
			}

			if ($input.closest('.wpcb-collapsible').length) {
				let group_id = 'field-' + $input.closest('.wpcb-configurator-field').data('id');
				let value_html = _getConfigurationCollapsibleOptionSummary(group_id);

				$input.closest('.wpcb-collapsible').find('.wpcb-configurator-field-value').html(value_html);

				if (wpcb_vars.settings.collapse_attributes) {
					// Auto collapse on value select.
					$input.closest('.wpcb-collapsible').addClass('wpcb-collapsed');
				}
			}

			if ($input.closest('.wpcb-input-content').find('.wpcb-button-customize-popup').length) {
				if ($input.val()) {
					$input.closest('.wpcb-input-content').find('.wpcb-button-customize-popup').addClass('active');
				} else {
					$input.closest('.wpcb-input-content').find('.wpcb-button-customize-popup').removeClass('active');
				}
			}
		}

		function _handleRangeInputChange(e) {
			let target = e.target;
			let val = target.value;

			$(target).closest('.wpcb-input-content').css('--val', val);
			$(target).siblings('.wpcb-input-range-value').text(val);
		}

		function _updateProductVariationDisplay(result, $option) {
			if (result.variation_id) {
				$option.find('input').attr('data-price', result.price);
				$option.find('input').attr('data-stock', result.stock);
				$option.find('input').attr('data-weight', result.weight);
				$option.find('input').attr('data-label', result.title);
				$option.find('input').attr('data-variation-id', result.variation_id);
				$option.find('input').attr('value', result.title);

				if (wpcb_vars.settings.show_low_stock) {
					if (result.stock_html) {
						if ($option.find('.wpcb-option-stock').length) {
							$option.find('.wpcb-option-stock').replaceWith(result.stock_html);
						} else {
							$option.find('.wpcb-option-thumbnail').after(result.stock_html);
						}
					} else {
						$option.find('.wpcb-option-stock').html('');
					}
				}

				if ($option.find('input[type=number]').length) {
					if ($option.find('input[type=number]').attr('data-max') > result.stock) {
						$option.find('input[type=number]').attr('max', result.stock);
					} else {
						$option.find('input[type=number]').attr('max', $option.find('input[type=number').attr('data-max'));
					}
				}

				$option.find('.wpcb-option-price').html(_formatPriceOutput(result.price));
				$option.find('.wpcb-option-thumbnail img').attr('src', result.thumbnail);

				_updateConfiguration($option.closest('.wpcb-configurator-field'), 'option');
			} else {
				// Resetting
				$option.find('input').attr('data-price', $option.find('.wpcb-option-form').data('price'));
				$option.find('input').attr('data-stock', $option.find('.wpcb-option-form').data('stock'));
				$option.find('input').attr('data-weight', $option.find('.wpcb-option-form').data('weight'));
				$option.find('input').attr('data-label', $option.find('.wpcb-option-form').data('label'));
				$option.find('input').attr('value', $option.find('.wpcb-option-form').data('label'));
				$option.find('input').attr('data-variation-id', '0');

				if ($option.find('input[type=number').length) {
					$option.find('input[type=number').attr('max', $option.find('input[type=number').attr('data-max'));
				}

				if ($option.find('.wpcb-option-stock').length) {
					$option.find('.wpcb-option-stock').remove();
				}

				$option.find('.wpcb-option-price').html(_formatPriceOutput($option.find('.wpcb-option-form').data('price')));
				$option.find('.wpcb-option-thumbnail img').attr('src', $option.find('.wpcb-option-form').data('thumbnail'));
			}
		}

		/**
		 * Form conditionals functions
		 * 
		 */

		function _updateFormFieldDisplay() {
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field-group[data-display-rules]').each(function (index, field) {
				let display = _validateConditionalRuleset(field);

				if ('show' === display) {
					_setConditionalFieldHtml(field);
				} else if ('hide' === display) {
					_removeConditionalFieldHtml(field);
				}
			});

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field[data-display-rules]').each(function (index, field) {
				let display = _validateConditionalRuleset(field);

				if ('show' === display) {
					_setConditionalFieldHtml(field);

					$(field).find('.wpcb-option[data-display-rules]').each(function (key, option) {
						let display = _validateConditionalRuleset(option);

						if ('show' === display) {
							_setConditionalOptionHtml(option);
						} else if ('hide' === display) {
							_removeConditionalOptionHtml(option);
						}
					});
				} else if ('hide' === display) {
					_removeConditionalFieldHtml(field);
				}
			});

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field .wpcb-option[data-display-rules]').each(function (key, option) {
				let display = _validateConditionalRuleset(option);

				if ('show' === display) {
					_setConditionalOptionHtml(option);
				} else if ('hide' === display) {
					_removeConditionalOptionHtml(option);
				} else if ('enable' === display) {
					_setConditionalOptionHtml(option, true);
				} else if ('disable' === display) {
					_removeConditionalOptionHtml(option, true);
				}
			});

			_refreshTotals();
		}

		function _validateImageDimensions(file, min_w, max_w, min_h, max_h) {
			return new Promise((resolve) => {
				const url = URL.createObjectURL(file);
				const img = new Image();
				img.onload = () => {
					const ok = img.naturalWidth >= min_w && img.naturalWidth <= max_w && img.naturalHeight >= min_h && img.naturalHeight <= max_h;
					URL.revokeObjectURL(url);
					resolve({ ok, w: img.naturalWidth, h: img.naturalHeight });
				};
				img.onerror = () => {
					URL.revokeObjectURL(url);
					resolve({ ok: false, w: 0, h: 0 });
				};
				img.src = url;
			});
		}

		function _validateConfigurationField($field_wrapper, fieldtype, $field = false) {

			if ('option' === fieldtype) {

				let min = $field_wrapper.data('min');
				let max = $field_wrapper.data('max');
				let required = $field_wrapper.data('required') ? true : false;

				if (required && min === 0) {
					min = 1; // Required question. Minimum selection of 1.
				}

				if ($field_wrapper.hasClass('wpcb-configurator-dropdown')) {

					// Dropdown option
					if (required && !$field_wrapper.find('select').val()) {
						$field_wrapper.addClass('wpcb-invalid');
					} else {
						$field_wrapper.removeClass('wpcb-invalid');
					}

				} else if ($field_wrapper.hasClass('wpcb-configurator-products')) {

					let checkedItemCount = 0;
					if ($field_wrapper.find('input[type=number]').length) {
						// Shared item check
						$field_wrapper.find('input:checked + .wpcb-option-item input[type=number]').each(function (index, input) {
							checkedItemCount += parseFloat($(input).val());

							// Single item check
							let field_min = parseInt($(input).attr('min'));
							let field_max = parseInt($(input).attr('max'));
							let field_val = parseInt($(input).val());

							if (required && !field_val) {
								$field_wrapper.addClass('wpcb-invalid');
							} else if (field_val && ((field_min && field_val < field_min) || (field_max && field_val > field_max))) {
								$field_wrapper.addClass('wpcb-invalid');
							} else {
								$field_wrapper.removeClass('wpcb-invalid');
							}
						});

						if (!$field_wrapper.hasClass('wpcb-invalid')) {
							// Not flagged invalid yet. Check total sum.
							if (required || (checkedItemCount > 0 && checkedItemCount < min) || (max && checkedItemCount > max)) {
								$field_wrapper.addClass('wpcb-invalid');
							} else {
								$field_wrapper.removeClass('wpcb-invalid');

								if ($field_wrapper.find('.wpcb-error-note').length) {
									$field_wrapper.find('.wpcb-error-note').remove();
								}
							}
						}
					} else {
						checkedItemCount = $field_wrapper.find('input:checked').length;

						// Block user to select more when max is reached.
						$field_wrapper.find('input:not(:checked)').prop('disabled', (checkedItemCount === max));

						if (required || (checkedItemCount && (checkedItemCount < min || checkedItemCount > max))) {
							$field_wrapper.addClass('wpcb-invalid');
						} else {
							$field_wrapper.removeClass('wpcb-invalid');

							if ($field_wrapper.find('.wpcb-error-note').length) {
								$field_wrapper.find('.wpcb-error-note').remove();
							}
						}
					}

					$field_wrapper.data('count', checkedItemCount);

					if (!$field_wrapper.hasClass('wpcb-invalid') && $field_wrapper.find('input:checked + .wpcb-option-item .wpcb-option-form').length && checkedItemCount > 0) {
						if (!$field_wrapper.find('input[type=checkbox]').attr('data-variation-id') || 0 == $field_wrapper.find('input[type=checkbox]').attr('data-variation-id')) {
							$field_wrapper.addClass('wpcb-invalid');
						} else {
							$field_wrapper.removeClass('wpcb-invalid');
						}
					}
				} else {
					// Regular options
					let checkedItemLength = $field_wrapper.find('input:checked').length;

					if ($field_wrapper.find('input[type=checkbox]').length) {
						// Block user to select more when max is reached.
						$field_wrapper.find('input:not(:checked)').prop('disabled', (checkedItemLength === max));
					}

					if (required && checkedItemLength < min) {
						$field_wrapper.addClass('wpcb-invalid');
					} else {
						$field_wrapper.removeClass('wpcb-invalid');

						if ($field_wrapper.find('.wpcb-error-note').length) {
							$field_wrapper.find('.wpcb-error-note').remove();
						}
					}
				}

			} else if ('input' === fieldtype) {

				let required = $field.attr('required');

				if ($field.attr('type') === 'text') {
					let min = $field.attr('minlength');
					let max = $field.attr('maxlength');

					if (required && !$field.val()) {
						$field_wrapper.addClass('wpcb-invalid');
					} else if ((min && $field.val().length < min) || (max && $field.val().length > max)) {
						$field_wrapper.addClass('wpcb-invalid');
					} else {
						$field_wrapper.removeClass('wpcb-invalid');

						if ($field_wrapper.find('.wpcb-error-note').length) {
							$field_wrapper.find('.wpcb-error-note').remove();
						}
					}
				} else if ($field.attr('type') !== 'file') {
					let min = parseFloat($field.attr('min'));
					let max = parseFloat($field.attr('max'));
					let val = parseFloat($field.val());

					if (required && !$field.val()) {
						$field_wrapper.addClass('wpcb-invalid');
					} else if (val && ((min && val < min) || (max && val > max))) {
						$field_wrapper.addClass('wpcb-invalid');
					} else {
						$field_wrapper.removeClass('wpcb-invalid');

						if ($field_wrapper.find('.wpcb-error-note').length) {
							$field_wrapper.find('.wpcb-error-note').remove();
						}
					}
				}
			}
		}

		function _validateForm() {
			let valid = true;
			let checkForm = $('.wpcb-configurator-form[data-wpcb-id="' + wpcb_id + '"] ');
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step.active').length) {
				checkForm = $('.wpcb-configurator-form[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step.active');
			}

			checkForm.find('.wpcb-configurator-field:not(.wpcb-removed)').each(function (key, field) {
				if ($(field).hasClass('wpcb-configurator-input')) {
					if ($(field).find('textarea').length) {
						_validateConfigurationField($(field), 'textarea', $(field).find('textarea'));
					} else {
						_validateConfigurationField($(field), 'input', $(field).find('input'));
					}
				} else {
					_validateConfigurationField($(field), 'option');
				}
			});

			if (checkForm.find('.wpcb-configurator-field .wpcb-error-note').length) {
				checkForm.find('.wpcb-configurator-field .wpcb-error-note').remove();
			}

			if (checkForm.find('.wpcb-configurator-field.wpcb-invalid').length) {
				valid = false;

				checkForm.find('.wpcb-configurator-field.wpcb-invalid').addClass('wpcb-error');

				checkForm.find('.wpcb-configurator-field.wpcb-invalid').each(function (index, invalidField) {
					let error_notice = wpcb_vars.error_required;
					let selectedAmount = $(invalidField).data('count') ?? 0;
					let fieldMin = $(invalidField).data('min') ?? false;
					let fieldMax = $(invalidField).data('max') ?? false;

					if ((selectedAmount && fieldMin && selectedAmount < fieldMin)
						|| (selectedAmount && fieldMax && selectedAmount > fieldMax)) {
						error_notice = wpcb_vars.error_selection;

						error_notice = error_notice.replace('{min}', $(invalidField).data('min'));
						error_notice = error_notice.replace('{max}', $(invalidField).data('max'));
					}

					else if ($(invalidField).hasClass('wpcb-configurator-input')) {

						if ($(invalidField).find('input[type=text]').length || $(invalidField).find('textarea').length) {
							error_notice = wpcb_vars.error_length;

							if ($(invalidField).find('textarea').length) {
								error_notice = error_notice.replace('{min}', $(invalidField).find('textarea').attr('minlength'));
								error_notice = error_notice.replace('{max}', $(invalidField).find('textarea').data('maxlength'));
							} else {
								error_notice = error_notice.replace('{min}', $(invalidField).find('input').attr('minlength'));
								error_notice = error_notice.replace('{max}', $(invalidField).find('input').data('maxlength'));
							}
						} else {
							error_notice = wpcb_vars.error_value;

							error_notice = error_notice.replace('{min}', $(invalidField).find('input').attr('min'));
							error_notice = error_notice.replace('{max}', $(invalidField).find('input').attr('max'));
						}
					}
					else if ($(invalidField).find('.wpcb-option-product').length) {

						if ($(invalidField).find('input[type=number]').length) {
							let field_error = false;

							$(invalidField).find('input[type=number]').each(function (index, invalidInput) {
								let min = parseFloat($(invalidInput).attr('min'));
								let max = parseFloat($(invalidInput).attr('max'));
								let val = $(invalidInput).val();

								if ('0' != val) {
									if (val < min || val > max) {
										error_notice = wpcb_vars.error_value;

										error_notice = error_notice.replace('{min}', min);
										error_notice = error_notice.replace('{max}', max);

										field_error = true;
										return;
									}
								}
							});

							if (!field_error) {
								error_notice = wpcb_vars.error_product;
							}
						} else {
							error_notice = wpcb_vars.error_product;
						}
					}

					if (!$(invalidField).find('.wpcb-error-note').length) {
						$(invalidField).append('<small class="wpcb-error-note">' + error_notice + '</small>');
					}

					if ($(invalidField).parents('.wpcb-configurator-panel').length) {
						$(invalidField).parents('.wpcb-configurator-panel').addClass('active');
						$('body').addClass('wpcb-panel-active');
					}
				});
			}

			return valid;
		}

		function _findArrayOverlap(arr1, arr2) {
			return arr1.filter(item => arr2.includes(item));
		}

		function _validateButtonRules( rules ) {
			let jsonRules = rules.replaceAll("'", '"');
			jsonRules = JSON.parse(jsonRules);

			let validRules = 0;
			let totalRules = 0;

			for ( var key in jsonRules ) {
				let ruleset = jsonRules[key];
				let fieldId = ruleset.choice;
				let values = [];

				if ( typeof wpcb_configuration.selection['field-' + fieldId] !== 'undefined' ) {
					if ( wpcb_configuration.selection['field-' + fieldId].values && wpcb_configuration.selection['field-' + fieldId].values.length ) {
						if ( typeof wpcb_configuration.selection['field-' + fieldId].values[0].id !== 'undefined' ) {
							// Choice. Return ID
							values = wpcb_configuration.selection['field-' + fieldId].values.map( (val_obj) => val_obj.id );
						}
					}
				}

				totalRules++;

				if (!values.length) {
					return;
				}

				if ('==' == ruleset.compare) {
					if (!values.includes(ruleset.value)) {
						return;
					}
				}
				else if ('!=' == ruleset.compare) {
					if (values.includes(ruleset.value)) {
						return;
					}
				}
				else if ('contains' == ruleset.compare) {
					if (values.filter(value => value.includes(ruleset.value)).length === 0) {
						return;
					}
				}
				else if ('!contains' == ruleset.compare) {
					if (values.filter(value => value.includes(ruleset.value)).length > 0) {
						return;
					}
				}
				else if ('empty' == ruleset.compare) {
					if (values.length > 0) {
						return;
					}
				}
				else if ('!empty' == ruleset.compare) {
					if (values.length === 0) {
						return;
					}
				}

				validRules++;
			}

			return totalRules > 0 && validRules == totalRules;
		}

		function _validateDiscountRules( rules ) {
			let jsonRules = rules.replaceAll("'", '"');
			jsonRules = JSON.parse(jsonRules);

			let validRules = 0;
			let totalRules = 0;

			for ( var fieldId in jsonRules ) {
				let compareValues = [];
				let expectedValues = jsonRules[fieldId];

				if (typeof wpcb_configuration.selection['field-' + fieldId] !== 'undefined') {
					if (wpcb_configuration.selection['field-' + fieldId].values && wpcb_configuration.selection['field-' + fieldId].values.length) {
						if (typeof wpcb_configuration.selection['field-' + fieldId].values[0].id !== 'undefined') {
							// Choice. Return ID
							compareValues = wpcb_configuration.selection['field-' + fieldId].values.map((val_obj) => val_obj.id);
						}
					}
				}

				let result = _findArrayOverlap(compareValues, expectedValues);
				if ( result.length ) {
					validRules++;
				}

				totalRules++;
			}

			return totalRules > 0 && validRules == totalRules;
		}

		function _validateConditionalRuleset(field) {
			let action = 'none';
			let validRuleCount = 0;
			let rules = $(field).data('display-rules');
			let jsonRules = rules.replaceAll("'", '"');
			jsonRules = JSON.parse(jsonRules);

			if (! 'conditions' in jsonRules) {
				return action;
			}

			if (jsonRules.conditions.length <= 0) {
				return action;
			}

			jsonRules.conditions.forEach(function (ruleset, rulekey) {
				let compareValues = [];
				
				if (typeof wpcb_configuration.selection['field-' + ruleset.field] === 'undefined') {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field-group').each(function (index, group) {
						let groupId = $(group).data('id');

						if (typeof wpcb_configuration.selection['group-' + groupId] === 'undefined') {
							return;
						}

						if (!wpcb_configuration.selection['group-' + groupId].values) {
							return;
						}

						wpcb_configuration.selection['group-' + groupId].values.forEach(function (subfield, index) {
							if (subfield.id == ruleset.field && subfield.values.length) {
								if (typeof subfield.values[0].id !== 'undefined') {
									// Choice. Return ID
									compareValues = subfield.values.map((val_obj) => val_obj.id);
								} else {
									// Input. Return value
									compareValues = subfield.values.map((val_obj) => val_obj.value);
								}
							}
						});
					});
				} else {
					if (wpcb_configuration.selection['field-' + ruleset.field].values && wpcb_configuration.selection['field-' + ruleset.field].values.length) {
						if (typeof wpcb_configuration.selection['field-' + ruleset.field].values[0].id !== 'undefined') {
							// Choice. Return ID
							compareValues = wpcb_configuration.selection['field-' + ruleset.field].values.map((val_obj) => val_obj.id);
						} else {
							// Input. Return value
							compareValues = wpcb_configuration.selection['field-' + ruleset.field].values.map((val_obj) => val_obj.value);
						}
					}
				}

				if (!compareValues.length) {
					return;
				}

				if ('==' == ruleset.compare) {
					if (!compareValues.includes(ruleset.value)) {
						return;
					}
				}
				else if ('!=' == ruleset.compare) {
					if (compareValues.includes(ruleset.value)) {
						return;
					}
				}
				else if ('contains' == ruleset.compare) {
					if (compareValues.filter(value => value.includes(ruleset.value)).length === 0) {
						return;
					}
				}
				else if ('!contains' == ruleset.compare) {
					if (compareValues.filter(value => value.includes(ruleset.value)).length > 0) {
						return;
					}
				}
				else if ('empty' == ruleset.compare) {
					if (compareValues.length > 0) {
						return;
					}
				}
				else if ('!empty' == ruleset.compare) {
					if (compareValues.length === 0) {
						return;
					}
				}
				else if ('<' == ruleset.compare) {
					if (!(parseFloat(compareValues[0]) < parseFloat(ruleset.value))) {
						return;
					}
				}
				else if ('>' == ruleset.compare) {
					if (!(parseFloat(compareValues[0]) > parseFloat(ruleset.value))) {
						return;
					}
				}
				else if ('<=' == ruleset.compare) {
					if (!(parseFloat(compareValues[0]) <= parseFloat(ruleset.value))) {
						return;
					}
				}
				else if ('>=' == ruleset.compare) {
					if (!(parseFloat(compareValues[0]) >= parseFloat(ruleset.value))) {
						return;
					}
				}

				validRuleCount++;
			});

			if (('AND' === jsonRules.relation && validRuleCount === jsonRules.conditions.length)
				|| ('OR' === jsonRules.relation && 0 < validRuleCount)) {
				// Valid rules. Follow display
				if ('enable' === jsonRules.display || 'disable' === jsonRules.display) {
					action = ('enable' === jsonRules.display) ? 'enable' : 'disable';
				} else {
					action = ('show' === jsonRules.display) ? 'show' : 'hide';
				}
			} else {
				// No valid. Inverse display.
				if ('enable' === jsonRules.display || 'disable' === jsonRules.display) {
					action = ('enable' === jsonRules.display) ? 'disable' : 'enable';
				} else {
					action = ('show' === jsonRules.display) ? 'hide' : 'show';
				}
			}

			return action;
		}

		function _setConditionalFieldHtml(field) {
			// show
			if ($(field).hasClass('wpcb-removed')) {
				let fieldId = $(field).data('id');
				let fieldHtml = sessionStorage.getItem('wpcb-field-' + fieldId);

				$(field).html(fieldHtml);
				$(field).removeClass('wpcb-removed');

				// check input types
				if ($(field).find('.ui-selectmenu-button').length) {
					$(field).find('.ui-selectmenu-button').remove();

					_renderAdvancedFields();

					if ($(field).find('option[data-selected="selected"]').length) {
						$(field).find('option[data-selected="selected"]').prop('selected', true);
					}
					else if ($(field).find('option[data-default]').length) {
						$(field).find('option[data-default]').prop('selected', true);
					}
					else if (wpcb_vars.settings.enable_defaults) {
						$(field).find('option:first-of-type').prop('selected', true);
					}

					$(field).find('select').trigger('change');
					$(field).find('select').selectmenu('refresh', true);
				}

				if ($(field).find('input[checked]').length) {
					$(field).find('input[checked]').trigger('change');
				} else {
					if (wpcb_vars.settings.enable_defaults && $(field).data('required')) {
						$(field).find('label:first-of-type input').prop('checked', true);
						$(field).find('label:first-of-type input').trigger('change');
					}
				}

				if ($(field).find('input[value]').length) {
					$(field).find('input[value]').trigger('input');
				}
			}
		}

		function _removeConditionalFieldHtml(field) {
			if (!$(field).hasClass('wpcb-removed')) {
				// hide
				let fieldId = $(field).data('id');
				let fieldHtml = $(field).html();

				if (fieldHtml) {
					sessionStorage.setItem('wpcb-field-' + fieldId, fieldHtml);
				}

				// Clear value.
				if ($(field).closest('.wpcb-configurator-field-group').length) {
					let group_id = 'group-' + $(field).closest('.wpcb-configurator-field-group').data('id');
					wpcb_configuration.selection[group_id] = {};
				} else if ($(field).closest('[data-repeater-list]').length) {
					let repeater_id = 'repeater-' + $(field).closest('[data-repeater-list]').data('repeater-list');
					wpcb_configuration.selection[repeater_id] = {};
				} else {
					let field_id = 'field-' + $(field).data('id');
					wpcb_configuration.selection[field_id] = {};
				}

				let selected = $(field).find('option').length ? 'option:selected' : 'input:checked';
				if (!$(field).find('input[type=radio]').length && !$(field).find('input[type=checkbox]').length) {
					if ($(field).find('textarea').length) {
						selected = 'textarea';
					} else {
						selected = 'input';
					}
				}

				if ($(field).find(selected).length) {
					$(field).find(selected).each(function (index, option) {
						_updateCanvasView($(field), $(option), true);
					});
				}

				$(field).html('');
				$(field).addClass('wpcb-removed');
			}
		}

		function _setConditionalOptionHtml(option, disabled = false) {
			// show
			if (disabled) {
				$(option).removeClass('disabled');
				$(option).find('input').prop('disabled', false);
			}
			else if ($(option).hasClass('wpcb-removed')) {
				let optionId = $(option).attr('for');
				let optionHtml = sessionStorage.getItem('wpcb-field-option-' + optionId);

				$(option).html(optionHtml);
				$(option).removeClass('wpcb-removed');

				if ($(option).closest('.swiper-slide').length) {
					$(option).closest('.swiper-slide').removeClass('swiper-slide-removed');
				}

				if ($(option).find('input[checked]').length) {
					if ($(option).closest('.wpcb-configurator-field').data('max')) {
						let max = parseInt($(option).closest('.wpcb-configurator-field').data('max'));
						let length = $(option).closest('.wpcb-configurator-field').find('input[checked]').length;

						if (length === max) {
							// Max reached. No room for new option.
							$(option).find('input[checked]').attr('checked', false);
						}
					} else {
						$(option).find('input[checked]').trigger('change');
					}

					$(option).find('input[checked]').trigger('change');
				}
			}
		}

		function _removeConditionalOptionHtml(option, disabled = false) {

			if (disabled) {
				$(option).addClass('disabled');
				$(option).find('input').prop('disabled', true);

				// Only execute if default 1 option checked.

				if ($(option).find('input').is(':checked')) {
					$(option).find('input').prop('checked', false);

					if (wpcb_vars.settings.enable_defaults) {
						$(option).closest('.wpcb-configurator-field').find('.wpcb-option:not(.wpcb-removed):not(.disabled):first').trigger('click');
					}
				}
			}
			else if (!$(option).hasClass('wpcb-removed')) {
				// hide
				let optionId = $(option).attr('for');
				let optionHtml = $(option).html();

				if (optionHtml) {
					sessionStorage.setItem('wpcb-field-option-' + optionId, optionHtml);
				}

				$(option).addClass('wpcb-removed');
				if ($(option).closest('.swiper-slide').length) {
					$(option).closest('.swiper-slide').addClass('swiper-slide-removed');
				}

				// Only execute if default 1 option checked.

				if ($(option).find('input').is(':checked')) {
					$(option).find('input').prop('checked', false);
					$(option).find('input').trigger('change');

					if (wpcb_vars.settings.enable_defaults) {
						$(option).closest('.wpcb-configurator-field').find('.wpcb-option:not(.wpcb-removed):not(.disabled):first').trigger('click');
					}
				}

				$(option).html('');
			}
		}

		function _setSelectedConfigurationOptions(selections) {

			for (let select_key in selections) {

				let select_group = selections[select_key];

				if (select_group.values && select_group.values.length) {

					if (select_group.values[0].values && select_group.values[0].values.length) {

						for (let sub_select_key in select_group.values) {

							let sub_select_group = select_group.values[sub_select_key];

							sub_select_group.name = _getSanitizedLabel(sub_select_group.label);

							_setSelectedFieldOptions(sub_select_group);
						}
					} else {
						select_group.name = _getSanitizedLabel(select_group.label);

						_setSelectedFieldOptions(select_group);
					}
				}
			}
		}

		function _setSelectedFieldOptions(select_group) {

			select_group.values.forEach(function (option, index) {

				if ($('.wpcb-configurator-field input[name="' + select_group.name + '"][data-id="' + option.id + '"]').length) {

					$('.wpcb-configurator-field input[name="' + select_group.name + '"][data-id="' + option.id + '"]').prop('checked', true);
					$('.wpcb-configurator-field input[name="' + select_group.name + '"][data-id="' + option.id + '"]').trigger('change');

					if ($('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').parents('.wpcb-option-product-input').length) {

						$('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').next('.wpcb-option-item').find('input[type="number"]').val(option.value);
						$('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').next('.wpcb-option-item').find('input[type="number"]').trigger('input');

					}

					if ($('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').parents('.wpcb-option-product').length &&
						$('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').parents('.wpcb-option-product').find('.wpcb-option-form').length) {

						let $option = $('input[name="' + select_group.name + '"][data-id="' + option.id + '"]').closest('.wpcb-option');

						$.ajax({
							type: 'post',
							url: wpcb_vars.ajax_url,
							data: {
								action: 'wpcb_get_variation_product',
								nonce: wpcb_vars.nonce,
								product: option.product_id,
								variation: option.variation_id,
							},
							success: function (response) {
								let result = JSON.parse(response);
								for (let key in result.attributes) {
									$option.find('[name="attribute_' + key + '"]').val(result.attributes[key]);
								}
								_updateProductVariationDisplay(result, $option);
							},
							error: function (error) {
								console.error(error);
							}
						});
					}

				} else if ($('.wpcb-configurator-field select[name="' + select_group.name + '"] option[data-id="' + option.id + '"]').length) {

					$('.wpcb-configurator-field select[name="' + select_group.name + '"] option[data-id="' + option.id + '"]').prop('selected', true);
					$('.wpcb-configurator-field select[name="' + select_group.name + '"] option[data-id="' + option.id + '"]').closest('.wpcb-select').trigger('change');

					if ($('.wpcb-configurator-field select[name="' + select_group.name + '"] option[data-id="' + option.id + '"]').closest('.wpcb-dropdown').find('.ui-selectmenu-button').length) {
						$('.wpcb-configurator-field select[name="' + select_group.name + '"] option[data-id="' + option.id + '"]').closest('.wpcb-select').selectmenu('refresh', true);
					}

				} else if ($('input[name="' + select_group.name + '"]').length) {

					if ($('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-true-false').length) {

						$('.wpcb-configurator-field input[name="' + select_group.name + '"]').prop('checked', true);
						$('.wpcb-configurator-field input[name="' + select_group.name + '"]').trigger('change');

					} else if ($('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').length) {

						if ($('input[name="' + select_group.name + '"]').data('has-preview')) {
							_updateCanvasInputImage(
								$('input[name="' + select_group.name + '"]'),
								option.value
							);
						}

						$('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').find('.wpcb-input-file').addClass('active');
						if ($('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').find('.wpcb-input-file.active .wpcb-input-file-image img').length) {
							$('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').find('.wpcb-input-file.active .wpcb-input-file-image img').attr('src', option.value);
						} else {
							$('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').find('.wpcb-input-file.active .wpcb-input-file-image').html('<img src="' + option.value + '" alt="' + select_group.value + '">');
						}
						$('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload').find('.wpcb-input-file.active .wpcb-input-file-name').text(select_group.value);

						_updateConfiguration($('input[name="' + select_group.name + '"]').parents('.wpcb-configurator-file-upload'), 'input');

						_updateFormFieldDisplay();

					} else {

						$('.wpcb-configurator-field input[name="' + select_group.name + '"]').val(option.value);
						$('.wpcb-configurator-field input[name="' + select_group.name + '"]').trigger('input', { bubbles: true });

						if ($('.wpcb-configurator-field input[name="' + select_group.name + '"]').attr('value')) {
							$('.wpcb-configurator-field input[name="' + select_group.name + '"]').attr('value', option.value);
						}
						if ($('.wpcb-configurator-field input[name="' + select_group.name + '"]').attr('data-value')) {
							$('.wpcb-configurator-field input[name="' + select_group.name + '"]').attr('data-value', option.value);
						}

						if ($('.wpcb-configurator-field input[name="' + select_group.name + '"]').closest('.wpcb-input-content').find('.clr-field').length) {
							// COLOR PICKER FIELD
							document.querySelectorAll('.wpcb-configurator-field input[name="' + select_group.name + '"]')[0].dispatchEvent(new Event('input', { bubbles: true }));
						}
					}
				}
			});
		}

		function _setDefaultConfigurationOptions(initial = false) {
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field').each(function (index, field) {
				if ( $(field).find('.wpcb-option-type-template').length ) {
					return;
				}

				let group_id = '';
				if ($(field).parents('.wpcb-configurator-field-group').length) {
					// Grouped val with single price or single value.
					let $parent_wrapper = $(field).parents('.wpcb-configurator-field-group');
					group_id = 'group-' + $parent_wrapper.data('id');

					if (group_id in wpcb_configuration.selection) {
						return;
					}

					// List item in configuration.
					wpcb_configuration.selection = {
						...wpcb_configuration.selection,
						[group_id]: {},
					};
				} else {
					let field_id = 'field-' + $(field).data('id');

					if (field_id in wpcb_configuration.selection) {
						return;
					}

					// List item in configuration.
					wpcb_configuration.selection = {
						...wpcb_configuration.selection,
						[field_id]: {},
					};
				}

				// Check item values.
				if ($(field).hasClass('wpcb-configurator-input')) {

					if ($(field).find('.wpcb-input-file.active').length) {
						// Upload field.
						let defaultSrc = '';
						let defaultName = '';

						if ($(field).find('input[type=file]').data('filename')) {
							defaultSrc = $(field).find('input[type=file]').data('fileurl');
							defaultName = $(field).find('input[type=file]').data('filename');
						}

						// Update preview
						if ($(field).data('has-preview')) {
							_updateCanvasInputImage($(field).find('input'), defaultSrc);
						} else {
							_updateCanvasInputImage($(field).find('input'), '');
						}

						// Update field html
						if ('' !== defaultSrc) {
							$(field).find('.wpcb-input-file-image img').attr('src', defaultSrc);
							$(field).find('.wpcb-input-file-name').text(defaultName);
						} else {
							$(field).find('input').val('').trigger('change');
							$(field).find('.wpcb-input-file').removeClass('active');
							$(field).find('.wpcb-input-file-image').html('');
							$(field).find('.wpcb-input-file-name').html('');

							if ($(field).find('.wpcb-button-customize-popup.active').length) {
								$(field).find('.wpcb-button-customize-popup').removeClass('active');
							}
						}
					} else if ($(field).find('textarea').length) {
						// Input field.
						let value = '';
						if ($(field).find('textarea').attr('value')) {
							value = $(field).find('textarea').data('value');
						}

						if (value || initial) {
							$(field).find('textarea').val(value);
							$(field).find('textarea').trigger('input');
						}
					} else {
						// Input field.
						let value = '';
						if ($(field).find('input').attr('value')) {
							value = $(field).find('input').data('value');
						}

						if (value || initial) {
							$(field).find('input').val(value);
							$(field).find('input').trigger('input');
						}
					}

					if ($(field).closest('.wpcb-input-content').find('.wpcb-button-customize-popup').length) {
						if ((value && '' !== value) || (defaultSrc && '' !== defaultSrc)) {
							$(field).closest('.wpcb-input-content').find('.wpcb-button-customize-popup').addClass('active');
						} else {
							$(field).closest('.wpcb-input-content').find('.wpcb-button-customize-popup').removeClass('active');
						}
					}
				} else {

					if ($(field).find('.wpcb-option-product-input').length) {

						// Product input field.
						let value = 0;
						if ($(field).find('input').data('value')) {
							value = $(field).find('input').data('value');
						}

						if ( value || initial ) {
							$(field).find('input').val(value);
							$(field).find('input').trigger('input');
						}

					} else if ($(field).find('.wpcb-dropdown').length) {

						if ( ! initial ) {
							$(field).find('option').prop('selected', false);
						}

						if ($(field).find('option[data-default]').length) {
							$(field).find('option[data-default]').prop('selected', true);
						}
						else if (wpcb_vars.settings.enable_defaults && !$(field).parents('.wpcb-configurator-field-filter').length) {
							$(field).find('option:first-of-type').prop('selected', true);
						}

						$(field).find('select').trigger('change');

						if ($(field).find('.ui-selectmenu-button').length) {
							$(field).find('select').selectmenu('refresh', true);
						}
					} else {
						// $(field).find('label input').prop('disabled', false);
						if ( ! initial ) {
							$(field).find('label input').prop('checked', false);
						}

						if ($(field).find('label[data-default]').length) {
							$(field).find('label[data-default] input').prop('checked', true);
						}
						else if (wpcb_vars.settings.enable_defaults && !$(field).find('.wpcb-option-product').length && !$(field).parents('.wpcb-configurator-field-filter').length) {
							$(field).find('label:first-child input').prop('checked', true);
						}

						$(field).find('label input').trigger('change');
					}
				}
			});
		}

		function _updateConfiguration($field_wrapper, fieldtype) {

			if ($field_wrapper.parents('[data-repeater-list]').length || fieldtype === 'repeater') {

				/**
				 * Repeater val
				 */

				let $parent_wrapper = $field_wrapper;
				let repeater_id = 'repeater-' + $parent_wrapper.data('repeater-list');
				if ($field_wrapper.parents('[data-repeater-list]').length) {
					$parent_wrapper = $field_wrapper.parents('[data-repeater-list]');
					repeater_id = 'repeater-' + $parent_wrapper.data('repeater-list');
				}

				let repeaterObj = {
					id: repeater_id,
					label: $.trim($field_wrapper.parents('.wpcb-configurator-repeater').find('.wpcb-configurator-repeater-label').text()),
					type: 'repeater',
					values: [],
					value: '',
					tax: 0,
					cost: 0,
				};

				let repeater_cost = 0;
				let repeater_tax = 0;

				$parent_wrapper.find('.wpcb-configurator-repeater-item').each(function (item_index, item_wrapper) {
					$(item_wrapper).find('.wpcb-configurator-field').each(function (field_index, field_wrapper) {
						let subfieldtype = 'option';
						if ($(field_wrapper).find('.wpcb-input-field').length) {
							subfieldtype = 'input';
						}

						let fieldObj = _getSingleFieldValue($(field_wrapper), subfieldtype, repeater_id);
						if ('' === fieldObj.value && !fieldObj.values.length) {
							return;
						}
						fieldObj.value = fieldObj.label + ': ' + fieldObj.value;

						repeater_tax += fieldObj.tax;
						repeater_cost += fieldObj.cost;

						repeaterObj.values.push(fieldObj);
					});
				});

				repeaterObj.cost = repeater_cost;
				repeaterObj.tax = repeater_tax;

				wpcb_configuration.selection[repeater_id] = repeaterObj;
			}

			else if ($field_wrapper.parents('.wpcb-configurator-field-group[data-single-price]').length
				|| $field_wrapper.parents('.wpcb-configurator-field-group[data-single-value]').length) {

				/**
				 * Grouped val with single price or single value.
				 */
				let $parent_wrapper = $field_wrapper.parents('.wpcb-configurator-field-group');
				let group_id = 'group-' + $parent_wrapper.data('id');

				let totalWeight = 0;
				let groupObj = {
					id: group_id,
					label: $.trim($parent_wrapper.find('.wpcb-configurator-field-group-label').text()),
					type: 'group',
					values: [],
					value: '',
					tax_percent: wpcb_vars.product_tax_percent,
					weight: 0,
					cost: 0,
				};

				$parent_wrapper.find('.wpcb-configurator-field').each(function (index, field_wrapper) {
					let subfieldtype = 'option';
					if ($(field_wrapper).find('.wpcb-input-field').length) {
						subfieldtype = 'input';
					}

					let fieldObj = _getSingleFieldValue($(field_wrapper), subfieldtype, group_id);
					if ('' === fieldObj.value && !fieldObj.values.length) {
						return;
					}

					fieldObj.values.forEach(function (subval, index) {
						if (subval.tax_percent && subval.tax_percent > groupObj.tax_percent) {
							groupObj.tax_percent = subval.tax_percent;
						}

						if (subval.weight) {
							totalWeight += subval.weight;
						}
					});

					// Check if linked table prices to this field
					if ($('[data-wpcb-id="' + wpcb_id + '"] input[data-table-field="' + $field_wrapper.data('id') + '"]').length) {
						$('[data-wpcb-id="' + wpcb_id + '"] input[data-table-field="' + $field_wrapper.data('id') + '"]').each(function (index, input) {
							let price_table_id = parseInt($(input).attr('data-price-table'));
							let price_value = 1;
							if ('' !== fieldObj.value) {
								price_value = fieldObj.value;
							}

							let label = $(input).data('label');
							let field_id = $(input).closest('.wpcb-configurator-field').data('id');
							let tax_percent = wpcb_vars.product_tax_percent;
							if ($(input).closest('.wpcb-configurator-field').data('tax')) {
								tax_percent = parseInt($(input).closest('.wpcb-configurator-field').data('tax'));
							}

							_getRangeTableValue(field_id, label, price_table_id, price_value, '', tax_percent);
						});
					}

					groupObj.values.push(fieldObj);
				});

				if ($parent_wrapper.data('price-table')) {
					groupObj.table_id = $parent_wrapper.data('price-table');
					groupObj.sale_table_id = $parent_wrapper.data('price-table-sale');
					groupObj.table_x = $parent_wrapper.data('price-table-x');
					groupObj.table_y = $parent_wrapper.data('price-table-y');
					groupObj.table_tx = $parent_wrapper.data('price-table-tx');
					groupObj.table_ty = $parent_wrapper.data('price-table-ty');
				}

				if ($parent_wrapper.data('price-formula')) {
					groupObj.formula = $parent_wrapper.data('price-formula');
					groupObj.rounding = $parent_wrapper.data('price-rounding');
				}

				if ($parent_wrapper.data('group-value')) {
					groupObj.format = $parent_wrapper.data('group-value');
				}

				groupObj.weight = totalWeight;

				groupObj = _formatSingleGroupValues(groupObj);

				if ($parent_wrapper.find('.wpcb-configurator-field-group-price').length) {
					$parent_wrapper.find('.wpcb-configurator-field-group-price').html(_formatPriceOutput(groupObj.cost, '', true));
				}

				wpcb_configuration.selection[group_id] = groupObj;

			} else if ($field_wrapper.data('id')) {

				let field_id = 'field-' + $field_wrapper.data('id');
				let fieldObj = _getSingleFieldValue($field_wrapper, fieldtype);
				fieldObj.type = 'field';

				if ($field_wrapper.parents('.wpcb-configurator-field-filter').length) {
					// field_id = 'filter-' + $field_wrapper.data('id');
					fieldObj.label = $.trim($field_wrapper.parents('.wpcb-configurator-field-filter').data('label'));

					if (fieldObj.values.length) {
						let value = fieldObj.values[0].label;
						fieldObj.format = 'filter';
						fieldObj.value = fieldObj.label + ': ' + value;
					}
				}

				wpcb_configuration.selection[field_id] = fieldObj;

				// Check if linked table prices to this field
				if ($('[data-wpcb-id="' + wpcb_id + '"] input[data-table-field="' + $field_wrapper.data('id') + '"]').length) {
					$('[data-wpcb-id="' + wpcb_id + '"] input[data-table-field="' + $field_wrapper.data('id') + '"]').each(function (index, input) {
						let price_table_id = parseInt($(input).attr('data-price-table'));
						let price_value = 1;
						if ('' !== fieldObj.value) {
							price_value = fieldObj.value;
						}

						let label = $(input).data('label');
						let field_id = $(input).closest('.wpcb-configurator-field').data('id');
						let tax_percent = wpcb_vars.product_tax_percent;
						if ($(input).closest('.wpcb-configurator-field').data('tax')) {
							tax_percent = parseInt($(input).closest('.wpcb-configurator-field').data('tax'));
						}

						_getRangeTableValue(field_id, label, price_table_id, price_value, '', tax_percent);
					});
				}
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-table')) {
				let price_table_id = parseInt($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-table'));
				let fieldId = 'product_price';
				let label = 'Quantity';
				let quantity = _getConfigurationQuantitySelector();
				let value = $(quantity).val();

				_getRangeTableValue(fieldId, label, price_table_id, value, '', wpcb_vars.product_tax_percent);
			}

			if (wpcb_pending_calls.length) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-inline-summary').addClass('wpcb-loading');
				$('.wpcb-configurator-form-totals[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');

				Promise.all(wpcb_pending_calls).then(function (results) {
					results.forEach(function (response, index) {
						let result = JSON.parse(response);

						let id = result.id;
						let label = result.label;
						let price = result.price;

						if (id && id.toString().includes('group')) {
							// Update prices at positions in configuration selections
							let totalPrice = price;
							let totalTax = 0;

							if (wpcb_configuration.selection[result.id].formula) {
								let formula = _fillGroupFormula(
									wpcb_configuration.selection[result.id].formula, // formula
									wpcb_configuration.selection[result.id] // object
								);

								let totals = _calculateTotals();

								formula = formula.replace('${Matrix table}', totalPrice);
								formula = formula.replace('${Product price}', wpcb_vars.product_price);
								formula = formula.replace('${Total price}', totals.total_price);
								formula = formula.replace('${Total weight}', totals.total_weight);

								if (formula.indexOf('${') === -1) {
									totalPrice = eval(formula);
								}

								if (wpcb_configuration.selection[result.id].rounding) {
									totalPrice = _getRoundedFormulaPrice(totalPrice, wpcb_configuration.selection[result.id].rounding);
								}
							}

							if (wpcb_configuration.selection[result.id].tax_percent > 0) {
								totalTax = _getConfigurationOptionTax(totalPrice, wpcb_configuration.selection[result.id].tax_percent);

								if ('excl' === wpcb_vars.settings.price_tax_display && wpcb_vars.settings.price_tax_included) {
									totalPrice -= totalTax;
								} else if ('incl' === wpcb_vars.settings.price_tax_display && !wpcb_vars.settings.price_tax_included) {
									totalPrice += totalTax;
								}
							}

							wpcb_configuration.selection[result.id].tax = totalTax;
							wpcb_configuration.selection[result.id].cost = parseFloat(totalPrice);

							if ($('.wpcb-configurator-field-group[data-id="' + result.id.replace('group-', '') + '"]').find('.wpcb-configurator-field-group-price').length && totalPrice) {
								$('.wpcb-configurator-field-group[data-id="' + result.id.replace('group-', '') + '"]').find('.wpcb-configurator-field-group-price').html(
									_formatPriceOutput(totalPrice, '', true)
								);
							}

						} else if (result.group && wpcb_configuration.selection[result.group]) {

							// Update prices at positions in configuration selections
							wpcb_configuration.selection[result.group].values.forEach((subfield, subkey) => {
								if (subfield.id === parseInt(id)) {
									let totalPrice = 0;

									subfield.values.forEach((value, key) => {
										if (value.label === label) {
											let val_price = parseFloat(price);
											if ('table_unit' == wpcb_configuration.selection[result.group].values[subkey].values[key].price_type) {
												val_price *= wpcb_configuration.selection[result.group].values[subkey].values[key].value;
											}

											wpcb_configuration.selection[result.group].values[subkey].values[key].price = parseFloat(val_price);
											totalPrice += parseFloat(val_price);
										}
									});

									wpcb_configuration.selection[result.group].values[subkey].cost = parseFloat(totalPrice);
									if (wpcb_configuration.selection[result.group].values[subkey].tax_percent > 0) {
										wpcb_configuration.selection[result.group].values[subkey].tax = _getConfigurationOptionTax(totalPrice, wpcb_configuration.selection[result.group].values[subkey].tax_percent);
									}
								}
							});

							if ($('.wpcb-configurator-field-group[data-id="' + result.group.replace('group-', '') + '"]').find('.wpcb-configurator-field-group-price').length) {
								$('.wpcb-configurator-field-group[data-id="' + result.group.replace('group-', '') + '"]').find('.wpcb-configurator-field-group-price').html(
									_formatPriceOutput(wpcb_configuration.selection[result.group].values[subkey].cost, '', true)
								);
							}

						} else if (wpcb_configuration.selection['field-' + id]) {

							let totalPrice = 0;
							let totalTax = 0;
							let tax_percent = 0;
							// Update prices at positions in configuration selections
							wpcb_configuration.selection['field-' + id].values.forEach((value, key) => {
								if (value.label === label) {
									let val_price = parseFloat(price);

									if ('table_unit' == wpcb_configuration.selection['field-' + id].values[key].price_type) {
										val_price *= wpcb_configuration.selection['field-' + id].values[key].value;
									}

									wpcb_configuration.selection['field-' + id].values[key].price = parseFloat(val_price);
								}

								if (wpcb_configuration.selection['field-' + id].values[key].tax_percent) {
									tax_percent = wpcb_configuration.selection['field-' + id].values[key].tax_percent;
								}

								totalPrice += wpcb_configuration.selection['field-' + id].values[key].price;
							});

							// Update option prices for price difference display
							wpcb_configuration.selection['field-' + id].options.forEach((value, key) => {
								if (value.label === label) {
									let val_price = parseFloat(price);
									wpcb_configuration.selection['field-' + id].options[key].price = parseFloat(val_price);
								}
							});

							if (tax_percent > 0) {
								totalTax = _getConfigurationOptionTax(totalPrice, tax_percent);

								if ('excl' === wpcb_vars.settings.price_tax_display && wpcb_vars.settings.price_tax_included) {
									totalPrice -= totalTax;
								} else if ('incl' === wpcb_vars.settings.price_tax_display && !wpcb_vars.settings.price_tax_included) {
									totalPrice += totalTax;
								}
							}

							wpcb_configuration.selection['field-' + id].tax = totalTax;
							wpcb_configuration.selection['field-' + id].cost = parseFloat(totalPrice);

							if ($('.wpcb-configurator-field[data-id="' + id + '"]').find('.wpcb-input-field-price').length) {
								$('.wpcb-configurator-field[data-id="' + id + '"]').find('.wpcb-input-field-price').html(
									_formatPriceOutput(totalPrice, '', true)
								);
							}
						} else if ('product_price' === id) {
							wpcb_vars.product_price = parseFloat(price);

							wpcb_configuration.totals.product_price = parseFloat(price);
							wpcb_configuration.totals.product_qty = parseFloat(result.value);
						}
					});

					_updateConfigurationPrices();

					$('.wpcb-configurator-inline-summary').removeClass('wpcb-loading');
					$('.wpcb-configurator-form-totals').removeClass('wpcb-loading');

					_refreshTotals();

					// Clear var.
					wpcb_pending_calls = [];
				});
			} else {
				_updateConfigurationPrices();

				_refreshTotals();
			}
		}

		function _fillGroupFormula(formula, groupObj = '') {
			let filled_formula = formula;

			if (groupObj && groupObj.values) {
				groupObj.values.forEach(function (subvalue, index) {
					filled_formula = filled_formula.replaceAll('${' + subvalue.id + '}', subvalue.cost);
					filled_formula = filled_formula.replaceAll('${' + subvalue.id + ':value}', subvalue.value);
					filled_formula = filled_formula.replaceAll('${' + subvalue.id + ':length}', subvalue.value.length);
				});

				filled_formula = filled_formula.replaceAll('${' + groupObj.id + ':count}', groupObj.values.length);
				filled_formula = filled_formula.replaceAll('${' + groupObj.id + '}', groupObj.cost);
			}

			if (filled_formula.indexOf('${') !== -1) {
				for (let groupId in wpcb_configuration.selection) {

					if (wpcb_configuration.selection[groupId].values) {
						wpcb_configuration.selection[groupId].values.forEach(function (subvalue, index) {
							filled_formula = filled_formula.replaceAll('${' + subvalue.id + '}', subvalue.cost);
							filled_formula = filled_formula.replaceAll('${' + subvalue.id + ':value}', subvalue.value);
							filled_formula = filled_formula.replaceAll('${' + subvalue.id + ':length}', subvalue.value.length);
						});

						filled_formula = filled_formula.replaceAll('${' + wpcb_configuration.selection[groupId].id + ':count}', wpcb_configuration.selection[groupId].values.length);
						filled_formula = filled_formula.replaceAll('${' + wpcb_configuration.selection[groupId].id + '}', wpcb_configuration.selection[groupId].cost);
					}
				}
			}

			return filled_formula;
		}

		function _formatSingleGroupValues(groupObj) {
			let total_cost = 0;

			if (groupObj.table_id && groupObj.values.length) {
				let tableObj = {
					id: groupObj.id,
					type: 'group',
					tax_rate: groupObj.tax_rate,
					label: groupObj.label,
					matrix: groupObj.table_id,
					matrix_sale: groupObj.sale_table_id,
					label_x: groupObj.table_x,
					label_y: groupObj.table_y,
					type_x: groupObj.table_tx,
					type_y: groupObj.table_ty
				};

				groupObj.values.forEach(function (subvalue, index) {
					if (tableObj.label_x === subvalue.label) {
						tableObj.value_x = subvalue.value;
					}

					if (tableObj.label_y === subvalue.label) {
						tableObj.value_y = subvalue.value;
					}
				});

				if (tableObj.value_x && tableObj.value_y) {
					_getMatrixPriceTableValue(tableObj);
				}
			}

			if (groupObj.formula && groupObj.values.length) {
				wpcb_pending_calls.push(new Promise(function (resolve, reject) {
					resolve(JSON.stringify({
						'id': groupObj.id,
					}));
				}));
				// let totals = _calculateTotals(true);
				// let formula = _fillGroupFormula( groupObj.formula, groupObj );

				// formula = formula.replace('${Product price}', wpcb_vars.product_price);
				// formula = formula.replace('${Total price}', totals.total_price);
				// formula = formula.replace('${Total weight}', totals.total_weight);

				// console.log(formula);

				// if ( formula.indexOf( '${' ) === -1 ) {
				// 	total_cost = eval(formula);
				// }
				// if ( groupObj.rounding ) {
				// 	total_cost = _getRoundedFormulaPrice( total_cost, groupObj.rounding );
				// }
			}

			groupObj.cost = total_cost;

			if (groupObj.format) {
				let format = groupObj.format;

				groupObj.values.forEach(function (subvalue, index) {
					format = format.replace('${' + subvalue.label + '}', subvalue.value);
				});

				if (format.indexOf('${') === -1) {
					groupObj.value = format;
				}
			}

			return groupObj;
		}

		function _getSingleFieldValue($field_wrapper, fieldtype, group_id = '') {

			/**
			 * Single field options.
			 */

			let fieldObj = {
				id: $field_wrapper.data('id'),
				type: fieldtype,
				price_type: 'fixed',
				label: $.trim($field_wrapper.data('label')),
				values: [],
				options: [],
				value: '',
				weight: 0,
				tax: 0,
				cost: 0
			};

			let total_cost = 0;
			let total_percent = 0;
			let total_weight = 0;
			let total_sale_cost = false;
			let total_stock = 999999;
			let price_type = 'fixed';

			if ('option' === fieldtype) {
				let options = 'input';
				let compare = ':checked';
				if ($field_wrapper.hasClass('wpcb-configurator-dropdown')) {
					options = 'option';
					compare = ':selected';
				}

				$field_wrapper.find(options).each(function (index, option) {
					let valueObj = _getChoiceOptionValue(option);

					if (!valueObj.id && !valueObj.label) {
						return;
					}

					valueObj.active = $(option).is(compare);

					if ($(option).closest('.wpcb-option-product').length) {
						// product item
						if ($(option).closest('.wpcb-option').find('input[type=number]').length) {
							// with quantity input
							let quantity = $(option).closest('.wpcb-option').find('input[type=number]').val();

							valueObj.value = quantity;
							valueObj.price = valueObj.price * quantity;

							if (valueObj.sale) {
								valueObj.sale = valueObj.sale * quantity
							}
						}
					}

					if (valueObj.price_percent) {
						price_type = 'percentage';
					}
					if (valueObj.price_formula) {
						price_type = 'formula';
					}

					if ($(option).attr('data-price-table')) {
						price_type = 'table';
						let price_table_id = parseInt($(option).attr('data-price-table'));
						let price_value = 1;

						if ($(option).attr('data-table-field')) {
							let price_id = parseInt($(option).attr('data-table-field'));
							if ($('.wpcb-configurator-field input#' + price_id).length) {
								price_value = $('.wpcb-configurator-field input#' + price_id).val();
							}
							else if (wpcb_configuration.selection['field-' + price_id] && '' !== wpcb_configuration.selection['field-' + price_id].value) {
								price_value = wpcb_configuration.selection['field-' + price_id].value;
							}
						} else {
							let quantity = _getConfigurationQuantitySelector();
							price_value = $(quantity).val();
						}

						_getRangeTableValue(fieldObj.id, valueObj.label, price_table_id, price_value, '', valueObj.tax_percent);
					}

					fieldObj.options.push(valueObj);

					if (valueObj.active) {
						fieldObj.values.push(valueObj);

						total_cost += parseFloat(valueObj.price);
						if (valueObj.sale) {
							total_sale_cost += parseFloat(valueObj.sale);
						} else {
							total_sale_cost += parseFloat(valueObj.price);
						}

						if (valueObj.hasOwnProperty('stock')) {
							if (valueObj.stock < total_stock) {
								total_stock = valueObj.stock;
							}
						}

						if (valueObj.hasOwnProperty('weight')) {
							total_weight += valueObj.weight;
						}

						if (valueObj.hasOwnProperty('tax_percent')) {
							total_percent = valueObj.tax_percent;
						}
					}
				});

			} else if ('input' === fieldtype) {

				if ($field_wrapper.find('input').length) {

					$field_wrapper.find('input').each(function (index, input) {

						let valueObj = _getFieldInputValue(fieldObj.id, input, group_id);
						valueObj.active = true;
						if ('checkbox' === $(input).attr('type')) {
							valueObj.active = $(input).is(':checked');
						}
						if (!valueObj.value && 'checkbox' !== $(input).attr('type')) {
							valueObj.active = false;
						}
						if (valueObj.hasOwnProperty('tax_percent')) {
							total_percent = valueObj.tax_percent;
						}

						fieldObj.options.push(valueObj);

						if ($(input).closest('.wpcb-input-field').find('.wpcb-input-field-price').length) {
							if (valueObj.sale && valueObj.sale < valueObj.price ) {
								$(input).closest('.wpcb-input-field').find('.wpcb-input-field-price').html(
									'<del>' + _formatPriceOutput(valueObj.price, '', true) + '</del> ' + _formatPriceOutput(valueObj.sale, '', false)
								);
							} else {
								$(input).closest('.wpcb-input-field').find('.wpcb-input-field-price').html(_formatPriceOutput(valueObj.price, '', true));
							}
						}

						if (valueObj.active) {
							fieldObj.values.push(valueObj);

							total_cost += parseFloat(valueObj.price);
							if (valueObj.sale) {
								total_sale_cost += parseFloat(valueObj.sale);
							} else {
								total_sale_cost += parseFloat(valueObj.price);
							}
						}
					});

				} else if ($field_wrapper.find('textarea').length) {

					$field_wrapper.find('textarea').each(function (index, textarea) {
						let valueObj = _getFieldInputValue(fieldObj.id, textarea, group_id);
						valueObj.active = true;

						if (!$(textarea).val()) {
							valueObj.active = false;
						}

						fieldObj.options.push(valueObj);

						if (valueObj.hasOwnProperty('tax_percent')) {
							total_percent = valueObj.tax_percent;
						}

						if ($(textarea).closest('.wpcb-input-field').find('.wpcb-input-field-price').length) {
							if (valueObj.sale && valueObj.sale < valueObj.price ) {
								$(textarea).closest('.wpcb-input-field').find('.wpcb-input-field-price').html(
									'<del>' + _formatPriceOutput(valueObj.price, '', true) + '</del> ' + _formatPriceOutput(valueObj.sale, '', false)
								);
							} else {
								$(textarea).closest('.wpcb-input-field').find('.wpcb-input-field-price').html(_formatPriceOutput(valueObj.price, '', true));
							}
						}

						if (valueObj.active) {
							fieldObj.values.push(valueObj);

							total_cost += parseFloat(valueObj.price);
							if (valueObj.sale) {
								total_sale_cost += parseFloat(valueObj.sale);
							} else {
								total_sale_cost += parseFloat(valueObj.price);
							}
						}
					});
				}
			}

			if ($field_wrapper.data('drag')) {
				fieldObj.drag = $field_wrapper.data('drag') == 'yes';
			}
			if ($field_wrapper.data('drag-top')) {
				fieldObj.drag_top = $field_wrapper.data('drag-top');
			}
			if ($field_wrapper.data('drag-left')) {
				fieldObj.drag_left = $field_wrapper.data('drag-left');
			}
			if ($field_wrapper.data('resize')) {
				fieldObj.resize = $field_wrapper.data('resize') == 'yes';
			}
			if ($field_wrapper.data('rotate')) {
				fieldObj.rotate = $field_wrapper.data('rotate') == 'yes';
			}
			if ($field_wrapper.data('slide-order')) {
				fieldObj.layers = $field_wrapper.data('slide-order');
			}
			if ($field_wrapper.data('slide-positions')) {
				fieldObj.indexes = $field_wrapper.data('slide-positions');
			}
			if ($field_wrapper.data('model-group')) {
				fieldObj.model_group = $field_wrapper.data('model-group');
			}
			if ($field_wrapper.data('model-type')) {
				fieldObj.model_type = $field_wrapper.data('model-type');
			}

			fieldObj.cost = total_cost;
			fieldObj.stock = total_stock;
			fieldObj.in_stock = total_stock > 0;
			fieldObj.price_type = price_type;

			if (total_percent > 0 && total_cost > 0) {
				let tax_cost = total_sale_cost ? total_sale_cost : total_cost;
				let tax = _getConfigurationOptionTax(tax_cost, total_percent);
				fieldObj.tax = tax;
			}

			if (total_weight) {
				fieldObj.weight = total_weight;
			}

			if (total_sale_cost) {
				fieldObj.sale_cost = total_sale_cost;
			}

			if ('' === fieldObj.value) {
				let field_value = '';

				fieldObj.values.forEach((field_item_val, field_item_index) => {
					if (field_item_index > 0) {
						field_value += ', ';
					}

					if (field_item_val.input_type === 'file') {
						field_value += field_item_val.label;
					} else {
						field_value += field_item_val.value;
					}
				});

				fieldObj.value = field_value;
			}

			return fieldObj;
		}

		function _getChoiceOptionValue(option) {
			let id = $(option).data('id');
			let input_type = $(option).attr('type');
			let label = $(option).attr('data-label');
			let value = $(option).attr('value');
			let sku = $(option).attr('data-sku');
			let price_key = $(option).attr('data-key');
			let price = 0;
			let price_percent = false;
			let price_field = '';
			let price_formula = '';
			let price_rounding = '';
			let product_id = false;
			let variation_id = false;
			let sale = false;
			let stock = 999999;
			let weight = 0;
			let tax_percent = 0;
			let thumb = false;

			if ('yes' == $(option).closest('.wpcb-configurator-field').data('thumbnail')) {
				// Use thumbnails in visuals
				if ($(option).siblings('.wpcb-option-item').find('.wpcb-option-thumbnail img').length) {
					thumb = $(option).siblings('.wpcb-option-item').find('.wpcb-option-thumbnail img').attr('src');
				}
				else if ($(option).siblings('.wpcb-swatch-inner').length) {
					if ($(option).siblings('.wpcb-swatch-inner').find('img').length) {
						thumb = $(option).siblings('.wpcb-swatch-inner').find('img').attr('src');
					} else {
						thumb = $(option).data('preview-value');
					}
				}
			}

			if ($(option).attr('data-price')) {
				price = parseFloat($(option).attr('data-price'));
			}
			if ($(option).attr('data-sale')) {
				sale = parseFloat($(option).attr('data-sale'));
			}
			if ($(option).attr('data-percent')) {
				price_percent = parseFloat($(option).attr('data-percent'));
			}
			if ($(option).attr('data-formula')) {
				price_formula = $(option).attr('data-formula');
				price_rounding = $(option).attr('data-rounding');
			}
			if ($(option).attr('data-field')) {
				price_field = $(option).attr('data-field');
			}
			if ($(option).attr('data-stock')) {
				stock = parseInt($(option).attr('data-stock'));
			}
			if ($(option).attr('data-weight')) {
				weight = parseFloat($(option).attr('data-weight'));
			}
			if ($(option).closest('.wpcb-configurator-field').attr('data-tax')) {
				tax_percent = parseInt($(option).closest('.wpcb-configurator-field').attr('data-tax'));
			} else if (wpcb_vars.product_tax_percent) {
				tax_percent = wpcb_vars.product_tax_percent;
			}
			if ($(option).attr('data-product-id')) {
				product_id = parseInt($(option).attr('data-product-id'));
			}
			if ($(option).attr('data-variation-id')) {
				variation_id = parseInt($(option).attr('data-variation-id'));
			}

			if ($(option).attr('data-discount') && $(option).attr('data-discount-rules')) {
				var discount_rules = $(option).attr('data-discount-rules');
				if ( _validateDiscountRules( discount_rules ) ) {
					sale = parseFloat( $(option).attr('data-discount') );
				} else {
					sale = false;
				}
			}

			return {
				id,
				label,
				thumb,
				value,
				input_type,
				sku,
				price_key,
				price,
				sale,
				price_field,
				price_percent,
				price_formula,
				price_rounding,
				stock,
				product_id,
				variation_id,
				weight,
				tax_percent,
			};
		}

		function _getFieldInputValue(fieldId, input, groupId = '') {
			let label = $(input).closest('.wpcb-configurator-input').data('label');
			let value = $(input).val();
			let input_type = $(input).attr('type');
			let price = 0;
			let sale = false;
			let price_type = 'fixed';
			let price_key = $(input).attr('data-key');
			let tax_percent = 0;
			let min = 0;

			if ($(input).attr('min')) {
				min = $(input).attr('min');
			}

			if ('file' === $(input).attr('type')) {
				if ($(input).closest('.wpcb-input-content').find('.wpcb-input-file-image img').length) {
					label = $.trim($(input).closest('.wpcb-input-content').find('.wpcb-input-file-name').text());
					value = $(input).closest('.wpcb-input-content').find('.wpcb-input-file-image img').attr('src');
				} else {
					value = '';
				}
			}

			if ($(input).closest('.wpcb-configurator-field').attr('data-tax')) {
				tax_percent = parseInt($(input).closest('.wpcb-configurator-field').attr('data-tax'));
			} else {
				tax_percent = wpcb_vars.product_tax_percent;
			}

			if ($(input).attr('data-price')) {
				price = parseFloat($(input).attr('data-price'));

				if ($(input).attr('data-sale')) {
					sale = parseFloat($(input).attr('data-sale'));
				}
			} else if ($(input).attr('data-price-unit')) {
				price_type = 'unit';
				let price_unit = parseFloat($(input).attr('data-price-unit'));
				let price_unit_sale = false;

				if ( $(input).attr('data-price-unit-sale') ) {
					price_unit_sale = parseFloat($(input).attr('data-price-unit-sale'));
				}

				if ('text' == $(input).attr('type') || 'textarea' == $(input).attr('type')) {
					price = parseFloat(value.toString().replaceAll(' ', '').length * price_unit);

					if ( price_unit_sale ) {
						sale = parseFloat(value.toString().replaceAll(' ', '').length * price_unit_sale);
					}
				} else {
					let price_val = value;
					if (wpcb_vars.settings.price_unit_min_base) {
						price_val = value - min;
					}

					price = parseFloat(price_val * price_unit);
					if ( price_unit_sale ) {
						sale = parseFloat(price_val * price_unit_sale);
					}
				}
			} else if ($(input).attr('data-price-table')) {
				let price_table_id = parseInt($(input).attr('data-price-table'));
				price_type = $(input).data('price-type');

				_getRangeTableValue(fieldId, label, price_table_id, value, groupId, tax_percent);
			}

			return {
				label,
				value,
				input_type,
				price,
				sale,
				price_type,
				price_key,
				tax_percent
			}
		}

		function _initializeRepeater($repeater_main) {
			let repeaterValues = [];
			let repeaterKey = $repeater_main.find('[data-repeater-list]').data('repeater-list');
			let repeaterMax = $repeater_main.find('[data-repeater-list]').data('repeater-max');

			if (wpcb_configuration.selection['repeater-' + repeaterKey] && wpcb_configuration.selection['repeater-' + repeaterKey].values.length) {
				let firstKey = '';
				let repeaterRow = 0;

				wpcb_configuration.selection['repeater-' + repeaterKey].values.forEach(function (item, index) {
					let itemName = item.label.toLowerCase().replaceAll(' ', '-');
					let itemValues = [];
					let itemKey = item.id.toString();
					if ( itemKey.indexOf('-') !== -1 ) {
						itemKey = itemKey.split('-')[0];
					}

					if ( ! firstKey ) {
						firstKey = itemKey;
					}
					else if ( firstKey === itemKey ) {
						repeaterRow++;
					}

					item.values.forEach(function (val, key) {
						itemValues.push(val.value);
					});

					if (!repeaterValues[repeaterRow]) {
						repeaterValues[repeaterRow] = { [itemName]: itemValues };
					} else {
						repeaterValues[repeaterRow][itemName] = itemValues;
					}
				});
			}

			let $repeater = $repeater_main.repeater({
				initEmpty: true,
				min: 0,
				max: repeaterMax,
				defaultValues: {},
				repeaters: [], // nested repeaters
				show: function (element) {
					$repeater_main.find('.wpcb-configurator-repeater-empty-note').hide();

					$(this).parents('[data-repeater-list]').find('[data-repeater-item]').each(function (index, item) {
						$(item).find('.index').text(index + 1);
					});

					if ($(this).hasClass('collapsible')) {
						$(this).prev('[data-repeater-item]').addClass('wpcb-collapsed');
					}

					// _renderAdvancedFields();
					// _setSelectedFieldOptions( $(this) );

					$(this).slideDown();
				},
				hide: function (deleteElement) {
					if (confirm(wpcb_vars.error_repeater)) {
						$(this).slideUp(deleteElement, function () {
							$(deleteElement).remove();

							if ($(this).parents('[data-repeater-list]').find('[data-repeater-item]').length == 1) {
								$('.wpcb-configurator-repeater-empty-note').show();
							} else {
								$(this).parents('[data-repeater-list]').find('[data-repeater-item]:visible').each(function (index, item) {
									$(item).find('.index').text(index + 1);
								});
							}

							$(this).find('input, textarea').each(function (index, input) {
								if ($(input).attr('type') == 'radio' || $(input).attr('type') == 'checkbox') {
									return;
								}

								let positions = [1];
								if ($(input).data('slide-positions')) {
									positions = $(input).data('slide-positions').toString().split(/[,][ ]+/);
								}

								positions.forEach(function (position, index) {
									let inputObj = _getCanvasObjectById($(input).attr('id'), position);
									if (inputObj.length) {
										inputObj[0].set('text', '');
									}
								});
							});

							let repeater = $(this).parents('[data-repeater-list]');

							$(this).remove();

							_updateConfiguration(repeater, 'repeater');

							_refreshTotals();

							_renderConfigurationGallery();
						});
					}
				},
			});

			if (repeaterValues.length > 0) {
				$repeater.setList(repeaterValues);
				$repeater_main.find('.wpcb-configurator-repeater-empty-note').hide();
				_renderAdvancedFields();
				_refreshTotals();
			}

			wpcb_repeater_items[repeaterKey] = $repeater;
		}

		function _emptyRepeater($repeater_main) {
			let repeaterKey = $repeater_main.find('[data-repeater-list]').data('repeater-list');
			let $repeater = wpcb_repeater_items[repeaterKey];
			if ($repeater) {
				$repeater.setList([]);
			}
			$repeater_main.find('.wpcb-configurator-repeater-empty-note').show();
		}

		/**
		 * Configuration form actions
		 */

		// function _handleConfiguratorSummaryAction() {
		// 	$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').each(function(slot, image) {
		// 		let position = slot + 1;
		// 		if ( typeof wpcb_canvasses !== 'undefined' ) {
		// 			let dataUrl = _getCanvasDataUrl( position );
		// 			wpcb_configuration.renders[slot] = dataUrl;
		// 		}
		// 	});

		// 	let configuration_name = '';
		// 	if ( sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id) ) {
		// 		configuration_name = sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id);
		// 	}

		// 	$.ajax({
		// 		type: 'post',
		// 		url: wpcb_vars.ajax_url,
		// 		data: {
		// 			action: 'wpcb_save_configuration_file',
		// 			nonce: wpcb_vars.nonce,
		// 			configuration: wpcb_configuration,
		// 			configuration_name: configuration_name
		// 		},
		//         success: function (data) {
		// 			let response = JSON.parse(data);
		// 			let product_base_url = window.location.origin + window.location.pathname;
		// 			let options = '?wpcb_summary=' + response.filename;

		// 			window.location = product_base_url + options;
		// 		}
		// 	});
		// }

		async function _handleConfiguratorShareAction(configuration_data, qr_code = false) {
			_showLoader();

			let configuration_name = '';
			if (sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id)) {
				configuration_name = sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id);
			}

			$.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: {
					action: 'wpcb_save_configuration_file',
					nonce: wpcb_vars.nonce,
					configuration: configuration_data,
					configuration_name: configuration_name
				},
				success: function (data) {
					let response = JSON.parse(data);
					let product_base_url = window.location.origin + window.location.pathname;
					let options = '?wpcb_configuration=' + response.filename;

					if (!configuration_name) {
						sessionStorage.setItem('wpcb_configuration_file_' + wpcb_id, response.filename);
					}

					if ($('#wpcb-leave-popup-' + wpcb_id + '.active').length) {
						$('#wpcb-leave-popup-' + wpcb_id + '.active').removeClass('active');
					}

					_removeLoader();

					if (qr_code) {
						_wpcbGenerateQRCode(product_base_url + options);

						$('#wpcb-qr-code-popup-' + wpcb_id).addClass('active');
						$('body').addClass('wpcb-panel-active');
					} else {
						$('#wpcb-share-link-popup-' + wpcb_id + ' .wpcb-popup-form-link').data('link', product_base_url + options);
						$('#wpcb-share-link-popup-' + wpcb_id + ' .wpcb_configuration_link').text(product_base_url + options);

						$('#wpcb-share-link-popup-' + wpcb_id).addClass('active');
						$('body').addClass('wpcb-panel-active');
					}

					_updateConfigurationAnalytics('save', { url: product_base_url + options });
				}
			});
		}

		async function _handleConfiguratorWishlistAction() {
			_showLoader();

			// Clone current configuration, in case user wants to add various versions of configuration to wishlist.
			let wpcb_wishlist_item = structuredClone(wpcb_configuration);

			// Capture first image
			wpcb_wishlist_item = await _getCanvasRenders(wpcb_wishlist_item, true);
			wpcb_wishlist_item.wishlist_id = 'wi' + (new Date()).getTime();
			wpcb_wishlist_item.totals = _calculateTotals();

			let page_url = location.protocol + '//' + location.host + location.pathname;
			wpcb_wishlist_item.wishlist_page_url = page_url;

			wpcb_wishlist_item_ids.push(wpcb_wishlist_item.wishlist_id);

			localStorage.setItem('wpcb_wishlist_item_ids', JSON.stringify(wpcb_wishlist_item_ids));

			$.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: {
					action: 'wpcb_update_wishlist',
					nonce: wpcb_vars.nonce,
					configuration: wpcb_wishlist_item,
					configuration_name: wpcb_wishlist_item.wishlist_id,
					wishlist_items: wpcb_wishlist_item_ids
				},
				success: function (result) {
					wpcb_wishlist_items = result.data.map(item => JSON.parse(item));

					_removeLoader();

					let options = _getSimplifiedConfigurationOptions(wpcb_wishlist_item.selection);
					_updateConfigurationAnalytics('wishlist', {
						options: options,
						price: wpcb_wishlist_item.totals.total_price,
					});

					// Refresh table.
					_setWishlistTableHtml();
				}
			});
		}

		function _handleConfiguratorFilterResetAction(filter) {
			$(filter).find('.wpcb-filter-item-thumbnail').html('');
			$(filter).find('.wpcb-filter-item-label').html($(filter).find('.wpcb-filter-item').data('label'));
			$(filter).find('.wpcb-filter-item-note').html('');
			$(filter).find('.wpcb-filter-item-totals').html('');

			$(filter).find('input:checked').prop('checked', false);
			$(filter).find('.wpcb-configurator-filter-clear-action').addClass('hidden');
		}

		function _handleConfiguratorResetAction() {
			// Clone base configuration.
			wpcb_configuration = structuredClone(wpcb_base_configuration);

			// Reset form data.
			wpcb_configuration.step.visited = 1;
			wpcb_configuration.step.active = 1;

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button').removeClass('visited');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button').removeClass('visited');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field').removeClass('.wpcb-invalid');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field-summary').removeClass('active');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-error-note').remove();

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-repeater-main').length) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-repeater-main').each(function (index, repeater) {
					_emptyRepeater($(repeater));
				});
			}

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field-filter-main').each(function (item, filter) {
				_handleConfiguratorFilterResetAction(filter);
			});

			_setActiveStep();

			_setDefaultConfigurationOptions();

			_updateFormFieldDisplay();

			if (wpcb_model_viewer) {
				// Refresh model viewer view
				const currentSrc = wpcb_model_viewer.src;
				wpcb_model_viewer.src = wpcb_model_viewer.src + '?refresh=true';

				setTimeout(() => {
					wpcb_model_viewer.src = currentSrc;

					wpcb_scene_sym = Object.getOwnPropertySymbols(wpcb_model_viewer).find(x => x.description === "scene");
					wpcb_model_viewer[wpcb_scene_sym].traverse((child) => {
						if (child.type && child.type == "Mesh") {
							wpcb_model_objects.push(child);
						}
					});

					if (wpcb_vars.base_nodes.length) {
						wpcb_visible_nodes = structuredClone(wpcb_vars.base_nodes);
						_updateViewerNodes();
					}

					if (wpcb_vars.base_hotspots.length) {
						wpcb_visible_hotspots = structuredClone(wpcb_vars.base_hotspots);
						_updateViewerHotspots();
					}
				}, 100);
			}

			// Refresh option list (if not done already).
			_refreshTotals();

			// Clean URL
			window.history.pushState({}, document.title, window.location.origin + window.location.pathname);
		}

		async function _handleConfiguratorQuoteAction(quoteType = [], $button = false) {

			_showLoader();

			let filename = '';
			let configuration = structuredClone(wpcb_configuration);
			let queryString = window.location.search.replace('?', '');
			let isPopupForm = true;
			let product_url = window.location.origin + window.location.pathname;

			configuration = await _getCanvasRenders(configuration, true, true);

			configuration.url = product_url;
			configuration.totals = _calculateTotals();

			// Prepare POST JSON file
			let config_data = JSON.stringify(configuration);
			let config_utf8 = new TextEncoder().encode(config_data);
			let binary = "";
			for (let i = 0; i < config_utf8.length; i++) {
				binary += String.fromCharCode(config_utf8[i]);
			}
			config_data = btoa(binary);

			if (queryString.includes('wpcb_summary')) {
				let optionsParts = queryString.split('&');
				for (let index in optionsParts) {
					if (optionsParts[index].includes('wpcb_summary=')) {
						filename = optionsParts[index].replace('wpcb_summary=', '');
					}
				}
			}

			let $formWrapper = $('#wpcb-single-request-quote-form-' + wpcb_id);
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-inline-quote-form').length) {
				$formWrapper = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-inline-quote-form');
				isPopupForm = false;
			}

			if (quoteType.includes('pdf')) {
				// Not on summary page.
				// Generate configuration link.
				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_pdf_file_path',
						nonce: wpcb_vars.nonce,
						filename: filename,
						configuration: config_data,
					},
					success: function (data) {
						let pdf_url = data.file_url;
						let pdfname = data.filename;

						$formWrapper.find('.wpcb-form-field-pdf input').attr('value', pdf_url);
						if ($formWrapper.find('.wpcb-form-field-pdf .wpcb-form-field-preview').length) {
							$formWrapper.find('.wpcb-form-field-pdf .wpcb-form-field-preview').html('<img src="' + wpcb_vars.icon_pdf + '"><p>' + pdfname + '</p>');
						} else {
							$formWrapper.find('.wpcb-form-field-pdf').append('<div class="wpcb-form-field-preview"><img src="' + wpcb_vars.icon_pdf + '"><p>' + pdfname + '</p></div>');
						}

						if (isPopupForm) {
							$formWrapper.addClass('active');
							$('body').addClass('wpcb-panel-active');
						} else {
							$formWrapper.removeClass('hidden');
						}

						_removeLoader();
					}
				});
			}

			if (quoteType.includes('image') && configuration.renders.length) {
				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_configurator_image_link',
						nonce: wpcb_vars.nonce,
						image: configuration.renders[0],
					},
					success: function (result) {
						let image = result.data;

						if ($formWrapper.find('.wpcb-form-field-image .wpcb-form-field-preview').length) {
							$formWrapper.find('.wpcb-form-field-image .wpcb-form-field-preview').html('<img src="' + image.url + '" width="32px" height="32px"><p>' + image.name + '</p>');
						} else {
							$formWrapper.find('.wpcb-form-field-image').append('<div class="wpcb-form-field-preview"><img src="' + image.url + '"><p>' + image.name + '</p></div>');
						}

						$formWrapper.find('.wpcb-form-field-image input').attr('value', image.url);

						if ( ! quoteType.includes('link') && ! quoteType.includes('pdf') ) {
							if (isPopupForm) {
								$formWrapper.addClass('active');
								$('body').addClass('wpcb-panel-active');
							} else {
								$formWrapper.removeClass('hidden');
							}

							_removeLoader();
						}
					}
				});
			}

			if (quoteType.includes('link')) {
				if ('' === filename) {
					// Not on summary page.
					// Generate configuration link.
					let configuration_name = '';
					if (sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id)) {
						configuration_name = sessionStorage.getItem('wpcb_configuration_file_' + wpcb_id);
					}

					// Prepare POST JSON file
					let link_config = structuredClone( configuration );
					// Clean images to reduce server space.
					link_config.canvasses = {};
					link_config.renders = [];

					let link_config_data = JSON.stringify(link_config);
					let link_config_utf8 = new TextEncoder().encode(link_config_data);
					let link_binary = "";
					for (let i = 0; i < link_config_utf8.length; i++) {
						link_binary += String.fromCharCode(link_config_utf8[i]);
					}
					link_config_data = btoa(link_binary);

					$.ajax({
						type: 'post',
						url: wpcb_vars.ajax_url,
						data: {
							action: 'wpcb_save_configuration_file',
							nonce: wpcb_vars.nonce,
							configuration: link_config_data,
							configuration_name: configuration_name
						},
						success: function (data) {
							let response = JSON.parse(data);
							let options = '?wpcb_configuration=' + response.filename;
							let final_url = product_url + options;

							if ('' === filename) {
								filename = response.filename;
							}

							$formWrapper.find('.wpcb-form-field-url input').attr('value', final_url);

							if ($formWrapper.find('.wpcb-form-field-url .wpcb-form-field-preview').length) {
								$formWrapper.find('.wpcb-form-field-url .wpcb-form-field-preview').html('<img src="' + wpcb_vars.icon_link + '"><p>' + filename + '</p>');
							} else {
								$formWrapper.find('.wpcb-form-field-url').append('<div class="wpcb-form-field-preview"><img src="' + wpcb_vars.icon_link + '"><p>' + filename + '</p></div>');
							}

							if (isPopupForm) {
								$formWrapper.addClass('active');
								$('body').addClass('wpcb-panel-active');
							} else {
								$formWrapper.removeClass('hidden');
							}

							_removeLoader();
						}
					});
				} else {
					// On summary page.
					// Collect configuration link.
					let options = '?wpcb_configuration=' + filename;
					let final_url = product_url + options;

					$formWrapper.find('.wpcb-form-field-url input').attr('value', final_url);

					if ($formWrapper.find('.wpcb-form-field-url .wpcb-form-field-preview').length) {
						$formWrapper.find('.wpcb-form-field-url .wpcb-form-field-preview').html('<img src="' + wpcb_vars.icon_link + '"><p>' + filename + '</p>');
					} else {
						$formWrapper.find('.wpcb-form-field-url').append('<div class="wpcb-form-field-preview"><img src="' + wpcb_vars.icon_link + '"><p>' + filename + '</p></div>');
					}

					if (isPopupForm) {
						$formWrapper.addClass('active');
						$('body').addClass('wpcb-panel-active');
					} else {
						$formWrapper.removeClass('hidden');
					}

					_removeLoader();
				}
			}

			let field_text = '';
			if (quoteType.includes('field')) {
				// _collect summary email text
				field_text = _getConfigurationOptionsText(wpcb_configuration.selection);
				$formWrapper.find('.wpcb-form-field-summary textarea').val(field_text);

				if ( ! quoteType.includes('pdf') && ! quoteType.includes('link') ) {
					if (isPopupForm) {
						$formWrapper.addClass('active');
						$('body').addClass('wpcb-panel-active');
					} else {
						$formWrapper.removeClass('hidden');
					}

					_removeLoader();
				}
			}

			let options = _getSimplifiedConfigurationOptions(configuration.selection);
			let data = {
				options: options,
				price: configuration.totals.total_price,
				vat: configuration.totals.total_tax
			};
			if ($formWrapper.find('.wpcb-form-field-image').length) {
				data.image = $formWrapper.find('.wpcb-form-field-image').attr('value');
			}
			if ($formWrapper.find('.wpcb-form-field-url').length) {
				data.url = $formWrapper.find('.wpcb-form-field-url').attr('value');
			}
			if ($formWrapper.find('.wpcb-form-field-pdf').length) {
				data.pdf = $formWrapper.find('.wpcb-form-field-pdf').attr('value');
			}

			if ($button && !$button.hasClass('wpcb-tracked')) {
				// Prevent excessive duplicate tracking
				_updateConfigurationStatistics(configuration.selection);
				_updateConfigurationAnalytics('quote', data);

				$button.addClass('wpcb-tracked');
			}
		}

		async function _handleConfiguratorPdfDownloadAction( $button = false ) {
			// Clone current configuration, in case user wants to add various versions of configuration to wishlist.
			let configuration = structuredClone(wpcb_configuration);
			configuration = await _getCanvasRenders(configuration, false, true);

			let product_url = window.location.origin + window.location.pathname;
			configuration.url = product_url;
			configuration.totals = _calculateTotals();

			let data = {
				action: 'wpcb_download_configuration_pdf',
				nonce: wpcb_vars.nonce,
				configuration: configuration,
			};

			$.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: data,
				success: function (response) {
					// PDF data
					const response_body = response;

					// Extract the base64-encoded part
					const base64Data = response_body.file.split(',')[1];
					const binaryPdfData = atob(base64Data);
					const uint8Array = new Uint8Array(binaryPdfData.length);
					for (let i = 0; i < binaryPdfData.length; i++) {
						uint8Array[i] = binaryPdfData.charCodeAt(i);
					}
					const pdfBlob = new Blob([uint8Array], { type: 'application/pdf' });

					// Create a URL for the Blob
					const pdfUrl = URL.createObjectURL(pdfBlob);

					if ($button && !$button.hasClass('wpcb-tracked')) {
						// Prevent excessive duplicate tracking
						_updateConfigurationStatistics(configuration.selection);
						_updateConfigurationAnalytics('pdf', { url: pdfUrl });

						$button.addClass('wpcb-tracked');
					}

					_removeLoader();

					const downloadLink = document.createElement('a');
					downloadLink.href = pdfUrl;
					downloadLink.download = response_body.file_name;
					document.body.appendChild(downloadLink);
					downloadLink.click();
					document.body.removeChild(downloadLink);
					URL.revokeObjectURL(pdfUrl);
				}
			});
		}

		async function _handleConfiguratorCartAction(configuration) {
			configuration = await _getCanvasRenders(configuration, true);

			if (!configuration.product_id) {
				configuration.url = window.location.href;

				if (!$('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="wpcb_redirect_uri"]').length) {
					$('form.cart[data-wpcb-id="' + wpcb_id + '"]').append('<input type="hidden" name="wpcb_redirect_uri" value="' + window.location.href + '">');
				}
			}

			configuration.totals = _calculateTotals();

			let config_data = JSON.stringify(configuration);
			let config_utf8 = new TextEncoder().encode(config_data);
			let binary = "";
			for (let i = 0; i < config_utf8.length; i++) {
				binary += String.fromCharCode(config_utf8[i]);
			}
			config_data = btoa(binary);

			if ($('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="wpcb_configuration_data"]').length) {
				$('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="wpcb_configuration_data"]').attr('value', config_data);
			} else {
				$('form.cart[data-wpcb-id="' + wpcb_id + '"]').append('<input type="hidden" name="wpcb_configuration_data" value="' + config_data + '">');
			}

			if (configuration.totals.product_qty > 1) {
				if (!$('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name=quantity]').length) {
					$('form.cart[data-wpcb-id="' + wpcb_id + '"]').append('<input type="hidden" name="quantity" value="' + configuration.totals.product_qty + '">');
				} else {
					$('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name=quantity]').val(configuration.totals.product_qty);
				}
			}

			return configuration;
		}

		// function _showNotice( notice_html ) {
		// 	$('.wpcb-notice-wrapper').html( notice_html );
		// 	$('.wpcb-notice-wrapper').addClass('active');

		// 	if (wpcb_notice_timer) {
		// 		clearTimeout(wpcb_notice_timer);
		// 	}

		// 	wpcb_notice_timer = setTimeout(function() {
		// 		$('.wpcb-notice-wrapper').removeClass('active');
		// 		$('.wpcb-notice-wrapper').html('');
		// 	}, 12000);
		// }

		function _setWishlistTableHtml() {
			let wishlist_html = '<div class="wpcb-wishlist-table">';
			let wishlist_item_html = '';

			if (wpcb_wishlist_items.length) {
				wpcb_wishlist_items.forEach(function (wishlist_item, wishlist_key) {
					let wishlist_options_html = _getConfigurationOptionsHtml(wishlist_item.selection);
					let wishlist_price = '';

					if (wpcb_vars.settings.price_visible) {
						if (wishlist_item.totals.sale_price && wishlist_item.totals.sale_price < wishlist_item.totals.total_price) {
							wishlist_price = `<p><del>${_formatPriceOutput(wishlist_item.totals.total_price)}</del> ${_formatPriceOutput(wishlist_item.totals.sale_price)}</p>`;
						} else {
							wishlist_price = `<p>${_formatPriceOutput(wishlist_item.totals.total_price)}</p>`;
						}
					}

					let wishlist_render = '';
					if (wishlist_item.renders.length) {
						wishlist_render = `<div class="wpcb-wishlist-table-item-image">
							<img src="${wishlist_item.renders[0]}" alt="${wishlist_item.name}">
						</div>`;
					}

					wishlist_item_html = `<div class="wpcb-wishlist-table-row">
						<div class="wpcb-wishlist-table-item">
							<div class="wpcb-wishlist-table-item-heading">
								<div class="wpcb-wishlist-table-item-title">
									<strong>${wishlist_item.name}</strong>
									${wishlist_price}
								</div>
								<div class="wpcb-wishlist-table-item-actions">
									<a href="#" class="wpcb-wishlist-action wpcb-wishlist-load-item" data-id="${wishlist_item.wishlist_id}">
										<img src="${wpcb_vars.icon_load}" width="20" height="20" alt="Load icon">
										<span class="screen-reader-text">Load wishlist item</span>
									</a>
									<a href="#" class="wpcb-wishlist-action wpcb-wishlist-remove-item" data-id="${wishlist_item.wishlist_id}">
										<img src="${wpcb_vars.icon_remove}" width="20" height="20" alt="Remove icon">
										<span class="screen-reader-text">Remove wishlist item</span>
									</a>
								</div>
							</div>
							<div class="wpcb-wishlist-table-item-details">
								${wishlist_render}
								<div class="wpcb-wishlist-table-item-options">
									${wishlist_options_html}
								</div>
							</div>
						</div>
					</div>`;

					wishlist_html += wishlist_item_html;
				});
			} else {
				wishlist_html += '<p>' + wpcb_vars.wishlist_empty_label + '</p>';
			}

			wishlist_html += '</div>';

			$('#wpcb-account-wishlist-' + wpcb_id + ' .wpcb-panel-content').html('<div class="wpcb-wishlist-table">' + wishlist_item_html + '</div>');
			$('#wpcb-wishlist-' + wpcb_id + ' .wpcb-panel-content').html(wishlist_html);
		}

		function _getConfigurationOptionsHtml(selections) {
			let options_html = '';

			Object.keys(selections).forEach((group_name) => {
				let selected_group = selections[group_name];

				if (typeof selected_group.value === 'undefined') {
					return;
				}

				if ('' === selected_group.value && !selected_group.values || 0 == selected_group.values.length) {
					return;
				}

				let group_display_price = selected_group.sale_cost ? selected_group.sale_cost : selected_group.cost;
				if (wpcb_vars.settings.price_visible) {
					options_html += `<dl><dt><span class="wpcb-table-label">${selected_group.label}</span><span class="wpcb-table-price">${_formatPriceOutput(group_display_price)}</span></td>`;
				} else {
					let option_group_value = '';
					if (selected_group.values.length === 1) {
						option_group_value = '<span class="wpcb-table-value">';
						if (selected_group.values[0].input_type === 'file') {
							option_group_value += selected_group.values[0].label;
						} else {
							option_group_value += selected_group.values[0].value;
						}

						if (wpcb_vars.settings.summary_sku_visible && selected_group.values[0].sku) {
							option_group_value += ' (' + selected_group.values[0].sku + ')';
						}

						option_group_value += '</span>';
					}

					options_html += `<dl><dt><span>${selected_group.label}</span>${option_group_value}</td>`;
				}

				if (selected_group.format) {
					options_html += `<dd>${selected_group.value}</dd>`;
				} else if (selected_group.values.length > 1) {
					Object.keys(selected_group.values).forEach((value_key) => {
						let selected_option = selected_group.values[value_key];

						if (selected_option.hasOwnProperty('values')) {
							if (selected_option.values.length) {
								// Grouped field value.
								options_html += `<dd>${selected_option.label}: `;

								let optionLength = selected_option.values.length;
								selected_option.values.forEach((selected_sub_val, index) => {
									if (selected_sub_val.product_id) {
										options_html += selected_sub_val.label;
										if (selected_sub_val.value > 1) {
											options_html += ': ' + selected_sub_val.value;
										}
									}
									else if (selected_sub_val.input_type === 'file') {
										options_html += selected_sub_val.label;
									}
									else {
										options_html += selected_sub_val.value;
									}

									if (wpcb_vars.settings.summary_sku_visible && selected_sub_val.sku) {
										options_html += ' (' + selected_sub_val.sku + ')';
									}

									if (index + 1 !== optionLength) {
										options_html += ', ';
									}
								});

								options_html += `</dd>`;
							}
						} else {
							// Multiple single field value.
							let display_price = selected_option.sale ? selected_option.sale : selected_option.price;

							if (selected_group.values[0].product_id) {
								options_html += `<dd>${selected_option.label}`;
								if (selected_group.values[0].value > 1) {
									options_html += ': ' + selected_option.value;
								}
							}
							else if (selected_group.values[0].input_type === 'file') {
								options_html += `<dd>${selected_option.label}`;
							}
							else {
								options_html += `<dd>${selected_option.value}`;
							}

							if (wpcb_vars.settings.summary_sku_visible && selected_option.sku) {
								options_html += ' (' + selected_option.sku + ')';
							}

							if (wpcb_vars.settings.price_visible && !selected_group.values[0].product_id) {
								options_html += `: ${_formatPriceOutput(display_price)}`;
							}

							options_html += `</dd>`;
						}
					});
				} else if (selected_group.values.length) {
					if (wpcb_vars.settings.price_visible) {
						options_html += '<dd>';

						if (selected_group.values[0].product_id) {
							options_html += selected_group.values[0].label;
							if (selected_group.values[0].value > 1) {
								options_html += ': ' + selected_group.values[0].value;
							}
						}
						else if (selected_group.values[0].input_type === 'file') {
							options_html += selected_group.values[0].label;
						}
						else {
							options_html += selected_group.values[0].value;
						}

						if (wpcb_vars.settings.summary_sku_visible && selected_group.values[0].sku) {
							options_html += ' (' + selected_group.values[0].sku + ')';
						}

						options_html += '</dd>';
					}
				}

				options_html += `</dl>`;
			});

			return options_html;
		}

		function _getConfigurationOptionsText(selections, is_email = false) {
			let options_html = '';
			let sep_code = is_email ? '%0D%0A' : '\r\n';
			let space = is_email ? '%20' : ' ';

			Object.keys(selections).forEach((group_name) => {
				let selected_group = selections[group_name];

				if (typeof selected_group.value === 'undefined') {
					return;
				}

				if ('' === selected_group.value && !selected_group.values) {
					return;
				}

				let group_display_price = selected_group.sale_cost ? selected_group.sale_cost : selected_group.cost;
				if (wpcb_vars.settings.price_visible) {
					options_html += sep_code + selected_group.label + ':' + space + _formatPriceOutput(group_display_price);
				} else {
					let option_group_value = '';
					if (selected_group.values.length === 1) {
						if (selected_group.values[0].input_type === 'file') {
							option_group_value += selected_group.values[0].label;
						} else {
							option_group_value += selected_group.values[0].value;
						}

						if (wpcb_vars.settings.summary_sku_visible && selected_group.values[0].sku) {
							option_group_value += space + '(' + selected_group.values[0].sku + ')';
						}
					}

					options_html += sep_code + selected_group.label + ':' + space + option_group_value;
				}

				if (selected_group.format) {
					options_html += sep_code + selected_group.value;
				} else if (selected_group.values.length > 1) {
					Object.keys(selected_group.values).forEach((value_key) => {
						let selected_option = selected_group.values[value_key];

						if (selected_option.hasOwnProperty('values')) {
							if (selected_option.values.length) {
								// Grouped field value.
								options_html += sep_code + selected_option.label + ':' + space;

								let optionLength = selected_option.values.length;
								selected_option.values.forEach((selected_sub_val, index) => {
									if (selected_sub_val.product_id) {
										options_html += selected_sub_val.label;
										if (selected_sub_val.value > 1) {
											options_html += ':' + space + selected_sub_val.value;
										}
									}
									else if (selected_sub_val.input_type === 'file') {
										options_html += selected_sub_val.label;
									}
									else {
										options_html += selected_sub_val.value;
									}

									if (wpcb_vars.settings.summary_sku_visible && selected_sub_val.sku) {
										options_html += space + '(' + selected_sub_val.sku + ')';
									}

									if (index + 1 !== optionLength) {
										options_html += ',' + space;
									}
								});
							}
						} else {
							// Multiple single field value.
							let display_price = selected_option.sale ? selected_option.sale : selected_option.price;

							if (selected_group.values[0].product_id) {
								options_html += sep_code + selected_option.label;
								if (selected_group.values[0].value > 1) {
									options_html += ':' + space + + selected_option.value;
								}
							}
							else if (selected_group.values[0].input_type === 'file') {
								options_html += sep_code + selected_option.label;
							}
							else {
								options_html += sep_code + selected_option.value;
							}

							if (wpcb_vars.settings.summary_sku_visible && selected_option.sku) {
								options_html += space + '(' + selected_option.sku + ')';
							}

							if (wpcb_vars.settings.price_visible && !selected_group.values[0].product_id) {
								options_html += ':' + space + _formatPriceOutput(display_price);
							}
						}
					});
				} else if (selected_group.values.length) {
					if (wpcb_vars.settings.price_visible) {
						options_html += sep_code;

						if (selected_group.values[0].product_id) {
							options_html += selected_group.values[0].label;
							if (selected_group.values[0].value > 1) {
								options_html += ':' + space + selected_group.values[0].value;
							}
						}
						else if (selected_group.values[0].input_type === 'file') {
							options_html += selected_group.values[0].label;
						}
						else {
							options_html += selected_group.values[0].value;
						}

						if (wpcb_vars.settings.summary_sku_visible && selected_group.values[0].sku) {
							options_html += space + '(' + selected_group.values[0].sku + ')';
						}
					}
				}
			});

			return options_html;
		}

		function _getSimplifiedConfigurationOptions(selections) {
			let options_obj = {};

			Object.keys(selections).forEach((group_name) => {
				let selected_group = selections[group_name];

				if (typeof selected_group.value === 'undefined') {
					return;
				}
				if ('' === selected_group.value && !selected_group.values) {
					return;
				}

				let option_group_value = '';
				if (selected_group.values.length === 1) {
					if (selected_group.values[0].input_type === 'file') {
						option_group_value = selected_group.values[0].label;
					} else {
						option_group_value = selected_group.values[0].value;
					}

					options_obj[selected_group.label] = option_group_value;
				}
				else if (selected_group.values.length > 1) {
					Object.keys(selected_group.values).forEach((value_key) => {
						let selected_option = selected_group.values[value_key];

						if (selected_option.hasOwnProperty('values')) {
							if (selected_option.values.length) {
								// Grouped field value.
								let optionLength = selected_option.values.length;
								selected_option.values.forEach((selected_sub_val, index) => {
									if (selected_sub_val.input_type === 'file') {
										option_group_value += selected_sub_val.label;
									} else {
										option_group_value += selected_sub_val.value;
									}

									if (index + 1 !== optionLength) {
										option_group_value += ', ';
									}
								});
							}
						} else {
							// Multiple single field value.
							if (selected_group.values[0].input_type === 'file') {
								option_group_value += `${selected_option.label}`;
							} else {
								option_group_value += `${selected_option.value}`;
							}

							if (selected_group.values[value_key + 1] && selected_group.values[value_key + 1].length) {
								option_group_value += ', ';
							}
						}
					});

					options_obj[selected_group.label] = option_group_value;
				} else if (selected_group.values.length) {
					if (selected_group.values[0].input_type === 'file') {
						options_obj[selected_group.label] = selected_group.values[0].label;
					} else {
						options_obj[selected_group.label] = selected_group.values[0].value;
					}
				}
			});

			return options_obj;
		}

		function _getConfigurationCollapsibleOptionSummary($field, group_name) {
			let selected_group = wpcb_configuration.selection[group_name];
			if (!selected_group) {
				return;
			}

			let hide_price = false;
			let hide_label = false;
			if ($field.closest('.wpcb-configurator-field').data('label-hidden')) {
				hide_label = true;
			}
			if ($field.closest('.wpcb-configurator-field').data('price-hidden')) {
				hide_price = true;
			}

			let collapsible_value_html = '';
			let value_price = 0;

			if (selected_group.values && selected_group.values.length) {
				let valuesCount = selected_group.values.length;

				selected_group.values.forEach((val, index) => {
					if (!hide_label) {
						if (val.input_type === 'file') {
							collapsible_value_html += `${val.label}`;
						} else if ( val.product_id && ! isNaN( val.value ) ) {
							if ( val.value != '1' ) {
								collapsible_value_html += `${val.label}: ${val.value}`;
							} else {
								collapsible_value_html += `${val.label}`;
							}
						} else {
							collapsible_value_html += `${val.value}`;
						}
					}

					if (index + 1 < valuesCount) {
						collapsible_value_html += ', ';
					}

					if (!hide_price) {
						if (val.price && wpcb_vars.settings.price_visible) {
							if (val.sale) {
								value_price += val.sale;
							} else {
								value_price += val.price;
							}
						}
					}
				})
			}

			if ('' === collapsible_value_html) {
				collapsible_value_html = wpcb_vars.collapse_empty_label;
			}

			collapsible_value_html = '<span>' + collapsible_value_html + '</span>';
			if (value_price > 0) {
				collapsible_value_html += '<span>' + _formatPriceOutput(value_price, '', true) + '</span>';
			}

			return collapsible_value_html;
		}

		function _getConfigurationFieldSummary($field, group_name, field_id = false) {
			let selected_group = wpcb_configuration.selection[group_name];
			if (!selected_group) {
				return;
			}

			let hide_price = false;
			let hide_label = false;
			if ($field.closest('.wpcb-configurator-field').data('label-hidden')) {
				hide_label = true;
			}
			if ($field.closest('.wpcb-configurator-field').data('price-hidden')) {
				hide_price = true;
			}
			if ($field.closest('.wpcb-configurator-field-group').data('single-price')) {
				hide_price = true;
			}
			if (hide_label) {
				return;
			}

			let collapsible_value_html = '';
			let value_price = 0;
			if (selected_group.values && selected_group.values.length) {
				if (selected_group.values && selected_group.values.length) {
					if (false !== field_id) {
						let field_group = false;
						selected_group.values.forEach((field, index) => {
							if (field.id == field_id) {
								field_group = field;
								return;
							}
						});

						if (!field_group) {
							return collapsible_value_html;
						}
						selected_group = field_group;
					}
				}

				let valuesCount = selected_group.values.length;
				selected_group.values.forEach((val, index) => {
					if (!hide_label) {
						if (val.input_type === 'file') {
							collapsible_value_html += `${val.label}`;
						} else if ( val.product_id && ! isNaN( val.value ) ) {
							if ( val.value != '1' ) {
								collapsible_value_html += `${val.label}: ${val.value}`;
							} else {
								collapsible_value_html += `${val.label}`;
							}
						} else {
							collapsible_value_html += `${val.value}`;
						}
					}

					if (index + 1 < valuesCount) {
						collapsible_value_html += ', ';
					}

					if (!hide_price) {
						if (val.price && wpcb_vars.settings.price_visible) {
							if (val.sale) {
								value_price += val.sale;
							} else {
								value_price += val.price;
							}
						}
					}
				})
			}

			collapsible_value_html = '<span>' + collapsible_value_html;
			if (value_price > 0) {
				collapsible_value_html += ' ' + _formatPriceOutput(value_price, '', true);
			}
			collapsible_value_html += '</span>';

			return collapsible_value_html;
		}

		function _renderAdvancedFields() {

			$('.wpcb-image-dropdown select').each(function (index, selectmenu) {
				let $selectmenu = $(selectmenu).selectmenu(_getSelectMenuOptions());
				$selectmenu.data("ui-selectmenu")._renderItem = _selectMenuItemRenderer;
				$selectmenu.data("ui-selectmenu")._renderButtonItem = _selectMenuButtonRenderer;

				if ($('.wpcb-is-rtl').length) {
					$('.ui-menu.ui-widget').addClass('wpcb-is-rtl');
				}
				if ($('.wpcb-configurator-form .wpcb-border-square').length) {
					$('.ui-menu.ui-widget').addClass('wpcb-border-square');
				}
				if ($('.wpcb-configurator-form .wpcb-border-pill').length) {
					$('.ui-menu.ui-widget').addClass('wpcb-border-pill');
				}
			});

			if (typeof Swiper !== 'undefined') {
				// Check if our swiper instance exists.
				$('.wpcb-configurator-field[data-slider="yes"]').each(function (index, slider) {
					let slides = parseInt($(slider).data('rows'));
					let slidesTb = parseInt($(slider).data('rows-tablet'));
					let slidesMb = parseInt($(slider).data('rows-mobile'));
					let rows = parseInt($(slider).data('slider-rows'));
					let arrows = $(slider).find('.swiper-button-next').length;
					let scrollbar = $(slider).find('.swiper-scrollbar').length;
					let swiperConfig = {};

					if ('vertical' == $(slider).data('slider-dir')) {
						let height = $(slider).find('.wpcb-swatch-option').height() + 20;

						$(slider).find('.swiper').css('height', height * rows);
						$(slider).find('.swiper-slide').css('height', 'auto');

						swiperConfig = {
							direction: 'vertical',
							slidesPerView: 'auto',
							freeMode: true,
							mousewheel: true,
						};
					} else {
						swiperConfig = {
							slidesPerView: slidesMb,
							breakpoints: {
								768: {
									slidesPerView: slidesTb,
								},
								1200: {
									slidesPerView: slides,
								}
							}
						};
					}

					if (arrows) {
						$(slider).find('.swiper').addClass('swiper-has-arrows');
						swiperConfig.navigation = {
							nextEl: '.swiper-button-next',
							prevEl: '.swiper-button-prev',
						};
					}
					if (scrollbar) {
						$(slider).find('.swiper').addClass('swiper-has-scrollbar');
						swiperConfig.scrollbar = {
							el: '.swiper-scrollbar',
							draggable: true,
						};
					}

					new Swiper($(slider).find('.swiper').get(0), swiperConfig);
				});
			}

			if (typeof Coloris !== 'undefined') {
				// Check if our Coloris instance exists.
				Coloris({
					el: '.wpcb-configurator-colorpicker input[type=text]',
					alpha: false,
					themeMode: wpcb_vars.settings.icon_theme == 'dark' ? 'light' : 'dark',
					swatches: []
				});
			}

			if ($('.wpcb-configurator-datepicker').length) {
				$('.wpcb-configurator-datepicker input').each(function (index, datepicker) {
					let options = {};
					if ($(datepicker).data('date-format')) {
						options.dateFormat = $(datepicker).data('date-format');
					} else {
						options.dateFormat = 'dd/mm/yy';
					}
					if ($(datepicker).data('min')) {
						options.minDate = $(datepicker).data('min');
					}
					if ($(datepicker).data('max')) {
						options.maxDate = $(datepicker).data('max');
					}
					if ($(datepicker).attr('value')) {
						options.defaultDate = $(datepicker).attr('value');
					}
					if ($(datepicker).attr('data-date-invalid')) {
						options.disabledDates = $(datepicker).data('date-invalid').toString().split(/[,][ ]+/);
					}
					if ($(datepicker).attr('data-days-blocked')) {
						options.disabledDays = $(datepicker).data('days-blocked').toString().split(/[,][ ]+/);
					}
					if ($(datepicker).data('date-invalid') || $(datepicker).data('days-blocked')) {
						options.beforeShowDay = function (date) {
							if (options.disabledDays.length) {
								let valid = options.disabledDays.indexOf(date.getDay().toString()) == -1;
								if (!valid) {
									return [valid];
								}
							}
							let string = $.datepicker.formatDate(options.dateFormat, date);
							return [options.disabledDates.indexOf(string) == -1]
						}
					}

					if ($(datepicker).data('date-inline')) {
						$(datepicker).next('.wpcb-datepicker-inline').datepicker(options);
						$(datepicker).next('.wpcb-datepicker-inline').change(function () {
							$(this).parent('.wpcb-input-content').find('input[type=text]').val($(this).val());
							$(this).parent('.wpcb-input-content').find('input[type=text]').trigger('input');
						});
					} else {
						$(datepicker).datepicker(options).on("change", function () {
							$(this).trigger('input');
						});
						$(datepicker).attr('data-value', $.datepicker.formatDate(options.dateFormat, $(datepicker).datepicker('getDate')));
					}
				});

				if ($('.wpcb-is-rtl').length) {
					$('.ui-datepicker.ui-widget').addClass('wpcb-is-rtl');
				}
				if ($('.wpcb-configurator-form .wpcb-border-square').length) {
					$('.ui-datepicker.ui-widget').addClass('wpcb-border-square');
				}
				if ($('.wpcb-configurator-form .wpcb-border-pill').length) {
					$('.ui-datepicker.ui-widget').addClass('wpcb-border-pill');
				}
			}
		}

		/**
		 * Gallery functions
		 */

		function _updateCanvasView($field_wrapper, $option, clear_option = false) {

			let options = 'input';
			let active = ':checked';
			if ($field_wrapper.hasClass('wpcb-configurator-dropdown')) {
				options = 'option';
				active = ':selected';
			}

			let show_dims = 'no';
			if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.toggle-dims').length ) {
				show_dims = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.toggle-dims').hasClass('active') ? 'yes' : 'no';
			}

			if (wpcb_model_viewer) {

				// Update 3D model view

				if (($option.attr('type') == 'radio' || $option.attr('type') == 'checkbox') && !$option.is(active)) {
					return;
				}

				if ($option && $option.data('material-name') && $option.data('canvas-width')) {

					/**
					 * Material canvas preview
					 */

					if ('file' !== $option.attr('type')) {

						// Text preview

						let canvasId = $option.closest('.wpcb-configurator-field').find('canvas').attr('id');
						let objConfig = _getCanvasObjectSettings($option);
						let textObj = false;

						if (!wpcb_canvasses) {
							wpcb_canvasses = {};
						};

						if (!wpcb_canvasses.hasOwnProperty(canvasId)) {
							wpcb_canvasses[canvasId] = new fabric.Canvas(canvasId);
							textObj = new fabric.IText($option.val(), objConfig);
							wpcb_canvasses[canvasId].add(textObj);
						} else {
							let objConfig = _getCanvasObjectSettings($option);
							textObj = _getCanvasObjectById(objConfig.id, canvasId);
							textObj[0].set('text', $option.val());
						}

						wpcb_canvasses[canvasId].renderAll();

						let dataURL = wpcb_canvasses[canvasId].toDataURL();
						_createAndApplyViewerTexture($option.data('material-name'), $option.data('material-type'), dataURL);
						_setViewerMaterialColor($option.data('material-name'), ""); // trigger update

						if (objConfig.selectable) {
							wpcb_canvasses[canvasId].on('object:moving', function () {
								let dataURL = wpcb_canvasses[canvasId].toDataURL();
								_createAndApplyViewerTexture($option.data('material-name'), $option.data('material-type'), dataURL);
							});
						}
						if (objConfig.resize) {
							wpcb_canvasses[canvasId].on('object:scaling', function () {
								let dataURL = wpcb_canvasses[canvasId].toDataURL();
								_createAndApplyViewerTexture($option.data('material-name'), $option.data('material-type'), dataURL);
							});
						}
						if (objConfig.rotate) {
							wpcb_canvasses[canvasId].on('object:rotating', function () {
								let dataURL = wpcb_canvasses[canvasId].toDataURL();
								_createAndApplyViewerTexture($option.data('material-name'), $option.data('material-type'), dataURL);
							});
						}
					}

					_updateViewerCameraSettings($option);

				} else {

					/**
					 * Regular 3D option selection
					 */

					_renderConfigurationGallery($option);

				}

			} else {

				/**
				 * Update 2D canvas view
				 */

				if ($option) {

					let id = $option.attr('id');
					let savedObj = null;

					if ($option.closest('.wpcb-configurator-field').data('slide-active') || $option.data('slide-active')) {
						let show_slide = $option.data('slide-active') - 1;

						if ($option.closest('.wpcb-configurator-field').data('slide-active')) {
							show_slide = $option.closest('.wpcb-configurator-field').data('slide-active') - 1;
						}
						if (show_slide < 0) {
							show_slide = 0;
						}

						if (!_hasLoader() && $option.is(active)) {
							if (wpcb_canvas_swiper) {
								wpcb_canvas_swiper.slick('slickGoTo', show_slide);
							}

							$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').removeClass('active');
							$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item[data-slot="' + (show_slide + 1) + '"]').addClass('active');
						}
					}

					let positions = [1];

					if ($option.closest('.wpcb-configurator-field').data('slide-positions') || $option.data('slide-positions')) {
						if ($option.data('slide-positions')) {
							positions = $option.data('slide-positions').toString().split(',');
						} else {
							positions = $option.closest('.wpcb-configurator-field').data('slide-positions').toString().split(',');
						}
						positions = positions.map(pos => parseInt(pos));
					}

					let layer_index = 0;
					if ($option.closest('.wpcb-configurator-field').data('slide-order') || $option.data('slide-order')) {
						if ($option.data('slide-order')) {
							layer_index = $option.data('slide-order');
						} else {
							layer_index = $option.closest('.wpcb-configurator-field').data('slide-order');
						}
					}

					_renderConfigurationGallery();

					// Add image for current changed option.
					if ($option.is(active)) {

						if ($option.closest('.wpcb-configurator-field').data('reference') || $option.data('reference')) {

							let property, property_val = [];
							let reference = '';

							if ($option.closest('.wpcb-configurator-field').data('reference')) {
								reference = $option.closest('.wpcb-configurator-field').data('reference').toString().split(/[,][ ]+/);

								if ($option.closest('.wpcb-configurator-field').data('property')) {
									property = $option.closest('.wpcb-configurator-field').data('property').toString().split(/[,][ ]+/);
								}
							} else {
								reference = $option.data('reference').toString().split(/[,][ ]+/);
								property = $option.data('property').toString().split(/[,][ ]+/);
							}

							if ($option.data('preview-value')) {
								property_val = $option.data('preview-value').toString().split(';');
							}

							if (reference.length && property.length) {
								for (let pos_index in positions) {
									let position = positions[pos_index];

									for (let rf = 0; rf < reference.length; rf++) {
										if (reference[rf]) {
											let inputObj = _getCanvasObjectById(reference[rf], position);

											if (inputObj.length) {
												for (let pv = 0; pv < property.length; pv++) {
													if (property_val[pv]) {
														inputObj[0].set(`${property[pv]}`, `${property_val[pv]}`);
													}
												}
											}
										}
									}

									_updateCanvasThumbnails(position);
								}
							}
						}

					} else if ('file' !== $option.attr('type')) {

						if ($option.data('has-preview') && !$option.closest('.wpcb-configurator-field').data('reference') && !$option.data('reference')) {

							for (let pos_index in positions) {

								let position = positions[pos_index];
								let inputObj = _getCanvasObjectById($option.attr('id'), position);

								if (inputObj.length) {

									if (clear_option) {
										// TEXT PREVIEW
										inputObj[0].set('text', '');
									} else {
										// TEXT PREVIEW
										inputObj[0].set('text', $option.val());
									}

									inputObj[0].on('changed', function () {
										if (inputObj[0].text.includes('\n')) {
											inputObj[0].text = inputObj[0].text.replace(/\n/g, '');
										}
									});

									if ( $option.data('is-dim') ) {
										if ( 'no' == show_dims ) {
											inputObj[0].visible = false;
										} else {
											inputObj[0].visible = true;
										}
									}

									if ( 'fittext' == $option.data('overflow') ) {
										if (inputObj[0].width > inputObj[0].fixedWidth) {
											// MAKE FONT SIZE SMALLER TO FIT IN SCREEN
											while (inputObj[0].width > inputObj[0].fixedWidth) {
												inputObj[0].fontSize *= inputObj[0].fixedWidth / ( inputObj[0].width + 1);
												inputObj[0].width = inputObj[0].fixedWidth;
												inputObj[0].initDimensions();
											}
										} 
										else {
											if ( '' === $option.val() ) {
												inputObj[0].fontSize = inputObj[0].baseFontSize;
												inputObj[0].width = inputObj[0].fixedWidth;
											} else if ( inputObj[0].fontSize < inputObj[0].baseFontSize ) {

												// TODO make font size larger again
												let actualWidth = inputObj[0].calcTextWidth
													? inputObj[0].calcTextWidth()   // Textbox
													: inputObj[0].width;

												if (actualWidth !== 0 && actualWidth < inputObj[0].fixedWidth) {
													let scale = inputObj[0].fixedWidth / actualWidth;

													inputObj[0].fontSize *= scale;
													if (inputObj[0].fontSize > inputObj[0].baseFontSize) {
														inputObj[0].fontSize = inputObj[0].baseFontSize;
													}
												}

												// while (inputObj[0].actualWidth > inputObj[0].fixedWidth) {
												// 	inputObj[0].fontSize = inputObj[0].baseFontSize * ( inputObj[0].fixedWidth / ( inputObj[0].width + 1) );
												// 	inputObj[0].width = inputObj[0].fixedWidth;
												// 	// inputObj[0].initDimensions();
												// }
											}
										}
									}

									if ( inputObj[0].get('fixedWidth') && 'center' === inputObj[0].get('textAlign') ) {
										inputObj[0].set('left', (inputObj[0].get('fixedWidth') - inputObj[0].get('width')) / 2 );
									}

								} else {

									if (wpcb_configuration.canvasses && wpcb_configuration.canvasses[position]) {
										savedObj = wpcb_configuration.canvasses[position].objects.filter(o => o.id == id);
									}

									let objConfig = _getCanvasObjectSettings($option, position, savedObj);
									let textObj = false;
									if (objConfig.width) {
										// Fixed box width
										textObj = new fabric.IText($option.val(), objConfig);

										if ( 'center' === textObj.get('textAlign') ) {
											textObj.set('left', textObj.fixedWidth / 2 );
											textObj.set('originX', 'center' );
										}
									} else {
										// Auto width
										textObj = new fabric.Text($option.val(), objConfig);
									}

									textObj.setControlsVisibility(_getCanvasControlVisibilitySettings(objConfig));

									if (typeof wpcb_canvasses !== 'undefined' && wpcb_canvasses[position]) {
										wpcb_canvasses[position].add(textObj);
									}

									if (typeof wpcb_canvas_layers !== 'undefined' && wpcb_canvas_layers[position]) {
										let layer = layer_index;
										if (layer_index.toString().includes(',')) {
											layer = layer_index.split(',')[position].trim();
										}

										wpcb_canvas_layers[position][objConfig.id] = layer;
									}
								}

								_updateCanvasThumbnails(position);
							}

						} else if ($option.closest('.wpcb-configurator-field').data('reference') || $option.data('reference')) {

							if ($option.attr('type') == 'text' || $option.attr('type') == 'number') {

								let property_val = [$option.val()];
								let property = [];
								let reference = '';

								if ($option.closest('.wpcb-configurator-field').data('reference')) {
									reference = $option.closest('.wpcb-configurator-field').data('reference').toString().split(/[,][ ]+/);
								} else {
									reference = $option.data('reference').toString().split(/[,][ ]+/);
								}

								if ($option.closest('.wpcb-configurator-field').data('property')) {
									property = $option.closest('.wpcb-configurator-field').data('property').toString().split(/[,][ ]+/);
								} else {
									property = $option.data('property').toString().split(/[,][ ]+/);
								}

								if (reference.length && property.length) {

									for (let pos_index in positions) {
										let position = positions[pos_index];

										for (let rf = 0; rf < reference.length; rf++) {
											if (reference[rf]) {
												let inputObj = _getCanvasObjectById(reference[rf], position);

												if (inputObj.length) {
													for (let pv = 0; pv < property.length; pv++) {
														inputObj[0].set(`${property[pv]}`, `${property_val[pv]}`);
													}
												}
											}
										}

										_updateCanvasThumbnails(position);
									}
								}
							} else {

								// Not checked. Unset reference value.

								let property_val = [$option.val()];
								let property = [];
								let reference = '';

								if ($option.closest('.wpcb-configurator-field').data('reference')) {
									reference = $option.closest('.wpcb-configurator-field').data('reference').toString().split(/[,][ ]+/);
								} else {
									reference = $option.data('reference').toString().split(/[,][ ]+/);
								}

								if ($option.closest('.wpcb-configurator-field').data('property')) {
									property = $option.closest('.wpcb-configurator-field').data('property').toString().split(/[,][ ]+/);
								} else {
									property = $option.data('property').toString().split(/[,][ ]+/);
								}

								let propertyMap = {
									fontFamily: 'font-family',
									fontSize: 'font-size',
									baseFontSize: 'font-size',
									fontStyle: 'font-style',
									fontWeight: 'font-weight',
									fill: 'font-color',
									textAlign: 'text-align'
								}

								if (reference.length && property.length) {
									for (let pos_index in positions) {
										let position = positions[pos_index];

										for (let rf = 0; rf < reference.length; rf++) {
											if (reference[rf]) {
												let inputObj = _getCanvasObjectById(reference[rf], position);

												if (inputObj.length) {
													for (let pv = 0; pv < property.length; pv++) {
														inputObj[0].set(`${property[pv]}`, $('input#' + reference[rf]).data(propertyMap[property[pv]]));
													}
												}
											}
										}

										_updateCanvasThumbnails(position);
									}
								}
							}
						}
					}
				}
			}
		}

		/**
		 * 2D canvas
		 */

		var galleryTimer;
		async function _renderConfigurationGallery($option = false) {
			if (wpcb_vars.settings.enable_loader) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-visuals').addClass('wpcb-loading');
			}

			clearTimeout(galleryTimer);

			galleryTimer = setTimeout(function() {

				let variation_id = 0;
				if ($('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="variation_id"]').length && $('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="variation_id"]').val() != 0) {
					variation_id = $('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="variation_id"]').val();
				}
				if ($('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').length && $('form.cart[data-wpcb-id="' + wpcb_id + '"] input[name="variation_id"]').val() != 0) {
					variation_id = $('[data-wpcb-id="' + wpcb_id + '"] form.cart input[name="variation_id"]').val();
				}
				if ( wpcb_configuration.variation_id ) {
					variation_id = wpcb_configuration.variation_id;
				}

				let product_id = wpcb_configuration.product_id;
				let preview = false;
				if ( $('#wpcb-admin-preview').length ) {
					product_id = 0;
					preview = true;
				}

				let show_dims = 'no';
				if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.toggle-dims').length ) {
					show_dims = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.toggle-dims').hasClass('active') ? 'yes' : 'no';
				}

				$.ajax({
					type: 'post',
					url: wpcb_vars.ajax_url,
					data: {
						action: 'wpcb_get_configuration_visuals',
						nonce: wpcb_vars.nonce,
						configuration_id: wpcb_configuration.configurator_id,
						product_id: product_id,
						variation_id: variation_id,
						show_dims: show_dims,
						is_preview: preview,
						selection: wpcb_configuration.selection
					},
					success: function (response) {
						// Preview image option
						let visuals = response.data;

						if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').length) {

							if (!visuals.model || wpcb_model_viewer.src === visuals.model[0]) {

								_updateVisualModelProperties(visuals, $option);

							} else {

								wpcb_model_viewer.src = visuals.model[0];

								wpcb_model_viewer.addEventListener("load", () => {

									wpcb_scene_sym = Object.getOwnPropertySymbols(wpcb_model_viewer).find(x => x.description === "scene");
									wpcb_model_objects = [];
									wpcb_model_viewer[wpcb_scene_sym].traverse((child) => {
										if (child.type && child.type == "Mesh") {
											wpcb_model_objects.push(child);
										}
									});

									_updateVisualModelProperties(visuals, $option);

								});
							}

						} else {

							let currentImages = [];

							for (let position = 1; position <= $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length; position++) {
								// Cleanup current image set.
								if (typeof wpcb_canvasses !== 'undefined' && wpcb_canvasses[position]) {

									if (!currentImages[position]) {
										currentImages[position] = [];
									}

									let images = wpcb_canvasses[position].getObjects();

									if (visuals[position]) {

										// Only delete option images. Keep user added text / images.
										let savedImages = {};

										images.forEach((imgObj, key) => {
											if (imgObj.objectType === 'optionimage') {
												delete wpcb_canvas_layers[position][imgObj.id];

												savedImages[imgObj.id] = imgObj;

												wpcb_canvasses[position].remove(imgObj);
											}
										});

										// get all objects
										let totalVisuals = visuals[position].length;

										// Add in current images
										visuals[position].forEach(function (visual, key) {
											if (!wpcb_vars.image_stack && key + 1 !== totalVisuals) {
												return; // Single image and not final image reached.
											}

											let layer = visual.layer;
											if (layer.toString().includes(',')) {
												layer = layer.split(',')[key].trim();
											}

											if ( 'base-image' == visual.id ) {
												_updateCanvasImage(visual.image, visual.id, position);
											} else {
												let objConfig = {
													id: visual.id,
													objectType: 'optionimage',
													hoverCursor: 'default',
													top: 0,
													left: 0,
													editable: false,
													selectable: false,
													hasControls: false,
													hasBorders: false,
													...controlSettings,
												};

												if (visual.drag) {
													objConfig.selectable = true;

													if (wpcb_canvasses && wpcb_canvasses.hasOwnProperty(position)) {
														objConfig.hoverCursor = wpcb_canvasses[position].hoverCursor;
													}
												} else {
													if (visual.resize || visual.rotate) {
														objConfig.selectable = true;
														objConfig.hoverCursor = 'pointer';
														objConfig.lockMovementX = true;
														objConfig.lockMovementY = true;
													}
												}

												if (visual.resize) {
													objConfig.hasControls = true;
													objConfig.hasBorders = true;
													objConfig.resize = true;
												}

												if (visual.rotate) {
													objConfig.hasControls = true;
													objConfig.hasBorders = true;
													objConfig.rotate = true;
												}

												if (visual.top) {
													objConfig.top = visual.top;
												}
												if (visual.left) {
													objConfig.left = visual.left;
												}

												if (Object.keys(savedImages).includes(visual.id)) {
													objConfig.top = savedImages[visual.id].top;
													objConfig.left = savedImages[visual.id].left;
													objConfig.scaleX = savedImages[visual.id].scaleX;
													objConfig.scaleY = savedImages[visual.id].scaleY;
													objConfig.angle = savedImages[visual.id].angle;
													objConfig.fill = savedImages[visual.id].fill;
												}

												_addCanvasImage(visual.image, objConfig, position, layer);

												currentImages[position].push(visual.id);

											}
										});
									} else {
										// No new images returned. Clear all previews.
										images.forEach((imgObj, key) => {
											if (imgObj.objectType === 'optionimage') {
												delete wpcb_canvas_layers[position][imgObj.id];
												wpcb_canvasses[position].remove(imgObj);
											}
										});
									}
								}
								
								_updateCanvasThumbnails(position);
							}
						}

						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-visuals').removeClass('wpcb-loading');

						if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').length) {
							// Always update references passed by options
							if ($option && ($option.closest('.wpcb-configurator-field').data('reference') || $option.data('reference'))) {
								let reference = $option.closest('.wpcb-configurator-field').data('reference');
								if (!reference) {
									reference = $option.data('reference');
								}
								let property_val = [$option.val()];

								let property = [];
								if ($option.closest('.wpcb-configurator-field').data('property')) {
									property = $option.closest('.wpcb-configurator-field').data('property').toString().split(/[,][ ]+/);
								} else if ($option.data('property')) {
									property = $option.data('property').toString().split(/[,][ ]+/);
								}
								if ($option.data('preview-value')) {
									property_val = $option.data('preview-value').toString().split(/[,][ ]+/);
								}

								if (reference && property.length) {
									for (let pv = 0; pv < property.length; pv++) {
										let prop = property[pv];
										let value = '';

										if (isNaN(property_val[pv])) {
											value = property_val[pv];
										} else {
											value = parseFloat(property_val[pv]);
										}

										_updateOptionMaterialCanvas(reference, { [prop]: value });
									}
								}
							}
						}
					},
					error: function (error) {
						// remove loader
						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-visuals').removeClass('wpcb-loading');
					}
				});
			}, 200);
		}
 
		async function _addCanvasImage(imageUrl, config, position, layer, savedObj = null) {
			if ( imageUrl && imageUrl.includes('.svg') ) {
				return fabric.loadSVGFromURL(
					imageUrl,
					function (objects, options) {
						let obj = fabric.util.groupSVGElements(objects, options);

						obj.set(config);

						if (!savedObj) {
							if (config.height) {
								obj.scaleToHeight(config.height, false);
							}
							if (config.width) {
								obj.scaleToWidth(config.width, false);
							}
						}

						obj.setControlsVisibility(_getCanvasControlVisibilitySettings(config));

						if (typeof wpcb_canvasses !== 'undefined' && wpcb_canvasses[position]) {
							wpcb_canvasses[position].add(obj);
						}

						if (typeof wpcb_canvas_layers !== 'undefined' && wpcb_canvasses[position]) {
							wpcb_canvas_layers[position][config.id] = parseInt(layer);
						}

						_updateCanvasIndexes(position);

						_updateCanvasThumbnails(position);
					}
				);

			} else {

				return new fabric.Image.fromURL(
					imageUrl,
					function (image) {
						image.set(config);

						if (!savedObj) {
							if (config.height) {
								image.scaleToHeight(config.height, false);
							}
							if (config.width) {
								image.scaleToWidth(config.width, false);
							}
						}

						image.setControlsVisibility(_getCanvasControlVisibilitySettings(config));

						if (typeof wpcb_canvasses !== 'undefined' && wpcb_canvasses[position]) {
							let currentImage = _getCanvasObjectById(config.id);
							if (currentImage) {
								wpcb_canvasses[position].remove(currentImage);
							}
							wpcb_canvasses[position].add(image);
						}

						if (typeof wpcb_canvas_layers !== 'undefined' && wpcb_canvasses[position]) {
							wpcb_canvas_layers[position][config.id] = parseInt(layer);
						}

						_updateCanvasIndexes(position);

						_updateCanvasThumbnails(position);
					},
					{
						crossOrigin: 'anonymous'
					}
				);
			}
		}

		async function _updateCanvasImage(imageUrl, imageId, position) {
			if (typeof wpcb_canvasses !== 'undefined' && wpcb_canvasses[position]) {
				let currentImage = _getCanvasObjectById(imageId, position);

				if (currentImage[0]) {
					currentImage[0]._element.src = imageUrl;

					currentImage[0]._element.addEventListener("load", function () {
						wpcb_canvasses[position].renderAll.bind(wpcb_canvasses[position]);

						_updateCanvasThumbnails(position);
					});
				}
			}
		}

		function _updateCanvasThumbnails(position) {
			if (typeof wpcb_canvasses === 'undefined') {
				return;
			}
			if (!wpcb_canvasses[position]) {
				return {};
			}

			// _updateCanvasIndexes( position );

			wpcb_canvasses[position].renderAll();

			// Nav gallery.
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav--thumbnails').length) {
				let url = wpcb_canvasses[position].toDataURL('png');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item[data-slot="' + position + '"] img').attr('src', url);
			}
		}

		function _updateCanvasIndexes(position) {

			// Nav gallery.
			if (wpcb_canvas_layers && typeof wpcb_canvas_layers[position] !== 'undefined') {
				let layersSorted = Object.keys(wpcb_canvas_layers[position]).sort(function (a, b) {
					return wpcb_canvas_layers[position][a] - wpcb_canvas_layers[position][b]
				});

				for (let layer_pos = 0; layer_pos < layersSorted.length; layer_pos++) {
					let key = layersSorted[layer_pos];
					let obj = _getCanvasObjectById(key, position);

					if (obj.length) {
						obj[0].moveTo(layer_pos + 1);
					}
				}
			}
		}

		function _updateCanvasInputImage(input, image_data) {
			let $input = $(input);

			if ($('[data-wpcb-id="' + wpcb_id + '"] model-viewer').length) {

				// 3D image

				let id = $(input).attr('id');
				let canvasId = $(input).closest('.wpcb-configurator-field').find('canvas').attr('id');
				let savedObj = null;
				if (wpcb_configuration.canvasses && wpcb_configuration.canvasses[canvasId]) {
					savedObj = wpcb_configuration.canvasses[canvasId].objects.filter(o => o.id == id);
				}

				let objConfig = _getCanvasObjectSettings(input, 1, savedObj);
				if (!wpcb_canvasses) {
					wpcb_canvasses = {};
				}

				if (!wpcb_canvasses[canvasId]) {
					wpcb_canvasses[canvasId] = new fabric.Canvas(canvasId);
				} else {
					wpcb_canvasses[canvasId].clear();
				}

				fabric.Image.fromURL(image_data, function (image) {
					image.set(objConfig);

					if (objConfig.height) {
						image.scaleToHeight(objConfig.height, false);
					}
					if (objConfig.width) {
						image.scaleToWidth(objConfig.width, false);
					}

					image.setControlsVisibility(_getCanvasControlVisibilitySettings(objConfig));

					wpcb_canvasses[canvasId].add(image);
					wpcb_canvasses[canvasId].renderAll();

					let dataURL = wpcb_canvasses[canvasId].toDataURL();

					_createAndApplyViewerTexture($input.data('material-name'), $input.data('material-type'), dataURL);
					_setViewerMaterialColor($input.data('material-name'), ""); // trigger update

					if ($input.closest('.wpcb-configurator-field').find('.wpcb-button-customize-popup').length) {
						$input.closest('.wpcb-configurator-field').find('.wpcb-button-customize-popup').addClass('active');
					}
				});

				_updateViewerCameraSettings(input);

			} else {

				// 2D slide image upload
				let positions = [1];

				let id = $(input).attr('id');
				if ($(input).closest('.wpcb-configurator-field').data('slide-positions')) {
					positions = $(input).closest('.wpcb-configurator-field').data('slide-positions').toString().split(',');
					positions = positions.map(pos => parseInt(pos));
				} else if ($(input).data('slide-positions')) {
					positions = $(input).data('slide-positions').toString().split(',');
					positions = positions.map(pos => parseInt(pos));
				}

				let canvasId = $input.closest('.wpcb-configurator-field').find('canvas').attr('id');
				let objConfig = _getCanvasObjectSettings(input);

				if (!wpcb_canvasses) {
					wpcb_canvasses = {};
				}
				if (!wpcb_canvasses[canvasId]) {
					wpcb_canvasses[canvasId] = new fabric.Canvas(canvasId);
				} else {
					wpcb_canvasses[canvasId].clear();
				}

				let position;
				for (let pos_index in positions) {
					let position = positions[pos_index];
					let layer_index = _getCanvasLayerIndex(position) * 10;
					if ($(input).closest('.wpcb-configurator-field').data('slide-order')) {
						layer_index = $(input).closest('.wpcb-configurator-field').data('slide-order');
					} else if ($(input).data('slide-order')) {
						layer_index = $(input).data('slide-order');
					}

					let layer = layer_index
					let savedObj = null;
					if (wpcb_configuration.canvasses && wpcb_configuration.canvasses[position]) {
						savedObj = wpcb_configuration.canvasses[position].objects.filter(o => o.id == id);
					}

					if (layer_index.toString().includes(',')) {
						layer = layer_index.split(',')[pos_index].trim();
					}

					let objConfig = _getCanvasObjectSettings(input, position, savedObj);
					let imageObj = _getCanvasObjectById(objConfig.id, position);

					if (imageObj.length) {
						wpcb_canvasses[position].remove(imageObj[0]);
					}

					// Checked? add new one.
					if ('' !== image_data) {
						_addCanvasImage(image_data, objConfig, position, layer, savedObj);
					} else {
						_updateCanvasThumbnails(position);
					}
				}

				fabric.Image.fromURL(image_data, function (image) {
					if (wpcb_configuration.canvasses && wpcb_configuration.canvasses[canvasId]) {
						let savedObj = wpcb_configuration.canvasses[canvasId].objects.filter(o => o.id == id);

						if (savedObj) {
							image.set(savedObj[0]);
						}
					} else {
						image.set('id', objConfig.id);

						if (objConfig.height) {
							image.scaleToHeight(objConfig.height, false);
						}
						if (objConfig.width) {
							image.scaleToWidth(objConfig.width, false);
						}
					}

					image.set({ ...controlSettings });
					image.setControlsVisibility(_getCanvasControlVisibilitySettings(objConfig));

					wpcb_canvasses[canvasId].add(image);
					wpcb_canvasses[canvasId].renderAll();

					let dataURL = wpcb_canvasses[canvasId].toDataURL();

					_updateCanvasImage(dataURL, objConfig.id, position);

					if ($input.closest('.wpcb-configurator-field').find('.wpcb-button-customize-popup').length) {
						$input.closest('.wpcb-configurator-field').find('.wpcb-button-customize-popup').addClass('active');
					}
				},
					{
						crossOrigin: 'anonymous'
					});
			}

			_updateConfiguration($input.closest('.wpcb-configurator-field'), 'input');
		}

		function _getCanvasObjectById(id, cIndex = 1) {
			if (typeof wpcb_canvasses === 'undefined') {
				return {};
			}
			if (!wpcb_canvasses[cIndex]) {
				return {};
			}
			return wpcb_canvasses[cIndex].getObjects().filter(obj => obj.id.includes(id));
		}

		function _getCanvasLayerIndex(cIndex = 1) {
			if (typeof wpcb_canvasses === 'undefined') {
				return 0;
			}
			if (!wpcb_canvasses[cIndex]) {
				return 0;
			}
			return wpcb_canvasses[cIndex].getObjects().length;
		}

		function _resizeGalleryCanvas() {
			for (let canvas_view in wpcb_canvasses) {
				let canvas = wpcb_canvasses[canvas_view];
				let newWidth = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').width();
				if (newWidth > initialSize.width || newWidth === 0) {
					newWidth = initialSize.width;
				}

				let scale = (newWidth / initialSize.width);
				if (scale > 1) {
					scale = 1; // Max scale 100%
				}
				let newHeight = initialSize.height * scale;

				canvas.setHeight(newHeight);
				canvas.setWidth(newWidth);
				canvas.setZoom(scale);
				canvas.renderAll();
			}
		}

		function _resizePanelCanvas(canvas_view, initialWidth, initialHeight, newWidth = 0) {
			let canvas = wpcb_canvasses[canvas_view];
			if (newWidth > initialWidth || newWidth === 0) {
				newWidth = initialWidth;
			}
			let scale = (newWidth / initialWidth);
			if (scale > 1) {
				scale = 1; // Max scale 100%
			}
			let newHeight = initialHeight * scale;
			canvas.setHeight(newHeight);
			canvas.setWidth(newWidth);
			canvas.setZoom(scale);
			canvas.renderAll();
		}

		function _getCanvasObjectSettings(input, cIndex = 1, savedObj = null) {
			let input_id = $(input).attr('id');

			let objConfig = {
				id: input_id,
				top: 0,
				left: 0,

				editable: false,
				selectable: false,
				hasControls: false,
				hasBorders: false,

				...controlSettings,
			};

			if (wpcb_canvasses && wpcb_canvasses.hasOwnProperty(cIndex)) {
				objConfig.hoverCursor = wpcb_canvasses[cIndex].defaultCursor;
			}

			if ($(input).data('drag') && 'yes' == $(input).data('drag')) {
				objConfig.selectable = true;

				if (wpcb_canvasses && wpcb_canvasses.hasOwnProperty(cIndex)) {
					objConfig.hoverCursor = wpcb_canvasses[cIndex].hoverCursor;
				}
			} else {
				if ($(input).data('resize') || $(input).data('rotate')) {
					objConfig.selectable = true;
					objConfig.hoverCursor = 'pointer';
					objConfig.lockMovementX = true;
					objConfig.lockMovementY = true;
				}
			}

			if ($(input).data('resize') && 'yes' == $(input).data('resize')) {
				objConfig.hasControls = true;
				objConfig.hasBorders = true;
				objConfig.resize = true;
			}

			if ($(input).data('rotate') && 'yes' == $(input).data('rotate')) {
				objConfig.hasControls = true;
				objConfig.hasBorders = true;
				objConfig.rotate = true;
			}

			if ($(input).data('top')) {
				objConfig.top = $(input).data('top');

				if (savedObj && savedObj[0]) {
					objConfig.top = savedObj[0].top;
				}
			}
			if ($(input).data('left')) {
				objConfig.left = $(input).data('left');

				if (savedObj && savedObj[0]) {
					objConfig.left = savedObj[0].left;
				}
			}
			if ($(input).data('width')) {
				objConfig.width = $(input).data('width');
				objConfig.fixedWidth = $(input).data('width');

				if (savedObj && savedObj[0]) {
					objConfig.width = savedObj[0].width;
				}
			}
			if ($(input).data('canvas-width')) {
				objConfig.width = $(input).data('canvas-width');
				if (savedObj && savedObj[0]) {
					objConfig.width = savedObj[0].width;
				}
			}
			if ($(input).data('canvas-height')) {
				objConfig.height = $(input).data('canvas-height');
				if (savedObj && savedObj[0]) {
					objConfig.height = savedObj[0].height;
				}
			}
			if ($(input).data('font-family')) {
				objConfig.fontFamily = $(input).data('font-family');
			}
			if ($(input).data('font-size')) {
				objConfig.fontSize = $(input).data('font-size');
				objConfig.baseFontSize = $(input).data('font-size');
			}
			if ($(input).data('font-style')) {
				objConfig.fontStyle = $(input).data('font-style');
			}
			if ($(input).data('font-weight')) {
				objConfig.fontWeight = $(input).data('font-weight');
			}
			if ($(input).data('font-color')) {
				objConfig.fill = $(input).data('font-color');
			}
			if ($(input).data('text-align')) {
				objConfig.textAlign = $(input).data('text-align');
			}
			if ($(input).data('text-rotation')) {
				objConfig.angle = $(input).data('text-rotation');
			}

			// Drag / Resize saved settings
			if (savedObj && savedObj[0] && savedObj[0].angle) {
				objConfig.angle = savedObj[0].angle;
			}
			if (savedObj && savedObj[0] && savedObj[0].scaleX) {
				objConfig.scaleX = savedObj[0].scaleX;
			}
			if (savedObj && savedObj[0] && savedObj[0].scaleY) {
				objConfig.scaleY = savedObj[0].scaleY;
			}

			return objConfig;
		}

		function _getCanvasControlVisibilitySettings(settings) {
			let cvObject = {
				mtr: false,
				tl: false,
				tr: false,
				bl: false,
				br: false, //
				mt: false, // middle top disable
				mb: false, // midle bottom
				ml: false, // middle left
				mr: false, // I think you get it
			};

			if (settings.resize) {
				cvObject.tl = true;
				cvObject.tr = true;
				cvObject.bl = true;
				cvObject.br = true;
			}

			if (settings.rotate) {
				cvObject.mtr = true;
			}

			return cvObject;
		}

		function _getCanvasDataUrl(position) {
			if (!wpcb_canvasses[position]) {
				return '';
			}

			let multiplier = 1;
			if ('' !== wpcb_vars.settings.image_width) {
				let targetWidth = parseInt(wpcb_vars.settings.image_width);
				let currentWidth = wpcb_canvasses[position].getWidth();
				multiplier = targetWidth / currentWidth;
			}

			if ('jpg' == wpcb_vars.settings.image_export) {
				return wpcb_canvasses[position].toDataURL({
					format: 'jpg',
					quality: 1.0,
					enableRetinaScaling: false,
					multiplier: multiplier
				});
			} else {
				return wpcb_canvasses[position].toDataURL({
					format: 'png',
					enableRetinaScaling: false,
					multiplier: multiplier
				});
			}
		}

		async function _getCanvasRenders(configuration, canvasPositions = false, multiple = false) {
			let dataUrl = '';
			if (typeof wpcb_canvasses !== 'undefined' && $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length) {
				dataUrl = _getCanvasDataUrl(1);
			}

			if (typeof wpcb_model_viewer !== 'undefined') {
				if ('jpg' == wpcb_vars.settings.image_export) {
					dataUrl = wpcb_model_viewer.toDataURL('jpg');
				} else {
					dataUrl = wpcb_model_viewer.toDataURL('png');
				}
			}

			if (dataUrl) {
				if ( ! configuration.renders ) {
					configuration.renders = [];
				}
				configuration.renders[0] = dataUrl;

				if (typeof wpcb_canvasses !== 'undefined') {
					if (canvasPositions) {
						if ( ! configuration.renders ) {
							configuration.canvasses = {};
						}

						for (let canvas_key in wpcb_canvasses) {
							wpcb_canvasses[canvas_key].includeDefaultValues = false;
							let json = wpcb_canvasses[canvas_key].toDatalessJSON(['id']);

							configuration.canvasses[canvas_key] = json;
						}
					}

					if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length > 1 && multiple) {
						for (let r = 2; r <= $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery__image').length; r++) {
							configuration.renders[r - 1] = _getCanvasDataUrl(r);
						}
					}
				}
				else if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item').length && multiple) {
					let items = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-nav__item');

					for (let i = 0; i < items.length; i++) {
						let item = items[i];

						$(item).trigger('click');

						await _getNextViewerFrame();
						await _getNextViewerFrame();

						if (wpcb_vars.settings.image_export === 'jpg') {
							configuration.renders[i] = wpcb_model_viewer.toDataURL('jpg');
						} else {
							configuration.renders[i] = wpcb_model_viewer.toDataURL('png');
						}
					}
				}
			}

			return configuration;
		}

		/**
		 * 3D model
		 */
		function _getNextViewerFrame() {
			return new Promise(resolve => requestAnimationFrame(resolve));
		}

		async function _createAndApplyViewerTexture(name, channel, textureUrl) {
			let texture = null;
			if (textureUrl) {
				texture = await wpcb_model_viewer.createTexture(textureUrl);
			}

			let material = wpcb_model_viewer.model.getMaterialByName(name);
			if (material) {
				await material.ensureLoaded();

				if (channel.includes("base") || channel.includes("metallic")) {
					if ("base" == channel) {
						channel = "baseColorTexture";
					} else {
						channel = "metallicRoughnessTexture";
					}

					if (material.pbrMetallicRoughness[channel]) {
						material.pbrMetallicRoughness[channel].setTexture(texture);
					}
				} else {
					channel = channel + "Texture"; // e.g. occlusionTexture.

					if (material[channel]) {
						material[channel].setTexture(texture);
					}
				}
			}
		}

		async function _setViewerMaterialColor(name, color) {
			let material = wpcb_model_viewer.model.getMaterialByName(name);

			if (material) {
				// Make sure material is loaded.
				await material.ensureLoaded();

				if (material != null) {
					material.pbrMetallicRoughness.setBaseColorFactor(color);
				}
			}
		}

		async function _setViewerMaterialRoughness(name, factor) {
			let material = wpcb_model_viewer.model.getMaterialByName(name);

			if (material) {
				// Make sure material is loaded.
				await material.ensureLoaded();

				if (material != null) {
					material.pbrMetallicRoughness.setRoughnessFactor(factor);
				}
			}
		}

		async function _setViewerMaterialMetalness(name, factor) {
			let material = wpcb_model_viewer.model.getMaterialByName(name);

			if (material) {
				// Make sure material is loaded.
				await material.ensureLoaded();

				if (material != null) {
					material.pbrMetallicRoughness.setMetallicFactor(factor);
				}
			}
		}

		function _updateOptionMaterialCanvas(name, values) {
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field canvas[data-material-name="' + name + '"]').length) {
				let canvasId = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field canvas[data-material-name="' + name + '"]').attr('id');

				if (wpcb_canvasses && wpcb_canvasses.hasOwnProperty(canvasId)) {
					let objectId = canvasId.replace('canvas_', '');
					let text = _getCanvasObjectById(objectId, canvasId);

					for (let prop in values) {
						text[0].set(prop, values[prop]);
					}

					wpcb_canvasses[canvasId].renderAll();

					let dataURL = wpcb_canvasses[canvasId].toDataURL();
					let materialType = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-field canvas[data-material-name="' + name + '"]').closest('.wpcb-configurator-field').find('input').data('material-type');
					_createAndApplyViewerTexture(name, materialType, dataURL);
				}
			}
		}

		function _updateVisualModelProperties(visuals, $option) {
			if (visuals.material) {
				visuals.material.forEach(function (material, index) {
					let value = material.value;
					let name = material.name;
					let texture = material.type;

					if ('color' === texture && $option.data('preview-value')) {
						let color = $option.data('preview-value');
						_setViewerMaterialColor(name, color);
						_updateOptionMaterialCanvas(name, { 'fill': color });
					} else if ('metalness' == texture) {
						_setViewerMaterialMetalness(name, value);
					} else if ('roughness' == texture) {
						_setViewerMaterialRoughness(name, value);
					} else if ('' !== texture && value) {
						_createAndApplyViewerTexture(name, texture, value);
					}
				});
			}

			if (visuals.variant) {
				let variant = visuals.variant[0];
				wpcb_model_viewer['variantName'] = variant;
			}

			if (visuals.animation) {
				$(wpcb_model_viewer).attr("animation-name", visuals.animation[0].name);
				$(wpcb_model_viewer).get(0).play();

				if ('once' === visuals.animation[0].type) {
					self.setTimeout(() => {
						$(wpcb_model_viewer).get(0).pause();
					}, $(wpcb_model_viewer).get(0).duration * 1000);
				}
			} else {
				$(wpcb_model_viewer).attr("animation-name", "");
				$(wpcb_model_viewer).get(0).pause();
			}

			if (wpcb_vars.base_nodes.length) {
				wpcb_visible_nodes = structuredClone(wpcb_vars.base_nodes);
				if (visuals.node) {
					visuals.node.forEach((node, key) => {
						let nodes = node.split(/[,][ ]+/);
						for (let n = 0; n < nodes.length; n++) {
							wpcb_visible_nodes.push(nodes[n]);
						}
					});
				}
				_updateViewerNodes();
			}

			if (wpcb_vars.base_hotspots.length) {
				wpcb_visible_hotspots = structuredClone(wpcb_vars.base_hotspots);
				if (visuals.hotspot) {
					visuals.hotspot.forEach((hotspot, key) => {
						let hotspots = hotspot.split(/[,][ ]+/);
						for (let h = 0; h < hotspots.length; h++) {
							wpcb_visible_hotspots.push(hotspots[h]);
						}
					});
				}
				_updateViewerHotspots();
			}

			_updateViewerCameraSettings($option);
		}

		function _updateViewerNodes() {
			if (!wpcb_model_objects) {
				return;
			}

			for (let i = 0; i < wpcb_model_objects.length; i++) {
				if (wpcb_visible_nodes.indexOf(wpcb_model_objects[i].name) === -1) {
					wpcb_model_objects[i].visible = false;
				} else {
					wpcb_model_objects[i].visible = true;
				}
			}

			wpcb_model_viewer[wpcb_scene_sym].isDirty = true;
		}

		function _updateViewerNodeSize(nodeList, property, value) {
			let nodes = nodeList.toString().split(/[,][ ]+/);

			for (let i = 0; i < wpcb_model_objects.length; i++) {
				if (nodes.indexOf(wpcb_model_objects[i].name) !== -1) {
					let node = wpcb_model_objects[i];

					if (node.isMesh && node.geometry && node.geometry.attributes.position) {
						let position = node.geometry.attributes.position;

						if (!wpcb_model_object_pos[node.name]) {
							wpcb_model_object_pos[node.name] = new Float32Array(position.count * 3);

							for (let bp = 0; bp < position.count; bp++) {
								wpcb_model_object_pos[node.name][bp * 3] = position.getX(bp);
								wpcb_model_object_pos[node.name][bp * 3 + 1] = position.getY(bp);
								wpcb_model_object_pos[node.name][bp * 3 + 2] = position.getZ(bp);
							}
						}

						let size;
						if (!wpcb_model_object_sizes[node.name]) {
							node.geometry.computeBoundingBox();

							let bbox = node.geometry.boundingBox;
							size = new THREE.Vector3();
							bbox.getSize(size);

							wpcb_model_object_sizes[node.name] = size;
						} else {
							size = wpcb_model_object_sizes[node.name];
						}

						if (property === 'x') {
							let scaleFactor = value / size.x;

							xValues.push(parseFloat(value) / 1000);

							for (let p = 0; p < position.count; p++) {
								let baseX = wpcb_model_object_pos[node.name][p * 3]; // Original X

								position.setX(p, baseX * scaleFactor);
							}
						} else if (property === 'y') {
							let scaleFactor = value / size.y;

							yValues.push(parseFloat(value) / 1000);

							for (let p = 0; p < position.count; p++) {
								let baseY = wpcb_model_object_pos[node.name][p * 3 + 1]; // Original Y

								position.setY(p, baseY * scaleFactor);
							}
						} else if (property === 'z') {
							let scaleFactor = value / size.z;

							zValues.push(parseFloat(value) / 1000);

							for (let p = 0; p < position.count; p++) {
								let baseZ = wpcb_model_object_pos[node.name][p * 3 + 2]; // Original Z

								position.setZ(p, baseZ * scaleFactor);
							}
						}

						position.needsUpdate = true;

						if (node.geometry.computeVertexNormals) {
							node.geometry.computeVertexNormals();
						}
						if (node.geometry.computeBoundingBox) {
							node.geometry.computeBoundingBox();
						}
						if (node.geometry.computeBoundingSphere) {
							node.geometry.computeBoundingSphere();
						}
					}

					wpcb_model_objects[i].updateMatrix(); // Ensure matrix updates
					wpcb_model_objects[i].updateMatrixWorld(true); // Ensure matrix updates
				}
			}

			wpcb_model_viewer[wpcb_scene_sym].traverse((obj) => {
				if (obj.isMesh) {
					// Update geometry bounding box if needed
					if (!obj.geometry.boundingBox) {
						obj.geometry.computeBoundingBox();
					}
				}
			});

			// 1. Ensure all matrices are up-to-date
			wpcb_model_viewer[wpcb_scene_sym].updateMatrixWorld(true);
			// 2. Compute the bounding box of the entire scene
			let box = computeSceneBoundingBox(wpcb_model_viewer[wpcb_scene_sym]);
			// 3. Get dimensions (size of the bounding box)
			let size = new THREE.Vector3();
			box.getSize(size);
			// 4. Update framing after size update.
			wpcb_model_viewer.updateFraming();

			if ($('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+X-Y"]').length) {
				let center = wpcb_model_viewer.getBoundingBoxCenter();
				let x2 = size.x / 2;
				let y2 = size.y / 2;
				let z2 = size.z / 2;

				setTimeout(function () {
					_updateViewerDimLines(center, size, x2, y2, z2);
					_renderDimLinesSVG();
				}, 100);
			}
		}

		function computeSceneBoundingBox(scene) {
			const box = new THREE.Box3();
			const vec = new THREE.Vector3();
			// Recursively traverse all meshes
			scene.traverse((obj) => {
				if (obj.isMesh && obj.geometry && obj.geometry.attributes.position) {
					const posAttr = obj.geometry.attributes.position;
					// Ensure matrixWorld is up to date
					obj.updateMatrixWorld(true);
					// Loop through all vertices in this geometry
					for (let i = 0; i < posAttr.count; i++) {
						// Get local vertex position
						vec.fromBufferAttribute(posAttr, i);
						// Transform to world space
						vec.applyMatrix4(obj.matrixWorld);
						// Expand bounding box to include this point
						box.expandByPoint(vec);
					}
				}
			});
			return box;
		}

		function _renderDimLinesSVG() {
			let dimLines = wpcb_model_viewer.querySelectorAll('[data-wpcb-id="' + wpcb_id + '"] .wpcb-dim-line');
			_drawDimLine(dimLines[0], wpcb_model_viewer.queryHotspot('hotspot-dot+X-Y+Z'), wpcb_model_viewer.queryHotspot('hotspot-dot+X-Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dim+X-Y'));
			_drawDimLine(dimLines[1], wpcb_model_viewer.queryHotspot('hotspot-dot+X-Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dot+X+Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dim+X-Z'));
			_drawDimLine(dimLines[2], wpcb_model_viewer.queryHotspot('hotspot-dot+X+Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dot-X+Y-Z')); // always visible
			_drawDimLine(dimLines[3], wpcb_model_viewer.queryHotspot('hotspot-dot-X+Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dot-X-Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dim-X-Z'));
			_drawDimLine(dimLines[4], wpcb_model_viewer.queryHotspot('hotspot-dot-X-Y-Z'), wpcb_model_viewer.queryHotspot('hotspot-dot-X-Y+Z'), wpcb_model_viewer.queryHotspot('hotspot-dim-X-Y'));
		}

		function _drawDimLine(svgLine, dotHotspot1, dotHotspot2, dimensionHotspot) {
			if (dotHotspot1 && dotHotspot2) {
				svgLine.setAttribute('x1', dotHotspot1.canvasPosition.x);
				svgLine.setAttribute('y1', dotHotspot1.canvasPosition.y);
				svgLine.setAttribute('x2', dotHotspot2.canvasPosition.x);
				svgLine.setAttribute('y2', dotHotspot2.canvasPosition.y);

				// use provided optional hotspot to tie visibility of this svg line to
				if (dimensionHotspot && !dimensionHotspot.facingCamera) {
					svgLine.classList.add('hide');
				}
				else {
					svgLine.classList.remove('hide');
				}
			}
		}

		function _updateViewerDimLines(center, size, x2, y2, z2) {
			let unit = $('#wpcb-dim-lines-' + wpcb_id).data('unit');
			let multiplier = 100;

			if ('mm' == unit) {
				multiplier = 1000;
			} else if ('m' == unit) {
				multiplier = 1;
			} else if ('in' == unit) {
				multiplier = 39.3700787;
			} else if ('ft' == unit) {
				multiplier = 3.2808399;
			} else if ('yd' == unit) {
				multiplier = 1.0936133;
			}

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot+X-Y+Z',
				position: `${center.x + x2} ${center.y - y2} ${center.z + z2}`
			});

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dim+X-Y',
				position: `${center.x + x2 * 1.2} ${center.y - y2 * 1.1} ${center.z}`
			});

			wpcb_model_viewer.querySelector('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+X-Y"]').textContent = `${(size.z * multiplier).toFixed(0)} ${unit}`;

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot+X-Y-Z',
				position: `${center.x + x2} ${center.y - y2} ${center.z - z2}`
			});

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dim+X-Z',
				position: `${center.x + x2 * 1.2} ${center.y} ${center.z - z2 * 1.2}`
			});
			wpcb_model_viewer.querySelector('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+X-Z"]').textContent = `${(size.y * multiplier).toFixed(0)} ${unit}`;

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot+X+Y-Z',
				position: `${center.x + x2} ${center.y + y2} ${center.z - z2}`
			});

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dim+Y-Z',
				position: `${center.x} ${center.y + y2 * 1.1} ${center.z - z2 * 1.1}`
			});
			wpcb_model_viewer.querySelector('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim+Y-Z"]').textContent = `${(size.x * multiplier).toFixed(0)} ${unit}`;

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot-X+Y-Z',
				position: `${center.x - x2} ${center.y + y2} ${center.z - z2}`
			});

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dim-X-Z',
				position: `${center.x - x2 * 1.2} ${center.y} ${center.z - z2 * 1.2}`
			});
			wpcb_model_viewer.querySelector('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim-X-Z"]').textContent = `${(size.y * multiplier).toFixed(0)} ${unit}`;

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot-X-Y-Z',
				position: `${center.x - x2} ${center.y - y2} ${center.z - z2}`
			});

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dim-X-Y',
				position: `${center.x - x2 * 1.2} ${center.y - y2 * 1.1} ${center.z}`
			});
			wpcb_model_viewer.querySelector('[data-wpcb-id="' + wpcb_id + '"] button[slot="hotspot-dim-X-Y"]').textContent = `${(size.z * multiplier).toFixed(0)} ${unit}`;

			wpcb_model_viewer.updateHotspot({
				name: 'hotspot-dot-X-Y+Z',
				position: `${center.x - x2} ${center.y - y2} ${center.z + z2}`
			});
		}

		function _updateViewerHotspots() {
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer .hotspot').remove();

			for (let bh = 0; bh < wpcb_vars.base_hotspots.length; bh++) {
				let hotspotData = wpcb_vars.hotspots.find(data => data.id == wpcb_vars.base_hotspots[bh]);
				if (hotspotData) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').append(
						'<button class="hotspot" slot="hotspot-' + hotspotData.id + '" data-position="' + hotspotData.position + '" data-normal="' + hotspotData.normal + '"><div class="hotspot-toggle">' + wpcb_vars.icon_plus + wpcb_vars.icon_minus + '</div><div class="hotspot-content">' + hotspotData.content + '</div></button>'
					);
				}
			}

			if (wpcb_visible_hotspots) {
				for (let vh = 0; vh < wpcb_visible_hotspots.length; vh++) {
					let key = wpcb_visible_hotspots[vh];
					if (!wpcb_vars.base_hotspots.includes(key)) {
						let hotspotData = wpcb_vars.hotspots.find(data => data.id == wpcb_visible_hotspots[vh]);
						if (hotspotData) {
							$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery model-viewer').append(
								'<button class="hotspot" slot="hotspot-' + hotspotData.id + '" data-position="' + hotspotData.position + '" data-normal="' + hotspotData.normal + '"><div class="hotspot-toggle">' + wpcb_vars.icon_plus + wpcb_vars.icon_minus + '</div><div class="hotspot-content">' + hotspotData.content + '</div></button>'
							);
						}
					}
				}
			}
		}

		function _wpcbGenerateQRCode(url) {
			let bgColor = $('#wpcb-qr-code-popup-' + wpcb_id + ' .wpcb_configuration_qr').data('color');
			let textColor = $('#wpcb-qr-code-popup-' + wpcb_id + ' .wpcb_configuration_qr').data('background');
			// Clear contents.
			$('#wpcb-qr-code-popup-' + wpcb_id + ' .wpcb_configuration_qr').html('');
			// Add QR code
			new QRCode($('#wpcb-qr-code-popup-' + wpcb_id + ' .wpcb_configuration_qr').get(0), {
				correctLevel: QRCode.CorrectLevel.L,
				text: url,
				colorLight: bgColor,
				colorDark: textColor
			});
		}

		function _updateViewerCameraSettings(input) {
			if ($(wpcb_model_viewer).hasClass("init")) {
				return;
			}

			if (_hasLoader()) {
				return;
			}

			if ($(input).closest(".wpcb-configurator-field").data("model-target")) {

				$(wpcb_model_viewer).attr("camera-target", $(input).closest(".wpcb-configurator-field").data("model-target"));
				$(wpcb_model_viewer).attr("camera-orbit", $(input).closest(".wpcb-configurator-field").data("model-orbit"));
				$(wpcb_model_viewer).attr("field-of-view", $(input).closest(".wpcb-configurator-field").data("model-view"));
				$(wpcb_model_viewer).addClass("view-changed");

			} else if ($(input).data("model-target")) {

				$(wpcb_model_viewer).attr("camera-target", $(input).data("model-target"));
				$(wpcb_model_viewer).attr("camera-orbit", $(input).data("model-orbit"));
				$(wpcb_model_viewer).attr("field-of-view", $(input).data("model-view"));
				$(wpcb_model_viewer).removeClass("view-changed");

			} else if ($(wpcb_model_viewer).hasClass("view-changed")) {

				$(wpcb_model_viewer).attr("camera-target", "");
				$(wpcb_model_viewer).attr("camera-orbit", "");
				$(wpcb_model_viewer).attr("field-of-view", "");
				$(wpcb_model_viewer).removeClass("view-changed");

			}
		}

		/**
		 * Form functions
		 */

		function _setActiveStep( load = false ) {
			// No step found. No step active.
			if (!$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').length) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').removeClass('hidden');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_add_to_cart_button').removeClass('disabled');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_request_quote_button').removeClass('disabled');
				return;
			}

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button').removeClass('active');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button').removeClass('active');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').removeClass('active');

			let active_step = wpcb_configuration.step.active;

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button[data-step-index="' + active_step + '"]').addClass('active');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button[data-step-index="' + active_step + '"]').addClass('active');
			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step[data-step-index="' + active_step + '"]').addClass('active');

			for (let visited_step = 1; visited_step <= active_step; visited_step++) {
				if (!$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button[data-step-index="' + visited_step + '"]').hasClass('visited')) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-button[data-step-index="' + visited_step + '"]').addClass('visited');
				}
				if (!$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button[data-step-index="' + visited_step + '"]').hasClass('visited')) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-index-button[data-step-index="' + visited_step + '"]').addClass('visited');
				}
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-progress-bar').length) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-control-bar span').text(active_step);

				let total = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').length;
				let percent = Math.floor(active_step / total * 100);

				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-progress-bar-value').text(percent + '%');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step-progress-bar-value').css('width', percent + '%');
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-prev').length) {
				if (1 === active_step) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-prev').addClass('disabled');
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-prev').removeClass('disabled');
				}
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').length === active_step) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals-button').removeClass('disabled');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals-summary-button').removeClass('disabled');

				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').removeClass('hidden');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_add_to_cart_button').removeClass('disabled');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_request_quote_button').removeClass('disabled');

				// if ( $('.wpcb-configurator-inline-quote-form').length) {
				// 	$('.wpcb-configurator-form-control-actions').hide();
				// 	$('.wpcb-configurator-form-totals-actions').hide();
				// 	$('.wpcb-configurator-form-actions-list').hide();

				// 	_handleConfiguratorQuoteAction( $('#single_request_quote_button').data('quote-type').split(',') );
				// }
			} else {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals-button').addClass('disabled');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals-summary-button').addClass('disabled');

				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').addClass('hidden');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_add_to_cart_button').addClass('disabled');
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-totals .single_request_quote_button').addClass('disabled');

				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-inline-quote-form').length) {
					// 	$('.wpcb-configurator-form-control-actions').show();
					// 	$('.wpcb-configurator-form-totals-actions').show();
					// 	$('.wpcb-configurator-form-actions-list').show();
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-inline-quote-form').addClass('hidden');
				}
			}

			if ( ! load ) {
				// Switching steps, start at top.
				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-template-height-fixed').length && $(window).width() > 991 || $('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
					$('.wpcb-configurator-options').scrollTop(0);
				} else if ($('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
					$('html,body').scrollTop($('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').offset().top);
				}
			}

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-next').length) {
				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step').length === active_step) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-next').addClass('disabled');
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-next').removeClass('disabled');
				}

				if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-step[data-step-index="' + active_step + '"]').data('step-button')) {
					// Allow to set custom button text
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-step-next').text($('.wpcb-configurator-step[data-step-index="' + active_step + '"]').data('step-button'));
				}
			}
		}

		function _setOptionSummary($option) {
			let isDropdown = false;
			let summaryId, summary = '';

			if ($option.siblings('.wpcb-option-summary').length) {
				summary = $option.siblings('.wpcb-option-summary').get(0).outerHTML;
				summaryId = $option.siblings('.wpcb-option-summary').attr('data-summary-id');
			} else if ($option.closest('.wpcb-dropdown').length) {
				isDropdown = true;

				summaryId = $option.attr('id');
				summary = '';

				if ($option.closest('.wpcb-configurator-field').find('[data-summary-id="' + summaryId + '"]').length) {
					summary = $option.closest('.wpcb-configurator-field').find('[data-summary-id="' + summaryId + '"]').get(0).outerHTML;
				}
			}

			if (1 == $option.closest('.wpcb-configurator-field').data('max') || isDropdown) {
				// Single item
				if ($option.is(':checked') || $option.is(':selected')) {
					$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').html(summary);
				}
				else if (!_hasLoader()) {
					$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').html('');
				}
			} else {
				// Multiple items
				if ($option.is(':checked')) {
					if (!$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').find('div[data-summary-id="' + summaryId + '"]').length) {
						$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').append(summary);
					}
				} else {
					$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary .wpcb-option-summary[data-summary-id="' + summaryId + '"]').remove();
				}
			}

			if ($option.closest('.wpcb-configurator-field').find('.wpcb-option-summary').length && !$option.closest('.wpcb-configurator-field').hasClass('wpcb-collapsed')) {
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').addClass('active');
			} else {
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-field-summary').removeClass('active');
			}
		}

		function _setOptionPanelHtml(optionId, $option) {
			let label = $('input#' + optionId).closest('.wpcb-option').find('.wpcb-option-label').text();
			if ( ! label ) {
				label = $('input#' + optionId).data('label');
			}

			let price = $('input#' + optionId).closest('.wpcb-option').find('.wpcb-option-price').html();
			if ( ! price ) {
				let option_price = $('input#' + optionId).data('price');
				let option_sale = $('input#' + optionId).data('sale');

				if ( option_sale ) {
					price = '<del>' + _formatPriceOutput(option_price, '', true) + '</del>' + _formatPriceOutput(option_sale);
				} else if ( option_price ) {
					price = _formatPriceOutput(option_price, '', true);
				}
			}

			let description = $('input#' + optionId).closest('.wpcb-option').find('.wpcb-option-description-html').html();
			let gallery_html = $('input#' + optionId).closest('.wpcb-option').find('.wpcb-option-description-html').data('images');

			let type = 'panel';
			if (!$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-label').length) {
				type = 'popup';
			}

			$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-label').html(label);
			$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-price').html(price);
			$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-content').html(description);

			if (gallery_html) {
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-gallery').html('<div class="wpcb-configurator-' + type + '-slick">' + gallery_html + '</div>');
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-gallery').addClass('active');
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-title').html(
					$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-details').html()
				);
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-slick').slick(_getFieldPanelGalleryOptions());
			} else {
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-gallery').removeClass('active');
				$option.closest('.wpcb-configurator-field').find('.wpcb-configurator-' + type + '-gallery').html('');
			}
		}

		function _setFieldPanelHtml($field, selector = '.wpcb-configurator-field') {
			let price = '';
			let label = $field.closest(selector).find('.wpcb-configurator-field-label').text();
			let description = $field.closest(selector).find('.wpcb-configurator-field-description').html();
			let gallery_html = $field.closest(selector).find('.wpcb-configurator-field-description').data('images');

			let type = 'panel';
			if (!$field.closest(selector).find('.wpcb-configurator-' + type + '-label').length) {
				type = 'popup';
			}

			$field.closest(selector).find('.wpcb-configurator-' + type + '-label').html(label);
			$field.closest(selector).find('.wpcb-configurator-' + type + '-price').html(price);
			$field.closest(selector).find('.wpcb-configurator-' + type + '-content').html(description);

			if (gallery_html) {
				$field.closest(selector).find('.wpcb-configurator-' + type + '-gallery').html('<div class="wpcb-configurator-' + type + '-slick">' + gallery_html + '</div>');
				$field.closest(selector).find('.wpcb-configurator-' + type + '-gallery').addClass('active');
				$field.closest(selector).find('.wpcb-configurator-' + type + '-title').html(
					$field.closest(selector).find('.wpcb-configurator-' + type + '-details').html()
				);
				$field.closest(selector).find('.wpcb-configurator-' + type + '-slick').slick(_getFieldPanelGalleryOptions());
			} else {
				$field.closest(selector).find('.wpcb-configurator-' + type + '-gallery').removeClass('active');
				$field.closest(selector).find('.wpcb-configurator-' + type + '-gallery').html('');
			}
		}

		function _getFieldPanelGalleryOptions() {
			let slickPanelOptions = {
				slidesToShow: 2,
				slidesToScroll: 2,
				infinite: false,
				draggable: false,
				swipe: false,
				swipeToSlide: false,
				touchMove: true,
				arrows: true,
				prevArrow: '<button type="button" class="slick-prev"><img src="' + wpcb_vars.icon_arrow + '" alt="slick arrow prev" width="24" height="24"></button>',
				nextArrow: '<button type="button" class="slick-next"><img src="' + wpcb_vars.icon_arrow + '" alt="slick arrow next" width="24" height="24"></button>',
				responsive: [
					{
						breakpoint: 991,
						settings: {
							slidesToShow: 1,
							slidesToScroll: 1,
						}
					}
				]
			};
			if ($('.wpcb-is-rtl').length) {
				slickPanelOptions.rtl = true;
			}

			return slickPanelOptions;
		}

		/**
		 * Pricing functions
		 */

		function _updateOptionPriceDisplay(field_id) {
			if (!wpcb_vars.settings.price_option_difference) {
				return; // Don't show difference. Return.
			}
			if (!wpcb_configuration.selection[field_id].values.length) {
				return;
			}
			if (wpcb_configuration.selection[field_id].values.length > 1) {
				return;
			}

			if (field_id.includes('filter-')) {
				return;
			}
			if ($('.wpcb-configurator-field[data-id="' + field_id.replace('field-', '') + '"]').find('.wpcb-option-product').length) {
				return;
			}
			if ($('.wpcb-configurator-field[data-id="' + field_id.replace('field-', '') + '"]').closest('.wpcb-configurator-field-filter').length) {
				return;
			}

			let val_id = wpcb_configuration.selection[field_id].values[0].id;
			let value = false;

			wpcb_configuration.selection[field_id].options.forEach(function (option, index) {
				if (option.id == val_id) {
					value = option;
				}
			});

			if (value) {
				wpcb_configuration.selection[field_id].options.forEach(function (option, index) {
					if (option.id == val_id) {
						$('label[for=' + option.id + '] .wpcb-option-details').find('.wpcb-option-price').html('');
						$('label[for=' + option.id + '] .wpcb-option-totals').find('.wpcb-option-price').html('');
						// $('.wpcb-option-summary[data-summary-id=' + option.id + ']').find('.wpcb-option-price').html('');
					} else {
						let price_diff = option.price - value.price;
						$('label[for=' + option.id + '] .wpcb-option-details').find('.wpcb-option-price').html(_formatPriceOutput(price_diff));
						$('label[for=' + option.id + '] .wpcb-option-totals').find('.wpcb-option-price').html(_formatPriceOutput(price_diff));
					}
				});
			}
		}

		function _updateConfigurationPrices() {
			// Update conditional price discounts
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-options input[data-discount]').length ) {
				Object.keys(wpcb_configuration.selection).forEach((group_name) => {
					let group_details = wpcb_configuration.selection[group_name];
					if (!group_details.values) {
						return;
					}
					if (!group_details.options) {
						return;
					}

					group_details.options.forEach((option, index) => {
						let option_id = wpcb_configuration.selection[group_name].options[index].id;

						if ( $('input[data-id="' + option_id + '"][data-discount]:checked').length ) {
							let field_id = wpcb_configuration.selection[group_name].id;
							var new_sale = parseFloat( $('input[data-id="' + option_id + '"]').data('discount') );
							var base_sale = parseFloat( $('input[data-id="' + option_id + '"]').data('sale') );

							if ( _validateDiscountRules( $('input[data-id="' + option_id + '"]').data('discount-rules') ) ) {
								wpcb_configuration.selection[group_name].sale_cost = wpcb_configuration.selection[group_name].cost - wpcb_configuration.selection[group_name].options[index].price + new_sale;
								wpcb_configuration.selection[group_name].options[index].sale = new_sale;
							} else if ( base_sale ) {
								wpcb_configuration.selection[group_name].options[index].sale = base_sale;
								wpcb_configuration.selection[group_name].sale_cost = base_sale;
							} else {
								wpcb_configuration.selection[group_name].options[index].sale = false;
								wpcb_configuration.selection[group_name].sale_cost = false;
							}

							if ($('input[data-id="' + option_id + '"]').closest('.wpcb-configurator-field').hasClass('wpcb-collapsible')) {
								let value_html = _getConfigurationCollapsibleOptionSummary($('input[data-id="' + option_id + '"]'), 'field-' + field_id);
								$('.wpcb-configurator-field[data-id="' + field_id + '"]').find('.wpcb-configurator-field-value').html(value_html);
							} else if ($('input[data-id="' + option_id + '"]').closest('.wpcb-configurator-field').find('.wpcb-configurator-field-value').length ) {
								let value_html = _getConfigurationFieldSummary( $('input[data-id="' + option_id + '"]'), 'field-' + field_id );
								$('.wpcb-configurator-field[data-id="' + field_id + '"]').find('.wpcb-configurator-field-value').html(value_html);
							}

							if ( $('[data-summary-id="' + option_id + '"]').length || $('input[data-id="' + option_id + '"]').closest('.wpcb-option').find('.wpcb-option-price').length ) {
								var price = wpcb_configuration.selection[group_name].options[index].price;
								var sale = wpcb_configuration.selection[group_name].options[index].sale;
								var price_html = '';
								if ( wpcb_configuration.selection[group_name].options[index].sale ) {
									price_html = '<del>' + _formatPriceOutput( price, '', true ) + '</del>' + _formatPriceOutput( sale, '', false );

									if ( $('input[data-id="' + option_id + '"]').closest('.wpcb-option').find('.wpcb-discount-label').length ) {
										var discount = Math.ceil( ( wpcb_configuration.selection[group_name].options[index].price - sale ) / wpcb_configuration.selection[group_name].options[index].price * 100 );
										price_html += '<span class="wpcb-discount-label">-' + discount + '%</span>';
									}
								} else {
									price_html = _formatPriceOutput( price, '', true );
								}

								if ( $('[data-summary-id="' + option_id + '"]').length ) {
									$('[data-summary-id="' + option_id + '"]').find('.wpcb-option-price').html( price_html );
								} else {
									$('input[data-id="' + option_id + '"]').closest('.wpcb-option').find('.wpcb-option-price').html( price_html );
								}
							}
						}
					});
				});
			}

			let totals = _calculateTotals(true);

			let price_keys = {};
			Object.keys(wpcb_configuration.selection).forEach((group_name) => {
				let group_details = wpcb_configuration.selection[group_name];
				let group_value = group_details.value;

				if (!group_details.values) {
					return;
				}

				if (group_value || group_details.values) {
					if (group_details.sale_cost) {
						price_keys[group_details.label] = group_details.sale_cost;
					} else {
						price_keys[group_details.label] = group_details.cost;
					}
				}
			});

			Object.keys(wpcb_configuration.selection).forEach((group_name) => {
				let group_details = wpcb_configuration.selection[group_name];
				if (!group_details.options) {
					return;
				}

				group_details.options.forEach((option, index) => {
					if (option.price_field && option.price_percent) {
						let price = 0;

						if (option.price_field == 'total_price') {
							price = totals.total_price;
						}
						else if (option.price_field == 'option_price') {
							price = totals.option_price;
						}
						else if (option.price_field == 'product_price') {
							price = totals.product_price;
						}
						else if ('product_price' in price_keys) {
							price = price_keys['product_price'];
						}
						else {
							for (let groupId in wpcb_configuration.selection) {
								if (Array.isArray(wpcb_configuration.selection[groupId].values)) {
									if (wpcb_configuration.selection[groupId].id == option.price_field) {
										price = wpcb_configuration.selection[groupId].cost;
									}
								}
							}
						}

						if (price) {
							let option_price = price * (option.price_percent / 100);
							let option_tax = 0;

							if (wpcb_configuration.selection[group_name].options[index].tax_percent > 0) {
								option_tax = _getConfigurationOptionTax(option_price, wpcb_configuration.selection[group_name].options[index].tax_percent);

								if ('excl' === wpcb_vars.settings.price_tax_display && wpcb_vars.settings.price_tax_included) {
									option_price -= option_tax;
								} else if ('incl' === wpcb_vars.settings.price_tax_display && !wpcb_vars.settings.price_tax_included) {
									option_price += option_tax;
								}
							}

							wpcb_configuration.selection[group_name].options[index].price = option_price;

							if (wpcb_configuration.selection[group_name].options[index].active) {
								wpcb_configuration.selection[group_name].cost = option_price;
								wpcb_configuration.selection[group_name].tax = option_tax;
							}

							if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').length) {
								$('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').html(
									_formatPriceOutput(option_price, '', true)
								);
							}

							if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').hasClass('wpcb-collapsible')) {
								let field_id = $('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').data('id');
								let value_html = _getConfigurationCollapsibleOptionSummary($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]'), 'field-' + field_id);
								$('.wpcb-configurator-field[data-id="' + field_id + '"]').find('.wpcb-configurator-field-value').html(value_html);
							}
						}
					}

					if (option.price_formula) {
						let option_price = 0;
						let option_tax = 0;
						let formula = option.price_formula;
						let rounding = option.price_rounding;

						for (let groupId in wpcb_configuration.selection) {
							if (Array.isArray(wpcb_configuration.selection[groupId].values)) {
								wpcb_configuration.selection[groupId].values.forEach(function (subvalue, index) {
									formula = formula.replaceAll('${' + subvalue.label + '}', subvalue.cost);
									formula = formula.replaceAll('${' + subvalue.label + ':value}', subvalue.value);
									formula = formula.replaceAll('${' + subvalue.label + ':length}', subvalue.value.length);
								});

								formula = formula.replaceAll('${' + wpcb_configuration.selection[groupId].label + ':count}', wpcb_configuration.selection[groupId].values.length);
								formula = formula.replaceAll('${' + wpcb_configuration.selection[groupId].label + '}', wpcb_configuration.selection[groupId].cost);
							}
						}

						formula = formula.replace('${Product price}', wpcb_vars.product_price);
						formula = formula.replace('${Total price}', wpcb_configuration.totals.total_price);
						formula = formula.replace('${Total weight}', wpcb_configuration.totals.total_weight);

						if (formula.indexOf('${') === -1) {
							option_price = eval(formula);
						}

						if (rounding) {
							option_price = _getRoundedFormulaPrice(option_price, rounding);
						}

						if (wpcb_configuration.selection[group_name].options[index].tax_percent > 0) {
							option_tax = _getConfigurationOptionTax(option_price, wpcb_configuration.selection[group_name].options[index].tax_percent);

							if ('excl' === wpcb_vars.settings.price_tax_display && wpcb_vars.settings.price_tax_included) {
								option_price -= option_tax;
							} else if ('incl' === wpcb_vars.settings.price_tax_display && !wpcb_vars.settings.price_tax_included) {
								option_price += option_tax;
							}
						}

						wpcb_configuration.selection[group_name].options[index].price = option_price;

						if (wpcb_configuration.selection[group_name].options[index].active) {
							wpcb_configuration.selection[group_name].cost = option_price;
							wpcb_configuration.selection[group_name].tax = option_tax;
						}

						if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').length) {
							$('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').html(
								_formatPriceOutput(option_price, '', true)
							);
						}

						if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').hasClass('wpcb-collapsible')) {
							let field_id = $('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').data('id');
							let value_html = _getConfigurationCollapsibleOptionSummary($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]'), 'field-' + field_id);
							$('.wpcb-configurator-field[data-id="' + field_id + '"]').find('.wpcb-configurator-field-value').html(value_html);
						}
					}

					if (wpcb_configuration.selection[group_name].price_type === 'table') {
						// Option table pricings. Update calculated costs
						let option_price = option.price;
						let option_tax = 0;

						if (wpcb_configuration.selection[group_name].options[index].tax_percent > 0) {
							option_tax = _getConfigurationOptionTax(option_price, wpcb_configuration.selection[group_name].options[index].tax_percent);

							if ('excl' === wpcb_vars.settings.price_tax_display && wpcb_vars.settings.price_tax_included) {
								option_price -= option_tax;
							} else if ('incl' === wpcb_vars.settings.price_tax_display && !wpcb_vars.settings.price_tax_included) {
								option_price += option_tax;
							}
						}

						wpcb_configuration.selection[group_name].options[index].price = option_price;

						if (wpcb_configuration.selection[group_name].options[index].active) {
							wpcb_configuration.selection[group_name].cost = option_price;
							wpcb_configuration.selection[group_name].tax = option_tax;
						}

						if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').length) {
							$('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('label').find('.wpcb-option-price').html(
								_formatPriceOutput(option_price, '', true)
							);
						}

						if ($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').hasClass('wpcb-collapsible')) {
							let field_id = $('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]').closest('.wpcb-configurator-field').data('id');
							let value_html = _getConfigurationCollapsibleOptionSummary($('input[data-id="' + wpcb_configuration.selection[group_name].options[index].id + '"]'), 'field-' + field_id);
							$('.wpcb-configurator-field[data-id="' + field_id + '"]').find('.wpcb-configurator-field-value').html(value_html);
						}
					}
				});

				_updateOptionPriceDisplay(group_name);
			});

			let total_formula = '';
			let total_formula_rounding = '';
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-formula')) {
				total_formula = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-formula');
				total_formula_rounding = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-rounding');
			}

			if ('' !== total_formula) {
				let total_formula = _fillGroupFormula(
					total_formula, // formula
					false // based on full configuration
				);

				total_formula = total_formula.replace('${Product price}', wpcb_vars.product_price);
				total_formula = total_formula.replace('${Total price}', wpcb_configuration.totals.total_price);
				total_formula = total_formula.replace('${Total weight}', wpcb_configuration.totals.total_weight);
				if (total_formula.indexOf('${') === -1) {
					let formula_total = eval(total_formula);

					if (total_formula_rounding) {
						formula_total = _getRoundedFormulaPrice(formula_total, total_formula_rounding);
					}

					wpcb_configuration.totals.formula_total = formula_total;
				}
			}
		}

		function _getConfigurationOptionTax(cost, tax_percent) {
			let tax_price = 0;
			if ('incl' === wpcb_vars.settings.price_tax_display) {
				tax_price = cost / (1 + (tax_percent / 100));
			} else {
				tax_price = cost * (1 + (tax_percent / 100));
			}
			// Get difference
			let option_tax = Math.abs(cost - tax_price);
			return option_tax;
		}

		function _calculateTotals(dynamic = false) {
			let total_price = 0;
			let total_sale_price = 0;
			let total_tax = 0;
			let total_weight = 0;
			let sku_format = false;
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-product-sku[data-format]').length) {
				sku_format = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-product-sku').data('format');
			}

			Object.keys(wpcb_configuration.selection).forEach((group_name) => {
				let group_details = wpcb_configuration.selection[group_name];
				let group_value = group_details.value;

				if (group_details.hasOwnProperty('weight')) {
					total_weight += parseFloat(group_details.weight);
				}

				if (!group_details.values) {
					return;
				}

				if (sku_format) {
					group_details.values.forEach(function (item, key) {
						if (item.sku) {
							sku_format = sku_format.replace('{' + group_details.id + '}', item.sku);
						}
						else if ( item.input_type && item.value ) {
							sku_format = sku_format.replace('{' + group_details.id + '}', item.value);
						}
					});
					sku_format = sku_format.replace('{' + group_details.id + '}', '');
				}
				
				if (!group_details.cost) {
					return;
				}
				if (dynamic && ('formula' == group_details.price_type || 'percentage' == group_details.price_type)) {
					return;
				}

				if (group_details.hasOwnProperty('tax')) {
					total_tax += parseFloat(group_details.tax);
				}

				if (group_value || group_details.values) {
					total_price += group_details.cost;

					if (group_details.sale_cost) {
						total_sale_price += group_details.sale_cost;
					} else {
						total_sale_price += group_details.cost;
					}
				}
			});

			let qty = wpcb_configuration.totals.product_qty;
			let price_qty = wpcb_configuration.totals.product_qty;
			let product_base_total = 0;
			if (wpcb_vars.settings.use_product_price) {
				product_base_total = '' != wpcb_vars.product_sale_price ? wpcb_vars.product_sale_price : wpcb_vars.product_price;
			}
			if (wpcb_vars.settings.price_totals_single) {
				price_qty = 1;
			}

			let table_id = false;
			let price_id = false;
			let price_type = 'fixed';
			let price_formula = '';
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-table')) {
				table_id = parseInt($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-table'));
				price_type = 'table';
				product_base_total = '' != wpcb_vars.product_sale_price ? wpcb_vars.product_sale_price : wpcb_vars.product_price;
			}
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-formula')) {
				price_id = parseInt($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('formula'));
				price_formula = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-form-add-to-cart').data('price-formula');
			}

			let product_option_total = total_sale_price ? total_sale_price : total_price;
			let product_grand_total = (product_base_total + total_price) * price_qty;
			let product_grand_sale = (product_base_total + total_sale_price) * price_qty;

			// if ( total_tax > 0 ) {
			// 	// Temp total calculation
			// 	if ( 'incl' === wpcb_vars.settings.price_tax_display ) {
			// 		total_tax = total_sale_price / ( 1 + (wpcb_vars.product_tax_percent / 100) );
			// 	} else {
			// 		total_tax = total_sale_price * ( 1 + (wpcb_vars.product_tax_percent / 100) );
			// 	}
			// 	total_tax = Math.abs( total_tax - total_sale_price );
			// }

			if (wpcb_vars.product_tax_percent > 0) {
				let product_base_tax = 0;
				if ('incl' === wpcb_vars.settings.price_tax_display) {
					product_base_tax = product_base_total / (1 + (wpcb_vars.product_tax_percent / 100));
				} else {
					product_base_tax = product_base_total * (1 + (wpcb_vars.product_tax_percent / 100));
				}
				total_tax += Math.abs(product_base_tax - product_base_total);
			}

			if (wpcb_configuration.totals.formula_total) {
				product_grand_total = (product_base_total + wpcb_configuration.totals.formula_total) * price_qty;
				product_grand_sale = (product_base_total + wpcb_configuration.totals.formula_total) * price_qty;
				product_option_total = wpcb_configuration.totals.formula_total;

				if (wpcb_vars.product_tax_percent > 0) {
					let product_grand_tax = 0;
					// Recalculate taxes based on total formula value
					if ('incl' === wpcb_vars.settings.price_tax_display) {
						product_grand_tax = product_grand_sale / (1 + (wpcb_vars.product_tax_percent / 100));
					} else {
						product_grand_tax = product_grand_sale * (1 + (wpcb_vars.product_tax_percent / 100));
					}
					total_tax = Math.abs(product_grand_tax - product_grand_sale);
				}
			}

			if (total_tax > 0) {
				total_tax = total_tax * price_qty;
			}

			let product_total_weight = total_weight;
			if (wpcb_vars.product_weight > 0) {
				product_total_weight += parseFloat(wpcb_vars.product_weight);
			}

			if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-weight[data-formula]').length ) {
				var weight_formula = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-weight').data('formula');

				if ('' !== weight_formula) {
					weight_formula = _fillGroupFormula(
						weight_formula, // formula
						false // based on full configuration
					);

					weight_formula = weight_formula.replace('${Product price}', wpcb_vars.product_price);
					weight_formula = weight_formula.replace('${Total price}', wpcb_configuration.totals.total_price);
					weight_formula = weight_formula.replace('${Total weight}', product_total_weight);
					if (weight_formula.indexOf('${') === -1) {
						let formula_total = eval(weight_formula);
						let weight_formula_rounding = false;
						if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-weight[data-formula-rounding]').length ) {
							weight_formula_rounding = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-weight').data('formula-rounding');
						}

						if (weight_formula_rounding) {
							formula_total = _getRoundedFormulaPrice(formula_total, weight_formula_rounding);
						}

						product_total_weight = formula_total;
					}
				}
			}

			if (sku_format && sku_format.indexOf('{') !== -1) {
				sku_format = sku_format.replaceAll(/{\d+}/g, '');
			}

			let total = product_grand_total;
			let total_sale = product_grand_sale;
			let subtotal = product_grand_total + total_tax;
			let subtotal_sale = product_grand_sale + total_tax;
			if ('incl' == wpcb_vars.settings.price_tax_display) {
				subtotal = product_grand_total - total_tax;
				subtotal_sale = product_grand_sale - total_tax;
			}

			return {
				price_type: price_type,
				price_formula: price_formula,
				price_id: price_id,
				price_table_id: table_id,
				product_price: product_base_total,
				product_qty: qty,
				product_sku: sku_format,
				product_tax_rate: wpcb_vars.product_tax_percent,
				option_price: (product_option_total * price_qty),
				subtotal_price: subtotal,
				subtotal_sale: subtotal_sale,
				total_tax: total_tax,
				total_price: total,
				sale_price: total_sale,
				total_weight: (product_total_weight * price_qty),
			}
		}

		function _refreshTotals() {
			// Check and set stock / product quantity
			let stock_compare = 1;
			let selector = _getConfigurationQuantitySelector();
			if ($(selector).length) {
				stock_compare = parseInt($(selector).val());
			}
			wpcb_configuration.totals.product_qty = stock_compare;

			let totals = _calculateTotals();
			let in_stock = wpcb_configuration.product_stock > 0;
			Object.keys(wpcb_configuration.selection).forEach((group_name) => {
				let group_details = wpcb_configuration.selection[group_name];
				if (!group_details.values) {
					return;
				}
				if (group_details.hasOwnProperty('in_stock') && !group_details.in_stock) {
					in_stock = false;
				}
				if (group_details.hasOwnProperty('stock') && group_details.stock < stock_compare) {
					in_stock = false;
				}
			});

			if (totals.product_sku) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-product-sku').text(totals.product_sku);
			}

			if (!wpcb_vars.settings.allow_backorders && !wpcb_configuration.allow_backorder) {
				// Backorders not allowed. Disable cart button.
				$('[data-wpcb-id="' + wpcb_id + '"] .single_add_to_cart_button').prop('disabled', !in_stock);
			}

			let price_table_html = `
			<div class="wpcb-price-table">
				<div class="wpcb-price-table-items">`;

			let options_html = _getConfigurationOptionsHtml(wpcb_configuration.selection);
			if ('' === options_html) {
				options_html += '<dl><td>' + wpcb_vars.summary_empty_label + '</dt></dl>';
			}

			price_table_html += options_html;

			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-stock').length) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-stock').removeClass('active');

				if (!in_stock) {
					if (!wpcb_vars.settings.allow_backorders && !wpcb_configuration.allow_backorder) {
						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-stock.out-of-stock').addClass('active');
					} else {
						$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-stock.on-backorder').addClass('active');
					}
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-stock.in-stock').addClass('active');
				}
			}

			if ('price' === wpcb_vars.weight_display) {
				let weight_html = parseFloat(totals.total_weight).toFixed(2) + ' ' + wpcb_vars.weight_unit;
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-weight').html(weight_html);
			}

			let table_total_html = '';
			if (wpcb_vars.settings.price_visible) {
				let total_weight_col = '';
				if ('table' === wpcb_vars.weight_display) {
					let weight_html = parseFloat(totals.total_weight).toFixed(2) + ' ' + wpcb_vars.weight_unit;

					total_weight_col = `<div class="wpcb-price-table-row">
						<div class="wpcb-price-table-col"><p>${wpcb_vars.table_weight_label}</p></div>
						<div class="wpcb-price-table-col"><p>${weight_html}</p></div>
					</div>`;
				}

				let total_tax_col = '';
				if (wpcb_vars.table_tax_label && wpcb_vars.settings.tax_row_display) {
					total_tax_col = `<div class="wpcb-price-table-row">
						<div class="wpcb-price-table-col"><p>${wpcb_vars.table_tax_label}</p></div>
						<div class="wpcb-price-table-col"><p>${_formatPriceOutput(totals.total_tax)}</p></div>
					</div>`;
				}

				let subtotal_col = '';
				if (wpcb_vars.table_subtotal_label && wpcb_vars.settings.price_tax_display_both) {
					subtotal_col = `<div class="wpcb-price-table-row">
						<div class="wpcb-price-table-col"><p>${wpcb_vars.table_subtotal_label}</p></div>
						<div class="wpcb-price-table-col"><p>${_formatPriceOutput(totals.subtotal_sale)}</p></div>
					</div>`;
				}

				let grand_total_col = '';
				if (wpcb_vars.table_total_label) {
					grand_total_col = `<div class="wpcb-price-table-row">
						<div class="wpcb-price-table-col"><strong>${wpcb_vars.table_total_label}</strong></div>
						<div class="wpcb-price-table-col"><strong>${_formatPriceOutput(totals.sale_price)}</strong></div>
					</div>`;
				}

				let total_breakdown_col = '';
				if (wpcb_vars.settings.price_breakdown) {
					total_breakdown_col = `
						<div class="wpcb-price-table-row">
							<div class="wpcb-price-table-col"><p>${wpcb_vars.table_product_label}</p></div>
							<div class="wpcb-price-table-col"><p>${_formatPriceOutput(totals.product_price)}</p></div>
						</div>
						<div class="wpcb-price-table-row">
							<div class="wpcb-price-table-col"><p>${wpcb_vars.table_option_label}</p></div>
							<div class="wpcb-price-table-col"><p>${_formatPriceOutput(totals.option_price)}</p></div>
						</div>`;
				}

				table_total_html = `
				<div class="wpcb-price-table-totals">
					${total_weight_col}
					${total_breakdown_col}
					${total_tax_col}
					${subtotal_col}
					${grand_total_col}
				</div>`;

				if (wpcb_vars.settings.price_totals_inline && $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-price-table-totals').length) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-price-table-totals').html(
						`<div class="wpcb-price-table-totals">
							${total_weight_col}
							${total_breakdown_col}
							${total_tax_col}
						</div>`
					);
					price_table_html += `</div></div>`;
				} else if ($('.wpcb-configurator-review[data-wpcb-id="' + wpcb_id + '"]').length) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-price-table-totals').html(
						'<div class="wpcb-price-table">' + table_total_html + '</div>'
					);
					price_table_html += `</div></div>`;
				} else {
					price_table_html += `</div>${table_total_html}</div>`;
				}
			}

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-price-table').html(price_table_html);

			if (totals.sale_price || wpcb_vars.product_sale_price) {
				if (totals.total_price !== totals.sale_price) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-price').html('<del>' + _formatPriceOutput(totals.total_price) + '</del><ins>' + _formatPriceOutput(totals.sale_price) + '</ins>');
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-price').html(_formatPriceOutput(totals.total_price));
				}
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-tax').html(_formatPriceOutput(totals.subtotal_sale));
			} else {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-price').html(_formatPriceOutput(totals.total_price));
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-total-tax').html(_formatPriceOutput(totals.subtotal_sale));
			}

			if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-wrapper[data-rules]').length ) {
				var rules = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-wrapper').data('rules');
				if ( _validateButtonRules(rules) ) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-wrapper').show();
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-wrapper').hide();
				}
			}

			if ( $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-alt-wrapper[data-rules]').length ) {
				var rules = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-alt-wrapper').data('rules');
				if ( _validateButtonRules(rules) ) {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-alt-wrapper').show();
				} else {
					$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button-alt-wrapper').hide();
				}
			}

			// Refresh email link.
			if ($('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button.single_send_email_button').length) {
				_refreshEmailBodyLink();
			}
		}

		function _refreshEmailBodyLink() {
			let fullEmailLink = $('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button.single_send_email_button').attr('href');
			let baseLink = fullEmailLink.split('body=')[0];
			let emailLink = baseLink + 'body=';
			let emailBody = _getConfigurationOptionsText(wpcb_configuration.selection, true);

			if (wpcb_vars.email_body_prefix) {
				emailBody = wpcb_vars.email_body_prefix + '%0D%0A' + emailBody;
			}

			if (wpcb_vars.email_body_suffix) {
				emailBody += '%0D%0A%0D%0A' + wpcb_vars.email_body_suffix;
			}

			$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-button.single_send_email_button').attr('href', emailLink + emailBody);
		}

		function _updateConfigurationAnalytics(action, data = {}) {
			$.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: {
					action: 'wpcb_update_analytics',
					nonce: wpcb_vars.nonce,
					post_id: wpcb_vars.configurator_id,
					method: action,
				}
			});

			if ('ga' === wpcb_vars.analytics_type && typeof gtag !== 'undefined') {
				gtag('event', 'wcb_event_'.action, {
					configuration: wpcb_vars.configurator_id,
					...data
				});
			} else if ('gtm' === wpcb_vars.analytics_type && typeof dataLayer !== 'undefined') {
				window.dataLayer.push({
					event: 'wcb_event_'.action,
					configuration: wpcb_vars.configurator_id,
					...data
				});
			}
		}

		function _updateConfigurationStatistics(configuration) {
			$.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: {
					action: 'wpcb_update_statistics',
					nonce: wpcb_vars.nonce,
					post_id: wpcb_vars.configurator_id,
					selections: configuration,
				}
			});
		}

		function _getRangeTableValue(field_id, label, table, val, group_id = '', tax_rate = 0) {
			let data = {
				action: 'wpcb_get_range_table_value',
				nonce: wpcb_vars.nonce,
				id: field_id, // Pass for later reference
				group: group_id, // Pass for later reference
				label: label, // Pass for later reference
				table_id: table,
				value: val,
				tax_rate: tax_rate,
			};

			wpcb_pending_calls.push($.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: data
			}));
		}

		function _getMatrixPriceTableValue(details = {}) {
			let data = {
				action: 'wpcb_get_matrix_table_value',
				nonce: wpcb_vars.nonce,
				...details
			};

			wpcb_pending_calls.push($.ajax({
				type: 'post',
				url: wpcb_vars.ajax_url,
				data: data
			}));
		}

		function _getConfigurationQuantitySelector() {
			return wpcb_vars.product_quantity_name;
		}

		function _formatPriceOutput(price, tax_label = '', show_prefix = false, tax = 0) {
			let totalPrice = parseFloat(price),
				htmlPrice;

			totalPrice = totalPrice.toFixed(wpcb_vars.settings.number_of_decimals);
			htmlPrice = totalPrice.toString().replace('.', wpcb_vars.settings.decimal_separator);

			if (wpcb_vars.settings.thousand_separator.length > 0) {
				htmlPrice = _addThousandSep(htmlPrice);
			}

			if (wpcb_vars.settings.currency_symbol) {
				if (wpcb_vars.settings.currency_position == 'right') {
					htmlPrice = htmlPrice + wpcb_vars.settings.currency_symbol;
				} else if (wpcb_vars.settings.currency_position == 'right_space') {
					htmlPrice = htmlPrice + ' ' + wpcb_vars.settings.currency_symbol;
				} else if (wpcb_vars.settings.currency_position == 'left_space') {
					htmlPrice = wpcb_vars.settings.currency_symbol + ' ' + htmlPrice;
				} else {
					htmlPrice = wpcb_vars.settings.currency_symbol + htmlPrice;
				}
			}

			if ('' !== wpcb_vars.price_prefix && show_prefix) {
				return wpcb_vars.price_prefix + htmlPrice + tax_label;
			}

			return htmlPrice + tax_label;
		}

		function _addThousandSep(n) {
			let rx = /(\d+)(\d{3})/;
			return String(n).replace(/^\d+/, function (w) {
				while (rx.test(w)) {
					w = w.replace(rx, '$1' + wpcb_vars.settings.thousand_separator + '$2');
				}
				return w;
			});
		}

		function _getRoundedFormulaPrice(price, ending = 0.99) {
			let whole = Math.floor(price);
			let rounded = whole + ending;

			if (price > rounded) {
				rounded = whole + 1 + ending;
			}

			return parseFloat(rounded.toFixed(2));
		}

		function _showLoader() {
			if ($('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
				$('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
			}
			else if ($('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
				$('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
			}
			else {
				$('.wpcb-configurator-visuals[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
				$('.wpcb-configurator-form[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
				$('.wpcb-configurator-form-totals[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
				$('.wpcb-configurator-form-actions[data-wpcb-id="' + wpcb_id + '"]').addClass('wpcb-loading');
			}
		}

		function _hasLoader() {
			return (
				$('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').hasClass('wpcb-loading') ||
				$('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').hasClass('wpcb-loading') ||
				$('.wpcb-configurator-form[data-wpcb-id="' + wpcb_id + '"]').hasClass('wpcb-loading')
			);
		}

		function _removeLoader() {
			if ($('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
				$('.wpcb-configurator-wrapper[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
			}
			else if ($('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').length) {
				$('.wpcb-configurator-popup-wrapper[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
			}
			else {
				$('.wpcb-configurator-visuals[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
				$('.wpcb-configurator-form[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
				$('.wpcb-configurator-form-totals[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
				$('.wpcb-configurator-form-actions[data-wpcb-id="' + wpcb_id + '"]').removeClass('wpcb-loading');
			}
		}

		function _getSanitizedLabel(label) {
			return label.toString()
				.replace(/\s+/g, '-')
				.replace(/[^a-zA-Z0-9-]/g, '')
				.replace(/-+/g, '-')
				.replace(/^-+|-+$/g, '')
				.toLowerCase()
				.slice(0, 32);
		}

		function _getSelectMenuOptions() {
			return {
				position: {
					my: 'left top',
					at: 'left bottom',
					collision: 'flip',
					using: function (obj, info) {
						if (info.vertical != "top") {
							$(this).addClass("flipped");
							$(info.target.element[0]).addClass("flipped");
						} else {
							$(this).removeClass("flipped");
							$(info.target.element[0]).removeClass("flipped");
						}
						$(this).css({
							left: obj.left + 'px',
							top: obj.top + 'px'
						});
					},
				},
				create: function (event, ui) {
					// Update the selectmenu text with the image when the menu is created
					_updateSelectMenuText(event.target);
				},
				change: function (event, ui) {
					// if ( ATTR_AUTO_COLLAPSE && $(event.target).closest('.option-group.collapsible.open').length ) {
					// 	$(event.target).closest('.option-group.collapsible').removeClass('open').addClass('collapsed');
					// }
					$(event.target).trigger('change');
				}
			};
		}

		function _updateSelectMenuText(select) {
			let selectedOption = $(select).find('option:selected');
			let itemText = selectedOption.text();

			let newText = '';
			if (selectedOption.data('thumb')) {
				newText += '<img src="' + selectedOption.data('thumb') + '" width="50" height="50" class="ui-selectmenu-image" />';
			}
			// Create a new string with the image and text
			newText += '<span class="ui-selectmenu-label">' + itemText;
			if (selectedOption.data('show-note')) {
				newText += '<small>' + selectedOption.data('note') + '</small>';
			}
			newText += '</span>';
			if (selectedOption.data('show-price')) {
				let price = selectedOption.data('sale') ? selectedOption.data('sale') : selectedOption.data('price');
				newText += '<span class="ui-selectmenu-price">' + _formatPriceOutput(price) + '</span>';
			}

			// Manually set the updated button text
			$(select).next('.ui-selectmenu-button').find('.ui-selectmenu-text').html(newText);
		}

		function _selectMenuItemRenderer(ul, item) {
			let li = $("<li>", {
				"class": "wpcb-ui-selectmenu-item"
			});
			if ($(item.element[0]).hasClass('hidden')) {
				li.addClass("ui-state-hidden");
			}
			if (item.disabled) {
				li.addClass("ui-state-disabled");
			}

			item.image = '';
			if ($(item.element[0]).data('thumb')) {
				item.image = '<img src="' + $(item.element[0]).data('thumb') + '" width="50" height="50" class="ui-selectmenu-image" />';
			}
			item.price = '';
			if ($(item.element[0]).data('show-price') && $(item.element[0]).data('price')) {
				let price = $(item.element[0]).data('sale') ? $(item.element[0]).data('sale') : $(item.element[0]).data('price');
				item.price = '<span class="ui-selectmenu-price">' + _formatPriceOutput(price) + '</span>';
			}
			let label = '<span class="ui-selectmenu-label">' + item.label;
			if ($(item.element[0]).data('show-note') && $(item.element[0]).data('note')) {
				label += '<small>' + $(item.element[0]).data('note') + '</small>';
			}
			label += '</span>';

			let option = $("<div>" + item.image + label + item.price + "</div>");
			if ($(item.element[0]).attr('style')) {
				option.attr('style', $(item.element[0]).attr('style'));
				if ($(item.element[0]).data('input-key')) {
					_updatePreviewTextFont($(item.element[0]));
				}
			}

			return li.append(option).appendTo(ul);
		};

		function _selectMenuButtonRenderer(item) {
			item.image = '';
			if ($(item.element[0]).data('thumb')) {
				item.image = '<img src="' + $(item.element[0]).data('thumb') + '" width="50" height="50" class="ui-selectmenu-image" />';
			}
			item.price = '';
			if ($(item.element[0]).data('show-price') && $(item.element[0]).data('price')) {
				let price = $(item.element[0]).data('sale') ? $(item.element[0]).data('sale') : $(item.element[0]).data('price');
				item.price = '<span class="ui-selectmenu-price">' + _formatPriceOutput(price) + '</span>';
			}

			let label = '<span class="ui-selectmenu-label">' + item.label;
			if ($(item.element[0]).data('show-note') && $(item.element[0]).data('note')) {
				label += '<small>' + $(item.element[0]).data('note') + '</small>';
			}
			label += '</span>';

			let buttonItem = $('<div class="ui-selectmenu-text">' + item.image + label + item.price + "</div>");

			// this._setText( buttonItem, item.label );

			if ($(item.element[0]).attr('style')) {
				buttonItem.attr("style", $(item.element[0]).attr('style'));
				if ($(item.element[0]).data('input-key')) {
					_updatePreviewTextFont($(item.element[0]));
				}
			}

			return buttonItem;
		}

		/**
		 * Misc
		 */

		document.addEventListener('fullscreenchange', exitHandler);
		document.addEventListener('webkitfullscreenchange', exitHandler);
		document.addEventListener('mozfullscreenchange', exitHandler);
		document.addEventListener('MSFullscreenChange', exitHandler);

		function exitHandler() {
			if (!document.fullscreenElement && !document.webkitIsFullScreen && !document.mozFullScreen && !document.msFullscreenElement) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.fullscreen').removeClass('active');

				setTimeout(function () {
					_resizeGalleryCanvas();
				}, 500);
			}
		}

		function toggleFullScreen(el) {
			if (!el) {
				el = document.body; // Make the body go full screen.
			}
			let isInFullScreen = (document.fullScreenElement && document.fullScreenElement !== null) || (document.mozFullScreen || document.webkitIsFullScreen);
			if (isInFullScreen) {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.fullscreen').removeClass('active');
				cancelFullScreen();
			} else {
				$('[data-wpcb-id="' + wpcb_id + '"] .wpcb-configurator-gallery-action.fullscreen').addClass('active');
				requestFullScreen(el);
			}
			return false;
		}

		function cancelFullScreen() {
			let el = document;
			let requestMethod = el.cancelFullScreen || el.webkitCancelFullScreen || el.mozCancelFullScreen || el.exitFullscreen || el.webkitExitFullscreen;
			if (requestMethod) { // cancel full screen.
				requestMethod.call(el);
			} else if (typeof window.ActiveXObject !== "undefined") { // Older IE.
				let wscript = new ActiveXObject("WScript.Shell");
				if (wscript !== null) {
					wscript.SendKeys("{F11}");
				}
			}
		}

		function requestFullScreen(el) {
			// Supports most browsers and their versions.
			let requestMethod = el.requestFullScreen || el.webkitRequestFullScreen || el.mozRequestFullScreen || el.msRequestFullscreen;
			if (requestMethod) { // Native full screen.
				requestMethod.call(el);
			} else if (typeof window.ActiveXObject !== "undefined") { // Older IE.
				let wscript = new ActiveXObject("WScript.Shell");
				if (wscript !== null) {
					wscript.SendKeys("{F11}");
				}
			}
			return false;
		}
	}
})(jQuery);

jQuery(document).ready(function($) {
	var config_ids = [];

	$('[data-wpcb-id]').each(function(index,config) {
		var id = $(config).data('wpcb-id').toString();

		if ( ! config_ids.includes( id ) && Object.keys(wpcb_vars).includes(id) ) {
			$(config).wpcbConfigurator({
				...wpcb_globals,
				...wpcb_vars[ id ]
			});

			config_ids.push(id);
		}
	});
});
