(() => {
    const items = Array.isArray(window.SPEAKING_ITEMS)
        ? window.SPEAKING_ITEMS
        : [];

    if (items.length === 0) {
        return;
    }

    const setId = Number(window.SPEAKING_SET_ID || 0);
    const saveUrl = String(
        window.SPEAKING_SAVE_URL || '../api/save_recording.php'
    );
    const assessUrl = String(
        window.SPEAKING_ASSESS_URL || '../api/pronunciation_assessment.php'
    );

    const wordNumber = document.getElementById('wordNumber');
    const recordedCount = document.getElementById('recordedCount');
    const recordingTime = document.getElementById('recordingTime');
    const speakingProgress = document.getElementById('speakingProgress');

    const speakingImageBox = document.getElementById('speakingImageBox');
    const speakingImage = document.getElementById('speakingImage');
    const speakingWord = document.getElementById('speakingWord');
    const speakingIpa = document.getElementById('speakingIpa');

    const listenButton = document.getElementById('listenButton');
    const recordButton = document.getElementById('recordButton');
    const recordIcon = document.getElementById('recordIcon');
    const recordButtonText = document.getElementById('recordButtonText');
    const recordState = document.getElementById('recordState');
    const recordWave = document.getElementById('recordWave');

    const aiFeedbackPanel = document.getElementById('aiFeedbackPanel');
    const aiScore = document.getElementById('aiScore');
    const aiPraise = document.getElementById('aiPraise');
    const aiFeedbackText = document.getElementById('aiFeedbackText');
    const aiFeedbackTip = document.getElementById('aiFeedbackTip');
    const aiScoreProgressBar = document.getElementById('aiScoreProgressBar');

    const playbackPanel = document.getElementById('playbackPanel');
    const recordingPlayer = document.getElementById('recordingPlayer');
    const recordAgainButton = document.getElementById('recordAgainButton');
    const saveRecordingStatus = document.getElementById('saveRecordingStatus');

    const previousWordButton = document.getElementById('previousWordButton');
    const nextWordButton = document.getElementById('nextWordButton');

    const finishPanel = document.getElementById('finishPanel');
    const finalRecordedCount = document.getElementById('finalRecordedCount');
    const finishIcon = document.getElementById('finishIcon');
    const finishTitle = document.getElementById('finishTitle');
    const finishMessage = document.getElementById('finishMessage');
    const finishScoreBox = document.getElementById('finishScoreBox');
    const finishAverageScore = document.getElementById('finishAverageScore');
    const finishRating = document.getElementById('finishRating');
    const practiceAgainButton = document.getElementById('practiceAgainButton');

    let currentIndex = 0;
    let activeAudio = null;
    let currentBlob = null;
    let currentBlobUrl = '';

    let audioContext = null;
    let mediaStream = null;
    let sourceNode = null;
    let processorNode = null;
    let muteNode = null;
    let pcmChunks = [];
    let sourceSampleRate = 48000;

    let timerId = null;
    let recordingStartedAt = 0;
    let isRecording = false;
    let pointerIsDown = false;

    const recordedWordIds = new Set();
    const pronunciationScores = new Map();

    function currentItem() {
        return items[currentIndex];
    }

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

    function updateTimer() {
        if (!isRecording) return;

        const elapsed = Math.floor(
            (Date.now() - recordingStartedAt) / 1000
        );
        recordingTime.textContent = formatTime(elapsed);
    }

    function startTimer() {
        stopTimer();
        recordingStartedAt = Date.now();
        recordingTime.textContent = '00:00';
        timerId = window.setInterval(updateTimer, 250);
    }

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

    function clearFeedback() {
        aiFeedbackPanel.hidden = true;
        aiScore.textContent = '';
        aiPraise.textContent = '';
        aiFeedbackText.textContent = '';
        aiFeedbackTip.textContent = '';
        aiFeedbackPanel.classList.remove(
            'is-high',
            'is-medium',
            'is-low'
        );

        if (aiScoreProgressBar) {
            aiScoreProgressBar.style.width = '0%';
        }
    }

    function clearPlayback() {
        if (currentBlobUrl) {
            URL.revokeObjectURL(currentBlobUrl);
            currentBlobUrl = '';
        }

        currentBlob = null;
        recordingPlayer.removeAttribute('src');
        recordingPlayer.load();
        playbackPanel.hidden = true;
        saveRecordingStatus.textContent = '';
        saveRecordingStatus.classList.remove('is-error');
        clearFeedback();
    }

    function resetRecordingUi() {
        isRecording = false;
        pointerIsDown = false;
        recordButton.disabled = false;
        recordButton.classList.remove('is-recording');
        recordWave.classList.remove('is-active');
        recordIcon.textContent = '🎤';
        recordButtonText.textContent = 'Hold to Speak';
        recordState.textContent = '';
        stopTimer();
        recordingTime.textContent = '00:00';
    }

    function stopActiveWordAudio() {
        if (activeAudio) {
            activeAudio.pause();
            activeAudio.currentTime = 0;
            activeAudio = null;
        }

        if ('speechSynthesis' in window) {
            window.speechSynthesis.cancel();
        }
    }

    function browserSpeak(word) {
        if (!('speechSynthesis' in window)) return;

        const utterance = new SpeechSynthesisUtterance(word);
        utterance.lang = 'en-US';
        utterance.rate = 0.78;
        window.speechSynthesis.speak(utterance);
    }

    function speakCurrentWord() {
        const item = currentItem();
        if (!item) return;

        stopActiveWordAudio();

        if (item.audio) {
            activeAudio = new Audio(item.audio);
            activeAudio.play().catch(() => browserSpeak(item.word));
            return;
        }

        browserSpeak(item.word);
    }

    function renderCurrentWord() {
        const item = currentItem();
        if (!item) return;

        stopActiveWordAudio();
        stopRecording(true);
        clearPlayback();
        resetRecordingUi();

        wordNumber.textContent = `${currentIndex + 1} / ${items.length}`;
        recordedCount.textContent = recordedWordIds.size;
        speakingProgress.style.width =
            `${((currentIndex + 1) / items.length) * 100}%`;

        speakingWord.textContent = item.word;
        speakingIpa.textContent = item.ipa || '';

        speakingImageBox.classList.remove('is-portrait', 'is-landscape');

        if (item.image) {
            speakingImage.onload = () => {
                speakingImageBox.classList.remove(
                    'is-portrait',
                    'is-landscape'
                );

                if (speakingImage.naturalHeight > speakingImage.naturalWidth) {
                    speakingImageBox.classList.add('is-portrait');
                } else {
                    speakingImageBox.classList.add('is-landscape');
                }
            };

            speakingImage.onerror = () => {
                speakingImage.hidden = true;
                speakingImageBox.classList.add('is-empty');
            };

            speakingImageBox.classList.remove('is-empty');
            speakingImage.src = item.image;
            speakingImage.alt = '';
            speakingImage.hidden = false;
        } else {
            speakingImage.removeAttribute('src');
            speakingImage.hidden = true;
            speakingImageBox.classList.add('is-empty');
        }

        previousWordButton.disabled = currentIndex === 0;
        nextWordButton.textContent =
            currentIndex === items.length - 1 ? 'Finish' : 'Next →';
    }

    async function requestMicrophone() {
        if (!navigator.mediaDevices?.getUserMedia) {
            throw new Error('Trình duyệt không hỗ trợ ghi âm.');
        }

        return navigator.mediaDevices.getUserMedia({
            audio: {
                channelCount: 1,
                echoCancellation: true,
                noiseSuppression: true,
                autoGainControl: true
            }
        });
    }

    function mergeFloat32(chunks) {
        const totalLength = chunks.reduce(
            (total, chunk) => total + chunk.length,
            0
        );
        const merged = new Float32Array(totalLength);
        let offset = 0;

        chunks.forEach(chunk => {
            merged.set(chunk, offset);
            offset += chunk.length;
        });

        return merged;
    }

    function downsampleBuffer(buffer, inputRate, outputRate) {
        if (outputRate === inputRate) return buffer;
        if (outputRate > inputRate) {
            throw new Error('Tần số âm thanh không hợp lệ.');
        }

        const ratio = inputRate / outputRate;
        const newLength = Math.round(buffer.length / ratio);
        const result = new Float32Array(newLength);

        let resultOffset = 0;
        let bufferOffset = 0;

        while (resultOffset < result.length) {
            const nextBufferOffset = Math.round(
                (resultOffset + 1) * ratio
            );

            let sum = 0;
            let count = 0;

            for (
                let index = bufferOffset;
                index < nextBufferOffset && index < buffer.length;
                index += 1
            ) {
                sum += buffer[index];
                count += 1;
            }

            result[resultOffset] = count > 0 ? sum / count : 0;
            resultOffset += 1;
            bufferOffset = nextBufferOffset;
        }

        return result;
    }

    function writeAscii(view, offset, text) {
        for (let index = 0; index < text.length; index += 1) {
            view.setUint8(offset + index, text.charCodeAt(index));
        }
    }

    function encodeWav(samples, sampleRate = 16000) {
        const buffer = new ArrayBuffer(44 + samples.length * 2);
        const view = new DataView(buffer);

        writeAscii(view, 0, 'RIFF');
        view.setUint32(4, 36 + samples.length * 2, true);
        writeAscii(view, 8, 'WAVE');
        writeAscii(view, 12, 'fmt ');
        view.setUint32(16, 16, true);
        view.setUint16(20, 1, true);
        view.setUint16(22, 1, true);
        view.setUint32(24, sampleRate, true);
        view.setUint32(28, sampleRate * 2, true);
        view.setUint16(32, 2, true);
        view.setUint16(34, 16, true);
        writeAscii(view, 36, 'data');
        view.setUint32(40, samples.length * 2, true);

        let offset = 44;

        for (let index = 0; index < samples.length; index += 1) {
            const sample = Math.max(-1, Math.min(1, samples[index]));
            view.setInt16(
                offset,
                sample < 0 ? sample * 0x8000 : sample * 0x7fff,
                true
            );
            offset += 2;
        }

        return new Blob([view], { type: 'audio/wav' });
    }

    async function startRecording() {
        if (isRecording) return;

        clearPlayback();
        saveRecordingStatus.textContent = '';

        try {
            mediaStream = await requestMicrophone();

            const AudioContextClass =
                window.AudioContext || window.webkitAudioContext;

            if (!AudioContextClass) {
                throw new Error('Trình duyệt không hỗ trợ xử lý âm thanh.');
            }

            audioContext = new AudioContextClass();
            sourceSampleRate = audioContext.sampleRate;
            pcmChunks = [];

            sourceNode = audioContext.createMediaStreamSource(mediaStream);
            processorNode = audioContext.createScriptProcessor(4096, 1, 1);
            muteNode = audioContext.createGain();
            muteNode.gain.value = 0;

            processorNode.onaudioprocess = event => {
                if (!isRecording) return;

                const input = event.inputBuffer.getChannelData(0);
                pcmChunks.push(new Float32Array(input));
            };

            sourceNode.connect(processorNode);
            processorNode.connect(muteNode);
            muteNode.connect(audioContext.destination);

            isRecording = true;
            recordButton.classList.add('is-recording');
            recordWave.classList.add('is-active');
            recordIcon.textContent = '🔴';
            recordButtonText.textContent = 'Thả chuột để kết thúc';
            recordState.textContent = 'Bấm giữ chuột khi ghi âm';
            startTimer();
        } catch (error) {
            releaseAudioResources();
            resetRecordingUi();
            recordState.textContent =
                error instanceof Error
                    ? error.message
                    : 'Không thể sử dụng micro.';
        }
    }

    function releaseAudioResources() {
        if (processorNode) {
            processorNode.onaudioprocess = null;
            try { processorNode.disconnect(); } catch (_) {}
            processorNode = null;
        }

        if (sourceNode) {
            try { sourceNode.disconnect(); } catch (_) {}
            sourceNode = null;
        }

        if (muteNode) {
            try { muteNode.disconnect(); } catch (_) {}
            muteNode = null;
        }

        if (mediaStream) {
            mediaStream.getTracks().forEach(track => track.stop());
            mediaStream = null;
        }

        if (audioContext) {
            audioContext.close().catch(() => {});
            audioContext = null;
        }
    }

    async function stopRecording(silent = false) {
        if (!isRecording) return;

        isRecording = false;
        stopTimer();

        recordButton.classList.remove('is-recording');
        recordWave.classList.remove('is-active');
        recordIcon.textContent = '🎤';
        recordButtonText.textContent = 'Hold to Speak';

        const chunks = pcmChunks;
        const inputRate = sourceSampleRate;
        pcmChunks = [];

        releaseAudioResources();

        if (silent) {
            resetRecordingUi();
            return;
        }

        if (chunks.length === 0) {
            resetRecordingUi();
            recordState.textContent = 'Chưa ghi nhận được âm thanh.';
            return;
        }

        recordButton.disabled = true;
        recordState.textContent = 'Đang chấm phát âm...';

        const merged = mergeFloat32(chunks);
        const downsampled = downsampleBuffer(merged, inputRate, 16000);
        currentBlob = encodeWav(downsampled, 16000);

        if (currentBlobUrl) URL.revokeObjectURL(currentBlobUrl);
        currentBlobUrl = URL.createObjectURL(currentBlob);

        recordingPlayer.src = currentBlobUrl;
        playbackPanel.hidden = false;

        const item = currentItem();
        recordedWordIds.add(Number(item.id));
        recordedCount.textContent = recordedWordIds.size;

        await Promise.all([
            saveRecording(item, currentBlob),
            assessPronunciation(item, currentBlob)
        ]);

        recordButton.disabled = false;
    }

    async function saveRecording(item, recordingBlob) {
        const formData = new FormData();
        formData.append('set_id', String(setId));
        formData.append('word_id', String(item.id));
        formData.append('word', item.word);
        formData.append(
            'recording',
            recordingBlob,
            `speaking-${setId}-${item.id}.wav`
        );

        try {
            const response = await fetch(saveUrl, {
                method: 'POST',
                body: formData
            });
            const result = await response.json();

            if (!response.ok || !result.success) {
                throw new Error(
                    result.message || 'Không thể lưu bản ghi âm.'
                );
            }
        } catch (error) {
            saveRecordingStatus.textContent =
                'Không thể tự động lưu bản ghi âm. Hãy thử lại.';
            saveRecordingStatus.classList.add('is-error');
            console.warn('Unable to auto-save recording.', error);
        }
    }

    function renderAssessment(result) {
        const score = Math.max(
            0,
            Math.min(100, Math.round(Number(result.score || 0)))
        );

        aiFeedbackPanel.hidden = false;
        aiFeedbackPanel.classList.remove(
            'is-high',
            'is-medium',
            'is-low'
        );

        if (score >= 85) {
            aiFeedbackPanel.classList.add('is-high');
        } else if (score >= 65) {
            aiFeedbackPanel.classList.add('is-medium');
        } else {
            aiFeedbackPanel.classList.add('is-low');
        }

        aiScore.textContent = `${score} / 100`;

        const assessedItem = currentItem();
        if (assessedItem) {
            pronunciationScores.set(Number(assessedItem.id), score);
        }

        const praise = String(result.praise || '');
        aiPraise.textContent = praise
            ? `😊 ${praise}`
            : '😊 Đã phân tích phát âm';

        const feedback = String(result.feedback || '');
        aiFeedbackText.textContent = feedback
            ? `🔶 ${feedback}`
            : '🔶 Nghe lại và đọc cho rõ hơn nhé.';

        const tip = String(result.tip || '');
        aiFeedbackTip.textContent = tip
            ? `🔊 ${tip}`
            : '🔊 Nghe mẫu rồi ghi âm lại một lần nữa.';

        if (aiScoreProgressBar) {
            window.requestAnimationFrame(() => {
                aiScoreProgressBar.style.width = `${score}%`;
            });
        }

        recordState.textContent = 'Đã phân tích phát âm';
    }

    async function assessPronunciation(item, recordingBlob) {
        clearFeedback();

        const formData = new FormData();
        formData.append('set_id', String(setId));
        formData.append('word_id', String(item.id));
        formData.append('recording', recordingBlob, 'pronunciation.wav');

        try {
            const response = await fetch(assessUrl, {
                method: 'POST',
                body: formData
            });
            const result = await response.json();

            if (!response.ok || !result.success) {
                throw new Error(
                    result.message || 'Không thể chấm phát âm.'
                );
            }

            renderAssessment(result.assessment);
        } catch (error) {
            recordState.textContent = 'Đã ghi âm';
            saveRecordingStatus.textContent =
                error instanceof Error
                    ? error.message
                    : 'Không thể chấm phát âm lúc này.';
            saveRecordingStatus.classList.add('is-error');
            console.warn('Pronunciation assessment failed.', error);
        }
    }

    function moveWord(direction) {
        const nextIndex = currentIndex + direction;
        if (nextIndex < 0 || nextIndex >= items.length) return;

        currentIndex = nextIndex;
        renderCurrentWord();
    }

    function showFinish() {
        stopActiveWordAudio();
        stopRecording(true);
        releaseAudioResources();

        const totalWords = items.length;
        const practicedWords = recordedWordIds.size;
        const scores = Array.from(pronunciationScores.values())
            .filter(score => Number.isFinite(score));

        finalRecordedCount.textContent =
            `${practicedWords} / ${totalWords}`;

        if (scores.length === 0) {
            finishIcon.textContent = '🎉';
            finishTitle.textContent = 'Hoàn thành bài luyện nói!';
            finishMessage.textContent =
                'Nghe mẫu và luyện lại để phát âm hay hơn nhé.';
            finishScoreBox.hidden = true;
            finishRating.hidden = true;
        } else {
            const average = Math.round(
                scores.reduce((sum, score) => sum + score, 0) / scores.length
            );

            finishScoreBox.hidden = false;
            finishRating.hidden = false;
            finishAverageScore.textContent = `${average} / 100`;

            if (average >= 95) {
                finishIcon.textContent = '🏆';
                finishTitle.textContent = 'Xuất sắc!';
                finishMessage.textContent =
                    'Em phát âm rất chính xác. Tiếp tục giữ cách đọc này nhé!';
                finishRating.textContent = 'Mức đạt: Xuất sắc';
            } else if (average >= 85) {
                finishIcon.textContent = '🌟';
                finishTitle.textContent = 'Rất tốt!';
                finishMessage.textContent =
                    'Em phát âm khá rõ. Luyện thêm để đạt điểm cao hơn nhé.';
                finishRating.textContent = 'Mức đạt: Rất tốt';
            } else if (average >= 70) {
                finishIcon.textContent = '👍';
                finishTitle.textContent = 'Hoàn thành tốt!';
                finishMessage.textContent =
                    'Nghe mẫu và luyện lại để phát âm rõ hơn nhé.';
                finishRating.textContent = 'Mức đạt: Tốt';
            } else if (average >= 50) {
                finishIcon.textContent = '💪';
                finishTitle.textContent = 'Cố gắng thêm nhé!';
                finishMessage.textContent =
                    'Chú ý các âm được gợi ý rồi thử đọc lại nhé.';
                finishRating.textContent = 'Mức đạt: Cần luyện thêm';
            } else {
                finishIcon.textContent = '🎧';
                finishTitle.textContent = 'Cùng luyện lại nhé!';
                finishMessage.textContent =
                    'Nghe mẫu, đọc chậm và rõ từng âm nhé.';
                finishRating.textContent = 'Mức đạt: Hãy luyện lại';
            }
        }

        finishPanel.hidden = false;
    }

    function nextOrFinish() {
        if (currentIndex >= items.length - 1) {
            showFinish();
            return;
        }
        moveWord(1);
    }

    function restartPractice() {
        recordedWordIds.clear();
        pronunciationScores.clear();
        currentIndex = 0;
        finishPanel.hidden = true;
        recordedCount.textContent = '0';
        renderCurrentWord();
    }

    listenButton.addEventListener('click', speakCurrentWord);
    speakingImageBox.addEventListener('click', speakCurrentWord);
    speakingWord.addEventListener('click', speakCurrentWord);

    recordButton.addEventListener('pointerdown', event => {
        event.preventDefault();
        pointerIsDown = true;
        startRecording();
    });

    recordButton.addEventListener('pointerup', event => {
        event.preventDefault();
        if (!pointerIsDown) return;

        pointerIsDown = false;
        stopRecording();
    });

    recordButton.addEventListener('pointercancel', () => {
        pointerIsDown = false;
        stopRecording();
    });

    recordButton.addEventListener('pointerleave', event => {
        if (pointerIsDown && event.pointerType === 'mouse') {
            pointerIsDown = false;
            stopRecording();
        }
    });

    recordAgainButton.addEventListener('click', () => {
        clearPlayback();
        resetRecordingUi();
        recordState.textContent = 'Bấm giữ nút để ghi âm lại.';
    });

    previousWordButton.addEventListener('click', () => moveWord(-1));
    nextWordButton.addEventListener('click', nextOrFinish);
    practiceAgainButton.addEventListener('click', restartPractice);

    window.addEventListener('beforeunload', () => {
        stopActiveWordAudio();
        releaseAudioResources();

        if (currentBlobUrl) {
            URL.revokeObjectURL(currentBlobUrl);
        }
    });

    renderCurrentWord();
})();
