// 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().
 */
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;

        // DEBUG
        console.log(`[quiz ${quizEl.dataset.quizId}] div data-randomize-answers="${quizEl.dataset.randomizeAnswers}" → quizRandomizeAnswers=${quizRandomizeAnswers}`);

        // ── 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;

                const qRand = qEl.dataset.randomizeAnswers;
                let shouldRandomize;
                if (qRand === 'true') shouldRandomize = true;
                else if (qRand === 'false') shouldRandomize = false;
                else shouldRandomize = quizRandomizeAnswers; // "inherit" ou absent

                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;

                const qRand = qEl.dataset.randomizeAnswers;
                let shouldRandomize;
                if (qRand === 'true') shouldRandomize = true;
                else if (qRand === 'false') shouldRandomize = false;
                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;

                const qRand = qEl.dataset.randomizeAnswers;
                let shouldRandomize;
                if (qRand === 'true') shouldRandomize = true;
                else if (qRand === 'false') shouldRandomize = false;
                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);
                    });
                    ddEl.dataset.selectedIndices = '[]';
                });
            });
        } else if (quizType === 'orderquiz') {
            // orderquiz : mélanger les items de chaque question
            quizEl.querySelectorAll('.mkq-order-question[data-question-id]').forEach(qEl => {
                const itemsContainer = qEl.querySelector('.mkq-order-items');
                if (!itemsContainer) return;
                const items = Array.from(itemsContainer.querySelectorAll('.mkq-order-item'));
                if (items.length <= 1) return;
                shuffleArray(items);
                items.forEach(item => itemsContainer.appendChild(item));
            });
        }

        // ── 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
 */
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
 */
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
 */
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);

    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;
        const ddDf = ddEm && ddEm.default_false !== null && ddEm.default_false !== undefined
            ? Number(ddEm.default_false) : null;
        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 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 {
            // 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);
}

// ─────────────────────────────────────────────────────────────────────────────
// Orderquiz — Kendall tau scoring
// ─────────────────────────────────────────────────────────────────────────────

/**
 * kendallTauScore(userOrder, n)
 * userOrder : tableau d'indices originaux dans l'ordre soumis par l'utilisateur
 * Retourne un ratio [0, 1] : 1 = ordre parfait, 0 = ordre inversé complet
 * Formule : 1 - (inversions / inversions_max)
 * inversions_max = n*(n-1)/2
 */
function kendallTauScore(userOrder, n) {
    if (n <= 1) return 1;
    const maxInversions = n * (n - 1) / 2;
    let inversions = 0;
    for (let i = 0; i < userOrder.length; i++) {
        for (let j = i + 1; j < userOrder.length; j++) {
            if (userOrder[i] > userOrder[j]) inversions++;
        }
    }
    return 1 - (inversions / maxInversions);
}

/**
 * scoreOrderQuestion
 * userOrder : tableau d'indices originaux dans l'ordre soumis (lus depuis le DOM)
 * Modes supportés : all_or_nothing, proportional (Kendall tau)
 */
function scoreOrderQuestion(questionData, userOrder) {
    const points   = Number(questionData.points || 1);
    const mode     = questionData.scoring_mode || 'proportional';
    const n        = (questionData.items || []).length;
    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 perfect = userOrder.length === n && userOrder.every((idx, pos) => idx === pos);

    if (mode === 'all_or_nothing') {
        return clampScore(perfect ? points : 0, points, minScore, clampMax);
    }

    // proportional : Kendall tau
    const ratio = kendallTauScore(userOrder, n);
    return clampScore(points * ratio, 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 if (quizType === 'orderquiz') validateOrderQuiz(quiz, quizId);
    else validateChoiceQuiz(quiz, quizId);
    saveAnswers(quizId);
}

/**
 * validateChoiceQuiz — mcquiz / scquiz
 */
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
 */
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
 */
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);

        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;

            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');
                ddEl.querySelectorAll('.mkq-custom-mcdropdown-btn').forEach(btn => { btn.onclick = null; btn.style.pointerEvents = 'none'; });
            } else {
                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);

                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);
}

// ─────────────────────────────────────────────────────────────────────────────
// Orderquiz — validation
// ─────────────────────────────────────────────────────────────────────────────

