<?php
declare(strict_types=1);

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

$isPreview = (int)($_GET['preview'] ?? 0) === 1;

if ($isPreview) {
    $teacherLoggedIn = !empty($_SESSION['user_id'])
        && in_array((string)($_SESSION['user_role'] ?? ''), ['teacher', 'admin'], true);

    if (!$teacherLoggedIn) {
        $_SESSION['teacher_return_url'] = (string)($_SERVER['REQUEST_URI'] ?? '');
        header('Location: ../teacher/login.php');
        exit;
    }
} elseif (empty($_SESSION['student_id'])) {
    $_SESSION['student_return_url'] = (string)($_SERVER['REQUEST_URI'] ?? '');
    header('Location: student-login.php');
    exit;
}

require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/student-runtime/engines/pronunciation_feedback_engine.php';


$pdo = getDbConnection();

$bookSlug = trim((string)($_GET['book'] ?? ''));
$levelSlug = trim((string)($_GET['level'] ?? ''));
$levelId = max(0, (int)($_GET['level_id'] ?? 0));
$unitId = max(0, (int)($_GET['unit_id'] ?? ($_GET['id'] ?? 0)));
$lessonId = max(0, (int)($_GET['lesson_id'] ?? 0));
$activityId = max(0, (int)($_GET['activity_id'] ?? ($_GET['activity'] ?? 0)));
if ($bookSlug === '' || $levelId <= 0 || $unitId <= 0 || $lessonId <= 0 || $activityId <= 0) {
    header('Location: student-learning.php');
    exit;
}

function slrColumns(PDO $pdo, string $table): array
{
    $stmt = $pdo->prepare(
        "SELECT COUNT(*) FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?"
    );
    $stmt->execute([$table]);

    if ((int)$stmt->fetchColumn() === 0) {
        return [];
    }

    $rows = $pdo->query("SHOW COLUMNS FROM `{$table}`")->fetchAll(PDO::FETCH_ASSOC);
    return array_column($rows, null, 'Field');
}

function slrAssetUrl(string $path): string
{
    $path = trim($path);

    if ($path === '') {
        return '';
    }

    if (
        str_starts_with($path, 'http://')
        || str_starts_with($path, 'https://')
        || str_starts_with($path, 'data:')
        || str_starts_with($path, '/')
    ) {
        return $path;
    }

    return '/EnglishSpeak/' . ltrim($path, './');
}

$activityColumns = slrColumns($pdo, 'activities');
$relationColumns = slrColumns($pdo, 'flashcard_set_words');
$wordColumns = slrColumns($pdo, 'words');

if (
    $activityColumns === []
    || $relationColumns === []
    || $wordColumns === []
    || !isset($activityColumns['flashcard_set_id'])
) {
    http_response_code(500);
    exit('Thiếu bảng dữ liệu Listen & Repeat.');
}

$activityTitleColumn = isset($activityColumns['title'])
    ? 'title'
    : (isset($activityColumns['name']) ? 'name' : null);

$setFk = isset($relationColumns['set_id'])
    ? 'set_id'
    : (isset($relationColumns['flashcard_set_id']) ? 'flashcard_set_id' : null);

$wordFk = isset($relationColumns['word_id']) ? 'word_id' : null;

$orderColumn = isset($relationColumns['order_no'])
    ? 'order_no'
    : (isset($relationColumns['sort_order']) ? 'sort_order' : 'id');

$wordTextColumn = isset($wordColumns['word'])
    ? 'word'
    : (isset($wordColumns['term']) ? 'term' : null);

$ipaColumn = isset($wordColumns['phonetic'])
    ? 'phonetic'
    : (isset($wordColumns['ipa']) ? 'ipa' : null);

$imageColumn = isset($wordColumns['image_path'])
    ? 'image_path'
    : (isset($wordColumns['image']) ? 'image' : null);

$audioColumn = isset($wordColumns['audio_us_path'])
    ? 'audio_us_path'
    : (isset($wordColumns['audio_path'])
        ? 'audio_path'
        : (isset($wordColumns['audio']) ? 'audio' : null));

if (
    $activityTitleColumn === null
    || $setFk === null
    || $wordFk === null
    || $wordTextColumn === null
) {
    http_response_code(500);
    exit('Cấu trúc dữ liệu Listen & Repeat chưa đầy đủ.');
}

$activityStatusWhere = isset($activityColumns['status'])
    ? "AND a.status = 'active'"
    : '';

$pronunciationFocusSelect = isset($activityColumns['pronunciation_focus'])
    ? "a.pronunciation_focus AS pronunciation_focus,"
    : "'none' AS pronunciation_focus,";

$contextStmt = $pdo->prepare(
    "SELECT
        a.id AS activity_id,
        a.flashcard_set_id,
        a.xp_reward AS xp_reward,
        {$pronunciationFocusSelect}
        a.`{$activityTitleColumn}` AS activity_title,
        le.id AS lesson_id,
        le.lesson_number,
        le.title AS lesson_title,
        u.id AS unit_id,
        u.unit_number,
        u.title AS unit_title,
        l.id AS level_id,
        l.name AS level_name,
        l.slug AS level_slug,
        b.id AS book_id,
        b.name AS book_name,
        b.slug AS book_slug
     FROM activities a
     INNER JOIN lessons le ON le.id = a.lesson_id
     INNER JOIN units u ON u.id = le.unit_id
     INNER JOIN levels l ON l.id = u.level_id
     INNER JOIN books b ON b.id = l.book_id
     WHERE a.id = ?
       AND le.id = ?
       AND u.id = ?
       AND l.id = ?
       AND b.slug = ?
       {$activityStatusWhere}
     LIMIT 1"
);
$contextStmt->execute([$activityId, $lessonId, $unitId, $levelId, $bookSlug]);
$activity = $contextStmt->fetch(PDO::FETCH_ASSOC);

if (!$activity) {
    http_response_code(404);
    exit('Không tìm thấy Listen & Repeat trong cấp độ đã chọn.');
}

/*
 * Listen & Repeat chỉ lưu tiến độ qua API dùng chung:
 * /api/save-activity-progress.php
 * Không còn endpoint POST riêng trong file này.
 */

$activityPronunciationFocus = trim(
    (string)($activity['pronunciation_focus'] ?? 'none')
);

if ($activityPronunciationFocus === '') {
    $activityPronunciationFocus = 'none';
}

$pronunciationEngine = new PronunciationFeedbackEngine(
    __DIR__ . '/student-runtime/engines'
);

$pronunciationFocusKey = $activityPronunciationFocus;

$pronunciationConfig = $pronunciationEngine
    ->buildStudentConfig($pronunciationFocusKey);

$xpReward = max(0, (int)($activity['xp_reward'] ?? 0));
if ($xpReward <= 0) {
    $xpReward = 15;
}

$passingScore = 80;

if (slrColumns($pdo, 'activity_settings') !== []) {
    $passingScoreStmt = $pdo->prepare(
        'SELECT settings_json FROM activity_settings WHERE activity_id = ? LIMIT 1'
    );
    $passingScoreStmt->execute([$activityId]);
    $passingScoreJson = $passingScoreStmt->fetchColumn();

    if (is_string($passingScoreJson) && $passingScoreJson !== '') {
        $passingScoreSettings = json_decode($passingScoreJson, true);

        if (is_array($passingScoreSettings)) {
            $passingScore = max(
                0,
                min(100, (int)($passingScoreSettings['passing_score'] ?? 80))
            );

        }
    }
}

