<?php

// Guarded: if a caller already loaded a config that provides getDBConnection()
// (e.g. the root edutrack/config.php, which also defines it), loading this
// local exams_generator/config.php too would fatal on a redeclare. Callers
// that load nothing first (e.g. the CLI worker) still get it as before.
if (!function_exists('getDBConnection')) {
    require_once __DIR__ . '/../config.php';
}
require_once dirname(__DIR__, 2) . '/vendor/autoload.php';
require_once __DIR__ . '/exam_diagram_adapter.php';
require_once __DIR__ . '/exam_format_profile_schema.php';

use Smalot\PdfParser\Parser;

function extractTextFromPDFFile($filepath) {
    try {
        $parser = new Parser();
        $pdf = $parser->parseFile($filepath);
        $text = trim($pdf->getText());
        return preg_replace('/\s+/', ' ', $text);
    } catch (Exception $e) {
        error_log('PDF extraction error: ' . $e->getMessage());
        return false;
    }
}

function getLearningMaterialsWithContent($className, $subjectId, $selectedMaterialIds = []) {
    $conn = getDBConnection();
    $materials = [];
    $query = "SELECT * FROM learning_materials WHERE class_name = ? AND subject_id = ?";
    $types = "si";
    $params = [$className, $subjectId];

    if (!empty($selectedMaterialIds)) {
        $placeholders = implode(',', array_fill(0, count($selectedMaterialIds), '?'));
        $query .= " AND id IN ($placeholders)";
        $types .= str_repeat('i', count($selectedMaterialIds));
        foreach ($selectedMaterialIds as $materialId) {
            $params[] = (int) $materialId;
        }
    }

    $query .= " ORDER BY material_type, uploaded_at DESC";
    $stmt = $conn->prepare($query);
    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $result = $stmt->get_result();

    while ($row = $result->fetch_assoc()) {
        if (!empty($row['extracted_text']) && strlen($row['extracted_text']) > 100) {
            $row['content'] = $row['extracted_text'];
        } elseif (empty($row['content']) && !empty($row['file_path'])) {
            $absolutePath = dirname(__DIR__) . '/' . $row['file_path'];
            if (file_exists($absolutePath) && strtolower(pathinfo($absolutePath, PATHINFO_EXTENSION)) === 'pdf') {
                $extracted = extractTextFromPDFFile($absolutePath);
                if ($extracted) {
                    $row['content'] = $extracted;
                }
            }
        }

        $content = trim((string) ($row['content'] ?? ''));
        $row['content_preview'] = strlen($content) > 5000 ? substr($content, 0, 5000) . '...' : $content;
        $materials[] = $row;
    }

    $stmt->close();
    return $materials;
}

function normalizeExamData(array $examData, array $requestData = []) {
    $normalized = $examData;
    $normalized['title'] = trim((string) ($normalized['title'] ?? $requestData['exam_title'] ?? 'Generated Examination'));
    $normalized['subject'] = trim((string) ($normalized['subject'] ?? $requestData['subject_name'] ?? 'Subject'));
    $normalized['class'] = trim((string) ($normalized['class'] ?? $requestData['class_name'] ?? 'Class'));
    $normalized['time'] = trim((string) ($normalized['time'] ?? (($requestData['duration'] ?? 60) . ' minutes')));
    $normalized['instructions'] = trim((string) ($normalized['instructions'] ?? 'Answer all questions.'));
    $normalized['sections'] = is_array($normalized['sections'] ?? null) ? $normalized['sections'] : [];

    foreach ($normalized['sections'] as $sectionIndex => $section) {
        $normalized['sections'][$sectionIndex]['section'] = trim((string) ($section['section'] ?? chr(65 + $sectionIndex)));
        $normalized['sections'][$sectionIndex]['title'] = trim((string) ($section['title'] ?? ('Section ' . chr(65 + $sectionIndex))));
        $normalized['sections'][$sectionIndex]['instructions'] = trim((string) ($section['instructions'] ?? ''));
        $normalized['sections'][$sectionIndex]['type'] = trim((string) ($section['type'] ?? 'subjective'));
        $normalized['sections'][$sectionIndex]['questions'] = is_array($section['questions'] ?? null) ? $section['questions'] : [];
        if (($normalized['sections'][$sectionIndex]['type'] ?? '') === 'comprehension') {
            $normalized['sections'][$sectionIndex]['passage'] = trim((string) ($section['passage'] ?? ''));
        }

        foreach ($normalized['sections'][$sectionIndex]['questions'] as $questionIndex => $question) {
            $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['number'] = (int) ($question['number'] ?? ($questionIndex + 1));
            $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['text'] = trim((string) ($question['text'] ?? ''));
            $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['marks'] = max(1, (int) ($question['marks'] ?? 1));
            if (($normalized['sections'][$sectionIndex]['type'] ?? '') === 'objective') {
                $options = is_array($question['options'] ?? null) ? $question['options'] : [];
                $normalizedOptions = [];
                foreach (['A', 'B', 'C', 'D'] as $optionKey) {
                    $normalizedOptions[$optionKey] = trim((string) ($options[$optionKey] ?? ''));
                }
                $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['options'] = $normalizedOptions;
            }
            if (isset($question['correct_option'])) {
                $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['correct_option'] = trim((string) $question['correct_option']);
            }
            if (isset($question['model_answer'])) {
                $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['model_answer'] = trim((string) $question['model_answer']);
            }
            if (isset($question['rubric'])) {
                $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['rubric'] = array_values(array_filter(array_map('trim', is_array($question['rubric']) ? $question['rubric'] : [])));
            }
            if (isset($question['diagram'])) {
                $diagramSpec = normalizeDiagramSpec($question['diagram']);
                if ($diagramSpec !== null) {
                    $normalized['sections'][$sectionIndex]['questions'][$questionIndex]['diagram'] = $diagramSpec;
                } else {
                    unset($normalized['sections'][$sectionIndex]['questions'][$questionIndex]['diagram']);
                }
            }
        }
    }

    return $normalized;
}

