// // quiz.js
// /**
//  * mkdocs-superquiz - Logique principale JavaScript
//  */

// window.QUIZ_CONFIG = window.QUIZ_CONFIG || {};

// function t(key, fallback = "") {
//     const keys = key.split('.');
//     let value = window.QUIZ_CONFIG?.i18n;
//     for (const k of keys) {
//         if (value && typeof value === 'object') value = value[k];
//         else return fallback || key;
//     }
//     return value || fallback || key;
// }

// let currentUnlockContext = null;

// function createUnlockModal() {
//     const modal = document.createElement('div');
//     modal.id = 'mkq-unlock-modal';
//     modal.className = 'mkq-modal';
//     modal.innerHTML = `
//         <div class="mkq-modal-content">
//             <button class="mkq-modal-close" onclick="closeUnlockModal()">×</button>
//             <h3 id="mkq-unlock-title">${t("ui.unlock_title", "Unlock Correction")}</h3>
//             <div class="qr-code-section">
//                 <p>${t("ui.unlock_qr_instruction", "Ask your teacher to scan this QR code:")}</p>
//                 <div id="qr-code-container"></div>
//             </div>
//             <div class="unlock-input-section">
//                 <label for="mkq-unlock-code-input">${t("ui.unlock_code_label", "Unlock code (provided by teacher):")}</label>
//                 <input type="text" id="mkq-unlock-code-input" placeholder="${t("ui.unlock_code_placeholder", "e.g., A7F3E9C2")}">
//                 <button onclick="validateUnlockCode()">${t("ui.unlock_validate", "Validate")}</button>
//             </div>
//         </div>
//     `;
//     document.body.appendChild(modal);
// }

// function getSiteUrl() {
//     const currentOrigin = window.location.origin;
//     const currentPath = window.location.pathname;
//     const baseElement = document.querySelector('base');
//     if (baseElement && baseElement.href) {
//         const baseUrl = new URL(baseElement.href);
//         return baseUrl.origin + baseUrl.pathname.replace(/\/$/, '');
//     }
//     const pathParts = currentPath.split('/').filter(p => p);
//     if (pathParts.length > 0) {
//         const firstPart = pathParts[0];
//         const isProjectName = !firstPart.includes('.html') && !firstPart.includes('.') &&
//             firstPart !== 'search' && firstPart !== 'assets';
//         if (currentPath.includes('/mkdocs-superquiz/')) return currentOrigin + '/mkdocs-superquiz';
//         const knownPages = ['index', 'sample_quiz', 'search', 'sitemap'];
//         if (isProjectName && !knownPages.includes(firstPart)) return currentOrigin + '/' + firstPart;
//     }
//     return currentOrigin;
// }

// function showUnlockModal(type, exerciseId = null, questionId = null) {
//     const modal = document.getElementById('mkq-unlock-modal');
//     if (!modal) return;
//     const pageUuid = document.querySelector('.mkdocs-superquiz')?.dataset.pageUuid;
//     if (!pageUuid) return;
//     const siteUrl = getSiteUrl();
//     const data = { type, page_id: pageUuid, page_title: document.title, site_url: siteUrl };
//     if (type === 'exercise' || type === 'question') {
//         data.exercise_id = exerciseId;
//         const exerciseElement = document.querySelector(`[data-quiz-id="${exerciseId}"]`);
//         if (exerciseElement) {
//             const titleElement = exerciseElement.querySelector('.admonition-title');
//             let title = titleElement ? titleElement.textContent.trim() : '';
//             title = title.replace(/🔒|🔓|❌|🔐|✅/g, '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
//             data.exercise_title = title;
//         }
//     }
//     if (type === 'question') {
//         data.question_id = questionId;
//         const questionElement = document.querySelector(`[data-quiz-id="${exerciseId}"] [data-question-id="${questionId}"]`);
//         if (questionElement) {
//             const textElement = questionElement.querySelector('.mkq-question-text');
//             let questionText = textElement ? textElement.textContent.trim() : '';
//             questionText = questionText.replace(/🔒|🔓|❌|🔐|✅/g, '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
//             data.question_text = questionText;
//         }
//     }
//     currentUnlockContext = data;
//     const titleElement = document.getElementById('mkq-unlock-title');
//     if (titleElement) {
//         if (type === 'page') titleElement.textContent = t("ui.unlock_page_title", "Unlock All Page Quizzes");
//         else if (type === 'exercise') titleElement.textContent = `${t("ui.unlock_quiz", "Unlock Quiz")} ${exerciseId}`;
//         else titleElement.textContent = `${t("ui.unlock_quiz_question", "Unlock this quiz question")} ${questionId}`;
//     }
//     generateQRCode(data);
//     modal.classList.add('active');
// }

// function generateQRCode(data) {
//     const container = document.getElementById('qr-code-container');
//     if (!container) return;
//     const jsonString = JSON.stringify(data);
//     const utf8Bytes = new TextEncoder().encode(jsonString);
//     let binaryString = '';
//     for (let i = 0; i < utf8Bytes.length; i++) binaryString += String.fromCharCode(utf8Bytes[i]);
//     const encodedData = btoa(binaryString);
//     const unlockUrl = `${data.site_url}/quiz-unlock.html?data=${encodedData}`;
//     if (typeof qrcode === 'undefined') {
//         container.innerHTML = `<p style="color:red;">${t("ui.error_no_library", "Error: No QR Code library has been downloaded")}</p>`;
//         return;
//     }
//     try {
//         const qr = qrcode(0, 'H');
//         qr.addData(unlockUrl);
//         qr.make();
//         container.innerHTML = qr.createSvgTag(4, 4);
//         const svg = container.querySelector('svg');
//         if (svg) {
//             svg.style.display = 'block'; svg.style.maxWidth = '256px'; svg.style.height = 'auto';
//             svg.style.margin = '0 auto'; svg.style.padding = '10px'; svg.style.background = 'white';
//             svg.style.borderRadius = '8px'; svg.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
//         }
//     } catch (error) {
//         container.innerHTML = `<p style="color:red;">${t("ui.error_generating_qrcode", "Error generating QR Code")}</p>`;
//     }
// }

// function closeUnlockModal() {
//     const modal = document.getElementById('mkq-unlock-modal');
//     if (modal) modal.classList.remove('active');
//     const input = document.getElementById('mkq-unlock-code-input');
//     if (input) input.value = '';
// }

// async function validateUnlockCode() {
//     const input = document.getElementById('mkq-unlock-code-input');
//     if (!input) return;
//     const code = input.value.trim().toUpperCase();
//     if (!code) { alert(t("ui.please_enter_code", "Please enter a code")); return; }
//     if (!currentUnlockContext) { alert(t("ui.error_no_unlocking_context", "Error: Unlocking context not found")); return; }
//     const expectedCode = await calculateUnlockCode(currentUnlockContext);
//     if (code === expectedCode) {
//         unlockCorrections(currentUnlockContext);
//         saveUnlockState(currentUnlockContext);
//         closeUnlockModal();
//     } else {
//         alert(t("ui.error_incorrect_code", "Incorrect code. Check with your teacher."));
//     }
// }

// async function calculateUnlockCode(context) {
//     const teacherCodeHash = window.QUIZ_CONFIG.teacher_code_hash || '';
//     let payload = teacherCodeHash + context.page_id + context.type;
//     if (context.type === 'exercise' || context.type === 'question') payload += (context.exercise_id || '');
//     if (context.type === 'question') payload += (context.question_id || '');
//     const hash = await sha256(payload);
//     return hash.substring(0, 8).toUpperCase();
// }

// async function sha256(str) {
//     const buffer = new TextEncoder().encode(str);
//     const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
//     const hashArray = Array.from(new Uint8Array(hashBuffer));
//     return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// }

// function updateLockIcon(element, unlocked) {
//     if (!element) return;
//     if (unlocked) {
//         element.textContent = '🔓';
//         element.classList.remove('locked'); element.classList.add('unlocked');
//         element.setAttribute('data-lock-state', 'unlocked');
//         element.setAttribute('title', t("ui.unlocked_corrections", "Unlocked corrections"));
//         element.onclick = null; element.style.cursor = 'default';
//     } else {
//         element.textContent = '🔒';
//         element.classList.remove('unlocked'); element.classList.add('locked');
//         element.setAttribute('data-lock-state', 'locked');
//         element.setAttribute('title', t("ui.lock_tooltip_locked", "Click to unlock corrections"));
//     }
// }

// function shouldShowCorrections(quiz) {
//     const lockIndicator = quiz.querySelector('.mkq-lock-indicator, .mkq-unlock-btn');
//     if (!lockIndicator) return true;
//     const lockState = lockIndicator.getAttribute('data-lock-state');
//     if (lockState === 'locked-permanent') return false;
//     if (lockState === 'unlocked') return true;
//     if (lockState === 'locked') {
//         const pageUuid = quiz.dataset.pageUuid;
//         const quizId = quiz.dataset.quizId;
//         const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
//         return unlocks.some(u => u.page_id === pageUuid && (u.type === 'page' || u.exercise_id === quizId));
//     }
//     return true;
// }

// function unlockCorrections(context) {
//     if (context.type === 'page') {
//         document.querySelectorAll('.mkq-unlock-btn').forEach(btn => updateLockIcon(btn, true));
//         document.querySelectorAll('.mkdocs-superquiz').forEach(quiz => {
//             const quizId = quiz.dataset.quizId;
//             if (quizId) validateQuiz(quizId);
//         });
//     } else if (context.type === 'exercise') {
//         const exercise = document.querySelector(`[data-quiz-id="${context.exercise_id}"]`);
//         if (exercise) {
//             updateLockIcon(exercise.querySelector('.mkq-unlock-btn'), true);
//             validateQuiz(context.exercise_id);
//         }
//     } else if (context.type === 'question') {
//         const exercise = document.querySelector(`[data-quiz-id="${context.exercise_id}"]`);
//         if (exercise) {
//             const question = exercise.querySelector(`[data-question-id="${context.question_id}"]`);
//             if (question) updateLockIcon(question.querySelector('.mkq-unlock-btn'), true);
//             validateQuiz(context.exercise_id);
//         }
//     }
// }

// function saveUnlockState(context) {
//     const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
//     unlocks.push({
//         page_id: context.page_id, type: context.type,
//         exercise_id: context.exercise_id, question_id: context.question_id,
//         timestamp: new Date().toISOString()
//     });
//     localStorage.setItem('mkq_unlocks', JSON.stringify(unlocks));
// }

// function restoreUnlockedCorrections() {
//     const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
//     const currentPageUuid = document.querySelector('.mkdocs-superquiz')?.dataset.pageUuid;
//     if (!currentPageUuid) return;
//     unlocks.forEach(unlock => { if (unlock.page_id === currentPageUuid) unlockCorrections(unlock); });
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Randomisation côté client
// // ─────────────────────────────────────────────────────────────────────────────

// /**
//  * Fisher-Yates shuffle (in-place).
//  */
// function shuffleArray(arr) {
//     for (let i = arr.length - 1; i > 0; i--) {
//         const j = Math.floor(Math.random() * (i + 1));
//         [arr[i], arr[j]] = [arr[j], arr[i]];
//     }
// }

// /**
//  * Applique la randomisation sur tous les quiz de la page.
//  * Appelé au DOMContentLoaded, AVANT loadSavedAnswers().
//  *
//  * randomize_answers  → mélange les réponses/options à l'intérieur de chaque question
//  * randomize_questions → mélange l'ordre des questions dans le quiz
//  *                       (renumérotation visuelle après shuffle)
//  *
//  * Stratégie de scoring post-shuffle :
//  *   - mcquiz/scquiz : le scoring JS compare par TEXTE (correct_answers_text)
//  *                     et non par index DOM → fonctionne quelle que soit la position
//  *   - scdropdown      : le scoring JS compare par TEXTE de l'option sélectionnée
//  *                     (data-option-text) et non par data-value index
//  */
// function applyRandomization() {
//     document.querySelectorAll('.mkdocs-superquiz').forEach(quizEl => {
//         const quizRandomizeAnswers   = quizEl.dataset.randomizeAnswers   === 'true';
//         const randomizeQuestions = quizEl.dataset.randomizeQuestions === 'true';
//         const quizType = quizEl.dataset.quizType;

//         // ── randomize_answers ──────────────────────────────────────────────
//         if (quizType === 'mcquiz' || quizType === 'scquiz') {
//             quizEl.querySelectorAll('.mkq-question[data-question-id]').forEach(qEl => {
//                 const qId = qEl.dataset.questionId;
//                 if (!qId || qId === '0') return;

//                 // Priorité : data-randomize-answers sur la question > flag du quiz
//                 let shouldRandomize;
//                 if (qEl.hasAttribute('data-randomize-answers')) {
//                     shouldRandomize = qEl.dataset.randomizeAnswers === 'true';
//                 } else {
//                     shouldRandomize = quizRandomizeAnswers;
//                 }
//                 if (!shouldRandomize) return;

//                 const answersDiv = qEl.querySelector('.mkq-answers');
//                 if (!answersDiv) return;
//                 const labels = Array.from(answersDiv.querySelectorAll('.mkq-answer'));
//                 shuffleArray(labels);
//                 labels.forEach(l => answersDiv.appendChild(l));
//             });
//         } else if (quizType === 'scdropdown') {
//             quizEl.querySelectorAll('.mkq-question[data-question-id]').forEach(qEl => {
//                 const qId = qEl.dataset.questionId;
//                 if (!qId || qId === '0') return;

//                 // Priorité : data-randomize-answers sur la question > flag du quiz
//                 let shouldRandomize;
//                 if (qEl.hasAttribute('data-randomize-answers')) {
//                     shouldRandomize = qEl.dataset.randomizeAnswers === 'true';
//                 } else {
//                     shouldRandomize = quizRandomizeAnswers;
//                 }
//                 if (!shouldRandomize) return;

//                 qEl.querySelectorAll('.mkq-custom-scdropdown').forEach(ddEl => {
//                     const optionsDiv = ddEl.querySelector('.mkq-custom-scdropdown-options');
//                     if (!optionsDiv) return;
//                     const options = Array.from(optionsDiv.querySelectorAll('.mkq-custom-scdropdown-option'));
//                     shuffleArray(options);
//                     options.forEach((opt, newIdx) => {
//                         opt.dataset.value = newIdx;
//                         optionsDiv.appendChild(opt);
//                     });
//                 });
//             });
//         }

//         // ── randomize_questions ────────────────────────────────────────────
//         if (randomizeQuestions) {
//             const questionEls = Array.from(
//                 quizEl.querySelectorAll('.mkq-question[data-question-id]')
//             ).filter(q => q.dataset.questionId !== '0');

//             if (questionEls.length > 1) {
//                 const parent = questionEls[0].parentElement;
//                 // ✅ Bug 2 fix : insérer AVANT .quiz-actions pour ne pas
//                 //    repousser les questions après le bouton Validate
//                 const actionsDiv = parent.querySelector('.quiz-actions');
//                 shuffleArray(questionEls);
//                 questionEls.forEach((qEl, newIdx) => {
//                     const numEl = qEl.querySelector('.mkq-number');
//                     if (numEl) numEl.textContent = `${newIdx + 1}.`;
//                     if (actionsDiv) {
//                         parent.insertBefore(qEl, actionsDiv);
//                     } else {
//                         parent.appendChild(qEl);
//                     }
//                 });
//             }
//         }
//     });
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Scoring helpers
// // ─────────────────────────────────────────────────────────────────────────────

// function clampScore(score, pointsMax, minScore, clampMax) {
//     let s = score;
//     if (clampMax) s = Math.min(s, pointsMax);
//     if (minScore !== null && minScore !== undefined) {
//         const min = Number(minScore);
//         if (min < pointsMax) s = Math.max(s, min);
//     }
//     return s;
// }

// function arraysEqual(a, b) {
//     if (a.length !== b.length) return false;
//     for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
//     return true;
// }

// function normalizeLatexLoose(str) {
//     return str.replace(/\s+/g, '').replace(/\*/g, '').replace(/²/g, '^2').replace(/³/g, '^3');
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // compareAnswers — NEW FORMAT ONLY
// // ─────────────────────────────────────────────────────────────────────────────

// function normalizeCompareConfig(cfg) {
//     const DEFAULTS = {
//         spaces: 'none',
//         characters: '',
//         case: false,
//         diacritics: false
//     };

//     if (!cfg || typeof cfg !== 'object') {
//         return { ignore: Object.assign({}, DEFAULTS), equivalence: {} };
//     }

//     const ignore = Object.assign({}, DEFAULTS, cfg.ignore || {});

//     const validSpaces = ['all', 'inside', 'outside', 'none'];
//     if (!validSpaces.includes(ignore.spaces)) ignore.spaces = 'none';

//     const rawEquiv = cfg.equivalence || {};
//     const equivalence = {};
//     for (const [k, v] of Object.entries(rawEquiv)) {
//         equivalence[k] = Array.isArray(v) ? v.map(String) : [String(v)];
//     }

//     return { ignore, equivalence };
// }

// function normalizeAnswer(str, ignore) {
//     if (typeof str !== 'string') str = String(str ?? '');

//     const chars = (ignore.characters != null) ? String(ignore.characters) : '';
//     if (chars.length > 0) {
//         const charSet = new Set([...chars]);
//         str = [...str].filter(c => !charSet.has(c)).join('');
//     }

//     const spacesMode = (ignore.spaces != null) ? ignore.spaces : 'none';
//     if (spacesMode === 'all') {
//         str = str.replace(/\s+/g, '');
//     } else if (spacesMode === 'outside') {
//         str = str.trim();
//     } else if (spacesMode === 'inside') {
//         const leading = str.match(/^\s*/)[0];
//         const trailing = str.match(/\s*$/)[0];
//         const innerStart = leading.length;
//         const innerEnd = str.length - trailing.length;
//         const inner = str.slice(innerStart, innerEnd);
//         str = leading + inner.replace(/\s+/g, '') + trailing;
//     }

//     if (ignore.case === true) str = str.toLowerCase();
//     if (ignore.diacritics === true) str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');

//     return str;
// }

// function applyEquivalences(str, equivalence, caseInsensitive) {
//     if (!equivalence || typeof equivalence !== 'object') return str;