function validateOrderQuiz(quiz, quizId) {
    const questions = quiz.querySelectorAll('.mkq-order-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 container = questionEl.querySelector('.mkq-order-items');
        if (!container) return;

        // Lire l'ordre soumis : tableau d'indices originaux dans l'ordre actuel du DOM
        const userOrder = Array.from(container.querySelectorAll('.mkq-order-item'))
            .map(el => parseInt(el.dataset.originalIndex, 10));

        const score = scoreOrderQuestion(qData, userOrder);
        earnedPoints += score;

        if (showCorrection) {
            const n = (qData.items || []).length;
            const perfect = userOrder.length === n && userOrder.every((idx, pos) => idx === pos);

            container.querySelectorAll('.mkq-order-item').forEach((item, pos) => {
                item.classList.remove('correct', 'incorrect');
                const originalIdx = parseInt(item.dataset.originalIndex, 10);
                const isInPlace = (originalIdx === pos);
                item.classList.add(isInPlace ? 'correct' : 'incorrect');

                // Badge indiquant la position correcte attendue
                let badge = item.querySelector('.mkq-order-position-badge');
                if (!badge) {
                    badge = document.createElement('span');
                    badge.className = 'mkq-order-position-badge';
                    item.appendChild(badge);
                }
                badge.textContent = isInPlace ? '✓' : `→ pos. ${originalIdx + 1}`;
                badge.style.color = isInPlace
                    ? 'var(--mkq-success)'
                    : 'var(--mkq-error)';
            });
        }
    });

    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
// ─────────────────────────────────────────────────────────────────────────────

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';
    document.querySelectorAll('.mkq-custom-mcdropdown-options').forEach(p => p.style.display = 'none');
    panel.style.display = isOpen ? 'none' : 'block';
};

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');

    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);

    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 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');
});

// ─────────────────────────────────────────────────────────────────────────────
// Orderquiz — Drag & Drop (desktop + touch)
// ─────────────────────────────────────────────────────────────────────────────