/**
 * Clean and whitelist an AI-supplied diagram spec.
 * Returns a normalized spec array, or null when there is no drawable diagram.
 */
function normalizeDiagramSpec($spec) {
    if (!is_array($spec)) {
        return null;
    }
    $allowed = ['bar_chart', 'line_chart', 'pie_chart', 'number_line', 'triangle', 'right_triangle', 'rectangle', 'circle', 'angle', 'coordinate_plane', 'table'];
    $type = strtolower(trim((string) ($spec['type'] ?? '')));
    if ($type === '' || $type === 'none' || !in_array($type, $allowed, true)) {
        return null;
    }
    return [
        'type'    => $type,
        'title'   => trim((string) ($spec['title'] ?? '')),
        'data'    => is_array($spec['data'] ?? null) ? $spec['data'] : [],
        'labels'  => array_values(array_filter(array_map('trim', is_array($spec['labels'] ?? null) ? $spec['labels'] : []))),
        'caption' => trim((string) ($spec['caption'] ?? '')),
    ];
}

function validateExamData(array $examData, array $requestData = []) {
    $errors = [];
    $warnings = [];
    $computedTotalMarks = 0;

    if (trim((string) ($examData['title'] ?? '')) === '') {
        $errors[] = 'Exam title is required.';
    }
    if (trim((string) ($examData['instructions'] ?? '')) === '') {
        $warnings[] = 'Exam instructions are empty.';
    }

    $sections = $examData['sections'] ?? [];
    if (!is_array($sections) || empty($sections)) {
        $errors[] = 'At least one exam section is required.';
    }

    foreach ($sections as $sectionIndex => $section) {
        $sectionTitle = trim((string) ($section['title'] ?? ('Section ' . ($sectionIndex + 1))));
        $sectionType = trim((string) ($section['type'] ?? 'subjective'));
        $questions = $section['questions'] ?? [];

        if (!is_array($questions) || empty($questions)) {
            $errors[] = $sectionTitle . ' must contain at least one question.';
            continue;
        }
        if ($sectionType === 'comprehension' && trim((string) ($section['passage'] ?? '')) === '') {
            $errors[] = $sectionTitle . ' is missing its comprehension passage.';
        }

        foreach ($questions as $questionIndex => $question) {
            $label = $sectionTitle . ' question ' . ($questionIndex + 1);
            $questionText = trim((string) ($question['text'] ?? ''));
            $marks = (int) ($question['marks'] ?? 0);
            if ($questionText === '') {
                $errors[] = $label . ' is empty.';
            }
            if ($marks <= 0) {
                $errors[] = $label . ' must have positive marks.';
            } else {
                $computedTotalMarks += $marks;
            }
            if ($sectionType === 'objective') {
                $options = is_array($question['options'] ?? null) ? $question['options'] : [];
                foreach (['A', 'B', 'C', 'D'] as $optionKey) {
                    if (trim((string) ($options[$optionKey] ?? '')) === '') {
                        $errors[] = $label . ' is missing option ' . $optionKey . '.';
                    }
                }
            }
        }
    }

    $requestedTotal = (int) ($requestData['total_marks'] ?? 0);
    if ($requestedTotal > 0 && $computedTotalMarks !== $requestedTotal) {
        $warnings[] = 'Exam total marks (' . $computedTotalMarks . ') does not match requested total marks (' . $requestedTotal . ').';
    }

    return [
        'valid' => empty($errors),
        'errors' => $errors,
        'warnings' => array_values(array_unique($warnings)),
        'computed_total_marks' => $computedTotalMarks,
    ];
}