$runtimeSettings = $_SESSION['activity_settings_data'] ?? [];

$cardsPerSession = (int)($runtimeSettings['cards_per_session']
    ?? $runtimeSettings['word_count']
    ?? 0);

$showImage = !empty($runtimeSettings['show_image']);
$showIPA = !empty($runtimeSettings['show_ipa']);
$showListenButton = !empty($runtimeSettings['show_listen_button'])
    || !empty($runtimeSettings['show_listen']);
$autoPlay = !empty($runtimeSettings['auto_play']);
$autoNext = !empty($runtimeSettings['auto_next']);
$audioSource = (string)($runtimeSettings['audio_source'] ?? 'auto');
$unlimitedPractice = !empty($runtimeSettings['unlimited_practice']);
$maxAttempts = max(1,(int)($runtimeSettings['attempts'] ?? 3));

$ipaSelect = $ipaColumn !== null ? "w.`{$ipaColumn}` AS ipa" : "'' AS ipa";
$imageSelect = $imageColumn !== null ? "w.`{$imageColumn}` AS image_path" : "'' AS image_path";
$audioSelect = $audioColumn !== null ? "w.`{$audioColumn}` AS audio_path" : "'' AS audio_path";
$wordStatusWhere = isset($wordColumns['status']) ? "AND w.status = 'active'" : '';

$wordStmt = $pdo->prepare(
    "SELECT
        w.id,
        w.`{$wordTextColumn}` AS word_text,
        {$ipaSelect},
        {$imageSelect},
        {$audioSelect},
        fsw.`{$orderColumn}` AS display_order
     FROM flashcard_set_words fsw
     INNER JOIN words w ON w.id = fsw.`{$wordFk}`
     WHERE fsw.`{$setFk}` = ?
       {$wordStatusWhere}
     ORDER BY fsw.`{$orderColumn}` ASC, fsw.id ASC"
);
$wordStmt->execute([(int)$activity['flashcard_set_id']]);
$rows = $wordStmt->fetchAll(PDO::FETCH_ASSOC);

$cards = array_map(
    static function (array $row): array {
        return [
            'word' => (string)($row['word_text'] ?? ''),
            'ipa' => (string)($row['ipa'] ?? ''),
            'image' => slrAssetUrl((string)($row['image_path'] ?? '')),
            'audio' => slrAssetUrl((string)($row['audio_path'] ?? '')),
        ];
    },
    $rows
);

if (
    $cardsPerSession > 0
    && $cardsPerSession < 100
    && count($cards) > $cardsPerSession
) {
    $cards = array_slice($cards, 0, $cardsPerSession);
}

$rawUnitNumber = trim((string)$activity['unit_number']);
$rawLessonNumber = trim((string)$activity['lesson_number']);

$displayUnitNumber = preg_match('/(\d+)/', $rawUnitNumber, $unitMatch)
    ? (int)$unitMatch[1]
    : $rawUnitNumber;

$displayLessonNumber = preg_match('/(\d+)/', $rawLessonNumber, $lessonMatch)
    ? (int)$lessonMatch[1]
    : $rawLessonNumber;

$backUrl = $isPreview
    ? '../teacher/activity-preview/index.php?activity_id=' . (int)$activityId
    : 'student-activities.php?' . http_build_query([
        'book' => (string)$activity['book_slug'],
        'level' => (string)$activity['level_slug'],
        'level_id' => (int)$activity['level_id'],
        'unit_id' => (int)$activity['unit_id'],
        'lesson_id' => (int)$activity['lesson_id'],
        'lesson' => $displayLessonNumber,
    ]);

