<?php
declare(strict_types=1);

session_start();

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

if (!isset($_SESSION['student_id']) && !$isPreview) {
    header('Location: student-login.php');
    exit;
}

require_once __DIR__ . '/../config/database.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 smtColumns(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 smtAssetUrl(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 = smtColumns($pdo, 'activities');
$relationColumns = smtColumns($pdo, 'flashcard_set_words');
$wordColumns = smtColumns($pdo, 'words');

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

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

$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 Matching chưa đầy đủ.');
}

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

$contextStmt = $pdo->prepare(
    "SELECT
        a.id AS activity_id,
        a.flashcard_set_id,
        a.xp_reward AS xp_reward,
        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 Matching trong cấp độ đã chọn.');
}

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

/*
 * STEP 1 — Matching reads only "pairs" from Activity Settings.
 * Other settings are intentionally untouched in this step.
 */
$matchingSettings = $_SESSION['activity_settings_data'] ?? [];

/*
 * Always refresh from database so changes made in Activity Settings
 * take effect immediately and are not masked by stale Session data.
 */
if (smtColumns($pdo, 'activity_settings') !== []) {
    $settingsStmt = $pdo->prepare(
        'SELECT settings_json FROM activity_settings WHERE activity_id = ? LIMIT 1'
    );
    $settingsStmt->execute([$activityId]);
    $settingsJson = $settingsStmt->fetchColumn();

    if (is_string($settingsJson) && $settingsJson !== '') {
        $decodedSettings = json_decode($settingsJson, true);

        if (is_array($decodedSettings)) {
            $matchingSettings = array_merge(
                $matchingSettings,
                $decodedSettings
            );

            $_SESSION['activity_settings_data'] = $matchingSettings;
        }
    }
}

$pairsPerSession = max(
    2,
    min(100, (int)($matchingSettings['pairs'] ?? 6))
);

$showImages = filter_var(
    $matchingSettings['show_image'] ?? true,
    FILTER_VALIDATE_BOOL
);

$showWords = filter_var(
    $matchingSettings['show_word'] ?? true,
    FILTER_VALIDATE_BOOL
);

$wordAudioOnTap = filter_var(
    $matchingSettings['word_audio_on_tap'] ?? false,
    FILTER_VALIDATE_BOOL
);

$autoNext = filter_var(
    $matchingSettings['auto_next'] ?? false,
    FILTER_VALIDATE_BOOL
);

$passingScore = max(
    0,
    min(100, (int)($matchingSettings['passing_score'] ?? 70))
);

/*
 * Matching requires both sides to remain playable.
 * If both options are accidentally disabled, restore both.
 */
if (!$showImages && !$showWords) {
    $showImages = true;
    $showWords = true;
}

$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,
        {$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'] ?? ''),
            'image' => smtAssetUrl((string)($row['image_path'] ?? '')),
            'audio' => smtAssetUrl((string)($row['audio_path'] ?? '')),
        ];
    },
    $rows
);

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

$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 = '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,
]);

$isRuntime = (int)($_GET['runtime'] ?? 0) === 1
    || isset($_SESSION['runtime_activity_queue']);

$nextRuntimeUrl = 'student-runtime/index.php?' . http_build_query(
    [
        'runtime_action' => 'next',
        'current_activity_id' => $activityId,
        'lesson_id' => $lessonId,
        'preview' => (int)($_GET['preview'] ?? 0) === 1 ? 1 : 0,
    ],
    '',
    '&',
    PHP_QUERY_RFC3986
);

$_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>Matching | EnglishSpeak</title>
<style>
:root{
  --blue:#198fe3;--blue-dark:#0868c7;--soft:#eaf5ff;--page:#f4f8fc;
  --text:#15314b;--muted:#718399;--line:#dfe8f0;--green:#26a65b;
  --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}