//     const repMap = new Map();
//     for (const [canonical, variants] of Object.entries(equivalence)) {
//         const canonKey = caseInsensitive ? canonical.toLowerCase() : canonical;
//         for (const v of (Array.isArray(variants) ? variants : [variants])) {
//             const vKey = caseInsensitive ? String(v).toLowerCase() : String(v);
//             repMap.set(vKey, canonKey);
//         }
//     }
//     if (repMap.size === 0) return str;

//     const sortedKeys = [...repMap.keys()].sort((a, b) => b.length - a.length);
//     const escaped = sortedKeys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
//     const re = new RegExp(escaped.join('|'), 'g');
//     return str.replace(re, match => repMap.get(match) ?? match);
// }

// function compareAnswers(userAnswer, correctAnswer, compareConfig) {
//     const rawCfg = compareConfig
//         || window.QUIZ_CONFIG?.fillblanks?.compare_answers
//         || {};

//     const cfg = normalizeCompareConfig(rawCfg);
//     const { ignore, equivalence } = cfg;
//     const caseInsensitive = (ignore.case === true);

//     let user = normalizeAnswer(String(userAnswer ?? ''), ignore);
//     let correct = normalizeAnswer(String(correctAnswer ?? ''), ignore);

//     user = applyEquivalences(user, equivalence, caseInsensitive);
//     correct = applyEquivalences(correct, equivalence, caseInsensitive);

//     return user === correct;
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Parse errors_mode keys for mcquiz: "[Paris, Londres]" → list
// // ─────────────────────────────────────────────────────────────────────────────

// function parseBracketKey(key) {
//     const s = String(key).trim();
//     if (!s.startsWith('[') || !s.endsWith(']')) return null;
//     const inner = s.slice(1, -1).trim();
//     if (!inner) return [];
//     return inner.split(',').map(x => x.trim()).filter(Boolean);
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Score functions
// // ─────────────────────────────────────────────────────────────────────────────

// /**
//  * scoreChoiceQuestion — mcquiz
//  *
//  * ✅ Post-shuffle : on compare par TEXTE (correct_answers_text) plutôt que par
//  *    index DOM, pour que le scoring soit correct quelle que soit l'ordre affiché.
//  *
//  *    selectedTexts : tableau des textes des réponses cochées (lus depuis
//  *    data-answer-text sur les <label> cochés — voir validateChoiceQuiz).
//  */
// function scoreChoiceQuestion(questionData, selectedTexts) {
//     const points = Number(questionData.points || 1);
//     const scoringMode = questionData.scoring_mode || 'all_or_nothing';

//     const qdf = questionData.question_default_false;
//     const hasQdf = (qdf !== null && qdf !== undefined);

//     const defaultFalse = hasQdf ? Number(qdf)
//         : (questionData.default_false !== null && questionData.default_false !== undefined)
//             ? Number(questionData.default_false)
//             : null;
//     const hasDefaultFalse = (defaultFalse !== null);

//     const correctTexts = Array.isArray(questionData.correct_answers_text)
//         ? questionData.correct_answers_text
//         : [];
//     const correctSet = new Set(correctTexts.map(t => t.trim()));

//     let nbGood = 0, nbFalse = 0;
//     selectedTexts.forEach(txt => {
//         if (correctSet.has(txt.trim())) nbGood++;
//         else nbFalse++;
//     });

//     const nbGoodTotal = correctTexts.length || 1;
//     const isEmpty = (selectedTexts.length === 0);

//     const selSorted = [...selectedTexts].map(t => t.trim()).sort();
//     const corSorted = [...correctTexts].map(t => t.trim()).sort();
//     const exact = arraysEqual(selSorted, corSorted);

//     const em = questionData.errors_mode || null;
//     const hasErrorsModeRules = em && Array.isArray(em.rules) && em.rules.length > 0;
//     const hasEmDf = em && em.default_false !== null && em.default_false !== undefined;
//     const hasAnyErrorsMode = hasErrorsModeRules || hasEmDf || hasQdf;

//     const minScore = questionData.min_score !== undefined ? questionData.min_score
//         : window.QUIZ_CONFIG.min_score !== undefined ? window.QUIZ_CONFIG.min_score : null;
//     const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

//     const effectiveDefaultFalse = hasEmDf ? Number(em.default_false)
//         : hasDefaultFalse ? defaultFalse
//         : 0;

//     // ── Réponse exacte → toujours points max ────────────────────────────────
//     if (exact && !isEmpty) {
//         return clampScore(points, points, minScore, clampMax);
//     }

//     // ── all_or_nothing ───────────────────────────────────────────────────────
//     if (scoringMode === 'all_or_nothing') {
//         let base = 0;
//         if (hasErrorsModeRules && !isEmpty) {
//             let matched = null;
//             for (const r of em.rules) {
//                 const list = parseBracketKey(r.key);
//                 if (!list) continue;
//                 const a = [...selectedTexts].map(x => x.trim()).sort();
//                 const b = [...list].map(x => x.trim()).sort();
//                 if (arraysEqual(a, b)) { matched = r; break; }
//             }
//             if (matched) base = Number(matched.score);
//             else base = effectiveDefaultFalse;
//         } else if (hasAnyErrorsMode) {
//             base = effectiveDefaultFalse;
//         }
//         return clampScore(base, points, minScore, clampMax);
//     }

//     // ── some_or_nothing ──────────────────────────────────────────────────────
//     if (scoringMode === 'some_or_nothing') {
//         // ✅ Réponse vide + errors_mode défini → effectiveDefaultFalse
//         if (isEmpty && hasAnyErrorsMode) {
//             return clampScore(effectiveDefaultFalse, points, minScore, clampMax);
//         }
//         if (nbFalse === 0) {
//             const base = points * (nbGood / nbGoodTotal);
//             return clampScore(base, points, minScore, clampMax);
//         }
//         if (!hasAnyErrorsMode) {
//             return clampScore(0, points, minScore, clampMax);
//         }
//         let base = 0;
//         if (hasErrorsModeRules) {
//             let matched = null;
//             for (const r of em.rules) {
//                 const list = parseBracketKey(r.key);
//                 if (!list) continue;
//                 const a = [...selectedTexts].map(x => x.trim()).sort();
//                 const b = [...list].map(x => x.trim()).sort();
//                 if (arraysEqual(a, b)) { matched = r; break; }
//             }
//             if (matched) base = Number(matched.score);
//             else base = effectiveDefaultFalse;
//         } else {
//             base = effectiveDefaultFalse;
//         }
//         return clampScore(base, points, minScore, clampMax);
//     }

//     // ── proportional ─────────────────────────────────────────────────────────
//     let base = points * ((nbGood - nbFalse) / nbGoodTotal);

//     if (hasErrorsModeRules) {
//         let matched = null;
//         for (const r of em.rules) {
//             const list = parseBracketKey(r.key);
//             if (!list) continue;
//             const a = [...selectedTexts].map(x => x.trim()).sort();
//             const b = [...list].map(x => x.trim()).sort();
//             if (arraysEqual(a, b)) { matched = r; break; }
//         }
//         if (matched) base = Number(matched.score);
//         else base = effectiveDefaultFalse;
//     } else if (hasAnyErrorsMode) {
//         base = points * (nbGood / nbGoodTotal) + effectiveDefaultFalse * nbFalse;
//     }

//     return clampScore(base, points, minScore, clampMax);
// }





// /**
//  * scoreScQuizQuestion — scquiz
//  *
//  * ✅ Post-shuffle : compare par TEXTE de la réponse sélectionnée.
//  *    selectedText : texte brut de la réponse cochée.
//  */

// function scoreScQuizQuestion(questionData, selectedText) {
//     const points = Number(questionData.points || 1);

//     // ✅ question_default_false prime sur default_false quiz
//     const qdf = questionData.question_default_false;
//     const hasQdf = (qdf !== null && qdf !== undefined);
//     const defaultFalse = hasQdf ? Number(qdf)
//         : (questionData.default_false !== null && questionData.default_false !== undefined)
//             ? Number(questionData.default_false) : 0;

//     const correctTexts = Array.isArray(questionData.correct_answers_text)
//         ? questionData.correct_answers_text : [];
//     const correctText = correctTexts.length ? correctTexts[0] : '';

//     const exact = (selectedText !== null && selectedText !== undefined && selectedText !== '')
//         && (String(selectedText).trim() === String(correctText).trim());

//     const em = questionData.errors_mode || null;
//     const hasErrorsModeRules = em && Array.isArray(em.rules) && em.rules.length > 0;
//     const hasErrorsModeDf = em && em.default_false !== null && em.default_false !== undefined;
//     // ✅ hasErrorsMode inclut aussi question_default_false
//     const hasErrorsMode = hasErrorsModeRules || hasErrorsModeDf || hasQdf;

//     let base = exact ? points : 0;

//     if (!exact && !hasErrorsMode && defaultFalse !== 0) base = defaultFalse;
//     if (!exact && hasErrorsMode) {
//         if (hasErrorsModeRules) {
//             let matched = null;
//             for (const r of em.rules) {
//                 if (String(r.key).trim() === String(selectedText ?? '').trim()) { matched = r; break; }
//             }
//             if (matched) base = Number(matched.score);
//             else if (hasErrorsModeDf) base = Number(em.default_false);
//             else base = defaultFalse; // ✅ question_default_false ou default_false quiz
//         } else {
//             // ✅ pas de règles, mais question_default_false ou em.default_false défini
//             base = hasErrorsModeDf ? Number(em.default_false) : defaultFalse;
//         }
//     }

//     const minScore = questionData.min_score !== undefined ? questionData.min_score
//         : window.QUIZ_CONFIG.min_score !== undefined ? window.QUIZ_CONFIG.min_score : 0;
//     const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);
//     return clampScore(base, points, minScore, clampMax);
// }


// function scoreFillblanksQuestion(questionData, userAnswers) {
//     const points = Number(questionData.points || 1);
//     const blanks = Array.isArray(questionData.blanks) ? questionData.blanks : [];
//     const n = blanks.length || 1;
//     const scoringMode = questionData.scoring_mode || 'proportional';
//     const perFillblank = points / n;
//     const em = questionData.errors_mode || { blanks: [] };

//     const compareCfg = questionData.compare_answers
//         || questionData.quiz_compare_answers
//         || window.QUIZ_CONFIG.fillblanks?.compare_answers
//         || {};

//     const exerciseDefaultFalse = (questionData.default_false !== null && questionData.default_false !== undefined)
//         ? Number(questionData.default_false)
//         : null;
//     const hasExerciseDefaultFalse = (exerciseDefaultFalse !== null);

//     let correctCount = 0, anyWrongNonEmpty = false;
//     const blankStates = [];

//     for (let i = 0; i < blanks.length; i++) {
//         const blank = blanks[i];
//         const ua = String(userAnswers[i] ?? '');
//         const accepted = Array.isArray(blank.answers) ? blank.answers : [];
//         const isEmpty = (ua.trim() === '');
//         const isCorrect = !isEmpty && accepted.some(ans => compareAnswers(ua, ans, compareCfg));
//         if (isCorrect) correctCount++;
//         if (!isCorrect && !isEmpty) anyWrongNonEmpty = true;
//         blankStates.push({ isCorrect, isEmpty, ua });
//     }

//     const minScore = (questionData.min_score !== undefined)
//         ? questionData.min_score   // null explicite = pas de plancher
//         : (window.QUIZ_CONFIG.min_score !== undefined && window.QUIZ_CONFIG.min_score !== null)
//             ? window.QUIZ_CONFIG.min_score
//             : null;

//     const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

//     const hasAnyFillblankEm = blanks.some((_, i) => em.blanks && em.blanks[i] &&
//         (em.blanks[i].rules?.length > 0 || em.blanks[i].default_false !== null));

//     // ── DEBUG ────────────────────────────────────────────────────────────────
//     console.log('FB score debug', {
//         scoringMode,
//         hasAnyFillblankEm,
//         exerciseDefaultFalse,
//         hasExerciseDefaultFalse,
//         minScore,
//         blankStates: blankStates.map(s => ({ ...s })),
//         emBlanks: em.blanks
//     });
//     // ── END DEBUG ────────────────────────────────────────────────────────────

//     // ── all_or_nothing ou some_or_nothing AVEC errors_mode ──────────────────
//     if (scoringMode === 'all_or_nothing' || (scoringMode === 'some_or_nothing' && hasAnyFillblankEm)) {

//         if (!hasAnyFillblankEm) {
//             const allCorrect = (correctCount === blanks.length);
//             const base = allCorrect ? points
//                 : (anyWrongNonEmpty && hasExerciseDefaultFalse) ? exerciseDefaultFalse
//                 : anyWrongNonEmpty ? 0
//                 : 0;
//             return clampScore(base, points, minScore, clampMax);
//         }

//         let total = 0;
//         let hasUncoveredFillblank = false;

//         for (let i = 0; i < blanks.length; i++) {
//             const st = blankStates[i];

//             if (st.isCorrect) { total += perFillblank; continue; }

//             const fbEm = (em.blanks && em.blanks[i]) ? em.blanks[i] : null;
//             const blankDf = fbEm && fbEm.default_false !== null && fbEm.default_false !== undefined
//                 ? Number(fbEm.default_false) : null;
//             const hasFillblankDf = (blankDf !== null);
//             const effectiveDf = hasFillblankDf ? blankDf
//                 : hasExerciseDefaultFalse ? exerciseDefaultFalse
//                 : null;
//             const hasDf = (effectiveDf !== null);

//             console.log(`FB blank[${i}]`, { ua: st.ua, isEmpty: st.isEmpty, fbEm, blankDf, hasFillblankDf, effectiveDf, hasDf });

//             if (st.isEmpty) {
//                 if (hasDf) { total += effectiveDf; }
//                 else { hasUncoveredFillblank = true; break; }
//                 continue;
//             }

//             if (!fbEm || !fbEm.rules || fbEm.rules.length === 0) {
//                 if (hasDf) { total += effectiveDf; }
//                 else { hasUncoveredFillblank = true; break; }
//                 continue;
//             }

//             const rules = Array.isArray(fbEm.rules) ? fbEm.rules : [];
//             let matched = null;
//             for (const r of rules) {
//                 if (compareAnswers(st.ua, r.answer, compareCfg)) { matched = r; break; }
//             }

//             console.log(`FB blank[${i}] rules match`, { matched, rules });

//             if (matched) { total += Number(matched.score); }
//             else if (hasDf) { total += effectiveDf; }
//             else { hasUncoveredFillblank = true; break; }
//         }

//         console.log('FB result', { total, hasUncoveredFillblank, minScore });

//         if (hasUncoveredFillblank) total = 0;
//         return clampScore(total, points, minScore, clampMax);
//     }

//     // ── some_or_nothing SANS errors_mode ────────────────────────────────────
//     if (scoringMode === 'some_or_nothing') {
//         const base = anyWrongNonEmpty ? 0 : (perFillblank * correctCount);
//         return clampScore(base, points, minScore, clampMax);
//     }

//     // ── proportional ─────────────────────────────────────────────────────────
//     let total = perFillblank * correctCount;

//     for (let i = 0; i < blanks.length; i++) {
//         const st = blankStates[i];
//         if (st.isCorrect) continue;

//         const fbEm = (em.blanks && em.blanks[i]) ? em.blanks[i] : null;
//         const blankDf = fbEm && fbEm.default_false !== null && fbEm.default_false !== undefined
//             ? Number(fbEm.default_false) : null;
//         const hasFillblankDf = (blankDf !== null);
//         const effectiveDf = hasFillblankDf ? blankDf
//             : hasExerciseDefaultFalse ? exerciseDefaultFalse
//             : 0;

//         const rules = fbEm ? (Array.isArray(fbEm.rules) ? fbEm.rules : []) : [];
//         let matched = null;
//         for (const r of rules) { if (compareAnswers(st.ua, r.answer, compareCfg)) { matched = r; break; } }

//         if (matched) total += Number(matched.score);
//         else total += effectiveDf;
//     }

//     return clampScore(total, points, minScore, clampMax);
// }



// function scoreScdropdownQuestion(questionData, selectedTexts) {
//     const actual = Array.isArray(questionData.scdropdowns) ? questionData.scdropdowns
//         : (questionData.scdropdown ? [questionData.scdropdown] : []);
//     const per = actual.length > 0 ? Number(questionData.points || 1) / actual.length : Number(questionData.points || 1);
//     const points = Number(questionData.points || 1);
//     const scoringMode = questionData.scoring_mode || 'all_or_nothing';
//     const em = questionData.errors_mode || { scdropdowns: [] };

//     const minScore = (questionData.min_score !== undefined)
//         ? questionData.min_score   // null explicite = pas de plancher
//         : (window.QUIZ_CONFIG.min_score !== undefined && window.QUIZ_CONFIG.min_score !== null)
//             ? window.QUIZ_CONFIG.min_score
//             : null;

//     const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

//     // null = non défini (default_false: 0 est une valeur légitime)
//     const questionDefaultFalse = (questionData.default_false !== null && questionData.default_false !== undefined)
//         ? Number(questionData.default_false)
//         : null;
//     const hasQuestionDf = (questionDefaultFalse !== null);

//     const hasAnyDdEm = actual.some((_, i) => em.scdropdowns && em.scdropdowns[i] &&
//         (em.scdropdowns[i].rules?.length > 0 || em.scdropdowns[i].default_false !== null));

//     // Helper : évalue un scdropdown et retourne { isCorrect, isEmpty, chosenText }
//     const evalScdropdowns = () => actual.map((dd, i) => {
//         const opts = Array.isArray(dd.options) ? dd.options : [];
//         const correctText = String(opts[Number(dd.correct_index ?? 0)] ?? '').trim();
//         const chosenText = selectedTexts[i] ?? null;
//         const isEmpty = (chosenText === null);
//         const isCorrect = !isEmpty && String(chosenText).trim() === correctText;
//         return { isCorrect, isEmpty, chosenText, correctText };
//     });

//     // ── all_or_nothing ou some_or_nothing AVEC errors_mode ──────────────────
//     if (scoringMode === 'all_or_nothing' || (scoringMode === 'some_or_nothing' && hasAnyDdEm)) {