(function initOrderDragDrop() {

    let dragSrc      = null;
    let touchSrc     = null;
    let touchClone   = null;
    let touchOffsetX = 0;
    let touchOffsetY = 0;
    let placeholder  = null;
    let lastTarget   = null;

    // ── Placeholder ───────────────────────────────────────────────────────

    function createPlaceholder(referenceItem) {
        const ph = document.createElement('div');
        ph.className = 'mkq-order-placeholder';
        ph.style.height = referenceItem.offsetHeight + 'px';
        return ph;
    }

    function removePlaceholder() {
        if (placeholder && placeholder.parentNode) placeholder.parentNode.removeChild(placeholder);
        placeholder = null;
        lastTarget  = null;
    }

    // ── Position helper ───────────────────────────────────────────────────

    function getInsertPosition(src, target) {
        const container = target.closest('.mkq-order-items');
        if (!container || !src) return 'before';
        const items = Array.from(container.querySelectorAll('.mkq-order-item'))
            .filter(el => el !== placeholder);
        const srcIdx = items.indexOf(src);
        const tgtIdx = items.indexOf(target);
        return srcIdx < tgtIdx ? 'after' : 'before';
    }

    function insertPlaceholderNear(src, target) {
        if (!placeholder) return;
        if (target === lastTarget) return;
        lastTarget = target;

        const position = getInsertPosition(src, target);
        if (position === 'before') {
            target.parentNode.insertBefore(placeholder, target);
        } else {
            target.parentNode.insertBefore(placeholder, target.nextSibling);
        }
    }

    // ── Desktop ──────────────────────────────────────────────────────────

    function onDragStart(e) {
        dragSrc = e.currentTarget;
        dragSrc.classList.add('dragging');
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/plain', '');
        placeholder = createPlaceholder(dragSrc);
        // Insérer le placeholder immédiatement après la source dans le DOM
        dragSrc.parentNode.insertBefore(placeholder, dragSrc.nextSibling);
    }

    function onDragEnd() {
        if (dragSrc) dragSrc.classList.remove('dragging');
        removePlaceholder();
        dragSrc = null;
    }

    function onDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
        const target = e.currentTarget;
        if (!dragSrc || target === dragSrc || target === placeholder) return;
        const container = target.closest('.mkq-order-items');
        if (!container) return;
        insertPlaceholderNear(dragSrc, target);
    }

    function onDragLeave(e) {
        // Intentionnellement vide — le placeholder reste stable
    }

    function onDrop(e) {
        e.preventDefault();
        // Déléguer au handler container (le drop peut tomber n'importe où)
        onContainerDrop(e);
    }

    function onContainerDragOver(e) {
        e.preventDefault();
        if (!dragSrc || !placeholder) return;
        // Si on survole directement le container (zone vide en bas) : placeholder à la fin
        if (e.target === e.currentTarget) {
            if (lastTarget !== e.currentTarget) {
                lastTarget = e.currentTarget;
                e.currentTarget.appendChild(placeholder);
            }
        }
    }

    function onContainerDrop(e) {
        e.preventDefault();
        if (!dragSrc || !placeholder || !placeholder.parentNode) {
            removePlaceholder();
            return;
        }
        placeholder.parentNode.insertBefore(dragSrc, placeholder);
        removePlaceholder();
    }

    // ── Touch ─────────────────────────────────────────────────────────────

    function onTouchStart(e) {
        if (e.touches.length !== 1) return;
        touchSrc = e.currentTarget;
        touchSrc.classList.add('dragging');

        const touch = e.touches[0];
        const rect  = touchSrc.getBoundingClientRect();
        touchOffsetX = touch.clientX - rect.left;
        touchOffsetY = touch.clientY - rect.top;

        touchClone = touchSrc.cloneNode(true);
        touchClone.className = 'mkq-order-item mkq-order-ghost';
        touchClone.style.cssText = [
            'position:fixed',
            'pointer-events:none',
            'opacity:0.85',
            'z-index:9999',
            `width:${rect.width}px`,
            `left:${touch.clientX - touchOffsetX}px`,
            `top:${touch.clientY - touchOffsetY}px`,
        ].join(';');
        document.body.appendChild(touchClone);

        placeholder = createPlaceholder(touchSrc);
        // Insérer le placeholder immédiatement après la source dans le DOM
        touchSrc.parentNode.insertBefore(placeholder, touchSrc.nextSibling);
        e.preventDefault();
    }

    function onTouchMove(e) {
        if (!touchSrc || !touchClone || e.touches.length !== 1) return;
        e.preventDefault();
        const touch = e.touches[0];
        touchClone.style.left = `${touch.clientX - touchOffsetX}px`;
        touchClone.style.top  = `${touch.clientY - touchOffsetY}px`;

        touchClone.style.display = 'none';
        const elBelow = document.elementFromPoint(touch.clientX, touch.clientY);
        touchClone.style.display = '';
        if (!elBelow) return;

        const target    = elBelow.closest('.mkq-order-item');
        const container = elBelow.closest('.mkq-order-items');

        if (target && target !== touchSrc && target !== placeholder) {
            insertPlaceholderNear(touchSrc, target);
        } else if (container && elBelow === container) {
            if (lastTarget !== container) {
                lastTarget = container;
                container.appendChild(placeholder);
            }
        }
    }

    function onTouchEnd() {
        if (!touchSrc) return;
        touchSrc.classList.remove('dragging');
        if (touchClone) { touchClone.remove(); touchClone = null; }

        if (placeholder && placeholder.parentNode) {
            placeholder.parentNode.insertBefore(touchSrc, placeholder);
        }
        removePlaceholder();
        touchSrc = null;
    }

    // ── Attach events ─────────────────────────────────────────────────────

    function attachOrderItemEvents(item) {
        item.addEventListener('dragstart',  onDragStart);
        item.addEventListener('dragend',    onDragEnd);
        item.addEventListener('dragover',   onDragOver);
        item.addEventListener('dragleave',  onDragLeave);
        item.addEventListener('drop',       onDrop);
        item.addEventListener('touchstart', onTouchStart, { passive: false });
        item.addEventListener('touchmove',  onTouchMove,  { passive: false });
        item.addEventListener('touchend',   onTouchEnd);
    }

    function attachContainerEvents(container) {
        container.addEventListener('dragover', onContainerDragOver);
        container.addEventListener('drop',     onContainerDrop);
    }

    function initAllOrderItems() {
        document.querySelectorAll('.mkq-order-item').forEach(attachOrderItemEvents);
        document.querySelectorAll('.mkq-order-items').forEach(attachContainerEvents);
    }

    const observer = new MutationObserver(mutations => {
        mutations.forEach(m => m.addedNodes.forEach(node => {
            if (node.nodeType !== 1) return;
            if (node.classList?.contains('mkq-order-item'))
                attachOrderItemEvents(node);
            else if (node.classList?.contains('mkq-order-items'))
                attachContainerEvents(node);
            else if (node.querySelectorAll) {
                node.querySelectorAll('.mkq-order-item').forEach(attachOrderItemEvents);
                node.querySelectorAll('.mkq-order-items').forEach(attachContainerEvents);
            }
        }));
    });

    window._initOrderDragDrop = function() {
        initAllOrderItems();
        observer.observe(document.body, { childList: true, subtree: true });
    };

})();






