<?php
declare(strict_types=1);

/**
 * EnglishSpeak - Student Runtime Router
 * File: student/student-runtime/index.php
 *
 * Chức năng:
 * - Nhận activity_id duy nhất.
 * - Kiểm tra Book -> Level -> Unit -> Lesson -> Activity.
 * - Cho phép giáo viên mở preview=1 mà không cần phiên học sinh.
 * - Chuyển Activity đến đúng game hiện có trong thư mục student.
 * - Giữ nguyên các tham số ngữ cảnh mà game hiện tại đang sử dụng.
 */

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

require_once __DIR__ . '/../../config/database.php';

$pdo = getDbConnection();

$activityId = max(
    0,
    (int)($_GET['activity_id'] ?? $_GET['activity'] ?? 0)
);
$lessonId = max(0, (int)($_GET['lesson_id'] ?? $_GET['lesson'] ?? 0));
$isPreview = (int)($_GET['preview'] ?? 0) === 1;
$runtimeAction = strtolower(trim((string)($_GET['runtime_action'] ?? '')));
$runtimeCurrentActivityId = max(0, (int)($_GET['current_activity_id'] ?? 0));
$runtimeMode = strtolower(trim((string)($_GET['runtime_mode'] ?? '')));

/*
 * Khi mở bằng lesson_id mà không chỉ định activity_id, đây là một lượt
 * chạy lại/toàn bộ Lesson. Luôn bắt đầu từ Activity đầu tiên và không dùng
 * trạng thái Queue cũ trong Session.
 */
$isLessonReplayEntry = $lessonId > 0 && $activityId <= 0;

if ($isLessonReplayEntry) {
    $runtimeMode = 'replay';

    unset(
        $_SESSION['runtime_activity_queue'],
        $_SESSION['runtime_current_index'],
        $_SESSION['runtime_current_activity_id'],
        $_SESSION['runtime_previous_activity_id'],
        $_SESSION['runtime_next_activity_id'],
        $_SESSION['runtime_is_last_activity']
    );
}

// Yêu cầu Next rõ ràng từ game: dùng Activity hiện tại để Runtime tự tính lại
// Activity kế tiếp theo thứ tự mới nhất trong database.
if ($runtimeAction === 'next' && $runtimeCurrentActivityId > 0) {
    $activityId = $runtimeCurrentActivityId;
}

/*
 * Runtime hỗ trợ hai điểm vào:
 * - activity_id: mở trực tiếp một Activity.
 * - lesson_id: tự tìm Activity đầu tiên đang bật theo thứ tự của Lesson.
 */
if ($activityId <= 0 && $lessonId <= 0) {
    http_response_code(400);
    exit('Thiếu activity_id hoặc lesson_id.');
}

/**
 * Trả về danh sách cột của một bảng, hoặc [] nếu bảng chưa tồn tại.
 */
function esrColumns(PDO $pdo, string $table): array
{
    $existsStmt = $pdo->prepare(
        "SELECT COUNT(*)
         FROM information_schema.TABLES
         WHERE TABLE_SCHEMA = DATABASE()
           AND TABLE_NAME = ?"
    );
    $existsStmt->execute([$table]);

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

    $rows = $pdo->query("SHOW COLUMNS FROM `{$table}`")->fetchAll(PDO::FETCH_ASSOC);

    return array_column($rows, null, 'Field');
}

/**
 * Chọn cột đầu tiên đang tồn tại.
 */
function esrFirstColumn(array $columns, array $candidates): ?string
{
    foreach ($candidates as $candidate) {
        if (isset($columns[$candidate])) {
            return $candidate;
        }
    }

    return null;
}

/**
 * Tạo URL tuyệt đối từ thư mục gốc EnglishSpeak.
 */
function esrProjectUrl(string $relativePath): string
{
    static $baseUrl = null;

    if ($baseUrl === null) {
        $projectRoot = str_replace(
            '\\',
            '/',
            (string)(realpath(dirname(__DIR__, 2)) ?: dirname(__DIR__, 2))
        );
        $documentRoot = str_replace(
            '\\',
            '/',
            (string)(realpath($_SERVER['DOCUMENT_ROOT'] ?? '') ?: '')
        );

        $baseUrl = '/EnglishSpeak';

        if (
            $documentRoot !== ''
            && str_starts_with($projectRoot . '/', rtrim($documentRoot, '/') . '/')
        ) {
            $detected = substr($projectRoot, strlen(rtrim($documentRoot, '/')));
            $baseUrl = '/' . trim(str_replace('\\', '/', $detected), '/');
        }
    }

    return rtrim($baseUrl, '/') . '/' . ltrim($relativePath, '/');
}

