<?php

/**
 * Exam Format Distiller
 *
 * Turns a raw extracted past-paper sample into a compact, structured format
 * spec (sections, question counts, marks, phrasing style) via one AI call —
 * done ONCE at upload, not on every generation. The generator later reads the
 * stored format_spec_json instead of re-reading the whole sample each time.
 *
 * Deliberately requires ONLY provider_engine.php (which self-guards its own
 * config require via `if (!defined('DB_HOST'))`) — never exam_utils.php,
 * which would pull in exams_generator/config.php and collide with whichever
 * config the caller already loaded. See exam_format_profile_schema.php for
 * the same isolation rule.
 */
require_once dirname(__DIR__, 2) . '/ai/provider_engine.php';

/**
 * Cap the sample fed to the distillation prompt so a large past paper stays
 * within provider token budgets. This runs once at upload, not per exam, so
 * generosity here is fine — it just should not be unbounded.
 */
function truncateForFormatDistillation(string $text, int $wordLimit = 6000): string {
    $words = preg_split('/\s+/', trim($text), -1, PREG_SPLIT_NO_EMPTY);
    if ($words === false || count($words) <= $wordLimit) {
        return $text;
    }
    return implode(' ', array_slice($words, 0, $wordLimit)) . "\n\n[Sample truncated for analysis.]";
}

/**
 * Build the strict-JSON format-analysis prompt.
 */
function buildFormatDistillationPrompt(string $subjectName, string $className, string $rawText): string {
    $sample = truncateForFormatDistillation($rawText);

    return <<<PROMPT
You are analyzing a REFERENCE SAMPLE of a school's exam paper to learn its FORMAT and STRUCTURE — not its specific questions.

Context:
- Subject: {$subjectName}
- Class: {$className}

Reference sample (extracted text; may be noisy from OCR/PDF extraction):
"""
{$sample}
"""

Describe ONLY the structural format of this paper as strict JSON. Do not quote or reuse specific question content — focus on structure, layout, phrasing style, and mark distribution.

Return strict JSON with this shape:
{
  "sections": [
    {"label": "string", "type": "objective|subjective|comprehension|other", "question_count": 0, "marks_per_question": 0, "notes": "string"}
  ],
  "has_comprehension": false,
  "total_marks": 0,
  "duration_minutes": 0,
  "header_instructions": "string describing how instructions to candidates are phrased",
  "command_words": ["string"],
  "tone_notes": "string describing register, difficulty, and style",
  "confidence": "high|medium|low"
}

Rules:
- Infer question_count and marks_per_question from the sample; use 0 if genuinely not determinable.
- command_words: list the instructional verbs actually used (e.g. state, explain, calculate, describe, list), max 12.
- If the sample is incomplete, garbled, or not actually an exam paper, set confidence to "low" and describe what you can.
- Do not wrap the JSON in markdown.
PROMPT;
}

/**
 * Decode the model's JSON response, tolerating markdown code fences.
 */
function decodeFormatDistillerJson(string $content): ?array {
    $content = trim($content);
    if ($content === '') {
        return null;
    }

    if (str_starts_with($content, '```')) {
        $content = preg_replace('/^```[a-zA-Z0-9]*\s*/', '', $content);
        $content = preg_replace('/\s*```$/', '', (string) $content);
        $content = trim((string) $content);
    }

    $decoded = json_decode($content, true);
    if (is_array($decoded)) {
        return $decoded;
    }

    $start = strpos($content, '{');
    $end   = strrpos($content, '}');
    if ($start !== false && $end !== false && $end > $start) {
        $decoded = json_decode(substr($content, $start, $end - $start + 1), true);
        if (is_array($decoded)) {
            return $decoded;
        }
    }

    return null;
}

/**
 * Whitelist and clean the AI's format spec — never trust it blindly.
 */