//         if (!hasAnyDdEm) {
//             // all_or_nothing SANS errors_mode
//             const states = evalScdropdowns();
//             const allCorrect = states.every(s => s.isCorrect);
//             const anyWrong = states.some(s => !s.isCorrect && !s.isEmpty);
//             const base = allCorrect ? points
//                 : (anyWrong && hasQuestionDf) ? questionDefaultFalse
//                 : anyWrong ? 0
//                 : 0;
//             return clampScore(base, points, minScore, clampMax);
//         }

//         // Avec errors_mode
//         let total = 0;
//         let hasUncoveredDd = false;
//         const states = evalScdropdowns();

//         for (let i = 0; i < actual.length; i++) {
//             const st = states[i];

//             if (st.isCorrect) { total += per; continue; }

//             const ddEm = (em.scdropdowns && em.scdropdowns[i]) ? em.scdropdowns[i] : null;
//             const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
//                 ? Number(ddEm.default_false) : null;
//             const hasDdDf = (ddDf !== null);
//             const effectiveDf = hasDdDf ? ddDf
//                 : hasQuestionDf ? questionDefaultFalse
//                 : null;
//             const hasDf = (effectiveDf !== null);

//             if (st.isEmpty) {
//                 if (hasDf) { total += effectiveDf; }
//                 else { hasUncoveredDd = true; break; }
//                 continue;
//             }

//             if (!ddEm || !ddEm.rules || ddEm.rules.length === 0) {
//                 if (hasDf) { total += effectiveDf; }
//                 else { hasUncoveredDd = true; break; }
//                 continue;
//             }

//             const rules = Array.isArray(ddEm.rules) ? ddEm.rules : [];
//             let matched = null;
//             for (const r of rules) {
//                 if (String(r.option).trim() === String(st.chosenText).trim()) { matched = r; break; }
//             }

//             if (matched) { total += Number(matched.score); }
//             else if (hasDf) { total += effectiveDf; }
//             else { hasUncoveredDd = true; break; }
//         }

//         if (hasUncoveredDd) total = 0;
//         return clampScore(total, points, minScore, clampMax);
//     }

//     // ── some_or_nothing SANS errors_mode ────────────────────────────────────
//     // 0 si au moins un scdropdown incorrect non vide, sinon (k/t)*n
//     if (scoringMode === 'some_or_nothing') {
//         const states = evalScdropdowns();
//         const anyWrong = states.some(s => !s.isCorrect && !s.isEmpty);
//         const correctCount = states.filter(s => s.isCorrect).length;
//         const base = anyWrong ? 0 : (per * correctCount);
//         return clampScore(base, points, minScore, clampMax);
//     }

//     // ── proportional ─────────────────────────────────────────────────────────
//     const states = evalScdropdowns();
//     let total = 0;

//     for (let i = 0; i < actual.length; i++) {
//         const st = states[i];

//         if (st.isCorrect) { total += per; continue; }
//         if (st.isEmpty) continue;

//         const ddEm = (em.scdropdowns && em.scdropdowns[i]) ? em.scdropdowns[i] : null;
//         const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
//             ? Number(ddEm.default_false) : null;
//         const effectiveDf = (ddDf !== null) ? ddDf
//             : hasQuestionDf ? questionDefaultFalse
//             : 0;

//         const rules = ddEm ? (Array.isArray(ddEm.rules) ? ddEm.rules : []) : [];
//         let matched = null;
//         for (const r of rules) {
//             if (String(r.option).trim() === String(st.chosenText).trim()) { matched = r; break; }
//         }
//         if (matched) total += Number(matched.score);
//         else total += effectiveDf;
//     }

//     return clampScore(total, points, minScore, clampMax);
// }



// // ─────────────────────────────────────────────────────────────────────────────
// // Validation
// // ─────────────────────────────────────────────────────────────────────────────

// function validateQuiz(quizId) {
//     const quiz = document.querySelector(`[data-quiz-id="${quizId}"]`);
//     if (!quiz) return;
//     const quizType = quiz.dataset.quizType;
//     if (quizType === 'fillblanks') validateFillblanksQuiz(quiz, quizId);
//     else if (quizType === 'scdropdown') validateScdropdownQuiz(quiz, quizId);
//     else validateChoiceQuiz(quiz, quizId);
//     saveAnswers(quizId);
// }

// /**
//  * validateChoiceQuiz — mcquiz / scquiz
//  *
//  * ✅ Post-shuffle : on lit le texte de chaque réponse cochée depuis
//  *    data-answer-text sur le <label>, et on passe ces textes à scoreChoiceQuestion
//  *    / scoreScQuizQuestion (comparaison par texte, pas par index DOM).
//  */
// function validateChoiceQuiz(quiz, quizId) {
//     const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
//     let totalPoints = 0, earnedPoints = 0;
//     const correctionData = decryptAnswers(quiz.dataset.encrypted);
//     //  DEBUG
//     console.log('PAYLOAD SC-14', JSON.stringify(correctionData, null, 2));

//     if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
//     const showCorrection = shouldShowCorrections(quiz);

//     questions.forEach((questionEl) => {
//         const questionId = parseInt(questionEl.dataset.questionId, 10);
//         const qData = correctionData.questions.find(q => q.number === questionId);
//         if (!qData) return;
//         totalPoints += Number(qData.points || 1);

//         // ✅ Lire les textes cochés (indépendant de l'ordre DOM post-shuffle)
//         const checkedLabels = Array.from(questionEl.querySelectorAll(
//             'input[type="checkbox"]:checked, input[type="radio"]:checked'
//         )).map(input => input.closest('.mkq-answer'));

//         const selectedTexts = checkedLabels
//             .map(label => label ? (label.dataset.answerText || '') : '')
//             .filter(t => t !== '');

//         let score;
//         if (correctionData.quiz_type === 'scquiz') {
//             score = scoreScQuizQuestion(qData, selectedTexts.length ? selectedTexts[0] : '');
//         } else {
//             score = scoreChoiceQuestion(qData, selectedTexts);
//         }
//         earnedPoints += score;

//         if (showCorrection) {
//             const correctTexts = new Set(
//                 (qData.correct_answers_text || []).map(t => t.trim())
//             );
//             // Parcourir tous les labels dans l'ordre DOM actuel (post-shuffle)
//             questionEl.querySelectorAll('.mkq-answer').forEach(label => {
//                 const input = label.querySelector('input');
//                 const feedback = label.querySelector('.mkq-answer-feedback');
//                 label.classList.remove('correct', 'incorrect');
//                 if (feedback) { feedback.style.display = 'none'; feedback.textContent = ''; feedback.classList.remove('correct', 'incorrect'); }

//                 const answerText = (label.dataset.answerText || '').trim();
//                 const isChecked = input && input.checked;
//                 const isCorrectAnswer = correctTexts.has(answerText);

//                 if (isCorrectAnswer) {
//                     label.classList.add('correct');
//                     if (feedback) { feedback.textContent = t("feedback.correct", "Correct"); feedback.classList.add('correct'); feedback.style.display = 'inline'; }
//                 } else if (isChecked) {
//                     label.classList.add('incorrect');
//                     if (feedback) { feedback.textContent = t("feedback.incorrect", "Incorrect"); feedback.classList.add('incorrect'); feedback.style.display = 'inline'; }
//                 }
//             });
//         }
//     });
//     displayScorePoints(quizId, earnedPoints, totalPoints);
// }

// function validateFillblanksQuiz(quiz, quizId) {
//     const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
//     let totalPoints = 0, earnedPoints = 0;
//     const correctionData = decryptAnswers(quiz.dataset.encrypted);
    
//     // DEBUG
//     console.log('PAYLOAD FB', JSON.stringify(correctionData, null, 2)); // ← ajoute ici

//     if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
//     const showCorrection = shouldShowCorrections(quiz);
//     const droppedElements = [];
//     questions.forEach((questionEl) => {
//         const questionId = parseInt(questionEl.dataset.questionId, 10);
//         const qData = correctionData.questions.find(q => q.number === questionId);
//         if (!qData) return;
//         totalPoints += Number(qData.points || 1);
//         const blankInputs = questionEl.querySelectorAll('.mkq-blank-input');
//         const userAnswers = [];
//         blankInputs.forEach((input) => userAnswers.push(input.value));

//         qData.quiz_compare_answers = correctionData.compare_answers || null;

//         earnedPoints += scoreFillblanksQuestion(qData, userAnswers);
//         if (!showCorrection) return;
//         blankInputs.forEach((input, blankIndex) => {
//             const userAnswer = input.value;
//             const blank = qData.blanks[blankIndex];
//             if (!blank) return;
//             const compareCfg = qData.compare_answers
//                 || correctionData.compare_answers
//                 || window.QUIZ_CONFIG.fillblanks?.compare_answers
//                 || {};
//             const isCorrect = blank.answers.some(answer => compareAnswers(userAnswer, answer, compareCfg));
//             const nextSibling = input.nextSibling;
//             if (nextSibling && nextSibling.classList?.contains('mkq-blank-correction-wrapper')) nextSibling.remove();
//             if (isCorrect) {
//                 input.style.display = ''; input.classList.add('correct'); input.classList.remove('incorrect');
//             } else {
//                 input.style.display = 'none'; input.classList.remove('correct'); input.classList.add('incorrect');
//                 const wrapper = document.createElement('span');
//                 wrapper.className = 'mkq-blank-correction-wrapper';
//                 const label = document.createElement('span');
//                 label.className = 'mkq-blank-wrong-label';
//                 const displayAnswer = userAnswer.trim();
//                 label.innerHTML = `<s>${displayAnswer || t("ui.empty_answer", "(empty)")}</s>`;
//                 wrapper.appendChild(label);
//                 const scdropdown = createFillblanksMathJaxScdropdown(
//                     `mkq-scdropdown-${quizId}-q${questionId}-blank${blankIndex}`, blank.answers
//                 );
//                 wrapper.appendChild(scdropdown);
//                 input.after(wrapper);
//                 droppedElements.push(wrapper);
//             }
//         });
//     });
//     displayScorePoints(quizId, earnedPoints, totalPoints);
//     if (droppedElements.length > 0) {
//         setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise(droppedElements).catch(() => { }); }, 150);
//     }
// }

// /**
//  * validateScdropdownQuiz
//  *
//  * ✅ Post-shuffle : on lit le texte de l'option sélectionnée depuis
//  *    data-option-text sur le div sélectionné, et on passe ce texte à
//  *    scoreScdropdownQuestion (comparaison par texte).
//  *
//  *    La correction visuelle compare aussi par texte vs options[correct_index].
//  */
// function validateScdropdownQuiz(quiz, quizId) {
//     const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
//     let totalPoints = 0, earnedPoints = 0;
//     const correctionData = decryptAnswers(quiz.dataset.encrypted);
//     if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
//     const showCorrection = shouldShowCorrections(quiz);

//     questions.forEach((questionEl) => {
//         const questionId = parseInt(questionEl.dataset.questionId, 10);
//         const qData = correctionData.questions.find(q => q.number === questionId);
//         if (!qData) return;
//         totalPoints += Number(qData.points || 1);

//         const scdropdownEls = Array.from(questionEl.querySelectorAll('.mkq-custom-scdropdown'));
//         const ddDataList = Array.isArray(qData.scdropdowns) ? qData.scdropdowns : (qData.scdropdown ? [qData.scdropdown] : []);
//         const count = Math.min(scdropdownEls.length, ddDataList.length);

//         // ✅ Lire le texte sélectionné (post-shuffle safe)
//         const selectedTexts = [];
//         for (let i = 0; i < count; i++) {
//             const ddEl = scdropdownEls[i];
//             const rawValue = ddEl.dataset.selectedValue;
//             if (rawValue === undefined || rawValue === null || rawValue === '') {
//                 selectedTexts.push(null); // vide
//             } else {
//                 // Trouver l'option sélectionnée par sa position DOM (data-value mis à jour au shuffle)
//                 const selectedOptEl = ddEl.querySelector(`.mkq-custom-scdropdown-option[data-value="${rawValue}"]`);
//                 const optText = selectedOptEl ? (selectedOptEl.dataset.optionText || selectedOptEl.textContent.trim()) : null;
//                 selectedTexts.push(optText);
//             }
//         }

//         // // DEBUG
//         // console.log('DD debug', scdropdownEls.length, selectedTexts, ddDataList);

//         earnedPoints += scoreScdropdownQuestion(qData, selectedTexts);

//         if (!showCorrection) return;

//         for (let i = 0; i < count; i++) {
//             const scdropdown = scdropdownEls[i];
//             const ddData = ddDataList[i];
//             const chosenText = selectedTexts[i];
//             const isEmpty = (chosenText === null);
//             const allOptions = ddData.options || [];
//             const correctText = String(allOptions[ddData.correct_index] ?? '').trim();
//             const isCorrect = !isEmpty && String(chosenText).trim() === correctText;

//             const next = scdropdown.nextSibling;
//             if (next && next.classList?.contains('mkq-scdropdown-correction-wrapper') && next.dataset?.forId === scdropdown.id) next.remove();

//             if (isCorrect) {
//                 scdropdown.style.display = ''; scdropdown.classList.remove('incorrect'); scdropdown.classList.add('correct');
//             } else {
//                 const wrongAnswerHTML = isEmpty
//                     ? t("ui.empty_answer", "(empty)")
//                     : convertLatexDelimiters(String(chosenText));
//                 scdropdown.style.display = 'none';
//                 const wrapper = document.createElement('span');
//                 wrapper.className = 'mkq-scdropdown-correction-wrapper';
//                 wrapper.dataset.forId = scdropdown.id;
//                 wrapper.style.cssText = 'display:inline-flex;align-items:center;blank:.5em;position:relative;';
//                 const wrongLabel = document.createElement('span');
//                 wrongLabel.className = 'mkq-blank-wrong-label'; wrongLabel.innerHTML = wrongAnswerHTML;
//                 wrapper.appendChild(wrongLabel);
//                 const correctScdropdown = document.createElement('div');
//                 correctScdropdown.className = 'mkq-custom-scdropdown correct';
//                 correctScdropdown.style.cssText = 'display:inline-block;pointer-events:none;';
//                 const correctBtn = document.createElement('button');
//                 correctBtn.type = 'button'; correctBtn.className = 'mkq-custom-scdropdown-btn correct'; correctBtn.style.cursor = 'default';
//                 const correctValueSpan = document.createElement('span');
//                 correctValueSpan.className = 'mkq-custom-scdropdown-value';
//                 correctValueSpan.innerHTML = convertLatexDelimiters(correctText);
//                 const arrow = document.createElement('span');
//                 arrow.className = 'mkq-custom-scdropdown-arrow'; arrow.textContent = '✓'; arrow.style.color = 'green';
//                 correctBtn.appendChild(correctValueSpan); correctBtn.appendChild(arrow);
//                 correctScdropdown.appendChild(correctBtn); wrapper.appendChild(correctScdropdown);
//                 scdropdown.after(wrapper);
//                 setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise([wrapper]).catch(() => { }); }, 150);
//             }
//         }
//     });
//     displayScorePoints(quizId, earnedPoints, totalPoints);
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Scdropdown interactions
// // ─────────────────────────────────────────────────────────────────────────────

// function toggleScdropdownQuiz(button) {
//     const scdropdown = button.closest('.mkq-custom-scdropdown');
//     const options = scdropdown.querySelector('.mkq-custom-scdropdown-options');
//     const isOpen = options.style.display === 'block';
//     document.querySelectorAll('.mkq-custom-scdropdown-options').forEach(opt => opt.style.display = 'none');
//     options.style.display = isOpen ? 'none' : 'block';
// }

// function selectScdropdownQuizOption(optionElement) {
//     const scdropdown = optionElement.closest('.mkq-custom-scdropdown');
//     const button = scdropdown.querySelector('.mkq-custom-scdropdown-btn');
//     const valueSpan = button.querySelector('.mkq-custom-scdropdown-value');
//     const optionsDiv = scdropdown.querySelector('.mkq-custom-scdropdown-options');
//     valueSpan.innerHTML = optionElement.innerHTML;
//     scdropdown.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
//     optionElement.classList.add('selected');
//     // ✅ data-selected-value stocke la position DOM courante (post-shuffle)
//     scdropdown.dataset.selectedValue = optionElement.dataset.value;
//     optionsDiv.style.display = 'none';
//     if (window.MathJax?.typesetPromise) MathJax.typesetPromise([valueSpan]).catch(() => { });
// }

// document.addEventListener('click', (e) => {
//     if (!e.target.closest('.mkq-custom-scdropdown'))
//         document.querySelectorAll('.mkq-custom-scdropdown-options').forEach(opt => opt.style.display = 'none');
// });

// // ─────────────────────────────────────────────────────────────────────────────
// // Fillblanks correction scdropdown helpers
// // ─────────────────────────────────────────────────────────────────────────────

// function convertLatexDelimiters(text) {
//     let result = text;
//     result = result.replace(/\$\$(.*?)\$\$/g, '\\[$1\\]');
//     result = result.replace(/\$([^\$]+)\$/g, '\\($1\\)');
//     return result;
// }

