/**
 * @var {Object} WpProQuizGlobal Global object for wpProQuiz.
 */
const learndash = window.learndash || {};

learndash.forms = learndash.forms || {};
learndash.forms.sortable = learndash.forms.sortable || {};
learndash.scrollTo = learndash.scrollTo || {};

const localStorage = window.localStorage || {};

/* eslint-disable vars-on-top, camelcase*/
(function ($) {
	/**
	 * @param  element
	 * @param  options
	 * @memberOf $
	 */
	$.wpProQuizFront = function (element, options) {
		const $e = $(element);
		const config = options;
		let result_settings = {};
		const plugin = this;
		let results = new Object();
		let catResults = new Object();
		let startTime = 0;
		let currentQuestion = null;
		let quizSolved = [];
		let lastButtonValue = '';
		let inViewQuestions = false;
		let currentPage = 1;
		let timespent = 0;
		let sending_timer = null;

		let cookie_name = '';
		let storage_name = '';
		let cookieSendTimer = false;
		let cookie_value = '';
		let cookieExpireDate = 0;
		let cookieSending = false;
		let quiz_resume_data = {};

		/**
		 * LocalStorage helper methods for quiz data persistence.
		 * Uses localStorage instead of cookies to handle larger data.
		 * Falls back to cookies for reading existing data (migration).
		 * Keys include both user_id and quiz_id to prevent data corruption
		 * when users impersonate other users.
		 *
		 * @since 4.25.8
		 */
		const quizStorage = {
			/**
			 * Get the storage key that includes both user_id and quiz_id.
			 *
			 * @since 4.25.8
			 *
			 * @param {string} baseName The base cookie name.
			 * @return {string} The storage key with user_id included.
			 */
			getStorageKey( baseName ) {
				// Insert user_id into the key for user-specific storage
				// Format: ld_save_{quizId}_{userId}_quiz_responses
				// or: ld_{quizId}_{userId}_quiz_responses
				if ( baseName.includes( '_quiz_responses' ) ) {
					return baseName.replace(
						'_quiz_responses',
						'_' + config.user_id + '_quiz_responses'
					);
				}
				return baseName + '_' + config.user_id;
			},

			/**
			 * Get data from localStorage.
			 *
			 * @since 4.25.8
			 *
			 * @param {string} key The storage key.
			 * @return {Object|null} The parsed data or null.
			 */
			get( key ) {
				try {
					const data = localStorage.getItem( key );
					if ( data ) {
						return JSON.parse( data );
					}
				} catch ( e ) {
					if ( config.ld_script_debug ) {
						console.log( 'localStorage get error:', e ); // eslint-disable-line no-console
					}
				}
				return null;
			},

			/**
			 * Set data in localStorage.
			 *
			 * @since 4.25.8
			 *
			 * @param {string} key   The storage key.
			 * @param {Object} value The data to store.
			 * @return {boolean} True on success, false on failure.
			 */
			set( key, value ) {
				try {
					localStorage.setItem( key, JSON.stringify( value ) );
					return true;
				} catch (e) {
					if ( config.ld_script_debug ) {
						console.log( 'localStorage set error:', e ); // eslint-disable-line no-console
					}

					// Fallback to cookie if localStorage fails (e.g., quota exceeded)
					return false;
				}
			},

			/**
			 * Remove data from localStorage.
			 *
			 * @since 4.25.8
			 *
			 * @param {string} key The storage key.
			 */
			remove( key ) {
				try {
					localStorage.removeItem( key );
				} catch (e) {
					if ( config.ld_script_debug ) {
						console.log( 'localStorage remove error:', e ); // eslint-disable-line no-console
					}
				}
			},

			/**
			 * Get data with fallback to cookies (for migration).
			 * Checks localStorage first, then falls back to cookies.
			 *
			 * @since 4.25.8
			 *
			 * @param {string} storageKey The localStorage key.
			 * @param {string} cookieKey  The original cookie key (without user_id).
			 * @return {Object} The parsed data or empty object.
			 */
			getWithCookieFallback( storageKey, cookieKey ) {
				// First try localStorage with the new key format
				let data = this.get( storageKey );
				if ( data && Object.keys( data ).length > 0 ) {
					return data;
				}

				// Fallback: try localStorage with the old key format (quiz_id only, no user_id)
				const oldStorageKey = cookieKey;
				data = this.get( oldStorageKey );
				if ( data && Object.keys( data ).length > 0 ) {
					if ( config.ld_script_debug ) {
						// eslint-disable-next-line no-console
						console.log(
							'Migrating data from old localStorage key:',
							oldStorageKey
						);
					}
					// Migrate to new key format
					this.set( storageKey, data );
					this.remove( oldStorageKey );
					return data;
				}

				// Fallback: try cookie with the old key format
				const cookieData = jQuery.cookie( cookieKey );
				if ( cookieData && cookieData !== '%7B%7D' ) {
					try {
						data = JSON.parse( cookieData );
						if ( data && Object.keys( data ).length > 0 ) {
							if ( config.ld_script_debug ) {
								// eslint-disable-next-line no-console
								console.log(
									'Migrating data from cookie:',
									cookieKey
								);
							}
							// Migrate to localStorage and clear the cookie
							this.set( storageKey, data );
							jQuery.cookie( cookieKey, '' );
							return data;
						}
					} catch ( e ) {
						if ( config.ld_script_debug ) {
							console.log( 'Cookie parse error:', e ); // eslint-disable-line no-console
						}
					}
				}

				return {};
			},
		};

		if (config.ld_script_debug == true) {
			console.log('config[%o]', config);
		}

		const bitOptions = {
			randomAnswer: 0,
			randomQuestion: 0,
			disabledAnswerMark: 0,
			checkBeforeStart: 0,
			preview: 0,
			cors: 0,
			isAddAutomatic: 0,
			quizSummeryHide: 0,
			skipButton: 0,
			reviewQustion: 0,
			autoStart: 0,
			forcingQuestionSolve: 0,
			hideQuestionPositionOverview: 0,
			formActivated: 0,
			maxShowQuestion: 0,
			sortCategories: 0,
		};

		const quizStatus = {
			isQuizStart: 0,
			isLocked: 0,
			loadLock: 0,
			isPrerequisite: 0,
			isUserStartLocked: 0,
		};

		const globalNames = {
			check: 'input[name="check"]',
			next: 'input[name="next"]',
			tip: 'input[name="tip"]',
			questionList: '.wpProQuiz_questionList',
			skip: 'input[name="skip"]',
			singlePageLeft: 'input[name="wpProQuiz_pageLeft"]',
			singlePageRight: 'input[name="wpProQuiz_pageRight"]',
		};

		let globalElements = {
			self: $e,
			back: $e.find('input[name="back"]'),
			next: $e.find(globalNames.next),
			quiz: $e.find('.wpProQuiz_quiz'),
			questionList: $e.find('.wpProQuiz_list'),
			results: $e.find('.wpProQuiz_results'),
			sending: $e.find('.wpProQuiz_sending'),
			quizStartPage: $e.find('.wpProQuiz_text'),
			timelimit: $e.find('.wpProQuiz_time_limit'),
			toplistShowInButton: $e.find('.wpProQuiz_toplistShowInButton'),
			listItems: $(),
		};

		const toplistData = {
			token: '',
			isUser: 0,
		};

		const formPosConst = {
			START: 0,
			END: 1,
		};

		/**
		 * @memberOf timelimit
		 */
		const timelimit = {
			counter: config.timelimit,
			intervalId: 0,
			instance: {},
			// set cookie for different users and different quizzes
			timer_cookie:
				'ldadv-time-limit-' + config.user_id + '-' + config.quizId,

			stop() {
				if (this.counter) {
					$.removeCookie(this.timer_cookie);
					window.clearInterval(this.intervalId);
					globalElements.timelimit.hide();
				}
			},
			start() {
				if (!this.counter) {
					return;
				}
				this.timer_cookie;
				$.cookie.raw = true;

				const full = this.counter * 1000;
				let tick = jQuery.cookie(this.timer_cookie);
				let limit = 0;

				if (tick) {
					limit = tick;
				} else {
					limit = this.counter;
				}

				const resume_data = learndash_prepare_quiz_resume_data(config);
				if (resume_data !== false) {
					if (resume_data[this.timer_cookie]) {
						limit = resume_data[this.timer_cookie];
					}
				}

				const x = limit * 1000;

				const $timeText = globalElements.timelimit
					.find('span')
					.text(plugin.methode.parseTime(limit));
				const $timeDiv = globalElements.timelimit.find(
					'.wpProQuiz_progress'
				);

				globalElements.timelimit.show();

				const beforeTime = +new Date();

				this.intervalId = window.setInterval(
					function () {
						const diff = +new Date() - beforeTime;
						const remainingTime = x - diff;

						if (diff >= 500) {
							tick = remainingTime / 1000;
							$timeText.text(
								plugin.methode.parseTime(Math.ceil(tick))
							);
							$.cookie(this.timer_cookie, tick);
						}

						$timeDiv.css(
							'width',
							(remainingTime / full) * 100 + '%'
						);

						if (remainingTime <= 0) {
							this.stop();
							plugin.methode.finishQuiz(true);
						}
					}.bind(this),
					16
				);

				if (config.quiz_resume_enabled === '1') {
					plugin.methode.startCookieSendTimer();
				}
			},
		};

		/**
		 * @memberOf reviewBox
		 */
		const reviewBox = new (function () {
			let $contain = [],
				$cursor = [],
				$list = [],
				$items = [];
			let x = 0,
				offset = 0,
				diff = 0,
				top = 0,
				max = 0;
			const itemsStatus = [];

			this.init = function () {
				$contain = $e.find('.wpProQuiz_reviewQuestion');
				$cursor = $contain.find('div');
				$list = $contain.find('ol');
				$items = $list.children();

				if (config.mode != 2) {
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-correct'
					).hide();
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-incorrect'
					).hide();
				}

				this.reset();

				const resume_data = learndash_prepare_quiz_resume_data(config);
				if (config.ld_script_debug == true) {
					console.log('resume_data[%o]', resume_data);
				}

				if (typeof resume_data.reviewBox !== 'undefined') {
					jQuery(resume_data.reviewBox).each(function (idx, item) {
						if (typeof item === 'object') {
							const item_tmp = item;
							item = {};
							if (typeof item_tmp.solved !== 'undefined') {
								item.solved = true;
							}

							if (typeof item_tmp.correct !== 'undefined') {
								item.correct = true;
							}

							if (typeof item_tmp.incorrect !== 'undefined') {
								item.incorrect = true;
							}

							if (typeof item_tmp.skip !== 'undefined') {
								item.skip = true;
							}

							if (typeof item_tmp.review !== 'undefined') {
								item.review = true;
							}
						}
						itemsStatus[idx] = item;
						setColor(idx);
					});
				}

				$cursor.on('mousedown', function (e) {
					e.preventDefault();
					e.stopPropagation();

					offset = e.pageY - $cursor.offset().top + top;

					$(document).on('mouseup.scrollEvent', endScroll);
					$(document).on('mousemove.scrollEvent', moveScroll);
				});

				$items.on('click', function (e) {
					plugin.methode.showQuestion($(this).index());
				});

				$e.on('questionSolved', function (e) {
					itemsStatus[e.values.index].solved = e.values.solved;
					setColor(e.values.index);
					updateItemsStatus();
				});

				$e.on('questionSolvedCorrect', function (e) {
					itemsStatus[e.values.index].correct = true;
					setColor(e.values.index);
					updateItemsStatus();
				});

				$e.on('questionSolvedIncorrect', function (e) {
					itemsStatus[e.values.index].incorrect = true;
					setColor(e.values.index);
					updateItemsStatus();
				});

				$e.on('changeQuestion', function (e) {
					// On Matrix sort questions we need to set the sort capture UL to full height.
					if (e.values.item[0] != 'undefined') {
						const questionItem = e.values.item[0];
						plugin.methode.setupMatrixSortHeights();
					}

					$items.removeClass('wpProQuiz_reviewQuestionTarget');
					//$items.removeClass('wpProQuiz_reviewQuestionSolved');
					//$items.removeClass('wpProQuiz_reviewQuestionReview');

					$items
						.eq(e.values.index)
						.addClass('wpProQuiz_reviewQuestionTarget');
					//updateItemsStatus();

					if (e.values.item.length > 0) {
						learndash.scrollTo( e.values.item[ 0 ] );
					}

					// Attempt to focus the question text and let screen readers read it.
					$(e.values.item).find('.wpProQuiz_question_text').focus();
				});

				$e.on('skipQuestion', function (e) {
					itemsStatus[e.values.index].skip =
						!itemsStatus[e.values.index].skip;
					setColor(e.values.index);
					updateItemsStatus();
				});

				$e.on('reviewQuestion', function (e) {
					itemsStatus[e.values.index].review =
						!itemsStatus[e.values.index].review;
					setColor(e.values.index);
					updateItemsStatus();
				});

				/*
				$contain.on('mousewheel DOMMouseScroll', function(e) {
					e.preventDefault();

					var ev = e.originalEvent;
					var w = ev.wheelDelta ? -ev.wheelDelta / 120 : ev.detail / 3;
					var plus = 20 * w;

					var x = top - $list.offset().top  + plus;

					if(x > max)
						x = max;

					if(x < 0)
						x = 0;

					var o = x / diff;

					$list.attr('style', 'margin-top: ' + (-x) + 'px !important');
					$cursor.css({top: o});

					return false;
				});
				*/
			};

			this.show = function (save) {
				if (bitOptions.reviewQustion) {
					$contain.parent().show();
				}

				$e.find('.wpProQuiz_reviewDiv .wpProQuiz_button2').show();

				if (save) {
					return;
				}

				$list.attr('style', 'margin-top: 0px !important');
				$cursor.css({ top: 0 });

				const h = $list.outerHeight();
				const c = $contain.height();
				x = c - $cursor.height();
				offset = 0;
				max = h - c;
				diff = max / x;

				//this.reset();

				if (h > 100) {
					$cursor.show();
				}

				top = $cursor.offset().top;
			};

			this.hide = function () {
				$contain.parent().hide();
			};

			this.toggle = function () {
				if (bitOptions.reviewQustion) {
					// The toggle is called after the quiz submit. So we want to hide the 'review' and 'answered' legend items.
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-current'
					).hide();
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-review'
					).hide();
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-answered'
					).hide();

					// And show the 'correct' and 'incorrect' indicators.
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-correct'
					).show();
					$(
						'.wpProQuiz_reviewLegend li.learndash-quiz-review-legend-item-incorrect'
					).show();

					$contain.parent().toggle();
					$items.removeClass('wpProQuiz_reviewQuestionTarget');
					$e.find('.wpProQuiz_reviewDiv .wpProQuiz_button2').hide();

					$list.attr('style', 'margin-top: 0px !important');
					$cursor.css({ top: 0 });

					const h = $list.outerHeight();
					const c = $contain.height();
					x = c - $cursor.height();
					offset = 0;
					max = h - c;
					diff = max / x;

					if (h > 100) {
						$cursor.show();
					}

					top = $cursor.offset().top;
				}
			};

			this.reset = function () {
				for (let i = 0, c = $items.length; i < c; i++) {
					itemsStatus[i] = {};
				}

				//$items.removeClass('wpProQuiz_reviewQuestionTarget').css('background-color', '');
				$items.removeClass('wpProQuiz_reviewQuestionTarget');
				$items.removeClass('wpProQuiz_reviewQuestionSolved');
				$items.removeClass('wpProQuiz_reviewQuestionReview');
			};

			function scroll(index) {
				/*
				var $item = $items.eq(index);
				var iTop = $item.offset().top;
				var cTop = $contain.offset().top;
				var calc = iTop - cTop;

				if((calc - 4) < 0 || (calc + 32) > 100) {
					var x = cTop - $items.eq(0).offset().top - (cTop - $list.offset().top)  + $item.position().top;

					if(x > max)
						x = max;

					var o = x / diff;

					$list.attr('style', 'margin-top: ' + (-x) + 'px !important');
					$cursor.css({top: o});
				}
				*/
			}

			function setColor(index) {
				const itemStatus = itemsStatus[index];

				$items
					.eq(index)
					.removeClass(
						'wpProQuiz_reviewQuestionSolved wpProQuiz_reviewQuestionReview wpProQuiz_reviewQuestionSkip'
					);

				if (0 === Object.keys(itemStatus).length) {
					return;
				}

				let css_class = '';
				let statusText = $('learndash-quiz-review-legend-item-current').text();

				if (itemStatus.correct) {
					css_class = 'wpProQuiz_reviewQuestionSolvedCorrect';
					statusText = $(
						'.learndash-quiz-review-legend-item-correct'
					).text();
				} else if (itemStatus.incorrect) {
					css_class = 'wpProQuiz_reviewQuestionSolvedIncorrect';
					statusText = $(
						'.learndash-quiz-review-legend-item-incorrect'
					).text();
				} else if (
					itemStatus.solved === true ||
					itemStatus.solved === false
				) {
					css_class = 'wpProQuiz_reviewQuestionSolved';
					statusText = $(
						'.learndash-quiz-review-legend-item-answered'
					).text();
				} else if (itemStatus.review) {
					css_class = 'wpProQuiz_reviewQuestionReview';
					statusText = $(
						'.learndash-quiz-review-legend-item-review'
					).text();
				} else if (itemStatus.skip) {
					css_class = 'wpProQuiz_reviewQuestionSkip';
					statusText = $(
						'.learndash-quiz-review-legend-item-review'
					).text();
				}

				if (css_class != '') {
					$items.eq(index).addClass(css_class);

					let $status = $items
						.eq(index)
						.find('.learndash-quiz-review__item-status');

					if (!$status.length) {
						$items
							.eq(index)
							.append(
								$(
									'<span class="learndash-quiz-review__item-status screen-reader-text">'
								)
							);

						$status = $items
							.eq(index)
							.find('.learndash-quiz-review__item-status');
					}

					$status.text(
						'. ' +
							wp.i18n.sprintf(
								// translators: placeholder: Status text.
								wp.i18n.__('Status: %s', 'learndash'),
								statusText
							)
					);
				}
			}

			function moveScroll(e) {
				e.preventDefault();

				let o = e.pageY - offset;

				if (o < 0) {
					o = 0;
				}

				if (o > x) {
					o = x;
				}

				const v = diff * o;

				$list.attr('style', 'margin-top: ' + -v + 'px !important');

				$cursor.css({ top: o });
			}

			function endScroll(e) {
				e.preventDefault();

				$(document).unbind('.scrollEvent');
			}

			function updateItemsStatus() {
				plugin.methode.saveMetaDataToCookie({ reviewBox: itemsStatus });
			}
		})();

		function QuestionTimer() {
			let questionStartTime = 0;
			let currentQuestionId = -1;

			let quizStartTimer = 0;
			let isQuizStart = false;

			this.questionStart = function (questionId) {
				if (currentQuestionId != -1) {
					this.questionStop();
				}

				currentQuestionId = questionId;
				questionStartTime = +new Date();
			};

			this.questionStop = function () {
				if (currentQuestionId == -1) {
					return;
				}

				results[currentQuestionId].time += Math.round(
					(new Date() - questionStartTime) / 1000
				);

				currentQuestionId = -1;
			};

			this.startQuiz = function () {
				if (isQuizStart) {
					this.stopQuiz();
				}

				quizStartTimer = +new Date();

				// Use server start time when quiz is resumed.
				// Server sends timestamp in seconds (Unix timestamp), but JavaScript Date uses milliseconds.
				if (
					config.quiz_resume_enabled === '1' &&
					typeof config.quiz_resume_quiz_started === 'string'
				) {
					if ('0' !== config.quiz_resume_quiz_started) {
						// Convert from saved seconds to milliseconds by multiplying by 1000.
						quizStartTimer =
							parseInt( config.quiz_resume_quiz_started, 10 ) *
							1000;
					}
				}

				isQuizStart = true;
			};
			this.getQuizStart = function () {
				if (!isQuizStart) {
					return;
				}
				return quizStartTimer;
			};
			this.stopQuiz = function () {
				if (!isQuizStart) {
					return;
				}

				quizEndTimer = +new Date();
				results.comp.quizTime += Math.round(
					(quizEndTimer - quizStartTimer) / 1000
				);

				results.comp.quizEndTimestamp = quizEndTimer;
				results.comp.quizStartTimestamp = quizStartTimer;

				isQuizStart = false;
			};

			this.init = function () {};
		}

		const questionTimer = new QuestionTimer();

		const readResponses = function (
			name,
			data,
			$question,
			$questionList,
			lockResponse
		) {
			if (lockResponse == undefined) {
				lockResponse = true;
			}
			let response = {};
			const func = {
				singleMulti() {
					const input = $questionList.find(
						'.wpProQuiz_questionInput'
					);
					if (lockResponse == true) {
						$questionList
							.find('.wpProQuiz_questionInput')
							.attr('disabled', 'disabled');
					}
					//$questionList.children().each(function(i) {
					// Changed in v2.3 from the above. children() was pickup up some other random HTML elements within the UL like <p></p>.
					// Now we are targetting specifically the .wpProQuiz_questionListItem HTML elements.
					jQuery('.wpProQuiz_questionListItem', $questionList).each(
						function (i) {
							const $item = $(this);
							const index = $item.attr('data-pos');
							if (typeof index !== 'undefined') {
								response[index] = input.eq(i).is(':checked');
							}
						}
					);
				},

				sort_answer() {
					const $items = $questionList.children(
						'.wpProQuiz_questionListItem'
					);
					let idx = 0;
					$items.each(function (item_idx, item) {
						const data_pos = jQuery(item).data('pos');
						if (typeof data_pos !== 'undefined') {
							response[idx] = data_pos;
							idx++;
						}
					});

					if (lockResponse === true) {
						// Backwards compatibility with templates older than 4.21.3.
						if (
							!$questionList.hasClass('.ld-sortable--sort_answer')
						) {
							$questionList.sortable();
							$questionList.sortable('disable');
						} else {
							learndash.forms.sortable.destroySortable(
								$questionList[0] ?? null
							);
						}
					}
				},

				matrix_sort_answer() {
					const $items = $questionList.children();
					const matrix = new Array();
					statistcAnswerData = { 0: -1 };

					/*
					// Old broken logic from LD 3.5.0/3.5.1 Replaced with the original code below.
					$items.each( function( index ) {
						var $this = $( this );
						var $stringUl = $this.find( '.wpProQuiz_maxtrixSortCriterion' );
						var $stringItem = $stringUl.children();

						if( $stringItem.length ) {
							var idx = $stringItem.attr('data-pos');
							statistcAnswerData[index] = $items[idx].getAttribute('data-pos');
						} else {
							statistcAnswerData[index] = '';
						}
					} );
					*/

					$items.each(function () {
						const $this = $(this);
						const id = $this.attr('data-pos');
						const $stringUl = $this.find(
							'.wpProQuiz_maxtrixSortCriterion'
						);
						const $stringItem = $stringUl.children();

						if ($stringItem.length) {
							statistcAnswerData[$stringItem.attr('data-pos')] =
								id;
						}

						response = statistcAnswerData;
					});

					response = statistcAnswerData;

					if (lockResponse == true) {
						$question
							.find(
								'.wpProQuiz_sortStringList, .wpProQuiz_maxtrixSortCriterion'
							)
							.sortable();
						$question
							.find(
								'.wpProQuiz_sortStringList, .wpProQuiz_maxtrixSortCriterion'
							)
							.sortable('disable');
					}
				},

				free_answer() {
					const $li = $questionList.children();
					const value = $li.find('.wpProQuiz_questionInput').val();
					if (lockResponse == true) {
						$li.find('.wpProQuiz_questionInput').attr(
							'disabled',
							'disabled'
						);
					}
					response = value;
				},

				cloze_answer() {
					response = {};
					$questionList
						.find('.wpProQuiz_cloze')
						.each(function (i, v) {
							const $this = $(this);
							const cloze = $this.children();
							const input = cloze.eq(0);
							const span = cloze.eq(1);
							const inputText = plugin.methode.cleanupCurlyQuotes(
								input.val()
							);
							response[i] = inputText;
							if (lockResponse == true) {
								input.attr('disabled', 'disabled');
							}
						});
				},

				assessment_answer() {
					correct = true;
					const $input = $questionList.find(
						'.wpProQuiz_questionInput'
					);
					if (lockResponse == true) {
						$questionList
							.find('.wpProQuiz_questionInput')
							.attr('disabled', 'disabled');
					}
					let val = 0;

					$input.filter(':checked').each(function () {
						val += parseInt($(this).val());
					});

					response = val;
				},

				essay() {
					const question_id = $question
						.find('.wpProQuiz_questionList')
						.data('question_id');
					if (lockResponse == true) {
						$questionList
							.find('.wpProQuiz_questionEssay')
							.attr('disabled', 'disabled');
					}

					const essayText = $questionList
						.find('.wpProQuiz_questionEssay')
						.val();
					const essayFiles = $questionList
						.find('#uploadEssayFile_' + question_id)
						.val();

					if (typeof essayText !== 'undefined') {
						response = essayText;
					}

					if (typeof essayFiles !== 'undefined') {
						response = essayFiles;
					}
				},
			};

			func[name]();

			return { response };
		};

		// Called from the Cookie handler logic. If the Quiz is loaded and a cookie is present the values of the cookie are used
		// to set the Quiz elements here. Once the value is set we call the trigger 'questionSolved' to update the question overview panel.
		function setResponse(
			question_data,
			question_value,
			question,
			$questionList,
			lockQuestion
		) {
			var lockQuestion = lockQuestion || false;
			if (
				question_data.type == 'single' ||
				question_data.type == 'multiple'
			) {
				$questionList.children().each(function (i) {
					const $item = $(this);
					const index = $item.attr('data-pos');

					if (question_value[index] != undefined) {
						const index_value = question_value[index];
						if (index_value == true) {
							$('.wpProQuiz_questionInput', $item).prop(
								'checked',
								'checked'
							);
							if (lockQuestion) {
								question
									.find('.wpProQuiz_questionInput')
									.attr('disabled', 'disabled');
								question
									.find('.wpProQuiz_questionInput')
									.css('pointer-events', 'none');

								navigationElementslockQuestion(question);
							}
							$e.trigger({
								type: 'questionSolved',
								values: {
									item: question,
									index: question.index(),
									solved: true,
								},
							});
						}
					}
				});
			} else if (question_data.type == 'free_answer') {
				question_value = learndash_decodeHTML(question_value);

				$questionList.children().each(function (i) {
					const $item = $(this);
					$('.wpProQuiz_questionInput', this).val(question_value);
					if (lockQuestion) {
						$('.wpProQuiz_questionInput', this).attr(
							'disabled',
							'disabled'
						);
						$('.wpProQuiz_questionInput', this).css(
							'pointer-events',
							'none'
						);

						navigationElementslockQuestion(question);
					}
				});
				$e.trigger({
					type: 'questionSolved',
					values: {
						item: question,
						index: question.index(),
						solved: true,
					},
				});
			} else if (question_data.type === 'sort_answer') {
				jQuery.each(question_value, function (pos, key) {
					const this_li = $(
						'.wpProQuiz_questionListItem[data-pos="' + key + '"]',
						$questionList
					);
					const this_li_inner = $('.wpProQuiz_sortable', this_li);
					const this_li_inner_value = $(this_li_inner).text();

					jQuery($questionList).append(this_li);
					if (lockQuestion) {
						// Backwards compatibility with templates older than 4.21.3.
						if (
							!$questionList.hasClass('ld-sortable--sort_answer')
						) {
							jQuery($questionList).sortable();
							jQuery($questionList).sortable('disable');
						} else {
							learndash.forms.sortable.destroySortable(
								$questionList[0] ?? null
							);
						}

						navigationElementslockQuestion(question);
					}
				});
				$e.trigger({
					type: 'questionSolved',
					values: {
						item: question,
						index: question.index(),
						solved: true,
					},
				});
			} else if (question_data.type == 'matrix_sort_answer') {
				jQuery.each(question_value, function (pos, key) {
					const question_response_item = $(
						'.wpProQuiz_matrixSortString .wpProQuiz_sortStringList li[data-pos="' +
							pos +
							'"]',
						question
					);
					const question_destination_outer_li = $(
						'.wpProQuiz_questionListItem[data-pos="' +
							key +
							'"] ul.wpProQuiz_maxtrixSortCriterion',
						$questionList
					);

					jQuery(question_response_item).appendTo(
						question_destination_outer_li
					);
					if (lockQuestion) {
						jQuery(question_destination_outer_li).sortable();
						jQuery(question_destination_outer_li).sortable(
							'disable'
						);
						navigationElementslockQuestion(question);
					}
				});
				$e.trigger({
					type: 'questionSolved',
					values: {
						item: question,
						index: question.index(),
						solved: true,
					},
				});
			} else if (question_data.type == 'cloze_answer') {
				// Get the input fields within the questionList parent
				jQuery(
					'span.wpProQuiz_cloze input[type="text"]',
					$questionList
				).each(function (index) {
					if (typeof question_value[index] !== 'undefined') {
						$(this).val(
							learndash_decodeHTML(question_value[index])
						);
						if (lockQuestion) {
							$(this).attr('disabled', 'disabled');
							navigationElementslockQuestion(question);
						}
					}
				});
				$e.trigger({
					type: 'questionSolved',
					values: {
						item: question,
						index: question.index(),
						solved: true,
					},
				});
			} else if (question_data.type == 'assessment_answer') {
				$(
					'input.wpProQuiz_questionInput[value="' +
						question_value +
						'"]',
					$questionList
				).attr('checked', 'checked');
				if (lockQuestion) {
					$(
						'input.wpProQuiz_questionInput[value="' +
							question_value +
							'"]',
						$questionList
					).attr('disabled', 'disabled');
					navigationElementslockQuestion(question);
				}
				$e.trigger({
					type: 'questionSolved',
					values: {
						item: question,
						index: question.index(),
						solved: true,
					},
				});
			} else if (question_data.type == 'essay') {
				// The 'essay' value is generic. We need to figure out if this is an upload or inline essay.
				if (
					$questionList.find('#uploadEssayFile_' + question_data.id)
						.length
				) {
					const question_input = $questionList.find(
						'#uploadEssayFile_' + question_data.id
					);
					$(question_input).val(question_value);
					$('<p>' + basename(question_value) + '</p>').insertAfter(
						question_input
					);
					if (lockQuestion) {
						$questionList
							.find('form[name="uploadEssay"]')
							.css('pointer-events', 'none');
						navigationElementslockQuestion(question);
					}
					$e.trigger({
						type: 'questionSolved',
						values: {
							item: question,
							index: question.index(),
							solved: true,
						},
					});
				} else if (
					$questionList.find('.wpProQuiz_questionEssay').length
				) {
					$questionList
						.find('.wpProQuiz_questionEssay')
						.text(learndash_decodeHTML(question_value));
					if (lockQuestion) {
						$questionList
							.find('.wpProQuiz_questionEssay')
							.attr('disabled', 'disabled');
						navigationElementslockQuestion(question);
					}
					$e.trigger({
						type: 'questionSolved',
						values: {
							item: question,
							index: question.index(),
							solved: true,
						},
					});
				}
			}
			if (lockQuestion) {
				plugin.methode.setCheckedStatusFromData(
					question_data,
					question,
					$questionList
				);
			}
		}

		/**
		 * Manage navigation elements after 'checked' question answers have been set from the cookie.
		 *
		 * @param {Object} question Dom Object
		 */
		function navigationElementslockQuestion(question) {
			question.find(globalNames.check).hide();
			question.find(globalNames.tip).hide();
			question.find(globalNames.skip).hide();
			question.find(globalNames.next).show();
			question.find(globalNames.next).attr('data-question-lock', true);
		}

		function basename(path) {
			if (path != undefined) {
				return path.split('/').reverse()[0];
			}
			return '';
		}

		const formClass = {
			funcs: {
				isEmpty(str) {
					str = str.trim();
					return !str || 0 === str.length;
				},
			},
			typeConst: {
				TEXT: 0,
				TEXTAREA: 1,
				NUMBER: 2,
				CHECKBOX: 3,
				EMAIL: 4,
				YES_NO: 5,
				DATE: 6,
				SELECT: 7,
				RADIO: 8,
			},
			checkForm() {
				let check = true;
				const that = this;
				$e.find(
					'.wpProQuiz_forms input, .wpProQuiz_forms textarea, .wpProQuiz_forms .wpProQuiz_formFields, .wpProQuiz_forms select'
				).each(function () {
					const $this = $(this);
					const isRequired = $this.data('required') == 1;
					const type = $this.data('type');
					let test = true;
					const value = $this.val().trim();

					switch (type) {
						case that.typeConst.TEXT:
						case that.typeConst.TEXTAREA:
						case that.typeConst.SELECT:
							if (isRequired) {
								test = !that.funcs.isEmpty(value);
							}

							break;
						case that.typeConst.NUMBER:
							if (isRequired || !that.funcs.isEmpty(value)) {
								test =
									!that.funcs.isEmpty(value) && !isNaN(value);
							}

							break;
						case that.typeConst.EMAIL:
							if (isRequired || !that.funcs.isEmpty(value)) {
								//test = !funcs.isEmpty(value) && new RegExp(/^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/)
								//		.test(value);

								// Use the same RegEx as the HTML5 email field. Per https://emailregex.com
								test =
									!that.funcs.isEmpty(value) &&
									new RegExp(
										/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
									).test(value);
							}

							break;
						case that.typeConst.CHECKBOX:
							if (isRequired) {
								test = $this.is(':checked');
							}

							break;
						case that.typeConst.YES_NO:
						case that.typeConst.RADIO:
							if (isRequired) {
								test =
									$this
										.find('input[type="radio"]:checked')
										.val() !== undefined;
							}

							break;
						case that.typeConst.DATE:
							var num = 0,
								co = 0;

							$this.find('select').each(function () {
								num++;
								co += that.funcs.isEmpty($(this).val()) ? 0 : 1;
							});

							if (isRequired || co > 0) {
								test = num == co;
							}

							break;
					}

					if (test) {
						$this.siblings('.wpProQuiz_invalidate').hide();
					} else {
						check = false;
						$this.siblings('.wpProQuiz_invalidate').show();
					}
				});

				return check;
			},
			getFormData() {
				const data = {};
				const that = this;
				$e.find(
					'.wpProQuiz_forms input, .wpProQuiz_forms textarea, .wpProQuiz_forms .wpProQuiz_formFields, .wpProQuiz_forms select'
				).each(function () {
					const $this = $(this);
					const id = $this.data('form_id');
					const type = $this.data('type');

					switch (type) {
						case that.typeConst.TEXT:
						case that.typeConst.TEXTAREA:
						case that.typeConst.SELECT:
						case that.typeConst.NUMBER:
						case that.typeConst.EMAIL:
							data[id] = $this.val();
							break;
						case that.typeConst.CHECKBOX:
							data[id] = $this.is(':checked') ? 1 : 0;
							break;
						case that.typeConst.YES_NO:
						case that.typeConst.RADIO:
							data[id] = $this
								.find('input[type="radio"]:checked')
								.val();
							break;
						case that.typeConst.DATE:
							data[id] = {
								day: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_day"]'
									)
									.val(),
								month: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_month"]'
									)
									.val(),
								year: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_year"]'
									)
									.val(),
							};
							break;
					}
				});

				return data;
			},
			saveFormDataToCookie() {
				const formData = {};
				const that = this;
				$e.find(
					'.wpProQuiz_forms input, .wpProQuiz_forms textarea, .wpProQuiz_forms .wpProQuiz_formFields, .wpProQuiz_forms select'
				).each(function () {
					const $this = $(this);
					const id = $this.data('form_id');
					// Don't save duplicated form fields.
					if (typeof id === 'undefined') {
						return;
					}
					const type = $this.data('type');

					formData.id = id;
					switch (type) {
						case that.typeConst.TEXT:
						case that.typeConst.TEXTAREA:
						case that.typeConst.SELECT:
						case that.typeConst.NUMBER:
						case that.typeConst.EMAIL:
							formData.value = $this.val();
							formData.type = type;
							break;
						case that.typeConst.CHECKBOX:
							formData.value = $this.is(':checked') ? 1 : 0;
							formData.type = type;
							break;
						case that.typeConst.YES_NO:
						case that.typeConst.RADIO:
							formData.value = $this
								.find('input[type="radio"]:checked')
								.val();
							formData.type = type;
							break;
						case that.typeConst.DATE:
							formData.value = {
								day: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_day"]'
									)
									.val(),
								month: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_month"]'
									)
									.val(),
								year: $this
									.find(
										'select[name="wpProQuiz_field_' +
											id +
											'_year"]'
									)
									.val(),
							};
							formData.type = type;
							break;
					}

					plugin.methode.CookieSaveResponse(
						'formData' + formData.id,
						formData.id,
						formData.type,
						formData.value
					);
				});
			},
			setFormDataFromServer() {
				if (config.quiz_resume_enabled !== '1') {
					return;
				}

				if (config.quiz_resume_data == undefined) {
					return;
				}

				const resume_data = learndash_prepare_quiz_resume_data(config);
				if (resume_data === false) {
					return;
				}

				const that = this;
				$e.find(
					'.wpProQuiz_forms input, .wpProQuiz_forms textarea, .wpProQuiz_forms .wpProQuiz_formFields, .wpProQuiz_forms select'
				).each(function () {
					const $this = $(this);
					const id = $this.data('form_id');
					const type = $this.data('type');
					const formId = 'formData' + id;
					if (resume_data[formId]) {
						if (resume_data[formId].type === type) {
							switch (type) {
								case that.typeConst.TEXT:
								case that.typeConst.TEXTAREA:
								case that.typeConst.SELECT:
								case that.typeConst.NUMBER:
								case that.typeConst.EMAIL:
									var form_value = resume_data[formId].value;
									form_value =
										learndash_decodeHTML(form_value);
									$this.val(form_value);
									break;
								case that.typeConst.CHECKBOX:
									if (resume_data[formId].value) {
										$this.attr('checked', true);
									}

									break;
								case that.typeConst.YES_NO:
								case that.typeConst.RADIO:
									if (resume_data[formId].value) {
										$this
											.find(
												'input[type="radio"][value="' +
													resume_data[formId].value +
													'"]'
											)
											.attr('checked', true);
									}

									break;
								case that.typeConst.DATE:
									$this
										.find(
											'select[name="wpProQuiz_field_' +
												id +
												'_day"]'
										)
										.val(resume_data[formId].value.day);
									$this
										.find(
											'select[name="wpProQuiz_field_' +
												id +
												'_month"]'
										)
										.val(resume_data[formId].value.month);
									$this
										.find(
											'select[name="wpProQuiz_field_' +
												id +
												'_year"]'
										)
										.val(resume_data[formId].value.year);

									break;
							}
						}
					}
				});
			},
		};

		const fetchAllAnswerData = function (resultData) {
			$e.find('.wpProQuiz_questionList').each(function () {
				const $this = $(this);
				const questionId = $this.data('question_id');
				const type = $this.data('type');
				const data = {};

				if (type == 'single' || type == 'multiple') {
					$this.find('.wpProQuiz_questionListItem').each(function () {
						data[$(this).attr('data-pos')] = +$(this)
							.find('.wpProQuiz_questionInput')
							.is(':checked');
					});
				} else if (type == 'free_answer') {
					data[0] = $this.find('.wpProQuiz_questionInput').val();
				} else if (type == 'sort_answer') {
					return true;
					//					$this.find('.wpProQuiz_questionListItem').each(function() {
					//						data[$(this).index()] = $(this).attr('data-pos');
					//					});
				} else if (type == 'matrix_sort_answer') {
					return true;
					//					$this.find('.wpProQuiz_questionListItem').each(function() {
					//						data[$(this).attr('data-pos')] = $(this).find('.wpProQuiz_answerCorrect').length;
					//					});
				} else if (type == 'cloze_answer') {
					let i = 0;
					$this.find('.wpProQuiz_cloze input').each(function () {
						data[i++] = $(this).val();
					});
				} else if (type == 'assessment_answer') {
					data[0] = '';

					$this
						.find('.wpProQuiz_questionInput:checked')
						.each(function () {
							data[$(this).data('index')] = $(this).val();
						});
				} else if (type == 'essay') {
					return;
				}

				resultData[questionId].data = data;
			});
		};

		plugin.methode = {
			/**
			 * @memberOf plugin.methode
			 */

			parseBitOptions() {
				if (config.bo) {
					bitOptions.randomAnswer = config.bo & (1 << 0);
					bitOptions.randomQuestion = config.bo & (1 << 1);
					bitOptions.disabledAnswerMark = config.bo & (1 << 2);
					bitOptions.checkBeforeStart = config.bo & (1 << 3);
					bitOptions.preview = config.bo & (1 << 4);
					bitOptions.isAddAutomatic = config.bo & (1 << 6);
					bitOptions.reviewQustion = config.bo & (1 << 7);
					bitOptions.quizSummeryHide = config.bo & (1 << 8);
					bitOptions.skipButton = config.bo & (1 << 9);
					bitOptions.autoStart = config.bo & (1 << 10);
					bitOptions.forcingQuestionSolve = config.bo & (1 << 11);
					bitOptions.hideQuestionPositionOverview =
						config.bo & (1 << 12);
					bitOptions.formActivated = config.bo & (1 << 13);
					bitOptions.maxShowQuestion = config.bo & (1 << 14);
					bitOptions.sortCategories = config.bo & (1 << 15);

					const cors = config.bo & (1 << 5);

					if (
						cors &&
						jQuery.support != undefined &&
						jQuery.support.cors != undefined &&
						jQuery.support.cors == false
					) {
						bitOptions.cors = cors;
					}
				}
			},

			setClozeStyle() {
				$e.find('.wpProQuiz_cloze input').each(function () {
					const $this = $(this);
					let word = '';
					const wordLen = $this.data('wordlen');

					for (let i = 0; i < wordLen; i++) {
						word += 'w';
					}

					const clone = $(document.createElement('span'))
						.css('visibility', 'hidden')
						.text(word)
						.appendTo($('body'));

					const width = clone.width();

					clone.remove();

					$this.width(width + 5);
				});
			},

			parseTime(sec) {
				let seconds = parseInt(sec % 60);
				let minutes = parseInt((sec / 60) % 60);
				let hours = parseInt((sec / 3600) % 24);

				seconds = (seconds > 9 ? '' : '0') + seconds;
				minutes = (minutes > 9 ? '' : '0') + minutes;
				hours = (hours > 9 ? '' : '0') + hours;

				return hours + ':' + minutes + ':' + seconds;
			},

			cleanupCurlyQuotes(str) {
				str = str.replace(/\u2018/, "'");
				str = str.replace(/\u2019/, "'");

				str = str.replace(/\u201C/, '"');
				str = str.replace(/\u201D/, '"');

				//return str.trim().toLowerCase();

				// Changes in v2.5 to NOT set cloze answers to lowercase
				return str.trim();
			},

			resetMatrix(selector) {
				selector.each(function () {
					const $this = $(this);
					const $list = $this.find('.wpProQuiz_sortStringList');

					$this.find('.wpProQuiz_sortStringItem').each(function () {
						$list.append($(this));
					});
				});
			},

			marker(e, correct) {
				if (!bitOptions.disabledAnswerMark) {
					if (correct === true) {
						e.addClass('wpProQuiz_answerCorrect');
					} else if (correct === false) {
						e.addClass('wpProQuiz_answerIncorrect');
					} else {
						e.addClass(correct);
					}
				}
			},

			startQuiz(loadData) {
				if (config.ld_script_debug == true) {
					console.log('in startQuiz');
				}

				if (quizStatus.loadLock) {
					quizStatus.isQuizStart = 1;

					return;
				}

				quizStatus.isQuizStart = 0;

				if (quizStatus.isLocked) {
					globalElements.quizStartPage.hide();
					$e.find('.wpProQuiz_lock').show();

					return;
				}

				if (quizStatus.isPrerequisite) {
					globalElements.quizStartPage.hide();
					$e.find('.wpProQuiz_prerequisite').show();

					return;
				}

				if (quizStatus.isUserStartLocked) {
					globalElements.quizStartPage.hide();
					$e.find('.wpProQuiz_startOnlyRegisteredUser').show();

					return;
				}

				if (bitOptions.maxShowQuestion && !loadData) {
					if (config.formPos == formPosConst.START) {
						if (!formClass.checkForm()) {
							return;
						}
					}

					globalElements.quizStartPage.hide();
					$e.find('.wpProQuiz_loadQuiz').show();

					plugin.methode.loadQuizDataAjax(true);

					return;
				}

				if (
					bitOptions.formActivated &&
					config.formPos == formPosConst.START
				) {
					if (!formClass.checkForm()) {
						return;
					}
				}

				plugin.methode.loadQuizData();
				quiz_resume_data = learndash_prepare_quiz_resume_data(config);

				if (quiz_resume_data === false) {
					quiz_resume_data = {};
				}

				// Init our Cookies
				//if (config.quiz_resume_enabled === "1") {
				//	cookie_name = "ld_save_" + config.quizId + "_quiz_responses";
				//	plugin.methode.CookieInit();
				//} else if (config.timelimitcookie !== 0) {
				//	cookie_name = "ld_" + config.quizId + "_quiz_responses";
				//	plugin.methode.CookieInit();
				//}

				questionTimer.startQuiz();

				if (
					bitOptions.randomQuestion &&
					jQuery.isEmptyObject(quiz_resume_data) &&
					!quiz_resume_data.randomQuestions
				) {
					plugin.methode.random(
						globalElements.questionList,
						'question'
					);
				}

				if (bitOptions.randomAnswer) {
					plugin.methode.random($e.find(globalNames.questionList));
				}

				if (bitOptions.sortCategories) {
					plugin.methode.sortCategories();
				}

				// randomize the matrix sort question items
				plugin.methode.random($e.find('.wpProQuiz_sortStringList'));

				// randomize the sort question answers
				//plugin.methode.random($e.find('.wpProQuiz_questionList[data-type="sort_answer"]'));

				$e.find('.wpProQuiz_listItem').each(function (i, v) {
					const $this = $(this);
					$this
						.find('.wpProQuiz_question_page span:eq(0)')
						.text(i + 1);
					$this.find('> h5 span').text(i + 1);

					$this
						.find('.wpProQuiz_questionListItem')
						.each(function (i, v) {
							$(this)
								.find('> span:not(.wpProQuiz_cloze)')
								.text(i + 1 + '. ');
						});
				});

				globalElements.next = $e.find(globalNames.next);

				switch (config.mode) {
					case 3:
						$e.find('input[name="checkSingle"]').show();
						break;
					case 2:
						$e.find(globalNames.check).show();

						if (
							!bitOptions.skipButton &&
							bitOptions.reviewQustion
						) {
							$e.find(globalNames.skip).show();
						}

						break;
					case 1:
						$e.find('input[name="back"]').slice(1).show();
					case 0:
						globalElements.next.show();
						break;
				}

				if (
					bitOptions.hideQuestionPositionOverview ||
					config.mode == 3
				) {
					$e.find('.wpProQuiz_question_page').hide();
				}

				//Change last name
				const $lastButton = globalElements.next.last();
				lastButtonValue = $lastButton.val();
				$lastButton.val(config.lbn);

				const $listItem = globalElements.questionList.children();

				globalElements.listItems = $e.find('.wpProQuiz_list > li');

				//quiz_resume_data.lastQuestion = parseInt(quiz_resume_data.lastQuestion);

				if (
					config.mode === 3 &&
					'number' !== typeof quiz_resume_data.lastQuestion
				) {
					plugin.methode.showSinglePage(0);
				}

				if ('number' !== typeof quiz_resume_data.lastQuestion) {
					quiz_resume_data.lastQuestion = 0;
				}

				// Original
				/*
				if ( config.mode !== 3 ) {
					currentQuestion = $listItem.eq( 0 ).show();
					var questionId = currentQuestion.find( globalNames.questionList ).data( 'question_id' );
					questionTimer.questionStart( questionId );
				}
				*/

				if (config.mode !== 3) {
					if (config.ld_script_debug == true) {
						console.log(
							'line 1359: quiz_resume_data.lastQuestion[%o]',
							quiz_resume_data.lastQuestion
						);
					}

					if (quiz_resume_data.lastQuestion > 0) {
						$listItem.each(function (q_idx, q_item) {
							const q_id = $(q_item)
								.find('.wpProQuiz_questionList')
								.data('question_id');

							if (config.ld_script_debug == true) {
								console.log(
									'q_id[%o] quiz_resume_data.lastQuestion[%o]',
									q_id,
									quiz_resume_data.lastQuestion
								);
							}

							if (q_id === quiz_resume_data.lastQuestion) {
								currentQuestion = $listItem.eq(q_idx);
								if (config.ld_script_debug == true) {
									console.log(
										'currentQuestion[%o]',
										currentQuestion
									);
									console.log('$listItem[%o]', $listItem);
								}

								//$listItem.eq(q_idx).show();
								return false;
							}
						});

						/**
						 * The saved question may no longer be in the rendered set (e.g. a
						 * different random subset on resume); fall back to a valid question so
						 * currentQuestion is never null.
						 */
						if (!currentQuestion || !currentQuestion.length) {
							currentQuestion = $listItem.eq(0);
						}
					} else {
						if (config.ld_script_debug == true) {
							console.log(
								'line 1382: ELSE: quiz_resume_data.lastQuestion zero'
							);
							console.log('$listItem[%o]', $listItem);
						}
						currentQuestion = $listItem.eq(0);

						if (config.ld_script_debug == true) {
							console.log('currentQuestion[%o]', currentQuestion);
						}

						$listItem.eq(0).show();
					}
				} else {
					if (config.ld_script_debug == true) {
						console.log('line 1390: ELSE: condif.mode = 3');
					}
					currentQuestion = $listItem.eq(0);
				}
				if (config.ld_script_debug == true) {
					console.log('after currentQuestion[%o]', currentQuestion);
				}

				// Backwards compatibility with templates older than 4.21.3.
				$e.find(
					'.wpProQuiz_sortable:not(.ld-sortable__item-handle)'
				).each(function (index, sortableHandle) {
					const $sortable = $(sortableHandle).parents(
						'.wpProQuiz_questionList'
					);

					$sortable
						.sortable({
							scroll: true,
							scrollSensitivity: 10 || config.scrollSensitivity,
							scrollSpeed: 10 || config.scrollSpeed,
							update() {
								const $p = $(this).parents(
									'.wpProQuiz_listItem'
								);

								$e.trigger({
									type: 'questionSolved',
									values: {
										item: $p,
										index: $p.index(),
										solved: true,
									},
								});
							},
						})
						.disableSelection();
				});

				$e.find('.ld-sortable--sort_answer').each(function (
					index,
					sortable
				) {
					let instance = sortable?.ldSortable;

					if (!instance) {
						instance =
							learndash.forms.sortable.initSortable(sortable);
					}

					const callback = (container, item) => {
						const $p = $(container).parents('.wpProQuiz_listItem');

						$e.trigger({
							type: 'questionSolved',
							values: {
								item: $p,
								index: $p.index(),
								solved: true,
							},
						});
					};

					instance.on('dropped', callback);
					instance.on('reorder', callback);
				});

				$e.find(
					'.wpProQuiz_sortStringList, .wpProQuiz_maxtrixSortCriterion'
				)
					.sortable({
						connectWith:
							'.wpProQuiz_maxtrixSortCriterion:not(:has(li)), .wpProQuiz_sortStringList',
						placeholder: 'wpProQuiz_placehold',
						scroll: true,
						scrollSensitivity: 10 || config.scrollSensitivity,
						scrollSpeed: 10 || config.scrollSpeed,
						update(event, ui) {
							const $p = $(this).parents('.wpProQuiz_listItem');
							$e.trigger({
								type: 'questionSolved',
								values: {
									item: $p,
									index: $p.index(),
									solved: true,
								},
							});
						},
					})
					.disableSelection();

				quizSolved = [];

				timelimit.start();

				startTime = +new Date();

				results = {
					comp: { points: 0, correctQuestions: 0, quizTime: 0 },
				};

				$e.find('.wpProQuiz_questionList').each(function () {
					const questionId = $(this).data('question_id');

					results[questionId] = { time: 0 };
				});

				catResults = {};

				$.each(options.catPoints, function (i, v) {
					catResults[i] = 0;
				});

				globalElements.quizStartPage.hide();
				$e.find('.wpProQuiz_loadQuiz').hide();
				globalElements.quiz.show();
				reviewBox.show();

				// Init our Cookies
				if (config.timelimitcookie !== 0) {
					cookie_name = 'ld_' + config.quizId + '_quiz_responses';
					plugin.methode.CookieInit();
				}

				if (config.quiz_resume_enabled === '1') {
					cookie_name =
						'ld_save_' + config.quizId + '_quiz_responses';
					plugin.methode.CookieInit();
				}

				//$('li.wpProQuiz_listItem', globalElements.questionList).each( function (idx, questionItem) {
				plugin.methode.setupMatrixSortHeights();
				//});

				if (config.ld_script_debug == true) {
					console.log(
						'currentQuestion.index [%o] currentQuestion[%o]',
						currentQuestion.index(),
						currentQuestion
					);
				}

				if (config.mode != 3) {
					$e.trigger({
						type: 'changeQuestion',
						values: {
							item: currentQuestion,
							index: currentQuestion.index(),
						},
					});
				}
			},
			showSingleQuestion(question) {
				//console.log('in showSingleQuestion question[%o]', question);

				const page = question ? Math.ceil(question / config.qpp) : 1;

				if (
					config.mode === 3 &&
					'number' !== typeof quiz_resume_data.lastQuestion
				) {
					this.showSinglePage(page);
				} else {
					this.singlePageNavigationQuizResume(question);
				}
			},
			showSinglePage(page) {
				$listItem = globalElements.questionList.children().hide();

				if (!config.qpp) {
					$listItem.show();

					return;
				}

				page = page ? +page : 1;

				const maxPage = Math.ceil(
					$e.find('.wpProQuiz_list > li').length / config.qpp
				);

				if (page > maxPage) {
					return;
				}

				const pl = $e.find(globalNames.singlePageLeft).hide();
				const pr = $e.find(globalNames.singlePageRight).hide();
				const cs = $e.find('input[name="checkSingle"]').hide();

				if (page > 1) {
					pl.val(pl.data('text').replace(/%d/, page - 1)).show();
				}

				if (page == maxPage) {
					cs.show();
				} else {
					pr.val(pr.data('text').replace(/%d/, page + 1)).show();
				}

				currentPage = page;

				const start = config.qpp * (page - 1);

				$listItem.slice(start, start + config.qpp).show();
				//plugin.methode.scrollTo( globalElements.quizStartPage );

				/**
				 * LEARNDASH-6591 On Quiz Start the scroll to the top of the page
				 * because the quiz element is hidden. So within the scrollTo()
				 * function the element position can't determined.
				 *
				 * Also changed the scroll element to the outer quiz wrapper.
				 */
				if (globalElements.self !== undefined) {
					if (globalElements.self.is(':hidden')) {
						globalElements.self.show();
					}
					plugin.methode.scrollTo(globalElements.self);
				}
			},

			nextQuestion() {
				if (
					config.timelimitcookie !== 0 ||
					config.quiz_resume_enabled === '1'
				) {
					if (
						currentQuestion
							.find(globalNames.next)
							.data('question-lock') === true
					) {
						plugin.methode.CookieProcessQuestionResponse(
							currentQuestion,
							true
						);
					} else {
						plugin.methode.CookieProcessQuestionResponse(
							currentQuestion
						);
					}
				}

				if (config.quiz_resume_enabled === '1') {
					// current Question (before pressing Next)
					plugin.methode.saveMetaDataToCookie({
						lastQuestion: jQuery(currentQuestion[0]).data(
							'question-meta'
						).question_pro_id,
					});

					// next Question
					const nextQuestion = jQuery(currentQuestion.next()[0]);
					if (
						typeof nextQuestion.data('question-meta') !==
						'undefined'
					) {
						plugin.methode.saveMetaDataToCookie({
							nextQuestion:
								nextQuestion.data('question-meta')
									.question_pro_id,
						});
					}
				}

				jQuery('.mejs-pause').trigger('click');
				this.showQuestionObject(currentQuestion.next());
			},

			prevQuestion() {
				this.showQuestionObject(currentQuestion.prev());
			},

			showQuestion(index) {
				const $element = globalElements.listItems.eq(index);

				if (config.mode == 3 || inViewQuestions) {
					if (config.qpp) {
						plugin.methode.showSingleQuestion(index + 1);
					}
					plugin.methode.scrollTo($element, 1);
					questionTimer.startQuiz();
					return;
				}

				this.showQuestionObject($element);
			},

			showQuestionObject(obj) {
				if (config.ld_script_debug == true) {
					//console.trace();
					console.log('showQuestionObject: obj[%o]', obj);
				}

				// We hide the current question IF it is set.
				if (currentQuestion && currentQuestion.length > 0) {
					globalElements.questionList.children().each(function () {
						if (
							$(this).data('question-meta').question_pro_id !==
							currentQuestion.data('question-meta')
								.question_pro_id
						) {
							$(this).hide();
						}
					});
				}

				/**
				 * Defensive: a stale resume reference may pass a null/undefined target.
				 * Normalize to an empty jQuery object so the end-of-quiz handling below runs
				 * instead of throwing on a missing question.
				 */
				obj = obj || $();

				if (
					!obj.length &&
					bitOptions.forcingQuestionSolve &&
					bitOptions.quizSummeryHide &&
					bitOptions.reviewQustion
				) {
					// First get all the questions...
					list = globalElements.questionList.children();

					if (list != null) {
						list.each(function () {
							const $this = $(this);
							const $questionList = $this.find(
								globalNames.questionList
							);
							const question_id =
								$questionList.data('question_id');
							const data =
								config.json[$questionList.data('question_id')];

							// Within the following logic. If the question type is 'sort_answer' there is a chance
							// the sortable answers will be displayed in the correct order. In that case the user will click
							// the next button.
							// The trigger to set the question was answered is normally a function of the sort/drag action
							// by the user. So we need to set the question answered flag in the case the Quiz summary is enabled.
							//if (data.type == 'sort_answer') {
							//	$e.trigger({type: 'questionSolved', values: {item: $this, index: $this.index(), solved: true}});
							//}
						});
					}

					for (
						let i = 0, c = $e.find('.wpProQuiz_listItem').length;
						i < c;
						i++
					) {
						if (!quizSolved[i]) {
							alert(WpProQuizGlobal.questionsNotSolved);
							return false;
						}
					}
				}

				//globalElements.questionList.children().hide();
				if (currentQuestion && currentQuestion.length) {
					currentQuestion.hide();
				}

				currentQuestion = obj.show();

				plugin.methode.scrollTo(globalElements.quiz);

				$e.trigger({
					type: 'changeQuestion',
					values: {
						item: currentQuestion,
						index: currentQuestion.index(),
					},
				});

				if (!currentQuestion.length) {
					plugin.methode.showQuizSummary();
				} else {
					const questionId = currentQuestion
						.find(globalNames.questionList)
						.data('question_id');
					questionTimer.questionStart(questionId);
				}
			},

			skipQuestion() {
				$e.trigger({
					type: 'skipQuestion',
					values: {
						item: currentQuestion,
						index: currentQuestion.index(),
					},
				});
				plugin.methode.nextQuestion();
			},
			reviewQuestion() {
				$e.trigger({
					type: 'reviewQuestion',
					values: {
						item: currentQuestion,
						index: currentQuestion.index(),
					},
				});
			},

			uploadFile(event) {
				const question_id = event.currentTarget.id.replace(
					'uploadEssaySubmit_',
					''
				);
				const file = $('#uploadEssay_' + question_id)[0].files[0];

				if (typeof file !== 'undefined') {
					const nonce = $('#_uploadEssay_nonce_' + question_id).val();
					const uploadEssaySubmit = $(
						'#uploadEssaySubmit_' + question_id
					);

					const uploadEssayMessage = $(
						'#uploadEssayMessage_' + question_id
					);
					uploadEssayMessage.removeClass('uploadEssayMessage_fail');
					uploadEssayMessage.removeClass(
						'uploadEssayMessage_success'
					);
					uploadEssayMessage.html(config.essayUploading);

					const data = new FormData();
					data.append('action', 'learndash_upload_essay');
					data.append('nonce', nonce);
					data.append('question_id', question_id);
					data.append('course_id', config.course_id);
					data.append('essayUpload', file);

					$.ajax({
						method: 'POST',
						type: 'POST',
						url: WpProQuizGlobal.ajaxurl,
						data,
						cache: false,
						contentType: false,
						processData: false,
						success(response) {
							// Update the response message. Then later apply the class for the color.
							if (typeof response.data.message !== 'undefined') {
								uploadEssayMessage.html(response.data.message);
							}

							if (
								response.success == true &&
								typeof response.data.filelink !== 'undefined'
							) {
								uploadEssayMessage.addClass(
									'uploadEssayMessage_success'
								);
								$('#uploadEssayFile_' + question_id).val(
									response.data.filelink
								);

								// disable the upload button. Only one file per quiz.
								uploadEssaySubmit.attr('disabled', 'disabled');

								const $item = $(
									'#uploadEssayFile_' + question_id
								).parents('.wpProQuiz_listItem');
								$e.trigger({
									type: 'questionSolved',
									values: {
										item: $item,
										index: $item.index(),
										solved: true,
									},
								});
							} else {
								uploadEssayMessage.addClass(
									'uploadEssayMessage_fail'
								);
								uploadEssaySubmit.removeAttr('disabled');
							}
						},
					});
				}
				event.preventDefault();
			},

			showQuizSummary() {
				questionTimer.questionStop();
				questionTimer.stopQuiz();
				cookieSending = true;

				if (bitOptions.quizSummeryHide || !bitOptions.reviewQustion) {
					if (
						bitOptions.formActivated &&
						config.formPos == formPosConst.END
					) {
						reviewBox.hide();
						globalElements.quiz.hide();
						plugin.methode.scrollTo(
							$e.find('.wpProQuiz_infopage').show()
						);
					} else {
						plugin.methode.finishQuiz();
					}

					return;
				}

				const quizSummary = $e.find('.wpProQuiz_checkPage');

				// Clone the Review questions and legand and add to the
				//$e.find('.wpProQuiz_checkPage .wpProQuiz_reviewSummary').html('');
				$e.find('.wpProQuiz_checkPage .wpProQuiz_reviewSummary').append(
					$e
						.find('.wpProQuiz_reviewDiv .wpProQuiz_reviewQuestion')
						.clone()
				);

				$e.find('.wpProQuiz_checkPage .wpProQuiz_reviewSummary').append(
					$e
						.find('.wpProQuiz_reviewDiv .wpProQuiz_reviewLegend')
						.clone()
				);

				$e.find(
					'.wpProQuiz_checkPage .wpProQuiz_reviewSummary .wpProQuiz_reviewQuestion li'
				).removeClass('wpProQuiz_reviewQuestionTarget');

				$e.find(
					'.wpProQuiz_checkPage .wpProQuiz_reviewSummary .wpProQuiz_reviewQuestion li'
				).on('click', function (event) {
					$e.find(
						'.wpProQuiz_checkPage .wpProQuiz_reviewSummary'
					).html('');

					quizSummary.hide();
					globalElements.quiz.show();
					reviewBox.show(true);

					const index = $(this).index();
					plugin.methode.showQuestion(index);
				});

				let cSolved = 0;

				for (let i = 0, c = quizSolved.length; i < c; i++) {
					if (quizSolved[i]) {
						cSolved++;
					}
				}

				quizSummary.find('span:eq(0)').text(cSolved);

				reviewBox.hide();
				globalElements.quiz.hide();

				quizSummary.show();

				plugin.methode.scrollTo(quizSummary);
			},

			finishQuiz(timeover) {
				// LEARNDASH-4412 : Disable final buttons on submit.
				globalElements.next.last().attr('disabled', 'disabled');
				$e.find('input[name="checkSingle"]').attr(
					'disabled',
					'disabled'
				);

				questionTimer.questionStop();
				questionTimer.stopQuiz();
				timelimit.stop();
				cookieSending = true;

				let time = (+new Date() - startTime) / 1000;
				time =
					config.timelimit && time > config.timelimit
						? config.timelimit
						: time;
				timespent = time;
				$e.find('.wpProQuiz_quiz_time span').text(
					plugin.methode.parseTime(time)
				);

				if (timeover) {
					globalElements.results
						.find('.wpProQuiz_time_limit_expired')
						.show();
				}

				plugin.methode.checkQuestion(
					globalElements.questionList.children(),
					true
				);
			},
			finishQuizEnd() {
				$e.find('.wpProQuiz_correct_answer').text(
					results.comp.correctQuestions
				);

				if (config.globalPoints > 0) {
					results.comp.result =
						Math.round(
							( results.comp.points / config.globalPoints ) * 100 * 100
						) / 100;
				} else {
					results.comp.result = 0;
				}

				let hasNotGradedQuestion = false;
				$.each(results, function () {
					if (
						typeof this.graded_status !== 'undefined' &&
						this.graded_status == 'not_graded'
					) {
						hasNotGradedQuestion = true;
					}
				});

				if (
					typeof certificate_details !== 'undefined' &&
					certificate_details.certificateLink != undefined &&
					certificate_details.certificateLink != ''
				) {
					const certificateContainer = $e.find(
						'.wpProQuiz_certificate'
					);
					if (
						results.comp.result >=
						certificate_details.certificate_threshold * 100
					) {
						//if (true == hasNotGradedQuestion && typeof certificate_pending !== 'undefined') {
						//	certificateContainer.append('<br />'+certificate_pending );
						//}
						certificateContainer.show();
					} else if (
						true == hasNotGradedQuestion &&
						typeof certificate_pending !== 'undefined'
					) {
						certificateContainer.html(certificate_pending);
						certificateContainer.show();
					}
				}

				const quiz_continue_link = $e.find('.quiz_continue_link');

				let show_quiz_continue_buttom_on_fail = false;
				if (
					jQuery(quiz_continue_link).hasClass(
						'show_quiz_continue_buttom_on_fail'
					)
				) {
					show_quiz_continue_buttom_on_fail = true;
				}

				if (
					typeof options.passingpercentage !== 'undefined' &&
					parseFloat(options.passingpercentage) >= 0.0
				) {
					if (
						results.comp.result >= options.passingpercentage ||
						show_quiz_continue_buttom_on_fail
					) {
						$e.addClass('ld-quiz-result-passed');
						$e.removeClass('ld-quiz-result-failed');

						$e.trigger({
							type: 'learndash-quiz-finished',
							values: {
								status: 'passed',
								item: $e,
								results,
							},
						});
						$e.trigger({
							type: 'learndash-quiz-finished-passed',
							values: {
								status: 'passed',
								item: $e,
								results,
							},
						});

						//For now, Just append the HTML to the page
						if (typeof continue_details !== 'undefined') {
							$e.find('.quiz_continue_link').html(
								continue_details
							);
							$e.find('.quiz_continue_link').show();
						}
					} else {
						$e.removeClass('ld-quiz-result-passed');
						$e.addClass('ld-quiz-result-failed');

						$e.trigger({
							type: 'learndash-quiz-finished',
							values: {
								status: 'failed',
								item: $e,
								results,
							},
						});
						$e.trigger({
							type: 'learndash-quiz-finished-failed',
							values: {
								status: 'failed',
								item: $e,
								results,
							},
						});

						$e.find('.quiz_continue_link').hide();
					}
				} else if (typeof continue_details !== 'undefined') {
					$e.find('.quiz_continue_link').html(continue_details);
					$e.find('.quiz_continue_link').show();
				}

				$pointFields = $e.find('.wpProQuiz_points span');
				$gradedPointsFields = $e.find('.wpProQuiz_graded_points span');

				$pointFields.eq(0).text(results.comp.points);
				$pointFields.eq(1).text(config.globalPoints);
				$pointFields.eq(2).text(results.comp.result + '%');

				$gradedQuestionCount = 0;
				$gradedQuestionPoints = 0;

				$.each(results, function (question_id, result) {
					if (!isNaN(question_id) && result.graded_id) {
						const possible = result.possiblePoints - result.points;
						if (possible > 0) {
							$gradedQuestionPoints += possible;
							$gradedQuestionCount++;
						}
					}
				});

				if ($gradedQuestionCount > 0) {
					$('.wpProQuiz_points').hide();
					$('.wpProQuiz_graded_points').show();
					$gradedPointsFields.eq(0).text(results.comp.points);
					$gradedPointsFields.eq(1).text(config.globalPoints);
					$gradedPointsFields.eq(2).text(results.comp.result + '%');
					$gradedPointsFields.eq(3).text($gradedQuestionCount);
					$gradedPointsFields.eq(4).text($gradedQuestionPoints);
				}

				$e.find('.wpProQuiz_resultsList > li')
					.eq(plugin.methode.findResultIndex(results.comp.result))
					.show();

				plugin.methode.setAverageResult(results.comp.result, false);

				this.setCategoryOverview();

				plugin.methode.sendCompletedQuiz();

				if (bitOptions.isAddAutomatic && toplistData.isUser) {
					plugin.methode.addToplist();
				}

				reviewBox.hide();

				$e.find('.wpProQuiz_checkPage, .wpProQuiz_infopage').hide();
				globalElements.quiz.hide();
			},
			sending(start, end, step_size) {
				globalElements.sending.show();
				const sending_progress_bar = globalElements.sending.find(
					'.sending_progress_bar'
				);
				let i;
				if (typeof start === undefined || start == null) {
					i =
						parseInt(
							(sending_progress_bar.width() * 100) /
								sending_progress_bar.offsetParent().width()
						) + 156;
				} else {
					i = start;
				}

				if (end == undefined) {
					var end = 80;
				}

				if (step_size == undefined) {
					step_size = 1;
				}

				if (
					sending_timer != null &&
					typeof sending_timer !== undefined
				) {
					clearInterval(sending_timer);
				}
				sending_timer = setInterval(function () {
					const currentWidth = parseInt(
						(sending_progress_bar.width() * 100) /
							sending_progress_bar.offsetParent().width()
					);
					if (currentWidth >= end) {
						clearInterval(sending_timer);
						if (currentWidth >= 100) {
							setTimeout(plugin.methode.showResults(), 2000);
						}
					}
					sending_progress_bar.css('width', i + '%');
					i = i + step_size;
				}, 300);
			},
			showResults() {
				globalElements.sending.hide();
				globalElements.results.show();
				plugin.methode.scrollTo(globalElements.results);
			},
			setCategoryOverview() {
				results.comp.cats = {};

				$e.find('.wpProQuiz_catOverview li').each(function () {
					const $this = $(this);
					const catId = $this.data('category_id'); // cspell:disable-line

					if (config.catPoints[catId] === undefined) {
						// cspell:disable-line
						$this.hide();
						return true;
					}

					const r =
						Math.round(
							(catResults[catId] / config.catPoints[catId]) *
								100 *
								100
						) / 100; // cspell:disable-line

					results.comp.cats[catId] = r; // cspell:disable-line

					$this.find('.wpProQuiz_catPercent').text(r + '%');

					$this.show();
				});
			},

			questionSolved(e) {
				//if (config.ld_script_debug == true) {
				//	console.log('questionSolved: e.values[%o]', e.values);
				//}

				quizSolved[e.values.index] = e.values.solved;
			},

			sendCompletedQuiz() {
				if (bitOptions.preview) {
					return;
				}
				// Prevent sending cookie data during quiz submission.
				cookieSending = true;

				//console.log('sendCompletedQuiz: results[%o]', results);

				fetchAllAnswerData(results);

				const formData = formClass.getFormData();

				// Add tip / hint count to results object.
				// Server-side data

				var quiz_resume_data =
					learndash_prepare_quiz_resume_data(config);
				if (config.ld_script_debug == true) {
					console.log(
						'line: 1936 quiz_resume_data[%o]',
						quiz_resume_data
					);
				}
				if (quiz_resume_data === false) {
					quiz_resume_data = {};
				}

				if ('undefined' !== typeof config.quiz_resume_data) {
					try {
						var quiz_resume_data = JSON.parse(
							config.quiz_resume_data
						);
					} catch (exception) {
						console.log('JSON.parse error [%o]', exception);
						var quiz_resume_data = {};
					}
					if (!jQuery.isEmptyObject(quiz_resume_data)) {
						for (var element in quiz_resume_data) {
							if (element.startsWith('tip')) {
								var id = quiz_resume_data[element].question_id;
								results[id].tip = 1;
							}
						}
					}
				}

				// Cookie data
				if (
					'undefined' !== typeof cookie_value &&
					'' !== cookie_value
				) {
					for (var element in cookie_value) {
						if (element.startsWith('tip')) {
							var id = cookie_value[element].question_id;
							results[id].tip = 1;
						}
					}
				}

				jQuery.ajax({
					type: 'POST',
					url: WpProQuizGlobal.ajaxurl,
					dataType: 'json',
					cache: false,
					data: {
						action: 'wp_pro_quiz_completed_quiz',
						course_id: config.course_id,
						lesson_id: config.lesson_id,
						topic_id: config.topic_id,
						quiz: config.quiz,
						quizId: config.quizId,
						results: JSON.stringify(results),
						timespent,
						forms: formData,
						quiz_nonce: config.quiz_nonce,
					},
					success(json) {
						if (json != null) {
							if (typeof config.quizId !== 'undefined') {
								const quiz_pro_id = parseInt(config.quizId);
								if (
									typeof json[quiz_pro_id] !== 'undefined' &&
									typeof json[quiz_pro_id]
										.quiz_result_settings !== 'undefined'
								) {
									result_settings =
										json[quiz_pro_id].quiz_result_settings;
									plugin.methode.afterSendUpdateIU(
										result_settings
									);
								}
							}
						}

						plugin.methode.sending(null, 100, 15); //Complete the remaining progress bar faster and show results

						// Clear Cookie on restart
						plugin.methode.CookieDelete();
					},
				});
			},

			afterSendUpdateIU(quiz_result_settings) {
				if (
					typeof quiz_result_settings.showAverageResult !==
					'undefined'
				) {
					if (!quiz_result_settings.showAverageResult) {
						$e.find('.wpProQuiz_resultTable').remove();
					}
				}

				if (
					typeof quiz_result_settings.showCategoryScore !==
					'undefined'
				) {
					if (!quiz_result_settings.showCategoryScore) {
						$e.find('.wpProQuiz_catOverview').remove();
					}
				}

				if (
					typeof quiz_result_settings.showRestartQuizButton !==
					'undefined'
				) {
					if (!quiz_result_settings.showRestartQuizButton) {
						$e.find('input[name="restartQuiz"]').remove();
					}
				}

				if (
					typeof quiz_result_settings.showResultPoints !== 'undefined'
				) {
					if (!quiz_result_settings.showResultPoints) {
						$e.find('.wpProQuiz_points').remove();
					}
				}

				if (
					typeof quiz_result_settings.showResultQuizTime !==
					'undefined'
				) {
					if (!quiz_result_settings.showResultQuizTime) {
						$e.find('.wpProQuiz_quiz_time').remove();
					}
				}

				if (
					typeof quiz_result_settings.showViewQuestionButton !==
					'undefined'
				) {
					if (!quiz_result_settings.showViewQuestionButton) {
						$e.find('input[name="reShowQuestion"]').remove();
					}
				}

				if (
					typeof quiz_result_settings.showContinueButton !==
					'undefined'
				) {
					if (!quiz_result_settings.showContinueButton) {
						$e.find('.quiz_continue_link').remove();
					}
				}
			},
			findResultIndex(p) {
				const r = config.resultsGrade;
				let index = -1;
				let diff = 999999;

				for (let i = 0; i < r.length; i++) {
					const v = r[i];

					if (p >= v && p - v < diff) {
						diff = p - v;
						index = i;
					}
				}

				return index;
			},

			showQustionList() {
				inViewQuestions = !inViewQuestions;
				globalElements.toplistShowInButton.hide();
				globalElements.quiz.toggle();
				$e.find('.wpProQuiz_QuestionButton').hide();
				globalElements.questionList.children().show();
				reviewBox.toggle();

				$e.find('.wpProQuiz_question_page').hide();
			},
			random(group, type) {
				var type = type || false;
				let randomized;
				group.each(function () {
					const answer_type = $(this).data('type');
					let e;
					if (answer_type !== '' && answer_type !== 'sort_answer') {
						e = $(this)
							.children()
							.get()
							.sort(function () {
								return Math.round(Math.random()) - 0.5;
							});
					} else {
						e = $(this).children().get();
					}
					$(e).appendTo(e[0].parentNode);
					randomized = e;
				});
				if (config.quiz_resume_enabled === '1') {
					if (
						'undefined' !== typeof randomized &&
						type === 'question'
					) {
						plugin.methode.saveRandomQuestions(randomized);
					}
				}
			},
			saveRandomQuestions(questions) {
				const orderedQuestions = [];
				jQuery(questions).each(function (index, question) {
					const id =
						jQuery(question).data('question-meta').question_pro_id;
					orderedQuestions.push(id);
				});

				if (orderedQuestions.length > 0) {
					plugin.methode.saveMetaDataToCookie({
						randomQuestions: true,
						randomOrder: orderedQuestions,
					});
				}
			},
			sortCategories() {
				const e = $('.wpProQuiz_list')
					.children()
					.get()
					.sort(function (a, b) {
						const aQuestionId = $(a)
							.find('.wpProQuiz_questionList')
							.data('question_id');
						const bQuestionId = $(b)
							.find('.wpProQuiz_questionList')
							.data('question_id');

						return (
							config.json[aQuestionId].catId -
							config.json[bQuestionId].catId
						); // cspell:disable-line
					});

				$(e).appendTo(e[0].parentNode);
			},

			restartQuiz() {
				globalElements.results.hide();
				//globalElements.quizStartPage.show();
				globalElements.questionList.children().hide();
				globalElements.toplistShowInButton.hide();
				reviewBox.hide();

				$e.find('.wpProQuiz_questionInput, .wpProQuiz_cloze input')
					.removeAttr('disabled')
					.removeAttr('checked')
					.css('background-color', '');

				// Reset all the question types to empty values. This really should be moved into a reset function to be called at other times like at Quiz Init.
				//				$e.find('.wpProQuiz_cloze input').val('');
				$e.find('.wpProQuiz_questionListItem input[type="text"]').val(
					''
				);

				$e.find(
					'.wpProQuiz_answerCorrect, .wpProQuiz_answerIncorrect'
				).removeClass(
					'wpProQuiz_answerCorrect wpProQuiz_answerIncorrect'
				);

				$e.find('.wpProQuiz_listItem').data('check', false);
				$e.find('textarea.wpProQuiz_questionEssay').val('');
				$e.find('input.uploadEssayFile').val('');
				$e.find('input.wpProQuiz_upload_essay').val('');

				$e.find('.wpProQuiz_response').hide().children().hide();

				plugin.methode.resetMatrix($e.find('.wpProQuiz_listItem'));

				$e.find(
					'.wpProQuiz_sortStringItem, .wpProQuiz_sortable'
				).removeAttr('style');

				$e.find(
					'.wpProQuiz_clozeCorrect, .wpProQuiz_QuestionButton, .wpProQuiz_resultsList > li'
				).hide();

				$e.find('.wpProQuiz_question_page, input[name="tip"]').show();

				$e.find('.wpProQuiz_certificate').attr(
					'style',
					'display: none !important'
				);

				globalElements.results
					.find('.wpProQuiz_time_limit_expired')
					.hide();

				globalElements.next.last().val(lastButtonValue);

				inViewQuestions = false;

				// Clear Cookie on restart
				//plugin.methode.CookieDelete();

				// LEARNDASH-3201 - Added reload to force check on Quiz Repeats / Run Once logic.
				window.location.reload(true);
			},
			showSpinner() {
				$e.find('.wpProQuiz_spinner').show();
			},
			hideSpinner() {
				$e.find('.wpProQuiz_spinner').hide();
			},
			checkQuestion(list, endCheck) {
				const finishQuiz = list == undefined ? false : true;
				const responses = {};
				const r = {};

				list = list == undefined ? currentQuestion : list;

				list.each( function () {
					const $this = $( this );
					const question_index = $this.index();
					const $questionList = $this.find(
						globalNames.questionList
					);
					const question_id = $questionList.data( 'question_id' );
					const data =
						config.json[ $questionList.data( 'question_id' ) ];
					let name = data.type;

					questionTimer.questionStop();

					if ( $this.data( 'check' ) ) {
						return true;
					}

					if ( data.type == 'single' || data.type == 'multiple' ) {
						name = 'singleMulti';
					}
					//if (config.ld_script_debug == true) {
					//	console.log('checkQuestion: calling readResponses');
					//}

					responses[ question_id ] = readResponses(
						name,
						data,
						$this,
						$questionList,
						true
					);
					responses[ question_id ].question_pro_id = data.id;
					responses[ question_id ].question_post_id =
						data.question_post_id;
					//console.log('responses[%o]', responses);

					if (
						config.timelimitcookie !== 0 ||
						config.quiz_resume_enabled === '1'
					) {
						plugin.methode.CookieSaveResponse(
							question_id,
							question_index,
							data.type,
							responses[ question_id ],
							true
						);
					}
				} );
				//console.log('responses[%o]', responses);
				config.checkAnswers = {
					list,
					responses,
					endCheck,
					finishQuiz,
				};

				if ( finishQuiz ) {
					plugin.methode.sending( 1, 80, 3 );
				} else {
					plugin.methode.showSpinner();
				}

				// Get the current quiz resume data from localStorage/cookie (client-side state).
				// This contains the live, up-to-date quiz progress as the user interacts.

				const current_quiz_resume_data =
					cookie_value &&
					typeof cookie_value === 'object' &&
					Object.keys( cookie_value ).length > 0
						? cookie_value
						: [];

				plugin.methode.ajax(
					{
						action: 'ld_adv_quiz_pro_ajax',
						func: 'checkAnswers',
						data: {
							course_id: config.course_id,
							quiz_nonce: config.quiz_nonce,
							quiz_started: questionTimer.getQuizStart(),
							quiz: config.quiz,
							quizId: config.quizId,
							responses: JSON.stringify( responses ),
							quiz_resume_data: JSON.stringify( current_quiz_resume_data ),
						},
					},
					function ( json ) {
						//console.log('json[%o]', json);

						plugin.methode.hideSpinner();
						const list = config.checkAnswers.list;
						const responses = config.checkAnswers.responses;
						const r = config.checkAnswers.r;
						const endCheck = config.checkAnswers.endCheck;
						const finishQuiz = config.checkAnswers.finishQuiz;

						list.each( function () {
							const $this = $( this );
							//console.log('this[%o]', $this);

							const $questionList = $this.find(
								globalNames.questionList
							);
							const question_id =
								$questionList.data( 'question_id' );
							//var data = {id: question_id};

							if ( $this.data( 'check' ) ) {
								return true;
							}

							if ( typeof json[ question_id ] !== 'undefined' ) {
								const result = json[ question_id ];

								data =
									config.json[
										$questionList.data( 'question_id' )
									];

								$this.find( '.wpProQuiz_response' ).show();
								$this.find( globalNames.check ).hide();
								$this.find( globalNames.skip ).hide();
								$this.find( globalNames.next ).show();
								$this
									.find( globalNames.next )
									.attr( 'data-question-lock', true );

								results[ data.id ].points = result.p;
								if ( typeof result.p_nonce !== 'undefined' ) {
									results[ data.id ].p_nonce = result.p_nonce;
								} else {
									results[ data.id ].p_nonce = '';
								}

								results[ data.id ].correct = Number( result.c );
								results[ data.id ].data = result.s;
								if ( typeof result.a_nonce !== 'undefined' ) {
									results[ data.id ].a_nonce = result.a_nonce;
								} else {
									results[ data.id ].a_nonce = '';
								}
								results[ data.id ].possiblePoints =
									result.e.possiblePoints;

								// If the sort_answer or matrix_sort_answer question type is not 100% correct then the returned
								// result.s object will be empty. So in order to pass the user's answers to the server for the
								// sendCompletedQuiz AJAX call we need to grab the result.e.r object and store into results.
								if (
									jQuery.isEmptyObject(
										results[ data.id ].data
									)
								) {
									if (
										result.e.type != undefined &&
										( result.e.type == 'sort_answer' ||
											result.e.type ==
												'matrix_sort_answer' )
									) {
										results[ data.id ].data = result.e.r;
									}
								}

								if (
									typeof result.e.graded_id !== 'undefined' &&
									result.e.graded_id > 0
								) {
									results[ data.id ].graded_id =
										result.e.graded_id;
								}

								if (
									typeof result.e.graded_status !==
									'undefined'
								) {
									results[ data.id ].graded_status =
										result.e.graded_status;
								}

								results.comp.points = parseFloat(
									parseFloat(
										results.comp.points + result.p
									).toFixed( 2 )
								);

								$this.find( '.wpProQuiz_response' ).show();
								$this.find( globalNames.check ).hide();
								$this.find( globalNames.skip ).hide();
								$this.find( globalNames.next ).show();
								$this
									.find( globalNames.next )
									.attr( 'data-question-lock', true );

								//results[data.id].points = result.p;
								//results[data.id].correct = Number(result.c);
								//results[data.id].data = result.s;

								// If the sort_answer or matrix_sort_answer question type is not 100% correct then the returned
								// result.s object will be empty. So in order to pass the user's answers to the server for the
								// sendCompletedQuiz AJAX call we need to grab the result.e.r object and store into results.
								if (
									jQuery.isEmptyObject(
										results[ data.id ].data
									)
								) {
									if (
										typeof result.e.type !== 'undefined'
									) {
										if (
											result.e.type == 'sort_answer' ||
											result.e.type ==
												'matrix_sort_answer'
										) {
											if (
												typeof result.e.r !==
												'undefined'
											) {
												results[ data.id ].data =
													result.e.r;
											}
										}

										if ( result.e.type == 'essay' ) {
											if (
												typeof result.e.graded_id !==
												'undefined'
											) {
												results[ data.id ].data = {
													graded_id:
														result.e.graded_id,
												};
											}
										}
									}
								}

								catResults[ data.catId ] += result.p; // cspell:disable-line

								if ( config.quiz_resume_enabled === '1' ) {
									plugin.methode.saveMetaDataToCookie(
										'checked' + question_id,
										result
									);
								}

								//Marker
								plugin.methode.markCorrectIncorrect(
									result,
									$this,
									$questionList
								);

								if ( result.c ) {
									if (
										typeof result.e.AnswerMessage !==
										'undefined'
									) {
										$this
											.find( '.wpProQuiz_correct' )
											.find( '.wpProQuiz_AnswerMessage' )
											.html( result.e.AnswerMessage );
										$this
											.find( '.wpProQuiz_correct' )
											.trigger(
												'learndash-quiz-answer-response-contentchanged'
											);
									}

									//if(!endCheck) {
									//	$e.trigger({type: 'questionSolved', values: {item: $this, index: $this.index(), solved: true}});
									//}
									$e.trigger( {
										type: 'questionSolvedCorrect',
										values: {
											item: $this,
											index: $this.index(),
											solved: true,
											result,
										},
									} );

									$this.find( '.wpProQuiz_correct' ).show();
									results.comp.correctQuestions += 1;
								} else {
									if (
										typeof result.e.AnswerMessage !==
										'undefined'
									) {
										$this
											.find( '.wpProQuiz_incorrect' )
											.find( '.wpProQuiz_AnswerMessage' )
											.html( result.e.AnswerMessage );
										$this
											.find( '.wpProQuiz_incorrect' )
											.trigger(
												'learndash-quiz-answer-response-contentchanged'
											);
									}

									//if (!endCheck) {
									//	$e.trigger({ type: 'questionSolved', values: { item: $this, index: $this.index(), solved: true } });
									//}
									$e.trigger( {
										type: 'questionSolvedIncorrect',
										values: {
											item: $this,
											index: $this.index(),
											solved: true,
											result,
										},
									} );

									$this.find( '.wpProQuiz_incorrect' ).show();
								}

								$this
									.find( '.wpProQuiz_responsePoints' )
									.text( result.p );

								// Hide the move icon in matrix sorting answer box after answer checking is successful.
								$this
									.find( '.wpProQuiz_sortStringItem_icon' )
									.hide();

								$this.data( 'check', true );
							}
						} );

						// Set a default just in case.
						//results.comp.p_nonce = '';

						//if ( typeof json['comp'] !== 'undefined' ) {
						//	if ( ( typeof json['comp']['p'] !== 'undefined' ) && ( typeof json['comp']['p_nonce'] !== 'undefined' ) ) {
						//		results.comp.points = json['comp']['p'];
						//		results.comp.p_nonce = json['comp']['p_nonce'];
						//	}
						//}

						if ( finishQuiz ) {
							plugin.methode.finishQuizEnd();
						}
					}
				);
			},
			markCorrectIncorrect(result, $question, $questionList) {
				if (typeof result.e.c === 'undefined') {
					return;
				}

				switch (result.e.type) {
					case 'single':
					case 'multiple':
						$questionList.children().each(function (i) {
							const $item = $(this);
							const index = $item.attr('data-pos');

							if (result.e.c[index]) {
								const checked = $(
									'input.wpProQuiz_questionInput',
									$item
								).is(':checked');
								if (checked) {
									plugin.methode.marker($item, true);
								} else {
									plugin.methode.marker(
										$item,
										'wpProQuiz_answerCorrectIncomplete'
									);
								}
							} else if (!result.c && result.e.r[index]) {
								plugin.methode.marker($item, false);
							}
						});
						break;
					case 'free_answer':
						var $li = $questionList.children();
						if (result.c) {
							plugin.methode.marker($li, true);
						} else {
							plugin.methode.marker($li, false);
						}
						if (
							typeof result.e.c !== 'undefined' &&
							typeof result.e.c !== 'undefined' &&
							result.e.c.length > 0
						) {
							autosizeInput($question.find('input[type="text"]'), 12);

							// If the answer is incorrect, show the correct answer message.
							if (!result.c) {
								// Escape the correct answer text to ensure that it is not interpreted as HTML.
								const escapedAnswers = result.e.c
									.map((answer) =>
										$('<div>').text(answer).html()
									)
									.join(', ');

								const message =
									WpProQuizGlobal.incorrectAnswer.replace(
										'@@LearnDash_Incorrect@@',
										escapedAnswers
									);

								$question
									.find('span.wpProQuiz_freeCorrect')
									.html(message)
									.show();
							}
						}
						break;

					case 'cloze_answer':
						$questionList
							.find('.wpProQuiz_cloze')
							.each(function (i, v) {
								const $this = $(this);
								const cloze = $this.children();
								const input = cloze.eq(0);
								const span = cloze.eq(1);
								const inputText =
									plugin.methode.cleanupCurlyQuotes(
										input.val()
									);

								if (result.s[i]) {
									//input.css('background-color', '#B0DAB0');
									$this.addClass('wpProQuiz_answerCorrect');

									// Add a new span next to the `input` element to show the correct answer icon.
									const correctAnswerIcon = $(
										'<span class="ld-quiz__cloze-icon ld-quiz__cloze-icon--correct dashicons"></span>'
									);
									input.after(correctAnswerIcon);

									span.show();
								} else {
									$this.addClass('wpProQuiz_answerIncorrect');

									// Add a new span next to the `input` element to show the incorrect answer icon.
									const incorrectAnswerIcon = $(
										'<span class="ld-quiz__cloze-icon ld-quiz__cloze-icon--incorrect dashicons"></span>'
									);
									input.after(incorrectAnswerIcon);

									if (typeof result.e.c[i] !== 'undefined') {
										span.html( '<span class="ld-quiz__cloze-results--correct-answer">(' + result.e.c[i].join() + ')</span>' );
										span.show();
									}
								}
								input.attr('disabled', 'disabled');
							});
						break;
					case 'sort_answer':
						var sortlist_container = jQuery(
							'div.wpProQuiz_questionList_containers',
							$question
						);
						if (
							typeof sortlist_container !== 'undefined' &&
							sortlist_container.length
						) {
							// LD 3.6.0: New logic to handle showing the "student" vs. "correct" answers.

							if (
								$questionList.hasClass(
									'ld-sortable--sort_answer'
								)
							) {
								learndash.forms.sortable.destroySortable(
									$questionList[0] ?? null
								);

								/**
								 * Improve accessibility of answered sortable list.
								 *
								 * This is destructive, and not easily reversible.
								 * Therefore, it is not a part of learndash.forms.sortable.destroySortable().
								 */
								if ($questionList[0]) {
									const $items =
										$questionList.find(
											'.ld-sortable__item'
										);

									$items.each(function(index, item) {
										const $handle = $(item).find(
											'.ld-sortable__item-handle'
										);

										$handle.removeAttr('tabindex');

										const attributes = {};
										$.each(
											$handle[0]
												? $handle[0].attributes
												: {},
											function () {
												attributes[this.nodeName] =
													this.nodeValue;
											}
										);

										// Convert the handle to a <div> so that a screen reader won't treat it as a button.
										const $nonButtonHandle = $(
											'<div />',
											attributes
										).append($handle.contents());

										$handle.replaceWith($nonButtonHandle);

										$(item).attr('tabindex', '0');
									});
								}
							}

							// Clone the student answert list in order to show the correct order.
							const $questionList_correct = $questionList.clone();
							$questionList_correct.insertAfter($questionList);
							$questionList_correct.addClass(
								'wpProQuiz_questionList_correct'
							);

							var $items = $questionList_correct.children(
								'.wpProQuiz_questionListItem'
							);
							var index = new Array();
							jQuery.each(result.e.c, function (i, v) {
								index[v] = i;
							});
							$items.sort(function (a, b) {
								return index[$(a).attr('data-pos')] >
									index[$(b).attr('data-pos')]
									? 1
									: -1;
							});
							$questionList_correct.append($items);
							$questionList_correct
								.children('.wpProQuiz_questionListItem')
								.addClass('wpProQuiz_answerCorrect');

							if (
								$questionList_correct.hasClass(
									'ld-sortable--sort_answer'
								)
							) {
								$questionList_correct
									.children('.ld-sortable__item')
									.addClass(
										'ld-sortable__item--correct-answer'
									);
							}

							// Show the correct/incorrect indicators on the student answers.
							jQuery.each(
								result.e.c,
								function (correct_item_idx, correct_item_key) {
									const student_item_el =
										$questionList.children(
											'.wpProQuiz_questionListItem'
										)[correct_item_idx];

									if (
										typeof student_item_el !==
											'undefined' &&
										student_item_el !== ''
									) {
										if (
											correct_item_key ==
											jQuery(student_item_el).data('pos')
										) {
											plugin.methode.marker(
												jQuery(student_item_el),
												true
											);

											if (
												$(student_item_el).hasClass(
													'ld-sortable__item'
												)
											) {
												$(student_item_el).addClass(
													'ld-sortable__item--correct'
												);
											}
										} else {
											plugin.methode.marker(
												jQuery(student_item_el),
												false
											);

											if (
												$(student_item_el).hasClass(
													'ld-sortable__item'
												)
											) {
												$(student_item_el).addClass(
													'ld-sortable__item--incorrect'
												);
											}
										}
									}
								}
							);

							jQuery(sortlist_container)
								.find(
									'.wpProQuiz_questionList_container_student'
								)
								.append($questionList);
							jQuery(sortlist_container)
								.find(
									'.wpProQuiz_questionList_container_correct'
								)
								.append($questionList_correct);

							jQuery(sortlist_container)
								.find(
									'.wpProQuiz_questionList_container_student'
								)
								.show();
							jQuery(sortlist_container)
								.find(
									'.wpProQuiz_questionList_container_correct'
								)
								.hide();

							jQuery(sortlist_container)
								.find(
									'input.wpProQuiz_questionList_containers_view_student'
								)
								.on('click', function (e) {
									jQuery(sortlist_container)
										.find(
											'input.wpProQuiz_questionList_containers_view_student'
										)
										.addClass(
											'wpProQuiz_questionList_containers_view_active'
										);
									jQuery(sortlist_container)
										.find(
											'input.wpProQuiz_questionList_containers_view_correct'
										)
										.removeClass(
											'wpProQuiz_questionList_containers_view_active'
										);

									jQuery(sortlist_container)
										.find(
											'.wpProQuiz_questionList_container_correct'
										)
										.hide();
									jQuery(sortlist_container)
										.find(
											'.wpProQuiz_questionList_container_student'
										)
										.show();
									e.preventDefault();
								});

							jQuery(sortlist_container)
								.find(
									'input.wpProQuiz_questionList_containers_view_correct'
								)
								.on('click', function (e) {
									jQuery(sortlist_container)
										.find(
											'input.wpProQuiz_questionList_containers_view_student'
										)
										.removeClass(
											'wpProQuiz_questionList_containers_view_active'
										);
									jQuery(sortlist_container)
										.find(
											'input.wpProQuiz_questionList_containers_view_correct'
										)
										.addClass(
											'wpProQuiz_questionList_containers_view_active'
										);

									jQuery(sortlist_container)
										.find(
											'.wpProQuiz_questionList_container_student'
										)
										.hide();
									jQuery(sortlist_container)
										.find(
											'.wpProQuiz_questionList_container_correct'
										)
										.show();
									e.preventDefault();
								});
							jQuery(sortlist_container).show();
						} else {
							// Legacy logic in case the new 'div.wpProQuiz_questionList_containers' element is not present.

							var $items = $questionList.children(
								'.wpProQuiz_questionListItem'
							);

							$items.each(function (i, v) {
								const $this = $(this);

								if (result.e.c[i] == $this.attr('data-pos')) {
									plugin.methode.marker($this, true);
								} else {
									plugin.methode.marker($this, false);
								}
							});

							$items
								.children()
								.css({ 'box-shadow': '0 0', cursor: 'auto' });

							var index = new Array();
							jQuery.each(result.e.c, function (i, v) {
								index[v] = i;
							});
							$items.sort(function (a, b) {
								return index[$(a).attr('data-pos')] >
									index[$(b).attr('data-pos')]
									? 1
									: -1;
							});

							$questionList.append($items);
						}

						break;
					case 'matrix_sort_answer':
						var $items = $questionList.children();
						var matrix = new Array();
						statistcAnswerData = { 0: -1 };

						$items.each(function () {
							const $this = $(this);
							const id = $this.attr('data-pos');
							const $stringUl = $this.find(
								'.wpProQuiz_maxtrixSortCriterion'
							);
							const $stringItem = $stringUl.children();
							const i = $stringItem.attr('data-pos');

							if (
								$stringItem.length &&
								result.e.c[i] == $this.attr('data-pos')
							) {
								plugin.methode.marker($this, true);
							} else {
								plugin.methode.marker($this, false);
							}

							matrix[i] = $stringUl;
						});

						plugin.methode.resetMatrix($question);

						$question
							.find('.wpProQuiz_sortStringItem')
							.each(function () {
								const x = matrix[$(this).attr('data-pos')];
								if (x != undefined) {
									x.append(this);
								}
							})
							.css({ 'box-shadow': '0 0', cursor: 'auto' });
						$question
							.find(
								'.wpProQuiz_sortStringList, .wpProQuiz_maxtrixSortCriterion'
							)
							.sortable();
						$question
							.find(
								'.wpProQuiz_sortStringList, .wpProQuiz_maxtrixSortCriterion'
							)
							.sortable('destroy');
						break;
				}
			},
			setCheckedStatusFromData(data, question, list) {
				if (config.ld_script_debug == true) {
					console.log('setCheckedStatusFromData data[%o]', data);
					console.log(
						'setCheckedStatusFromData question[%o]',
						question
					);
					console.log('setCheckedStatusFromData list[%o]', list);
				}
				const dataObjects = [quiz_resume_data, cookie_value];
				jQuery(dataObjects).each(function (index, dataObject) {
					if (dataObject.hasOwnProperty('checked' + data.id)) {
						for (const element in dataObject) {
							if (element.startsWith('checked')) {
								const elementId = element.replace(
									'checked',
									''
								);
								if (elementId == data.id) {
									questionResult = dataObject[element];
									plugin.methode.markCorrectIncorrect(
										questionResult,
										question,
										list
									);

									if (questionResult.c) {
										if (
											typeof questionResult.e
												.AnswerMessage !== 'undefined'
										) {
											question
												.find('.wpProQuiz_correct')
												.find(
													'.wpProQuiz_AnswerMessage'
												)
												.html(
													learndash_decodeHTML(
														questionResult.e
															.AnswerMessage
													)
												);
											question
												.find('.wpProQuiz_correct')
												.trigger(
													'learndash-quiz-answer-response-contentchanged'
												);
										}

										question
											.find('.wpProQuiz_response')
											.show();
										question
											.find('.wpProQuiz_correct')
											.show();
									} else {
										if (
											typeof questionResult.e
												.AnswerMessage !== 'undefined'
										) {
											question
												.find('.wpProQuiz_incorrect')
												.find(
													'.wpProQuiz_AnswerMessage'
												)
												.html(
													learndash_decodeHTML(
														questionResult.e
															.AnswerMessage
													)
												);
											question
												.find('.wpProQuiz_incorrect')
												.trigger(
													'learndash-quiz-answer-response-contentchanged'
												);
										}

										question
											.find('.wpProQuiz_response')
											.show();
										question
											.find('.wpProQuiz_incorrect')
											.show();
									}
									// question.find( '.wpProQuiz_responsePoints' ).text( questionResult.p );
								}
							}
						}
					}
				});
			},
			showTip() {
				const $this = $(this);
				const id = $this
					.siblings('.wpProQuiz_question')
					.find(globalNames.questionList)
					.data('question_id');

				$this.siblings('.wpProQuiz_tipp').toggle('fast');

				results[id].tip = 1;
				if (config.quiz_resume_enabled === '1') {
					plugin.methode.saveMetaDataToCookie('tip' + id, {
						question_id: id,
					});
				}

				$(document).on('mouseup.tipEvent', function (e) {
					const $tip = $e.find('.wpProQuiz_tipp');
					const $btn = $e.find('input[name="tip"]');

					if (
						!$tip.is(e.target) &&
						$tip.has(e.target).length == 0 &&
						!$btn.is(e.target)
					) {
						$tip.hide('fast');
						$(document).unbind('.tipEvent');
					}
				});
			},

			ajax(data, success, dataType) {
				dataType = dataType || 'json';

				if (bitOptions.cors) {
					jQuery.support.cors = true;
				}

				if (data.quiz === undefined) {
					data.quiz = config.quiz;
				}
				if (data.course_id === undefined) {
					data.course_id = config.course_id;
				}
				if (data.quiz_nonce === undefined) {
					data.quiz_nonce = config.quiz_nonce;
				}
				$.ajax({
					method: 'POST',
					type: 'POST',
					url: WpProQuizGlobal.ajaxurl,
					data,
					success,
					dataType,
				});

				if (bitOptions.cors) {
					jQuery.support.cors = false;
				}
			},

			checkQuizLock() {
				quizStatus.loadLock = 1;

				plugin.methode.ajax(
					{
						action: 'wp_pro_quiz_check_lock',
						quizId: config.quizId,
					},
					function (json) {
						if (json.lock != undefined) {
							quizStatus.isLocked = json.lock.is;

							/**
							 * Removed as part of LEARNDASH-4027
							 * The restart button does not need to be removed. It can be hidden
							 * via the Quiz setting.
							 */

							/*
						if(json.lock.pre) {
							$e.find('input[name="restartQuiz"]').hide();
						}
						*/
						}

						if (json.prerequisite != undefined) {
							quizStatus.isPrerequisite = 1;
							$e.find('.wpProQuiz_prerequisite span').text(
								json.prerequisite
							);
						}

						if (json.startUserLock != undefined) {
							quizStatus.isUserStartLocked = json.startUserLock;
						}

						quizStatus.loadLock = 0;

						if (quizStatus.isQuizStart) {
							plugin.methode.startQuiz();
						}
					}
				);
			},

			loadQuizData() {
				plugin.methode.ajax(
					{
						action: 'wp_pro_quiz_load_quiz_data',
						quizId: config.quizId,
						quiz_nonce: config.quiz_nonce,
					},
					function (json) {
						if (json.toplist) {
							plugin.methode.handleToplistData(json.toplist);
						}

						if (json.averageResult != undefined) {
							plugin.methode.setAverageResult(
								json.averageResult,
								true
							);
						}
					}
				);
			},

			setAverageResult(p, g) {
				const v = $e.find(
					'.wpProQuiz_resultValue:eq(' + (g ? 0 : 1) + ') > * '
				);

				v.eq(1).text(p + '%');
				v.eq(0).css('width', (240 * p) / 100 + 'px');
			},

			handleToplistData(json) {
				const $tp = $e.find('.wpProQuiz_addToplist');
				const $addBox = $tp
					.find('.wpProQuiz_addBox')
					.show()
					.children('div');

				if (json.canAdd) {
					$tp.show();
					$tp.find('.wpProQuiz_addToplistMessage').hide();
					$tp.find('.wpProQuiz_toplistButton').show();

					toplistData.token = json.token;
					toplistData.isUser = 0;

					if (json.userId) {
						$addBox.hide();
						toplistData.isUser = 1;

						if (bitOptions.isAddAutomatic) {
							$tp.hide();
						}
					} else {
						$addBox.show();

						const $captcha = $addBox.children().eq(1);

						if (json.captcha) {
							$captcha
								.find('input[name="wpProQuiz_captchaPrefix"]')
								.val(json.captcha.code);
							$captcha
								.find('.wpProQuiz_captchaImg')
								.attr('src', json.captcha.img);
							$captcha
								.find('input[name="wpProQuiz_captcha"]')
								.val('');

							$captcha.show();
						} else {
							$captcha.hide();
						}
					}
				} else {
					$tp.hide();
				}
			},

			scrollTo(e, h) {
				const x = e.offset().top - 100;

				if (h || (window.pageYOffset || document.body.scrollTop) > x) {
					$('html,body').clearQueue().animate({ scrollTop: x }, 300);
				}
			},

			addToplist() {
				if (bitOptions.preview) {
					return;
				}

				const $addToplistMessage = $e
					.find('.wpProQuiz_addToplistMessage')
					.text(WpProQuizGlobal.loadData)
					.show();
				const $addBox = $e.find('.wpProQuiz_addBox').hide();

				plugin.methode.ajax(
					{
						action: 'wp_pro_quiz_add_toplist',
						quizId: config.quizId,
						quiz: config.quiz,
						quiz_nonce: config.quiz_nonce,
						token: toplistData.token,
						name: $addBox
							.find('input[name="wpProQuiz_toplistName"]')
							.val(),
						email: $addBox
							.find('input[name="wpProQuiz_toplistEmail"]')
							.val(),
						captcha: $addBox
							.find('input[name="wpProQuiz_captcha"]')
							.val(),
						prefix: $addBox
							.find('input[name="wpProQuiz_captchaPrefix"]')
							.val(),
						//points: 99, //results.comp.points, // LD v2.4.3 Calculated on server
						results: JSON.stringify(results),
						//p_nonce: results.comp.p_nonce, // LD v2.4.3 Calculated on server
						//totalPoints:config.globalPoints, // LD v2.4.3 Calculated on server
						timespent,
					},
					function (json) {
						$addToplistMessage.text(json.text);

						if (json.clear) {
							$addBox.hide();
							plugin.methode.updateToplist();
						} else {
							$addBox.show();
						}

						if (json.captcha) {
							$addBox
								.find('.wpProQuiz_captchaImg')
								.attr('src', json.captcha.img);
							$addBox
								.find('input[name="wpProQuiz_captchaPrefix"]')
								.val(json.captcha.code);
							$addBox
								.find('input[name="wpProQuiz_captcha"]')
								.val('');
						}
					}
				);
			},

			updateToplist() {
				if (typeof wpProQuiz_fetchToplist === 'function') {
					wpProQuiz_fetchToplist();
				}
			},

			registerSolved() {
				// Free Input field
				$e.find('.wpProQuiz_questionInput[type="text"]').on(
					'change',
					function (e) {
						const $this = $(this);
						const $p = $this.parents('.wpProQuiz_listItem');
						let s = false;

						if ($this.val() != '') {
							s = true;
						}

						$e.trigger({
							type: 'questionSolved',
							values: { item: $p, index: $p.index(), solved: s },
						});
					}
				);

				// Single Choice field
				$e.find(
					'.wpProQuiz_questionList[data-type="single"] .wpProQuiz_questionInput, .wpProQuiz_questionList[data-type="assessment_answer"] .wpProQuiz_questionInput'
				).on('change', function (e) {
					const $this = $(this);
					const $p = $this.parents('.wpProQuiz_listItem');
					const s = this.checked;

					$e.trigger({
						type: 'questionSolved',
						values: { item: $p, index: $p.index(), solved: s },
					});
				});

				// Cloze field
				$e.find('.wpProQuiz_cloze input').on('change', function () {
					const $this = $(this);
					const $p = $this.parents('.wpProQuiz_listItem');
					let s = true;

					$p.find('.wpProQuiz_cloze input').each(function () {
						if ($(this).val() == '') {
							s = false;
							return false;
						}
					});

					$e.trigger({
						type: 'questionSolved',
						values: { item: $p, index: $p.index(), solved: s },
					});
				});

				// ?? field
				$e.find(
					'.wpProQuiz_questionList[data-type="multiple"] .wpProQuiz_questionInput'
				).on('change', function (e) {
					const $this = $(this);
					const $p = $this.parents('.wpProQuiz_listItem');
					let c = 0;

					$p.find(
						'.wpProQuiz_questionList[data-type="multiple"] .wpProQuiz_questionInput'
					).each(function (e) {
						if (this.checked) {
							c++;
						}
					});

					$e.trigger({
						type: 'questionSolved',
						values: {
							item: $p,
							index: $p.index(),
							solved: c ? true : false,
						},
					});
				});

				// Essay textarea field. For Essay file uploads look into uploadFile() function
				$e.find(
					'.wpProQuiz_questionList[data-type="essay"] textarea.wpProQuiz_questionEssay'
				).on('change', function (e) {
					const $this = $(this);
					const $p = $this.parents('.wpProQuiz_listItem');
					let s = false;

					if ($this.val() != '') {
						s = true;
					}
					$e.trigger({
						type: 'questionSolved',
						values: { item: $p, index: $p.index(), solved: s },
					});
				});
			},

			loadQuizDataAjax(quizStart) {
				plugin.methode.ajax(
					{
						action: 'wp_pro_quiz_admin_ajax_load_data',
						func: 'quizLoadData',
						data: {
							quizId: config.quizId,
							quiz: config.quiz,
							quiz_nonce: config.quiz_nonce,
						},
					},
					function (json) {
						config.globalPoints = json.globalPoints;
						config.catPoints = json.catPoints;
						config.json = json.json;

						globalElements.quiz.remove();

						$e.find('.wpProQuiz_quizAnker').after(json.content);

						$(
							'table.wpProQuiz_toplistTable caption span.wpProQuiz_max_points'
						).html(config.globalPoints);

						//Reinit globalElements
						globalElements = {
							back: $e.find('input[name="back"]'),
							next: $e.find(globalNames.next),
							quiz: $e.find('.wpProQuiz_quiz'),
							questionList: $e.find('.wpProQuiz_list'),
							results: $e.find('.wpProQuiz_results'),
							sending: $e.find('.wpProQuiz_sending'),
							quizStartPage: $e.find('.wpProQuiz_text'),
							timelimit: $e.find('.wpProQuiz_time_limit'),
							toplistShowInButton: $e.find(
								'.wpProQuiz_toplistShowInButton'
							),
							listItems: $(),
						};

						plugin.methode.initQuiz();

						if (quizStart) {
							plugin.methode.startQuiz(true);
						}

						// load script to show player for ajax content
						const data = json.content;
						const audiotag = data.search('wp-audio-shortcode');
						const videotag = data.search('wp-video-shortcode');
						if (audiotag != '-1' || videotag != '-1') {
							$.getScript(
								json.site_url +
									'/wp-includes/js/mediaelement/mediaelement-and-player.min.js'
							);
							$.getScript(
								json.site_url +
									'/wp-includes/js/mediaelement/wp-mediaelement.js'
							);
							$('<link/>', {
								rel: 'stylesheet',
								type: 'text/css',
								href:
									json.site_url +
									'/wp-includes/js/mediaelement/mediaelementplayer.min.css',
							}).appendTo('head');
						}
					}
				);
			},
			nextQuestionClicked() {
				const $questionList = currentQuestion.find(
					globalNames.questionList
				);
				const data = config.json[$questionList.data('question_id')];

				// Within the following logic. If the question type is 'sort_answer' there is a chance
				// the sortable answers will be displayed in the correct order. In that case the user will click
				// the next button.
				// The trigger to set the question was answered is normally a function of the sort/drag action
				// by the user. So we need to set the question answered flag in the case the Quiz summary is enabled.
				//if (data.type == 'sort_answer') {
				//	var question_index = currentQuestion.index();
				//	if ( typeof quizSolved[question_index] === 'undefined') {
				//		$e.trigger({type: 'questionSolved', values: {item: currentQuestion, index: question_index, solved: true}});
				//	}
				//}

				if (
					bitOptions.forcingQuestionSolve &&
					!quizSolved[currentQuestion.index()] &&
					(bitOptions.quizSummeryHide || !bitOptions.reviewQustion)
				) {
					// Would really like to do something more stylized instead of a simple alert popup. yuk!
					alert(WpProQuizGlobal.questionNotSolved);
					return false;
				}

				plugin.methode.nextQuestion();
			},
			initQuiz() {
				//if (config.ld_script_debug == true) {
				//	console.log('in initQuiz');
				//}

				//plugin.methode.setClozeStyle();
				plugin.methode.registerSolved();

				globalElements.next.on(
					'click',
					plugin.methode.nextQuestionClicked
				);

				globalElements.back.on('click', function (e) {
					//console.log('back button clicked [%o]', e);
					plugin.methode.prevQuestion();
				});

				$e.find(globalNames.check).on('click', function () {
					if (
						bitOptions.forcingQuestionSolve &&
						!quizSolved[currentQuestion.index()]
					) {
						alert(WpProQuizGlobal.questionNotSolved);
						return false;
					}
					plugin.methode.checkQuestion();
				});

				$e.find('input[name="checkSingle"]').on('click', function () {
					// First get all the questions...
					const list = globalElements.questionList.children();
					if (list != null) {
						list.each(function () {
							const $this = $(this);
							const $questionList = $this.find(
								globalNames.questionList
							);
							const question_id =
								$questionList.data('question_id');
							const data =
								config.json[$questionList.data('question_id')];

							// Within the following logic. If the question type is 'sort_answer' there is a chance
							// the sortable answers will be displayed in the correct order. In that case the user will click
							// the next button.
							// The trigger to set the question was answered is normally a function of the sort/drag action
							// by the user. So we need to set the question answered flag in the case the Quiz summary is enabled.
							//if (data.type == 'sort_answer') {
							//	//var question_index = $this.index();
							//	//if ( typeof quizSolved[question_index] === 'undefined') {
							//		$e.trigger({type: 'questionSolved', values: {item: $this, index: $this.index(), solved: true}});
							//	//}
							//}
						});
					}

					if (
						bitOptions.forcingQuestionSolve &&
						(bitOptions.quizSummeryHide ||
							!bitOptions.reviewQustion)
					) {
						for (
							let i = 0,
								c = $e.find('.wpProQuiz_listItem').length;
							i < c;
							i++
						) {
							if (!quizSolved[i]) {
								alert(WpProQuizGlobal.questionsNotSolved);
								return false;
							}
						}
					}

					plugin.methode.showQuizSummary();
				});

				$e.find('input[name="tip"]').on(
					'click',
					plugin.methode.showTip
				);
				$e.find('input[name="skip"]').on(
					'click',
					plugin.methode.skipQuestion
				);

				$e.find('input[name="wpProQuiz_pageLeft"]').on(
					'click',
					function () {
						plugin.methode.showSinglePage(currentPage - 1);
						plugin.methode.setupMatrixSortHeights();
					}
				);

				$e.find('input[name="wpProQuiz_pageRight"]').on(
					'click',
					function () {
						plugin.methode.showSinglePage(currentPage + 1);

						plugin.methode.setupMatrixSortHeights();
					}
				);

				$e.find('input[id^="uploadEssaySubmit"]').on(
					'click',
					plugin.methode.uploadFile
				);

				// Added in LD v2.4 to allow external notification when quiz init happens.
				$e.trigger('learndash-quiz-init');
			},
			// Setup the Cookie specific to the Quiz ID.
			CookieInit() {
				// Comment out to force clear cookie on init.
				// jQuery.cookie(cookie_name, '');

				if (config.ld_script_debug == true) {
					console.log('in CookieInit');
				}

				// Generate the localStorage key that includes user_id for user-specific storage.
				// This prevents data corruption when users impersonate other users.
				storage_name = quizStorage.getStorageKey(cookie_name);

				if (config.ld_script_debug == true) {
					console.log('storage_name:', storage_name);
					console.log('cookie_name:', cookie_name);
				}

				// Use localStorage with fallback to cookies for migration.
				cookie_value = quizStorage.getWithCookieFallback(
					storage_name,
					cookie_name
				);

				if (config.ld_script_debug == true) {
					console.log('after parse: cookie_value[%o]', cookie_value);
				}

				// If we have form entries at the start, save them to cookie.
				if (
					bitOptions.formActivated &&
					config.formPos === formPosConst.START &&
					formClass.checkForm()
				) {
					formClass.saveFormDataToCookie();
				}

				plugin.methode.CookieSetResponses();
				plugin.methode.CookieResponseTimer();
			},
			CookieDelete() {
				//if (config.ld_script_debug == true) {
				//	console.log('CookieDelete: config.timelimitcookie[%o]', config.timelimitcookie);
				//}
				//if (config.ld_script_debug == true) {
				//	console.log('CookieDelete: cookie_name[%o]', cookie_name);
				//}
				// Delete from localStorage (new storage).
				quizStorage.remove(storage_name);
				// Also clear cookie for cleanup during migration.
				jQuery.cookie(cookie_name, '');
			},
			CookieProcessQuestionResponse(list, lockQuestion) {
				var lockQuestion = lockQuestion || false;
				if (list != null) {
					list.each(function () {
						const $this = $(this);

						const question_index = $this.index();
						const $questionList = $this.find(
							globalNames.questionList
						);
						const question_id = $questionList.data('question_id');
						const data =
							config.json[$questionList.data('question_id')];
						let name = data.type;
						if (data.type == 'single' || data.type == 'multiple') {
							name = 'singleMulti';
						}

						const question_response = readResponses(
							name,
							data,
							$this,
							$questionList,
							false
						);

						plugin.methode.saveMetaDataToCookie({
							lastQuestion: question_id,
						});
						plugin.methode.CookieSaveResponse(
							question_id,
							question_index,
							data.type,
							question_response,
							lockQuestion
						);
					});
				}
			},
			// Save the answer(response) to localStorage. This is called from 'checkQuestion' and cookie timer functions.
			CookieSaveResponse(
				question_id,
				question_index,
				question_type,
				question_response,
				lockQuestion
			) {
				var lockQuestion = lockQuestion || false;
				// set the value
				if (question_id === 'formData' + question_index) {
					cookie_value[question_id] = {
						index: question_index,
						value: question_response,
						type: question_type,
					};
				} else {
					cookie_value[question_id] = {
						index: question_index,
						value: question_response.response,
						type: question_type,
						lockQuestion,
					};
				}

				// Calculate the cookie date to expire (still used for server-side sync)
				plugin.methode.calculateCookieExpiry();

				// Store the values in localStorage (preferred) with cookie fallback.
				if (!quizStorage.set(storage_name, cookie_value)) {
					// Fallback to cookie if localStorage fails (e.g., quota exceeded)
					jQuery.cookie(cookie_name, JSON.stringify(cookie_value), {
						expires: cookieExpireDate,
					});
				}

				if (config.quiz_resume_enabled === '1') {
					plugin.methode.startCookieSendTimer();
				}
			},
			calculateCookieExpiry() {
				cookieExpireDate = new Date();
				if (
					config.timelimitcookie &&
					!config.quiz_resume_cookie_expiration
				) {
					cookieExpireDate.setTime(
						cookieExpireDate.getTime() +
							config.timelimitcookie * 1000
					);
				}

				if (
					config.quiz_resume_cookie_expiration &&
					!config.timelimitcookie
				) {
					cookieExpireDate.setTime(
						cookieExpireDate.getTime() +
							config.quiz_resume_cookie_expiration * 1000
					);
				}
			},
			saveMetaDataToCookie(id, metadata) {
				var metadata = metadata || false;
				// Calculate the cookie date to expire.
				plugin.methode.calculateCookieExpiry();

				if (!cookie_value || cookie_value == undefined) {
					cookie_value = {};
				}

				if (typeof id === 'string' && typeof metadata === 'object') {
					cookie_value[id] = {};
					for (var element in metadata) {
						// @todo Test in IE11
						const obj = { [element]: metadata[element] };
						Object.assign(cookie_value[id], obj);
					}
				} else if (typeof id === 'string' && !metadata) {
					cookie_value[id] = true;
				} else if (typeof id === 'object') {
					for (var element in id) {
						cookie_value[element] = id[element];
					}
				}

				// Store the values in localStorage (preferred) with cookie fallback.
				if (!quizStorage.set(storage_name, cookie_value)) {
					// Fallback to cookie if localStorage fails (e.g., quota exceeded)
					jQuery.cookie(cookie_name, JSON.stringify(cookie_value), {
						expires: cookieExpireDate,
					});
				}

				if (config.quiz_resume_enabled === '1' && !cookieSendTimer) {
					plugin.methode.startCookieSendTimer();
				}
			},
			// The cookie timer loops every 5 seconds to save the last response from the user.
			// This only effect Essay questions as there is some current logic where once
			// 'readResponses' is called the question is locked.
			CookieResponseTimer() {
				// Hook into the 'questionSolved' triggered event. This is much better than
				// a timer to grab the answer values. With the event trigger we only process the
				// single question when the user makes a change.
				$e.on('questionSolved', function (e) {
					//if (config.ld_script_debug == true) {
					//	console.log('CookieResponseTimer: e.values[%o]', e.values);
					//}
					if (
						config.timelimitcookie !== 0 ||
						config.quiz_resume_enabled === '1'
					) {
						plugin.methode.CookieProcessQuestionResponse(
							e.values.item
						);
					}
				});
			},
			// Load the Cookie (if present) and sets the values of the Quiz questions to the cookie saved value
			CookieSetResponses() {
				if (config.ld_script_debug == true) {
					console.log('In CookieSetResponses');
				}

				if (
					(cookie_value == undefined ||
						!Object.keys(cookie_value).length) &&
					!config.quiz_resume_id
				) {
					// console.log( 'returning' );
					return;
				}

				const list = globalElements.questionList.children();
				list.each(function () {
					const $this = $(this);

					const $questionList = $this.find(globalNames.questionList);
					const form_question_id = $questionList.data('question_id');

					// Handle server-side data
					if (quiz_resume_data[form_question_id] != undefined) {
						const quiz_resume_question_data =
							quiz_resume_data[form_question_id];
						var form_question_data =
							config.json[$questionList.data('question_id')];

						if (
							form_question_data.type ===
							quiz_resume_question_data.type
						) {
							setResponse(
								form_question_data,
								quiz_resume_question_data.value,
								$this,
								$questionList,
								quiz_resume_question_data.lockQuestion
							);
						}
					}
					// Handle cookie data
					if (cookie_value[form_question_id] != undefined) {
						const cookie_question_data =
							cookie_value[form_question_id];

						var form_question_data =
							config.json[$questionList.data('question_id')];
						if (
							form_question_data.type ===
							cookie_question_data.type
						) {
							setResponse(
								form_question_data,
								cookie_question_data.value,
								$this,
								$questionList,
								cookie_question_data.lockQuestion
							);
						}
					}

					// Move to next unanswered question
					if (
						typeof quiz_resume_data !== 'undefined' &&
						typeof cookie_value !== 'undefined'
					) {
						if (config.ld_script_debug == true) {
							console.log(
								"CookieSet: cookie_value['nextQuestion'][%o]",
								cookie_value.nextQuestion
							);
							console.log(
								'CookieSet: quiz_resume_data[%o]',
								quiz_resume_data
							);
						}
						if (
							typeof cookie_value.nextQuestion !== 'undefined' &&
							cookie_value.nextQuestion
						) {
							//console.log('cookie_value[%o]', cookie_value);
							plugin.methode.moveToNextUnansweredQuestion(
								cookie_value
							);
						} else {
							//console.log('quiz_resume_data[%o]', quiz_resume_data);
							plugin.methode.moveToNextUnansweredQuestion(
								quiz_resume_data
							);
						}
					}
				});
			},
			startCookieSendTimer() {
				// Immediately send the first cookie entry to the server
				// to trigger the Continue Quiz logic when no server-side
				// data exists.

				// If timer is already running, don't start another one.
				if (cookieSendTimer) {
					return;
				}

				// Set flag before starting timer.
				cookieSendTimer = true;

				/**
				 * Quiz Resume Data is an empty object when cleared,
				 * but an empty Array when not set in the DB and not set in localStorage.
				 */
				if (
					undefined === quiz_resume_data ||
					quiz_resume_data.length === 0
				) {
					/**
					 * Use setTimeout to defer the initial send until after the current
					 * JavaScript execution completes. This ensures all synchronous
					 * questionSolved event handlers have finished adding their data
					 * to cookie_value before we send the AJAX request.
					 * Without this, a race condition occurs where reviewBox data is
					 * sent before the question response data is added.
					 */
					setTimeout(function () {
						plugin.methode.prepareSendCookieData();
					}, 0);
				}

				// Start the Timer
				setInterval(function () {
					if (!cookieSending) {
						plugin.methode.prepareSendCookieData();
					}
				}, config.quiz_resume_cookie_send_timer * 1000);
			},
			prepareSendCookieData() {
				// Get Timelimit cookie data.
				if (config.timelimit) {
					plugin.methode.addTimelimitCookieData();
				}

				const cookieLength =
					plugin.methode.getObjectLength(cookie_value);
				if (cookieLength > 0) {
					const cookieKeys =
						plugin.methode.getObjectKeys(cookie_value);

					// Flag we are sending an ajax request.
					cookieSending = true;
					plugin.methode.sendCookieData(cookieKeys);
				}
			},
			getObjectLength(object) {
				return Object.keys(object).length;
			},
			getObjectKeys(object) {
				return Object.keys(object);
			},
			excludeKeysFromCount(object, exclude) {
				const allKeys = plugin.methode.getObjectKeys(object);
				let count = 0;
				allKeys.forEach(function (key) {
					if (!key.startsWith(exclude)) {
						count++;
					}
				});
				return count;
			},
			addTimelimitCookieData() {
				const quizTimelimitCookie = jQuery.cookie(
					timelimit.timer_cookie
				);
				if ('undefined' !== typeof quizTimelimitCookie) {
					plugin.methode.saveMetaDataToCookie({
						[timelimit.timer_cookie]:
							JSON.parse(quizTimelimitCookie),
					});
				}
			},
			moveToNextUnansweredQuestion(data) {
				if (config.ld_script_debug == true) {
					console.log('moveToNextUnansweredQuestion: data[%o]', data);
				}

				if (typeof data !== 'undefined') {
					let nextQuestion =
						typeof data.nextQuestion === 'number'
							? data.nextQuestion
							: 0;
					const lastQuestion =
						typeof data.lastQuestion === 'number'
							? data.lastQuestion
							: 0;

					if (config.ld_script_debug == true) {
						console.log(
							'moveToNextUnansweredQuestion: nextQuestion[%o]',
							nextQuestion
						);
						console.log(
							'moveToNextUnansweredQuestion: lastQuestion[%o]',
							lastQuestion
						);
						console.log(
							'moveToNextUnansweredQuestion: config.mode[%o]',
							config.mode
						);
					}

					if (config.mode === 3) {
						if (!config.qpp) {
							jQuery(globalElements.questionList)
								.children()
								.show();
							jQuery(globalElements.listItems).each(function (
								index,
								listItem
							) {
								if (
									$(listItem).data('question-meta')
										.question_pro_id === lastQuestion
								) {
									plugin.methode.scrollTo(
										globalElements.listItems.eq(index),
										1
									);
								}
							});
						} else {
							jQuery(globalElements.listItems).each(function (
								index,
								listItem
							) {
								if (
									$(listItem).data('question-meta')
										.question_pro_id === lastQuestion
								) {
									plugin.methode.singlePageNavigationQuizResume(
										index
									);
								}
							});
						}
					} else {
						if (config.mode == 1 && nextQuestion > 0) {
							nextQuestion = lastQuestion;
						} else if (nextQuestion == 0 && lastQuestion > 0) {
							nextQuestion = lastQuestion;
						}

						let resumed = false;

						jQuery(globalElements.listItems).each(function (
							index,
							listItem
						) {
							if (
								$(listItem).data('question-meta')
									.question_pro_id === nextQuestion
							) {
								if (config.ld_script_debug == true) {
									console.log(
										'moveToNextUnansweredQuestion: match: listItem[%o]',
										listItem
									);
								}

								currentQuestion =
									globalElements.listItems.eq(index);

								const questionId = currentQuestion
									.find(globalNames.questionList)
									.data('question_id');

								resumed = true;

								questionTimer.questionStart(questionId);
								plugin.methode.showQuestionObject(
									currentQuestion
								);

								return false; // break out of the loop.
							}
							// if (config.ld_script_debug == true) {
							// 	console.log("moveToNextUnansweredQuestion: not match: listItem[%o]", listItem);
							// }
						});

						/**
						 * The saved question is not in the rendered set (e.g. a different
						 * random subset on resume); fall back to the first question so the
						 * quiz never resumes with a null current question.
						 */
						if (!resumed) {
							currentQuestion = globalElements.listItems.eq(0);
							plugin.methode.showQuestionObject(currentQuestion);
						}
					}
				}
			},
			// Variation of showSinglePage()
			singlePageNavigationQuizResume(index) {
				const answeredQuestions = config.qpp >= 1 ? index : index + 1;
				const maxPage = Math.ceil(
					$e.find('.wpProQuiz_list > li').length / config.qpp
				);
				const activePage = Math.ceil(answeredQuestions / config.qpp);

				if (activePage <= maxPage) {
					const $listItem = globalElements.questionList
						.children()
						.hide();
					const pl = $e.find(globalNames.singlePageLeft).hide();
					const pr = $e.find(globalNames.singlePageRight).hide();
					const cs = $e.find('input[name="checkSingle"]').hide();

					if (activePage > 1) {
						pl.val(
							pl.data('text').replace(/%d/, activePage - 1)
						).show();
					}

					if (activePage === maxPage) {
						cs.show();
					} else {
						pr.val(
							pr.data('text').replace(/%d/, activePage + 1)
						).show();
					}

					currentPage = activePage;

					const start = config.qpp * (activePage - 1);

					$listItem.slice(start, start + config.qpp).show();
					if (globalElements.listItems.length === index) {
						index = index - 1;
					}
					plugin.methode.scrollTo(
						globalElements.listItems.eq(index),
						0
					);
				}
			},
			sendCookieData(keys) {
				jQuery.ajax({
					type: 'POST',
					url: WpProQuizGlobal.ajaxurl,
					dataType: 'json',
					cache: false,
					data: {
						action: 'wp_pro_quiz_cookie_save_quiz',
						course_id: config.course_id,
						quiz: config.quiz,
						quizId: config.quizId,
						quiz_started: questionTimer.getQuizStart(),
						results: JSON.stringify(cookie_value),
						quiz_nonce: config.quiz_nonce,
					},
					success(response) {
						if (response.success) {
							if (
								plugin.methode.compareObjectKeys(
									response.data,
									cookie_value
								)
							) {
								plugin.methode.deleteCookieKeys(keys);
							}
						} else if ( config.ld_script_debug == true ) {
							// Log validation errors for debugging.
							console.error(
								'Quiz resume save failed:',
								response.data?.message || 'Unknown error'
							);
						}

						// Always reset the sending flag to allow retries.
						cookieSending = false;
					},
					error(xhr) {
						console.log('xhr[%o]', xhr);
						const response = JSON.parse(xhr.responseText);
						console.log(response.data.message);
						cookieSending = false;
					},
				});
			},
			compareObjectKeys(a, b) {
				const keysObjectA = Object.keys(a).sort();
				const keysObjectB = Object.keys(b).sort();
				return (
					JSON.stringify(keysObjectA) === JSON.stringify(keysObjectB)
				);
			},
			deleteCookieKeys(keys) {
				// Remove saved keys from cookie object
				keys.forEach(function (key) {
					delete cookie_value[key];
				});

				// Write changed cookie object to localStorage (preferred) with cookie fallback.
				if (!quizStorage.set(storage_name, cookie_value)) {
					// Fallback to cookie if localStorage fails
					jQuery.cookie(cookie_name, JSON.stringify(cookie_value), {
						expires: cookieExpireDate,
					});
				}
			},
			setupMatrixSortHeights() {
				/**
				 * Here we have to do all the questions because the current logic when using X questions
				 * per page doesn't allow that information.
				 */
				$('li.wpProQuiz_listItem', globalElements.questionList).each(
					function (idx, questionItem) {
						const question_type = $(questionItem).data('type');
						if ('matrix_sort_answer' === question_type) {
							// On the draggable items get the items max height and set the parent ul to that.
							let sortitems_height = 0;
							$(
								'ul.wpProQuiz_sortStringList li',
								questionItem
							).each(function (idx, el) {
								const el_height = $(el).outerHeight();
								if (el_height > sortitems_height) {
									sortitems_height = el_height;
								}
							});
							if (sortitems_height > 0) {
								$(
									'ul.wpProQuiz_sortStringList',
									questionItem
								).css('min-height', sortitems_height);
							}

							$(
								'ul.wpProQuiz_maxtrixSortCriterion',
								questionItem
							).each(function (idx, el) {
								const parent_td = $(el).parent('td');
								if (typeof parent_td !== 'undefined') {
									const parent_td_height =
										$(parent_td).height();
									if (parent_td_height) {
										$(el).css('height', parent_td_height);
										$(el).css(
											'min-height',
											parent_td_height
										);
									}
								}
							});
						}
					}
				);
			},
		};

		/**
		 * @memberOf plugin
		 */
		plugin.preInit = function () {
			plugin.methode.parseBitOptions();
			reviewBox.init();

			$e.find('input[name="startQuiz"]').on('click', function () {
				plugin.methode.startQuiz();
				return false;
			});

			if (bitOptions.checkBeforeStart && !bitOptions.preview) {
				plugin.methode.checkQuizLock();
			}

			$e.find('input[name="reShowQuestion"]').on('click', function () {
				plugin.methode.showQustionList();
			});

			$e.find('input[name="restartQuiz"]').on('click', function () {
				plugin.methode.restartQuiz();
			});

			$e.find('input[name="review"]').on(
				'click',
				plugin.methode.reviewQuestion
			);

			$e.find('input[name="wpProQuiz_toplistAdd"]').on(
				'click',
				plugin.methode.addToplist
			);

			$e.find('input[name="quizSummary"]').on(
				'click',
				plugin.methode.showQuizSummary
			);

			$e.find('input[name="endQuizSummary"]').on('click', function () {
				if (bitOptions.forcingQuestionSolve) {
					// First get all the questions...
					list = globalElements.questionList.children();
					if (list != null) {
						list.each(function () {
							const $this = $(this);
							const $questionList = $this.find(
								globalNames.questionList
							);
							const question_id =
								$questionList.data('question_id');
							const data =
								config.json[$questionList.data('question_id')];

							// Within the following logic. If the question type is 'sort_answer' there is a chance
							// the sortable answers will be displayed in the correct order. In that case the user will click
							// the next button.
							// The trigger to set the question was answered is normally a function of the sort/drag action
							// by the user. So we need to set the question answered flag in the case the Quiz summary is enabled.
							//if (data.type == 'sort_answer') {
							//	var question_index = $this.index();
							//	if ( typeof quizSolved[question_index] === 'undefined') {
							//		$e.trigger({type: 'questionSolved', values: {item: $this, index: question_index, solved: true}});
							//	}
							//}
						});
					}

					for (
						let i = 0, c = $e.find('.wpProQuiz_listItem').length;
						i < c;
						i++
					) {
						if (!quizSolved[i]) {
							alert(WpProQuizGlobal.questionsNotSolved);
							return false;
						}
					}
				}

				if (
					bitOptions.formActivated &&
					config.formPos == formPosConst.END &&
					!formClass.checkForm()
				) {
					return;
				}

				plugin.methode.finishQuiz();
			});

			$e.find('input[name="endInfopage"]').on('click', function () {
				if (formClass.checkForm()) {
					plugin.methode.finishQuiz();
				}
			});

			$e.find('input[name="showToplist"]').on('click', function () {
				globalElements.quiz.hide();
				globalElements.toplistShowInButton.toggle();
			});

			$e.on('questionSolved', plugin.methode.questionSolved);

			if (!bitOptions.maxShowQuestion) {
				plugin.methode.initQuiz();
			}

			if (bitOptions.autoStart) {
				plugin.methode.startQuiz();
			}

			// If the form is shown before the quiz, we need to check if we have saved data.
			if (
				bitOptions.formActivated &&
				config.formPos === formPosConst.START
			) {
				formClass.setFormDataFromServer();
			}
		};

		plugin.preInit();
	};

	$.fn.wpProQuizFront = function (options) {
		return this.each(function () {
			if (undefined == $(this).data('wpProQuizFront')) {
				$(this).data(
					'wpProQuizFront',
					new $.wpProQuizFront(this, options)
				);
			}
		});
	};

	/**
	 * Autosize the input field.
	 *
	 * @since 4.21.4
	 *
	 * @param {jQuery} $input       - The input field to autosize.
	 * @param {number} [$padding=0] - The padding to add to the input field.
	 */
	function autosizeInput($input, $padding = 0) {
		const $sizer = $('<span>').css({
			position: 'absolute',
			visibility: 'hidden',
			whiteSpace: 'pre',
			font: $input.css('font'),
		}).appendTo('body');

		function update() {
			$sizer.text($input.val() || $input.attr('placeholder') || '');
			$input.width($sizer.width() + 6 + $padding);
		}

		$input.on('input', update);
		update();
	}
})(jQuery);

var learndash_prepare_quiz_resume_data = function (config) {
	if (config.quiz_resume_enabled === 'undefined') {
		return false;
	}
	if (config.quiz_resume_enabled !== '1') {
		return false;
	}

	if (config.quiz_resume_data === 'undefined') {
		return false;
	}

	let resume_data = {};
	if (config.ld_script_debug == true) {
		console.log(
			'config.quiz_resume_data (raw)[%o]',
			config.quiz_resume_data
		);
	}
	try {
		resume_data = JSON.parse(config.quiz_resume_data);
		if (config.ld_script_debug == true) {
			console.log('resume_data (parsed)[%o]', resume_data);
		}
	} catch (exception) {
		console.log('JSON.parse error [%o]', exception);
	}

	return resume_data;
};

var learndash_decodeHTML = function (html) {
	html = html || '';

	if (html.length > 0 && typeof html === 'string') {
		const txt = document.createElement('textarea');
		txt.innerHTML = html;

		html = txt.value;
	}

	return html;
};