.preview-banner{margin:10px 0 8px;padding:10px 12px;border:1px solid #b9d8ff;border-radius:12px;background:#eef6ff;color:#1769c2;text-align:center;font-size:13px;font-weight:800}
.app{width:min(100%,var(--width));min-height:100dvh;padding:0 12px 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 -12px 14px;padding:12px;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}
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:12px}
.stat{padding:10px 8px;border:1px solid var(--line);border-radius:14px;background:#fff;text-align:center}.stat strong{display:block;color:var(--blue-dark);font-size:18px}.stat span{display:block;margin-top:3px;color:var(--muted);font-size:11px}
.match-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:9px}
.match-card{min-height:110px;padding:8px;border:1px solid var(--line);border-radius:16px;background:#fff;color:var(--text);box-shadow:var(--shadow);cursor:pointer;display:grid;place-items:center;text-align:center}
.match-card img{width:100%;height:86px;object-fit:contain}
.match-card__word{font-size:clamp(16px,4.7vw,21px);font-weight:850;color:var(--blue-dark);line-height:1.2;overflow-wrap:anywhere}
.match-card.selected{border:2px solid var(--blue);background:var(--soft)}
.match-card.matched{border:2px solid #7dd69c;background:#f1fff5;opacity:.55;pointer-events:none}
.actions{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-top:14px}
.actions--single{grid-template-columns:1fr}
.action{min-height:50px;border:1px solid #cbddeb;border-radius:15px;background:#fff;color:var(--text);font-weight:850;cursor:pointer}
.action.primary{border-color:var(--blue);background:var(--blue);color:#fff}
.finish{display:none;min-height:calc(100dvh - 110px);padding:38px 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;color:#123e62;font-size:35px}.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:10px;padding-right:10px}.topbar{margin-left:-10px;margin-right:-10px;padding-left:10px;padding-right:10px}.match-grid{gap:7px}.match-card{min-height:100px;padding:7px}.match-card img{height:76px}.match-card__word{font-size:15px}}

/* 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 8px;
    color: #f39c12;
    font-size: 36px;
    font-weight: 900;
    line-height: 1;
}
.xp-popup__xp {
    display: none;
    margin: 0 0 17px;
    color: #16a34a;
    font-size: 22px;
    font-weight: 900;
    line-height: 1.2;
}
.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); }
}

</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="gameScreen">
  <div class="lesson"><div class="lesson__icon">🧩</div><strong>Matching</strong></div>
  <div class="stats">
    <div class="stat"><strong id="attempts">0</strong><span>Lượt chọn</span></div>
    <div class="stat"><strong id="pairs">0 / <?= count($cards) ?></strong><span>Cặp đúng</span></div>
  </div>
  <section class="match-grid" id="matchGrid" aria-label="Trò chơi ghép cặp"></section>
</section>

<section class="finish" id="finishScreen">
  <div class="finish__icon" id="finishIcon">🎉</div>
  <h2 id="finishTitle">Finished!</h2>
<div class="summary">
    <div class="summary-row"><span>Điểm</span><strong id="finalScore">0/100</strong></div>
    <div class="summary-row" id="finalXpRow"><span>XP nhận được</span><strong id="finalXp">+0 XP</strong></div>
    <div class="summary-row"><span>Cặp đúng</span><strong><?= count($cards) ?> / <?= count($cards) ?></strong></div>
    <div class="summary-row"><span>Số lượt chọn</span><strong id="finalAttempts">0</strong></div>
  </div>
  <div class="finish-actions">
    <?php if ($nextRuntimeUrl !== ''): ?>
    <a
      class="primary"
      id="continueButton"
      href="<?= htmlspecialchars($nextRuntimeUrl, ENT_QUOTES, 'UTF-8') ?>"
    >
      ➡ Next
    </a>
    <?php endif; ?>

    <button class="primary" id="playAgainButton" type="button">↻ Again</button>

    <a href="<?= htmlspecialchars($backUrl, ENT_QUOTES, 'UTF-8') ?>">☰ Back</a>
  </div>
</section>
<?php endif; ?>
</main>


<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">Great!</h2>
        <div class="xp-popup__score" id="xpPopupScore">100/100</div>
        <div class="xp-popup__xp" id="xpPopupXp">+2 XP</div>
        <button class="xp-popup__button" id="xpPopupButton" type="button">Xem kết quả</button>
    </div>
</div>

<?php if ($cards !== []): ?>
<script>

function showXpPopup(){
  const popup=document.getElementById('xpPopup');
  if(popup) popup.classList.add('show');
}
function hideXpPopup(){
  const popup=document.getElementById('xpPopup');
  if(popup) popup.classList.remove('show');
}
document.getElementById('xpPopupButton')?.addEventListener('click',hideXpPopup);


const sourceCards = <?= json_encode($cards, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES) ?>;
const matchingSettings = <?= json_encode([
    'show_image' => $showImages,
    'show_word' => $showWords,
    'word_audio_on_tap' => $wordAudioOnTap,
    'auto_next' => $autoNext,
    'passing_score' => $passingScore,
    'next_url' => $nextRuntimeUrl,
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const grid=document.getElementById('matchGrid');
const attemptsEl=document.getElementById('attempts');
const pairsEl=document.getElementById('pairs');
const gameScreen=document.getElementById('gameScreen');
const finishScreen=document.getElementById('finishScreen');
const activityId=<?= (int)$activityId ?>;
let deck=[],selected=null,attempts=0,matched=0,wrongAttempts=0,soundOn=true;
let activityStartedAt=Date.now();
let progressSavePromise=null;

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

  sourceCards.forEach((c,i)=>{
    if(matchingSettings.show_image){
      items.push({
        id:`${i}-image`,
        pair:i,
        type:'image',
        word:c.word,
        image:c.image||'',
        audio:c.audio||''
      });
    }

    if(matchingSettings.show_word){
      items.push({
        id:`${i}-word`,
        pair:i,
        type:'word',
        word:c.word,
        image:'',
        audio:c.audio||''
      });
    }
  });

  if(!matchingSettings.show_image || !matchingSettings.show_word){
    const clones=items.map(item=>({
      ...item,
      id:`${item.id}-copy`
    }));
    items.push(...clones);
  }

  return shuffle(items);
}
function speak(card){
  if(!soundOn){
    return;
  }

  /*
   * Matching:
   * bấm vào cả thẻ hình hoặc thẻ từ đều đọc đúng từ vựng đó.
   * Ưu tiên audio có sẵn; nếu không có thì dùng TTS.
   */
  if(card.audio){
    const audio=new Audio(card.audio);
    audio.play().catch(()=>tts(card.word));
  }else{
    tts(card.word);
  }
}

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

  const cleanText=String(text||'').trim();
  if(cleanText==='')return;

  window.speechSynthesis.cancel();

  const utterance=new SpeechSynthesisUtterance(cleanText);
  utterance.lang='en-US';
  utterance.rate=.72;
  utterance.pitch=1;
  utterance.volume=1;

  window.speechSynthesis.speak(utterance);
}
function render(){
  grid.innerHTML='';
  deck.forEach(card=>{
    const btn=document.createElement('button');
    btn.className='match-card';
    btn.type='button';
    btn.dataset.id=card.id;

    btn.innerHTML=card.type==='image'
      ? (card.image?`<img src="${card.image}" alt="${card.word}">`:`<span class="match-card__word">${card.word}</span>`)
      : `<span class="match-card__word">${card.word}</span>`;

    btn.onclick=()=>choose(btn,card);
    grid.appendChild(btn);
  });
}
function choose(el,card){
  if(el.classList.contains('matched'))return;

  speak(card);

  if(!selected){
    selected={el,card};
    el.classList.add('selected');
    return;
  }

  if(selected.el===el){
    el.classList.remove('selected');
    selected=null;
    return;
  }

  attempts++;
  attemptsEl.textContent=attempts;

  const samePair = selected.card.pair===card.pair;
  const differentCards = selected.card.id!==card.id;
  const complementaryTypes = selected.card.type!==card.type;
  const oneSideMode = !matchingSettings.show_image || !matchingSettings.show_word;

  if(
    samePair
    && differentCards
    && (complementaryTypes || oneSideMode)
  ){
    selected.el.classList.remove('selected');
    selected.el.classList.add('matched');
    el.classList.add('matched');
    matched++;
    pairsEl.textContent=`${matched} / ${sourceCards.length}`;
    selected=null;

    if(matched===sourceCards.length){
      setTimeout(finish,350);
    }
    return;
  }

  wrongAttempts++;

  const old=selected;
  old.el.classList.remove('selected');
  el.classList.add('selected');
  selected={el,card};

  setTimeout(()=>{
    if(selected && selected.el===el){
      el.classList.remove('selected');
      selected=null;
    }
  },500);
}
function reset(){
  deck=buildDeck();
  selected=null;
  attempts=0;
  matched=0;
  wrongAttempts=0;
  activityStartedAt=Date.now();
  progressSavePromise=null;
  attemptsEl.textContent='0';
  pairsEl.textContent=`0 / ${sourceCards.length}`;
  render();
}
async function saveMatchingProgress(scorePercent){
  if(progressSavePromise){
    return progressSavePromise;
  }

  const payload={
    activity_id:activityId,
    score:Math.max(0,Math.min(100,Math.round(Number(scorePercent)||0))),
    duration:Math.max(0,Date.now()-activityStartedAt),
    completed:Number(scorePercent)>=Number(matchingSettings.passing_score||0),
    passing_score:Number(matchingSettings.passing_score||70),
    preview:<?= $isPreview ? 'true' : 'false' ?>
  };

  progressSavePromise=fetch('/EnglishSpeak/api/save-activity-progress.php',{
    method:'POST',
    credentials:'same-origin',
    cache:'no-store',
    headers:{
      'Content-Type':'application/json',
      'Accept':'application/json'
    },
    body:JSON.stringify(payload)
  })
  .then(async response=>{
    const result=await response.json();
    if(!response.ok || !result?.ok){
      throw new Error(result?.message||'Could not save progress.');
    }
    return result;
  })
  .catch(error=>{
    console.warn('Matching progress save failed:',error);
    return {ok:false,saved:false,message:error?.message||'Could not save progress.'};
  });

  return progressSavePromise;
}

async function finish(){
  // Matching score: completing all pairs starts at 100%.
  // Each incorrect match deducts 5 points.
  const scorePercent=Math.max(0,100-(wrongAttempts*5));
  const passed=scorePercent >= Number(matchingSettings.passing_score||0);

  // Shared Student Progress + XP:
  // XP amount must come from the shared XP Engine, never calculated in Matching.
  const progressResult=await saveMatchingProgress(scorePercent);
  const xpAdded=Math.max(0,Number(progressResult?.xp_added||0));

  document.getElementById('finalAttempts').textContent=attempts;
  document.getElementById('finalScore').textContent=`${scorePercent}/100`;

  const finalXp=document.getElementById('finalXp');
  const finalXpRow=document.getElementById('finalXpRow');
  if(finalXp && finalXpRow){
    if(xpAdded > 0){
      finalXp.textContent=`+${xpAdded} XP`;
      finalXpRow.style.display='flex';
    }else{
      finalXpRow.style.display='none';
    }
  }
  document.getElementById('finishIcon').textContent=passed?'🎉':'💪';
  document.getElementById('finishTitle').textContent=passed?'Finished!':'Try Again!';

  const continueButton=document.getElementById('continueButton');
  if(continueButton){
    continueButton.style.display=passed?'grid':'none';
  }

  const playAgainButton=document.getElementById('playAgainButton');
  if(playAgainButton){
    playAgainButton.textContent=passed?'↻ Again':'↻ Try Again';
  }

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

  // Khi đạt Passing Score, che Finish Report bằng popup kết quả ngắn.
  // Bấm "Xem kết quả" mới đóng popup để xem Finish Report phía sau.
  if(passed){
    const xpPopupScore=document.getElementById('xpPopupScore');
    const xpPopupXp=document.getElementById('xpPopupXp');

    if(xpPopupScore){
      xpPopupScore.textContent=`${scorePercent}%`;
    }

    if(xpPopupXp){
      if(xpAdded > 0){
        xpPopupXp.textContent=`+${xpAdded} XP`;
        xpPopupXp.style.display='block';
      }else{
        xpPopupXp.textContent='';
        xpPopupXp.style.display='none';
      }
    }

    showXpPopup();
  }

  if(
    passed
    && matchingSettings.auto_next
    && matchingSettings.next_url
  ){
    window.setTimeout(()=>{
      window.location.href=matchingSettings.next_url;
    },2500);
  }
}
document.getElementById('playAgainButton').onclick=()=>{
  finishScreen.classList.remove('show');
  hideXpPopup();
  gameScreen.style.display='block';
  reset();
};
reset();
</script>
<?php endif; ?>
</body>
</html>