// function createFillblanksMathJaxScdropdown(id, answers) {
//     const container = document.createElement('div');
//     container.className = 'mkq-custom-select'; container.id = id;
//     container.setAttribute('role', 'combobox'); container.setAttribute('aria-haspopup', 'listbox');
//     container.setAttribute('aria-expanded', 'false');
//     const button = document.createElement('button');
//     button.className = 'mkq-custom-select-btn'; button.type = 'button';
//     button.setAttribute('aria-controls', `${id}-listbox`);
//     const valueSpan = document.createElement('span');
//     valueSpan.className = 'mkq-custom-select-value';
//     valueSpan.innerHTML = convertLatexDelimiters(answers[0]);
//     button.appendChild(valueSpan);
//     const arrow = document.createElement('span');
//     arrow.className = 'mkq-custom-select-arrow'; arrow.textContent = '▾'; arrow.setAttribute('aria-hidden', 'true');
//     const listbox = document.createElement('div');
//     listbox.className = 'mkq-custom-select-listbox'; listbox.id = `${id}-listbox`;
//     listbox.setAttribute('role', 'listbox'); listbox.setAttribute('tabindex', '-1');
//     answers.forEach((answer, index) => {
//         const option = document.createElement('div');
//         option.className = 'mkq-custom-select-option'; option.setAttribute('role', 'option');
//         option.setAttribute('tabindex', index === 0 ? '0' : '-1');
//         option.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
//         option.dataset.value = answer; option.innerHTML = convertLatexDelimiters(answer);
//         option.addEventListener('click', () => selectFillblanksOption(container, button, valueSpan, listbox, option));
//         option.addEventListener('keydown', (e) => {
//             if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectFillblanksOption(container, button, valueSpan, listbox, option); }
//         });
//         listbox.appendChild(option);
//     });
//     button.addEventListener('click', () => toggleFillblanksScdropdown(container, listbox));
//     listbox.addEventListener('keydown', (e) => handleFillblanksKeyboardNav(e, container, listbox));
//     container.appendChild(button); container.appendChild(arrow); container.appendChild(listbox);
//     document.addEventListener('click', (e) => { if (!container.contains(e.target)) container.setAttribute('aria-expanded', 'false'); });
//     setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise([container]).catch(() => { }); }, 50);
//     return container;
// }

// function toggleFillblanksScdropdown(container, listbox) {
//     const isOpen = container.getAttribute('aria-expanded') === 'true';
//     container.setAttribute('aria-expanded', !isOpen ? 'true' : 'false');
//     if (!isOpen) listbox.focus();
// }

// function selectFillblanksOption(container, button, valueSpan, listbox, option) {
//     listbox.querySelectorAll('.mkq-custom-select-option').forEach(opt => opt.setAttribute('aria-selected', 'false'));
//     option.setAttribute('aria-selected', 'true');
//     valueSpan.innerHTML = option.innerHTML;
//     if (valueSpan.innerHTML.includes('\\(') || valueSpan.innerHTML.includes('\\[')) {
//         if (window.MathJax?.typesetPromise) MathJax.typesetPromise([button]).catch(() => { });
//     }
//     container.setAttribute('aria-expanded', 'false'); button.focus();
// }

// function handleFillblanksKeyboardNav(e, container, listbox) {
//     const options = Array.from(listbox.querySelectorAll('.mkq-custom-select-option'));
//     const current = document.activeElement.classList.contains('mkq-custom-select-option') ? document.activeElement : options[0];
//     let idx = options.indexOf(current);
//     if (e.key === 'Escape') { e.preventDefault(); container.setAttribute('aria-expanded', 'false'); container.querySelector('.mkq-custom-select-btn').focus(); }
//     if (e.key === 'ArrowDown') { e.preventDefault(); options[Math.min(idx + 1, options.length - 1)].focus(); }
//     if (e.key === 'ArrowUp') { e.preventDefault(); options[Math.max(idx - 1, 0)].focus(); }
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Display score
// // ─────────────────────────────────────────────────────────────────────────────

// function displayScorePoints(quizId, earned, total) {
//     const scoreElement = document.getElementById(`score-${quizId}`);
//     if (!scoreElement) return;
//     const e = Number(earned), ttot = Number(total) > 0 ? Number(total) : 0;
//     const pct = ttot > 0 ? (e / ttot) * 100 : 0;
//     scoreElement.textContent = `${t("ui.score_label", "Score")}: ${e.toFixed(2)}/${ttot.toFixed(2)} (${pct.toFixed(1)}%)`;
//     scoreElement.style.display = 'block';
//     scoreElement.classList.remove('good', 'medium', 'bad');
//     if (pct >= 75) scoreElement.classList.add('good');
//     else if (pct >= 50) scoreElement.classList.add('medium');
//     else scoreElement.classList.add('bad');
// }

// // ─────────────────────────────────────────────────────────────────────────────
// // Crypto / storage
// // ─────────────────────────────────────────────────────────────────────────────

// function decryptAnswers(encryptedData) {
//     try {
//         const key = window.QUIZ_CONFIG.encryption_key || 'default-key-change-me';
//         const encrypted = atob(encryptedData);
//         const keyBytes = new TextEncoder().encode(key);
//         const encryptedBytes = new Uint8Array(encrypted.split('').map(c => c.charCodeAt(0)));
//         const decrypted = new Uint8Array(encryptedBytes.length);
//         for (let i = 0; i < encryptedBytes.length; i++) decrypted[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
//         return JSON.parse(new TextDecoder().decode(decrypted));
//     } catch (e) {
//         console.error(t("ui.error_decrypting", "Decrypting Error"), e);
//         return null;
//     }
// }


// function saveAnswers(quizId) {
//     const quiz = document.querySelector(`[data-quiz-id="${quizId}"]`);
//     if (!quiz) return;

//     // ✅ Vérifier save_answers depuis le payload chiffré
//     const correctionData = decryptAnswers(quiz.dataset.encrypted);
//     if (correctionData && correctionData.save_answers === false) {
//         localStorage.removeItem(`mkq_answers_${quizId}`);
//         return;
//     }

//     const answers = {};
//     quiz.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked').forEach(input => { answers[input.id] = true; });
//     quiz.querySelectorAll('.mkq-blank-input').forEach(input => { if (input.value.trim() !== '') answers[input.id] = input.value; });
//     quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(scdropdown => { if (scdropdown.dataset.selectedValue) answers[scdropdown.id] = scdropdown.dataset.selectedValue; });
//     if (Object.keys(answers).length > 0) localStorage.setItem(`mkq_answers_${quizId}`, JSON.stringify(answers));
//     else localStorage.removeItem(`mkq_answers_${quizId}`);
// }


// function loadSavedAnswers() {
//     document.querySelectorAll('.mkdocs-superquiz').forEach(quiz => {
//         const quizId = quiz.dataset.quizId;
//         try {
//             // ✅ Vérifier save_answers avant de restaurer
//             const correctionData = decryptAnswers(quiz.dataset.encrypted);
//             if (correctionData && correctionData.save_answers === false) {
//                 localStorage.removeItem(`mkq_answers_${quizId}`);
//                 // ✅ Forcer le reset visuel des inputs (cache navigateur)
//                 quiz.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => { input.checked = false; });
//                 quiz.querySelectorAll('.mkq-blank-input').forEach(input => { input.value = ''; });
//                 quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(scdropdown => {
//                     scdropdown.dataset.selectedValue = '';
//                     const valueSpan = scdropdown.querySelector('.mkq-custom-scdropdown-value');
//                     if (valueSpan) valueSpan.textContent = '--';
//                     scdropdown.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
//                 });
//                 return;
//             }

//             const savedAnswersJson = localStorage.getItem(`mkq_answers_${quizId}`);
//             if (!savedAnswersJson) return;
//             const savedAnswers = JSON.parse(savedAnswersJson);
//             if (!savedAnswers || typeof savedAnswers !== 'object') { localStorage.removeItem(`mkq_answers_${quizId}`); return; }
//             Object.keys(savedAnswers).forEach(inputId => {
//                 const input = document.getElementById(inputId);
//                 if (!input) return;
//                 const savedValue = savedAnswers[inputId];
//                 if (input.type === 'checkbox' || input.type === 'radio') { if (typeof savedValue === 'boolean') input.checked = savedValue; }
//                 else if (input.classList.contains('mkq-blank-input')) input.value = String(savedValue);
//                 else if (input.classList.contains('mkq-custom-scdropdown')) {
//                     input.dataset.selectedValue = savedValue;
//                     const valueSpan = input.querySelector('.mkq-custom-scdropdown-value');
//                     const selectedOption = input.querySelector(`.mkq-custom-scdropdown-option[data-value="${savedValue}"]`);
//                     if (valueSpan && selectedOption) valueSpan.innerHTML = selectedOption.innerHTML;
//                 }
//             });
//         } catch (error) { localStorage.removeItem(`mkq_answers_${quizId}`); }
//     });
// }


// function refreshAllQuizzes() {
//     const quizzes = document.querySelectorAll('.mkdocs-superquiz');
//     if (quizzes.length === 0) return;
//     const pageUuid = quizzes[0]?.dataset.pageUuid;
//     quizzes.forEach(quiz => {
//         const quizId = quiz.dataset.quizId;
//         const quizType = quiz.dataset.quizType;
//         if (quizType === 'fillblanks') {
//             quiz.querySelectorAll('.mkq-blank-correction-wrapper').forEach(w => w.remove());
//             quiz.querySelectorAll('.mkq-blank-input').forEach(input => { input.value = ''; input.style.display = ''; input.classList.remove('correct', 'incorrect'); });
//         } else if (quizType === 'scdropdown') {
//             quiz.querySelectorAll('.mkq-scdropdown-correction-wrapper').forEach(w => w.remove());
//             quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(scdropdown => {
//                 scdropdown.style.display = ''; scdropdown.dataset.selectedValue = '';
//                 const valueSpan = scdropdown.querySelector('.mkq-custom-scdropdown-value');
//                 if (valueSpan) valueSpan.textContent = '--';
//                 scdropdown.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
//                 scdropdown.classList.remove('correct', 'incorrect');
//             });
//         } else {
//             quiz.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => { input.checked = false; });
//         }
//         quiz.querySelectorAll('.mkq-answer').forEach(answer => {
//             answer.classList.remove('correct', 'incorrect');
//             const feedback = answer.querySelector('.mkq-answer-feedback');
//             if (feedback) { feedback.style.display = 'none'; feedback.textContent = ''; feedback.classList.remove('correct', 'incorrect'); }
//         });
//         const scoreElement = document.getElementById(`score-${quizId}`);
//         if (scoreElement) { scoreElement.style.display = 'none'; scoreElement.textContent = ''; scoreElement.classList.remove('good', 'medium', 'bad'); }
//         const lockBtn = quiz.querySelector('.mkq-unlock-btn, .mkq-lock-indicator');
//         if (lockBtn && !lockBtn.classList.contains('unlocked')) {
//             if (lockBtn.getAttribute('data-lock-state') === 'locked') updateLockIcon(lockBtn, false);
//         }
//         localStorage.removeItem(`mkq_answers_${quizId}`);
//     });
//     if (pageUuid) {
//         const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
//         localStorage.setItem('mkq_unlocks', JSON.stringify(unlocks.filter(u => u.page_id !== pageUuid)));
//     }
//     showRefreshNotification();
// }

// function showRefreshNotification() {
//     const notification = document.createElement('div');
//     notification.textContent = `✅ ${t("ui.all_quizzes_have_been_reset", "All Quizzes have been reset")}`;
//     notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4caf50;color:white;padding:1rem 1.5rem;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:10000;font-weight:500;font-size:2em;animation:slideIn 0.3s ease-out;';
//     document.body.appendChild(notification);
//     setTimeout(() => {
//         notification.style.opacity = '0'; notification.style.transition = 'opacity 0.3s';
//         setTimeout(() => notification.remove(), 300);
//     }, 2000);
// }

// function addRefreshButton() {
//     const pageTitle = document.querySelector('.md-content h1');
//     if (!pageTitle) return;
//     const quizzes = document.querySelectorAll('.mkdocs-superquiz');
//     if (quizzes.length === 0) return;
//     const refreshBtn = document.createElement('button');
//     refreshBtn.className = 'mkq-refresh-page-btn'; refreshBtn.title = t("ui.reset_all_quizzes", "Reset all quizzes");
//     refreshBtn.innerHTML = `<span class="mkq-btn-icon">🔄</span><span class="mkq-btn-text">${t("ui.reset_all_quizzes", "Reset all quizzes")}</span>`;
//     refreshBtn.addEventListener('click', () => refreshAllQuizzes());
//     const unlockPageBtn = document.createElement('button');
//     unlockPageBtn.className = 'mkq-unlock-page-btn'; unlockPageBtn.title = t("ui.unlock_all_page_quizzes", "Unlock all page quizzes");
//     unlockPageBtn.innerHTML = `<span class="mkq-btn-icon">🔓</span><span class="mkq-btn-text">${t("ui.unlock_all_page_quizzes", "Unlock all page quizzes")}</span>`;
//     unlockPageBtn.addEventListener('click', () => showUnlockModal('page'));
//     const buttonsContainer = document.createElement('div');
//     buttonsContainer.className = 'mkq-page-buttons';
//     buttonsContainer.appendChild(unlockPageBtn); buttonsContainer.appendChild(refreshBtn);
//     pageTitle.appendChild(buttonsContainer);
// }

// function clearAllQuizData() {
//     Object.keys(localStorage).filter(key => key.startsWith('mkq_')).forEach(key => localStorage.removeItem(key));
//     setTimeout(() => location.reload(), 1000);
// }
// window.clearAllQuizData = clearAllQuizData;

// function initQuizPlugin() {
//     if (!window.QUIZ_CONFIG || !window.QUIZ_CONFIG.i18n) return;
//     createUnlockModal();
//     // ✅ Randomisation AVANT le chargement des réponses sauvegardées
//     //    pour que les index DOM soient stables quand on restaure les valeurs
//     applyRandomization();
//     restoreUnlockedCorrections();
//     loadSavedAnswers();
//     addRefreshButton();
// }

// if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initQuizPlugin);
// else initQuizPlugin();

// ===========================================================================================================
// ===========================================================================================================
// ===========================================================================================================

// quiz.js
/**
 * mkdocs-superquiz - Logique principale JavaScript
 */

window.QUIZ_CONFIG = window.QUIZ_CONFIG || {};

function t(key, fallback = "") {
    const keys = key.split('.');
    let value = window.QUIZ_CONFIG?.i18n;
    for (const k of keys) {
        if (value && typeof value === 'object') value = value[k];
        else return fallback || key;
    }
    return value || fallback || key;
}

let currentUnlockContext = null;

function createUnlockModal() {
    const modal = document.createElement('div');
    modal.id = 'mkq-unlock-modal';
    modal.className = 'mkq-modal';
    modal.innerHTML = `
        <div class="mkq-modal-content">
            <button class="mkq-modal-close" onclick="closeUnlockModal()">×</button>
            <h3 id="mkq-unlock-title">${t("ui.unlock_title", "Unlock Correction")}</h3>
            <div class="qr-code-section">
                <p>${t("ui.unlock_qr_instruction", "Ask your teacher to scan this QR code:")}</p>
                <div id="qr-code-container"></div>
            </div>
            <div class="unlock-input-section">
                <label for="mkq-unlock-code-input">${t("ui.unlock_code_label", "Unlock code (provided by teacher):")}</label>
                <input type="text" id="mkq-unlock-code-input" placeholder="${t("ui.unlock_code_placeholder", "e.g., A7F3E9C2")}">
                <button onclick="validateUnlockCode()">${t("ui.unlock_validate", "Validate")}</button>
            </div>
        </div>
    `;
    document.body.appendChild(modal);
}

function getSiteUrl() {
    const currentOrigin = window.location.origin;
    const currentPath = window.location.pathname;
    const baseElement = document.querySelector('base');
    if (baseElement && baseElement.href) {
        const baseUrl = new URL(baseElement.href);
        return baseUrl.origin + baseUrl.pathname.replace(/\/$/, '');
    }
    const pathParts = currentPath.split('/').filter(p => p);
    if (pathParts.length > 0) {
        const firstPart = pathParts[0];
        const isProjectName = !firstPart.includes('.html') && !firstPart.includes('.') &&
            firstPart !== 'search' && firstPart !== 'assets';
        if (currentPath.includes('/mkdocs-superquiz/')) return currentOrigin + '/mkdocs-superquiz';
        const knownPages = ['index', 'sample_quiz', 'search', 'sitemap'];
        if (isProjectName && !knownPages.includes(firstPart)) return currentOrigin + '/' + firstPart;
    }
    return currentOrigin;
}