/* --------------------------------------------------------------------------
 | Quyền truy cập
 * -------------------------------------------------------------------------- */

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

    if (!$teacherLoggedIn) {
        $returnUrl = (string)($_SERVER['REQUEST_URI'] ?? '');
        $loginUrl = esrProjectUrl('teacher/login.php');

        if ($returnUrl !== '') {
            $loginUrl .= '?return=' . rawurlencode($returnUrl);
        }

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

/* --------------------------------------------------------------------------
 | Đọc cấu trúc dữ liệu hiện tại
 * -------------------------------------------------------------------------- */

$activityColumns = esrColumns($pdo, 'activities');
$lessonColumns = esrColumns($pdo, 'lessons');
$unitColumns = esrColumns($pdo, 'units');
$levelColumns = esrColumns($pdo, 'levels');
$bookColumns = esrColumns($pdo, 'books');

if (
    $activityColumns === []
    || $lessonColumns === []
    || $unitColumns === []
    || $levelColumns === []
    || $bookColumns === []
) {
    http_response_code(500);
    exit('Thiếu bảng dữ liệu học tập.');
}

$activityTitleColumn = esrFirstColumn($activityColumns, ['title', 'name']);
$activityTypeColumn = esrFirstColumn($activityColumns, ['activity_type', 'type']);
$activityStatusColumn = esrFirstColumn($activityColumns, ['status']);
$lessonStatusColumn = esrFirstColumn($lessonColumns, ['status']);
$unitStatusColumn = esrFirstColumn($unitColumns, ['status']);
$levelStatusColumn = esrFirstColumn($levelColumns, ['status']);
$bookStatusColumn = esrFirstColumn($bookColumns, ['status']);


/* --------------------------------------------------------------------------
 | Điều hướng Next an toàn: luôn tính lại Activity kế tiếp từ database
 * -------------------------------------------------------------------------- */

/*
 * Ưu tiên yêu cầu rõ ràng runtime_action=next&current_activity_id=...
 * để không phụ thuộc HTTP_REFERER và không dùng next_activity_id cũ.
 * Vẫn giữ cơ chế nhận diện cũ để tương thích với các game chưa cập nhật.
 */
$refererPath = (string)parse_url((string)($_SERVER['HTTP_REFERER'] ?? ''), PHP_URL_PATH);

/*
 * Khi học sinh bấm trực tiếp một Activity từ student-activities.php:
 * chỉ mở đúng Activity đó, không tiếp tục hàng đợi Runtime cũ.
 */
$isDirectActivityEntry = $activityId > 0
    && $runtimeAction === ''
    && str_contains(str_replace('\\', '/', $refererPath), '/student/student-activities.php');

if ($isDirectActivityEntry) {
    $runtimeMode = 'single';

    unset(
        $_SESSION['runtime_activity_queue'],
        $_SESSION['runtime_current_index'],
        $_SESSION['runtime_current_activity_id'],
        $_SESSION['runtime_previous_activity_id'],
        $_SESSION['runtime_next_activity_id'],
        $_SESSION['runtime_is_last_activity']
    );
}
$isExplicitRuntimeNext = $runtimeAction === 'next' && $runtimeCurrentActivityId > 0;
$isLegacyRuntimeNext = !$isExplicitRuntimeNext
    && $runtimeMode !== 'replay'
    && $runtimeMode !== 'single'
    && (string)($_SESSION['runtime_mode'] ?? '') !== 'replay'
    && (string)($_SESSION['runtime_mode'] ?? '') !== 'single'
    && $activityId > 0
    && !empty($_SESSION['runtime_current_activity_id'])
    && !empty($_SESSION['current_lesson_id'])
    && $refererPath !== ''
    && str_contains(str_replace('\\', '/', $refererPath), '/student/')
    && !str_contains(str_replace('\\', '/', $refererPath), '/student-runtime/');

if ($isExplicitRuntimeNext || $isLegacyRuntimeNext) {
    $runtimeLessonId = $lessonId > 0
        ? $lessonId
        : (int)($_SESSION['current_lesson_id'] ?? 0);
    $currentActivityForNext = $isExplicitRuntimeNext
        ? $runtimeCurrentActivityId
        : (int)($_SESSION['runtime_current_activity_id'] ?? 0);

    if ($runtimeLessonId <= 0 || $currentActivityForNext <= 0) {
        http_response_code(400);
        exit('Thiếu ngữ cảnh để chuyển Activity tiếp theo.');
    }

    $runtimeSortColumn = esrFirstColumn(
        $activityColumns,
        ['sort_order', 'display_order', 'position', 'order_index']
    );
    $runtimeOrderSql = $runtimeSortColumn !== null
        ? "`{$runtimeSortColumn}` ASC, id ASC"
        : 'id ASC';

    $runtimeWhere = ['lesson_id = ?'];
    $runtimeParams = [$runtimeLessonId];

    // Preview cũng chỉ chạy các Activity đang bật nếu bảng có cột status.
    // Nhờ vậy Activity bị Hide không xuất hiện trong hàng đợi xem thử.
    if ($activityStatusColumn !== null) {
        $runtimeWhere[] = "LOWER(TRIM(`{$activityStatusColumn}`)) IN ('active', 'published', 'ready')";
    }

    $runtimeQueueStmt = $pdo->prepare(
        'SELECT id
         FROM activities
         WHERE ' . implode(' AND ', $runtimeWhere) . '
         ORDER BY ' . $runtimeOrderSql
    );
    $runtimeQueueStmt->execute($runtimeParams);
    $runtimeQueueIds = array_map('intval', $runtimeQueueStmt->fetchAll(PDO::FETCH_COLUMN));

    $runtimeCurrentIndex = array_search(
        $currentActivityForNext,
        $runtimeQueueIds,
        true
    );

    if ($runtimeCurrentIndex === false) {
        http_response_code(409);
        exit('Activity hiện tại không còn nằm trong Lesson hoặc đã bị tắt.');
    }

    $freshNextActivityId = $runtimeQueueIds[$runtimeCurrentIndex + 1] ?? 0;

    if ($freshNextActivityId <= 0) {
        $_SESSION['runtime_is_last_activity'] = true;
        $_SESSION['runtime_next_activity_id'] = 0;

        $finishedQuery = [
            'lesson_id' => $runtimeLessonId,
        ];
        if ($isPreview) {
            $finishedQuery['preview'] = 1;
        }

        header(
            'Location: ' . esrProjectUrl('student/lesson-finished.php')
            . '?'
            . http_build_query($finishedQuery, '', '&', PHP_QUERY_RFC3986)
        );
        exit;
    }

    $activityId = $freshNextActivityId;
    $lessonId = $runtimeLessonId;
}

/* --------------------------------------------------------------------------
 | Nếu mở bằng lesson_id, chọn Activity đầu tiên trong hàng đợi của Lesson
 * -------------------------------------------------------------------------- */

if ($activityId <= 0 && $lessonId > 0) {
    $sortColumn = esrFirstColumn($activityColumns, ['sort_order', 'display_order', 'position', 'order_index']);
    $orderSql = $sortColumn !== null
        ? "`{$sortColumn}` ASC, id ASC"
        : 'id ASC';

    $where = ['lesson_id = ?'];
    $params = [$lessonId];

    if (!$isPreview && $activityStatusColumn !== null) {
        $where[] = "LOWER(TRIM(`{$activityStatusColumn}`)) IN ('active', 'published', 'ready')";
    }

    $firstActivityStmt = $pdo->prepare(
        'SELECT id
         FROM activities
         WHERE ' . implode(' AND ', $where) . '
         ORDER BY ' . $orderSql . '
         LIMIT 1'
    );
    $firstActivityStmt->execute($params);
    $activityId = (int)($firstActivityStmt->fetchColumn() ?: 0);

    if ($activityId <= 0) {
        http_response_code(404);
        exit('Lesson này chưa có Activity đang bật.');
    }
}

if ($activityTypeColumn === null) {
    http_response_code(500);
    exit('Bảng activities chưa có cột loại hoạt động.');
}

$activityTitleSql = $activityTitleColumn !== null
    ? "a.`{$activityTitleColumn}`"
    : "'Activity'";

$activityStatusSql = $activityStatusColumn !== null
    ? "a.`{$activityStatusColumn}`"
    : "'active'";
$lessonStatusSql = $lessonStatusColumn !== null
    ? "le.`{$lessonStatusColumn}`"
    : "'active'";
$unitStatusSql = $unitStatusColumn !== null
    ? "u.`{$unitStatusColumn}`"
    : "'active'";
$levelStatusSql = $levelStatusColumn !== null
    ? "lv.`{$levelStatusColumn}`"
    : "'active'";
$bookStatusSql = $bookStatusColumn !== null
    ? "b.`{$bookStatusColumn}`"
    : "'active'";

/*
 * Dự án hiện tại có thể liên kết Book qua levels.book_id hoặc units.book_id.
 * Ưu tiên levels.book_id vì các game học sinh hiện tại đang dùng cấu trúc này.
 */
if (isset($levelColumns['book_id'])) {
    $bookJoin = 'INNER JOIN books b ON b.id = lv.book_id';
} elseif (isset($unitColumns['book_id'])) {
    $bookJoin = 'INNER JOIN books b ON b.id = u.book_id';
} else {
    http_response_code(500);
    exit('Không xác định được liên kết Book của Unit/Level.');
}

$stmt = $pdo->prepare(
    "SELECT
        a.id AS activity_id,
        a.`{$activityTypeColumn}` AS activity_type,
        {$activityTitleSql} AS activity_title,
        {$activityStatusSql} AS activity_status,

        le.id AS lesson_id,
        le.lesson_number,
        le.title AS lesson_title,
        {$lessonStatusSql} AS lesson_status,

        u.id AS unit_id,
        u.unit_number,
        u.title AS unit_title,
        {$unitStatusSql} AS unit_status,

        lv.id AS level_id,
        lv.name AS level_name,
        lv.slug AS level_slug,
        {$levelStatusSql} AS level_status,

        b.id AS book_id,
        b.name AS book_name,
        b.slug AS book_slug,
        {$bookStatusSql} AS book_status
     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 lv ON lv.id = u.level_id
     {$bookJoin}
     WHERE a.id = ?
     LIMIT 1"
);
$stmt->execute([$activityId]);
$activity = $stmt->fetch(PDO::FETCH_ASSOC);

if (!$activity) {
    http_response_code(404);
    exit('Không tìm thấy hoạt động.');
}

/* Preview cho phép giáo viên xem cả nội dung đang chuẩn bị.
 * Học sinh chỉ được mở các tầng đang active.
 */
if (!$isPreview) {
    foreach (
        ['book_status', 'level_status', 'unit_status', 'lesson_status', 'activity_status']
        as $statusField
    ) {
        $status = strtolower(trim((string)($activity[$statusField] ?? 'active')));

        if ($status !== '' && !in_array($status, ['active', 'published', 'ready'], true)) {
            http_response_code(403);
            exit('Hoạt động này hiện chưa được mở.');
        }
    }
}

/* --------------------------------------------------------------------------
 | Chặn bypass Lesson Unlock ở Runtime
 | - Preview giáo viên/admin không bị ảnh hưởng.
 | - Học sinh không thể mở Activity của Lesson đang khóa bằng URL trực tiếp.
 | - Lesson đã có progress thì luôn được phép ôn lại, không relock.
 | - scheduled_date: chỉ mở khi đến unlock_date.
 | - sequential_daily: Lesson kế tiếp mở từ ngày sau khi Lesson trước hoàn thành.
 * -------------------------------------------------------------------------- */
if (!$isPreview) {
    $studentId = max(0, (int)($_SESSION['student_id'] ?? 0));
    $runtimeLessonId = (int)$activity['lesson_id'];

    $unlockMode = strtolower(trim((string)($lessonColumns['unlock_mode'] ?? null ? '' : '')));
    $lessonUnlockMode = '';
    $lessonUnlockDate = null;

    if (isset($lessonColumns['unlock_mode']) || isset($lessonColumns['unlock_date'])) {
        $unlockFields = ['id'];
        if (isset($lessonColumns['unlock_mode'])) {
            $unlockFields[] = 'unlock_mode';
        }
        if (isset($lessonColumns['unlock_date'])) {
            $unlockFields[] = 'unlock_date';
        }

        $unlockStmt = $pdo->prepare(
            'SELECT ' . implode(', ', $unlockFields) . '
             FROM lessons
             WHERE id = ?
             LIMIT 1'
        );
        $unlockStmt->execute([$runtimeLessonId]);

        if ($unlockRow = $unlockStmt->fetch(PDO::FETCH_ASSOC)) {
            $lessonUnlockMode = strtolower(trim((string)($unlockRow['unlock_mode'] ?? '')));
            $lessonUnlockDate = !empty($unlockRow['unlock_date'])
                ? (string)$unlockRow['unlock_date']
                : null;
        }
    }

    /*
     * Lịch mở riêng theo giáo viên/lớp/năm học.
     * Dùng năm học hiện tại của hệ thống, không dùng lớp tương lai trong students.
     */
    $currentAcademicYearId = 0;

    if (esrColumns($pdo, 'academic_years') !== []) {
        $currentYearStmt = $pdo->query(
            "SELECT id
             FROM academic_years
             WHERE is_current = 1
             ORDER BY id DESC
             LIMIT 1"
        );
        $currentAcademicYearId = (int)($currentYearStmt->fetchColumn() ?: 0);
    }

    if (
        $currentAcademicYearId > 0
        && esrColumns($pdo, 'teacher_lesson_schedules') !== []
    ) {
        $studentScheduleContext = null;

        if (esrColumns($pdo, 'student_class_history') !== []) {
            $historyStmt = $pdo->prepare(
                "SELECT academic_year_id, grade, class_name, school_id
                 FROM student_class_history
                 WHERE student_id = ?
                   AND academic_year_id = ?
                 ORDER BY recorded_at DESC, id DESC
                 LIMIT 1"
            );
            $historyStmt->execute([$studentId, $currentAcademicYearId]);
            $studentScheduleContext = $historyStmt->fetch(PDO::FETCH_ASSOC) ?: null;
        }

        if (!$studentScheduleContext && esrColumns($pdo, 'students') !== []) {
            $studentStmt = $pdo->prepare(
                "SELECT academic_year_id, grade, class_name, school_id
                 FROM students
                 WHERE id = ?
                   AND academic_year_id = ?
                 LIMIT 1"
            );
            $studentStmt->execute([$studentId, $currentAcademicYearId]);
            $studentScheduleContext = $studentStmt->fetch(PDO::FETCH_ASSOC) ?: null;
        }

        if (
            $studentScheduleContext
            && (int)($studentScheduleContext['school_id'] ?? 0) > 0
            && (int)($studentScheduleContext['grade'] ?? 0) > 0
            && trim((string)($studentScheduleContext['class_name'] ?? '')) !== ''
        ) {
            $scheduleStmt = $pdo->prepare(
                "SELECT unlock_mode, unlock_date
                 FROM teacher_lesson_schedules
                 WHERE school_id = ?
                   AND academic_year_id = ?
                   AND grade = ?
                   AND class_name = ?
                   AND lesson_id = ?
                 ORDER BY id DESC
                 LIMIT 1"
            );
            $scheduleStmt->execute([
                (int)$studentScheduleContext['school_id'],
                (int)$studentScheduleContext['academic_year_id'],
                (int)$studentScheduleContext['grade'],
                trim((string)$studentScheduleContext['class_name']),
                $runtimeLessonId,
            ]);

            if ($scheduleRow = $scheduleStmt->fetch(PDO::FETCH_ASSOC)) {
                $lessonUnlockMode = strtolower(trim(
                    (string)($scheduleRow['unlock_mode'] ?? $lessonUnlockMode)
                ));
                $lessonUnlockDate = !empty($scheduleRow['unlock_date'])
                    ? (string)$scheduleRow['unlock_date']
                    : null;
            }
        }
    }

    /*
     * Một Lesson đã bắt đầu/hoàn thành trước đây phải luôn mở để học sinh ôn lại.
     * Chỉ dùng progress hiện có; không ghi thêm XP/progress tại đây.
     */
    $lessonAlreadyOpened = false;
    $progressColumns = esrColumns($pdo, 'student_content_progress');

    if ($studentId > 0 && $progressColumns !== []) {
        $progressStudentCol = esrFirstColumn($progressColumns, ['student_id']);
        $progressTypeCol = esrFirstColumn($progressColumns, ['content_type']);
        $progressContentCol = esrFirstColumn($progressColumns, ['content_id']);
        $progressStatusCol = esrFirstColumn($progressColumns, ['status']);

        if ($progressStudentCol && $progressTypeCol && $progressContentCol) {
            /*
             * Chỉ coi Lesson là "đã từng mở" khi progress thể hiện học sinh
             * thực sự đã bắt đầu/học xong.
             *
             * Record status = not_started KHÔNG được phép bypass Lesson Unlock.
             */
            $lessonOpenedStatusWhere = $progressStatusCol
                ? "AND LOWER(TRIM(`{$progressStatusCol}`)) IN
                    ('in_progress','in-progress','started','completed','complete','passed')"
                : '';

            $openedStmt = $pdo->prepare(
                "SELECT COUNT(*)
                 FROM student_content_progress
                 WHERE `{$progressStudentCol}` = ?
                   AND LOWER(TRIM(`{$progressTypeCol}`)) = 'lesson'
                   AND `{$progressContentCol}` = ?
                   {$lessonOpenedStatusWhere}"
            );
            $openedStmt->execute([$studentId, $runtimeLessonId]);
            $lessonAlreadyOpened = (int)$openedStmt->fetchColumn() > 0;

            /*
             * Nếu chưa có progress cấp Lesson, chỉ Activity progress đã thật sự
             * bắt đầu/hoàn thành mới chứng minh Lesson từng được mở.
             */
            if (!$lessonAlreadyOpened) {
                $activityOpenedStatusWhere = $progressStatusCol
                    ? "AND LOWER(TRIM(p.`{$progressStatusCol}`)) IN
                        ('in_progress','in-progress','started','completed','complete','passed')"
                    : '';

                $openedStmt = $pdo->prepare(
                    "SELECT COUNT(*)
                     FROM student_content_progress p
                     INNER JOIN activities aa ON aa.id = p.`{$progressContentCol}`
                     WHERE p.`{$progressStudentCol}` = ?
                       AND LOWER(TRIM(p.`{$progressTypeCol}`)) = 'activity'
                       AND aa.lesson_id = ?
                       {$activityOpenedStatusWhere}"
                );
                $openedStmt->execute([$studentId, $runtimeLessonId]);
                $lessonAlreadyOpened = (int)$openedStmt->fetchColumn() > 0;
            }
        }
    }

    $lessonLocked = false;

    if (!$lessonAlreadyOpened && $lessonUnlockMode === 'scheduled_date' && $lessonUnlockDate) {
        $today = new DateTimeImmutable('today');
        $openDate = DateTimeImmutable::createFromFormat('!Y-m-d', $lessonUnlockDate);

        if ($openDate instanceof DateTimeImmutable && $today < $openDate) {
            $lessonLocked = true;
        }
    }

    if (!$lessonAlreadyOpened && $lessonUnlockMode === 'sequential_daily') {
        $lessonSortColumn = esrFirstColumn(
            $lessonColumns,
            ['sort_order', 'lesson_number', 'display_order', 'position', 'order_index']
        );
        $lessonOrderSql = $lessonSortColumn !== null
            ? "`{$lessonSortColumn}` ASC, id ASC"
            : 'id ASC';

        $lessonListWhere = ['unit_id = ?'];
        $lessonListParams = [(int)$activity['unit_id']];

        if ($lessonStatusColumn !== null) {
            $lessonListWhere[] =
                "LOWER(TRIM(`{$lessonStatusColumn}`)) IN ('active', 'published', 'ready')";
        }

        $lessonListStmt = $pdo->prepare(
            'SELECT id
             FROM lessons
             WHERE ' . implode(' AND ', $lessonListWhere) . '
             ORDER BY ' . $lessonOrderSql
        );
        $lessonListStmt->execute($lessonListParams);
        $lessonIds = array_map('intval', $lessonListStmt->fetchAll(PDO::FETCH_COLUMN));

        $lessonIndex = array_search($runtimeLessonId, $lessonIds, true);

        if ($lessonIndex !== false && $lessonIndex > 0) {
            $previousLessonId = (int)$lessonIds[$lessonIndex - 1];
            $previousCompletedAt = null;

            if ($studentId > 0 && $progressColumns !== []) {
                $progressStudentCol = esrFirstColumn($progressColumns, ['student_id']);
                $progressTypeCol = esrFirstColumn($progressColumns, ['content_type']);
                $progressContentCol = esrFirstColumn($progressColumns, ['content_id']);
                $progressStatusCol = esrFirstColumn($progressColumns, ['status']);
                $progressCompletedCol = esrFirstColumn($progressColumns, ['completed_at']);

                /*
                 * Trước hết tìm completion cấp Lesson.
                 */
                if ($progressStudentCol && $progressTypeCol && $progressContentCol) {
                    $completedSelect = $progressCompletedCol
                        ? "`{$progressCompletedCol}`"
                        : 'NULL';

                    $completedWhere = [
                        "`{$progressStudentCol}` = ?",
                        "LOWER(TRIM(`{$progressTypeCol}`)) = 'lesson'",
                        "`{$progressContentCol}` = ?",
                    ];

                    if ($progressStatusCol) {
                        $completedWhere[] =
                            "LOWER(TRIM(`{$progressStatusCol}`)) IN ('completed', 'complete', 'passed')";
                    }

                    $previousStmt = $pdo->prepare(
                        "SELECT {$completedSelect}
                         FROM student_content_progress
                         WHERE " . implode(' AND ', $completedWhere) . "
                         ORDER BY " .
                         ($progressCompletedCol ? "`{$progressCompletedCol}` DESC" : "`{$progressContentCol}` DESC") .
                        " LIMIT 1"
                    );
                    $previousStmt->execute([$studentId, $previousLessonId]);
                    $value = $previousStmt->fetchColumn();

                    if ($value !== false) {
                        $previousCompletedAt = $value ?: null;
                    }
                }
            }

            /*
             * Nếu hệ thống không lưu progress cấp Lesson, suy ra thời điểm Lesson
             * trước hoàn thành từ tất cả Activity đang bật của Lesson đó.
             */
            if ($previousCompletedAt === null && $studentId > 0 && $progressColumns !== []) {
                $progressStudentCol = esrFirstColumn($progressColumns, ['student_id']);
                $progressTypeCol = esrFirstColumn($progressColumns, ['content_type']);
                $progressContentCol = esrFirstColumn($progressColumns, ['content_id']);
                $progressStatusCol = esrFirstColumn($progressColumns, ['status']);
                $progressCompletedCol = esrFirstColumn($progressColumns, ['completed_at']);

                if (
                    $progressStudentCol
                    && $progressTypeCol
                    && $progressContentCol
                    && $progressCompletedCol
                ) {
                    $activityWhere = ['lesson_id = ?'];
                    $activityParams = [$previousLessonId];

                    if ($activityStatusColumn !== null) {
                        $activityWhere[] =
                            "LOWER(TRIM(`{$activityStatusColumn}`)) IN ('active', 'published', 'ready')";
                    }

                    $previousActivitiesStmt = $pdo->prepare(
                        'SELECT id
                         FROM activities
                         WHERE ' . implode(' AND ', $activityWhere)
                    );
                    $previousActivitiesStmt->execute($activityParams);
                    $previousActivityIds = array_map(
                        'intval',
                        $previousActivitiesStmt->fetchAll(PDO::FETCH_COLUMN)
                    );

                    if ($previousActivityIds !== []) {
                        $placeholders = implode(',', array_fill(0, count($previousActivityIds), '?'));

                        $completedActivityWhere = [
                            "p.`{$progressStudentCol}` = ?",
                            "LOWER(TRIM(p.`{$progressTypeCol}`)) = 'activity'",
                            "p.`{$progressContentCol}` IN ({$placeholders})",
                        ];

                        if ($progressStatusCol) {
                            $completedActivityWhere[] =
                                "LOWER(TRIM(p.`{$progressStatusCol}`)) IN ('completed', 'complete', 'passed')";
                        }

                        $activityCompletionStmt = $pdo->prepare(
                            "SELECT
                                COUNT(DISTINCT p.`{$progressContentCol}`) AS completed_count,
                                MAX(p.`{$progressCompletedCol}`) AS completed_at
                             FROM student_content_progress p
                             WHERE " . implode(' AND ', $completedActivityWhere)
                        );

                        $activityCompletionStmt->execute(
                            array_merge([$studentId], $previousActivityIds)
                        );
                        $completionRow = $activityCompletionStmt->fetch(PDO::FETCH_ASSOC);

                        if (
                            (int)($completionRow['completed_count'] ?? 0)
                            >= count($previousActivityIds)
                        ) {
                            $previousCompletedAt =
                                (string)($completionRow['completed_at'] ?? '') ?: null;
                        }
                    }
                }
            }

            if ($previousCompletedAt === null) {
                $lessonLocked = true;
            } else {
                try {
                    $completedDate = new DateTimeImmutable($previousCompletedAt);
                    $openDate = $completedDate
                        ->setTime(0, 0, 0)
                        ->modify('+1 day');
                    $today = new DateTimeImmutable('today');

                    if ($today < $openDate) {
                        $lessonLocked = true;
                    }
                } catch (Throwable $e) {
                    $lessonLocked = true;
                }
            }
        }
    }

    if ($lessonLocked) {
        /*
         * Không để queue/session cũ tiếp tục đẩy học sinh vào Lesson bị khóa.
         */
        unset(
            $_SESSION['runtime_activity_queue'],
            $_SESSION['runtime_current_index'],
            $_SESSION['runtime_current_activity_id'],
            $_SESSION['runtime_previous_activity_id'],
            $_SESSION['runtime_next_activity_id'],
            $_SESSION['runtime_is_last_activity']
        );

        $backQuery = [
            'book' => (string)$activity['book_slug'],
            'level' => (string)$activity['level_slug'],
            'level_id' => (int)$activity['level_id'],
            'unit_id' => (int)$activity['unit_id'],
            'id' => (int)$activity['unit_id'],
        ];

        header(
            'Location: ' . esrProjectUrl('student/student-lessons.php')
            . '?'
            . http_build_query($backQuery, '', '&', PHP_QUERY_RFC3986)
        );
        exit;
    }
}

/* --------------------------------------------------------------------------
 | Tạo Activity Queue của Lesson
 * -------------------------------------------------------------------------- */

$sortColumn = esrFirstColumn(
    $activityColumns,
    ['sort_order', 'display_order', 'position', 'order_index']
);
$queueOrderSql = $sortColumn !== null
    ? "`{$sortColumn}` ASC, id ASC"
    : 'id ASC';

$queueWhere = ['lesson_id = ?'];
$queueParams = [(int)$activity['lesson_id']];

if (!$isPreview && $activityStatusColumn !== null) {
    $queueWhere[] = "LOWER(TRIM(`{$activityStatusColumn}`)) IN ('active', 'published', 'ready')";
}

$queueTitleSql = $activityTitleColumn !== null
    ? "`{$activityTitleColumn}`"
    : "'Activity'";
$queueStatusSql = $activityStatusColumn !== null
    ? "`{$activityStatusColumn}`"
    : "'active'";

$queueStmt = $pdo->prepare(
    "SELECT
        id,
        `{$activityTypeColumn}` AS activity_type,
        {$queueTitleSql} AS activity_title,
        {$queueStatusSql} AS activity_status"
        . ($sortColumn !== null ? ", `{$sortColumn}` AS sort_order" : ', id AS sort_order') . "
     FROM activities
     WHERE " . implode(' AND ', $queueWhere) . "
     ORDER BY {$queueOrderSql}"
);
$queueStmt->execute($queueParams);
$activityQueue = $queueStmt->fetchAll(PDO::FETCH_ASSOC);

$currentIndex = null;
foreach ($activityQueue as $index => $queueItem) {
    if ((int)$queueItem['id'] === (int)$activity['activity_id']) {
        $currentIndex = $index;
        break;
    }
}

if ($currentIndex === null) {
    http_response_code(409);
    exit('Activity hiện tại không nằm trong hàng đợi của Lesson.');
}

$nextActivity = $activityQueue[$currentIndex + 1] ?? null;
$previousActivity = $activityQueue[$currentIndex - 1] ?? null;

/* --------------------------------------------------------------------------
 | Chọn đúng Game Template
 * -------------------------------------------------------------------------- */

$activityType = strtolower(trim((string)$activity['activity_type']));
$activityType = str_replace(['-', ' '], '_', $activityType);

// Có thể dùng config/game_map.php nếu tồn tại
$externalMap=dirname(__DIR__).'/../config/game_map.php';
if(is_file($externalMap)){ $gameMap=require $externalMap; } else {
$gameMap = [
    'vocabulary' => 'student-word-list.php',
    'word_list' => 'student-word-list.php',
    'wordlist' => 'student-word-list.php',

    'flashcard' => 'student-flashcards.php',
    'flashcards' => 'student-flashcards.php',

    'listen_repeat' => 'student-listen-repeat.php',
    'listen_and_repeat' => 'student-listen-repeat.php',
    'listening' => 'student-listen-repeat.php',
    'pronunciation' => 'student-listen-repeat.php',

    'memory' => 'student-memory.php',
    'memory_game' => 'student-memory.php',

    'match' => 'student-matching.php',
    'matching' => 'student-matching.php',
    'matching_game' => 'student-matching.php',

    'quiz' => 'student-quiz.php',
    'quiz_game' => 'student-quiz.php',

    // Speaking Sentences
    'speaking'               => 'sentence-listen-repeat.php',
    'sentence_speaking'      => 'sentence-listen-repeat.php',
    'speaking_sentences'     => 'sentence-listen-repeat.php',
    'sentence_listen_repeat' => 'sentence-listen-repeat.php',
    'sentence_repeat'        => 'sentence-listen-repeat.php',
    'speaking_copy'          => 'sentence-listen-repeat.php',

    // Find the Word
    'find_word'              => 'student-find-word.php',
    'find_the_word'          => 'student-find-word.php',

    // Fix the Word
    'fix_word'               => 'student-fix-word.php',
    'fix_the_word'           => 'student-fix-word.php',

    // Word Builder
    'word_builder'           => 'student-word-builder.php',
    'build_word'             => 'student-word-builder.php',
    'word_building'          => 'student-word-builder.php',

    // Guess the Word
    'guess_the_word'         => 'student-guess-the-word.php',
    'guess_word'             => 'student-guess-the-word.php',

    // Word Unscramble
    'word_unscramble'        => 'student-word-unscramble.php',
    'unscramble'             => 'student-word-unscramble.php',
    'word_scramble'          => 'student-word-unscramble.php',
    'scramble_word'          => 'student-word-unscramble.php',

    // Sentence Builder
    'sentence_builder'       => 'student-sentence-builder.php',
    'build_sentence'         => 'student-sentence-builder.php',
    'sentence_building'      => 'student-sentence-builder.php',

    // Dialogue / Ask & Answer
    'dialogue'               => 'student-dialogue.php',
    'ask_answer'             => 'student-dialogue-ask-answer.php',
    'ask_and_answer'         => 'student-dialogue-ask-answer.php',

    'practice' => 'student-practice.php',
];
}

$gameFile = $gameMap[$activityType] ?? '';

if ($gameFile === '') {
    http_response_code(422);
    exit('Loại hoạt động này chưa có Student Game Template.');
}

$absoluteGamePath = dirname(__DIR__) . '/' . $gameFile;

if (!is_file($absoluteGamePath)) {
    http_response_code(404);
    exit('Không tìm thấy file game: ' . htmlspecialchars($gameFile, ENT_QUOTES, 'UTF-8'));
}

/* --------------------------------------------------------------------------
 | Truyền ngữ cảnh cho các game hiện tại
 * -------------------------------------------------------------------------- */

$query = [
    'book' => (string)$activity['book_slug'],
    'level' => (string)$activity['level_slug'],
    'level_id' => (int)$activity['level_id'],
    'unit_id' => (int)$activity['unit_id'],
    'id' => (int)$activity['unit_id'],
    'lesson_id' => (int)$activity['lesson_id'],
    'lesson' => (string)$activity['lesson_number'],
    'activity_id' => (int)$activity['activity_id'],
    'activity' => (int)$activity['activity_id'],
];

if ($isPreview) {
    $query['preview'] = 1;
}

// Load Activity Settings
$_SESSION['activity_settings'] = [];
$_SESSION['activity_settings_data'] = [];

unset(
    $_SESSION['current_flashcard_set_id'],
    $_SESSION['current_sentence_set_id'],
    $_SESSION['current_quiz_set_id']
);

if (!empty(esrColumns($pdo, 'activity_settings'))) {
    $st = $pdo->prepare(
        'SELECT * FROM activity_settings WHERE activity_id = ? LIMIT 1'
    );
    $st->execute([(int)$activity['activity_id']]);

    if ($row = $st->fetch(PDO::FETCH_ASSOC)) {
        $decoded = [];

        if (!empty($row['settings_json'])) {
            $decodedJson = json_decode((string)$row['settings_json'], true);

            if (is_array($decodedJson)) {
                $decoded = $decodedJson;
            }
        }

        $row['settings'] = $decoded;

        // Keep both formats for compatibility with existing and new games.
        $_SESSION['activity_settings'] = $row;
        $_SESSION['activity_settings_data'] = $decoded;

        $resourceType = strtolower(trim((string)($decoded['resource_type'] ?? '')));
        $resourceId = max(0, (int)($decoded['resource_id'] ?? 0));

        if ($resourceId > 0) {
            switch ($resourceType) {
                case 'flashcard_set':
                    $_SESSION['current_flashcard_set_id'] = $resourceId;
                    $query['flashcard_set_id'] = $resourceId;
                    $query['word_set_id'] = $resourceId;
                    break;

                case 'sentence_set':
                    $_SESSION['current_sentence_set_id'] = $resourceId;
                    $query['sentence_set_id'] = $resourceId;
                    break;

                case 'quiz_set':
                    $_SESSION['current_quiz_set_id'] = $resourceId;
                    $query['quiz_set_id'] = $resourceId;
                    break;
            }
        }

        // Pass the most-used settings explicitly for older game templates.
        $runtimeSettingKeys = [
            'cards_per_session',
            'sentence_count',
            'word_count',
            'pairs',
            'passing_score',
            'unlimited_practice',
            'attempts',
            'show_image',
            'show_ipa',
            'show_meaning',
            'show_listen_button',
            'show_listen',
            'auto_play',
            'auto_next',
            'allow_replay',
            'allow_retry',
            'audio_source',
            'pronunciation_focus',
            'xp_reward',
            'practice_xp',
        ];

        foreach ($runtimeSettingKeys as $settingKey) {
            if (array_key_exists($settingKey, $decoded)) {
                $_SESSION['runtime_setting_' . $settingKey] = $decoded[$settingKey];
            }
        }
    }
}

// Runtime hook
$_SESSION['runtime_started_at']=time();

$_SESSION['current_activity_id'] = (int)$activity['activity_id'];
$_SESSION['current_activity_type'] = $activityType;
$_SESSION['current_activity_preview'] = $isPreview;
$_SESSION['current_lesson_id'] = (int)$activity['lesson_id'];
$_SESSION['current_unit_id'] = (int)$activity['unit_id'];
$_SESSION['current_level_id'] = (int)$activity['level_id'];
$_SESSION['current_book_id'] = (int)$activity['book_id'];
$_SESSION['runtime_mode'] = $runtimeMode !== ''
    ? $runtimeMode
    : (string)($_SESSION['runtime_mode'] ?? 'lesson');

if ($runtimeMode === 'single') {
    /*
     * Mở riêng một Activity từ danh sách Lesson:
     * không tạo queue, không truyền next_activity_id, không bật runtime=1.
     */
    unset(
        $_SESSION['runtime_activity_queue'],
        $_SESSION['runtime_current_index'],
        $_SESSION['runtime_previous_activity_id'],
        $_SESSION['runtime_next_activity_id'],
        $_SESSION['runtime_is_last_activity']
    );

    $_SESSION['runtime_current_activity_id'] = (int)$activity['activity_id'];
    $_SESSION['runtime_mode'] = 'single';

    $query['runtime'] = 0;
    $query['runtime_mode'] = 'single';
} else {
    // Lưu hàng đợi Runtime để các bước tiếp theo có thể chuyển Activity ổn định.
    $_SESSION['runtime_activity_queue'] = array_map(
        static function (array $item): array {
            return [
                'id' => (int)$item['id'],
                'type' => (string)$item['activity_type'],
                'title' => (string)$item['activity_title'],
                'status' => (string)$item['activity_status'],
                'sort_order' => (int)$item['sort_order'],
            ];
        },
        $activityQueue
    );

    $_SESSION['runtime_current_index'] = (int)$currentIndex;
    $_SESSION['runtime_current_activity_id'] = (int)$activity['activity_id'];
    $_SESSION['runtime_previous_activity_id'] = $previousActivity !== null
        ? (int)$previousActivity['id']
        : 0;
    $_SESSION['runtime_next_activity_id'] = $nextActivity !== null
        ? (int)$nextActivity['id']
        : 0;
    $_SESSION['runtime_is_last_activity'] = $nextActivity === null;

    // Chỉ chế độ chạy cả Lesson mới có runtime queue và next_activity_id.
    $query['runtime'] = 1;
    $query['runtime_mode'] = (string)$_SESSION['runtime_mode'];
    $query['runtime_index'] = (int)$currentIndex;
    $query['runtime_total'] = count($activityQueue);

    if ($nextActivity !== null) {
        $query['next_activity_id'] = (int)$nextActivity['id'];
    }
}

$targetUrl = esrProjectUrl('student/' . $gameFile)
    . '?'
    . http_build_query($query, '', '&', PHP_QUERY_RFC3986);

/*
 * DIRECT GAME URL GUARD
 *
 * Bình thường:
 *   student-runtime/index.php -> kiểm tra quyền mở Lesson -> redirect sang URL game.
 *
 * Nếu URL game được gọi trực tiếp:
 *   .htaccess trong thư mục student sẽ INTERNAL REWRITE request đó trở lại
 *   student-runtime/index.php và thêm __es_game_router=1.
 *
 * Khi đó Runtime vẫn chạy toàn bộ Lesson Unlock Guard ở phía trên.
 * - Nếu Lesson khóa: Runtime đã redirect về student-lessons.php.
 * - Nếu Lesson được phép: include đúng game ngay trong request hiện tại.
 *
 * Vì đây là INTERNAL REWRITE nên địa chỉ trình duyệt vẫn là /student/<game>.php,
 * các link tương đối của game vẫn hoạt động bình thường.
 *
 * Không sửa bất kỳ file game nào.
 */
$isDirectGameRouted = (int)($_GET['__es_game_router'] ?? 0) === 1;

if ($isDirectGameRouted) {
    /*
     * Chuẩn hóa GET theo context Runtime vừa xác minh để game không phụ thuộc
     * các tham số book/level/unit/lesson giả do người dùng tự sửa trên URL.
     */
    foreach ($query as $key => $value) {
        $_GET[$key] = $value;
    }

    unset($_GET['__es_game_router']);

    require $absoluteGamePath;
    exit;
}

header('Location: ' . $targetUrl);
exit;
