<?php
// /elms/admin/courses/game_builder.php
declare(strict_types=1);

if (session_status() === PHP_SESSION_NONE) session_start();

require_once __DIR__ . '/../../includes/db.php';

$mysqli = $mysqli ?? ($conn ?? null);
if (!$mysqli instanceof mysqli) {
    http_response_code(500);
    exit('Database connection not found.');
}
$mysqli->set_charset('utf8mb4');

function eg_esc($v): string {
    return htmlspecialchars((string)$v, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function eg_json(array $data): void {
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}
function eg_table_exists(mysqli $db, string $table): bool {
    $st = $db->prepare("SELECT 1 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? LIMIT 1");
    $st->bind_param('s', $table);
    $st->execute();
    $ok = (bool)$st->get_result()->fetch_row();
    $st->close();
    return $ok;
}
function eg_col_exists(mysqli $db, string $table, string $col): bool {
    $st = $db->prepare("SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=? AND COLUMN_NAME=? LIMIT 1");
    $st->bind_param('ss', $table, $col);
    $st->execute();
    $ok = (bool)$st->get_result()->fetch_row();
    $st->close();
    return $ok;
}
function eg_first_col(mysqli $db, string $table, array $cols): ?string {
    foreach ($cols as $c) {
        if (eg_col_exists($db, $table, $c)) return $c;
    }
    return null;
}
function eg_slugify(string $s): string {
    $s = preg_replace('~[^\pL0-9]+~u', '-', $s);
    $s = trim((string)$s, '-');
    if (function_exists('iconv')) $s = (string)@iconv('UTF-8', 'ASCII//TRANSLIT', $s);
    $s = strtolower($s);
    $s = preg_replace('~[^a-z0-9-]+~', '', $s);
    return $s ?: ('game-' . date('YmdHis'));
}
function eg_content_table(mysqli $db): array {
    foreach (['lms_content','lms_contents','lms_module_contents','module_contents','lms_content_items'] as $t) {
        if (!eg_table_exists($db, $t)) continue;
        $mid    = eg_first_col($db, $t, ['module_id','lms_module_id','unit_id']);
        $title  = eg_first_col($db, $t, ['title','name']);
        $url    = eg_first_col($db, $t, ['url','source_path_or_url','file_url','path','link']);
        $type   = eg_first_col($db, $t, ['content_type','type']);
        $desc   = eg_first_col($db, $t, ['description','details','desc']);
        $rel    = eg_first_col($db, $t, ['active','is_released','published']);
        $sort   = eg_first_col($db, $t, ['sort_order','order_no','position']);
        $course = eg_first_col($db, $t, ['course_id','lms_course_id']);
        if ($mid && $title && $url) return compact('t','mid','title','url','type','desc','rel','sort','course');
    }
    return ['t' => null];
}
function eg_courses(mysqli $db): array {
    if (!eg_table_exists($db, 'courses')) return [];
    $label = eg_first_col($db, 'courses', ['course_name','name','title']) ?? 'id';
    $rows = [];
    $rs = $db->query("SELECT id, `$label` AS label FROM courses ORDER BY `$label`, id");
    while ($rs && $r = $rs->fetch_assoc()) {
        $rows[] = ['id' => (int)$r['id'], 'label' => (string)$r['label']];
    }
    return $rows;
}
function eg_modules(mysqli $db, int $course_id): array {
    $rows = [];
    foreach (['course_modules','lms_modules'] as $t) {
        if (!eg_table_exists($db, $t)) continue;
        $courseCol = eg_first_col($db, $t, ['course_id','cid']);
        $title = eg_first_col($db, $t, ['title','name','module_name']) ?? 'id';
        $order = eg_col_exists($db, $t, 'sort_order') ? 'sort_order, id' : 'id';

        if ($courseCol && $course_id > 0) {
            $st = $db->prepare("SELECT id, `$title` AS title FROM `$t` WHERE `$courseCol`=? ORDER BY $order");
            $st->bind_param('i', $course_id);
        } else {
            $st = $db->prepare("SELECT id, `$title` AS title FROM `$t` ORDER BY $order");
        }

        $st->execute();
        $rs = $st->get_result();
        while ($r = $rs->fetch_assoc()) {
            $rows[(int)$r['id']] = [
                'id' => (int)$r['id'],
                'title' => trim((string)$r['title']) ?: ('Module #' . (int)$r['id'])
            ];
        }
        $st->close();
        if ($rows) break;
    }
    return array_values($rows);
}
function eg_course_module_label(mysqli $db, int $course_id, int $module_id): array {
    $course = 'Course #' . $course_id;
    $module = 'Module #' . $module_id;

    if (eg_table_exists($db, 'courses')) {
        $cCol = eg_first_col($db, 'courses', ['course_name','name','title']) ?? 'id';
        $st = $db->prepare("SELECT `$cCol` AS label FROM courses WHERE id=? LIMIT 1");
        $st->bind_param('i', $course_id);
        $st->execute();
        if ($r = $st->get_result()->fetch_assoc()) $course = (string)$r['label'];
        $st->close();
    }

    foreach (['course_modules','lms_modules'] as $t) {
        if (!eg_table_exists($db, $t)) continue;
        $courseCol = eg_first_col($db, $t, ['course_id','cid']);
        if (!$courseCol) continue;
        $mCol = eg_first_col($db, $t, ['title','name','module_name']) ?? 'id';
        $st = $db->prepare("SELECT `$mCol` AS label FROM `$t` WHERE id=? AND `$courseCol`=? LIMIT 1");
        $st->bind_param('ii', $module_id, $course_id);
        $st->execute();
        if ($r = $st->get_result()->fetch_assoc()) {
            $module = (string)$r['label'];
            $st->close();
            break;
        }
        $st->close();
    }
    return [$course, $module];
}
function eg_attempt_table_ensure(mysqli $db): void {
    $db->query("
        CREATE TABLE IF NOT EXISTS `lms_game_attempts` (
            `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
            `student_id` INT UNSIGNED NOT NULL DEFAULT 0,
            `game_id` INT UNSIGNED NULL,
            `content_id` INT UNSIGNED NULL,
            `game_url` VARCHAR(500) NULL,
            `score` INT NOT NULL DEFAULT 0,
            `total` INT NOT NULL DEFAULT 0,
            `percent` DECIMAL(6,2) NOT NULL DEFAULT 0,
            `time_sec` INT NOT NULL DEFAULT 0,
            `success` TINYINT(1) NOT NULL DEFAULT 0,
            `status` VARCHAR(40) NULL,
            `finished_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (`id`),
            KEY `idx_student` (`student_id`),
            KEY `idx_game` (`game_id`),
            KEY `idx_content` (`content_id`),
            KEY `idx_game_url` (`game_url`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    ");
}

eg_attempt_table_ensure($mysqli);

if (false && (($_GET['action'] ?? '') === 'student_attempt')) {
    $studentId = (int)($_SESSION['student_id'] ?? $_SESSION['student']['id'] ?? $_SESSION['id'] ?? 0);
    $gameUrl   = trim((string)($_POST['game_url'] ?? ''));
    $score     = (int)($_POST['score'] ?? 0);
    $total     = (int)($_POST['total'] ?? 0);
    $percent   = $total > 0 ? round(($score / $total) * 100, 2) : (float)($_POST['percent'] ?? 0);
    $timeSec   = (int)($_POST['time_sec'] ?? 0);
    $success   = $percent >= 50 ? 1 : 0;
    $status = 'completed';
    $contentId = 0;

    $CT = eg_content_table($mysqli);
    if ($gameUrl !== '' && !empty($CT['t'])) {
        $t = $CT['t'];
        $u = $CT['url'];
        $st = $mysqli->prepare("SELECT id FROM `$t` WHERE `$u`=? LIMIT 1");
        $st->bind_param('s', $gameUrl);
        $st->execute();
        if ($r = $st->get_result()->fetch_assoc()) $contentId = (int)$r['id'];
        $st->close();
    }

    $st = $mysqli->prepare("INSERT INTO lms_game_attempts(student_id,content_id,game_url,score,total,percent,time_sec,success,status) VALUES(?,?,?,?,?,?,?,?,?)");
    $st->bind_param('iisiiidis', $studentId, $contentId, $gameUrl, $score, $total, $percent, $timeSec, $success, $status);
    $st->execute();
    $st->close();
    eg_json(['ok' => true]);
}

if (is_file(__DIR__ . '/../../includes/auth.php')) {
    require_once __DIR__ . '/../../includes/auth.php';
    if (function_exists('require_role')) require_role(['super_admin','admin','branch_admin']);
}

function eg_build_game_html(string $title, string $type, array $data): string {
    $safeTitle = eg_esc($title);
    $payload = base64_encode(json_encode(['type' => $type, 'data' => $data], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));

    return <<<HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>{$safeTitle} · EDEN CAMPUS</title>
<style>
*{box-sizing:border-box}
html,body{margin:0;width:100%;min-height:100%;font-family:Arial,Helvetica,sans-serif;background:#fff;color:#222}
.ec-shell{width:100%;min-height:100vh;background:#fff}
.ec-card{width:100%;min-height:100vh;background:#fff;overflow:hidden}

.ec-top{
    height:86px;
    background:linear-gradient(135deg,#0f172a,#034ea2);
    color:#fff;
    display:grid;
    grid-template-columns:1fr 150px 150px 150px;
    align-items:center;
    box-shadow:0 10px 24px rgba(15,23,42,.18);
}

.ec-brand{
    height:86px;
    display:flex;
    align-items:center;
    gap:14px;
    padding:14px 22px;
    font-weight:900;
}

.ec-brand img{
    width:50px;
    height:50px;
    border-radius:14px;
    background:#fff;
    object-fit:contain;
    padding:5px;
    box-shadow:0 8px 20px rgba(0,0,0,.18);
}

.ec-title{
    margin:0;
    color:#fff;
    font-size:18px;
    font-weight:950;
    line-height:1.2;
    letter-spacing:.2px;
}

.ec-pill{
    height:86px;
    border-left:1px solid rgba(255,255,255,.14);
    display:flex;
    align-items:center;
    justify-content:center;
    flex-direction:column;
    font-weight:950;
    font-size:24px;
}

.ec-pill small{
    font-size:10px;
    opacity:.8;
    margin-top:6px;
    text-transform:uppercase;
    letter-spacing:.6px;
}

.ec-pill-score{
    background:rgba(255,255,255,.12);
    color:#fff;
    height:64px;
    margin:11px 10px;
    border-radius:16px;
    box-shadow:inset 0 0 0 1px rgba(255,255,255,.18);
}

.ec-body{
    min-height:calc(100vh - 86px);
    padding:24px;
    background:
        radial-gradient(circle at top left,rgba(3,78,162,.08),transparent 34%),
        linear-gradient(180deg,#ffffff,#f8fbff);
}

.start{
    min-height:calc(100vh - 180px);
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    text-align:center;
}

.start-logo{
    width:92px;
    height:92px;
    border:1px solid #e5e7eb;
    border-radius:24px;
    display:grid;
    place-items:center;
    overflow:hidden;
    margin-bottom:20px;
    background:#fff;
    box-shadow:0 16px 35px rgba(15,23,42,.10);
}

.start-logo img{
    width:100%;
    height:100%;
    object-fit:contain;
    padding:10px;
}

.start h1{
    margin:0 0 10px;
    color:#0f172a;
    font-size:32px;
    font-weight:950;
    letter-spacing:-.5px;
}

.start p{
    max-width:720px;
    margin:0 auto 26px;
    color:#475569;
    font-size:15px;
    line-height:1.6;
}

.btn-primary{
    min-width:132px;
    height:44px;
    border-radius:999px;
    background:#034ea2;
    color:#fff;
    border:0;
    box-shadow:0 12px 24px rgba(3,78,162,.24);
}

.btn-primary:hover{
    background:#023b7a;
}

.actions{position:fixed;left:0;right:0;bottom:0;height:62px;background:#24292f;display:flex;justify-content:center;align-items:center;gap:10px;z-index:40}
.match-area{
    position:relative;
    display:grid;
    grid-template-columns:1fr 1fr;
    gap:120px;
    padding:26px 46px;
    min-height:calc(100vh - 230px);
}
.match-col{
    display:flex;
    flex-direction:column;
    gap:18px;
    position:relative;
    z-index:2;
}
.match-card{
    min-height:62px;
    border:1px solid #d8e2ef;
    background:linear-gradient(180deg,#ffffff,#f2f7ff);
    color:#1f2937;
    border-radius:14px;
    padding:12px 18px;
    font-size:16px;
    font-weight:700;
    display:flex;
    align-items:center;
    justify-content:center;
    text-align:center;
    cursor:pointer;
    user-select:none;
    box-shadow:0 8px 18px rgba(15,23,42,.08);
    transition:.18s;
}
.match-card:hover{
    transform:translateY(-2px);
    border-color:#0875d1;
    box-shadow:0 12px 26px rgba(8,117,209,.15);
}
.match-card.sel{
    border:2px solid #facc15!important;
    background:#fff7cc!important;
}
.match-card.ok{
    border:2px solid #16a34a!important;
    background:#dcfce7!important;
    color:#14532d!important;
}
.match-card.bad{
    border:2px solid #dc2626!important;
    background:#fee2e2!important;
    color:#7f1d1d!important;
}
.match-card.left::before{
    content:"";
    width:12px;
    height:12px;
    border-radius:50%;
    background:#0875d1;
    position:absolute;
    right:-6px;
}
.match-card.right::before{
    content:"";
    width:12px;
    height:12px;
    border-radius:50%;
    background:#0875d1;
    position:absolute;
    left:-6px;
}
.match-card{position:relative;}
.match-svg{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:1;}
.blank{width:110px;height:24px;border:1px solid #ddd;border-radius:2px;background:#f2f2f2;padding:2px 6px;margin:0 4px;font-size:13px}
.fill-wrap{min-height:calc(100vh - 240px);background:#34383d;color:#fff;padding:28px 16px;font-size:15px;line-height:2.2}
.word-bank{float:right;width:28%;text-align:center;color:#fff;margin-top:0}
.word-bank-title{font-size:14px;margin-bottom:14px}
.chip,.bank,.blankdrop{display:inline-flex;align-items:center;justify-content:center;min-height:24px;border:1px solid #cfcfcf;background:#f3f3f3;color:#333;border-radius:2px;padding:4px 10px;margin:3px;font-size:12px;cursor:pointer}
.chip.sel,.bank.sel{border-color:#6ba421!important;background:#fffbe8!important}
.chip.ok,.blankdrop.ok{border-color:#6ba421!important;background:#eaffdf!important}
.chip.bad,.blankdrop.bad{border-color:#dc2626!important;background:#ffecec!important}
.tf-box{max-width:900px;margin:0 auto}
.tf-row{padding:14px;border-bottom:1px solid #ddd}
.order-box{display:grid;grid-template-columns:repeat(4,minmax(120px,1fr));gap:28px 80px;padding:70px 20px}
.item{min-height:38px;border:1px solid #999;background:linear-gradient(#fff,#ececec);border-radius:2px;display:flex;align-items:center;justify-content:center;font-size:12px;color:#333;cursor:grab;padding:8px}
.item.ok{border-color:#6ba421!important;background:#eaffdf!important}
.item.bad{border-color:#dc2626!important;background:#ffecec!important}
.mem{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:14px;padding:30px}
.mem-card{height:90px;border:1px solid #bdbdbd;background:linear-gradient(#fff,#eee);display:grid;place-items:center;border-radius:3px;font-size:14px;font-weight:700;cursor:pointer;text-align:center;padding:8px}
.mem-card.done{background:#dff7d8;border-color:#6ba421}
.ws{display:grid;gap:4px;justify-content:center;padding:24px}
.ws-cell{width:34px;height:34px;border:1px solid #999;background:#fff;display:grid;place-items:center;font-size:14px;font-weight:700;cursor:pointer}
.ws-cell.sel{background:#ffe680}
.cw-wrap{min-height:calc(100vh - 240px);display:grid;grid-template-columns:65% 35%;background:#aaa}
.cw-board{display:flex;align-items:flex-start;justify-content:center;padding:0;background:#aaa}
.cw{display:grid;gap:0;margin-top:0;box-shadow:0 8px 18px rgba(0,0,0,.18)}
.cw-cell{width:48px;height:48px;border:1px solid #555;background:linear-gradient(#777,#4f4f4f);position:relative}
.cw-cell.white{background:linear-gradient(#fff,#e7e7e7)}
.cw-cell.active{background:#fff08a}
.cw-cell input{width:100%;height:100%;border:0;background:transparent;text-align:center;text-transform:uppercase;font-size:20px;font-weight:800;outline:none}
.cw-num{position:absolute;left:5px;top:3px;font-size:10px;color:#777;z-index:2}
.cw-panel{background:#fff;padding:28px 12px;text-align:center;color:#333}
.cw-clue-no{font-size:13px;color:#aaa;border-bottom:1px solid #ddd;padding:8px 0;margin-bottom:18px}
.cw-clue-text{display:inline-block;background:#f2a900;color:#111;font-style:italic;padding:3px 8px;font-size:15px;margin-bottom:28px}
.cw-help{background:#ddd;padding:13px;margin-top:0}
.cw-help button{border:1px solid #bbb;background:#f5f5f5;padding:6px 14px;border-radius:3px;margin:0 3px}
.cw input.ok{background:#fff!important;color:#111}
.cw input.bad{background:#f19797!important;color:#111}
.cw-cell.active{background:#fff08a!important}
.cw-cell.active input{background:#fff08a!important}
.cw-cell.ok{background:#fff!important}
.cw-cell.ok input{background:#fff!important;color:#111}
.cw-cell.bad{background:#f19797!important}
.cw-cell.bad input{background:#f19797!important;color:#111}
.clues{background:#fff;padding:35px 22px;color:#222}
.clues b{display:block;text-align:center;color:#999;margin-bottom:14px}
.clues li{margin:10px 0;background:#f0a800;display:inline-block;padding:3px 8px;font-style:italic}
.hotspot-wrap{min-height:calc(100vh - 230px);display:grid;grid-template-columns:1fr 290px;gap:18px}
.hotspot-stage{position:relative;background:#f8fafc;border:1px solid #dbe3ef;border-radius:8px;min-height:520px;overflow:hidden;display:flex;align-items:center;justify-content:center}
.hotspot-img{max-width:100%;max-height:calc(100vh - 230px)}
.hotspot-point{position:absolute;transform:translate(-50%,-50%);width:32px;height:32px;border-radius:50%;background:#ffd21a;border:2px solid #111;color:#111;font-weight:900;cursor:pointer;z-index:4}
.hotspot-point.sel{background:#0875d1;color:#fff}
.hotspot-point.ok{background:#16a34a;color:#fff}
.hotspot-point.bad{background:#dc2626;color:#fff}
.hotspot-panel{background:#34383d;color:#fff;border-radius:8px;padding:18px;text-align:center}
.hotspot-panel h3{font-size:15px;margin:0 0 14px}
.hotspot-answer{display:block;width:100%;border:1px solid #cfcfcf;background:#f3f3f3;color:#333;border-radius:2px;padding:9px;margin:7px 0;font-size:12px;cursor:pointer}
.hotspot-answer.sel{border-color:#ffd21a;background:#fff8d2}
.overlay{position:fixed;inset:0;background:rgba(0,0,0,.62);display:none;align-items:center;justify-content:center;z-index:100}
.result-box{width:min(900px,92vw);background:#262b31;color:#fff;border-radius:3px;overflow:hidden;text-align:center}
.result-box h2{margin:0;background:#507d22;padding:18px;font-size:18px;text-transform:uppercase}
.result-box p{margin:0;padding:30px;font-size:24px;font-weight:800}
.result-box .btn-primary{margin:0 0 28px}
@media(max-width:900px){
.ec-top{grid-template-columns:1fr 90px 100px 100px}.ec-tools{display:none}.ec-pill{font-size:18px}.match-area{grid-template-columns:1fr;gap:10px}.cw-wrap,.hotspot-wrap{grid-template-columns:1fr}.word-bank{float:none;width:100%;margin-top:20px}.order-box{grid-template-columns:repeat(2,1fr);gap:16px}
}
</style>
</head>
<body>
<div class="ec-shell">
<div class="ec-card">
<div class="ec-top">
<div class="ec-brand"><img src="/elms/assets/119.jpg" alt="EDEN"><h1 class="ec-title">{$safeTitle}</h1></div>
<div class="ec-pill"><span id="tryNo">0</span><small>Num. Tries</small></div>
<div class="ec-pill ec-pill-score"><span id="score">0</span><small>Score</small></div>
<div class="ec-pill"><span id="timer">∞</span><small>Left Time</small></div>

</div>
<div class="ec-body">
<div id="startScreen" class="start">
<div class="start-logo"><img src="/elms/assets/119.jpg" alt="EDEN CAMPUS"></div>
<h1>{$safeTitle}</h1>
<p>Read the activity carefully and complete it before the timer ends.</p>
<button class="btn btn-primary" id="startBtn">Start Activity</button>
</div>
<div id="playScreen" style="display:none">
<div id="app"></div>
<div class="actions"><button class="btn btn-primary" id="checkBtn">Check</button></div>
</div>
</div>
</div>
</div>
<div class="overlay" id="overlay">
<div class="result-box">
<h2 id="modalTitle">Completed</h2>
<p id="modalText"></p>
<button class="btn btn-primary" onclick="location.reload()">Play Again</button>
</div>
</div>
<script>
(function(){
let GD={type:'match',data:{}};
try{GD=JSON.parse(atob("{$payload}"))||GD;}catch(e){}
const type=String(GD.type||'match').toLowerCase();
const data=GD.data||{};
const startScreen=document.getElementById('startScreen');
const playScreen=document.getElementById('playScreen');
const startBtn=document.getElementById('startBtn');
const app=document.getElementById('app');
const scoreEl=document.getElementById('score');
const timerEl=document.getElementById('timer');
const checkBtn=document.getElementById('checkBtn');
const tryNo=document.getElementById('tryNo');
let score=0,total=0,locked=false,finished=false,startTime=Date.now(),tries=0;

function esc(s){return String(s??'').replace(/[&<>"']/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function shuffle(a){return (a||[]).slice().sort(()=>Math.random()-.5);}
function setScore(){scoreEl.textContent=score+'/'+total;}
function incTry(){
 tries++;
 const max=parseInt(data.max_tries||2,10)||2;
 tryNo.textContent=tries+'/'+max;
}
function saveAttempt(status){
 if(total<=0)return;
 const fd=new FormData();
 fd.append('game_url',location.pathname);
 fd.append('score',score);
 fd.append('total',total);
 fd.append('percent',total>0?Math.round((score/total)*100):0);
 fd.append('time_sec',Math.floor((Date.now()-startTime)/1000));
 fd.append('status',status);
 fetch('/elms/student/game_score_save.php', {
    method: 'POST',
    body: fd
}).catch(() => {});
}
function finish(status='completed'){
 if(finished)return;
 finished=true;locked=true;checkBtn.disabled=true;
 saveAttempt(status);
 const percent=total>0?Math.round((score/total)*100):0;
 document.getElementById('overlay').style.display='flex';
 document.getElementById('modalTitle').textContent=status==='timeout'?'Time is Over':(percent>=50?'Congratulations! You Passed This Activity':'Activity Completed');
 document.getElementById('modalText').textContent='Score: '+score+'/'+total+' ('+percent+'%)';
}
function timer(){
 const duration=parseInt(data.duration||data.time||0,10)||0;
 if(duration<=0){timerEl.textContent='∞';return;}
 let t=duration;
 function fmt(x){return String(Math.floor(x/60)).padStart(2,'0')+':'+String(x%60).padStart(2,'0');}
 timerEl.textContent=fmt(t);
 const iv=setInterval(()=>{if(finished){clearInterval(iv);return;}t--;timerEl.textContent=fmt(t);if(t<=0){clearInterval(iv);if(typeof window.currentAutoCheck==='function')window.currentAutoCheck();finish('timeout');}},1000);
}
function getPairs(){return Array.isArray(data.pairs)?data.pairs:(Array.isArray(data.items)?data.items:[]);}
function renderMatch(){
 const pairs=getPairs().map(p=>({
   left:p.left||p.q||p.term||'',
   right:p.right||p.a||p.answer||''
 })).filter(p=>p.left&&p.right);

 total=pairs.length;
 setScore();

 const maxTries=parseInt(data.max_tries||2,10)||2;
 const rights=shuffle(pairs.map((p,i)=>({txt:p.right,idx:i})));
 let selectedLeft=null;
 let lines=[];

 app.innerHTML=
 '<div class="match-area" id="matchArea">'+
 '<svg class="match-svg" id="matchSvg"></svg>'+
 '<div class="match-col">'+pairs.map((p,i)=>'<div class="match-card left" data-i="'+i+'">'+esc(p.left)+'</div>').join('')+'</div>'+
 '<div class="match-col">'+rights.map(r=>'<div class="match-card right" data-i="'+r.idx+'">'+esc(r.txt)+'</div>').join('')+'</div>'+
 '</div>';

 const area=document.getElementById('matchArea');
 const svg=document.getElementById('matchSvg');

 function drawLines(){
   svg.innerHTML='';
   const ar=area.getBoundingClientRect();

   lines.forEach(l=>{
     const a=app.querySelector('.left[data-i="'+l.left+'"]');
     const b=app.querySelector('.right[data-i="'+l.right+'"]');
     if(!a||!b)return;

     const ra=a.getBoundingClientRect();
     const rb=b.getBoundingClientRect();

     const x1=ra.right-ar.left;
     const y1=ra.top+ra.height/2-ar.top;
     const x2=rb.left-ar.left;
     const y2=rb.top+rb.height/2-ar.top;

     const path=document.createElementNS('http://www.w3.org/2000/svg','path');
     const mid=(x1+x2)/2;
     path.setAttribute('d','M '+x1+' '+y1+' C '+mid+' '+y1+', '+mid+' '+y2+', '+x2+' '+y2);
     path.setAttribute('stroke','#0875d1');
     path.setAttribute('stroke-width','4');
     path.setAttribute('fill','none');
     path.setAttribute('stroke-linecap','round');
     svg.appendChild(path);
   });
 }

 app.querySelectorAll('.left').forEach(l=>{
   l.onclick=()=>{
     if(finished)return;
     selectedLeft=parseInt(l.dataset.i,10);
     app.querySelectorAll('.left').forEach(x=>x.classList.remove('sel'));
     l.classList.add('sel');
   };
 });

 app.querySelectorAll('.right').forEach(r=>{
   r.onclick=()=>{
     if(finished||selectedLeft===null)return;

     const ri=parseInt(r.dataset.i,10);
     lines=lines.filter(x=>x.left!==selectedLeft && x.right!==ri);
     lines.push({left:selectedLeft,right:ri});

     app.querySelectorAll('.left,.right').forEach(x=>x.classList.remove('sel'));
     selectedLeft=null;
     drawLines();
   };
 });

 function doCheck(){
   score=0;

   app.querySelectorAll('.match-card').forEach(x=>{
     x.classList.remove('ok','bad');
   });

   lines.forEach(l=>{
     const left=app.querySelector('.left[data-i="'+l.left+'"]');
     const right=app.querySelector('.right[data-i="'+l.right+'"]');

     if(l.left===l.right){
       score++;
       left?.classList.add('ok');
       right?.classList.add('ok');
     }else{
       left?.classList.add('bad');
       right?.classList.add('bad');
     }
   });

   setScore();
 }

 checkBtn.onclick=()=>{
   if(finished)return;
   incTry();
   doCheck();

   if(tries>=maxTries){
     finish('completed');
   }
 };

 window.currentAutoCheck=()=>{
   doCheck();
   finish('timeout');
 };

 window.addEventListener('resize',drawLines);
}
function renderTrueFalse(){
 const qs=(Array.isArray(data.questions)?data.questions:(Array.isArray(data.items)?data.items:[])).map(q=>({q:q.q||q.question||q.text||'',answer:String(q.answer??q.a??q.correct??'true').toLowerCase()})).filter(q=>q.q);
 total=qs.length;setScore();
 app.innerHTML='<div class="tf-box">'+qs.map((q,i)=>'<div class="tf-row"><b>'+esc(q.q)+'</b><br><button class="chip" data-i="'+i+'" data-v="true">True</button><button class="chip" data-i="'+i+'" data-v="false">False</button></div>').join('')+'</div>';
 app.querySelectorAll('.chip').forEach(b=>b.onclick=()=>{app.querySelectorAll('.chip[data-i="'+b.dataset.i+'"]').forEach(x=>x.classList.remove('sel'));b.classList.add('sel');});
 function doCheck(){if(locked)return;score=0;locked=true;qs.forEach((q,i)=>{const b=app.querySelector('.chip.sel[data-i="'+i+'"]');if(b&&b.dataset.v===q.answer)score++;});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function renderFill(){
 const text=String(data.text||data.passage||'');
 const answers=Array.isArray(data.answers)?data.answers:[];
 let idx=0;
 const html=esc(text).replace(/\\[\\[(.+?)\\]\\]|\\{\\{blank\\}\\}|___/g,(m,a)=>'<input class="blank" data-a="'+esc(a||answers[idx++]||'')+'">');
 total=(html.match(/class="blank"/g)||[]).length;setScore();
 app.innerHTML='<div class="fill-wrap">'+html+'</div>';
 function doCheck(){if(locked)return;score=0;locked=true;app.querySelectorAll('.blank').forEach(i=>{if(i.value.trim().toLowerCase()===i.dataset.a.toLowerCase()){score++;i.classList.add('ok');}else i.classList.add('bad');});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function renderWord(){
 const text=String(data.text||'');const answers=[];let idx=0;
 const html=esc(text).replace(/\\[\\[(.+?)\\]\\]/g,(_,a)=>{answers.push(a.trim());return '<span class="chip blankdrop" data-a="'+esc(a.trim())+'">______</span>';});
 total=answers.length;setScore();
 const bank=shuffle(Array.isArray(data.bank)&&data.bank.length?data.bank:answers);
 app.innerHTML='<div class="fill-wrap"><div style="width:68%;float:left">'+html+'</div><div class="word-bank"><div class="word-bank-title">These are the words to use</div>'+bank.map(w=>'<span class="bank">'+esc(w)+'</span>').join('')+'</div></div>';
 let selected='';
 app.querySelectorAll('.bank').forEach(b=>b.onclick=()=>{selected=b.textContent;app.querySelectorAll('.bank').forEach(x=>x.classList.remove('sel'));b.classList.add('sel');});
 app.querySelectorAll('.blankdrop').forEach(b=>b.onclick=()=>{if(selected){b.textContent=selected;b.dataset.g=selected;}});
 function doCheck(){if(locked)return;score=0;locked=true;app.querySelectorAll('.blankdrop').forEach(b=>{if((b.dataset.g||'').toLowerCase()===b.dataset.a.toLowerCase()){score++;b.classList.add('ok');}else b.classList.add('bad');});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function renderOrder(){
 const items=Array.isArray(data.items)?data.items:[];total=items.length;setScore();
 app.innerHTML='<div class="order-box" id="orderBox">'+shuffle(items).map(x=>'<div class="item" draggable="true">'+esc(x)+'</div>').join('')+'</div>';
 const box=document.getElementById('orderBox');let drag=null;
 box.addEventListener('dragstart',e=>drag=e.target);
 box.addEventListener('dragover',e=>{e.preventDefault();const it=e.target.closest('.item');if(!it||!drag)return;const r=it.getBoundingClientRect();box.insertBefore(drag,(e.clientY-r.top)>r.height/2?it.nextSibling:it);});
 function doCheck(){if(locked)return;score=0;locked=true;[...box.children].forEach((n,i)=>{if(n.textContent.trim()===String(items[i])){score++;n.classList.add('ok');}else n.classList.add('bad');});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function renderMemory(){
 const raw=Array.isArray(data.pairs)?data.pairs:(Array.isArray(data.items)?data.items:[]);
 const cards=shuffle(raw.flatMap((p,i)=>[{id:i,t:(p.left||p.q||p.term||p)},{id:i,t:(p.right||p.a||p.answer||p)}]));
 total=raw.length;setScore();checkBtn.style.display='none';
 let open=[],done={};
 app.innerHTML='<div class="mem">'+cards.map((c,i)=>'<div class="mem-card" data-i="'+i+'">?</div>').join('')+'</div>';
 app.querySelectorAll('.mem-card').forEach(card=>card.onclick=()=>{const i=parseInt(card.dataset.i,10);if(done[i]||open.includes(i)||finished)return;card.textContent=cards[i].t;open.push(i);if(open.length===2){incTry();const[a,b]=open;if(cards[a].id===cards[b].id){done[a]=done[b]=true;score++;setScore();app.querySelectorAll('.mem-card')[a].classList.add('done');app.querySelectorAll('.mem-card')[b].classList.add('done');if(score>=total)finish();}setTimeout(()=>{open=[];app.querySelectorAll('.mem-card').forEach((x,j)=>{if(!done[j])x.textContent='?';});},700);}});
 window.currentAutoCheck=()=>{};
}
function renderWordSearch(){
 const words=(Array.isArray(data.words)?data.words:[]).map(w=>String(w).toUpperCase().replace(/[^A-Z0-9]/g,'')).filter(Boolean);
 total=words.length;setScore();
 const size=Math.max(8,...words.map(w=>w.length));
 const grid=Array.from({length:size},()=>Array.from({length:size},()=>String.fromCharCode(65+Math.floor(Math.random()*26))));
 words.forEach((w,r)=>{if(r<size){for(let c=0;c<w.length&&c<size;c++)grid[r][c]=w[c];}});
 app.innerHTML='<div style="text-align:center;margin-bottom:14px">Find words: '+words.join(', ')+'</div><div class="ws" style="grid-template-columns:repeat('+size+',34px)">'+grid.flat().map(ch=>'<div class="ws-cell">'+ch+'</div>').join('')+'</div>';
 app.querySelectorAll('.ws-cell').forEach(c=>c.onclick=()=>c.classList.toggle('sel'));
 function doCheck(){if(locked)return;score=0;locked=true;const s=[...app.querySelectorAll('.ws-cell.sel')].map(x=>x.textContent).join('');words.forEach(w=>{if(s.includes(w))score++;});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function renderCrossword(){
 const entries=Array.isArray(data.entries)?data.entries:[];
 const clean=entries.map((e,i)=>({
   no:e.no||i+1,
   word:String(e.word||'').toUpperCase().replace(/[^A-Z0-9]/g,''),
   clue:e.clue||e.hint||e.word||'',
   dir:(e.dir||e.direction||'across').toLowerCase(),
   row:parseInt(e.row??e.y??i*2,10)||0,
   col:parseInt(e.col??e.x??0,10)||0
 })).filter(e=>e.word);

 let maxR=12,maxC=13;
 clean.forEach(e=>{
   if(e.dir==='down'){maxR=Math.max(maxR,e.row+e.word.length);maxC=Math.max(maxC,e.col+1);}
   else{maxR=Math.max(maxR,e.row+1);maxC=Math.max(maxC,e.col+e.word.length);}
 });

 const grid=Array.from({length:maxR},()=>Array.from({length:maxC},()=>null));
 clean.forEach(e=>{
   for(let i=0;i<e.word.length;i++){
     const r=e.row+(e.dir==='down'?i:0);
     const c=e.col+(e.dir==='across'?i:0);
     if(!grid[r][c]) grid[r][c]={ch:e.word[i],no:i===0?e.no:'',entries:[]};
     grid[r][c].ch=e.word[i];
     if(i===0) grid[r][c].no=e.no;
     grid[r][c].entries.push(e.no);
   }
 });

 total=clean.reduce((n,e)=>n+e.word.length,0);
 setScore();

 app.innerHTML=
 '<div class="cw-wrap"><div class="cw-board"><div class="cw" id="cwGrid" style="grid-template-columns:repeat('+maxC+',48px)">'+
 grid.map((row,r)=>row.map((cell,c)=>{
   if(!cell) return '<div class="cw-cell"></div>';
   return '<div class="cw-cell white" data-r="'+r+'" data-c="'+c+'" data-entries="'+cell.entries.join(',')+'">'+
     (cell.no?'<span class="cw-num">'+cell.no+'</span>':'')+
     '<input maxlength="1" data-a="'+esc(cell.ch)+'"></div>';
 }).join('')).join('')+
 '</div></div><div class="cw-panel">'+
 '<div class="cw-clue-no" id="cwNo">'+(clean[0]?.no||'')+'</div>'+
 '<div><span class="cw-clue-text" id="cwClue">'+esc(clean[0]?.clue||'')+'</span></div>'+
 '<div class="cw-help"><button type="button" id="showLetterBtn">Show Letter</button><button type="button" id="showWordBtn">Show Word</button></div>'+
 '</div></div>';

 const clueMap={};
 clean.forEach(e=>clueMap[e.no]=e.clue);

 let activeCell=app.querySelector('.cw-cell.white');
 let activeNo=clean[0]?.no||null;

 function selectClue(no){
   activeNo=no;
   document.getElementById('cwNo').textContent=no;
   document.getElementById('cwClue').textContent=clueMap[no]||'';
   app.querySelectorAll('.cw-cell.white').forEach(x=>{
     const list=(x.dataset.entries||'').split(',');
     x.classList.toggle('active',list.includes(String(no)));
   });
 }

 app.querySelectorAll('.cw-cell.white').forEach(cell=>{
   cell.onclick=()=>{
     activeCell=cell;
     const no=cell.dataset.entries.split(',')[0];
     selectClue(no);
     const inp=cell.querySelector('input');
     if(inp) inp.focus();
   };
 });

 document.getElementById('showLetterBtn').onclick=()=>{
   if(!activeCell) activeCell=app.querySelector('.cw-cell.white');
   const inp=activeCell?.querySelector('input');
   if(inp){inp.value=inp.dataset.a;inp.focus();}
 };

 document.getElementById('showWordBtn').onclick=()=>{
   const entry=clean.find(e=>String(e.no)===String(activeNo));
   if(!entry)return;
   for(let i=0;i<entry.word.length;i++){
     const r=entry.row+(entry.dir==='down'?i:0);
     const c=entry.col+(entry.dir==='across'?i:0);
     const inp=app.querySelector('.cw-cell.white[data-r="'+r+'"][data-c="'+c+'"] input');
     if(inp) inp.value=inp.dataset.a;
   }
 };

 function doCheck(){
   score=0;
   app.querySelectorAll('.cw input').forEach(i=>{
     i.classList.remove('ok','bad');
     if(i.value && i.value.toUpperCase()===i.dataset.a){score++;i.classList.add('ok');}
     else if(i.value){i.classList.add('bad');}
   });
   setScore();
 }

 const maxTries=parseInt(data.max_tries||data.tries||2,10)||2;

function doCheck(finalCheck=false){
 score=0;
 app.querySelectorAll('.cw input').forEach(i=>{
   i.classList.remove('ok','bad');

   if(i.value && i.value.toUpperCase()===i.dataset.a){
     score++;
     i.closest('.cw-cell')?.classList.remove('bad');
     i.closest('.cw-cell')?.classList.add('ok');
     i.classList.add('ok');
   }else if(i.value){
     i.closest('.cw-cell')?.classList.remove('ok');
     i.closest('.cw-cell')?.classList.add('bad');
     i.classList.add('bad');
   }
 });
 setScore();

 if(finalCheck || tries >= maxTries){
   finish('completed');
 }
}

checkBtn.onclick=()=>{
 if(finished)return;
 incTry();
 doCheck();

 const maxTries=parseInt(data.max_tries||2,10)||2;
 if(tries>=maxTries){
   finish('completed');
 }
};

window.currentAutoCheck=()=>{
 doCheck();
 finish('timeout');
};

selectClue(activeNo);
}
function renderHotspot(){
 const points=Array.isArray(data.points)?data.points:(Array.isArray(data.items)?data.items:[]);
 total=points.length;setScore();
 const img=data.image||data.image_url||data.bg||'/elms/assets/119.jpg';
 let html='<div class="hotspot-wrap"><div class="hotspot-stage"><img class="hotspot-img" src="'+esc(img)+'">';
 points.forEach((p,i)=>{html+='<button class="hotspot-point" data-i="'+i+'" style="left:'+(p.x||50)+'%;top:'+(p.y||50)+'%">'+(i+1)+'</button>';});
 html+='</div><div class="hotspot-panel"><h3>Select label, then click point</h3>'+shuffle(points.map((p,i)=>({i,label:p.label||p.answer||p.text||''}))).map(p=>'<button class="hotspot-answer" data-i="'+p.i+'">'+esc(p.label)+'</button>').join('')+'</div></div>';
 app.innerHTML=html;let selected=null;
 app.querySelectorAll('.hotspot-answer').forEach(b=>b.onclick=()=>{selected=b.dataset.i;app.querySelectorAll('.hotspot-answer').forEach(x=>x.classList.remove('sel'));b.classList.add('sel');});
 app.querySelectorAll('.hotspot-point').forEach(p=>p.onclick=()=>{if(selected===null||locked)return;p.dataset.answer=selected;p.classList.add('sel');});
 function doCheck(){if(locked)return;score=0;locked=true;app.querySelectorAll('.hotspot-point').forEach(p=>{if(p.dataset.answer===p.dataset.i){score++;p.classList.add('ok');}else p.classList.add('bad');});setScore();}
 checkBtn.onclick=()=>{incTry();doCheck();finish();};window.currentAutoCheck=doCheck;
}
function start(){
 startTime=Date.now();
 tryNo.textContent='0/'+(parseInt(data.max_tries||2,10)||2);
 startScreen.style.display='none';
 playScreen.style.display='block';
 const map={match:renderMatch,truefalse:renderTrueFalse,fill:renderFill,word:renderWord,order:renderOrder,wordsearch:renderWordSearch,memory:renderMemory,crossword:renderCrossword,image_hotspot:renderHotspot};
 (map[type]||renderMatch)();
 timer();
}
if (startBtn) {
    startBtn.addEventListener('click', function (e) {
        e.preventDefault();
        start();
    });
}
})();
</script>
</body>
</html>
HTML;
}

$action    = (string)($_GET['action'] ?? $_POST['action'] ?? '');
$course_id = (int)($_GET['course_id'] ?? $_POST['course_id'] ?? 0);
$module_id = (int)($_GET['module_id'] ?? $_POST['module_id'] ?? 0);
$CT = eg_content_table($mysqli);







if ($action === 'preview_game' || $action === 'save_game') {
    $session_id = (int)($_POST['session_id'] ?? 0);
$sidDb = $session_id > 0 ? $session_id : null;
    $title = trim((string)($_POST['title'] ?? 'Untitled Game'));
    $type = strtolower(trim((string)($_POST['game_type'] ?? 'match')));
    $duration = max(0, (int)($_POST['duration'] ?? 0));
    $payloadRaw = (string)($_POST['payload'] ?? '{}');
    $loadedUrl = trim((string)($_POST['loaded_url'] ?? ''));

    $allowed = ['match','truefalse','fill','word','order','wordsearch','memory','crossword','image_hotspot'];
    if (!in_array($type, $allowed, true)) $type = 'match';

    $data = json_decode($payloadRaw, true);
    if (!is_array($data)) $data = [];
    $data['duration'] = $duration;
    $html = eg_build_game_html($title ?: 'Untitled Game', $type, $data);

    if ($action === 'preview_game') {
        header('Content-Type: text/html; charset=utf-8');
        echo $html;
        exit;
    }

    if ($course_id <= 0 || $module_id <= 0) eg_json(['ok' => false, 'message' => 'Course and module are required.']);

    $root = realpath(dirname(__DIR__, 2));
    $gameDir = $root . '/uploads/games';
    if (!is_dir($gameDir) && !@mkdir($gameDir, 0775, true)) eg_json(['ok' => false, 'message' => 'Cannot create uploads/games folder.']);
    if (!is_writable($gameDir)) eg_json(['ok' => false, 'message' => 'uploads/games folder is not writable.']);

    $slug = '';
    if ($loadedUrl !== '') {
        $path = parse_url($loadedUrl, PHP_URL_PATH) ?: '';
        $slug = pathinfo($path, PATHINFO_FILENAME);
    }
    if ($slug === '') $slug = eg_slugify($title);
    $filePath = $gameDir . '/' . $slug . '.html';

    if (file_put_contents($filePath, $html) === false) eg_json(['ok' => false, 'message' => 'Failed to write game file.']);
    $gameUrl = '/elms/uploads/games/' . basename($filePath);
    
if (!empty($CT['t']) && !eg_col_exists($mysqli, $CT['t'], 'session_id')) {
    @$mysqli->query("ALTER TABLE `{$CT['t']}` ADD COLUMN session_id INT UNSIGNED NULL AFTER module_id");
}
if (!empty($CT['t'])) {
    $t = $CT['t'];
    $mid = $CT['mid'];
    $tt = $CT['title'];
    $uu = $CT['url'];

    $typeCol   = $CT['type'] ?? null;
    $descCol   = $CT['desc'] ?? null;
    $relCol    = $CT['rel'] ?? null;
    $sortCol   = $CT['sort'] ?? null;
    $courseCol = $CT['course'] ?? null;

    $existingId = 0;

    if ($loadedUrl !== '') {
        $st = $mysqli->prepare("SELECT id FROM `$t` WHERE `$uu`=? LIMIT 1");
        $st->bind_param('s', $loadedUrl);
        $st->execute();
        if ($r = $st->get_result()->fetch_assoc()) {
            $existingId = (int)$r['id'];
        }
        $st->close();
    }

    if ($existingId > 0) {
        $sets = "`$tt`=?, `$uu`=?";
        $types = 'ss';
        $vals = [$title, $gameUrl];

        if ($typeCol) {
            $sets .= ", `$typeCol`=?";
            $types .= 's';
            $vals[] = 'game';
        }

        if ($descCol) {
            $sets .= ", `$descCol`=?";
            $types .= 's';
            $vals[] = $type;
        }

        if ($relCol) {
            $sets .= ", `$relCol`=?";
            $types .= 'i';
            $vals[] = 1;
        }

        if (eg_col_exists($mysqli, $t, 'session_id')) {
            $sets .= ", `session_id`=?";
            $types .= 'i';
            $vals[] = $sidDb;
        }

        $types .= 'i';
        $vals[] = $existingId;

        $st = $mysqli->prepare("UPDATE `$t` SET $sets WHERE id=?");
        $st->bind_param($types, ...$vals);
        $st->execute();
        $st->close();

    } else {
        $cols = [
            $mid => $module_id,
            $tt  => $title,
            $uu  => $gameUrl,
        ];

        if ($courseCol) $cols[$courseCol] = $course_id;
        if ($typeCol)   $cols[$typeCol] = 'game';
        if ($descCol)   $cols[$descCol] = $type;
        if ($relCol)    $cols[$relCol] = 1;

        if (eg_col_exists($mysqli, $t, 'session_id')) {
            $cols['session_id'] = $sidDb;
        }

        if ($sortCol) {
            $ord = 1;
            $st = $mysqli->prepare("SELECT COALESCE(MAX(`$sortCol`),0)+1 AS n FROM `$t` WHERE `$mid`=?");
            $st->bind_param('i', $module_id);
            $st->execute();
            if ($r = $st->get_result()->fetch_assoc()) {
                $ord = (int)$r['n'];
            }
            $st->close();

            $cols[$sortCol] = $ord;
        }

        $fields = implode(',', array_map(fn($k) => "`$k`", array_keys($cols)));
        $place  = implode(',', array_fill(0, count($cols), '?'));

        $types = '';
        $vals = [];

        foreach ($cols as $v) {
            $types .= is_int($v) ? 'i' : 's';
            $vals[] = $v;
        }

        $st = $mysqli->prepare("INSERT INTO `$t` ($fields) VALUES ($place)");
        $st->bind_param($types, ...$vals);
        $st->execute();
        $st->close();
    }
  
}
eg_json(['ok' => true, 'url' => $gameUrl]);
}

if ($action === 'delete_game') {
    $url = trim((string)($_GET['url'] ?? ''));

    if ($url !== '' && !empty($CT['t'])) {
        $t = $CT['t']; $mid = $CT['mid']; $uu = $CT['url'];
        $st = $mysqli->prepare("DELETE FROM `$t` WHERE `$mid`=? AND `$uu`=? LIMIT 1");
        $st->bind_param('is', $module_id, $url);
        $st->execute();
        $st->close();

        $root = realpath(dirname(__DIR__, 2));
        $gamesDir = $root . '/uploads/games';
        $path = parse_url($url, PHP_URL_PATH) ?: '';
        $disk = $path && str_starts_with($path, '/elms/') ? $root . substr($path, strlen('/elms')) : $gamesDir . '/' . basename($path);
        $realDisk = realpath($disk);
        $realGames = realpath($gamesDir);
        if ($realDisk && $realGames && is_file($realDisk) && str_starts_with($realDisk, $realGames)) @unlink($realDisk);
    }

    header("Location: game_builder.php?course_id={$course_id}&module_id={$module_id}&deleted=1");
    exit;
}

if ($action === 'load_game') {
    $url = trim((string)($_GET['url'] ?? ''));
    $out = ['ok' => false];

    if ($url !== '') {
        $root = realpath(dirname(__DIR__, 2));
        $gamesDir = $root . '/uploads/games';
        $path = parse_url($url, PHP_URL_PATH) ?: '';
        $file = $path && str_starts_with($path, '/elms/') ? $root . substr($path, strlen('/elms')) : $gamesDir . '/' . basename($path);

        if (is_file($file)) {
            $html = (string)file_get_contents($file);
            $title = pathinfo($file, PATHINFO_FILENAME);
            $type = 'match';
            $data = [];

            if (preg_match('~<title>(.*?)</title>~si', $html, $m)) {
                $title = trim(strip_tags($m[1]));
                $title = preg_replace('~\s*·\s*EDEN CAMPUS\s*$~i', '', $title);
            }
            if (preg_match('~atob\("([^"]+)"\)~', $html, $m)) {
                $payload = json_decode(base64_decode($m[1]), true);
                if (is_array($payload)) {
                    $type = (string)($payload['type'] ?? 'match');
                    $data = is_array($payload['data'] ?? null) ? $payload['data'] : [];
                }
            }
            $out = ['ok' => true, 'title' => $title, 'type' => $type, 'data' => $data, 'url' => $url];
        }
    }
    eg_json($out);
}

$courses = eg_courses($mysqli);
$modules = $course_id > 0 ? eg_modules($mysqli, $course_id) : [];
[$courseLabel, $moduleLabel] = $course_id && $module_id ? eg_course_module_label($mysqli, $course_id, $module_id) : ['Select Course','Select Module'];

$games = [];
if (!empty($CT['t']) && $module_id > 0) {
    $t = $CT['t']; $mid = $CT['mid']; $tt = $CT['title']; $uu = $CT['url']; $typeCol = $CT['type'] ?? null;
    $where = "`$mid`=? AND `$uu` LIKE '%/uploads/games/%'";
    if ($typeCol) $where .= " AND (`$typeCol`='game' OR `$uu` LIKE '%/uploads/games/%')";
    $st = $mysqli->prepare("SELECT id, `$tt` AS title, `$uu` AS url FROM `$t` WHERE $where ORDER BY id DESC");
    $st->bind_param('i', $module_id);
    $st->execute();
    $rs = $st->get_result();
    while ($r = $rs->fetch_assoc()) $games[] = $r;
    $st->close();
}

$attempts = [];
$studentJoin = eg_table_exists($mysqli, 'studentss');
if ($studentJoin) {
    $nameCol = eg_first_col($mysqli, 'studentss', ['full_name','name','student_name']) ?? 'id';
    $regCol = eg_first_col($mysqli, 'studentss', ['center_reg_no','reference_no','reg_no','student_no']);
    $regSel = $regCol ? "s.`$regCol`" : "''";
    $sql = "
        SELECT ga.*, COALESCE(s.`$nameCol`, CONCAT('Student #', ga.student_id)) AS student_name, COALESCE($regSel,'') AS student_reg
        FROM lms_game_attempts ga
        LEFT JOIN studentss s ON s.id = ga.student_id
        ORDER BY ga.finished_at DESC, ga.id DESC
        LIMIT 300
    ";
} else {
    $sql = "SELECT ga.*, CONCAT('Student #', ga.student_id) AS student_name, '' AS student_reg FROM lms_game_attempts ga ORDER BY ga.finished_at DESC, ga.id DESC LIMIT 300";
}
$rs = $mysqli->query($sql);
while ($rs && $r = $rs->fetch_assoc()) $attempts[] = $r;

$PAGE_TITLE = 'Game Builder';
$ACTIVE_MENU = 'courses';

$__layoutOpened = false;
foreach (['admin_layout_top.php','layout_top.php','admin_header.php','header_admin.php'] as $f) {
    $p = __DIR__ . '/../../includes/' . $f;
    if (is_file($p)) { require_once $p; $__layoutOpened = true; break; }
}
if (!$__layoutOpened) echo '<div class="container-fluid p-3">';
?>

<style>
.gb-hero{background:linear-gradient(135deg,#0f172a,#2563eb);color:#fff;border-radius:20px;padding:20px 22px;box-shadow:0 14px 36px rgba(37,99,235,.18)}
.gb-hero h1{font-weight:900;font-size:23px;margin:0}
.gb-card{border:1px solid #e5e7eb;border-radius:18px;background:#fff;box-shadow:0 10px 28px rgba(15,23,42,.06);overflow:hidden}
.gb-head{background:#f8fafc;border-bottom:1px solid #e5e7eb;padding:14px 16px;display:flex;justify-content:space-between;gap:10px;align-items:center;flex-wrap:wrap}
.type-card{border:1px solid #dbe3ef;border-radius:16px;background:#fff;padding:14px;cursor:pointer;text-align:center;transition:.15s;min-height:94px;display:flex;flex-direction:column;align-items:center;justify-content:center}
.type-card:hover{transform:translateY(-2px);box-shadow:0 10px 22px rgba(15,23,42,.08)}
.type-card.active{border:2px solid #2563eb;background:#eff6ff}
.row-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:14px;padding:14px;margin-bottom:12px}
.small-help{font-size:.82rem;color:#64748b}
.sticky-actions{position:sticky;bottom:0;background:#fff;border-top:1px solid #e5e7eb;z-index:10}
</style>

<div class="gb-hero mb-3 d-flex justify-content-between align-items-center flex-wrap gap-3">
    <div>
        <h1>Game Builder</h1>
        <div class="small opacity-75"><?= eg_esc($courseLabel) ?> · <?= eg_esc($moduleLabel) ?></div>
    </div>
    <div class="d-flex gap-2 flex-wrap">
        <?php if ($course_id > 0 && $module_id > 0): ?>
            <button class="btn btn-light btn-sm fw-bold" id="btnNewGame">+ Create Game</button>
        <?php endif; ?>
        <a class="btn btn-outline-light btn-sm" href="content.php?course_id=<?= (int)$course_id ?>&module_id=<?= (int)$module_id ?>">← Content</a>
    </div>
</div>

<?php if (isset($_GET['deleted'])): ?><div class="alert alert-success py-2">Game deleted.</div><?php endif; ?>

<div class="gb-card mb-3">
    <div class="gb-head"><strong>Select Course & Module</strong></div>
    <div class="p-3">
        <form method="get" class="row g-3 align-items-end">
            <div class="col-md-6">
                <label class="form-label fw-bold">Course</label>
                <select name="course_id" class="form-select" onchange="this.form.module_id.value='';this.form.submit();">
                    <option value="">Choose course...</option>
                    <?php foreach ($courses as $c): ?>
                        <option value="<?= (int)$c['id'] ?>" <?= $course_id === (int)$c['id'] ? 'selected' : '' ?>><?= eg_esc($c['label']) ?></option>
                    <?php endforeach; ?>
                </select>
            </div>
            <div class="col-md-6">
                <label class="form-label fw-bold">Module</label>
                <select name="module_id" class="form-select" onchange="this.form.submit();">
                    <option value="">Choose module...</option>
                    <?php foreach ($modules as $m): ?>
                        <option value="<?= (int)$m['id'] ?>" <?= $module_id === (int)$m['id'] ? 'selected' : '' ?>>#<?= (int)$m['id'] ?> - <?= eg_esc($m['title']) ?></option>
                    <?php endforeach; ?>
                </select>
            </div>
        </form>
    </div>
</div>

<?php if ($course_id <= 0 || $module_id <= 0): ?>
    <div class="alert alert-info">Select a course and module to manage games.</div>
<?php else: ?>

<div class="gb-card mb-3">
    <div class="gb-head"><strong>Existing Games</strong><span class="badge bg-primary rounded-pill"><?= count($games) ?></span></div>
    <div class="p-3">
        <?php if (!$games): ?>
            <div class="text-muted">No games yet.</div>
        <?php else: ?>
            <div class="table-responsive">
                <table class="table align-middle">
                    <thead class="table-light"><tr><th>Title</th><th>URL</th><th class="text-end" style="width:280px;">Actions</th></tr></thead>
                    <tbody>
                    <?php foreach ($games as $g): ?>
                        <tr>
                            <td class="fw-bold"><?= eg_esc($g['title']) ?></td>
                            <td class="small text-muted"><?= eg_esc($g['url']) ?></td>
                            <td class="text-end">
                                <a class="btn btn-sm btn-outline-secondary" target="_blank" href="<?= eg_esc($g['url']) ?>">Open</a>
                                <button class="btn btn-sm btn-outline-primary btnEditGame" data-url="<?= eg_esc($g['url']) ?>">Edit</button>
                                <a class="btn btn-sm btn-outline-danger" onclick="return confirm('Delete this game?');" href="game_builder.php?action=delete_game&course_id=<?= (int)$course_id ?>&module_id=<?= (int)$module_id ?>&url=<?= urlencode((string)$g['url']) ?>">Delete</a>
                            </td>
                        </tr>
                    <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        <?php endif; ?>
    </div>
</div>

<div class="gb-card mb-4">
    <div class="gb-head"><strong>Game Attempts</strong><span class="badge bg-dark rounded-pill"><?= count($attempts) ?></span></div>
    <div class="p-3 table-responsive">
        <table class="table table-bordered table-hover align-middle">
            <thead class="table-light"><tr><th>ID</th><th>Student</th><th>Reg No</th><th>Score</th><th>Percent</th><th>Time</th><th>Status</th><th>Finished</th></tr></thead>
            <tbody>
            <?php if (!$attempts): ?>
                <tr><td colspan="8" class="text-center text-muted">No attempts yet.</td></tr>
            <?php else: ?>
                <?php foreach ($attempts as $r): ?>
                    <tr>
                        <td><?= (int)$r['id'] ?></td>
                        <td><?= eg_esc($r['student_name']) ?></td>
                        <td><?= eg_esc($r['student_reg']) ?></td>
                        <td><strong><?= (int)$r['score'] ?></strong> / <?= (int)$r['total'] ?></td>
                        <td><?= eg_esc($r['percent']) ?>%</td>
                        <td><?= (int)$r['time_sec'] ?>s</td>
                        <td><span class="badge bg-<?= ((int)$r['success'] === 1 ? 'success' : 'danger') ?>"><?= ((int)$r['success'] === 1 ? 'PASS' : 'FAIL') ?></span></td>
                        <td><?= eg_esc($r['finished_at']) ?></td>
                    </tr>
                <?php endforeach; ?>
            <?php endif; ?>
            </tbody>
        </table>
    </div>
</div>

<?php endif; ?>

<div class="modal fade" id="gameModal" tabindex="-1" aria-hidden="true">
    <div class="modal-dialog modal-xl modal-dialog-scrollable modal-dialog-centered">
        <div class="modal-content" style="border:0;border-radius:22px;overflow:hidden">
            <div class="modal-header bg-primary text-white">
                <h5 class="modal-title fw-bold" id="modalTitle">Create Game</h5>
                <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body bg-light">
                <form id="gameForm">
                    <input type="hidden" name="action" value="save_game">
                    <input type="hidden" name="course_id" value="<?= (int)$course_id ?>">
                    <input type="hidden" name="module_id" value="<?= (int)$module_id ?>">
                    <input type="hidden" name="loaded_url" id="loaded_url">
                    <input type="hidden" name="game_type" id="game_type" value="match">
                    <input type="hidden" name="payload" id="payload_post">

                    <div class="row g-3 mb-3">
                        <div class="col-md-3">
    <label class="form-label fw-bold">Session</label>
    <select class="form-select" name="session_id" id="session_id">
        <option value="0">Unassigned</option>
        <?php
        if (eg_table_exists($mysqli, 'lms_content_sessions')) {
            $st = $mysqli->prepare("
                SELECT id, title 
                FROM lms_content_sessions 
                WHERE course_id=? AND module_id=? 
                ORDER BY sort_order, id
            ");
            $st->bind_param('ii', $course_id, $module_id);
            $st->execute();
            $rs = $st->get_result();
            while ($s = $rs->fetch_assoc()):
        ?>
            <option value="<?= (int)$s['id'] ?>"><?= eg_esc($s['title']) ?></option>
        <?php
            endwhile;
            $st->close();
        }
        ?>
    </select>
</div>
                        <div class="col-md-5">
                            <label class="form-label fw-bold">Game Title</label>
                            <input class="form-control" name="title" id="title" required placeholder="Example: Computer Block Diagram">
                        </div>
                        <div class="col-md-2">
    <label class="form-label fw-bold">Timer Seconds</label>
    <input type="number" class="form-control" name="duration" id="duration" min="0" value="60">
</div>
<div class="col-md-2">
    <label class="form-label fw-bold">Max Tries</label>
    <input type="number" class="form-control" id="maxTries" min="1" value="2">
</div>
<div class="col-md-3">
                            <label class="form-label fw-bold">Image / Background URL</label>
                            <input class="form-control" id="bg" placeholder="/elms/uploads/games/computer.png">
                        </div>
                    </div>

                    <div class="row g-3 mb-3" id="typeGrid"></div>

                    <div class="bg-white border rounded-4 p-3 mb-3">
                        <div class="d-flex justify-content-between align-items-center mb-3">
                            <div class="fw-bold">Builder: <span class="badge bg-primary" id="typeBadge">MATCHING</span></div>
                            <button type="button" class="btn btn-sm btn-outline-secondary" id="loadSample">Load Sample</button>
                        </div>
                        <div id="builder"></div>
                    </div>

                    <details class="mb-3">
                        <summary class="fw-bold">Advanced JSON</summary>
                        <textarea id="payload_raw" class="form-control mt-2" rows="7"></textarea>
                    </details>

                    <div class="sticky-actions d-flex justify-content-end gap-2 py-3">
                        <button type="button" class="btn btn-outline-secondary" id="previewBtn">Preview</button>
                        <button type="submit" class="btn btn-primary px-4 fw-bold">Save Game</button>
                    </div>
                </form>
            </div>
        </div>
    </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
const samples = {
    match:{pairs:[{left:'CPU',right:'Central Processing Unit'},{left:'RAM',right:'Temporary Memory'},{left:'Hard Disk',right:'Storage Device'}]},
    truefalse:{questions:[{q:'CPU is known as the brain of the computer.',answer:true},{q:'RAM stores data permanently.',answer:false}]},
    fill:{text:'The [[CPU]] is the brain of the computer. [[RAM]] is temporary memory.'},
    word:{text:'A [[CPU]] processes data and [[RAM]] stores temporary data.',bank:['CPU','RAM','Monitor','Printer']},
    order:{items:['Input','Processing','Output','Storage']},
    wordsearch:{words:['CPU','RAM','MOUSE','KEYBOARD']},
    memory:{pairs:[{left:'CPU',right:'Processor'},{left:'RAM',right:'Memory'},{left:'HDD',right:'Storage'}]},
crossword:{
 max_tries:3,
 entries:[
  {no:1,word:'FLASH',clue:'Memory type used on pen drives',dir:'down',row:6,col:0},
  {no:2,word:'CPU',clue:'Brain of the computer',dir:'down',row:0,col:7},
  {no:8,word:'INPUT',clue:'Data entered into computer',dir:'across',row:1,col:0},
  {no:4,word:'RAM',clue:'Temporary memory',dir:'down',row:1,col:2},
  {no:5,word:'OUTPUT',clue:'Result produced by computer',dir:'across',row:1,col:6},
  {no:9,word:'USB',clue:'Port used for pen drives',dir:'down',row:3,col:11},
  {no:3,word:'MONITOR',clue:'Display device',dir:'across',row:4,col:5},
  {no:6,word:'MEMORY',clue:'Stores data and instructions',dir:'down',row:4,col:9},
  {no:11,word:'ROM',clue:'Read only memory',dir:'down',row:6,col:5},
  {no:17,word:'KEYBOARD',clue:'Typing input device',dir:'across',row:7,col:0},
  {no:10,word:'MOUSE',clue:'Pointing device',dir:'across',row:10,col:6}
 ]
},
image_hotspot:{
 image:'/elms/assets/119.jpg',
 points:[
  {x:50,y:18,label:'CPU'},
  {x:25,y:48,label:'Input Unit'},
  {x:75,y:48,label:'Output Unit'},
  {x:50,y:78,label:'Memory Unit'}
 ]
}
};
const types = {
    match:['🔗','Matching'],
    truefalse:['✅','True / False'],
    fill:['✏️','Fill Blanks'],
    word:['🧩','Word Bank'],
    order:['↕️','Line Up'],
    wordsearch:['🔎','Word Search'],
    memory:['🧠','Memory'],
    crossword:['🔳','Crossword'],
    image_hotspot:['🖼️','Image Hotspot']
};

const modal = new bootstrap.Modal(document.getElementById('gameModal'));
const form = document.getElementById('gameForm');
const typeGrid = document.getElementById('typeGrid');
const typeInput = document.getElementById('game_type');
const typeBadge = document.getElementById('typeBadge');
const builder = document.getElementById('builder');
const payloadPost = document.getElementById('payload_post');
const payloadRaw = document.getElementById('payload_raw');
const durationInput = document.getElementById('duration');
const maxTriesInput = document.getElementById('maxTries');
const bgInput = document.getElementById('bg');
let getData = () => ({});

function esc(s){return String(s ?? '').replace(/[&<>"']/g, m => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m]));}
function splitLines(v){return String(v||'').split(/\r?\n/).map(x=>x.trim()).filter(Boolean);}
function splitComma(v){return String(v||'').split(',').map(x=>x.trim()).filter(Boolean);}
function lines(v){return Array.isArray(v)?v.join('\n'):'';}
function field(label, html, help=''){return `<div class="mb-3"><label class="form-label fw-bold">${label}</label>${html}${help?`<div class="small-help">${help}</div>`:''}</div>`;}
function rowRemoveButton(){return `<button type="button" class="btn btn-sm btn-outline-danger remove-row">Remove</button>`;}
function bindRemove(){builder.querySelectorAll('.remove-row').forEach(btn=>btn.onclick=()=>{btn.closest('.row-card')?.remove();sync();});}
function setActive(type){
    document.querySelectorAll('.type-card').forEach(c=>c.classList.toggle('active',c.dataset.type===type));
    typeInput.value=type;
    typeBadge.textContent=(types[type]?.[1]||type).toUpperCase();
}
function buildTypeCards(){
    typeGrid.innerHTML='';
    Object.entries(types).forEach(([key,info])=>{
        const col=document.createElement('div');
        col.className='col-6 col-md-3 col-xl-2';
        col.innerHTML=`<div class="type-card" data-type="${key}"><div style="font-size:26px">${info[0]}</div><div class="fw-bold">${info[1]}</div></div>`;
        typeGrid.appendChild(col);
    });
    typeGrid.querySelectorAll('.type-card').forEach(card=>card.onclick=()=>render(card.dataset.type,samples[card.dataset.type]));
}
function render(type,data=null){
    data = data && Object.keys(data).length ? data : JSON.parse(JSON.stringify(samples[type] || samples.match));
    setActive(type);
    durationInput.value = data.duration || durationInput.value || 60;
    maxTriesInput.value = data.max_tries || maxTriesInput.value || 2;
    bgInput.value = data.bg || data.image || '';

    if(type==='match'){
        builder.innerHTML=`<div id="pairRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addPair">+ Add Pair</button>`;
        const box=document.getElementById('pairRows');
        function add(p={left:'',right:''}){const div=document.createElement('div');div.className='row-card';div.innerHTML=`<div class="row g-2"><div class="col-md-6"><label class="form-label fw-bold">Left Item</label><input class="form-control left" value="${esc(p.left)}"></div><div class="col-md-6"><label class="form-label fw-bold">Right Answer</label><input class="form-control right" value="${esc(p.right)}"></div></div><div class="mt-2">${rowRemoveButton()}</div>`;box.appendChild(div);bindRemove();sync();}
        (data.pairs||samples.match.pairs).forEach(add);
        document.getElementById('addPair').onclick=()=>add();
        getData=()=>({pairs:[...box.children].map(row=>({left:row.querySelector('.left').value.trim(),right:row.querySelector('.right').value.trim()})).filter(x=>x.left&&x.right)});
    } else if(type==='truefalse'){
        builder.innerHTML=`<div id="tfRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addTf">+ Add Statement</button>`;
        const box=document.getElementById('tfRows');
        function add(q={q:'',answer:true}){const div=document.createElement('div');div.className='row-card';div.innerHTML=`${field('Statement',`<input class="form-control q" value="${esc(q.q||q.question||'')}">`)}${field('Correct Answer',`<select class="form-select ans"><option value="true" ${String(q.answer)==='true'||q.answer===true?'selected':''}>True</option><option value="false" ${String(q.answer)==='false'||q.answer===false?'selected':''}>False</option></select>`)}${rowRemoveButton()}`;box.appendChild(div);bindRemove();sync();}
        (data.questions||samples.truefalse.questions).forEach(add);
        document.getElementById('addTf').onclick=()=>add();
        getData=()=>({questions:[...box.children].map(row=>({q:row.querySelector('.q').value.trim(),answer:row.querySelector('.ans').value==='true'})).filter(x=>x.q)});
    } else if(type==='fill'){
        builder.innerHTML=field('Paragraph',`<textarea id="fillText" class="form-control" rows="6">${esc(data.text||samples.fill.text)}</textarea>`,'Use [[answer]] for blanks.');
        getData=()=>({text:document.getElementById('fillText').value.trim()});
    } else if(type==='word'){
        builder.innerHTML=field('Paragraph',`<textarea id="wordText" class="form-control" rows="5">${esc(data.text||samples.word.text)}</textarea>`,'Use [[answer]] for blanks.')+field('Word Bank',`<input id="wordBank" class="form-control" value="${esc((data.bank||samples.word.bank).join(', '))}">`,'Comma separated words.');
        getData=()=>({text:document.getElementById('wordText').value.trim(),bank:splitComma(document.getElementById('wordBank').value)});
    } else if(type==='order'){
        builder.innerHTML=`<div id="orderRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addOrder">+ Add Item</button><div class="small-help mt-2">Add items in correct order.</div>`;
        const box=document.getElementById('orderRows');
        function add(v=''){const div=document.createElement('div');div.className='row-card';div.innerHTML=`<input class="form-control item" value="${esc(v)}"><div class="mt-2">${rowRemoveButton()}</div>`;box.appendChild(div);bindRemove();sync();}
        (data.items||samples.order.items).forEach(add);
        document.getElementById('addOrder').onclick=()=>add('');
        getData=()=>({items:[...box.querySelectorAll('.item')].map(i=>i.value.trim()).filter(Boolean)});
    } else if(type==='wordsearch'){
        builder.innerHTML=field('Words',`<textarea id="wsWords" class="form-control" rows="6">${esc(lines(data.words||samples.wordsearch.words))}</textarea>`,'One word per line.');
        getData=()=>({words:splitLines(document.getElementById('wsWords').value).map(x=>x.toUpperCase())});
    } else if(type==='memory'){
        builder.innerHTML=`<div id="memRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addMem">+ Add Pair</button>`;
        const box=document.getElementById('memRows');
        function add(p={left:'',right:''}){const div=document.createElement('div');div.className='row-card';div.innerHTML=`<div class="row g-2"><div class="col-md-6"><label class="form-label fw-bold">Card A</label><input class="form-control left" value="${esc(p.left||p)}"></div><div class="col-md-6"><label class="form-label fw-bold">Card B</label><input class="form-control right" value="${esc(p.right||p)}"></div></div><div class="mt-2">${rowRemoveButton()}</div>`;box.appendChild(div);bindRemove();sync();}
        (data.pairs||samples.memory.pairs).forEach(add);
        document.getElementById('addMem').onclick=()=>add();
        getData=()=>({pairs:[...box.children].map(row=>({left:row.querySelector('.left').value.trim(),right:row.querySelector('.right').value.trim()})).filter(x=>x.left&&x.right)});
    } else if(type==='crossword'){
    builder.innerHTML=`<div id="cwRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addCw">+ Add Word</button>
    <div class="small-help mt-2">Use row/col/dir to connect words like real crossword.</div>`;
    const box=document.getElementById('cwRows');

    function add(e={no:'',word:'',clue:'',dir:'across',row:0,col:0}){
        const div=document.createElement('div');
        div.className='row-card';
        div.innerHTML=`
        <div class="row g-2">
            <div class="col-md-1">
                <label class="form-label fw-bold">No</label>
                <input type="number" class="form-control no" value="${esc(e.no||'')}">
            </div>
            <div class="col-md-2">
                <label class="form-label fw-bold">Word</label>
                <input class="form-control word" value="${esc(e.word)}">
            </div>
            <div class="col-md-4">
                <label class="form-label fw-bold">Clue</label>
                <input class="form-control clue" value="${esc(e.clue)}">
            </div>
            <div class="col-md-2">
                <label class="form-label fw-bold">Direction</label>
                <select class="form-select dir">
                    <option value="across" ${e.dir==='across'?'selected':''}>Across</option>
                    <option value="down" ${e.dir==='down'?'selected':''}>Down</option>
                </select>
            </div>
            <div class="col-md-1">
                <label class="form-label fw-bold">Row</label>
                <input type="number" class="form-control rowx" value="${esc(e.row||0)}">
            </div>
            <div class="col-md-1">
                <label class="form-label fw-bold">Col</label>
                <input type="number" class="form-control colx" value="${esc(e.col||0)}">
            </div>
            <div class="col-md-1 d-flex align-items-end">
                ${rowRemoveButton()}
            </div>
        </div>`;
        box.appendChild(div);
        bindRemove();
        sync();
    }

    (data.entries||samples.crossword.entries).forEach(add);
    document.getElementById('addCw').onclick=()=>add();

    getData=()=>({
        entries:[...box.children].map(row=>({
            no:parseInt(row.querySelector('.no').value,10)||0,
            word:row.querySelector('.word').value.trim().toUpperCase().replace(/[^A-Z0-9]/g,''),
            clue:row.querySelector('.clue').value.trim(),
            dir:row.querySelector('.dir').value,
            row:parseInt(row.querySelector('.rowx').value,10)||0,
            col:parseInt(row.querySelector('.colx').value,10)||0
        })).filter(x=>x.word&&x.clue)
    });
    } else if(type==='image_hotspot'){
        builder.innerHTML=`${field('Image URL',`<input id="hotImage" class="form-control" value="${esc(data.image||data.bg||'/elms/assets/119.jpg')}">`,'Use uploaded computer block diagram image path.')}<div id="hotRows"></div><button type="button" class="btn btn-sm btn-outline-primary" id="addHot">+ Add Hotspot</button><div class="small-help mt-2">X/Y values are percentages on image.</div>`;
        const box=document.getElementById('hotRows');
        function add(p={x:50,y:50,label:''}){const div=document.createElement('div');div.className='row-card';div.innerHTML=`<div class="row g-2"><div class="col-md-2"><label class="form-label fw-bold">X %</label><input type="number" class="form-control x" value="${esc(p.x||50)}"></div><div class="col-md-2"><label class="form-label fw-bold">Y %</label><input type="number" class="form-control y" value="${esc(p.y||50)}"></div><div class="col-md-8"><label class="form-label fw-bold">Label</label><input class="form-control labelx" value="${esc(p.label||p.answer||p.text||'')}"></div></div><div class="mt-2">${rowRemoveButton()}</div>`;box.appendChild(div);bindRemove();sync();}
        (data.points||samples.image_hotspot.points).forEach(add);
        document.getElementById('addHot').onclick=()=>add();
        getData=()=>({image:document.getElementById('hotImage').value.trim(),points:[...box.children].map(row=>({x:parseFloat(row.querySelector('.x').value)||50,y:parseFloat(row.querySelector('.y').value)||50,label:row.querySelector('.labelx').value.trim()})).filter(x=>x.label)});
    }
    sync();
}
function buildPayload(){
    const data=getData()||{};
    data.duration=parseInt(durationInput.value||'0',10)||0;
    data.max_tries=parseInt(maxTriesInput.value||'2',10)||2;
    const bg=bgInput.value.trim();
    if(bg && typeInput.value !== 'image_hotspot') data.bg=bg;
    return data;
}
function sync(){
    const data=buildPayload();
    payloadPost.value=JSON.stringify(data);
    payloadRaw.value=JSON.stringify(data,null,2);
}
function validate(type,data){
    if(!document.getElementById('title').value.trim())return 'Please enter game title.';
    if(type==='match'&&(!data.pairs||data.pairs.length<2))return 'Matching game needs at least 2 pairs.';
    if(type==='truefalse'&&(!data.questions||data.questions.length<1))return 'True/False needs at least 1 statement.';
    if(type==='fill'&&(!data.text||!data.text.includes('[[')))return 'Fill Blanks needs [[answer]].';
    if(type==='word'&&(!data.text||!data.text.includes('[[')))return 'Word Bank needs [[answer]].';
    if(type==='order'&&(!data.items||data.items.length<2))return 'Line Up needs at least 2 items.';
    if(type==='memory'&&(!data.pairs||data.pairs.length<2))return 'Memory needs at least 2 pairs.';
    if(type==='crossword'&&(!data.entries||data.entries.length<2))return 'Crossword needs at least 2 words.';
    if(type==='image_hotspot'&&(!data.points||data.points.length<1))return 'Image Hotspot needs at least 1 hotspot.';
    return '';
}
document.addEventListener('input',e=>{
    if(builder.contains(e.target)||e.target===durationInput||e.target===maxTriesInput||e.target===bgInput)sync();
});
payloadRaw.addEventListener('input',()=>{try{payloadPost.value=JSON.stringify(JSON.parse(payloadRaw.value||'{}'));}catch(e){}});
document.getElementById('loadSample').onclick=()=>render(typeInput.value,samples[typeInput.value]);
document.getElementById('btnNewGame')?.addEventListener('click',()=>{form.reset();document.getElementById('loaded_url').value='';document.getElementById('modalTitle').textContent='Create Game';render('match',samples.match);modal.show();});
document.querySelectorAll('.btnEditGame').forEach(btn=>{btn.addEventListener('click',async()=>{const url=btn.dataset.url;const res=await fetch(`game_builder.php?action=load_game&url=${encodeURIComponent(url)}`);const j=await res.json();if(!j.ok){alert('Unable to load game.');return;}document.getElementById('modalTitle').textContent='Edit Game';document.getElementById('loaded_url').value=j.url||url;document.getElementById('title').value=j.title||'';document.getElementById('maxTries').value=(j.data&&j.data.max_tries)||2;document.getElementById('bg').value=(j.data&&j.data.bg)||(j.data&&j.data.image)||'';render(j.type || 'match', j.data || {});modal.show();});});
async function submitGame(preview=false){
    sync();
    let data={};
    try{data=JSON.parse(payloadPost.value||'{}');}catch(e){alert('Invalid JSON payload.');return;}
    const err=validate(typeInput.value,data);
    if(err){alert(err);return;}
    const fd=new FormData(form);
    fd.set('action',preview?'preview_game':'save_game');
    const btn=form.querySelector('button[type="submit"]');
    if(!preview){btn.disabled=true;btn.textContent='Saving...';}
    try{
        const res=await fetch('game_builder.php',{method:'POST',body:fd});
        if(preview){
            const html=await res.text();
            const w=window.open('','_blank');
            if(w){w.document.open();w.document.write(html);w.document.close();}
            return;
        }
        const j=await res.json();
        if(!j.ok){alert(j.message||'Save failed.');btn.disabled=false;btn.textContent='Save Game';return;}
        location.reload();
    }catch(e){
        alert('Save failed.');
        btn.disabled=false;
        btn.textContent='Save Game';
    }
}
document.getElementById('previewBtn').onclick=()=>submitGame(true);
form.addEventListener('submit',e=>{e.preventDefault();submitGame(false);});
buildTypeCards();
render('match', samples.match);
</script>

<?php
$__layoutClosed = false;
foreach (['admin_layout_bottom.php','layout_bottom.php','admin_footer.php','footer_admin.php'] as $f) {
    $p = __DIR__ . '/../../includes/' . $f;
    if (is_file($p)) { require_once $p; $__layoutClosed = true; break; }
}
if (!$__layoutClosed) echo '</div>';