function showUnlockModal(type, exerciseId = null, questionId = null) {
    const modal = document.getElementById('mkq-unlock-modal');
    if (!modal) return;
    const pageUuid = document.querySelector('.mkdocs-superquiz')?.dataset.pageUuid;
    if (!pageUuid) return;
    const siteUrl = getSiteUrl();
    const data = { type, page_id: pageUuid, page_title: document.title, site_url: siteUrl };
    if (type === 'exercise' || type === 'question') {
        data.exercise_id = exerciseId;
        const exerciseElement = document.querySelector(`[data-quiz-id="${exerciseId}"]`);
        if (exerciseElement) {
            const titleElement = exerciseElement.querySelector('.admonition-title');
            let title = titleElement ? titleElement.textContent.trim() : '';
            title = title.replace(/🔒|🔓|❌|🔐|✅/g, '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
            data.exercise_title = title;
        }
    }
    if (type === 'question') {
        data.question_id = questionId;
        const questionElement = document.querySelector(`[data-quiz-id="${exerciseId}"] [data-question-id="${questionId}"]`);
        if (questionElement) {
            const textElement = questionElement.querySelector('.mkq-question-text');
            let questionText = textElement ? textElement.textContent.trim() : '';
            questionText = questionText.replace(/🔒|🔓|❌|🔐|✅/g, '').replace(/\n/g, ' ').replace(/\s+/g, ' ').trim();
            data.question_text = questionText;
        }
    }
    currentUnlockContext = data;
    const titleElement = document.getElementById('mkq-unlock-title');
    if (titleElement) {
        if (type === 'page') titleElement.textContent = t("ui.unlock_page_title", "Unlock All Page Quizzes");
        else if (type === 'exercise') titleElement.textContent = `${t("ui.unlock_quiz", "Unlock Quiz")} ${exerciseId}`;
        else titleElement.textContent = `${t("ui.unlock_quiz_question", "Unlock this quiz question")} ${questionId}`;
    }
    generateQRCode(data);
    modal.classList.add('active');
}

function generateQRCode(data) {
    const container = document.getElementById('qr-code-container');
    if (!container) return;
    const jsonString = JSON.stringify(data);
    const utf8Bytes = new TextEncoder().encode(jsonString);
    let binaryString = '';
    for (let i = 0; i < utf8Bytes.length; i++) binaryString += String.fromCharCode(utf8Bytes[i]);
    const encodedData = btoa(binaryString);
    const unlockUrl = `${data.site_url}/quiz-unlock.html?data=${encodedData}`;
    if (typeof qrcode === 'undefined') {
        container.innerHTML = `<p style="color:red;">${t("ui.error_no_library", "Error: No QR Code library has been downloaded")}</p>`;
        return;
    }
    try {
        const qr = qrcode(0, 'H');
        qr.addData(unlockUrl);
        qr.make();
        container.innerHTML = qr.createSvgTag(4, 4);
        const svg = container.querySelector('svg');
        if (svg) {
            svg.style.display = 'block'; svg.style.maxWidth = '256px'; svg.style.height = 'auto';
            svg.style.margin = '0 auto'; svg.style.padding = '10px'; svg.style.background = 'white';
            svg.style.borderRadius = '8px'; svg.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
        }
    } catch (error) {
        container.innerHTML = `<p style="color:red;">${t("ui.error_generating_qrcode", "Error generating QR Code")}</p>`;
    }
}

function closeUnlockModal() {
    const modal = document.getElementById('mkq-unlock-modal');
    if (modal) modal.classList.remove('active');
    const input = document.getElementById('mkq-unlock-code-input');
    if (input) input.value = '';
}

async function validateUnlockCode() {
    const input = document.getElementById('mkq-unlock-code-input');
    if (!input) return;
    const code = input.value.trim().toUpperCase();
    if (!code) { alert(t("ui.please_enter_code", "Please enter a code")); return; }
    if (!currentUnlockContext) { alert(t("ui.error_no_unlocking_context", "Error: Unlocking context not found")); return; }
    const expectedCode = await calculateUnlockCode(currentUnlockContext);
    if (code === expectedCode) {
        unlockCorrections(currentUnlockContext);
        saveUnlockState(currentUnlockContext);
        closeUnlockModal();
    } else {
        alert(t("ui.error_incorrect_code", "Incorrect code. Check with your teacher."));
    }
}

async function calculateUnlockCode(context) {
    const teacherCodeHash = window.QUIZ_CONFIG.teacher_code_hash || '';
    let payload = teacherCodeHash + context.page_id + context.type;
    if (context.type === 'exercise' || context.type === 'question') payload += (context.exercise_id || '');
    if (context.type === 'question') payload += (context.question_id || '');
    const hash = await sha256(payload);
    return hash.substring(0, 8).toUpperCase();
}

async function sha256(str) {
    const buffer = new TextEncoder().encode(str);
    const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}

function updateLockIcon(element, unlocked) {
    if (!element) return;
    if (unlocked) {
        element.textContent = '🔓';
        element.classList.remove('locked'); element.classList.add('unlocked');
        element.setAttribute('data-lock-state', 'unlocked');
        element.setAttribute('title', t("ui.unlocked_corrections", "Unlocked corrections"));
        element.onclick = null; element.style.cursor = 'default';
    } else {
        element.textContent = '🔒';
        element.classList.remove('unlocked'); element.classList.add('locked');
        element.setAttribute('data-lock-state', 'locked');
        element.setAttribute('title', t("ui.lock_tooltip_locked", "Click to unlock corrections"));
    }
}

function shouldShowCorrections(quiz) {
    const lockIndicator = quiz.querySelector('.mkq-lock-indicator, .mkq-unlock-btn');
    if (!lockIndicator) return true;
    const lockState = lockIndicator.getAttribute('data-lock-state');
    if (lockState === 'locked-permanent') return false;
    if (lockState === 'unlocked') return true;
    if (lockState === 'locked') {
        const pageUuid = quiz.dataset.pageUuid;
        const quizId = quiz.dataset.quizId;
        const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
        return unlocks.some(u => u.page_id === pageUuid && (u.type === 'page' || u.exercise_id === quizId));
    }
    return true;
}

function unlockCorrections(context) {
    if (context.type === 'page') {
        document.querySelectorAll('.mkq-unlock-btn').forEach(btn => updateLockIcon(btn, true));
        document.querySelectorAll('.mkdocs-superquiz').forEach(quiz => {
            const quizId = quiz.dataset.quizId;
            if (quizId) validateQuiz(quizId);
        });
    } else if (context.type === 'exercise') {
        const exercise = document.querySelector(`[data-quiz-id="${context.exercise_id}"]`);
        if (exercise) {
            updateLockIcon(exercise.querySelector('.mkq-unlock-btn'), true);
            validateQuiz(context.exercise_id);
        }
    } else if (context.type === 'question') {
        const exercise = document.querySelector(`[data-quiz-id="${context.exercise_id}"]`);
        if (exercise) {
            const question = exercise.querySelector(`[data-question-id="${context.question_id}"]`);
            if (question) updateLockIcon(question.querySelector('.mkq-unlock-btn'), true);
            validateQuiz(context.exercise_id);
        }
    }
}

function saveUnlockState(context) {
    const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
    unlocks.push({
        page_id: context.page_id, type: context.type,
        exercise_id: context.exercise_id, question_id: context.question_id,
        timestamp: new Date().toISOString()
    });
    localStorage.setItem('mkq_unlocks', JSON.stringify(unlocks));
}

function restoreUnlockedCorrections() {
    const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
    const currentPageUuid = document.querySelector('.mkdocs-superquiz')?.dataset.pageUuid;
    if (!currentPageUuid) return;
    unlocks.forEach(unlock => { if (unlock.page_id === currentPageUuid) unlockCorrections(unlock); });
}

// ─────────────────────────────────────────────────────────────────────────────
// Randomisation côté client
// ─────────────────────────────────────────────────────────────────────────────

function shuffleArray(arr) {
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
}

/**
 * Applique la randomisation sur tous les quiz de la page.
 * Appelé au DOMContentLoaded, AVANT loadSavedAnswers().
 *
 * randomize_answers   → mélange les réponses/options à l'intérieur de chaque question
 * randomize_questions → mélange l'ordre des questions dans le quiz
 *
 * Stratégie de scoring post-shuffle :
 *   - mcquiz/scquiz   : compare par TEXTE (correct_answers_text), pas par index DOM
 *   - scdropdown      : compare par TEXTE de l'option sélectionnée (data-option-text)
 *   - mcdropdown      : compare par TEXTE des options sélectionnées (data-option-text)
 */
function applyRandomization() {
    document.querySelectorAll('.mkdocs-superquiz').forEach(quizEl => {
        const quizRandomizeAnswers   = quizEl.dataset.randomizeAnswers   === 'true';
        const randomizeQuestions = quizEl.dataset.randomizeQuestions === 'true';
        const quizType = quizEl.dataset.quizType;

        // ── randomize_answers ──────────────────────────────────────────────
        if (quizType === 'mcquiz' || quizType === 'scquiz') {
            quizEl.querySelectorAll('.mkq-question[data-question-id]').forEach(qEl => {
                const qId = qEl.dataset.questionId;
                if (!qId || qId === '0') return;
                let shouldRandomize;
                if (qEl.hasAttribute('data-randomize-answers')) {
                    shouldRandomize = qEl.dataset.randomizeAnswers === 'true';
                } else {
                    shouldRandomize = quizRandomizeAnswers;
                }
                if (!shouldRandomize) return;
                const answersDiv = qEl.querySelector('.mkq-answers');
                if (!answersDiv) return;
                const labels = Array.from(answersDiv.querySelectorAll('.mkq-answer'));
                shuffleArray(labels);
                labels.forEach(l => answersDiv.appendChild(l));
            });
        } else if (quizType === 'scdropdown') {
            quizEl.querySelectorAll('.mkq-question[data-question-id]').forEach(qEl => {
                const qId = qEl.dataset.questionId;
                if (!qId || qId === '0') return;
                let shouldRandomize;
                if (qEl.hasAttribute('data-randomize-answers')) {
                    shouldRandomize = qEl.dataset.randomizeAnswers === 'true';
                } else {
                    shouldRandomize = quizRandomizeAnswers;
                }
                if (!shouldRandomize) return;
                qEl.querySelectorAll('.mkq-custom-scdropdown').forEach(ddEl => {
                    const optionsDiv = ddEl.querySelector('.mkq-custom-scdropdown-options');
                    if (!optionsDiv) return;
                    const options = Array.from(optionsDiv.querySelectorAll('.mkq-custom-scdropdown-option'));
                    shuffleArray(options);
                    options.forEach((opt, newIdx) => {
                        opt.dataset.value = newIdx;
                        optionsDiv.appendChild(opt);
                    });
                });
            });
        } else if (quizType === 'mcdropdown') {
            quizEl.querySelectorAll('.mkq-question[data-question-id]').forEach(qEl => {
                const qId = qEl.dataset.questionId;
                if (!qId || qId === '0') return;
                let shouldRandomize;
                if (qEl.hasAttribute('data-randomize-answers')) {
                    shouldRandomize = qEl.dataset.randomizeAnswers === 'true';
                } else {
                    shouldRandomize = quizRandomizeAnswers;
                }
                if (!shouldRandomize) return;
                qEl.querySelectorAll('.mkq-custom-mcdropdown').forEach(ddEl => {
                    const optionsDiv = ddEl.querySelector('.mkq-custom-mcdropdown-options');
                    if (!optionsDiv) return;
                    const options = Array.from(optionsDiv.querySelectorAll('.mkq-custom-mcdropdown-option'));
                    shuffleArray(options);
                    options.forEach((opt, newIdx) => {
                        opt.dataset.value = newIdx;
                        optionsDiv.appendChild(opt);
                    });
                    // Reset selected indices après shuffle
                    ddEl.dataset.selectedIndices = '[]';
                });
            });
        }

        // ── randomize_questions ────────────────────────────────────────────
        if (randomizeQuestions) {
            const questionEls = Array.from(
                quizEl.querySelectorAll('.mkq-question[data-question-id]')
            ).filter(q => q.dataset.questionId !== '0');

            if (questionEls.length > 1) {
                const parent = questionEls[0].parentElement;
                const actionsDiv = parent.querySelector('.quiz-actions');
                shuffleArray(questionEls);
                questionEls.forEach((qEl, newIdx) => {
                    const numEl = qEl.querySelector('.mkq-number');
                    if (numEl) numEl.textContent = `${newIdx + 1}.`;
                    if (actionsDiv) {
                        parent.insertBefore(qEl, actionsDiv);
                    } else {
                        parent.appendChild(qEl);
                    }
                });
            }
        }
    });
}

// ─────────────────────────────────────────────────────────────────────────────
// Scoring helpers
// ─────────────────────────────────────────────────────────────────────────────

function clampScore(score, pointsMax, minScore, clampMax) {
    let s = score;
    if (clampMax) s = Math.min(s, pointsMax);
    if (minScore !== null && minScore !== undefined) {
        const min = Number(minScore);
        if (min < pointsMax) s = Math.max(s, min);
    }
    return s;
}

function arraysEqual(a, b) {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
    return true;
}

function normalizeLatexLoose(str) {
    return str.replace(/\s+/g, '').replace(/\*/g, '').replace(/²/g, '^2').replace(/³/g, '^3');
}

// ─────────────────────────────────────────────────────────────────────────────
// compareAnswers — NEW FORMAT ONLY
// ─────────────────────────────────────────────────────────────────────────────

function normalizeCompareConfig(cfg) {
    const DEFAULTS = { spaces: 'none', characters: '', case: false, diacritics: false };
    if (!cfg || typeof cfg !== 'object') {
        return { ignore: Object.assign({}, DEFAULTS), equivalence: {} };
    }
    const ignore = Object.assign({}, DEFAULTS, cfg.ignore || {});
    const validSpaces = ['all', 'inside', 'outside', 'none'];
    if (!validSpaces.includes(ignore.spaces)) ignore.spaces = 'none';
    const rawEquiv = cfg.equivalence || {};
    const equivalence = {};
    for (const [k, v] of Object.entries(rawEquiv)) {
        equivalence[k] = Array.isArray(v) ? v.map(String) : [String(v)];
    }
    return { ignore, equivalence };
}

function normalizeAnswer(str, ignore) {
    if (typeof str !== 'string') str = String(str ?? '');
    const chars = (ignore.characters != null) ? String(ignore.characters) : '';
    if (chars.length > 0) {
        const charSet = new Set([...chars]);
        str = [...str].filter(c => !charSet.has(c)).join('');
    }
    const spacesMode = (ignore.spaces != null) ? ignore.spaces : 'none';
    if (spacesMode === 'all') { str = str.replace(/\s+/g, ''); }
    else if (spacesMode === 'outside') { str = str.trim(); }
    else if (spacesMode === 'inside') {
        const leading = str.match(/^\s*/)[0];
        const trailing = str.match(/\s*$/)[0];
        const innerStart = leading.length;
        const innerEnd = str.length - trailing.length;
        const inner = str.slice(innerStart, innerEnd);
        str = leading + inner.replace(/\s+/g, '') + trailing;
    }
    if (ignore.case === true) str = str.toLowerCase();
    if (ignore.diacritics === true) str = str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
    return str;
}

function applyEquivalences(str, equivalence, caseInsensitive) {
    if (!equivalence || typeof equivalence !== 'object') return str;
    const repMap = new Map();
    for (const [canonical, variants] of Object.entries(equivalence)) {
        const canonKey = caseInsensitive ? canonical.toLowerCase() : canonical;
        for (const v of (Array.isArray(variants) ? variants : [variants])) {
            const vKey = caseInsensitive ? String(v).toLowerCase() : String(v);
            repMap.set(vKey, canonKey);
        }
    }
    if (repMap.size === 0) return str;
    const sortedKeys = [...repMap.keys()].sort((a, b) => b.length - a.length);
    const escaped = sortedKeys.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
    const re = new RegExp(escaped.join('|'), 'g');
    return str.replace(re, match => repMap.get(match) ?? match);
}

function compareAnswers(userAnswer, correctAnswer, compareConfig) {
    const rawCfg = compareConfig
        || window.QUIZ_CONFIG?.fillblanks?.compare_answers
        || {};
    const cfg = normalizeCompareConfig(rawCfg);
    const { ignore, equivalence } = cfg;
    const caseInsensitive = (ignore.case === true);
    let user = normalizeAnswer(String(userAnswer ?? ''), ignore);
    let correct = normalizeAnswer(String(correctAnswer ?? ''), ignore);
    user = applyEquivalences(user, equivalence, caseInsensitive);
    correct = applyEquivalences(correct, equivalence, caseInsensitive);
    return user === correct;
}

// ─────────────────────────────────────────────────────────────────────────────
// Parse errors_mode keys for mcquiz/mcdropdown: "[Paris, Londres]" → list
// ─────────────────────────────────────────────────────────────────────────────

function parseBracketKey(key) {
    const s = String(key).trim();
    if (!s.startsWith('[') || !s.endsWith(']')) return null;
    const inner = s.slice(1, -1).trim();
    if (!inner) return [];
    return inner.split(',').map(x => x.trim()).filter(Boolean);
}

// ─────────────────────────────────────────────────────────────────────────────
// Score functions
// ─────────────────────────────────────────────────────────────────────────────

/**
 * scoreChoiceQuestion — mcquiz
 * ✅ Post-shuffle : compare par TEXTE (correct_answers_text).
 */
function scoreChoiceQuestion(questionData, selectedTexts) {
    const points = Number(questionData.points || 1);
    const scoringMode = questionData.scoring_mode || 'all_or_nothing';

    const qdf = questionData.question_default_false;
    const hasQdf = (qdf !== null && qdf !== undefined);
    const defaultFalse = hasQdf ? Number(qdf)
        : (questionData.default_false !== null && questionData.default_false !== undefined)
            ? Number(questionData.default_false) : null;
    const hasDefaultFalse = (defaultFalse !== null);

    const correctTexts = Array.isArray(questionData.correct_answers_text)
        ? questionData.correct_answers_text : [];
    const correctSet = new Set(correctTexts.map(t => t.trim()));

    let nbGood = 0, nbFalse = 0;
    selectedTexts.forEach(txt => {
        if (correctSet.has(txt.trim())) nbGood++;
        else nbFalse++;
    });

    const nbGoodTotal = correctTexts.length || 1;
    const isEmpty = (selectedTexts.length === 0);

    const selSorted = [...selectedTexts].map(t => t.trim()).sort();
    const corSorted = [...correctTexts].map(t => t.trim()).sort();
    const exact = arraysEqual(selSorted, corSorted);

    const em = questionData.errors_mode || null;
    const hasErrorsModeRules = em && Array.isArray(em.rules) && em.rules.length > 0;
    const hasEmDf = em && em.default_false !== null && em.default_false !== undefined;
    const hasAnyErrorsMode = hasErrorsModeRules || hasEmDf || hasQdf;

    const minScore = questionData.min_score !== undefined ? questionData.min_score
        : window.QUIZ_CONFIG.min_score !== undefined ? window.QUIZ_CONFIG.min_score : null;
    const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

    const effectiveDefaultFalse = hasEmDf ? Number(em.default_false)
        : hasDefaultFalse ? defaultFalse : 0;

    if (exact && !isEmpty) return clampScore(points, points, minScore, clampMax);

    if (scoringMode === 'all_or_nothing') {
        let base = 0;
        if (hasErrorsModeRules && !isEmpty) {
            let matched = null;
            for (const r of em.rules) {
                const list = parseBracketKey(r.key);
                if (!list) continue;
                const a = [...selectedTexts].map(x => x.trim()).sort();
                const b = [...list].map(x => x.trim()).sort();
                if (arraysEqual(a, b)) { matched = r; break; }
            }
            if (matched) base = Number(matched.score);
            else base = effectiveDefaultFalse;
        } else if (hasAnyErrorsMode) {
            base = effectiveDefaultFalse;
        }
        return clampScore(base, points, minScore, clampMax);
    }

    if (scoringMode === 'some_or_nothing') {
        if (isEmpty && hasAnyErrorsMode) return clampScore(effectiveDefaultFalse, points, minScore, clampMax);
        if (nbFalse === 0) return clampScore(points * (nbGood / nbGoodTotal), points, minScore, clampMax);
        if (!hasAnyErrorsMode) return clampScore(0, points, minScore, clampMax);
        let base = 0;
        if (hasErrorsModeRules) {
            let matched = null;
            for (const r of em.rules) {
                const list = parseBracketKey(r.key);
                if (!list) continue;
                const a = [...selectedTexts].map(x => x.trim()).sort();
                const b = [...list].map(x => x.trim()).sort();
                if (arraysEqual(a, b)) { matched = r; break; }
            }
            if (matched) base = Number(matched.score);
            else base = effectiveDefaultFalse;
        } else { base = effectiveDefaultFalse; }
        return clampScore(base, points, minScore, clampMax);
    }

    // proportional
    let base = points * ((nbGood - nbFalse) / nbGoodTotal);
    if (hasErrorsModeRules) {
        let matched = null;
        for (const r of em.rules) {
            const list = parseBracketKey(r.key);
            if (!list) continue;
            const a = [...selectedTexts].map(x => x.trim()).sort();
            const b = [...list].map(x => x.trim()).sort();
            if (arraysEqual(a, b)) { matched = r; break; }
        }
        if (matched) base = Number(matched.score);
        else base = effectiveDefaultFalse;
    } else if (hasAnyErrorsMode) {
        base = points * (nbGood / nbGoodTotal) + effectiveDefaultFalse * nbFalse;
    }
    return clampScore(base, points, minScore, clampMax);
}

/**
 * scoreScQuizQuestion — scquiz
 * ✅ Post-shuffle : compare par TEXTE de la réponse sélectionnée.
 */
function scoreScQuizQuestion(questionData, selectedText) {
    const points = Number(questionData.points || 1);
    const qdf = questionData.question_default_false;
    const hasQdf = (qdf !== null && qdf !== undefined);
    const defaultFalse = hasQdf ? Number(qdf)
        : (questionData.default_false !== null && questionData.default_false !== undefined)
            ? Number(questionData.default_false) : 0;

    const correctTexts = Array.isArray(questionData.correct_answers_text)
        ? questionData.correct_answers_text : [];
    const correctText = correctTexts.length ? correctTexts[0] : '';

    const exact = (selectedText !== null && selectedText !== undefined && selectedText !== '')
        && (String(selectedText).trim() === String(correctText).trim());

    const em = questionData.errors_mode || null;
    const hasErrorsModeRules = em && Array.isArray(em.rules) && em.rules.length > 0;
    const hasErrorsModeDf = em && em.default_false !== null && em.default_false !== undefined;
    const hasErrorsMode = hasErrorsModeRules || hasErrorsModeDf || hasQdf;

    let base = exact ? points : 0;
    if (!exact && !hasErrorsMode && defaultFalse !== 0) base = defaultFalse;
    if (!exact && hasErrorsMode) {
        if (hasErrorsModeRules) {
            let matched = null;
            for (const r of em.rules) {
                if (String(r.key).trim() === String(selectedText ?? '').trim()) { matched = r; break; }
            }
            if (matched) base = Number(matched.score);
            else if (hasErrorsModeDf) base = Number(em.default_false);
            else base = defaultFalse;
        } else {
            base = hasErrorsModeDf ? Number(em.default_false) : defaultFalse;
        }
    }

    const minScore = questionData.min_score !== undefined ? questionData.min_score
        : window.QUIZ_CONFIG.min_score !== undefined ? window.QUIZ_CONFIG.min_score : 0;
    const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);
    return clampScore(base, points, minScore, clampMax);
}