function normalizeFormatSpec($spec): array {
    $spec = is_array($spec) ? $spec : [];
    $allowedTypes = ['objective', 'subjective', 'comprehension', 'other'];
    $allowedConfidence = ['high', 'medium', 'low'];

    $sections = [];
    foreach ((is_array($spec['sections'] ?? null) ? $spec['sections'] : []) as $section) {
        if (!is_array($section)) {
            continue;
        }
        $type = strtolower(trim((string) ($section['type'] ?? 'other')));
        if (!in_array($type, $allowedTypes, true)) {
            $type = 'other';
        }
        $sections[] = [
            'label'               => trim((string) ($section['label'] ?? 'Section')),
            'type'                => $type,
            'question_count'      => max(0, (int) ($section['question_count'] ?? 0)),
            'marks_per_question'  => max(0, (int) ($section['marks_per_question'] ?? 0)),
            'notes'               => trim((string) ($section['notes'] ?? '')),
        ];
    }

    $commandWords = array_values(array_unique(array_filter(array_map(
        static fn($w) => trim((string) $w),
        is_array($spec['command_words'] ?? null) ? $spec['command_words'] : []
    ))));
    $commandWords = array_slice($commandWords, 0, 12);

    $hasComprehension = !empty($spec['has_comprehension']);
    if (!$hasComprehension) {
        foreach ($sections as $section) {
            if ($section['type'] === 'comprehension') {
                $hasComprehension = true;
                break;
            }
        }
    }

    $confidence = strtolower(trim((string) ($spec['confidence'] ?? 'medium')));
    if (!in_array($confidence, $allowedConfidence, true)) {
        $confidence = 'medium';
    }

    return [
        'sections'            => $sections,
        'has_comprehension'   => $hasComprehension,
        'total_marks'         => max(0, (int) ($spec['total_marks'] ?? 0)),
        'duration_minutes'    => max(0, (int) ($spec['duration_minutes'] ?? 0)),
        'header_instructions' => trim((string) ($spec['header_instructions'] ?? '')),
        'command_words'       => $commandWords,
        'tone_notes'          => trim((string) ($spec['tone_notes'] ?? '')),
        'confidence'          => $confidence,
    ];
}

/**
 * Render a short, human-readable summary directly from the normalized spec
 * (not asked of the AI separately) so the summary can never drift from the
 * structured data a teacher or the generator actually relies on.
 */
function renderFormatSpecSummary(array $spec): string {
    $parts = [];

    $sections = $spec['sections'] ?? [];
    if (!empty($sections)) {
        $sectionDescriptions = [];
        foreach ($sections as $section) {
            $desc = $section['label'] . ' (' . $section['type'];
            if ($section['question_count'] > 0) {
                $desc .= ', ' . $section['question_count'] . ' questions';
            }
            if ($section['marks_per_question'] > 0) {
                $desc .= ', ' . $section['marks_per_question'] . ' marks each';
            }
            $desc .= ')';
            $sectionDescriptions[] = $desc;
        }
        $parts[] = 'This paper has ' . count($sections) . ' section' . (count($sections) === 1 ? '' : 's') . ': '
            . implode('; ', $sectionDescriptions) . '.';
    } else {
        $parts[] = 'No clear section structure could be detected in the sample.';
    }

    $meta = [];
    if (($spec['total_marks'] ?? 0) > 0) {
        $meta[] = 'Total marks: ' . $spec['total_marks'];
    }
    if (($spec['duration_minutes'] ?? 0) > 0) {
        $meta[] = 'Duration: ' . $spec['duration_minutes'] . ' minutes';
    }
    if (!empty($meta)) {
        $parts[] = implode('. ', $meta) . '.';
    }

    if (!empty($spec['header_instructions'])) {
        $parts[] = 'Instructions: ' . $spec['header_instructions'];
    }

    if (!empty($spec['command_words'])) {
        $parts[] = 'Common command words: ' . implode(', ', $spec['command_words']) . '.';
    }

    if (($spec['confidence'] ?? 'medium') === 'low') {
        $parts[] = 'Note: the sample was hard to read clearly — review this summary before relying on it.';
    }

    return trim(implode(' ', $parts));
}

/**
 * Distill a raw extracted sample into a structured format spec + summary.
 *
 * @return array {success: bool, format_spec?: array, summary?: string, provider?: string, message?: string}
 */
function distillExamFormatProfile(string $subjectName, string $className, string $rawText): array {
    if (!class_exists('RBCAIEngine')) {
        return ['success' => false, 'message' => 'AI provider engine is unavailable.'];
    }

    $prompt = buildFormatDistillationPrompt($subjectName, $className, $rawText);
    $system = 'You analyze school exam papers and extract only their structural format as strict JSON. '
            . 'Never reproduce specific question content verbatim.';

    $result = RBCAIEngine::generate($prompt, $system, 1500, true);
    if (empty($result['success']) || empty($result['content'])) {
        return [
            'success' => false,
            'message' => $result['error'] ?? 'AI providers were unable to analyze the sample.',
        ];
    }

    $decoded = decodeFormatDistillerJson((string) $result['content']);
    if ($decoded === null) {
        return [
            'success'  => false,
            'provider' => $result['provider'] ?? null,
            'message'  => 'AI returned a response that was not valid format-spec JSON.',
        ];
    }

    $spec    = normalizeFormatSpec($decoded);
    $summary = renderFormatSpecSummary($spec);

    return [
        'success'     => true,
        'format_spec' => $spec,
        'summary'     => $summary,
        'provider'    => $result['provider'] ?? 'unknown',
    ];
}