$_SESSION['last_learning_url'] = (string)($_SERVER['REQUEST_URI'] ?? '');
?>
<!doctype html>
<html lang="vi">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>Listen &amp; Repeat | EnglishSpeak</title>
<style>
:root{
  --blue:#198fe3;--blue-dark:#0868c7;--soft:#eaf5ff;--page:#f4f8fc;
  --text:#15314b;--muted:#718399;--line:#dfe8f0;--green:#27ae60;
  --orange:#f39c12;--red:#e55353;--width:520px;--shadow:0 7px 22px rgba(31,71,108,.10)
}
*{box-sizing:border-box}
html,body{margin:0;min-height:100%;background:var(--page);color:var(--text);font-family:"Segoe UI",Tahoma,Arial,sans-serif}
body{display:flex;justify-content:center}
button,a{font:inherit;-webkit-tap-highlight-color:transparent}
.app{width:min(100%,var(--width));min-height:100dvh;padding:0 14px 24px;background:#fff;box-shadow:0 0 28px rgba(30,79,115,.08)}
.topbar{position:sticky;top:0;z-index:40;display:grid;grid-template-columns:46px minmax(0,1fr) 46px;align-items:center;gap:9px;min-height:76px;margin:0 -14px 14px;padding:12px 14px;background:linear-gradient(135deg,#3488ef,#17b8e4);color:#fff;box-shadow:0 5px 15px rgba(31,135,210,.22)}
.round{display:grid;width:44px;height:44px;place-items:center;border:0;border-radius:50%;background:rgba(255,255,255,.15);color:#fff;text-decoration:none;font-size:24px;font-weight:850;cursor:pointer}
.heading{text-align:center;min-width:0}.heading strong{display:block;font-size:22px;line-height:1.15}.heading small{display:block;margin-top:4px;color:rgba(255,255,255,.92);font-size:12px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lesson{display:grid;grid-template-columns:52px minmax(0,1fr);align-items:center;gap:11px;min-height:72px;margin-bottom:12px;padding:12px 14px;border-radius:20px;background:linear-gradient(135deg,#edf6ff,#e6f1ff)}
.lesson__icon{width:48px;height:48px;display:grid;place-items:center;border-radius:14px;background:#fff;font-size:29px}.lesson strong{font-size:18px}
.progress-row{display:flex;justify-content:space-between;gap:10px;color:#38536c;font-size:13px;font-weight:800}.track{height:9px;margin:8px 0 13px;overflow:hidden;border-radius:999px;background:#dceaf5}.fill{width:0;height:100%;border-radius:inherit;background:linear-gradient(90deg,#1c7ce4,#18a9e7);transition:width .2s}
.practice-card{min-height:calc(100dvh - 292px);padding:15px;border:1px solid #e2eaf1;border-radius:22px;background:#fff;box-shadow:var(--shadow);display:flex;flex-direction:column;align-items:center}
.image-button{width:100%;height:min(34dvh,260px);min-height:180px;display:grid;place-items:center;padding:18px;border:0;border-radius:18px;background:#fbfdff;cursor:pointer}.image-button img{max-width:250px;max-height:250px;width:88%;height:88%;object-fit:contain}.fallback{font-size:100px;color:var(--blue)}
.word{margin:13px 0 0;color:var(--blue-dark);font-size:clamp(38px,12vw,58px);line-height:1.05;text-align:center}.ipa{min-height:23px;margin-top:5px;color:#4f7ca3;font-size:17px;font-style:italic}
.actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;width:100%;margin-top:auto;padding-top:20px}.action{height:52px;padding:0 10px;border:1px solid #cbddeb;border-radius:15px;background:#fff;color:var(--text);font-weight:850;cursor:pointer}.action.primary{border-color:var(--blue);background:linear-gradient(135deg,#237fe1,#159ddd);color:#fff}.action.recording{background:#e55353;border-color:#e55353;color:#fff;animation:pulse 1.2s infinite}
@keyframes pulse{50%{box-shadow:0 0 0 8px rgba(229,83,83,.12)}}
.feedback{display:none;width:100%;margin-top:12px;padding:13px 14px;border-radius:15px;font-size:14px;line-height:1.45;text-align:left}.feedback.show{display:block}.feedback.good{border:1px solid #bce5c8;background:#eefbf2;color:#19783a}.feedback.try{border:1px solid #f0d7a5;background:#fff8e8;color:#8a5c08}.feedback strong{display:block;margin-bottom:5px;font-size:16px}.feedback div{line-height:1.55}
.next-row{display:grid;grid-template-columns:1fr 1fr;gap:10px;width:100%;margin-top:10px;padding-top:0}.next-row button{height:52px;padding:0 10px;border:1px solid #cbddeb;border-radius:15px;background:#fff;font-weight:850;cursor:pointer}.next-row .next{border-color:var(--blue);background:var(--blue);color:#fff}
.finish{display:none;min-height:calc(100dvh - 110px);padding:36px 18px;align-items:center;justify-content:center;flex-direction:column;text-align:center}.finish.show{display:flex}.finish__icon{font-size:66px}.finish h2{margin:14px 0 8px;font-size:35px;color:#123e62}.finish p{margin:0;color:var(--muted);font-size:16px}.summary{width:100%;margin-top:22px;padding:5px 17px;border:1px solid var(--line);border-radius:18px;box-shadow:var(--shadow)}.summary-row{display:flex;justify-content:space-between;align-items:center;min-height:58px;border-bottom:1px solid #e5ecf2}.summary-row:last-child{border:0}.summary-row strong{color:var(--blue-dark);font-size:19px}.finish-actions{display:grid;gap:10px;width:100%;margin-top:18px}.finish-actions button,.finish-actions a{min-height:54px;display:grid;place-items:center;border:1px solid var(--blue);border-radius:15px;background:#fff;color:var(--blue-dark);text-decoration:none;font-weight:850}.finish-actions .primary{background:var(--blue);color:#fff}
.empty{padding:50px 20px;text-align:center}
@media(max-width:390px){.app{padding-left:11px;padding-right:11px}.topbar{margin-left:-11px;margin-right:-11px;padding-left:11px;padding-right:11px}.practice-card{padding:12px}.image-button{height:min(34dvh,250px);min-height:175px}.actions{grid-template-columns:1fr 1fr}.action{font-size:12.5px;padding:0 8px}.next-row button{font-size:13px}}

/* EnglishSpeak XP popup */
.xp-popup {
    position: fixed;
    z-index: 10000;
    inset: 0;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 20px;
    background: rgba(18, 49, 74, .38);
    backdrop-filter: blur(5px);
}
.xp-popup.show { display: flex; }
.xp-popup__card {
    width: min(100%, 350px);
    padding: 24px 20px 20px;
    border-radius: 24px;
    background: #fff;
    box-shadow: 0 18px 50px rgba(18, 49, 74, .24);
    text-align: center;
    animation: xpPopupIn .25s ease-out;
}
.xp-popup__icon {
    width: 76px;
    height: 76px;
    display: grid;
    place-items: center;
    margin: 0 auto 10px;
    border-radius: 50%;
    background: linear-gradient(135deg, #fff6bf, #ffe06e);
    font-size: 42px;
}
.xp-popup__title {
    margin: 0;
    color: #15314b;
    font-size: 27px;
    line-height: 1.2;
}
.xp-popup__text {
    margin: 7px 0 0;
    color: #718399;
    font-size: 14px;
}
.xp-popup__score {
    margin: 17px 0;
    color: #f39c12;
    font-size: 36px;
    font-weight: 900;
    line-height: 1;
}
.xp-popup__button {
    width: 100%;
    min-height: 52px;
    border: 0;
    border-radius: 15px;
    background: linear-gradient(135deg, #237fe1, #159ddd);
    color: #fff;
    font-weight: 850;
    cursor: pointer;
}
@keyframes xpPopupIn {
    from { opacity: 0; transform: translateY(14px) scale(.96); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
.preview-banner{margin:10px 0;padding:9px 12px;border:1px solid #bfdbfe;border-radius:13px;background:#eff6ff;color:#1d4ed8;text-align:center;font-size:13px;font-weight:800}

</style>
</head>
<body>
<main class="app">
<?php if ($isPreview): ?>
<div class="preview-banner">Preview mode — Không lưu XP hoặc tiến trình học sinh.</div>
<?php endif; ?>
<header class="topbar">
  <a class="round" href="<?= htmlspecialchars($backUrl, ENT_QUOTES, 'UTF-8') ?>" aria-label="Quay lại">←</a>
  <div class="heading">
    <strong>Unit <?= $displayUnitNumber ?></strong>
    <small><?= htmlspecialchars((string)($activity['unit_title'] ?? ''), ENT_QUOTES, 'UTF-8') ?></small>
  </div>
  <span class="round" aria-hidden="true" style="visibility:hidden"></span>
</header>

<?php if ($cards === []): ?>
  <section class="empty"><h2>Chưa có từ vựng</h2><p>Hoạt động này chưa có dữ liệu.</p></section>
<?php else: ?>
  <section id="practiceScreen">
    <div class="lesson"><div class="lesson__icon">🎤</div><strong>Listen & Repeat</strong></div>
    <div class="progress-row"><span id="progressLabel">1 / <?= count($cards) ?></span><span id="scoreLabel"></span></div>
    <div class="track"><div class="fill" id="progressFill"></div></div>

    <section class="practice-card">
      <button class="image-button" id="imageButton" type="button" aria-label="Nghe từ">
        <img id="wordImage" alt="" hidden>
        <span class="fallback" id="fallback">🖼️</span>
      </button>
      <h1 class="word" id="wordText"></h1>
      <div class="ipa" id="ipaText"></div>

      <div class="actions">
        <button class="action" id="listenButton" type="button">🔊 Listen</button>
        <button class="action primary" id="speakButton" type="button">🎤 Speak</button>
      </div>

      <div class="feedback" id="feedback" aria-live="polite"></div>

      <div class="next-row" id="nextRow" hidden style="display:none">
        <button id="retryButton" type="button" hidden>↻ Try Again</button>
        <button class="next" id="nextButton" type="button" hidden>Next →</button>
      </div>
    </section>
  </section>

  <section class="finish" id="finishScreen">
    <div class="finish__icon">🎉</div>
    <h2>Finished!</h2>
    <p>Em đã luyện xong <?= count($cards) ?> từ.</p>
    <div class="summary">
      <div class="summary-row"><span>Đã luyện</span><strong><?= count($cards) ?> / <?= count($cards) ?></strong></div>
      <div class="summary-row"><span>Điểm trung bình</span><strong id="finalScore">0/100</strong></div>
    </div>
    <div class="finish-actions">
      <a class="primary" href="student-runtime/index.php?<?= htmlspecialchars(http_build_query([
          'runtime_action' => 'next',
          'current_activity_id' => $activityId,
          'lesson_id' => $lessonId,
          'preview' => $isPreview ? 1 : 0,
      ], '', '&', PHP_QUERY_RFC3986), ENT_QUOTES, 'UTF-8') ?>">Next →</a>

      <button class="primary" id="replayButton" type="button">↻ Again</button>
      <a href="<?= htmlspecialchars($backUrl, ENT_QUOTES, 'UTF-8') ?>">☰ Back</a>
    </div>
  </section>
<?php endif; ?>
</main>


<?php if (!$isPreview): ?>
<div class="xp-popup" id="xpPopup" role="dialog" aria-modal="true" aria-labelledby="xpPopupTitle">
    <div class="xp-popup__card">
        <div class="xp-popup__icon" aria-hidden="true">⭐</div>
        <h2 class="xp-popup__title" id="xpPopupTitle">Hoàn thành!</h2>
        <p class="xp-popup__text" id="xpPopupText">Em nhận được</p>
        <div class="xp-popup__score" id="xpPopupScore"></div>
        <button class="xp-popup__button" id="xpPopupButton" type="button">Xem kết quả</button>
    </div>
</div>
<?php endif; ?>

<?php if ($cards !== []): ?>
<script src="https://aka.ms/csspeech/jsbrowserpackageraw">/* LISTEN-REPEAT-SAVE-REBUILT-20260806-01 */
</script>
<script>
'use strict';

/* =========================================================
   EnglishSpeak — Listen & Repeat
   Azure Speech SDK Engine
   ĐOẠN 1: Dữ liệu, trạng thái, giao diện và Azure Token
   ========================================================= */

const isPreview = <?= $isPreview ? 'true' : 'false' ?>;
const activityId = <?= (int)$activityId ?>;
const pronunciationEngineConfig = <?= json_encode(
    $pronunciationConfig,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
) ?>;

const cards = <?= json_encode(
    $cards,
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
) ?>;

const passingScore = <?= (int)$passingScore ?>;

const $ = id => document.getElementById(id);

/* ---------- Các thành phần giao diện ---------- */

const practiceScreen = $('practiceScreen');
const finishScreen = $('finishScreen');

const imageButton = $('imageButton');
const wordImage = $('wordImage');
const fallback = $('fallback');

const wordText = $('wordText');
const ipaText = $('ipaText');

const progressLabel = $('progressLabel');
const progressFill = $('progressFill');
const scoreLabel = $('scoreLabel');

const listenButton = $('listenButton');
const speakButton = $('speakButton');
const retryButton = $('retryButton');
const nextButton = $('nextButton');
const nextRow = $('nextRow');
const replayButton = $('replayButton');

const feedback = $('feedback');
const finalScore = $('finalScore');

/* ---------- Trạng thái hoạt động ---------- */

let currentIndex = 0;
let scores = [];

let speechRecognizer = null;
let isPreparing = false;
let isListening = false;
let stopRequested = false;
let assessmentHandled = false;
let autoStopTimer = null;
const isTouchSpeechMode = window.matchMedia?.('(hover: none), (pointer: coarse)')?.matches === true;
let isSavingProgress = false;

let cachedSpeechToken = '';
let cachedSpeechRegion = '';
let speechTokenExpiresAt = 0;

/* ---------- Popup XP ---------- */

function showXpPopup(result, activityScore) {
  if (!result || result.preview === true) return;

  const popup = document.getElementById('xpPopup');
  const title = document.getElementById('xpPopupTitle');
  const text = document.getElementById('xpPopupText');
  const score = document.getElementById('xpPopupScore');

  if (!popup || !title || !text || !score) return;

  const xpAdded = Number(result.xp_added || 0);

  /*
   * Listen & Repeat đã bắt buộc từng từ đạt Passing Score trước khi
   * cho chuyển thẻ. Vì vậy điểm tổng kết trên trang là nguồn kiểm tra
   * đáng tin cậy khi API trả passed thiếu hoặc sai kiểu dữ liệu.
   */
  const safeActivityScore = Math.max(
    0,
    Math.min(100, Math.round(Number(activityScore) || 0))
  );

  const passed =
    result.passed === true
    || result.passed === 1
    || result.passed === '1'
    || safeActivityScore >= passingScore;

  if (xpAdded > 0) {
    title.textContent = result.xp_event_type === 'completion'
      ? 'Hoàn thành!'
      : 'Luyện tập tốt!';
    text.hidden = false;
    text.textContent = 'Em nhận được';
    score.textContent = `+${xpAdded} XP`;
  } else if (passed) {
    /*
     * Điểm đã đạt nhưng hôm nay không còn Practice XP:
     * chỉ khen tự nhiên và hiển thị điểm, không nhấn vào việc hết XP.
     */
    title.textContent = 'Great!';
    text.textContent = '';
    text.hidden = true;
    score.textContent = `${safeActivityScore}%`;
  } else {
    title.textContent = 'Có cố gắng!';
    text.hidden = false;
    text.textContent = 'Try again!';
    score.textContent = `${safeActivityScore}%`;
  }

  popup.classList.add('show');
}

function hideXpPopup() {
    const popup = $('xpPopup');

    if (popup) {
        popup.classList.remove('show');
    }
}

$('xpPopupButton')?.addEventListener('click', hideXpPopup);

/* ---------- Điểm số ---------- */

function getAverageScore() {
    const validScores = scores.filter(Number.isFinite);

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

    const total = validScores.reduce(
        (sum, score) => sum + score,
        0
    );

    return Math.round(total / validScores.length);
}

function updateAverageScore() {
    if (scoreLabel) {
        scoreLabel.textContent = '';
    }
}

async function saveActivityProgress(score) {
    if (isPreview) {
        return {
            ok: true,
            preview: true,
            saved: false,
            xp_added: 0
        };
    }

    if (isSavingProgress) {
        return {
            ok: false,
            saved: false,
            xp_added: 0,
            reason: 'saving'
        };
    }

    isSavingProgress = true;

    try {
        const safeScore = Math.max(
            0,
            Math.min(100, Math.round(Number(score) || 0))
        );

        const response = await fetch(
            '/EnglishSpeak/api/save-activity-progress.php',
            {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json;charset=UTF-8',
                    'Accept': 'application/json'
                },
                credentials: 'same-origin',
                cache: 'no-store',
                body: JSON.stringify({
                    activity_id: activityId,
                    score: safeScore,
                    completed: safeScore >= passingScore,
                    passing_score: passingScore,
                    preview: false
                })
            }
        );

        const raw = await response.text();
        let result = null;

        try {
            result = raw ? JSON.parse(raw) : null;
        } catch (error) {
            throw new Error('API lưu tiến độ không trả JSON hợp lệ.');
        }

        if (!response.ok || !result || result.ok !== true) {
            throw new Error(
                result?.message || 'Không thể lưu tiến trình Listen & Repeat.'
            );
        }

        return result;
    } catch (error) {
        console.error('Lỗi lưu Listen & Repeat:', error);

        return {
            ok: false,
            saved: false,
            xp_added: 0,
            message: error?.message || 'Không thể lưu tiến trình.'
        };
    } finally {
        isSavingProgress = false;
    }
}

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

    scores[currentIndex] = Math.max(
        scores[currentIndex] || 0,
        safeScore
    );

    updateAverageScore();
}

/* ---------- Feedback ---------- */

function setResultActions(hasPassed) {
    const passed = Boolean(hasPassed);

    if (!nextRow || !retryButton || !nextButton) {
        console.error('Missing action buttons', {
            nextRow,
            retryButton,
            nextButton
        });
        return;
    }

    nextRow.hidden = false;
    nextRow.style.display = 'grid';
    nextRow.style.gridTemplateColumns = passed
        ? '1fr 1fr'
        : '1fr';

    retryButton.hidden = false;
    retryButton.disabled = false;
    retryButton.style.display = 'block';

    nextButton.hidden = !passed;
    nextButton.disabled = !passed;
    nextButton.style.display = passed
        ? 'block'
        : 'none';
}

function resetResultActions() {
    if (!nextRow || !retryButton || !nextButton) {
        return;
    }

    nextRow.hidden = true;
    nextRow.style.display = 'none';
    nextRow.style.gridTemplateColumns = '1fr';

    retryButton.hidden = true;
    retryButton.disabled = false;
    retryButton.style.display = 'none';

    nextButton.hidden = true;
    nextButton.disabled = true;
    nextButton.style.display = 'none';
}

function clearFeedback() {
    if (!feedback) {
        return;
    }

    feedback.className = 'feedback';
    feedback.innerHTML = '';
}

function escapeHtml(value) {
    return String(value ?? '')
        .replaceAll('&', '&amp;')
        .replaceAll('<', '&lt;')
        .replaceAll('>', '&gt;')
        .replaceAll('"', '&quot;')
        .replaceAll("'", '&#039;');
}

function showFeedback(type, title, message, allowHtml = false) {
    if (!feedback) {
        return;
    }

    const safeType = type === 'good' ? 'good' : 'try';
    const safeTitle = escapeHtml(title);
    const safeMessage = allowHtml
        ? String(message ?? '')
        : escapeHtml(message);

    feedback.className = `feedback show ${safeType}`;
    feedback.innerHTML = `
        <strong>${safeTitle}</strong>
        <div>${safeMessage}</div>
    `;
}

/* ---------- Trạng thái nút Speak ---------- */

function setSpeakButtonReady() {
    if (!speakButton) {
        return;
    }

    speakButton.disabled = false;
    speakButton.classList.remove('recording');
    speakButton.textContent = '🎤 Bấm để nói';
}

function setSpeakButtonPreparing() {
    if (!speakButton) {
        return;
    }

    speakButton.disabled = false;
    speakButton.classList.remove('recording');
    speakButton.textContent = '⏳ Đang kết nối...';
}

function setSpeakButtonListening() {
    if (!speakButton) {
        return;
    }

    speakButton.disabled = false;
    speakButton.classList.add('recording');
    speakButton.textContent = '🔴 Đang nghe...';
}

function setSpeakButtonScoring() {
    if (!speakButton) {
        return;
    }

    speakButton.disabled = true;
    speakButton.classList.remove('recording');
    speakButton.textContent = '⏳ Đang chấm...';
}

/* ---------- Phát âm mẫu ---------- */

function browserTts(text) {
    if (!('speechSynthesis' in window)) {
        return;
    }

    window.speechSynthesis.cancel();

    const utterance = new SpeechSynthesisUtterance(text);

    utterance.lang = 'en-US';
    utterance.rate = 0.72;
    utterance.pitch = 1;

    window.speechSynthesis.speak(utterance);
}

function playWordAudio(text, audioUrl = '') {
    if (audioUrl) {
        const audio = new Audio(audioUrl);

        audio.play().catch(() => {
            browserTts(text);
        });

        return;
    }

    browserTts(text);
}

/* ---------- Hiển thị thẻ hiện tại ---------- */

function renderCurrentCard() {
    const card = cards[currentIndex];

    if (!card) {
        return;
    }

    if (wordText) {
        wordText.textContent = card.word || '';
    }

    if (ipaText) {
        ipaText.textContent = card.ipa || '';
    }

    if (card.image) {
        wordImage.src = card.image;
        wordImage.alt = card.word || '';
        wordImage.hidden = false;
        fallback.hidden = true;
    } else {
        wordImage.hidden = true;
        fallback.hidden = false;
    }

    if (progressLabel) {
        progressLabel.textContent =
            `${currentIndex + 1} / ${cards.length}`;
    }

    if (progressFill) {
        progressFill.style.width =
            `${((currentIndex + 1) / cards.length) * 100}%`;
    }

    updateAverageScore();
    clearFeedback();
    resetResultActions();
    setSpeakButtonReady();

    // Tự đọc mẫu 1 lần mỗi khi thẻ/từ mới xuất hiện.
    // Giữ nguyên nút Listen để học sinh có thể nghe lại.
    const renderedIndex = currentIndex;
    window.setTimeout(() => {
        if (currentIndex !== renderedIndex) {
            return;
        }

        const currentCard = cards[currentIndex];
        if (currentCard?.word) {
            playWordAudio(currentCard.word, currentCard.audio || '');
        }
    }, 250);
}

/* ---------- Lấy Azure Speech Token ---------- */

async function getSpeechToken() {
    const now = Date.now();

    if (
        cachedSpeechToken !== ''
        && cachedSpeechRegion !== ''
        && now < speechTokenExpiresAt
    ) {
        return {
            token: cachedSpeechToken,
            region: cachedSpeechRegion
        };
    }

    const response = await fetch(
        '/EnglishSpeak/api/get-speech-token.php',
        {
            method: 'GET',
            credentials: 'same-origin',
            cache: 'no-store',
            headers: {
                'Accept': 'application/json'
            }
        }
    );

    let result;

    try {
        result = await response.json();
    } catch (error) {
        throw new Error(
            'API Azure Token không trả về dữ liệu hợp lệ.'
        );
    }

    if (
        !response.ok
        || !result.ok
        || !result.token
        || !result.region
    ) {
        throw new Error(
            result?.message
            || 'Không lấy được Azure Speech Token.'
        );
    }

    cachedSpeechToken = String(result.token);
    cachedSpeechRegion = String(result.region);

    const expiresIn = Math.max(
        60,
        Number(result.expires_in || 600)
    );

    speechTokenExpiresAt =
        now + Math.max(30000, (expiresIn - 60) * 1000);

    return {
        token: cachedSpeechToken,
        region: cachedSpeechRegion
    };
}

/* ---------- Kiểm tra Azure SDK ---------- */

function ensureSpeechSdkReady() {
    if (typeof SpeechSDK === 'undefined') {
        throw new Error(
            'Azure Speech SDK chưa tải được. '
            + 'Hãy kiểm tra Internet rồi tải lại trang.'
        );
    }
}

/* ===== HẾT ĐOẠN 1 — KHÔNG ĐÓNG THẺ SCRIPT ===== */
/* =========================================================
   ĐOẠN 2 — Azure Speech SDK: tạo recognizer và đọc kết quả
   ========================================================= */

/* ---------- Hủy recognizer cũ ---------- */

function disposeSpeechRecognizer() {
    if (!speechRecognizer) {
        return;
    }

    try {
        speechRecognizer.recognizing = undefined;
        speechRecognizer.recognized = undefined;
        speechRecognizer.canceled = undefined;
        speechRecognizer.sessionStopped = undefined;
        speechRecognizer.close();
    } catch (error) {
        console.warn('Không thể đóng Speech Recognizer:', error);
    }

    speechRecognizer = null;
}

/* ---------- Đọc JSON kết quả Azure an toàn ---------- */

function parseAzureResultJson(result) {
    try {
        const propertyId =
            SpeechSDK.PropertyId
                .SpeechServiceResponse_JsonResult;

        const rawJson = result?.properties?.getProperty(
            propertyId
        );

        if (!rawJson) {
            return null;
        }

        const parsed = JSON.parse(rawJson);

        if (!parsed || typeof parsed !== 'object') {
            return null;
        }

        return parsed;
    } catch (error) {
        console.warn('Không đọc được JSON Azure:', error);
        return null;
    }
}

/* ---------- Lấy NBest đầu tiên an toàn ---------- */

function getBestResult(azureJson) {
    if (
        !azureJson
        || !Array.isArray(azureJson.NBest)
        || azureJson.NBest.length === 0
    ) {
        return null;
    }

    const best = azureJson.NBest[0];

    return best && typeof best === 'object'
        ? best
        : null;
}

/* ---------- Lấy điểm Pronunciation Assessment ---------- */

function getAssessmentScores(bestResult) {
    const assessment =
        bestResult?.PronunciationAssessment;

    if (!assessment || typeof assessment !== 'object') {
        return {
            pronunciation: 0,
            accuracy: 0,
            fluency: 0,
            completeness: 0
        };
    }

    return {
        pronunciation: Math.round(
            Number(
                assessment.PronScore
                ?? assessment.PronunciationScore
                ?? 0
            )
        ),

        accuracy: Math.round(
            Number(assessment.AccuracyScore ?? 0)
        ),

        fluency: Math.round(
            Number(assessment.FluencyScore ?? 0)
        ),

        completeness: Math.round(
            Number(assessment.CompletenessScore ?? 0)
        )
    };
}

/* ---------- Bộ nhớ phản hồi theo từng từ ---------- */

let previousAttemptByCard = {};

/* ---------- Chuẩn hóa ký hiệu âm Azure ---------- */

function normalizePhoneme(phoneme) {
    const clean = String(phoneme || '')
        .trim()
        .toLowerCase();

    const aliases = {
        'th': 'θ',
        'dh': 'ð',
        'sh': 'ʃ',
        'ch': 'tʃ',
        'jh': 'dʒ',
        'ng': 'ŋ'
    };

    return aliases[clean] || clean;
}

function getStudentSoundLabel(phoneme) {
    const clean = normalizePhoneme(phoneme);

    const labels = {
        'θ': 'th',
        'ð': 'th',
        'ʃ': 'sh',
        'tʃ': 'ch',
        'dʒ': 'j',
        'ŋ': 'ng'
    };

    return labels[clean] || clean;
}

/* ---------- Tìm một phụ âm ưu tiên cần sửa ---------- */

function findPriorityPhoneme(bestResult) {
    const words = Array.isArray(bestResult?.Words)
        ? bestResult.Words
        : [];

    /*
     * Teacher Goal First:
     * - Nếu Activity có Pronunciation Focus, chỉ xét đúng âm/vị trí đó.
     * - Nếu focus = none, dùng danh sách ưu tiên sư phạm mặc định.
     * - Mỗi lần chỉ trả về một lỗi.
     */
    const configuredFocus =
        pronunciationEngineConfig.focus || {};

    const focusKey = String(
        pronunciationEngineConfig.focus_key || 'none'
    );

    const focusType = String(
        configuredFocus.type || 'none'
    );

    const focusSounds = Array.isArray(configuredFocus.sounds)
        ? configuredFocus.sounds.map(normalizePhoneme)
        : [];

    const hasTeacherFocus =
        focusKey !== 'none'
        && focusType !== 'none'
        && focusSounds.length > 0;

    const initialPriority = [
        'θ', 'ð', 'ʃ', 'f', 'v',
        'tʃ', 'dʒ', 'r', 'l', 'w', 'j',
        'k', 't', 'p', 'd', 's', 'z'
    ];

    const finalPriority = [
        'k', 't', 'p', 'd', 'f',
        'θ', 'ð', 'ʃ', 's', 'z',
        'tʃ', 'dʒ', 'ŋ', 'm', 'n', 'l', 'r', 'v'
    ];

    const thresholds =
        pronunciationEngineConfig.score_thresholds || {};

    const clearErrorMax = Number(
        thresholds.clear_error_max ?? 67
    );

    const candidates = [];

    words.forEach(wordItem => {
        const phonemes = Array.isArray(wordItem?.Phonemes)
            ? wordItem.Phonemes
            : [];

        phonemes.forEach((phonemeItem, index) => {
            const score = Number(
                phonemeItem?.PronunciationAssessment?.AccuracyScore ?? 100
            );

            const normalized = normalizePhoneme(
                phonemeItem?.Phoneme
            );

            const isInitial = index === 0;
            const isFinal = index === phonemes.length - 1;

            if (
                score > clearErrorMax
                || (!isInitial && !isFinal)
            ) {
                return;
            }

            const position = isFinal ? 'final' : 'initial';

            if (hasTeacherFocus) {
                const matchesPosition =
                    focusType === position;

                const matchesSound =
                    focusSounds.includes(normalized);

                if (!matchesPosition || !matchesSound) {
                    return;
                }

                candidates.push({
                    phoneme: normalized,
                    score,
                    priorityIndex: 0,
                    position: isFinal ? 'cuối' : 'đầu',
                    word: String(
                        cards[currentIndex]?.word || ''
                    ).trim()
                });

                return;
            }

            const priorityList = isFinal
                ? finalPriority
                : initialPriority;

            const priorityIndex =
                priorityList.indexOf(normalized);

            if (priorityIndex === -1) {
                return;
            }

            candidates.push({
                phoneme: normalized,
                score,
                priorityIndex,
                position: isFinal ? 'cuối' : 'đầu',
                word: String(
                    cards[currentIndex]?.word || ''
                ).trim()
            });
        });
    });

    if (candidates.length === 0) {
        return null;
    }

    candidates.sort((a, b) => {
        if (a.priorityIndex !== b.priorityIndex) {
            return a.priorityIndex - b.priorityIndex;
        }

        return a.score - b.score;
    });

    return candidates[0];
}

/* ---------- Phản hồi sư phạm ngắn, rõ ---------- */

function buildCorrectionFeedback(issue) {
    if (!issue) {
        return '';
    }

    const word = issue.word
        || cards[currentIndex]?.word
        || '';

    const sound = getStudentSoundLabel(
        issue.phoneme
    );

    if (issue.position === 'cuối') {
        return `Từ ${word}: nhớ đọc rõ /${sound}/ ở cuối.`;
    }

    return `Từ ${word}: nhớ đọc rõ /${sound}/.`;
}

function getSuccessTitle(score) {
    const thresholds =
        pronunciationEngineConfig.score_thresholds || {};

    const messages =
        pronunciationEngineConfig.messages?.success || {};

    const perfectMin = Number(
        thresholds.perfect_min ?? 99
    );

    const excellentMin = Number(
        thresholds.excellent_min ?? 95
    );

    if (score >= perfectMin) {
        return `${messages.perfect || '⭐ Perfect!'} ${score}%`;
    }

    if (score >= excellentMin) {
        return `${messages.excellent || '🌟 Excellent!'} ${score}%`;
    }

    return `${messages.great || '👏 Great!'} ${score}%`;
}

/* ---------- Hiển thị kết quả chấm ---------- */

function normalizeRecognizedWord(text) {
    return String(text || '')
        .trim()
        .toLowerCase()
        .replace(/[^a-z'-]/g, '');
}

function displayAssessmentResult(
    recognitionResult,
    azureJson
) {
    const bestResult = getBestResult(azureJson);

    const recognizedText = String(
        bestResult?.Display
        ?? recognitionResult?.text
        ?? ''
    ).trim();

    const currentWord = String(
        cards[currentIndex]?.word || ''
    ).trim();

    if (!bestResult || recognizedText === '') {
        showFeedback(
            'try',
            '🎤 Let\'s try again!',
            'Chưa nghe thấy giọng.',
            true
        );
        setResultActions(false);

        setResultActions(false);

        return;
    }

    const assessmentScores =
        getAssessmentScores(bestResult);

    const pronunciation =
        assessmentScores.pronunciation;

    if (pronunciation <= 0) {
        showFeedback(
            'try',
            '🎤 Let\'s try again!',
            'Chưa nghe thấy giọng.',
            true
        );
        setResultActions(false);

        setResultActions(false);

        return;
    }

    const previousScore = Number(
        previousAttemptByCard[currentIndex]?.score || 0
    );

    const improved =
        previousScore > 0
        && pronunciation >= previousScore + 4;

    previousAttemptByCard[currentIndex] = {
        score: Math.max(previousScore, pronunciation)
    };

    saveBestScore(pronunciation);

    /*
     * 90% trở lên: chỉ khen và chuyển tiếp.
     * Không tìm lỗi nhỏ, không yêu cầu đọc lại.
     */
    if (pronunciation >= passingScore) {
        const successMessages =
            pronunciationEngineConfig.messages?.success || {};

        showFeedback(
            'good',
            getSuccessTitle(pronunciation),
            improved
                ? (successMessages.better || 'Better!')
                : '',
            true
        );
        setResultActions(true);

        

        return;
    }

    /*
     * Nếu Azure nghe thành một từ hoàn toàn khác và điểm thấp,
     * không đoán một phụ âm cụ thể.
     */
    const normalizedTarget =
        normalizeRecognizedWord(currentWord);

    const normalizedRecognized =
        normalizeRecognizedWord(recognizedText);

    const clearlyDifferentWord =
        normalizedTarget !== ''
        && normalizedRecognized !== ''
        && normalizedTarget !== normalizedRecognized
        && pronunciation < 65;

    if (clearlyDifferentWord) {
        showFeedback(
            'try',
            `🙂 ${pronunciation}%`,
            `Nghe lại từ ${currentWord}.<br>Try again!`,
            true
        );

        setResultActions(false);

        return;
    }

    const priorityIssue =
        findPriorityPhoneme(bestResult);

    const correction =
        buildCorrectionFeedback(priorityIssue);

    /*
     * Có một lỗi phụ âm rõ ràng:
     * chỉ sửa đúng một lỗi và tách Try again! thành dòng riêng.
     */
    if (correction !== '') {
        showFeedback(
            pronunciation >= 72 ? 'good' : 'try',
            `🙂 ${pronunciation}%`,
            `${escapeHtml(correction)}<br>Try again!`,
            true
        );

        setResultActions(false);

        return;
    }

    /*
     * Không đủ chắc để chỉ ra phụ âm cụ thể:
     * không đoán lỗi, chỉ hướng dẫn nghe lại.
     */
    if (pronunciation >= 80) {
        showFeedback(
            'good',
            `🙂 ${pronunciation}%`,
            improved
                ? 'Better!<br>Try again!'
                : 'Nghe lại mẫu.<br>Try again!',
            true
        );

        setResultActions(false);

        return;
    }

    if (pronunciation >= 65) {
        showFeedback(
            'try',
            `🙂 ${pronunciation}%`,
            `Từ ${currentWord}: đọc chậm và rõ hơn.<br>Try again!`,
            true
        );

        setResultActions(false);

        return;
    }

    showFeedback(
        'try',
        `🙂 ${pronunciation}%`,
        `Từ ${currentWord}: nghe lại mẫu.<br>Try again!`,
        true
    );
    setResultActions(false);
}

/* ===== HẾT ĐOẠN 2 — KHÔNG ĐÓNG THẺ SCRIPT ===== */
/* =========================================================
   ĐOẠN 3 — Giữ để nói, thả để chấm bằng Azure Speech SDK
   ========================================================= */

/* ---------- Tạo Speech Recognizer ---------- */

async function createSpeechRecognizer() {
    ensureSpeechSdkReady();

    const auth = await getSpeechToken();

    const speechConfig =
        SpeechSDK.SpeechConfig.fromAuthorizationToken(
            auth.token,
            auth.region
        );

    speechConfig.speechRecognitionLanguage = 'en-US';
    speechConfig.outputFormat =
        SpeechSDK.OutputFormat.Detailed;

    const audioConfig =
        SpeechSDK.AudioConfig
            .fromDefaultMicrophoneInput();

    const recognizer =
        new SpeechSDK.SpeechRecognizer(
            speechConfig,
            audioConfig
        );

    const targetWord = String(
        cards[currentIndex]?.word || ''
    ).trim();

    const pronunciationConfig =
        new SpeechSDK.PronunciationAssessmentConfig(
            targetWord,
            SpeechSDK
                .PronunciationAssessmentGradingSystem
                .HundredMark,
            SpeechSDK
                .PronunciationAssessmentGranularity
                .Phoneme,
            false
        );

    pronunciationConfig.applyTo(recognizer);

    return recognizer;
}

/* ---------- Tự kết thúc sau khi người học nói xong ---------- */

function clearAutoStopTimer() {
    if (autoStopTimer !== null) {
        window.clearTimeout(autoStopTimer);
        autoStopTimer = null;
    }
}

async function finishRecognizedAssessment(result, azureJson) {
    if (assessmentHandled) {
        return;
    }

    assessmentHandled = true;
    clearAutoStopTimer();
    setSpeakButtonScoring();

    // Cho học sinh nhìn thấy trạng thái "Đang chấm" trước khi hiện kết quả.
    await new Promise(resolve => window.setTimeout(resolve, 220));

    displayAssessmentResult(result, azureJson);

    if (speechRecognizer) {
        try {
            await new Promise((resolve, reject) => {
                speechRecognizer.stopContinuousRecognitionAsync(resolve, reject);
            });
        } catch (error) {
            console.warn('Không tự dừng được Azure Speech:', error);
        }
    }

    isPreparing = false;
    isListening = false;
    disposeSpeechRecognizer();
    setSpeakButtonReady();
}

async function finishNoSpeechAssessment() {
    if (assessmentHandled) {
        return;
    }

    assessmentHandled = true;
    clearAutoStopTimer();
    setSpeakButtonScoring();

    if (speechRecognizer) {
        try {
            await new Promise((resolve, reject) => {
                speechRecognizer.stopContinuousRecognitionAsync(resolve, reject);
            });
        } catch (error) {}
    }

    showFeedback(
        'try',
        '🎤 Let\'s try again!',
        'Chưa nhận được giọng nói. Em nói gần micro rồi thử lại nhé.',
        true
    );
    setResultActions(false);

    isPreparing = false;
    isListening = false;
    disposeSpeechRecognizer();
    setSpeakButtonReady();
}

/* ---------- Bắt đầu nghe ---------- */

async function startSpeakingAssessment() {
    if (isPreparing || isListening) {
        return;
    }

    isPreparing = true;
    stopRequested = false;
    assessmentHandled = false;
    clearAutoStopTimer();

    clearFeedback();
    setSpeakButtonPreparing();

    try {
        disposeSpeechRecognizer();

        speechRecognizer =
            await createSpeechRecognizer();

        speechRecognizer.recognizing = (
            sender,
            event
        ) => {
            if (
                event?.result?.text
                && String(event.result.text).trim() !== ''
            ) {
                setSpeakButtonListening();
            }
        };

        speechRecognizer.recognized = (
            sender,
            event
        ) => {
            const result = event?.result;

            if (!result) {
                return;
            }

            if (
                result.reason
                !== SpeechSDK
                    .ResultReason
                    .RecognizedSpeech
            ) {
                return;
            }

            const azureJson =
                parseAzureResultJson(result);

            finishRecognizedAssessment(
                result,
                azureJson
            );
        };

        speechRecognizer.canceled = (
            sender,
            event
        ) => {
            console.warn(
                'Azure Speech canceled:',
                event
            );

            const errorDetails = String(
                event?.errorDetails || ''
            );

            if (
                errorDetails
                    .toLowerCase()
                    .includes('token')
            ) {
                cachedSpeechToken = '';
                cachedSpeechRegion = '';
                speechTokenExpiresAt = 0;
            }

            if (!isListening) {
                return;
            }

            showFeedback(
                'try',
                'Thử lại nhé',
                'Azure chưa nghe rõ. Em kiểm tra micro rồi thử lại nhé.'
            );
        };

        speechRecognizer.sessionStopped = () => {
            isListening = false;
            isPreparing = false;
            if (!assessmentHandled) {
                clearAutoStopTimer();
                setSpeakButtonReady();
            }
        };

        await new Promise((resolve, reject) => {
            speechRecognizer
                .startContinuousRecognitionAsync(
                    resolve,
                    reject
                );
        });

        isPreparing = false;
        isListening = true;
        setSpeakButtonListening();

        // Một từ ngắn chỉ cần vài giây. Nếu Azure không trả kết quả sau 6 giây,
        // coi là chưa nhận được giọng thay vì tự gán điểm thấp.
        autoStopTimer = window.setTimeout(() => {
            if (isListening && !assessmentHandled) {
                finishNoSpeechAssessment();
            }
        }, 6000);
    } catch (error) {
        isPreparing = false;
        isListening = false;
        clearAutoStopTimer();

        disposeSpeechRecognizer();
        setSpeakButtonReady();

        showFeedback(
            'try',
            'Chưa kết nối được Azure Speech',
            error?.message
            || 'Em kiểm tra Internet và micro rồi thử lại nhé.'
        );
    }
}

/* ---------- Dừng nghe và chấm ---------- */

async function stopSpeakingAssessment() {
    stopRequested = true;
    clearAutoStopTimer();

    if (isPreparing) {
        return;
    }

    if (!isListening || !speechRecognizer) {
        return;
    }

    isListening = false;
    setSpeakButtonScoring();

    try {
        await new Promise((resolve, reject) => {
            speechRecognizer
                .stopContinuousRecognitionAsync(
                    resolve,
                    reject
                );
        });

        await new Promise(resolve => {
            window.setTimeout(resolve, 250);
        });
    } catch (error) {
        console.warn(
            'Không dừng được Azure Speech:',
            error
        );

        showFeedback(
            'try',
            'Thử lại nhé',
            'Chưa nhận được kết quả. Em nói lại một lần nữa nhé.'
        );
    } finally {
        isPreparing = false;
        isListening = false;

        disposeSpeechRecognizer();
        setSpeakButtonReady();
    }
}

/* ---------- Chuyển sang thẻ tiếp theo ---------- */

async function goToNextCard() {
    const currentScore = Number(scores[currentIndex] || 0);

    if (currentScore < passingScore) {
        setResultActions(false);
        return;
    }

    if (isPreparing || isListening) {
        return;
    }

    if (currentIndex < cards.length - 1) {
        currentIndex++;
        // renderCurrentCard() đã tự phát âm mẫu đúng 1 lần.
        // Không gọi playWordAudio() thêm ở đây để tránh âm bị phát chồng.
        renderCurrentCard();

        return;
    }

    const averageScore = getAverageScore();
    const progressResult = await saveActivityProgress(averageScore);

    if (!isPreview && (!progressResult || progressResult.ok !== true)) {
        showFeedback(
            'try',
            'Chưa lưu được kết quả',
            'Em giữ nguyên trang và thử bấm Next lại nhé.'
        );
        setResultActions(true);
        return;
    }

    practiceScreen.style.display = 'none';
    finishScreen.classList.add('show');

    if (finalScore) {
        finalScore.textContent = `${averageScore}/100`;
    }

    showXpPopup(progressResult, averageScore);
}

/* ---------- Học lại từ đầu ---------- */

function restartActivity() {
    stopRequested = true;

    if (isListening || isPreparing) {
        stopSpeakingAssessment();
    }

    currentIndex = 0;
    scores = [];
    previousAttemptByCard = {};

    finishScreen.classList.remove('show');
    practiceScreen.style.display = 'block';

    hideXpPopup();
    renderCurrentCard();
}

/* ===== HẾT ĐOẠN 3 — KHÔNG ĐÓNG THẺ SCRIPT ===== */
/* =========================================================
   ĐOẠN 4 — Sự kiện giao diện và khởi động hoạt động
   ========================================================= */

/* ---------- Listen ---------- */

listenButton?.addEventListener('click', () => {
    const card = cards[currentIndex];

    if (!card) {
        return;
    }

    playWordAudio(
        card.word,
        card.audio
    );
});

imageButton?.addEventListener('click', () => {
    const card = cards[currentIndex];

    if (!card) {
        return;
    }

    playWordAudio(
        card.word,
        card.audio
    );
});

/* ---------- Thử lại ---------- */

retryButton?.addEventListener('click', () => {
    if (isPreparing || isListening) {
        return;
    }

    clearFeedback();

    const card = cards[currentIndex];

    if (card) {
        playWordAudio(
            card.word,
            card.audio
        );
    }
});

/* ---------- Sang thẻ tiếp theo ---------- */

nextButton?.addEventListener(
    'click',
    goToNextCard
);

/* ---------- Học lại toàn bộ ---------- */

replayButton?.addEventListener(
    'click',
    restartActivity
);

/* ---------- Nói: PC và mobile đều bấm một lần ---------- */

// Không dùng pointerup để dừng nữa. Azure tự xác định khi câu/từ đã kết thúc
// và sự kiện recognized sẽ tự dừng phiên rồi chấm.
speakButton?.addEventListener('click', event => {
    event.preventDefault();

    if (isPreparing || isListening) {
        return;
    }

    startSpeakingAssessment();
});

/* ---------- Không cho mở menu chuột phải trên nút Speak ---------- */

speakButton?.addEventListener(
    'contextmenu',
    event => {
        event.preventDefault();
    }
);

/* ---------- Dọn dẹp khi rời trang ---------- */

window.addEventListener(
    'beforeunload',
    () => {
        try {
            if (
                speechRecognizer
                && isListening
            ) {
                speechRecognizer
                    .stopContinuousRecognitionAsync(
                        () => {
                            disposeSpeechRecognizer();
                        },
                        () => {
                            disposeSpeechRecognizer();
                        }
                    );

                return;
            }
        } catch (error) {}

        disposeSpeechRecognizer();
    }
);

/* ---------- Khởi động ---------- */

renderCurrentCard();

/* =========================================================
   HẾT ENGINE LISTEN & REPEAT
   ========================================================= */
</script>
<?php endif; ?>
</body>
</html>