function scoreFillblanksQuestion(questionData, userAnswers) {
    const points = Number(questionData.points || 1);
    const blanks = Array.isArray(questionData.blanks) ? questionData.blanks : [];
    const n = blanks.length || 1;
    const scoringMode = questionData.scoring_mode || 'proportional';
    const perFillblank = points / n;
    const em = questionData.errors_mode || { blanks: [] };

    const compareCfg = questionData.compare_answers
        || questionData.quiz_compare_answers
        || window.QUIZ_CONFIG.fillblanks?.compare_answers
        || {};

    const exerciseDefaultFalse = (questionData.default_false !== null && questionData.default_false !== undefined)
        ? Number(questionData.default_false) : null;
    const hasExerciseDefaultFalse = (exerciseDefaultFalse !== null);

    let correctCount = 0, anyWrongNonEmpty = false;
    const blankStates = [];

    for (let i = 0; i < blanks.length; i++) {
        const blank = blanks[i];
        const ua = String(userAnswers[i] ?? '');
        const accepted = Array.isArray(blank.answers) ? blank.answers : [];
        const isEmpty = (ua.trim() === '');
        const isCorrect = !isEmpty && accepted.some(ans => compareAnswers(ua, ans, compareCfg));
        if (isCorrect) correctCount++;
        if (!isCorrect && !isEmpty) anyWrongNonEmpty = true;
        blankStates.push({ isCorrect, isEmpty, ua });
    }

    const minScore = (questionData.min_score !== undefined)
        ? questionData.min_score
        : (window.QUIZ_CONFIG.min_score !== undefined && window.QUIZ_CONFIG.min_score !== null)
            ? window.QUIZ_CONFIG.min_score : null;
    const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

    const hasAnyFillblankEm = blanks.some((_, i) => em.blanks && em.blanks[i] &&
        (em.blanks[i].rules?.length > 0 || em.blanks[i].default_false !== null));

    if (scoringMode === 'all_or_nothing' || (scoringMode === 'some_or_nothing' && hasAnyFillblankEm)) {
        if (!hasAnyFillblankEm) {
            const allCorrect = (correctCount === blanks.length);
            const base = allCorrect ? points
                : (anyWrongNonEmpty && hasExerciseDefaultFalse) ? exerciseDefaultFalse
                : anyWrongNonEmpty ? 0 : 0;
            return clampScore(base, points, minScore, clampMax);
        }

        let total = 0;
        let hasUncoveredFillblank = false;

        for (let i = 0; i < blanks.length; i++) {
            const st = blankStates[i];
            if (st.isCorrect) { total += perFillblank; continue; }

            const fbEm = (em.blanks && em.blanks[i]) ? em.blanks[i] : null;
            const blankDf = fbEm && fbEm.default_false !== null && fbEm.default_false !== undefined
                ? Number(fbEm.default_false) : null;
            const hasFillblankDf = (blankDf !== null);
            const effectiveDf = hasFillblankDf ? blankDf
                : hasExerciseDefaultFalse ? exerciseDefaultFalse : null;
            const hasDf = (effectiveDf !== null);

            if (st.isEmpty) {
                if (hasDf) { total += effectiveDf; } else { hasUncoveredFillblank = true; break; }
                continue;
            }
            if (!fbEm || !fbEm.rules || fbEm.rules.length === 0) {
                if (hasDf) { total += effectiveDf; } else { hasUncoveredFillblank = true; break; }
                continue;
            }

            const rules = Array.isArray(fbEm.rules) ? fbEm.rules : [];
            let matched = null;
            for (const r of rules) { if (compareAnswers(st.ua, r.answer, compareCfg)) { matched = r; break; } }
            if (matched) { total += Number(matched.score); }
            else if (hasDf) { total += effectiveDf; }
            else { hasUncoveredFillblank = true; break; }
        }

        if (hasUncoveredFillblank) total = 0;
        return clampScore(total, points, minScore, clampMax);
    }

    if (scoringMode === 'some_or_nothing') {
        const base = anyWrongNonEmpty ? 0 : (perFillblank * correctCount);
        return clampScore(base, points, minScore, clampMax);
    }

    // proportional
    let total = perFillblank * correctCount;
    for (let i = 0; i < blanks.length; i++) {
        const st = blankStates[i];
        if (st.isCorrect) continue;
        const fbEm = (em.blanks && em.blanks[i]) ? em.blanks[i] : null;
        const blankDf = fbEm && fbEm.default_false !== null && fbEm.default_false !== undefined
            ? Number(fbEm.default_false) : null;
        const effectiveDf = (blankDf !== null) ? blankDf
            : hasExerciseDefaultFalse ? exerciseDefaultFalse : 0;
        const rules = fbEm ? (Array.isArray(fbEm.rules) ? fbEm.rules : []) : [];
        let matched = null;
        for (const r of rules) { if (compareAnswers(st.ua, r.answer, compareCfg)) { matched = r; break; } }
        if (matched) total += Number(matched.score);
        else total += effectiveDf;
    }
    return clampScore(total, points, minScore, clampMax);
}

function scoreScdropdownQuestion(questionData, selectedTexts) {
    const actual = Array.isArray(questionData.scdropdowns) ? questionData.scdropdowns
        : (questionData.scdropdown ? [questionData.scdropdown] : []);
    const per = actual.length > 0 ? Number(questionData.points || 1) / actual.length : Number(questionData.points || 1);
    const points = Number(questionData.points || 1);
    const scoringMode = questionData.scoring_mode || 'all_or_nothing';
    const em = questionData.errors_mode || { scdropdowns: [] };

    const minScore = (questionData.min_score !== undefined)
        ? questionData.min_score
        : (window.QUIZ_CONFIG.min_score !== undefined && window.QUIZ_CONFIG.min_score !== null)
            ? window.QUIZ_CONFIG.min_score : null;
    const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

    const questionDefaultFalse = (questionData.default_false !== null && questionData.default_false !== undefined)
        ? Number(questionData.default_false) : null;
    const hasQuestionDf = (questionDefaultFalse !== null);

    const hasAnyDdEm = actual.some((_, i) => em.scdropdowns && em.scdropdowns[i] &&
        (em.scdropdowns[i].rules?.length > 0 || em.scdropdowns[i].default_false !== null));

    const evalScdropdowns = () => actual.map((dd, i) => {
        const opts = Array.isArray(dd.options) ? dd.options : [];
        const correctText = String(opts[Number(dd.correct_index ?? 0)] ?? '').trim();
        const chosenText = selectedTexts[i] ?? null;
        const isEmpty = (chosenText === null);
        const isCorrect = !isEmpty && String(chosenText).trim() === correctText;
        return { isCorrect, isEmpty, chosenText, correctText };
    });

    if (scoringMode === 'all_or_nothing' || (scoringMode === 'some_or_nothing' && hasAnyDdEm)) {
        if (!hasAnyDdEm) {
            const states = evalScdropdowns();
            const allCorrect = states.every(s => s.isCorrect);
            const anyWrong = states.some(s => !s.isCorrect && !s.isEmpty);
            const base = allCorrect ? points
                : (anyWrong && hasQuestionDf) ? questionDefaultFalse
                : anyWrong ? 0 : 0;
            return clampScore(base, points, minScore, clampMax);
        }

        let total = 0, hasUncoveredDd = false;
        const states = evalScdropdowns();
        for (let i = 0; i < actual.length; i++) {
            const st = states[i];
            if (st.isCorrect) { total += per; continue; }
            const ddEm = (em.scdropdowns && em.scdropdowns[i]) ? em.scdropdowns[i] : null;
            const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
                ? Number(ddEm.default_false) : null;
            const effectiveDf = (ddDf !== null) ? ddDf : hasQuestionDf ? questionDefaultFalse : null;
            const hasDf = (effectiveDf !== null);
            if (st.isEmpty) {
                if (hasDf) { total += effectiveDf; } else { hasUncoveredDd = true; break; }
                continue;
            }
            if (!ddEm || !ddEm.rules || ddEm.rules.length === 0) {
                if (hasDf) { total += effectiveDf; } else { hasUncoveredDd = true; break; }
                continue;
            }
            const rules = Array.isArray(ddEm.rules) ? ddEm.rules : [];
            let matched = null;
            for (const r of rules) {
                if (String(r.option).trim() === String(st.chosenText).trim()) { matched = r; break; }
            }
            if (matched) { total += Number(matched.score); }
            else if (hasDf) { total += effectiveDf; }
            else { hasUncoveredDd = true; break; }
        }
        if (hasUncoveredDd) total = 0;
        return clampScore(total, points, minScore, clampMax);
    }

    if (scoringMode === 'some_or_nothing') {
        const states = evalScdropdowns();
        const anyWrong = states.some(s => !s.isCorrect && !s.isEmpty);
        const correctCount = states.filter(s => s.isCorrect).length;
        return clampScore(anyWrong ? 0 : (per * correctCount), points, minScore, clampMax);
    }

    // proportional
    const states = evalScdropdowns();
    let total = 0;
    for (let i = 0; i < actual.length; i++) {
        const st = states[i];
        if (st.isCorrect) { total += per; continue; }
        if (st.isEmpty) continue;
        const ddEm = (em.scdropdowns && em.scdropdowns[i]) ? em.scdropdowns[i] : null;
        const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
            ? Number(ddEm.default_false) : null;
        const effectiveDf = (ddDf !== null) ? ddDf : hasQuestionDf ? questionDefaultFalse : 0;
        const rules = ddEm ? (Array.isArray(ddEm.rules) ? ddEm.rules : []) : [];
        let matched = null;
        for (const r of rules) {
            if (String(r.option).trim() === String(st.chosenText).trim()) { matched = r; break; }
        }
        if (matched) total += Number(matched.score);
        else total += effectiveDf;
    }
    return clampScore(total, points, minScore, clampMax);
}

/**
 * scoreMcdropdownQuestion — mcdropdown
 *
 * selectedTextsByDropdown : tableau de tableaux de textes sélectionnés,
 *   un sous-tableau par dropdown.
 * Scoring per-dropdown identique à scoreChoiceQuestion (mcquiz).
 */
function scoreMcdropdownQuestion(questionData, selectedTextsByDropdown) {
    const mcdropdowns = Array.isArray(questionData.mcdropdowns) ? questionData.mcdropdowns : [];
    const points = Number(questionData.points || 1);
    const per = mcdropdowns.length > 0 ? points / mcdropdowns.length : points;
    const scoringMode = questionData.scoring_mode || 'proportional';
    const em = questionData.errors_mode || { mcdropdowns: [] };

    const minScore = (questionData.min_score !== undefined)
        ? questionData.min_score
        : (window.QUIZ_CONFIG.min_score !== undefined && window.QUIZ_CONFIG.min_score !== null)
            ? window.QUIZ_CONFIG.min_score : null;
    const clampMax = (questionData.clamp_max_score ?? window.QUIZ_CONFIG.clamp_max_score ?? true);

    // Chaîne de priorité default_false :
    // 1. errors_mode inline du dropdown  (ddEm.default_false)
    // 2. front-matter question           (questionData.question_default_false)
    // 3. front-matter quiz               (questionData.quiz_default_false)
    // 4. config type de quiz             (questionData.default_false)
    // 5. 0
    const qtDefaultFalse = (questionData.default_false !== null && questionData.default_false !== undefined)
        ? Number(questionData.default_false) : null;
    const quizDefaultFalse = (questionData.quiz_default_false !== null && questionData.quiz_default_false !== undefined)
        ? Number(questionData.quiz_default_false) : null;
    const questionLevelDf = (questionData.question_default_false !== null && questionData.question_default_false !== undefined)
        ? Number(questionData.question_default_false) : null;

    let total = 0;

    for (let i = 0; i < mcdropdowns.length; i++) {
        const dd = mcdropdowns[i];
        const selectedTexts = Array.isArray(selectedTextsByDropdown[i]) ? selectedTextsByDropdown[i] : [];
        const correctIndices = Array.isArray(dd.correct_indices) ? dd.correct_indices : [];
        const opts = Array.isArray(dd.options) ? dd.options : [];
        const correctTexts = correctIndices.map(idx => String(opts[idx] ?? '').trim());
        const correctSet = new Set(correctTexts);

        const ddEm = (em.mcdropdowns && em.mcdropdowns[i]) ? em.mcdropdowns[i] : null;
        // ddDf = default_false résolu depuis errors_mode du dropdown (inline ou front-matter question)
        const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
            ? Number(ddEm.default_false) : null;
        // effectiveDefaultFalse : chaîne complète de priorité
        const effectiveDefaultFalse = (ddDf !== null) ? ddDf
            : (questionLevelDf !== null) ? questionLevelDf
            : (quizDefaultFalse !== null) ? quizDefaultFalse
            : (qtDefaultFalse !== null) ? qtDefaultFalse
            : 0;
        const hasAnyEm = (ddEm && (ddEm.rules?.length > 0 || ddEm.default_false !== null))
            || (questionLevelDf !== null) || (quizDefaultFalse !== null);

        const isEmpty = (selectedTexts.length === 0);
        let nbGood = 0, nbFalse = 0;
        selectedTexts.forEach(txt => {
            if (correctSet.has(txt.trim())) nbGood++;
            else nbFalse++;
        });
        const nbGoodTotal = correctTexts.length || 1;
        const selSorted = [...selectedTexts].map(t => t.trim()).sort();
        const corSorted = [...correctTexts].sort();
        const exact = arraysEqual(selSorted, corSorted) && !isEmpty;

        if (exact) { total += per; continue; }

        const hasRules = ddEm && Array.isArray(ddEm.rules) && ddEm.rules.length > 0;

        if (scoringMode === 'all_or_nothing') {
            if (hasRules && !isEmpty) {
                let matched = null;
                for (const r of ddEm.rules) {
                    const list = parseBracketKey(r.key);
                    if (!list) continue;
                    const a = [...selectedTexts].map(x => x.trim()).sort();
                    const b = [...list].map(x => x.trim()).sort();
                    if (arraysEqual(a, b)) { matched = r; break; }
                }
                total += matched ? Number(matched.score) : effectiveDefaultFalse;
            } else if (hasAnyEm) {
                total += effectiveDefaultFalse;
            }
            // else: 0
        } else if (scoringMode === 'some_or_nothing') {
            if (isEmpty && hasAnyEm) { total += effectiveDefaultFalse; }
            else if (nbFalse === 0) { total += per * (nbGood / nbGoodTotal); }
            else if (hasRules) {
                let matched = null;
                for (const r of ddEm.rules) {
                    const list = parseBracketKey(r.key);
                    if (!list) continue;
                    const a = [...selectedTexts].map(x => x.trim()).sort();
                    const b = [...list].map(x => x.trim()).sort();
                    if (arraysEqual(a, b)) { matched = r; break; }
                }
                total += matched ? Number(matched.score) : effectiveDefaultFalse;
            } else if (hasAnyEm) { total += effectiveDefaultFalse; }
            // else: 0
        } else {
            // proportional
            if (hasRules) {
                let matched = null;
                for (const r of ddEm.rules) {
                    const list = parseBracketKey(r.key);
                    if (!list) continue;
                    const a = [...selectedTexts].map(x => x.trim()).sort();
                    const b = [...list].map(x => x.trim()).sort();
                    if (arraysEqual(a, b)) { matched = r; break; }
                }
                total += matched ? Number(matched.score) : effectiveDefaultFalse;
            } else {
                total += per * ((nbGood - nbFalse) / nbGoodTotal);
            }
        }
    }

    return clampScore(total, points, minScore, clampMax);
}

