/**
 * MasterQuiz AI - Frontend JavaScript
 *
 * Handles: file upload (drag & drop + click), quiz generation via WP AJAX,
 * job status polling, quiz result display with navigation, export, and
 * LearnDash quiz creation.
 *
 * Depends on `masterquizConfig` localized by MasterQuiz_Shortcode::render().
 *
 * @package MasterQuiz
 * @since   1.0.0
 */

/* global masterquizConfig */

( function () {
	'use strict';

	/* =========================================================
	   Guard – config must be present
	   ========================================================= */
	if ( typeof masterquizConfig === 'undefined' ) {
		return;
	}

	var cfg  = masterquizConfig;
	var i18n = cfg.i18n || {};

	/* =========================================================
	   State
	   ========================================================= */
	var selectedFile      = null;
	var currentJobId      = null;
	var quizData          = null;
	var allQuestions       = [];
	var currentQIndex     = 0;
	var generationRunning = false;
	var pollTimer         = null;
	var fileUploaded      = false;
	var uploadInProgress  = false;
	var uploadPromise     = null;
	var savePromise       = null;
	var saveGeneration    = 0;
	var ownerToken        = '';
	var editorLocked      = false;

	/* =========================================================
	   DOM references (resolved once on init)
	   ========================================================= */
	var el = {};

	function resolveElements() {
		el.wrapper       = document.getElementById( 'masterquiz-wrapper' );
		el.dropzone      = document.getElementById( 'masterquiz-dropzone' );
		el.dropContent   = document.getElementById( 'masterquiz-dropzone-content' );
		el.fileInput     = document.getElementById( 'masterquiz-file-input' );
		el.fileInfo      = document.getElementById( 'masterquiz-file-info' );
		el.fileName      = document.getElementById( 'masterquiz-file-name' );
		el.fileSize      = document.getElementById( 'masterquiz-file-size' );
		el.removeFile    = document.getElementById( 'masterquiz-remove-file' );
		el.numQuestions   = document.getElementById( 'masterquiz-num-questions' );
		el.quizMode      = document.getElementById( 'masterquiz-quiz-mode' );
		el.generateBtn   = document.getElementById( 'masterquiz-generate-btn' );
		el.progress      = document.getElementById( 'masterquiz-progress' );
		el.progressBar   = document.getElementById( 'masterquiz-progress-bar' );
		el.progressText  = document.getElementById( 'masterquiz-progress-text' );
		el.message       = document.getElementById( 'masterquiz-message' );
		el.results       = document.getElementById( 'masterquiz-results' );
		el.questionsWrap = document.getElementById( 'masterquiz-questions-container' );
		el.navigation    = document.getElementById( 'masterquiz-navigation' );
		el.prevBtn       = document.getElementById( 'masterquiz-prev-btn' );
		el.nextBtn       = document.getElementById( 'masterquiz-next-btn' );
		el.counter       = document.getElementById( 'masterquiz-question-counter' );
		el.quizTitle     = document.getElementById( 'masterquiz-quiz-title' );
		el.saveQuizBtn   = document.getElementById( 'masterquiz-save-quiz-btn' );
		el.saveStatus    = document.getElementById( 'masterquiz-save-status' );
		el.exportBtn     = document.getElementById( 'masterquiz-export-btn' );
		el.exportModal   = document.getElementById( 'masterquiz-export-modal' );
		el.exportClose   = document.getElementById( 'masterquiz-export-close' );
		el.createLdBtn   = document.getElementById( 'masterquiz-create-ld-btn' );
		el.ldResult      = document.getElementById( 'masterquiz-learndash-result' );
		el.newQuizBtn    = document.getElementById( 'masterquiz-new-quiz-btn' );
	}

	/* =========================================================
	   Utility helpers
	   ========================================================= */

	/**
	 * Escape HTML entities to prevent XSS in dynamic content.
	 */
	function escapeHtml( text ) {
		var d       = document.createElement( 'div' );
		d.textContent = String( text || '' );
		return d.innerHTML;
	}

	/**
	 * Validate a File object: must be PDF within max size.
	 */
	function validateFile( file ) {
		if ( ! file || typeof file.name !== 'string' ) {
			return { ok: false, error: i18n.invalidFileType || 'Please select a PDF file.' };
		}
		var name  = file.name.toLowerCase();
		var type  = ( file.type || '' ).toLowerCase();
		var isPdf = ( type === 'application/pdf' || type === 'application/x-pdf' || name.slice( -4 ) === '.pdf' );
		if ( ! isPdf ) {
			return { ok: false, error: i18n.invalidFileType || 'Please select a PDF file.' };
		}
		if ( file.size > cfg.maxFileSize ) {
			var mb = Math.round( cfg.maxFileSize / 1024 / 1024 );
			return { ok: false, error: ( i18n.fileTooLarge || 'File is too large. Maximum size is' ) + ' ' + mb + ' MB.' };
		}
		return { ok: true };
	}

	/**
	 * Show an error / success message.
	 */
	function showMessage( text, type ) {
		if ( ! el.message ) {
			return;
		}
		el.message.textContent = text;
		el.message.className   = 'masterquiz-message visible ' + ( type || 'error' );
	}

	function hideMessage() {
		if ( el.message ) {
			el.message.className = 'masterquiz-message';
			el.message.textContent = '';
		}
	}

	/**
	 * Clamp question count to the allowed range (3-5).
	 * Values below 3 become 3; values above 5 become 5.
	 *
	 * @param {*} value Raw input value.
	 * @return {number} Clamped question count.
	 */
	function clampNumQuestions( value ) {
		var minQ = cfg.minQuestions || 3;
		var maxQ = Math.min( 5, cfg.maxQuestions || 5 );
		var num  = parseInt( value, 10 );

		if ( isNaN( num ) ) {
			num = minQ;
		}

		return Math.max( minQ, Math.min( maxQ, num ) );
	}

	/**
	 * Keep the number input inside 3-5.
	 * Above the maximum snaps immediately; below the minimum is corrected once
	 * the value is committed so mid-typing digits are not overwritten.
	 *
	 * @param {boolean} commit Whether the value is final (change/blur).
	 */
	function bindNumQuestionsClamp() {
		if ( ! el.numQuestions ) {
			return;
		}

		var minQ = cfg.minQuestions || 3;
		var maxQ = Math.min( 5, cfg.maxQuestions || 5 );

		function snapValue( commit ) {
			var raw = el.numQuestions.value;

			if ( raw === '' || raw === '-' || raw === '+' ) {
				return;
			}

			var num = parseInt( raw, 10 );

			if ( isNaN( num ) ) {
				return;
			}

			var clamped = num;

			if ( num > maxQ ) {
				clamped = maxQ;
			} else if ( commit && num < minQ ) {
				clamped = minQ;
			}

			if ( String( clamped ) !== String( raw ) ) {
				el.numQuestions.value = String( clamped );
			}
		}

		el.numQuestions.addEventListener( 'input', function () {
			snapValue( false );
		} );
		el.numQuestions.addEventListener( 'change', function () {
			snapValue( true );
		} );
		el.numQuestions.addEventListener( 'blur', function () {
			if ( el.numQuestions.value === '' || isNaN( parseInt( el.numQuestions.value, 10 ) ) ) {
				el.numQuestions.value = String( clampNumQuestions( cfg.defaultQuestions || maxQ ) );
				return;
			}
			snapValue( true );
		} );
		el.numQuestions.addEventListener( 'paste', function () {
			window.setTimeout( function () {
				snapValue( true );
			}, 0 );
		} );
		snapValue( true );
	}

	/**
	 * Update progress bar + text.
	 */
	function setProgress( percent, text ) {
		if ( el.progressBar ) {
			el.progressBar.style.width = Math.min( 100, Math.max( 0, percent ) ) + '%';
		}
		if ( el.progressText && text ) {
			el.progressText.textContent = text;
		}
	}

	function showProgress() {
		if ( el.progress ) {
			el.progress.className = 'masterquiz-progress visible';
		}
	}

	function hideProgress() {
		if ( el.progress ) {
			el.progress.className = 'masterquiz-progress';
		}
		setProgress( 0, '' );
	}

	/**
	 * Toggle the Generate button state.
	 */
	function setGenerateEnabled( enabled ) {
		if ( el.generateBtn ) {
			el.generateBtn.disabled  = ! enabled;
			el.generateBtn.textContent = enabled
				? ( i18n.generateQuiz || 'Generate Quiz' )
				: ( i18n.generating_btn || 'Generating...' );
		}
	}

	/**
	 * Perform a WP-AJAX request.
	 *
	 * @param {string}   action   The `action` parameter for admin-ajax.php.
	 * @param {FormData} formData Form data payload.
	 * @return {Promise<Object>}  Parsed JSON response.
	 */
	function wpAjax( action, formData ) {
		if ( ! ( formData instanceof FormData ) ) {
			formData = new FormData();
		}
		formData.append( 'action', action );
		formData.append( 'nonce', cfg.nonce );

		return fetch( cfg.ajaxUrl, {
			method: 'POST',
			credentials: 'same-origin',
			body: formData,
		} )
			.then( function ( res ) {
				if ( ! res.ok ) {
					throw new Error( 'Server responded with ' + res.status );
				}
				return res.json();
			} )
			.then( function ( json ) {
				if ( json.success === false ) {
					throw new Error( ( json.data && json.data.message ) || 'Request failed.' );
				}
				return json.data || json;
			} );
	}

	/* =========================================================
	   File selection / Dropzone
	   ========================================================= */

	/**
	 * Format file size in human-readable units.
	 */
	function formatFileSize( bytes ) {
		if ( bytes < 1024 ) {
			return bytes + ' B';
		}
		if ( bytes < 1024 * 1024 ) {
			return ( bytes / 1024 ).toFixed( 1 ) + ' KB';
		}
		return ( bytes / 1024 / 1024 ).toFixed( 2 ) + ' MB';
	}


	/**
	 * Upload PDF as soon as the user selects it so Generate can start immediately.
	 */
	function uploadFileInBackground( file ) {
		if ( uploadInProgress && uploadPromise ) {
			return uploadPromise;
		}
		uploadInProgress = true;
		fileUploaded     = false;

		var uploadData = new FormData();
		uploadData.append( 'pdf_file', file );

		uploadPromise = wpAjax( 'masterquiz_upload', uploadData )
			.then( function () {
				fileUploaded     = true;
				uploadInProgress = false;
			} )
			.catch( function ( err ) {
				fileUploaded     = false;
				uploadInProgress = false;
				uploadPromise    = null;
				throw err;
			} );

		return uploadPromise;
	}

	function handleFileSelect( file ) {
		var check = validateFile( file );
		if ( ! check.ok ) {
			showMessage( check.error, 'error' );
			return;
		}
		selectedFile = file;
		if ( el.fileName ) {
			el.fileName.textContent = file.name;
		}
		if ( el.fileSize ) {
			el.fileSize.textContent = formatFileSize( file.size );
		}
		if ( el.fileInfo ) {
			el.fileInfo.className = 'masterquiz-file-info visible';
		}
		if ( el.dropContent ) {
			el.dropContent.style.display = 'none';
		}
		setGenerateEnabled( true );
		hideMessage();
		uploadFileInBackground( file );
	}

	function removeFile() {
		selectedFile     = null;
		fileUploaded     = false;
		uploadInProgress = false;
		uploadPromise    = null;
		if ( el.fileInput ) {
			el.fileInput.value = '';
		}
		if ( el.fileInfo ) {
			el.fileInfo.className = 'masterquiz-file-info';
		}
		if ( el.dropContent ) {
			el.dropContent.style.display = '';
		}
		setGenerateEnabled( false );
	}

	function bindDropzone() {
		if ( ! el.dropzone || ! el.fileInput ) {
			return;
		}

		// Click to browse.
		el.dropzone.addEventListener( 'click', function ( e ) {
			if ( e.target.closest( '#masterquiz-remove-file' ) || e.target.closest( '#masterquiz-file-info' ) ) {
				return;
			}
			if ( selectedFile ) {
				return;
			}
			el.fileInput.click();
		} );

		// File input change.
		el.fileInput.addEventListener( 'change', function ( e ) {
			if ( e.target.files && e.target.files.length ) {
				handleFileSelect( e.target.files[ 0 ] );
			}
			e.target.value = '';
		} );

		// Drag & drop.
		el.dropzone.addEventListener( 'dragover', function ( e ) {
			e.preventDefault();
			el.dropzone.classList.add( 'dragover' );
		} );
		el.dropzone.addEventListener( 'dragleave', function () {
			el.dropzone.classList.remove( 'dragover' );
		} );
		el.dropzone.addEventListener( 'drop', function ( e ) {
			e.preventDefault();
			el.dropzone.classList.remove( 'dragover' );
			if ( e.dataTransfer.files.length ) {
				handleFileSelect( e.dataTransfer.files[ 0 ] );
			}
		} );

		// Remove file button.
		if ( el.removeFile ) {
			el.removeFile.addEventListener( 'click', function ( e ) {
				e.stopPropagation();
				e.preventDefault();
				removeFile();
			} );
		}
	}

	/* =========================================================
	   Quiz Generation Flow
	   ========================================================= */

	function startGeneration() {
		if ( generationRunning ) {
			return;
		}
		if ( ! selectedFile ) {
			showMessage( i18n.selectFile || 'Please select a PDF file first.', 'error' );
			return;
		}

		// Validate question types.
		var typeCheckboxes = document.querySelectorAll( 'input[name="masterquiz_question_types[]"]:checked' );
		var types = [];
		typeCheckboxes.forEach( function ( cb ) {
			types.push( cb.value );
		} );
		if ( types.length === 0 ) {
			showMessage( i18n.selectQuestionType || 'Please select at least one question type.', 'error' );
			return;
		}

		var numQ = el.numQuestions ? parseInt( el.numQuestions.value, 10 ) : 5;
		numQ = clampNumQuestions( numQ );
		if ( el.numQuestions ) {
			el.numQuestions.value = String( numQ );
		}

		var mode = el.quizMode ? el.quizMode.value : ( cfg.defaultMode || 'fast' );

		generationRunning = true;
		hideMessage();
		setGenerateEnabled( false );
		showProgress();

		// Hide previous results.
		if ( el.results ) {
			el.results.className = 'masterquiz-results';
		}

		function startGenerationJob() {
			setProgress( 15, i18n.generating || 'Generating quiz...' );

			var genData = new FormData();
			genData.append( 'quiz_title', selectedFile.name.replace( /\.pdf$/i, '' ) );
			genData.append( 'num_questions', numQ );
			genData.append( 'quiz_mode', mode );
			types.forEach( function ( t ) {
				genData.append( 'question_types[]', t );
			} );

			return wpAjax( 'masterquiz_generate', genData );
		}

		function onJobStarted( data ) {
			currentJobId = data.job_id;
			ownerToken   = data.owner_token || '';
			setProgress( 20, i18n.generating || 'Generating quiz...' );
			pollJobStatus();
		}

		function onGenerationError( err ) {
			generationRunning = false;
			hideProgress();
			setGenerateEnabled( true );
			showMessage( err.message || ( i18n.failed || 'Quiz generation failed. Please try again.' ), 'error' );
		}

		// PDF uploads on file select; only wait if upload is still in flight.
		if ( fileUploaded ) {
			startGenerationJob().then( onJobStarted ).catch( onGenerationError );
		} else if ( uploadInProgress && uploadPromise ) {
			setProgress( 5, i18n.uploading || 'Uploading PDF...' );
			uploadPromise
				.then( function () { return startGenerationJob(); } )
				.then( onJobStarted )
				.catch( onGenerationError );
		} else {
			setProgress( 5, i18n.uploading || 'Uploading PDF...' );
			uploadFileInBackground( selectedFile )
				.then( function () { return startGenerationJob(); } )
				.then( onJobStarted )
				.catch( onGenerationError );
		}
	}

	/* =========================================================
	   Job Status Polling
	   ========================================================= */

	function pollJobStatus() {
		if ( ! currentJobId ) {
			return;
		}

		var formData = new FormData();
		formData.append( 'job_id', currentJobId );

		wpAjax( 'masterquiz_job_status', formData )
			.then( function ( data ) {
				var status   = data.status || 'queued';
				var progress = parseInt( data.progress, 10 ) || 0;
				var message  = data.message || '';

				if ( ! message.includes( '%' ) && progress > 0 ) {
					message += ' ' + progress + '%';
				}

				setProgress( progress, message || ( i18n.generating || 'Generating quiz...' ) );

				if ( status === 'completed' ) {
					onJobCompleted( data );
					return;
				}

				if ( status === 'failed' ) {
					onJobFailed( data );
					return;
				}

				// Schedule next poll (1.5 seconds).
				pollTimer = setTimeout( pollJobStatus, 1500 );
			} )
			.catch( function ( err ) {
				// Retry on transient errors, up to 5 min total.
				pollTimer = setTimeout( pollJobStatus, 3000 );
			} );
	}

	function stopPolling() {
		if ( pollTimer ) {
			clearTimeout( pollTimer );
			pollTimer = null;
		}
	}

	function onJobCompleted( data ) {
		stopPolling();
		generationRunning = false;
		setProgress( 100, i18n.completed || 'Quiz generated successfully!' );

		setTimeout( function () {
			hideProgress();
		}, 800 );

		setGenerateEnabled( true );

		if ( data.quiz ) {
			quizData = data.quiz;
			displayQuizResults( quizData );
		} else {
			showMessage( i18n.completed || 'Quiz generated successfully!', 'success' );
		}
	}

	function onJobFailed( data ) {
		stopPolling();
		generationRunning = false;
		hideProgress();
		setGenerateEnabled( true );
		showMessage( data.error || ( i18n.failed || 'Quiz generation failed. Please try again.' ), 'error' );
	}

	/* =========================================================
	   Display Quiz Results
	   ========================================================= */

	function displayQuizResults( qData ) {
		quizData = qData;
		allQuestions = [];

		if ( qData.questions && Array.isArray( qData.questions ) ) {
			allQuestions = qData.questions;
		} else if ( Array.isArray( qData ) ) {
			allQuestions = qData;
		}

		if ( allQuestions.length === 0 ) {
			showMessage( 'No questions found in quiz data.', 'error' );
			return;
		}

		if ( el.quizTitle ) {
			el.quizTitle.value = qData.quiz_title || '';
		}

		if ( el.saveStatus ) {
			el.saveStatus.textContent = '';
			el.saveStatus.className   = 'masterquiz-save-status';
		}

		currentQIndex = 0;
		renderQuestion( 0 );
		updateNav();

		// Show results section.
		if ( el.results ) {
			el.results.className = 'masterquiz-results visible';
		}

		// Show navigation if more than one question.
		if ( el.navigation ) {
			el.navigation.style.display = allQuestions.length > 1 ? '' : 'none';
		}

		// Scroll to results.
		setTimeout( function () {
			if ( el.results ) {
				el.results.scrollIntoView( { behavior: 'smooth', block: 'start' } );
			}
		}, 100 );
	}

	/* =========================================================
	   Render a single question card
	   ========================================================= */

	function renderQuestion( index ) {
		if ( ! el.questionsWrap || index < 0 || index >= allQuestions.length ) {
			return;
		}

		var q             = allQuestions[ index ];
		var questionText  = q.question_text || q.question || '';
		var questionType  = q.type || 'multiple_choice';
		var options       = q.options || [];
		var explanation   = q.explanation || '';
		var hint          = q.hint || '';
		var cogLevel      = q.cognitive_level || '';
		var points        = q.points || '';
		var modelAnswer   = q.model_answer || '';
		var rubric        = q.rubric_criteria || [];
		var isEssay       = [ 'essay', 'analytical_essay', 'comparative_analysis' ].indexOf( questionType ) !== -1;

		var keywords = q.concept_tags || q.keywords || q.tags || q.topics || [];

		var html = '<div class="masterquiz-question-card">';

		// Question number.
		html += '<div class="masterquiz-question-number">';
		html += escapeHtml( ( i18n.question || 'Question' ) + ' ' + ( index + 1 ) + ' ' + ( i18n.of || 'of' ) + ' ' + allQuestions.length );
		html += '</div>';

		// Header badges.
		html += '<div class="masterquiz-question-header">';
		html += '<div class="masterquiz-question-badges">';
		html += '<span class="masterquiz-badge masterquiz-badge-type">' + escapeHtml( questionType.replace( /_/g, ' ' ) ) + '</span>';
		if ( points ) {
			html += '<span class="masterquiz-badge masterquiz-badge-points">' + escapeHtml( points ) + ' ' + escapeHtml( i18n.points || 'points' ) + '</span>';
		}
		if ( cogLevel ) {
			html += '<span class="masterquiz-badge masterquiz-badge-level">' + escapeHtml( cogLevel ) + '</span>';
		}
		html += '</div></div>';

		// Keyword / topic tags.
		if ( keywords.length > 0 ) {
			html += '<div class="masterquiz-keyword-tags">';
			keywords.forEach( function ( kw ) {
				html += '<span class="masterquiz-keyword-tag">' + escapeHtml( kw ) + '</span>';
			} );
			html += '</div>';
		}

		// Editable question text and hint.
		html += '<div class="masterquiz-editor-field">';
		html += '<label for="masterquiz-question-text">' + escapeHtml( i18n.questionText || 'Question Text' ) + '</label>';
		html += '<textarea id="masterquiz-question-text" data-field="question_text" rows="3" maxlength="5000">' + escapeHtml( questionText ) + '</textarea>';
		html += '</div>';

		html += '<div class="masterquiz-editor-field">';
		html += '<label for="masterquiz-question-hint">' + escapeHtml( i18n.hint || 'Hint' ) + '</label>';
		html += '<textarea id="masterquiz-question-hint" data-field="hint" rows="2" maxlength="2000">' + escapeHtml( hint ) + '</textarea>';
		html += '</div>';

		// Options (MCQ / True-False).
		if ( options.length > 0 ) {
			html += '<ul class="masterquiz-options">';
			options.forEach( function ( option, oi ) {
				var optText   = typeof option === 'string' ? option : ( option.text || String( option ) );
				var isCorrect = typeof option === 'object' && option.correct;
				var label     = String.fromCharCode( 65 + oi );
				var rationale = ( typeof option === 'object' && option.rationale ) ? option.rationale : '';
				var cls       = 'masterquiz-option' + ( isCorrect ? ' correct' : '' );

				html += '<li class="' + cls + '" data-option-index="' + oi + '">';
				html += '<div class="masterquiz-option-editor">';
				html += '<input type="radio" name="masterquiz-correct-option" value="' + oi + '"' + ( isCorrect ? ' checked' : '' ) + ' aria-label="' + escapeHtml( ( i18n.markCorrect || 'Mark option as correct' ) + ' ' + label ) + '" />';
				html += '<span class="masterquiz-option-label">' + label + '.</span>';
				html += '<input type="text" class="masterquiz-option-text-input" value="' + escapeHtml( optText ) + '" maxlength="2000" />';
				html += '</div>';
				if ( Object.prototype.hasOwnProperty.call( option, 'rationale' ) ) {
					html += '<div class="masterquiz-editor-field masterquiz-rationale-field">';
					html += '<label>' + escapeHtml( i18n.rationale || 'Rationale' ) + '</label>';
					html += '<textarea class="masterquiz-option-rationale" rows="2" maxlength="2000">' + escapeHtml( rationale ) + '</textarea>';
					html += '</div>';
				}
				html += '</li>';
			} );
			html += '</ul>';
		}

		// Model answer (essay).
		if ( isEssay || Object.prototype.hasOwnProperty.call( q, 'model_answer' ) ) {
			html += '<div class="masterquiz-editor-field masterquiz-model-answer">';
			html += '<label for="masterquiz-model-answer">' + escapeHtml( i18n.modelAnswer || 'Model Answer' ) + '</label>';
			html += '<textarea id="masterquiz-model-answer" data-field="model_answer" rows="4" maxlength="10000">' + escapeHtml( modelAnswer ) + '</textarea>';
			html += '</div>';
		}

		// Rubric criteria.
		if ( isEssay || Object.prototype.hasOwnProperty.call( q, 'rubric_criteria' ) ) {
			html += '<div class="masterquiz-editor-field">';
			html += '<label for="masterquiz-rubric">' + escapeHtml( i18n.rubric || 'Rubric Criteria (one per line)' ) + '</label>';
			html += '<textarea id="masterquiz-rubric" data-field="rubric_criteria" rows="4" maxlength="10000">' + escapeHtml( rubric.join( '\n' ) ) + '</textarea>';
			html += '</div>';
		}

		// Explanation.
		html += '<div class="masterquiz-editor-field">';
		html += '<label for="masterquiz-question-explanation">' + escapeHtml( i18n.explanation || 'Explanation' ) + '</label>';
		html += '<textarea id="masterquiz-question-explanation" data-field="explanation" rows="3" maxlength="5000">' + escapeHtml( explanation ) + '</textarea>';
		html += '</div>';

		html += '</div>'; // close card

		el.questionsWrap.innerHTML = html;
	}

	/**
	 * Copy the visible editor values into the in-memory question.
	 */
	function collectCurrentQuestion() {
		if ( ! el.questionsWrap || ! allQuestions[ currentQIndex ] ) {
			return;
		}

		var question     = allQuestions[ currentQIndex ];
		var questionText = el.questionsWrap.querySelector( '[data-field="question_text"]' );
		var hint         = el.questionsWrap.querySelector( '[data-field="hint"]' );
		var explanation  = el.questionsWrap.querySelector( '[data-field="explanation"]' );
		var modelAnswer  = el.questionsWrap.querySelector( '[data-field="model_answer"]' );
		var rubric       = el.questionsWrap.querySelector( '[data-field="rubric_criteria"]' );
		var optionRows   = el.questionsWrap.querySelectorAll( '[data-option-index]' );
		var correctInput = el.questionsWrap.querySelector( 'input[name="masterquiz-correct-option"]:checked' );

		if ( questionText ) {
			question.question_text = questionText.value.trim();
		}
		if ( hint ) {
			question.hint = hint.value.trim();
		}
		if ( explanation ) {
			question.explanation = explanation.value.trim();
		}
		if ( modelAnswer ) {
			question.model_answer = modelAnswer.value.trim();
		}
		if ( rubric ) {
			question.rubric_criteria = rubric.value.split( '\n' )
				.map( function ( criterion ) {
					return criterion.trim();
				} )
				.filter( function ( criterion ) {
					return criterion !== '';
				} );
		}

		if ( optionRows.length > 0 ) {
			var correctIndex = correctInput ? parseInt( correctInput.value, 10 ) : -1;
			var correctIds   = [];

			question.options = Array.prototype.map.call( optionRows, function ( row, optionIndex ) {
				var originalOption = question.options[ optionIndex ];
				var textInput      = row.querySelector( '.masterquiz-option-text-input' );
				var rationaleInput = row.querySelector( '.masterquiz-option-rationale' );
				var option         = ( originalOption && typeof originalOption === 'object' )
					? Object.assign( {}, originalOption )
					: {
						id: 'option_' + ( optionIndex + 1 ),
					};

				option.text    = textInput ? textInput.value.trim() : '';
				option.correct = optionIndex === correctIndex;

				if ( rationaleInput ) {
					option.rationale = rationaleInput.value.trim();
				}

				if ( option.correct ) {
					correctIds.push( option.id );
					question.correct_answer = option.text;
				}

				return option;
			} );

			question.correct_answer_ids = correctIds;
		}
	}

	/**
	 * Validate editable fields before sending them to WordPress.
	 *
	 * @return {string} Validation message, or an empty string when valid.
	 */
	function validateQuizEdits() {
		if ( ! quizData || ! el.quizTitle || el.quizTitle.value.trim() === '' ) {
			return i18n.titleRequired || 'Please enter a quiz title.';
		}

		for ( var questionIndex = 0; questionIndex < allQuestions.length; questionIndex++ ) {
			var question = allQuestions[ questionIndex ];

			if ( ! question.question_text || question.question_text.trim() === '' ) {
				return ( i18n.questionRequired || 'Question text cannot be empty.' ) + ' (' + ( questionIndex + 1 ) + ')';
			}

			if ( question.options && question.options.length > 0 ) {
				var correctCount = 0;

				for ( var optionIndex = 0; optionIndex < question.options.length; optionIndex++ ) {
					var option = question.options[ optionIndex ];
					var text   = typeof option === 'string' ? option : option.text;

					if ( ! text || text.trim() === '' ) {
						return ( i18n.optionRequired || 'Answer options cannot be empty.' ) + ' (' + ( questionIndex + 1 ) + ')';
					}
					if ( typeof option === 'object' && option.correct ) {
						correctCount++;
					}
				}

				if ( correctCount !== 1 ) {
					return ( i18n.correctRequired || 'Select exactly one correct answer.' ) + ' (' + ( questionIndex + 1 ) + ')';
				}
			}
		}

		return '';
	}

	function setSaveState( state, message ) {
		if ( el.saveQuizBtn ) {
			el.saveQuizBtn.disabled = state === 'saving' || editorLocked;
			el.saveQuizBtn.textContent = state === 'saving'
				? ( i18n.savingQuiz || 'Saving...' )
				: ( i18n.saveQuiz || 'Save Quiz Changes' );
		}

		if ( el.saveStatus ) {
			el.saveStatus.textContent = message || '';
			el.saveStatus.className   = 'masterquiz-save-status' + ( state ? ' ' + state : '' );
		}
	}

	/**
	 * Lock the editor while a save is in flight to prevent lost edits.
	 *
	 * @param {boolean} locked Whether editing should be locked.
	 */
	function setEditorLocked( locked ) {
		editorLocked = !! locked;

		if ( el.results ) {
			el.results.classList.toggle( 'masterquiz-editor-locked', editorLocked );
		}

		if ( el.quizTitle ) {
			el.quizTitle.disabled = editorLocked;
		}
		if ( el.exportBtn ) {
			el.exportBtn.disabled = editorLocked;
		}
		if ( el.createLdBtn ) {
			el.createLdBtn.disabled = editorLocked;
		}
		if ( el.newQuizBtn ) {
			el.newQuizBtn.disabled = editorLocked;
		}
		if ( el.saveQuizBtn ) {
			el.saveQuizBtn.disabled = editorLocked;
		}

		if ( el.questionsWrap ) {
			el.questionsWrap.querySelectorAll( 'input, textarea' ).forEach( function ( node ) {
				node.disabled = editorLocked;
			} );
		}

		if ( editorLocked ) {
			if ( el.prevBtn ) {
				el.prevBtn.disabled = true;
			}
			if ( el.nextBtn ) {
				el.nextBtn.disabled = true;
			}
		} else {
			updateNav();
		}
	}

	/**
	 * Save edits to the server-side quiz used by every export path.
	 *
	 * @return {Promise<Object>} Updated quiz data.
	 */
	function saveQuizEdits() {
		if ( savePromise ) {
			return savePromise;
		}

		collectCurrentQuestion();

		if ( quizData && el.quizTitle ) {
			quizData.quiz_title = el.quizTitle.value.trim();
		}

		var validationError = validateQuizEdits();

		if ( validationError ) {
			showMessage( validationError, 'error' );
			return Promise.reject( new Error( validationError ) );
		}

		var formData = new FormData();
		var saveToken = ++saveGeneration;
		var jobIdAtSave = currentJobId;

		formData.append( 'job_id', currentJobId );
		formData.append( 'owner_token', ownerToken );
		formData.append( 'quiz', JSON.stringify( quizData ) );
		setEditorLocked( true );
		setSaveState( 'saving', i18n.savingQuiz || 'Saving quiz changes...' );

		savePromise = wpAjax( 'masterquiz_update_quiz', formData )
			.then( function ( data ) {
				savePromise = null;

				// Ignore stale responses after "New Quiz" or a newer save session.
				if ( saveToken !== saveGeneration || jobIdAtSave !== currentJobId ) {
					return quizData;
				}

				quizData     = data.quiz || quizData;
				allQuestions = quizData.questions || allQuestions;

				if ( el.quizTitle ) {
					el.quizTitle.value = quizData.quiz_title || '';
				}

				renderQuestion( currentQIndex );
				setEditorLocked( false );
				hideMessage();
				setSaveState( 'saved', data.message || ( i18n.quizSaved || 'Quiz changes saved.' ) );

				return quizData;
			} )
			.catch( function ( err ) {
				savePromise = null;

				if ( saveToken !== saveGeneration || jobIdAtSave !== currentJobId ) {
					return Promise.reject( err );
				}

				setEditorLocked( false );
				setSaveState( 'error', err.message || ( i18n.saveFailed || 'Quiz changes could not be saved.' ) );
				showMessage( err.message || ( i18n.saveFailed || 'Quiz changes could not be saved.' ), 'error' );
				throw err;
			} );

		return savePromise;
	}

	function bindQuizEditor() {
		if ( el.saveQuizBtn ) {
			el.saveQuizBtn.addEventListener( 'click', function () {
				saveQuizEdits().catch( function () {
					// Error feedback is displayed by saveQuizEdits().
				} );
			} );
		}

		if ( el.results ) {
			el.results.addEventListener( 'input', function () {
				if ( el.saveStatus && el.saveStatus.classList.contains( 'saved' ) ) {
					setSaveState( '', '' );
				}
			} );
		}

		if ( el.questionsWrap ) {
			el.questionsWrap.addEventListener( 'change', function ( event ) {
				if ( event.target.name !== 'masterquiz-correct-option' ) {
					return;
				}

				el.questionsWrap.querySelectorAll( '[data-option-index]' ).forEach( function ( row ) {
					row.classList.remove( 'correct' );
				} );

				var selectedRow = event.target.closest( '[data-option-index]' );
				if ( selectedRow ) {
					selectedRow.classList.add( 'correct' );
				}
			} );
		}
	}

	/* =========================================================
	   Navigation
	   ========================================================= */

	function updateNav() {
		if ( el.prevBtn ) {
			el.prevBtn.disabled = ( currentQIndex <= 0 );
		}
		if ( el.nextBtn ) {
			el.nextBtn.disabled = ( currentQIndex >= allQuestions.length - 1 );
		}
		if ( el.counter ) {
			el.counter.textContent = ( i18n.question || 'Question' ) + ' ' + ( currentQIndex + 1 ) + ' ' + ( i18n.of || 'of' ) + ' ' + allQuestions.length;
		}
	}

	function bindNavigation() {
		if ( el.prevBtn ) {
			el.prevBtn.addEventListener( 'click', function () {
				if ( editorLocked || currentQIndex <= 0 ) {
					return;
				}
				collectCurrentQuestion();
				currentQIndex--;
				renderQuestion( currentQIndex );
				updateNav();
			} );
		}
		if ( el.nextBtn ) {
			el.nextBtn.addEventListener( 'click', function () {
				if ( editorLocked || currentQIndex >= allQuestions.length - 1 ) {
					return;
				}
				collectCurrentQuestion();
				currentQIndex++;
				renderQuestion( currentQIndex );
				updateNav();
			} );
		}
	}

	/* =========================================================
	   Export
	   ========================================================= */

	function bindExport() {
		// Open modal.
		if ( el.exportBtn ) {
			el.exportBtn.addEventListener( 'click', function () {
				saveQuizEdits()
					.then( function () {
						if ( el.exportModal ) {
							el.exportModal.className = 'masterquiz-export-modal visible';
						}
					} )
					.catch( function () {
						// Error feedback is displayed by saveQuizEdits().
					} );
			} );
		}

		// Close modal.
		if ( el.exportClose ) {
			el.exportClose.addEventListener( 'click', function () {
				if ( el.exportModal ) {
					el.exportModal.className = 'masterquiz-export-modal';
				}
			} );
		}

		// Close modal on backdrop click.
		if ( el.exportModal ) {
			el.exportModal.addEventListener( 'click', function ( e ) {
				if ( e.target === el.exportModal ) {
					el.exportModal.className = 'masterquiz-export-modal';
				}
			} );
		}

		// Format buttons.
		var formatBtns = document.querySelectorAll( '.masterquiz-export-format-btn' );
		formatBtns.forEach( function ( btn ) {
			btn.addEventListener( 'click', function () {
				var format = this.getAttribute( 'data-format' );
				if ( ! format || ! currentJobId ) {
					return;
				}
				exportQuiz( format );
			} );
		} );
	}

	function exportQuiz( format ) {
		if ( ! currentJobId ) {
			showMessage( 'No quiz to export.', 'error' );
			return;
		}

		var formData = new FormData();
		formData.append( 'job_id', currentJobId );
		formData.append( 'format', format );
		formData.append( 'action', 'masterquiz_export' );
		formData.append( 'nonce', cfg.nonce );

		// Export triggers a file download via AJAX.
		fetch( cfg.ajaxUrl, {
			method: 'POST',
			credentials: 'same-origin',
			body: formData,
		} )
			.then( function ( res ) {
				var disposition = res.headers.get( 'content-disposition' ) || '';
				var contentType = res.headers.get( 'content-type' ) || '';

				// Content-Disposition: attachment means a file download regardless of content type.
				if ( disposition.indexOf( 'attachment' ) !== -1 ) {
					return res.blob().then( function ( blob ) {
						return { blob: blob, res: res };
					} );
				}

				// JSON without attachment header is an error response.
				if ( contentType.indexOf( 'application/json' ) !== -1 ) {
					return res.json().then( function ( json ) {
						throw new Error( ( json.data && json.data.message ) || 'Export failed.' );
					} );
				}

				if ( ! res.ok ) {
					throw new Error( 'Export failed: ' + res.statusText );
				}

				return res.blob().then( function ( blob ) {
					return { blob: blob, res: res };
				} );
			} )
			.then( function ( result ) {
				if ( ! result || ! result.blob ) {
					return;
				}

				// Determine filename from Content-Disposition or fallback.
				var disposition = result.res.headers.get( 'content-disposition' ) || '';
				var filenameMatch = disposition.match( /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/ );
				var filename = filenameMatch ? filenameMatch[ 1 ].replace( /['"]/g, '' ) : 'quiz_export.' + format;

				var url = window.URL.createObjectURL( result.blob );
				var a   = document.createElement( 'a' );
				a.href     = url;
				a.download = filename;
				document.body.appendChild( a );
				a.click();

				setTimeout( function () {
					window.URL.revokeObjectURL( url );
					document.body.removeChild( a );
				}, 200 );

				// Close modal.
				if ( el.exportModal ) {
					el.exportModal.className = 'masterquiz-export-modal';
				}
			} )
			.catch( function ( err ) {
				showMessage( err.message || 'Export failed.', 'error' );
			} );
	}

	/* =========================================================
	   LearnDash Quiz Creation
	   ========================================================= */

	function bindLearnDash() {
		if ( ! el.createLdBtn ) {
			return;
		}

		el.createLdBtn.addEventListener( 'click', function () {
			if ( ! currentJobId ) {
				showMessage( 'No quiz data available.', 'error' );
				return;
			}

			el.createLdBtn.disabled    = true;
			el.createLdBtn.textContent = i18n.savingQuiz || 'Saving...';

			saveQuizEdits()
				.then( function () {
					var formData = new FormData();
					formData.append( 'job_id', currentJobId );
					el.createLdBtn.disabled    = true;
					el.createLdBtn.textContent = i18n.generating_btn || 'Creating...';

					return wpAjax( 'masterquiz_create_learndash_quiz', formData );
				} )
				.then( function ( data ) {
					el.createLdBtn.disabled    = false;
					el.createLdBtn.textContent = i18n.createLdQuiz || 'Create LearnDash Quiz';

					if ( el.ldResult ) {
						var link = data.edit_url
							? '<a href="' + escapeHtml( data.edit_url ) + '" target="_blank">' + escapeHtml( i18n.ldQuizCreated || 'LearnDash quiz created!' ) + '</a>'
							: escapeHtml( i18n.ldQuizCreated || 'LearnDash quiz created!' );
						el.ldResult.innerHTML  = link;
						el.ldResult.className  = 'masterquiz-learndash-result visible';
					}
				} )
				.catch( function ( err ) {
					el.createLdBtn.disabled    = false;
					el.createLdBtn.textContent = i18n.createLdQuiz || 'Create LearnDash Quiz';
					showMessage( err.message || 'LearnDash quiz creation failed.', 'error' );
				} );
		} );
	}

	/* =========================================================
	   New Quiz (reset)
	   ========================================================= */

	function bindNewQuiz() {
		if ( ! el.newQuizBtn ) {
			return;
		}

		el.newQuizBtn.addEventListener( 'click', function () {
			// Invalidate any in-flight save before clearing local state.
			saveGeneration++;
			savePromise  = null;
			ownerToken   = '';
			quizData     = null;
			allQuestions = [];
			currentQIndex = 0;
			currentJobId  = null;

			setEditorLocked( false );

			// Hide results.
			if ( el.results ) {
				el.results.className = 'masterquiz-results';
			}
			if ( el.ldResult ) {
				el.ldResult.className = 'masterquiz-learndash-result';
			}
			if ( el.quizTitle ) {
				el.quizTitle.value = '';
			}
			setSaveState( '', '' );

			hideMessage();
			removeFile();

			// Scroll to top of wrapper.
			if ( el.wrapper ) {
				el.wrapper.scrollIntoView( { behavior: 'smooth', block: 'start' } );
			}
		} );
	}

	/* =========================================================
	   Try with Sample PDF
	   ========================================================= */

	function bindTrySample() {
		var trySampleLink = document.getElementById( 'masterquiz-try-sample' );
		if ( ! trySampleLink || ! cfg.samplePdfUrl ) {
			return;
		}

		trySampleLink.addEventListener( 'click', function ( e ) {
			e.preventDefault();

			if ( generationRunning ) {
				return;
			}

			trySampleLink.style.pointerEvents = 'none';
			trySampleLink.style.opacity = '0.6';

			fetch( cfg.samplePdfUrl )
				.then( function ( res ) {
					if ( ! res.ok ) {
						throw new Error( 'Failed to load sample PDF.' );
					}
					return res.blob();
				} )
				.then( function ( blob ) {
					var file = new File( [ blob ], 'sample.pdf', { type: 'application/pdf' } );
					handleFileSelect( file );
					trySampleLink.style.pointerEvents = '';
					trySampleLink.style.opacity = '';

					// Auto-trigger generation.
					startGeneration();
				} )
				.catch( function ( err ) {
					trySampleLink.style.pointerEvents = '';
					trySampleLink.style.opacity = '';
					showMessage( err.message || 'Could not load sample PDF.', 'error' );
				} );
		} );
	}

	/* =========================================================
	   Initialize
	   ========================================================= */

	function init() {
		resolveElements();

		if ( ! el.wrapper ) {
			return;
		}

		// Set default quiz mode from config.
		if ( el.quizMode && cfg.defaultMode ) {
			el.quizMode.value = cfg.defaultMode;
		}

		bindNumQuestionsClamp();
		bindDropzone();
		bindQuizEditor();
		bindNavigation();
		bindExport();
		bindLearnDash();
		bindNewQuiz();
		bindTrySample();

		// Generate button.
		if ( el.generateBtn ) {
			el.generateBtn.addEventListener( 'click', function () {
				startGeneration();
			} );
		}
	}

	// Boot.
	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', init );
	} else {
		init();
	}

	// Cleanup on page unload.
	window.addEventListener( 'beforeunload', function () {
		stopPolling();
	} );

} )();