function encodeWarnings(array $warnings) {
    return json_encode(array_values(array_unique(array_filter(array_map('strval', $warnings)))), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
}

function getStatusLabel($status) {
    $labels = [
        'draft' => 'Draft',
        'review_requested' => 'Review Requested',
        'reviewed' => 'Reviewed',
        'approved' => 'Approved',
        'published' => 'Published',
    ];
    return $labels[$status] ?? ucfirst(str_replace('_', ' ', (string) $status));
}

function getStatusBadgeClass($status) {
    $classes = [
        'draft' => 'bg-secondary',
        'review_requested' => 'bg-warning text-dark',
        'reviewed' => 'bg-info text-dark',
        'approved' => 'bg-success',
        'published' => 'bg-primary',
    ];
    return $classes[$status] ?? 'bg-secondary';
}

function canReviewExam($role) {
    return in_array($role, ['admin', 'headteacher', 'director', 'supervisor'], true);
}

function canApproveExam($role) {
    return in_array($role, ['admin', 'headteacher', 'director'], true);
}

function canPublishExam($role) {
    return in_array($role, ['admin', 'headteacher', 'director'], true);
}

function getAvailableWorkflowActions(array $exam, array $sessionUser) {
    $actions = [];
    $status = $exam['status'] ?? 'draft';
    $role = $sessionUser['role'] ?? '';
    $userId = (int) ($sessionUser['user_id'] ?? 0);
    $isCreator = $userId > 0 && $userId === (int) ($exam['created_by'] ?? 0);

    if ($status === 'draft' && ($isCreator || canReviewExam($role) || $role === 'tutor')) {
        $actions[] = 'submit_review';
    }
    if ($status === 'review_requested' && canReviewExam($role)) {
        $actions[] = 'mark_reviewed';
        $actions[] = 'return_draft';
    }
    if ($status === 'reviewed' && canApproveExam($role)) {
        $actions[] = 'approve';
        $actions[] = 'return_draft';
    }
    if ($status === 'approved' && canPublishExam($role)) {
        $actions[] = 'publish';
        $actions[] = 'return_draft';
    }

    return array_values(array_unique($actions));
}

function logWorkflowAction($examId, $action, $fromStatus, $toStatus, $note, $userId) {
    $conn = getDBConnection();
    $stmt = $conn->prepare('INSERT INTO exam_workflow_logs (exam_id, action, from_status, to_status, note, acted_by) VALUES (?, ?, ?, ?, ?, ?)');
    $stmt->bind_param('issssi', $examId, $action, $fromStatus, $toStatus, $note, $userId);
    $stmt->execute();
    $stmt->close();
}

function getExamWorkflowHistory($examId) {
    $conn = getDBConnection();
    $stmt = $conn->prepare('SELECT l.*, u.full_name FROM exam_workflow_logs l LEFT JOIN users u ON l.acted_by = u.id WHERE exam_id = ? ORDER BY id DESC');
    $stmt->bind_param('i', $examId);
    $stmt->execute();
    $result = $stmt->get_result();
    $items = [];
    while ($row = $result->fetch_assoc()) {
        $items[] = $row;
    }
    $stmt->close();
    return $items;
}

function transitionExamWorkflow($examId, $action, $note, array $sessionUser) {
    $exam = loadExamRecord($examId);
    if (!$exam) {
        return ['success' => false, 'message' => 'Exam not found.'];
    }

    $userId = (int) ($sessionUser['user_id'] ?? 0);
    $role = $sessionUser['role'] ?? '';
    $currentStatus = $exam['status'] ?? 'draft';
    $newStatus = $currentStatus;

    if ($action === 'submit_review' && $currentStatus === 'draft') {
        $newStatus = 'review_requested';
    } elseif ($action === 'mark_reviewed' && $currentStatus === 'review_requested' && canReviewExam($role)) {
        $newStatus = 'reviewed';
    } elseif ($action === 'approve' && $currentStatus === 'reviewed' && canApproveExam($role)) {
        $newStatus = 'approved';
    } elseif ($action === 'publish' && $currentStatus === 'approved' && canPublishExam($role)) {
        $newStatus = 'published';
    } elseif ($action === 'return_draft' && in_array($currentStatus, ['review_requested', 'reviewed', 'approved'], true)) {
        $newStatus = 'draft';
    } else {
        return ['success' => false, 'message' => 'This workflow action is not allowed.'];
    }

    $conn = getDBConnection();
    $reviewedBy = $action === 'mark_reviewed' ? $userId : null;
    $approvedBy = $action === 'approve' ? $userId : null;
    $publishedBy = $action === 'publish' ? $userId : null;
    $stmt = $conn->prepare("UPDATE generated_exams SET status = ?, review_notes = ?, reviewed_by = COALESCE(?, reviewed_by), reviewed_at = CASE WHEN ? IS NULL THEN reviewed_at ELSE NOW() END, approved_by = COALESCE(?, approved_by), approved_at = CASE WHEN ? IS NULL THEN approved_at ELSE NOW() END, published_by = COALESCE(?, published_by), published_at = CASE WHEN ? IS NULL THEN published_at ELSE NOW() END WHERE id = ?");
    $stmt->bind_param('ssiiiiiii', $newStatus, $note, $reviewedBy, $reviewedBy, $approvedBy, $approvedBy, $publishedBy, $publishedBy, $examId);
    $stmt->execute();
    $stmt->close();

    logWorkflowAction($examId, $action, $currentStatus, $newStatus, $note, $userId);
    return ['success' => true, 'message' => 'Workflow updated successfully.'];
}

function generateWorkflowSummaryHtml(array $exam, array $sessionUser) {
    $status = $exam['status'] ?? 'draft';
    $actions = getAvailableWorkflowActions($exam, $sessionUser);
    $html = '<div class="card border-0 shadow-sm mb-3"><div class="card-body">';
    $html .= '<div class="d-flex justify-content-between align-items-center mb-2">';
    $html .= '<h5 class="mb-0">Workflow</h5>';
    $html .= '<span class="badge ' . getStatusBadgeClass($status) . '">' . htmlspecialchars(getStatusLabel($status)) . '</span>';
    $html .= '</div>';
    if (!empty($exam['review_notes'])) {
        $html .= '<div class="alert alert-light border">' . nl2br(htmlspecialchars($exam['review_notes'])) . '</div>';
    }
    if (!empty($actions)) {
        $html .= '<div class="d-flex flex-wrap gap-2 mb-3">';
        foreach ($actions as $action) {
            $labels = [
                'submit_review' => 'Submit For Review',
                'mark_reviewed' => 'Mark Reviewed',
                'approve' => 'Approve',
                'publish' => 'Publish',
                'return_draft' => 'Return To Draft'
            ];
            $html .= '<button type="button" class="btn btn-sm btn-outline-primary workflow-action-btn" data-action="' . htmlspecialchars($action) . '">' . htmlspecialchars($labels[$action] ?? ucfirst($action)) . '</button>';
        }
        $html .= '</div>';
        $html .= '<div class="mb-3"><label class="form-label fw-bold">Workflow Note</label><textarea class="form-control" id="workflowNote" rows="3" placeholder="Add review or approval note..."></textarea></div>';
    }
    if (!empty($exam['workflow_history'])) {
        $html .= '<div><label class="form-label fw-bold">History</label><div class="small">';
        foreach ($exam['workflow_history'] as $item) {
            $html .= '<div class="border rounded p-2 mb-2">';
            $html .= '<strong>' . htmlspecialchars(getStatusLabel($item['to_status'] ?? 'draft')) . '</strong> via ' . htmlspecialchars($item['action']);
            $html .= '<div class="text-muted">' . htmlspecialchars($item['full_name'] ?? 'System') . ' on ' . htmlspecialchars($item['acted_at']) . '</div>';
            if (!empty($item['note'])) {
                $html .= '<div>' . nl2br(htmlspecialchars($item['note'])) . '</div>';
            }
            $html .= '</div>';
        }
        $html .= '</div></div>';
    }
    $html .= '</div></div>';
    return $html;
}

function saveExamToDatabase(array $requestData, array $examData, $userId, $existingExamId = null, array $auditData = []) {
    $conn = getDBConnection();
    $academicYear = getSetting('academic_year', date('Y') . '-' . (date('Y') + 1));
    $instructions = $examData['instructions'] ?? 'Answer all questions.';
    $examJson = json_encode($examData, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    $generatorUsed = $auditData['generator_used'] ?? null;
    $warningText = encodeWarnings($auditData['warnings'] ?? []);
    $status = $auditData['status'] ?? 'draft';
    $reviewNotes = $auditData['review_notes'] ?? null;

    if ($existingExamId) {
        $query = "UPDATE generated_exams
                  SET title = ?, class_name = ?, subject_id = ?, subject_name = ?, exam_type = ?, term = ?,
                      academic_year = ?, instructions = ?, total_marks = ?, duration_minutes = ?, exam_data = ?,
                      generator_used = COALESCE(?, generator_used), generation_warnings = ?, review_notes = COALESCE(?, review_notes),
                      status = COALESCE(?, status), last_edited_by = ?, updated_at = CURRENT_TIMESTAMP
                  WHERE id = ?";
        $stmt = $conn->prepare($query);
        $stmt->bind_param(
            'ssisssssiissssiii',
            $requestData['exam_title'],
            $requestData['class_name'],
            $requestData['subject_id'],
            $requestData['subject_name'],
            $requestData['exam_type'],
            $requestData['term'],
            $academicYear,
            $instructions,
            $requestData['total_marks'],
            $requestData['duration'],
            $examJson,
            $generatorUsed,
            $warningText,
            $reviewNotes,
            $status,
            $userId,
            $existingExamId
        );
        $stmt->execute();
        $stmt->close();
        return (int) $existingExamId;
    }

    $query = "INSERT INTO generated_exams
                (title, class_name, subject_id, subject_name, exam_type, term, status, academic_year, instructions,
                 total_marks, duration_minutes, exam_data, generator_used, generation_warnings, review_notes,
                 created_by, last_edited_by, created_at, updated_at)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())";
    $stmt = $conn->prepare($query);
    $stmt->bind_param(
        'ssissssssiissssii',
        $requestData['exam_title'],
        $requestData['class_name'],
        $requestData['subject_id'],
        $requestData['subject_name'],
        $requestData['exam_type'],
        $requestData['term'],
        $status,
        $academicYear,
        $instructions,
        $requestData['total_marks'],
        $requestData['duration'],
        $examJson,
        $generatorUsed,
        $warningText,
        $reviewNotes,
        $userId,
        $userId
    );
    $stmt->execute();
    $examId = (int) $stmt->insert_id;
    $stmt->close();
    return $examId;
}

function loadExamRecord($examId) {
    $conn = getDBConnection();
    $query = "SELECT e.*, u.full_name AS creator, editor.full_name AS last_editor,
                     reviewer.full_name AS reviewer_name, approver.full_name AS approver_name, publisher.full_name AS publisher_name
              FROM generated_exams e
              LEFT JOIN users u ON e.created_by = u.id
              LEFT JOIN users editor ON e.last_edited_by = editor.id
              LEFT JOIN users reviewer ON e.reviewed_by = reviewer.id
              LEFT JOIN users approver ON e.approved_by = approver.id
              LEFT JOIN users publisher ON e.published_by = publisher.id
              WHERE e.id = ?";
    $stmt = $conn->prepare($query);
    $stmt->bind_param('i', $examId);
    $stmt->execute();
    $result = $stmt->get_result();
    $exam = $result->fetch_assoc();
    $stmt->close();

    if ($exam) {
        $exam['exam_data'] = json_decode($exam['exam_data'], true) ?: [];
        $decodedWarnings = json_decode((string) ($exam['generation_warnings'] ?? ''), true);
        $exam['generation_warnings'] = is_array($decodedWarnings) ? $decodedWarnings : [];
        $exam['workflow_history'] = getExamWorkflowHistory((int) $exam['id']);
    }

    return $exam ?: null;
}

function chunkTextForGeneration($text, $chunkLength = 800) {
    $text = trim((string) $text);
    if ($text === '') {
        return [];
    }
    $chunks = [];
    $parts = preg_split('/(?<=[.!?])\s+/', $text);
    $buffer = '';
    foreach ($parts as $part) {
        $candidate = trim($buffer . ' ' . $part);
        if (strlen($candidate) > $chunkLength && $buffer !== '') {
            $chunks[] = trim($buffer);
            $buffer = $part;
        } else {
            $buffer = $candidate;
        }
    }
    if (trim($buffer) !== '') {
        $chunks[] = trim($buffer);
    }
    return $chunks;
}

function scoreGenerationChunk($chunk, array $requestData = []) {
    $score = 0;
    $haystack = strtolower((string) $chunk);
    foreach (preg_split('/[,;\n]+/', (string) ($requestData['topics'] ?? '')) as $topic) {
        $topic = strtolower(trim($topic));
        if ($topic !== '' && str_contains($haystack, $topic)) {
            $score += 5;
        }
    }
    foreach (preg_split('/\s+/', strtolower((string) ($requestData['subject_name'] ?? ''))) as $token) {
        $token = trim($token);
        if ($token !== '' && strlen($token) > 3 && str_contains($haystack, $token)) {
            $score += 2;
        }
    }
    return $score + min(3, (int) floor(strlen($chunk) / 200));
}

function getSubjectGenerationRules($subjectName) {
    $subject = strtolower((string) $subjectName);
    if (str_contains($subject, 'mathematics')) {
        return ['Focus on worked solutions, computational accuracy, and practical problem solving.'];
    }
    if (str_contains($subject, 'english')) {
        return ['Use clear grammar, comprehension, vocabulary, and composition-style wording.'];
    }
    if (str_contains($subject, 'science')) {
        return ['Prefer explanatory questions, simple experiments, and everyday applications.'];
    }
    return ['Keep wording clear, classroom-aligned, and suitable for school examinations.'];
}

/**
 * Load the school's distilled Exam Format Profile for a subject × class, if
 * one has been uploaded and successfully distilled (status = 'ready').
 * Returns the normalized format_spec array, or null when there is nothing
 * usable — callers must treat null as "fall back to generic rules", never
 * throw, since a missing profile is the normal case for most subjects.
 */
function getExamFormatProfile($className, $subjectId) {
    $conn = getDBConnection();
    if (!$conn instanceof mysqli) {
        return null;
    }
    ensureExamFormatProfileTables($conn);

    $stmt = $conn->prepare(
        "SELECT format_spec_json FROM exam_format_profiles
         WHERE subject_id = ? AND class_name = ? AND status = 'ready' LIMIT 1"
    );
    $stmt->bind_param('is', $subjectId, $className);
    $stmt->execute();
    $row = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    if (!$row || empty($row['format_spec_json'])) {
        return null;
    }
    $spec = json_decode((string) $row['format_spec_json'], true);
    return is_array($spec) ? $spec : null;
}

function buildRetrievalContext(array $materials, array $requestData = []) {
    $chunks = [];
    foreach ($materials as $material) {
        $content = trim((string) ($material['content_preview'] ?? $material['content'] ?? ''));
        if ($content === '') {
            continue;
        }
        foreach (chunkTextForGeneration($content) as $chunk) {
            $chunks[] = [
                'title' => $material['title'] ?? 'Material',
                'content' => $chunk,
                'score' => scoreGenerationChunk($chunk, $requestData),
            ];
        }
    }
    usort($chunks, function ($a, $b) {
        return ($b['score'] ?? 0) <=> ($a['score'] ?? 0);
    });
    return array_slice($chunks, 0, 8);
}

function moderateGeneratedExam(array $examData, array $requestData = []) {
    $warnings = [];
    $seen = [];
    foreach (($examData['sections'] ?? []) as $section) {
        foreach (($section['questions'] ?? []) as $question) {
            $text = strtolower(trim((string) ($question['text'] ?? '')));
            if ($text === '') {
                continue;
            }
            if (isset($seen[$text])) {
                $warnings[] = 'Some generated questions are repeated or too similar.';
                break 2;
            }
            $seen[$text] = true;
        }
    }
    return $warnings;
}

function buildQuestionHash($questionText, $questionType) {
    $normalized = strtolower(trim(preg_replace('/\s+/', ' ', (string) $questionText)));
    return hash('sha256', $questionType . '|' . $normalized);
}

function inferQuestionTopic($questionText, $fallback = null) {
    $words = preg_split('/\s+/', trim(preg_replace('/[^A-Za-z0-9\s]/', ' ', (string) $questionText)));
    $words = array_values(array_filter($words, function ($word) {
        return strlen($word) > 4;
    }));
    return $words[0] ?? ($fallback ?: 'General Topic');
}

function saveExamQuestionsToBank($examId, array $exam, $userId) {
    $conn = getDBConnection();
    $saved = 0;
    foreach (($exam['exam_data']['sections'] ?? []) as $section) {
        $sectionType = $section['type'] ?? 'subjective';
        foreach (($section['questions'] ?? []) as $question) {
            $questionText = trim((string) ($question['text'] ?? ''));
            if ($questionText === '') {
                continue;
            }
            $hash = buildQuestionHash($questionText, $sectionType);
            $check = $conn->prepare('SELECT id FROM question_bank WHERE question_hash = ? LIMIT 1');
            $check->bind_param('s', $hash);
            $check->execute();
            $exists = $check->get_result()->fetch_assoc();
            $check->close();
            if ($exists) {
                continue;
            }
            $options = json_encode($question['options'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
            $difficulty = 'medium';
            $topic = inferQuestionTopic($questionText, $exam['subject_name'] ?? null);
            $bloom = 'understand';
            $sectionTitle = $section['title'] ?? 'Section';
            $sourceMaterialIds = null;
            $stmt = $conn->prepare('INSERT INTO question_bank (class_name, subject_id, subject_name, question_type, question_text, options, marks, difficulty, topic, bloom_level, section_title, source_exam_id, source_material_ids, question_hash, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())');
            $stmt->bind_param('sissssissssissi', $exam['class_name'], $exam['subject_id'], $exam['subject_name'], $sectionType, $questionText, $options, $question['marks'], $difficulty, $topic, $bloom, $sectionTitle, $examId, $sourceMaterialIds, $hash, $userId);
            $stmt->execute();
            $stmt->close();
            $saved++;
        }
    }
    return $saved;
}

function getQuestionBankItems($className, $subjectId, $type = null, $limit = 40) {
    $conn = getDBConnection();
    $query = 'SELECT * FROM question_bank WHERE class_name = ? AND subject_id = ?';
    $types = 'si';
    $params = [$className, $subjectId];
    if ($type !== null && $type !== '') {
        $query .= ' AND question_type = ?';
        $types .= 's';
        $params[] = $type;
    }
    $query .= ' ORDER BY created_at DESC LIMIT ' . max(1, (int) $limit);
    $stmt = $conn->prepare($query);
    $stmt->bind_param($types, ...$params);
    $stmt->execute();
    $result = $stmt->get_result();
    $items = [];
    while ($row = $result->fetch_assoc()) {
        $row['options'] = json_decode((string) ($row['options'] ?? ''), true) ?: [];
        $items[] = $row;
    }
    $stmt->close();
    return $items;
}

function markQuestionBankUsage($questionBankId) {
    $conn = getDBConnection();
    $stmt = $conn->prepare('UPDATE question_bank SET usage_count = COALESCE(usage_count, 0) + 1, last_used_at = NOW() WHERE id = ?');
    $stmt->bind_param('i', $questionBankId);
    $stmt->execute();
    $stmt->close();
}

function buildQuestionBankHtml(array $items) {
    if (empty($items)) {
        return '<div class="alert alert-info">No saved questions found for the selected class and subject.</div>';
    }
    $html = '<div class="list-group">';
    foreach ($items as $item) {
        $payload = rawurlencode(json_encode($item, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        $html .= '<div class="list-group-item">';
        $html .= '<div class="d-flex justify-content-between align-items-start gap-2">';
        $html .= '<div><strong>' . htmlspecialchars($item['section_title'] ?? ucfirst($item['question_type'])) . '</strong><div class="small text-muted">' . htmlspecialchars($item['topic'] ?? 'General') . ' | ' . (int) ($item['marks'] ?? 0) . ' marks</div><div class="mt-2">' . htmlspecialchars($item['question_text'] ?? '') . '</div></div>';
        $html .= '<button type="button" class="btn btn-sm btn-outline-primary insert-bank-question" data-question="' . htmlspecialchars($payload) . '">Insert</button>';
        $html .= '</div>';
        $html .= '</div>';
    }
    $html .= '</div>';
    return $html;
}

function inferAssessmentKeywords($questionText, $subjectName, $limit = 4) {
    $words = preg_split('/\s+/', strtolower(preg_replace('/[^a-z0-9\s]/i', ' ', (string) $questionText)));
    $words = array_values(array_filter($words, function ($word) {
        return strlen($word) > 4;
    }));
    $keywords = array_slice(array_unique($words), 0, $limit);
    if (empty($keywords)) {
        $keywords[] = strtolower((string) $subjectName);
    }
    return $keywords;
}

function buildModelAnswer($questionText, $subjectName) {
    $keywords = inferAssessmentKeywords($questionText, $subjectName);
    return 'A strong answer should address ' . implode(', ', $keywords) . ' accurately with clear explanation.';
}

function buildRubricPoints($questionText, $subjectName, $marks = 0) {
    $keywords = inferAssessmentKeywords($questionText, $subjectName, 2);
    $points = [
        'Credit correct understanding of ' . implode(' and ', $keywords) . '.',
        'Reward relevant examples, steps, or explanations.'
    ];
    if ((int) $marks >= 6) {
        $points[] = 'Reward good structure and completeness.';
    }
    return $points;
}

function enrichExamAssessmentData(array $examData, array $requestData = []) {
    $subjectName = $requestData['subject_name'] ?? ($examData['subject'] ?? 'the subject');
    foreach (($examData['sections'] ?? []) as $sectionIndex => $section) {
        $type = $section['type'] ?? 'subjective';
        foreach (($section['questions'] ?? []) as $questionIndex => $question) {
            if ($type === 'objective' && empty($question['correct_option'])) {
                $question['correct_option'] = 'A';
            }
            if (empty($question['model_answer'])) {
                $question['model_answer'] = buildModelAnswer($question['text'] ?? '', $subjectName);
            }
            if (empty($question['rubric']) || !is_array($question['rubric'])) {
                $question['rubric'] = buildRubricPoints($question['text'] ?? '', $subjectName, (int) ($question['marks'] ?? 0));
            }
            $examData['sections'][$sectionIndex]['questions'][$questionIndex] = $question;
        }
    }
    return $examData;
}

function generateExamPreviewHtml(array $exam, $version = 'student') {
    $examData = is_array($exam['exam_data'] ?? null) ? $exam['exam_data'] : [];
    $showTeacherGuide = $version === 'teacher';
    $html = '<div data-exam-id="' . (int) ($exam['id'] ?? 0) . '" class="exam-paper">';
    $html .= '<div class="exam-header text-center mb-4">';
    $html .= '<h2 class="fw-bold">' . htmlspecialchars($exam['title'] ?? ($examData['title'] ?? 'Generated Examination')) . '</h2>';
    $html .= '<div class="row mt-3">';
    $html .= '<div class="col-md-3"><strong>Class:</strong> ' . htmlspecialchars($exam['class_name'] ?? ($examData['class'] ?? '')) . '</div>';
    $html .= '<div class="col-md-3"><strong>Subject:</strong> ' . htmlspecialchars($exam['subject_name'] ?? ($examData['subject'] ?? '')) . '</div>';
    $html .= '<div class="col-md-3"><strong>Term:</strong> ' . htmlspecialchars($exam['term'] ?? '') . '</div>';
    $html .= '<div class="col-md-3"><strong>Duration:</strong> ' . (int) ($exam['duration_minutes'] ?? 0) . ' minutes</div>';
    $html .= '</div><div class="mt-2"><strong>Total Marks:</strong> ' . (int) ($exam['total_marks'] ?? 0) . '</div>';
    $html .= '<div class="mt-2"><span class="badge ' . getStatusBadgeClass($exam['status'] ?? 'draft') . '">' . htmlspecialchars(getStatusLabel($exam['status'] ?? 'draft')) . '</span>';
    if (!empty($exam['generator_used'])) {
        $html .= ' <span class="badge bg-secondary">' . htmlspecialchars($exam['generator_used']) . '</span>';
    }
    if ($showTeacherGuide) {
        $html .= ' <span class="badge bg-dark">Teacher Guide</span>';
    }
    $html .= '</div><hr class="my-3"></div>';

    if (!empty($exam['generation_warnings'])) {
        $html .= '<div class="alert alert-warning"><strong>Generation warnings:</strong><ul class="mb-0 mt-2">';
        foreach ($exam['generation_warnings'] as $warning) {
            $html .= '<li>' . htmlspecialchars($warning) . '</li>';
        }
        $html .= '</ul></div>';
    }
    if (!empty($exam['review_notes'])) {
        $html .= '<div class="alert alert-info"><strong>Review note:</strong><br>' . nl2br(htmlspecialchars($exam['review_notes'])) . '</div>';
    }
    if (!empty($examData['instructions'])) {
        $html .= '<div class="instructions bg-light p-3 mb-4 rounded"><strong>INSTRUCTIONS TO CANDIDATES:</strong><br>' . nl2br(htmlspecialchars($examData['instructions'])) . '</div>';
    }

    foreach (($examData['sections'] ?? []) as $section) {
        $html .= '<div class="section mt-4">';
        $html .= '<h4 class="section-title bg-primary text-white p-2 rounded">' . htmlspecialchars($section['title'] ?? 'Section') . '</h4>';
        if (!empty($section['instructions'])) {
            $html .= '<p class="text-muted mb-3">' . htmlspecialchars($section['instructions']) . '</p>';
        }
        if (($section['type'] ?? '') === 'comprehension' && !empty($section['passage'])) {
            $html .= '<div class="passage bg-light p-3 mb-3 rounded"><strong>Passage:</strong><br>' . nl2br(htmlspecialchars($section['passage'])) . '</div>';
        }
        foreach (($section['questions'] ?? []) as $question) {
            $html .= '<div class="question-item">';
            $html .= '<div><strong>' . (int) ($question['number'] ?? 0) . '.</strong> ' . htmlspecialchars($question['text'] ?? '') . ' <span class="badge bg-info">[' . (int) ($question['marks'] ?? 0) . ' marks]</span></div>';
            if (($section['type'] ?? '') === 'objective' && !empty($question['options'])) {
                $html .= '<div class="options ms-4 mt-2">';
                foreach ($question['options'] as $optionKey => $optionText) {
                    $html .= '<div>' . htmlspecialchars($optionKey) . '. ' . htmlspecialchars($optionText) . '</div>';
                }
                $html .= '</div>';
            }
            if (!empty($question['diagram']) && is_array($question['diagram'])) {
                $dg = $question['diagram'];
                $html .= '<div class="exam-diagram my-2" data-diagram="' . htmlspecialchars(json_encode($dg), ENT_QUOTES) . '">';
                if (!empty($dg['title'])) {
                    $html .= '<div class="text-center fw-semibold small mb-1">' . htmlspecialchars($dg['title']) . '</div>';
                }
                $html .= renderExamDiagram($dg);
                $html .= '</div>';
            }
            if ($showTeacherGuide) {
                $html .= '<div class="mt-3 p-3 border rounded bg-light">';
                if (($section['type'] ?? '') === 'objective') {
                    $html .= '<div><strong>Correct Option:</strong> ' . htmlspecialchars($question['correct_option'] ?? 'A') . '</div>';
                }
                $html .= '<div><strong>Model Answer:</strong> ' . htmlspecialchars($question['model_answer'] ?? '') . '</div>';
                if (!empty($question['rubric']) && is_array($question['rubric'])) {
                    $html .= '<div class="mt-2"><strong>Marking Guide:</strong><ul class="mb-0">';
                    foreach ($question['rubric'] as $point) {
                        $html .= '<li>' . htmlspecialchars($point) . '</li>';
                    }
                    $html .= '</ul></div>';
                }
                $html .= '</div>';
            }
            $html .= '</div>';
        }
        $html .= '</div>';
    }

    $html .= '<div class="exam-footer mt-5 pt-3 border-top text-center"><p><strong>END OF ' . ($showTeacherGuide ? 'TEACHER GUIDE' : 'EXAMINATION') . '</strong></p></div></div>';
    return $html;
}