// ─────────────────────────────────────────────────────────────────────────────
// Validation
// ─────────────────────────────────────────────────────────────────────────────

function validateQuiz(quizId) {
    const quiz = document.querySelector(`[data-quiz-id="${quizId}"]`);
    if (!quiz) return;
    const quizType = quiz.dataset.quizType;
    if (quizType === 'fillblanks') validateFillblanksQuiz(quiz, quizId);
    else if (quizType === 'scdropdown') validateScdropdownQuiz(quiz, quizId);
    else if (quizType === 'mcdropdown') validateMcdropdownQuiz(quiz, quizId);
    else validateChoiceQuiz(quiz, quizId);
    saveAnswers(quizId);
}

/**
 * validateChoiceQuiz — mcquiz / scquiz
 * ✅ Post-shuffle : lit le texte de chaque réponse cochée depuis data-answer-text.
 */
function validateChoiceQuiz(quiz, quizId) {
    const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
    let totalPoints = 0, earnedPoints = 0;
    const correctionData = decryptAnswers(quiz.dataset.encrypted);
    if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
    const showCorrection = shouldShowCorrections(quiz);

    questions.forEach((questionEl) => {
        const questionId = parseInt(questionEl.dataset.questionId, 10);
        const qData = correctionData.questions.find(q => q.number === questionId);
        if (!qData) return;
        totalPoints += Number(qData.points || 1);

        const checkedLabels = Array.from(questionEl.querySelectorAll(
            'input[type="checkbox"]:checked, input[type="radio"]:checked'
        )).map(input => input.closest('.mkq-answer'));

        const selectedTexts = checkedLabels
            .map(label => label ? (label.dataset.answerText || '') : '')
            .filter(t => t !== '');

        let score;
        if (correctionData.quiz_type === 'scquiz') {
            score = scoreScQuizQuestion(qData, selectedTexts.length ? selectedTexts[0] : '');
        } else {
            score = scoreChoiceQuestion(qData, selectedTexts);
        }
        earnedPoints += score;

        if (showCorrection) {
            const correctTexts = new Set((qData.correct_answers_text || []).map(t => t.trim()));
            questionEl.querySelectorAll('.mkq-answer').forEach(label => {
                const input = label.querySelector('input');
                const feedback = label.querySelector('.mkq-answer-feedback');
                label.classList.remove('correct', 'incorrect');
                if (feedback) { feedback.style.display = 'none'; feedback.textContent = ''; feedback.classList.remove('correct', 'incorrect'); }

                const answerText = (label.dataset.answerText || '').trim();
                const isChecked = input && input.checked;
                const isCorrectAnswer = correctTexts.has(answerText);

                if (isCorrectAnswer) {
                    label.classList.add('correct');
                    if (feedback) { feedback.textContent = t("feedback.correct", "Correct"); feedback.classList.add('correct'); feedback.style.display = 'inline'; }
                } else if (isChecked) {
                    label.classList.add('incorrect');
                    if (feedback) { feedback.textContent = t("feedback.incorrect", "Incorrect"); feedback.classList.add('incorrect'); feedback.style.display = 'inline'; }
                }
            });
        }
    });
    displayScorePoints(quizId, earnedPoints, totalPoints);
}

function validateFillblanksQuiz(quiz, quizId) {
    const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
    let totalPoints = 0, earnedPoints = 0;
    const correctionData = decryptAnswers(quiz.dataset.encrypted);
    if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
    const showCorrection = shouldShowCorrections(quiz);
    const droppedElements = [];

    questions.forEach((questionEl) => {
        const questionId = parseInt(questionEl.dataset.questionId, 10);
        const qData = correctionData.questions.find(q => q.number === questionId);
        if (!qData) return;
        totalPoints += Number(qData.points || 1);
        const blankInputs = questionEl.querySelectorAll('.mkq-blank-input');
        const userAnswers = [];
        blankInputs.forEach((input) => userAnswers.push(input.value));

        qData.quiz_compare_answers = correctionData.compare_answers || null;
        earnedPoints += scoreFillblanksQuestion(qData, userAnswers);

        if (!showCorrection) return;
        blankInputs.forEach((input, blankIndex) => {
            const userAnswer = input.value;
            const blank = qData.blanks[blankIndex];
            if (!blank) return;
            const compareCfg = qData.compare_answers
                || correctionData.compare_answers
                || window.QUIZ_CONFIG.fillblanks?.compare_answers
                || {};
            const isCorrect = blank.answers.some(answer => compareAnswers(userAnswer, answer, compareCfg));
            const nextSibling = input.nextSibling;
            if (nextSibling && nextSibling.classList?.contains('mkq-blank-correction-wrapper')) nextSibling.remove();
            if (isCorrect) {
                input.style.display = ''; input.classList.add('correct'); input.classList.remove('incorrect');
            } else {
                input.style.display = 'none'; input.classList.remove('correct'); input.classList.add('incorrect');
                const wrapper = document.createElement('span');
                wrapper.className = 'mkq-blank-correction-wrapper';
                const label = document.createElement('span');
                label.className = 'mkq-blank-wrong-label';
                const displayAnswer = userAnswer.trim();
                label.innerHTML = `<s>${displayAnswer || t("ui.empty_answer", "(empty)")}</s>`;
                wrapper.appendChild(label);
                const scdropdown = createFillblanksMathJaxScdropdown(
                    `mkq-scdropdown-${quizId}-q${questionId}-blank${blankIndex}`, blank.answers
                );
                wrapper.appendChild(scdropdown);
                input.after(wrapper);
                droppedElements.push(wrapper);
            }
        });
    });
    displayScorePoints(quizId, earnedPoints, totalPoints);
    if (droppedElements.length > 0) {
        setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise(droppedElements).catch(() => { }); }, 150);
    }
}

/**
 * validateScdropdownQuiz
 * ✅ Post-shuffle : lit le texte de l'option sélectionnée depuis data-option-text.
 */
function validateScdropdownQuiz(quiz, quizId) {
    const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
    let totalPoints = 0, earnedPoints = 0;
    const correctionData = decryptAnswers(quiz.dataset.encrypted);
    if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
    const showCorrection = shouldShowCorrections(quiz);

    questions.forEach((questionEl) => {
        const questionId = parseInt(questionEl.dataset.questionId, 10);
        const qData = correctionData.questions.find(q => q.number === questionId);
        if (!qData) return;
        totalPoints += Number(qData.points || 1);

        const scdropdownEls = Array.from(questionEl.querySelectorAll('.mkq-custom-scdropdown'));
        const ddDataList = Array.isArray(qData.scdropdowns) ? qData.scdropdowns : (qData.scdropdown ? [qData.scdropdown] : []);
        const count = Math.min(scdropdownEls.length, ddDataList.length);

        const selectedTexts = [];
        for (let i = 0; i < count; i++) {
            const ddEl = scdropdownEls[i];
            const rawValue = ddEl.dataset.selectedValue;
            if (rawValue === undefined || rawValue === null || rawValue === '') {
                selectedTexts.push(null);
            } else {
                const selectedOptEl = ddEl.querySelector(`.mkq-custom-scdropdown-option[data-value="${rawValue}"]`);
                const optText = selectedOptEl ? (selectedOptEl.dataset.optionText || selectedOptEl.textContent.trim()) : null;
                selectedTexts.push(optText);
            }
        }

        earnedPoints += scoreScdropdownQuestion(qData, selectedTexts);

        if (!showCorrection) return;

        for (let i = 0; i < count; i++) {
            const scdropdown = scdropdownEls[i];
            const ddData = ddDataList[i];
            const chosenText = selectedTexts[i];
            const isEmpty = (chosenText === null);
            const allOptions = ddData.options || [];
            const correctText = String(allOptions[ddData.correct_index] ?? '').trim();
            const isCorrect = !isEmpty && String(chosenText).trim() === correctText;

            const next = scdropdown.nextSibling;
            if (next && next.classList?.contains('mkq-scdropdown-correction-wrapper') && next.dataset?.forId === scdropdown.id) next.remove();

            if (isCorrect) {
                scdropdown.style.display = ''; scdropdown.classList.remove('incorrect'); scdropdown.classList.add('correct');
            } else {
                const wrongAnswerHTML = isEmpty ? t("ui.empty_answer", "(empty)") : convertLatexDelimiters(String(chosenText));
                scdropdown.style.display = 'none';
                const wrapper = document.createElement('span');
                wrapper.className = 'mkq-scdropdown-correction-wrapper';
                wrapper.dataset.forId = scdropdown.id;
                wrapper.style.cssText = 'display:inline-flex;align-items:center;gap:.5em;position:relative;';
                const wrongLabel = document.createElement('span');
                wrongLabel.className = 'mkq-blank-wrong-label'; wrongLabel.innerHTML = wrongAnswerHTML;
                wrapper.appendChild(wrongLabel);
                const correctScdropdown = document.createElement('div');
                correctScdropdown.className = 'mkq-custom-scdropdown correct';
                correctScdropdown.style.cssText = 'display:inline-block;pointer-events:none;';
                const correctBtn = document.createElement('button');
                correctBtn.type = 'button'; correctBtn.className = 'mkq-custom-scdropdown-btn correct'; correctBtn.style.cursor = 'default';
                const correctValueSpan = document.createElement('span');
                correctValueSpan.className = 'mkq-custom-scdropdown-value';
                correctValueSpan.innerHTML = convertLatexDelimiters(correctText);
                const arrow = document.createElement('span');
                arrow.className = 'mkq-custom-scdropdown-arrow'; arrow.textContent = '✓'; arrow.style.color = 'green';
                correctBtn.appendChild(correctValueSpan); correctBtn.appendChild(arrow);
                correctScdropdown.appendChild(correctBtn); wrapper.appendChild(correctScdropdown);
                scdropdown.after(wrapper);
                setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise([wrapper]).catch(() => { }); }, 150);
            }
        }
    });
    displayScorePoints(quizId, earnedPoints, totalPoints);
}

/**
 * validateMcdropdownQuiz
 *
 * Lecture des indices sélectionnés via data-selected-indices (JSON array).
 * Mapping indices → textes via data-option-text sur chaque option.
 * Correction visuelle : badge wrong + dropdown read-only correct.
 */
function validateMcdropdownQuiz(quiz, quizId) {
    const questions = quiz.querySelectorAll('.mkq-question[data-question-id]');
    let totalPoints = 0, earnedPoints = 0;
    const correctionData = decryptAnswers(quiz.dataset.encrypted);
    if (!correctionData) { alert(t("ui.error_downloading_corrections", "Error downloading corrections")); return; }
    const showCorrection = shouldShowCorrections(quiz);

    questions.forEach((questionEl) => {
        const questionId = parseInt(questionEl.dataset.questionId, 10);
        const qData = correctionData.questions.find(q => q.number === questionId);
        if (!qData) return;
        totalPoints += Number(qData.points || 1);

        const ddEls = Array.from(questionEl.querySelectorAll('.mkq-custom-mcdropdown'));
        const ddDataList = Array.isArray(qData.mcdropdowns) ? qData.mcdropdowns : [];
        const count = Math.min(ddEls.length, ddDataList.length);

        // Lire les textes sélectionnés pour chaque dropdown
        const selectedTextsByDropdown = [];
        for (let i = 0; i < count; i++) {
            const ddEl = ddEls[i];
            let selectedIndices = [];
            try { selectedIndices = JSON.parse(ddEl.dataset.selectedIndices || '[]'); } catch(e) {}
            const texts = selectedIndices.map(idx => {
                const optEl = ddEl.querySelector(`.mkq-custom-mcdropdown-option[data-value="${idx}"]`);
                return optEl ? (optEl.dataset.optionText || optEl.textContent.trim()) : null;
            }).filter(t => t !== null);
            selectedTextsByDropdown.push(texts);
        }

        earnedPoints += scoreMcdropdownQuestion(qData, selectedTextsByDropdown);

        if (!showCorrection) return;

        for (let i = 0; i < count; i++) {
            const ddEl = ddEls[i];
            const ddData = ddDataList[i];
            const selectedTexts = selectedTextsByDropdown[i];
            const opts = ddData.options || [];
            const correctIndices = ddData.correct_indices || [];
            const correctTexts = correctIndices.map(idx => String(opts[idx] ?? '').trim());
            const correctSet = new Set(correctTexts);

            const selSorted = [...selectedTexts].map(t => t.trim()).sort();
            const corSorted = [...correctTexts].sort();
            const isCorrect = arraysEqual(selSorted, corSorted) && selSorted.length > 0;

            // Supprimer ancienne correction
            const next = ddEl.nextSibling;
            if (next && next.classList?.contains('mkq-mcdropdown-correction-wrapper') && next.dataset?.forId === ddEl.id) next.remove();

            if (isCorrect) {
                ddEl.style.display = ''; ddEl.classList.remove('incorrect'); ddEl.classList.add('correct');
                // Rendre read-only
                ddEl.querySelectorAll('.mkq-custom-mcdropdown-btn').forEach(btn => { btn.onclick = null; btn.style.pointerEvents = 'none'; });
            } else {
                // Afficher badge wrong + dropdown correct en lecture seule
                const wrongLabel = selectedTexts.length === 0
                    ? t("ui.empty_answer", "(empty)")
                    : selectedTexts.map(convertLatexDelimiters).join(', ');

                ddEl.style.display = 'none';

                const wrapper = document.createElement('span');
                wrapper.className = 'mkq-mcdropdown-correction-wrapper';
                wrapper.dataset.forId = ddEl.id;
                wrapper.style.cssText = 'display:inline-flex;align-items:center;gap:.5em;position:relative;';

                const wLabel = document.createElement('span');
                wLabel.className = 'mkq-blank-wrong-label';
                wLabel.innerHTML = `<s>${wrongLabel}</s>`;
                wrapper.appendChild(wLabel);

                // Dropdown read-only montrant les correctes
                const correctDd = document.createElement('div');
                correctDd.className = 'mkq-custom-mcdropdown correct';
                correctDd.style.cssText = 'display:inline-block;pointer-events:none;';
                const correctBtn = document.createElement('button');
                correctBtn.type = 'button'; correctBtn.className = 'mkq-custom-mcdropdown-btn correct'; correctBtn.style.cursor = 'default';
                const correctValueSpan = document.createElement('span');
                correctValueSpan.className = 'mkq-custom-mcdropdown-value';
                correctValueSpan.innerHTML = correctTexts.map(convertLatexDelimiters).join(', ') || '--';
                const arrow = document.createElement('span');
                arrow.className = 'mkq-custom-mcdropdown-arrow'; arrow.textContent = '✓'; arrow.style.color = 'green';
                correctBtn.appendChild(correctValueSpan); correctBtn.appendChild(arrow);
                correctDd.appendChild(correctBtn);
                wrapper.appendChild(correctDd);

                ddEl.after(wrapper);
                setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise([wrapper]).catch(() => {}); }, 150);
            }
        }
    });
    displayScorePoints(quizId, earnedPoints, totalPoints);
}

// ─────────────────────────────────────────────────────────────────────────────
// Scdropdown interactions
// ─────────────────────────────────────────────────────────────────────────────

window.toggleScdropdownQuiz = function(button) {
    const scdropdown = button.closest('.mkq-custom-scdropdown');
    const options = scdropdown.querySelector('.mkq-custom-scdropdown-options');
    const isOpen = options.style.display === 'block';
    document.querySelectorAll('.mkq-custom-scdropdown-options').forEach(opt => opt.style.display = 'none');
    options.style.display = isOpen ? 'none' : 'block';
};

window.selectScdropdownQuizOption = function(optionElement) {
    const scdropdown = optionElement.closest('.mkq-custom-scdropdown');
    const button = scdropdown.querySelector('.mkq-custom-scdropdown-btn');
    const valueSpan = button.querySelector('.mkq-custom-scdropdown-value');
    const optionsDiv = scdropdown.querySelector('.mkq-custom-scdropdown-options');
    valueSpan.innerHTML = optionElement.innerHTML;
    scdropdown.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
    optionElement.classList.add('selected');
    scdropdown.dataset.selectedValue = optionElement.dataset.value;
    optionsDiv.style.display = 'none';
    if (window.MathJax?.typesetPromise) MathJax.typesetPromise([valueSpan]).catch(() => {});
};

// ─────────────────────────────────────────────────────────────────────────────
// Mcdropdown interactions
// ─────────────────────────────────────────────────────────────────────────────

/**
 * toggleMcdropdownPanel — ouvre/ferme le panneau, ferme les autres.
 */
window.toggleMcdropdownPanel = function(button) {
    const dd = button.closest('.mkq-custom-mcdropdown');
    const panel = dd.querySelector('.mkq-custom-mcdropdown-options');
    const isOpen = panel.style.display === 'block';
    // Fermer tous les autres panneaux mcdropdown
    document.querySelectorAll('.mkq-custom-mcdropdown-options').forEach(p => p.style.display = 'none');
    panel.style.display = isOpen ? 'none' : 'block';
};

/**
 * toggleMcdropdownOption — toggle la sélection d'une option (clic = on/off).
 * Le panneau reste ouvert.
 * Met à jour data-selected-indices et le label du bouton.
 */
window.toggleMcdropdownOption = function(optEl) {
    const dd = optEl.closest('.mkq-custom-mcdropdown');
    const btn = dd.querySelector('.mkq-custom-mcdropdown-btn');
    const valueSpan = btn.querySelector('.mkq-custom-mcdropdown-value');

    optEl.classList.toggle('selected');

    // Reconstruire la liste des indices sélectionnés
    const allOpts = Array.from(dd.querySelectorAll('.mkq-custom-mcdropdown-option'));
    const selectedIndices = allOpts
        .filter(o => o.classList.contains('selected'))
        .map(o => parseInt(o.dataset.value, 10));

    dd.dataset.selectedIndices = JSON.stringify(selectedIndices);

    // Mettre à jour le label du bouton
    if (selectedIndices.length === 0) {
        valueSpan.textContent = '--';
    } else {
        const selectedTexts = selectedIndices.map(idx => {
            const o = dd.querySelector(`.mkq-custom-mcdropdown-option[data-value="${idx}"]`);
            return o ? (o.dataset.optionText || o.textContent.trim()) : '';
        }).filter(Boolean);
        valueSpan.innerHTML = selectedTexts.map(convertLatexDelimiters).join(', ');
    }

    if (window.MathJax?.typesetPromise) MathJax.typesetPromise([valueSpan]).catch(() => {});
};