// ─────────────────────────────────────────────────────────────────────────────
// 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;
    });
    // orderquiz : sauvegarder l'ordre courant des items
    quiz.querySelectorAll('.mkq-order-items').forEach(container => {
        const order = Array.from(container.querySelectorAll('.mkq-order-item'))
            .map(el => el.dataset.originalIndex);
        if (order.length > 0) answers[container.id] = JSON.stringify(order);
    });

    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')) {
                    let indices = [];
                    try { indices = JSON.parse(savedValue); } catch(e) {}
                    el.dataset.selectedIndices = JSON.stringify(indices);
                    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');
                            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(', ');
                            if (window.MathJax?.typesetPromise) {
                                MathJax.typesetPromise([valueSpan]).catch(() => {});
                            }
                        } else {
                            valueSpan.textContent = '--';
                        }
                    }
                } else if (el.classList.contains('mkq-order-items')) {
                    // Restaurer l'ordre des items orderquiz
                    let order = [];
                    try { order = JSON.parse(savedValue); } catch(e) { return; }
                    const itemMap = {};
                    Array.from(el.querySelectorAll('.mkq-order-item')).forEach(item => {
                        itemMap[item.dataset.originalIndex] = item;
                    });
                    order.forEach(idx => {
                        const item = itemMap[idx];
                        if (item) el.appendChild(item);
                    });
                }
            });
        } 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');
                dd.querySelectorAll('.mkq-custom-mcdropdown-btn').forEach(btn => {
                    btn.style.pointerEvents = '';
                    btn.onclick = function() { window.toggleMcdropdownPanel(this); return false; };
                });
            });
        } else if (quizType === 'orderquiz') {
            // Supprimer les badges de correction
            quiz.querySelectorAll('.mkq-order-position-badge').forEach(b => b.remove());
            // Remettre les items dans un ordre aléatoire
            quiz.querySelectorAll('.mkq-order-items').forEach(container => {
                const items = Array.from(container.querySelectorAll('.mkq-order-item'));
                items.forEach(item => item.classList.remove('correct', 'incorrect'));
                shuffleArray(items);
                items.forEach(item => container.appendChild(item));
            });
        } 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 quizzes = document.querySelectorAll('.mkdocs-superquiz');
    if (quizzes.length === 0) return;

    // Éviter les doublons si appelé plusieurs fois
    if (document.querySelector('.mkq-header-buttons, .mkq-page-buttons')) return;

    const btnContainer = document.createElement('div');
    btnContainer.className = 'mkq-header-buttons';
    btnContainer.innerHTML = `
        <button class="mkq-header-unlock-btn" title="${t("ui.unlock_all_page_quizzes", "Unlock all page quizzes")}" onclick="showUnlockModal('page')">
            🔓
        </button>
        <button class="mkq-header-refresh-btn" title="${t("ui.reset_all_quizzes", "Reset all quizzes")}" onclick="refreshAllQuizzes()">
            🔄
        </button>
    `;

    const candidates = [
        'header nav form',
        'header nav .md-header__option',
        '.md-header__inner',
        'header nav',
        'header',
    ];

    let inserted = false;
    for (const selector of candidates) {
        const el = document.querySelector(selector);
        if (el) {
            el.insertAdjacentElement('beforebegin', btnContainer);
            inserted = true;
            break;
        }
    }

    if (!inserted) {
        const pageTitle = document.querySelector('.md-content h1, article h1, .content h1, h1');
        if (pageTitle) {
            const h1Container = document.createElement('div');
            h1Container.className = 'mkq-page-buttons';
            const unlockBtn = document.createElement('button');
            unlockBtn.className = 'mkq-unlock-page-btn';
            unlockBtn.title = t("ui.unlock_all_page_quizzes", "Unlock all page quizzes");
            unlockBtn.innerHTML = `<span class="mkq-btn-icon">🔓</span><span class="mkq-btn-text">${t("ui.unlock_all_page_quizzes", "Unlock all page quizzes")}</span>`;
            unlockBtn.addEventListener('click', () => showUnlockModal('page'));
            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());
            h1Container.appendChild(unlockBtn);
            h1Container.appendChild(refreshBtn);
            pageTitle.appendChild(h1Container);
        }
    }
}

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

    // Contexte Reveal.js : re-déclencher après parsing des slides
    document.addEventListener('mkdocs-revealjs-ready', function() {
        applyRandomization();
        restoreUnlockedCorrections();
        loadSavedAnswers();
        addRefreshButton();
        window._initOrderDragDrop && window._initOrderDragDrop();
    }, { once: true });
}

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


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




