(() => {
    const sourceItems = Array.isArray(window.QUIZ_ITEMS)
        ? window.QUIZ_ITEMS
        : [];

    if (sourceItems.length < 4) {
        return;
    }

    const questionNumber = document.getElementById('questionNumber');
    const correctCount = document.getElementById('correctCount');
    const quizTimer = document.getElementById('quizTimer');
    const quizXp = document.getElementById('quizXp');
    const quizCoins = document.getElementById('quizCoins');
    const quizProgress = document.getElementById('quizProgress');

    const questionBadge = document.getElementById('questionBadge');
    const questionInstruction = document.getElementById('questionInstruction');
    const audioPrompt = document.getElementById('audioPrompt');
    const imagePrompt = document.getElementById('imagePrompt');
    const questionImage = document.getElementById('questionImage');
    const textPrompt = document.getElementById('textPrompt');
    const answerGrid = document.getElementById('answerGrid');
    const quizFeedback = document.getElementById('quizFeedback');
    const nextQuestionButton = document.getElementById('nextQuestionButton');

    const resultPanel = document.getElementById('resultPanel');
    const resultIcon = document.getElementById('resultIcon');
    const resultStars = document.getElementById('resultStars');
    const resultTitle = document.getElementById('resultTitle');
    const resultMessage = document.getElementById('resultMessage');
    const finalCorrect = document.getElementById('finalCorrect');
    const finalAccuracy = document.getElementById('finalAccuracy');
    const finalTime = document.getElementById('finalTime');
    const finalXp = document.getElementById('finalXp');
    const finalCoins = document.getElementById('finalCoins');
    const fastestAnswer = document.getElementById('fastestAnswer');
    const perfectRound = document.getElementById('perfectRound');
    const wrongReview = document.getElementById('wrongReview');
    const wrongReviewList = document.getElementById('wrongReviewList');
    const practiceWrongButton = document.getElementById('practiceWrongButton');
    const playAgainButton = document.getElementById('playAgainButton');

    let questions = [];
    let currentIndex = 0;
    let correctAnswers = 0;
    let xp = 0;
    let coins = 0;
    let answered = false;
    let currentAudioItem = null;
    let activeAudio = null;
    let wrongItems = [];
    let reviewItems = [];
    let mode = 'full';

    let startedAt = 0;
    let questionStartedAt = 0;
    let timerId = null;
    let elapsedSeconds = 0;
    let fastestAnswerSeconds = null;

    const shuffle = values => {
        const result = [...values];
        for (let index = result.length - 1; index > 0; index -= 1) {
            const randomIndex = Math.floor(Math.random() * (index + 1));
            [result[index], result[randomIndex]] = [
                result[randomIndex],
                result[index]
            ];
        }
        return result;
    };

    const uniqueBy = (values, selector) => {
        const seen = new Set();
        return values.filter(value => {
            const selected = selector(value).trim().toLocaleLowerCase();
            if (!selected || seen.has(selected)) {
                return false;
            }
            seen.add(selected);
            return true;
        });
    };

    const escapeHtml = value => String(value ?? '').replace(
        /[&<>'"]/g,
        character => ({
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            "'": '&#039;',
            '"': '&quot;'
        })[character]
    );

    const formatTime = totalSeconds => {
        const minutes = Math.floor(totalSeconds / 60);
        const seconds = totalSeconds % 60;
        return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
    };

    function startTimer() {
        stopTimer();
        startedAt = Date.now();
        elapsedSeconds = 0;
        quizTimer.textContent = '00:00';
        timerId = window.setInterval(() => {
            elapsedSeconds = Math.floor((Date.now() - startedAt) / 1000);
            quizTimer.textContent = formatTime(elapsedSeconds);
        }, 500);
    }

    function stopTimer() {
        if (timerId !== null) {
            window.clearInterval(timerId);
            timerId = null;
        }
    }

    function availableTypes(item) {
        const types = ['audio_word'];
        if (item.image) {
            types.push('image_word');
        }
        if (item.meaning) {
            const meaningPool = uniqueBy(
                sourceItems.filter(candidate => candidate.meaning),
                candidate => candidate.meaning
            );
            if (meaningPool.length >= 4) {
                types.push('word_meaning');
                types.push('meaning_word');
            }
        }
        return types;
    }

    function wrongChoices(correctItem, selector) {
        return shuffle(
            uniqueBy(
                sourceItems.filter(
                    candidate => Number(candidate.id) !== Number(correctItem.id)
                ),
                selector
            )
        ).slice(0, 3).map(selector);
    }

    function createQuestion(item, type) {
        if (type === 'word_meaning') {
            const choices = shuffle([
                item.meaning,
                ...wrongChoices(item, candidate => candidate.meaning)
            ]);
            if (choices.length < 4) {
                return createQuestion(item, 'audio_word');
            }
            return {
                type,
                item,
                prompt: item.word,
                correct: item.meaning,
                choices
            };
        }

        if (type === 'meaning_word') {
            return {
                type,
                item,
                prompt: item.meaning,
                correct: item.word,
                choices: shuffle([
                    item.word,
                    ...wrongChoices(item, candidate => candidate.word)
                ])
            };
        }

        return {
            type,
            item,
            prompt: '',
            correct: item.word,
            choices: shuffle([
                item.word,
                ...wrongChoices(item, candidate => candidate.word)
            ])
        };
    }

    function buildQuestions(items) {
        const selectedItems = shuffle(items).slice(
            0,
            Math.min(items.length, 12)
        );
        return selectedItems.map(item => {
            const types = availableTypes(item);
            const type = types[Math.floor(Math.random() * types.length)];
            return createQuestion(item, type);
        });
    }

    function resetPrompt() {
        audioPrompt.hidden = true;
        imagePrompt.hidden = true;
        textPrompt.hidden = true;
        questionImage.removeAttribute('src');
        questionImage.alt = '';
        textPrompt.textContent = '';
        currentAudioItem = null;
    }

    function updateStatus() {
        questionNumber.textContent = `${currentIndex + 1} / ${questions.length}`;
        correctCount.textContent = correctAnswers;
        quizXp.textContent = xp;
        quizCoins.textContent = coins;
    }

    function renderQuestion() {
        answered = false;
        questionStartedAt = Date.now();
        answerGrid.innerHTML = '';
        nextQuestionButton.hidden = true;
        quizFeedback.textContent = 'Choose one answer.';
        quizFeedback.className = 'qz-feedback';
        resetPrompt();
        updateStatus();

        const question = questions[currentIndex];
        currentAudioItem = question.item;
        quizProgress.style.width = `${(currentIndex / questions.length) * 100}%`;

        if (question.type === 'image_word') {
            questionBadge.textContent = mode === 'review'
                ? 'Review: look and choose'
                : 'Look and choose';
            questionInstruction.textContent =
                'Which English word matches this picture?';
            imagePrompt.hidden = false;
            questionImage.src = question.item.image;
            questionImage.alt = '';
        } else if (question.type === 'audio_word') {
            questionBadge.textContent = mode === 'review'
                ? 'Review: listen and choose'
                : 'Listen and choose';
            questionInstruction.textContent =
                'Listen, then choose the word you hear.';
            audioPrompt.hidden = false;
            setTimeout(() => speak(question.item), 300);
        } else if (question.type === 'word_meaning') {
            questionBadge.textContent = mode === 'review'
                ? 'Review: choose the meaning'
                : 'Choose the meaning';
            questionInstruction.textContent =
                'What does this English word mean?';
            textPrompt.hidden = false;
            textPrompt.innerHTML = `
                <strong>${escapeHtml(question.prompt)}</strong>
                ${question.item.ipa
                    ? `<small>${escapeHtml(question.item.ipa)}</small>`
                    : ''}
            `;
        } else {
            questionBadge.textContent = mode === 'review'
                ? 'Review: choose the English word'
                : 'Choose the English word';
            questionInstruction.textContent =
                'Which English word has this meaning?';
            textPrompt.hidden = false;
            textPrompt.innerHTML = `
                <strong>${escapeHtml(question.prompt)}</strong>
            `;
        }

        question.choices.forEach(choice => {
            const button = document.createElement('button');
            button.type = 'button';
            button.className = 'qz-answer-button';
            button.textContent = choice;
            button.dataset.choice = choice;
            button.addEventListener('click', () => {
                chooseAnswer(button, choice);
            });
            answerGrid.appendChild(button);
        });
    }

    function rememberWrongItem(item) {
        if (wrongItems.some(existing => Number(existing.id) === Number(item.id))) {
            return;
        }
        wrongItems.push(item);
    }

    function chooseAnswer(button, choice) {
        if (answered) {
            return;
        }

        answered = true;
        const question = questions[currentIndex];
        const isCorrect = choice === question.correct;
        const answerSeconds = Math.max(
            1,
            Math.round((Date.now() - questionStartedAt) / 1000)
        );

        if (fastestAnswerSeconds === null || answerSeconds < fastestAnswerSeconds) {
            fastestAnswerSeconds = answerSeconds;
        }

        document
            .querySelectorAll('.qz-answer-button')
            .forEach(answerButton => {
                answerButton.disabled = true;
                if (answerButton.dataset.choice === question.correct) {
                    answerButton.classList.add('correct');
                }
            });

        if (isCorrect) {
            button.classList.add('correct');
            quizFeedback.textContent = `✓ Correct! ${question.item.word}`;
            quizFeedback.classList.add('correct');
            correctAnswers += 1;
            xp += mode === 'review' ? 5 : 10;
            coins += mode === 'review' ? 1 : 2;
            speak(question.item);
        } else {
            button.classList.add('wrong');
            quizFeedback.textContent =
                `Not quite. The correct answer is “${question.correct}”.`;
            quizFeedback.classList.add('wrong');
            rememberWrongItem(question.item);
            setTimeout(() => speak(question.item), 180);
        }

        updateStatus();
        quizProgress.style.width = `${((currentIndex + 1) / questions.length) * 100}%`;
        nextQuestionButton.textContent =
            currentIndex === questions.length - 1
                ? 'See Results'
                : 'Next Question →';
        nextQuestionButton.hidden = false;
    }

    function speak(item) {
        if (!item) {
            return;
        }
        if (activeAudio) {
            activeAudio.pause();
            activeAudio = null;
        }
        if ('speechSynthesis' in window) {
            speechSynthesis.cancel();
        }
        if (item.audio) {
            activeAudio = new Audio(item.audio);
            activeAudio.play().catch(() => browserSpeak(item.word));
            return;
        }
        browserSpeak(item.word);
    }

    function browserSpeak(word) {
        if (!('speechSynthesis' in window)) {
            return;
        }
        const utterance = new SpeechSynthesisUtterance(word);
        utterance.lang = 'en-US';
        utterance.rate = 0.78;
        speechSynthesis.speak(utterance);
    }

    function renderWrongReview() {
        wrongReviewList.innerHTML = '';
        if (wrongItems.length === 0) {
            wrongReview.hidden = true;
            practiceWrongButton.hidden = true;
            return;
        }

        wrongReview.hidden = false;
        practiceWrongButton.hidden = false;
        wrongItems.forEach(item => {
            const button = document.createElement('button');
            button.type = 'button';
            button.className = 'qz-wrong-item';
            button.innerHTML = `
                <span>
                    <strong>${escapeHtml(item.word)}</strong>
                    <small>${escapeHtml(item.meaning || '')}</small>
                </span>
                <b>🔊</b>
            `;
            button.addEventListener('click', () => speak(item));
            wrongReviewList.appendChild(button);
        });
    }

    function showResults() {
        stopTimer();
        elapsedSeconds = Math.max(
            elapsedSeconds,
            Math.floor((Date.now() - startedAt) / 1000)
        );
        const accuracy = Math.round((correctAnswers / questions.length) * 100);
        const speedBonus = Math.max(
            0,
            Math.min(20, questions.length * 8 - elapsedSeconds)
        );
        xp += speedBonus;

        finalCorrect.textContent = `${correctAnswers} / ${questions.length}`;
        finalAccuracy.textContent = `${accuracy}%`;
        finalTime.textContent = formatTime(elapsedSeconds);
        finalXp.textContent = xp;
        finalCoins.textContent = coins;
        fastestAnswer.textContent = fastestAnswerSeconds === null
            ? '—'
            : `${fastestAnswerSeconds}s`;
        perfectRound.textContent = accuracy === 100 ? 'Yes' : 'No';

        const starCount = accuracy === 100
            ? 5
            : accuracy >= 80
                ? 4
                : accuracy >= 60
                    ? 3
                    : accuracy >= 40
                        ? 2
                        : 1;
        resultStars.textContent = '⭐'.repeat(starCount) + '☆'.repeat(5 - starCount);

        if (mode === 'review') {
            resultIcon.textContent = wrongItems.length === 0 ? '✅' : '📚';
            resultTitle.textContent = wrongItems.length === 0
                ? 'Review complete!'
                : 'Keep practising!';
            resultMessage.textContent = wrongItems.length === 0
                ? 'You corrected every word from the previous round.'
                : 'Listen again and repeat the remaining words.';
        } else if (accuracy === 100) {
            resultIcon.textContent = '🏆';
            resultTitle.textContent = 'Perfect!';
            resultMessage.textContent =
                `All answers are correct. Speed bonus: +${speedBonus} XP.`;
        } else if (accuracy >= 75) {
            resultIcon.textContent = '🌟';
            resultTitle.textContent = 'Excellent!';
            resultMessage.textContent =
                `You remembered most of the words. Speed bonus: +${speedBonus} XP.`;
        } else if (accuracy >= 50) {
            resultIcon.textContent = '👏';
            resultTitle.textContent = 'Good job!';
            resultMessage.textContent =
                'Listen to the words below, then practise the wrong answers.';
        } else {
            resultIcon.textContent = '💪';
            resultTitle.textContent = 'Keep going!';
            resultMessage.textContent =
                'Review the words below before trying again.';
        }

        renderWrongReview();
        resultPanel.hidden = false;
    }

    function nextQuestion() {
        if (!answered) {
            return;
        }
        if (currentIndex >= questions.length - 1) {
            showResults();
            return;
        }
        currentIndex += 1;
        renderQuestion();
    }

    function beginRound(items, nextMode) {
        stopTimer();
        mode = nextMode;
        questions = buildQuestions(items);
        currentIndex = 0;
        correctAnswers = 0;
        xp = 0;
        coins = 0;
        answered = false;
        wrongItems = [];
        fastestAnswerSeconds = null;
        resultPanel.hidden = true;
        wrongReview.hidden = true;
        practiceWrongButton.hidden = true;
        wrongReviewList.innerHTML = '';
        startTimer();
        renderQuestion();
    }

    function startGame() {
        reviewItems = [];
        beginRound(sourceItems, 'full');
    }

    function practiceWrongAnswers() {
        reviewItems = [...wrongItems];
        if (reviewItems.length === 0) {
            return;
        }
        beginRound(reviewItems, 'review');
    }

    audioPrompt.addEventListener('click', () => speak(currentAudioItem));
    imagePrompt.addEventListener('click', () => speak(currentAudioItem));
    textPrompt.addEventListener('click', () => speak(currentAudioItem));
    nextQuestionButton.addEventListener('click', nextQuestion);
    playAgainButton.addEventListener('click', startGame);
    practiceWrongButton.addEventListener('click', practiceWrongAnswers);

    startGame();
})();