// Fermer les panneaux mcdropdown au clic extérieur
document.addEventListener('click', (e) => {
    if (!e.target.closest('.mkq-custom-scdropdown'))
        document.querySelectorAll('.mkq-custom-scdropdown-options').forEach(opt => opt.style.display = 'none');
    if (!e.target.closest('.mkq-custom-mcdropdown'))
        document.querySelectorAll('.mkq-custom-mcdropdown-options').forEach(p => p.style.display = 'none');
});

// ─────────────────────────────────────────────────────────────────────────────
// Fillblanks correction scdropdown helpers
// ─────────────────────────────────────────────────────────────────────────────

function convertLatexDelimiters(text) {
    let result = text;
    result = result.replace(/\$\$(.*?)\$\$/g, '\\[$1\\]');
    result = result.replace(/\$([^\$]+)\$/g, '\\($1\\)');
    return result;
}

function createFillblanksMathJaxScdropdown(id, answers) {
    const container = document.createElement('div');
    container.className = 'mkq-custom-select'; container.id = id;
    container.setAttribute('role', 'combobox'); container.setAttribute('aria-haspopup', 'listbox');
    container.setAttribute('aria-expanded', 'false');
    const button = document.createElement('button');
    button.className = 'mkq-custom-select-btn'; button.type = 'button';
    button.setAttribute('aria-controls', `${id}-listbox`);
    const valueSpan = document.createElement('span');
    valueSpan.className = 'mkq-custom-select-value';
    valueSpan.innerHTML = convertLatexDelimiters(answers[0]);
    button.appendChild(valueSpan);
    const arrow = document.createElement('span');
    arrow.className = 'mkq-custom-select-arrow'; arrow.textContent = '▾'; arrow.setAttribute('aria-hidden', 'true');
    const listbox = document.createElement('div');
    listbox.className = 'mkq-custom-select-listbox'; listbox.id = `${id}-listbox`;
    listbox.setAttribute('role', 'listbox'); listbox.setAttribute('tabindex', '-1');
    answers.forEach((answer, index) => {
        const option = document.createElement('div');
        option.className = 'mkq-custom-select-option'; option.setAttribute('role', 'option');
        option.setAttribute('tabindex', index === 0 ? '0' : '-1');
        option.setAttribute('aria-selected', index === 0 ? 'true' : 'false');
        option.dataset.value = answer; option.innerHTML = convertLatexDelimiters(answer);
        option.addEventListener('click', () => selectFillblanksOption(container, button, valueSpan, listbox, option));
        option.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); selectFillblanksOption(container, button, valueSpan, listbox, option); }
        });
        listbox.appendChild(option);
    });
    button.addEventListener('click', () => toggleFillblanksScdropdown(container, listbox));
    listbox.addEventListener('keydown', (e) => handleFillblanksKeyboardNav(e, container, listbox));
    container.appendChild(button); container.appendChild(arrow); container.appendChild(listbox);
    document.addEventListener('click', (e) => { if (!container.contains(e.target)) container.setAttribute('aria-expanded', 'false'); });
    setTimeout(() => { if (window.MathJax?.typesetPromise) MathJax.typesetPromise([container]).catch(() => { }); }, 50);
    return container;
}

function toggleFillblanksScdropdown(container, listbox) {
    const isOpen = container.getAttribute('aria-expanded') === 'true';
    container.setAttribute('aria-expanded', !isOpen ? 'true' : 'false');
    if (!isOpen) listbox.focus();
}

function selectFillblanksOption(container, button, valueSpan, listbox, option) {
    listbox.querySelectorAll('.mkq-custom-select-option').forEach(opt => opt.setAttribute('aria-selected', 'false'));
    option.setAttribute('aria-selected', 'true');
    valueSpan.innerHTML = option.innerHTML;
    if (valueSpan.innerHTML.includes('\\(') || valueSpan.innerHTML.includes('\\[')) {
        if (window.MathJax?.typesetPromise) MathJax.typesetPromise([button]).catch(() => { });
    }
    container.setAttribute('aria-expanded', 'false'); button.focus();
}

function handleFillblanksKeyboardNav(e, container, listbox) {
    const options = Array.from(listbox.querySelectorAll('.mkq-custom-select-option'));
    const current = document.activeElement.classList.contains('mkq-custom-select-option') ? document.activeElement : options[0];
    let idx = options.indexOf(current);
    if (e.key === 'Escape') { e.preventDefault(); container.setAttribute('aria-expanded', 'false'); container.querySelector('.mkq-custom-select-btn').focus(); }
    if (e.key === 'ArrowDown') { e.preventDefault(); options[Math.min(idx + 1, options.length - 1)].focus(); }
    if (e.key === 'ArrowUp') { e.preventDefault(); options[Math.max(idx - 1, 0)].focus(); }
}

// ─────────────────────────────────────────────────────────────────────────────
// Display score
// ─────────────────────────────────────────────────────────────────────────────

function displayScorePoints(quizId, earned, total) {
    const scoreElement = document.getElementById(`score-${quizId}`);
    if (!scoreElement) return;
    const e = Number(earned), ttot = Number(total) > 0 ? Number(total) : 0;
    const pct = ttot > 0 ? (e / ttot) * 100 : 0;
    scoreElement.textContent = `${t("ui.score_label", "Score")}: ${e.toFixed(2)}/${ttot.toFixed(2)} (${pct.toFixed(1)}%)`;
    scoreElement.style.display = 'block';
    scoreElement.classList.remove('good', 'medium', 'bad');
    if (pct >= 75) scoreElement.classList.add('good');
    else if (pct >= 50) scoreElement.classList.add('medium');
    else scoreElement.classList.add('bad');
}

// ─────────────────────────────────────────────────────────────────────────────
// Crypto / storage
// ─────────────────────────────────────────────────────────────────────────────

function decryptAnswers(encryptedData) {
    try {
        const key = window.QUIZ_CONFIG.encryption_key || 'default-key-change-me';
        const encrypted = atob(encryptedData);
        const keyBytes = new TextEncoder().encode(key);
        const encryptedBytes = new Uint8Array(encrypted.split('').map(c => c.charCodeAt(0)));
        const decrypted = new Uint8Array(encryptedBytes.length);
        for (let i = 0; i < encryptedBytes.length; i++) decrypted[i] = encryptedBytes[i] ^ keyBytes[i % keyBytes.length];
        return JSON.parse(new TextDecoder().decode(decrypted));
    } catch (e) {
        console.error(t("ui.error_decrypting", "Decrypting Error"), e);
        return null;
    }
}

function saveAnswers(quizId) {
    const quiz = document.querySelector(`[data-quiz-id="${quizId}"]`);
    if (!quiz) return;

    const correctionData = decryptAnswers(quiz.dataset.encrypted);
    if (correctionData && correctionData.save_answers === false) {
        localStorage.removeItem(`mkq_answers_${quizId}`);
        return;
    }

    const answers = {};
    quiz.querySelectorAll('input[type="checkbox"]:checked, input[type="radio"]:checked').forEach(input => { answers[input.id] = true; });
    quiz.querySelectorAll('.mkq-blank-input').forEach(input => { if (input.value.trim() !== '') answers[input.id] = input.value; });
    quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(dd => { if (dd.dataset.selectedValue) answers[dd.id] = dd.dataset.selectedValue; });
    quiz.querySelectorAll('.mkq-custom-mcdropdown').forEach(dd => {
        const indices = dd.dataset.selectedIndices || '[]';
        if (indices !== '[]') answers[dd.id] = indices;
    });
    if (Object.keys(answers).length > 0) localStorage.setItem(`mkq_answers_${quizId}`, JSON.stringify(answers));
    else localStorage.removeItem(`mkq_answers_${quizId}`);
}

function loadSavedAnswers() {
    document.querySelectorAll('.mkdocs-superquiz').forEach(quiz => {
        const quizId = quiz.dataset.quizId;
        try {
            const correctionData = decryptAnswers(quiz.dataset.encrypted);
            if (correctionData && correctionData.save_answers === false) {
                localStorage.removeItem(`mkq_answers_${quizId}`);
                quiz.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => { input.checked = false; });
                quiz.querySelectorAll('.mkq-blank-input').forEach(input => { input.value = ''; });
                quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(dd => {
                    dd.dataset.selectedValue = '';
                    const valueSpan = dd.querySelector('.mkq-custom-scdropdown-value');
                    if (valueSpan) valueSpan.textContent = '--';
                    dd.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
                });
                quiz.querySelectorAll('.mkq-custom-mcdropdown').forEach(dd => {
                    dd.dataset.selectedIndices = '[]';
                    const valueSpan = dd.querySelector('.mkq-custom-mcdropdown-value');
                    if (valueSpan) valueSpan.textContent = '--';
                    dd.querySelectorAll('.mkq-custom-mcdropdown-option').forEach(opt => opt.classList.remove('selected'));
                });
                return;
            }

            const savedAnswersJson = localStorage.getItem(`mkq_answers_${quizId}`);
            if (!savedAnswersJson) return;
            const savedAnswers = JSON.parse(savedAnswersJson);
            if (!savedAnswers || typeof savedAnswers !== 'object') { localStorage.removeItem(`mkq_answers_${quizId}`); return; }

            Object.keys(savedAnswers).forEach(inputId => {
                const el = document.getElementById(inputId);
                if (!el) return;
                const savedValue = savedAnswers[inputId];

                if (el.type === 'checkbox' || el.type === 'radio') {
                    if (typeof savedValue === 'boolean') el.checked = savedValue;
                } else if (el.classList.contains('mkq-blank-input')) {
                    el.value = String(savedValue);
                } else if (el.classList.contains('mkq-custom-scdropdown')) {
                    el.dataset.selectedValue = savedValue;
                    const valueSpan = el.querySelector('.mkq-custom-scdropdown-value');
                    const selectedOption = el.querySelector(`.mkq-custom-scdropdown-option[data-value="${savedValue}"]`);
                    if (valueSpan && selectedOption) valueSpan.innerHTML = selectedOption.innerHTML;
                } else if (el.classList.contains('mkq-custom-mcdropdown')) {
                    // savedValue est une string JSON d'indices
                    let indices = [];
                    try { indices = JSON.parse(savedValue); } catch(e) {}
                    el.dataset.selectedIndices = JSON.stringify(indices);
                    // Restaurer classes .selected et label
                    el.querySelectorAll('.mkq-custom-mcdropdown-option').forEach(opt => opt.classList.remove('selected'));
                    const selectedTexts = [];
                    indices.forEach(idx => {
                        const optEl = el.querySelector(`.mkq-custom-mcdropdown-option[data-value="${idx}"]`);
                        if (optEl) {
                            optEl.classList.add('selected');
                            // Toujours utiliser data-option-text (LaTeX brut), jamais textContent (déjà typeset)
                            selectedTexts.push(optEl.dataset.optionText || optEl.textContent.trim());
                        }
                    });
                    const valueSpan = el.querySelector('.mkq-custom-mcdropdown-value');
                    if (valueSpan) {
                        if (selectedTexts.length > 0) {
                            valueSpan.innerHTML = selectedTexts.map(convertLatexDelimiters).join(', ');
                            // Re-typeset après injection — nécessaire au refresh car MathJax
                            // a déjà tourné sur la page avant loadSavedAnswers()
                            if (window.MathJax?.typesetPromise) {
                                MathJax.typesetPromise([valueSpan]).catch(() => {});
                            }
                        } else {
                            valueSpan.textContent = '--';
                        }
                    }
                }
            });
        } catch (error) { localStorage.removeItem(`mkq_answers_${quizId}`); }
    });
}

function refreshAllQuizzes() {
    const quizzes = document.querySelectorAll('.mkdocs-superquiz');
    if (quizzes.length === 0) return;
    const pageUuid = quizzes[0]?.dataset.pageUuid;
    quizzes.forEach(quiz => {
        const quizId = quiz.dataset.quizId;
        const quizType = quiz.dataset.quizType;
        if (quizType === 'fillblanks') {
            quiz.querySelectorAll('.mkq-blank-correction-wrapper').forEach(w => w.remove());
            quiz.querySelectorAll('.mkq-blank-input').forEach(input => { input.value = ''; input.style.display = ''; input.classList.remove('correct', 'incorrect'); });
        } else if (quizType === 'scdropdown') {
            quiz.querySelectorAll('.mkq-scdropdown-correction-wrapper').forEach(w => w.remove());
            quiz.querySelectorAll('.mkq-custom-scdropdown').forEach(dd => {
                dd.style.display = ''; dd.dataset.selectedValue = '';
                const valueSpan = dd.querySelector('.mkq-custom-scdropdown-value');
                if (valueSpan) valueSpan.textContent = '--';
                dd.querySelectorAll('.mkq-custom-scdropdown-option').forEach(opt => opt.classList.remove('selected'));
                dd.classList.remove('correct', 'incorrect');
            });
        } else if (quizType === 'mcdropdown') {
            quiz.querySelectorAll('.mkq-mcdropdown-correction-wrapper').forEach(w => w.remove());
            quiz.querySelectorAll('.mkq-custom-mcdropdown').forEach(dd => {
                dd.style.display = ''; dd.dataset.selectedIndices = '[]';
                const valueSpan = dd.querySelector('.mkq-custom-mcdropdown-value');
                if (valueSpan) valueSpan.textContent = '--';
                dd.querySelectorAll('.mkq-custom-mcdropdown-option').forEach(opt => opt.classList.remove('selected'));
                dd.classList.remove('correct', 'incorrect');
                // Réactiver les boutons
                dd.querySelectorAll('.mkq-custom-mcdropdown-btn').forEach(btn => { btn.style.pointerEvents = ''; btn.onclick = function() { window.toggleMcdropdownPanel(this); return false; }; });
            });
        } else {
            quiz.querySelectorAll('input[type="checkbox"], input[type="radio"]').forEach(input => { input.checked = false; });
        }
        quiz.querySelectorAll('.mkq-answer').forEach(answer => {
            answer.classList.remove('correct', 'incorrect');
            const feedback = answer.querySelector('.mkq-answer-feedback');
            if (feedback) { feedback.style.display = 'none'; feedback.textContent = ''; feedback.classList.remove('correct', 'incorrect'); }
        });
        const scoreElement = document.getElementById(`score-${quizId}`);
        if (scoreElement) { scoreElement.style.display = 'none'; scoreElement.textContent = ''; scoreElement.classList.remove('good', 'medium', 'bad'); }
        const lockBtn = quiz.querySelector('.mkq-unlock-btn, .mkq-lock-indicator');
        if (lockBtn && !lockBtn.classList.contains('unlocked')) {
            if (lockBtn.getAttribute('data-lock-state') === 'locked') updateLockIcon(lockBtn, false);
        }
        localStorage.removeItem(`mkq_answers_${quizId}`);
    });
    if (pageUuid) {
        const unlocks = JSON.parse(localStorage.getItem('mkq_unlocks') || '[]');
        localStorage.setItem('mkq_unlocks', JSON.stringify(unlocks.filter(u => u.page_id !== pageUuid)));
    }
    showRefreshNotification();
}

function showRefreshNotification() {
    const notification = document.createElement('div');
    notification.textContent = `✅ ${t("ui.all_quizzes_have_been_reset", "All Quizzes have been reset")}`;
    notification.style.cssText = 'position:fixed;top:20px;right:20px;background:#4caf50;color:white;padding:1rem 1.5rem;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);z-index:10000;font-weight:500;font-size:2em;animation:slideIn 0.3s ease-out;';
    document.body.appendChild(notification);
    setTimeout(() => {
        notification.style.opacity = '0'; notification.style.transition = 'opacity 0.3s';
        setTimeout(() => notification.remove(), 300);
    }, 2000);
}

function addRefreshButton() {
    const pageTitle = document.querySelector('.md-content h1');
    if (!pageTitle) return;
    const quizzes = document.querySelectorAll('.mkdocs-superquiz');
    if (quizzes.length === 0) return;
    const refreshBtn = document.createElement('button');
    refreshBtn.className = 'mkq-refresh-page-btn'; refreshBtn.title = t("ui.reset_all_quizzes", "Reset all quizzes");
    refreshBtn.innerHTML = `<span class="mkq-btn-icon">🔄</span><span class="mkq-btn-text">${t("ui.reset_all_quizzes", "Reset all quizzes")}</span>`;
    refreshBtn.addEventListener('click', () => refreshAllQuizzes());
    const unlockPageBtn = document.createElement('button');
    unlockPageBtn.className = 'mkq-unlock-page-btn'; unlockPageBtn.title = t("ui.unlock_all_page_quizzes", "Unlock all page quizzes");
    unlockPageBtn.innerHTML = `<span class="mkq-btn-icon">🔓</span><span class="mkq-btn-text">${t("ui.unlock_all_page_quizzes", "Unlock all page quizzes")}</span>`;
    unlockPageBtn.addEventListener('click', () => showUnlockModal('page'));
    const buttonsContainer = document.createElement('div');
    buttonsContainer.className = 'mkq-page-buttons';
    buttonsContainer.appendChild(unlockPageBtn); buttonsContainer.appendChild(refreshBtn);
    pageTitle.appendChild(buttonsContainer);
}

function clearAllQuizData() {
    Object.keys(localStorage).filter(key => key.startsWith('mkq_')).forEach(key => localStorage.removeItem(key));
    setTimeout(() => location.reload(), 1000);
}
window.clearAllQuizData = clearAllQuizData;

function initQuizPlugin() {
    if (!window.QUIZ_CONFIG || !window.QUIZ_CONFIG.i18n) return;
    createUnlockModal();
    applyRandomization();
    restoreUnlockedCorrections();
    loadSavedAnswers();
    addRefreshButton();
}

if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initQuizPlugin);
else initQuizPlugin();