<?php

// API must return JSON only — never print PHP warnings/notices before json_encode (breaks frontend JSON.parse).
ini_set('display_errors', '0');
error_reporting(E_ALL);
ob_start();

header('Content-Type: application/json');

set_error_handler(static function (int $severity, string $message, string $file, int $line): bool {
    error_log('[generate_next_question] PHP warning: ' . $message . ' in ' . $file . ':' . $line);

    return true;
});

require_once __DIR__ . '/../config/database.php';
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../ai/prompt_generator.php';
require_once __DIR__ . '/../ai/form_generator_retry.php';
require_once __DIR__ . '/../ai/form_generator_default_skills.php';

/**
 * @param array<string, mixed> $payload
 */
function generationJsonResponse(array $payload): void
{
    while (ob_get_level() > 0) {
        ob_end_clean();
    }
    echo json_encode($payload, JSON_UNESCAPED_UNICODE);
    exit;
}

function generationSafeFailureMessage(): string
{
    return 'Generation failed. The AI did not return a valid question. No draft was saved.';
}

function generationLogInvalidRawOutput(string $raw, string $phase, int $jobId = 0): void
{
    $raw = trim($raw);
    if ($raw === '') {
        return;
    }
    $logDir = dirname(__DIR__, 2) . '/logs';
    if (!is_dir($logDir)) {
        @mkdir($logDir, 0755, true);
    }
    @file_put_contents(
        $logDir . '/sba_generation_raw_output.log',
        json_encode([
            'time' => gmdate('c'),
            'phase' => $phase,
            'job_id' => $jobId,
            'raw' => $raw,
        ], JSON_UNESCAPED_UNICODE) . "\n",
        FILE_APPEND | LOCK_EX
    );
}

/**
 * @param array<string, mixed> $entry
 */
function generationStepTrace(array $entry): void
{
    $logDir = dirname(__DIR__, 2) . '/logs';
    if (!is_dir($logDir)) {
        @mkdir($logDir, 0755, true);
    }
    @file_put_contents(
        $logDir . '/sba_generation_step_trace.log',
        json_encode($entry, JSON_UNESCAPED_UNICODE) . "\n",
        FILE_APPEND | LOCK_EX
    );
}

/**
 * @param array<string, mixed> $question
 */
function generationQuestionHasRequiredFields(array $question): bool
{
    $required = [
        'title', 'stem', 'lead_in', 'option_a', 'option_b', 'option_c', 'option_d', 'option_e',
        'correct_answer_letter', 'justification',
    ];
    foreach ($required as $field) {
        if (!array_key_exists($field, $question) || trim((string) $question[$field]) === '') {
            return false;
        }
    }
    $letter = strtoupper(trim((string) ($question['correct_answer_letter'] ?? '')));
    if (!in_array($letter, ['A', 'B', 'C', 'D', 'E'], true)) {
        return false;
    }

    return true;
}

/**
 * @param array<string, mixed> $job
 */
function generationFailureResponse(array $job, int $jid, string $detail, int $questionNumber, string $currentSkill): void
{
    generationJsonResponse([
        'ok' => false,
        'status' => 'error',
        'message' => generationSafeFailureMessage(),
        'error' => 'Generation failed',
        'detail' => $detail,
        'job_id' => $jid > 0 ? (string) $jid : (string) ($job['id'] ?? ''),
        'questions_completed' => (int) ($job['questions_completed'] ?? 0),
        'num_questions' => (int) ($job['num_questions'] ?? 0),
        'failed_question_number' => $questionNumber,
        'failed_skill' => $currentSkill,
    ]);
}

try {
    $job_id = $_POST['job_id'] ?? '';

    if (empty($job_id)) {
        generationJsonResponse([
            'status' => 'error',
            'message' => 'Missing job ID.'
        ]);
    }

    $sql = "SELECT * FROM generation_jobs WHERE id = :job_id LIMIT 1";
    $stmt = $pdo->prepare($sql);
    $stmt->execute(['job_id' => $job_id]);
    $job = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$job) {
        generationJsonResponse([
            'status' => 'error',
            'message' => 'Generation job not found.'
        ]);
    }

    if ($job['questions_completed'] >= $job['num_questions']) {
        generationJsonResponse([
            'status' => 'complete',
            'message' => 'All questions already generated.'
        ]);
    }

$question_number = $job['questions_completed'] + 1;

$jid = (int) ($job['id'] ?? $job_id);
$workspaceId = (string) ($job['workspace_id'] ?? '');
$job['_batch_prior_questions'] = [];
if ($jid > 0 && $workspaceId !== '') {
    $job['_batch_prior_questions'] = fetchFormGeneratorPriorQuestionsInBatch($pdo, $workspaceId, $jid);
}

$skills_array = array_values(array_filter(array_map('trim', explode(',', (string) ($job['skills'] ?? '')))));

if ($skills_array === []) {
    $skills_array = formGeneratorResolveJobSkillsList(
        (string) ($job['condition_name'] ?? ''),
        (string) ($job['presentation'] ?? ''),
        [],
        (int) ($job['num_questions'] ?? 1)
    );
}

$current_skill = formGeneratorSkillForQuestionNumber($skills_array, $question_number);
$requestStart = microtime(true);
$trace = [
    'job_id' => $jid > 0 ? $jid : (int) $job_id,
    'question_index' => (int) $question_number,
    'skill' => (string) $current_skill,
    'started_at' => gmdate('c'),
    'openai_call_started' => false,
    'openai_call_returned' => false,
    'json_parse_succeeded' => false,
    'validator_ran' => false,
    'retry_triggered' => false,
    'db_insert_happened' => false,
];
generationStepTrace(array_merge($trace, ['event' => 'request_started']));

$job['skill'] = $current_skill;
$job['_job_skills'] = implode(', ', $skills_array);
$job['num_questions'] = (int) ($job['num_questions'] ?? 1);
$job['questions_completed'] = (int) ($job['questions_completed'] ?? 0);

if (function_exists('sbaSepsisResolveEmergencyScenarioPlanItem')) {
    $planItem = sbaSepsisResolveEmergencyScenarioPlanItem($job);
    if ($planItem !== null) {
        $job['_sepsis_emergency_scenario_plan'] = $planItem;
    }
}
$lockedSepsisEmergencyPlan = $job['_sepsis_emergency_scenario_plan'] ?? null;

if (function_exists('sbaPeResolveEmergencyScenarioPlanItem')) {
    $pePlanItem = sbaPeResolveEmergencyScenarioPlanItem($job);
    if ($pePlanItem !== null) {
        $job['_pe_emergency_scenario_plan'] = $pePlanItem;
    }
}
$lockedPeEmergencyPlan = $job['_pe_emergency_scenario_plan'] ?? null;

$presentationRaw = trim((string) ($job['presentation'] ?? ''));
$presentationInferred = false;
if ($presentationRaw === '') {
    $job['presentation'] = inferGeneratorPresentationMlaStyle($job);
    $job['_presentation_inferred'] = true;
    $presentationInferred = true;
}

$prompt = buildQuestionPrompt($job);

$presentation_warnings = [];
if ($presentationInferred) {
    $presentation_warnings[] = [
        'code' => 'presentation.inferred',
        'severity' => 'warning',
        'message' => 'Presentation was left blank; inferred for this item as: ' . $job['presentation'],
        'field' => 'presentation',
    ];
}

$validatorPath = __DIR__ . '/../../validators/style_validator.php';
$retry_used = false;
$retry_batch_duplicate_trigger = false;
$retry_partial_component_trigger = false;
$first_pass_warnings_snapshot = null;
$user_prompt = $prompt;
$question_data = null;
$repaired_question = [];

for ($gen_attempt = 0; $gen_attempt < 2; ++$gen_attempt) {
    $promptPhase = $gen_attempt === 0 ? 'initial' : 'retry';
    $trace['openai_call_started'] = true;
    generationStepTrace(array_merge($trace, ['event' => 'openai_call_started', 'phase' => $promptPhase]));
    $fetch = formGeneratorOpenAiChatJsonQuestion($user_prompt, $promptPhase, $jid);
    $trace['openai_call_returned'] = true;
    generationStepTrace(array_merge($trace, ['event' => 'openai_call_returned', 'phase' => $promptPhase]));
    if (!$fetch['ok']) {
        generationLogInvalidRawOutput((string) ($fetch['raw'] ?? ''), $promptPhase, $jid);
        generationStepTrace(array_merge($trace, [
            'event' => 'openai_or_json_failed',
            'phase' => $promptPhase,
            'error' => (string) ($fetch['error'] ?? 'AI did not return valid JSON.'),
            'elapsed_ms' => (int) round((microtime(true) - $requestStart) * 1000),
            'ended_at' => gmdate('c'),
            'openai_returned_before_timeout' => (bool) $trace['openai_call_returned'],
        ]));
        generationFailureResponse(
            $job,
            $jid,
            $fetch['error'] !== '' ? (string) $fetch['error'] : 'AI did not return valid JSON.',
            $question_number,
            $current_skill
        );
    }
    $trace['json_parse_succeeded'] = true;
    $question_data = $fetch['data'];
    $repaired_question = $question_data;
    $guard_warnings = [];
    $style_warnings = [];

    if (is_readable($validatorPath)) {
        $trace['validator_ran'] = true;
        try {
            require_once $validatorPath;
            if (function_exists('repairSbaStyle')) {
                $tmp = repairSbaStyle($question_data);
                if (is_array($tmp)) {
                    $repaired_question = $tmp;
                }
            }
            if (function_exists('sbaClinicalGuardRepairAcsInitialInvestigationTroponin')) {
                $pair = sbaClinicalGuardRepairAcsInitialInvestigationTroponin($repaired_question, $job, $current_skill);
                if (is_array($pair) && isset($pair[0]) && is_array($pair[0])) {
                    $repaired_question = $pair[0];
                }
                if (is_array($pair) && !empty($pair[1]) && is_array($pair[1])) {
                    $guard_warnings = $pair[1];
                }
            }
            if (function_exists('validateSbaStyle')) {
                $qStyle = $repaired_question;
                $qStyle['_tutor_comments'] = (string) ($job['tutor_comments'] ?? '');
                $qStyle['_current_skill'] = (string) $current_skill;
                $qStyle['_condition_name'] = (string) ($job['condition_name'] ?? '');
                $qStyle['_batch_prior_questions'] = $job['_batch_prior_questions'] ?? [];
                $qStyle['_job_skills'] = (string) ($job['_job_skills'] ?? $job['skills'] ?? '');
                if (is_array($lockedSepsisEmergencyPlan)) {
                    $qStyle['_sepsis_emergency_scenario_plan'] = $lockedSepsisEmergencyPlan;
                }
                if (is_array($lockedPeEmergencyPlan)) {
                    $qStyle['_pe_emergency_scenario_plan'] = $lockedPeEmergencyPlan;
                }
                try {
                    $validated = validateSbaStyle($qStyle);
                    $style_warnings = is_array($validated) ? $validated : [];
                } catch (Throwable $e) {
                    $style_warnings = [[
                        'code' => 'style_validator.runtime_error',
                        'severity' => 'warning',
                        'message' => 'Style validation could not complete; question was still saved.',
                        'field' => null,
                    ]];
                }
            }
        } catch (Throwable $e) {
            $repaired_question = $question_data;
            $guard_warnings = [];
            $style_warnings = [];
        }
    }

    $merged_eval = array_merge($guard_warnings, $style_warnings);
    $q_for_trigger = $repaired_question;
    $q_for_trigger['_current_skill'] = (string) $current_skill;
    $q_for_trigger['_tutor_comments'] = (string) ($job['tutor_comments'] ?? '');
    $q_for_trigger['_condition_name'] = (string) ($job['condition_name'] ?? '');
    $q_for_trigger['_batch_prior_questions'] = $job['_batch_prior_questions'] ?? [];
    $q_for_trigger['_job_skills'] = (string) ($job['_job_skills'] ?? $job['skills'] ?? '');
    if (is_array($lockedSepsisEmergencyPlan)) {
        $q_for_trigger['_sepsis_emergency_scenario_plan'] = $lockedSepsisEmergencyPlan;
    }
    if (is_array($lockedPeEmergencyPlan)) {
        $q_for_trigger['_pe_emergency_scenario_plan'] = $lockedPeEmergencyPlan;
    }

    if ($gen_attempt === 0
        && is_readable($validatorPath)
        && formGeneratorStyleWarningsTriggerSeriousRetry($merged_eval, $q_for_trigger)) {
        $first_pass_warnings_snapshot = array_merge($presentation_warnings, $guard_warnings, $style_warnings);
        $retry_batch_duplicate_trigger = formGeneratorRetryWasTriggeredForBatchDuplicate($merged_eval, $q_for_trigger);
        $retry_partial_component_trigger = formGeneratorRetryWasTriggeredForPartialComponentDistractors($merged_eval);
        $user_prompt = formGeneratorBuildRetryUserPrompt($prompt, $first_pass_warnings_snapshot, $current_skill, $q_for_trigger);
        $retry_used = true;
        $trace['retry_triggered'] = true;
        generationStepTrace(array_merge($trace, ['event' => 'retry_triggered']));
        continue;
    }
    break;
}

if (!is_array($repaired_question) || !generationQuestionHasRequiredFields($repaired_question)) {
    generationLogInvalidRawOutput(json_encode($repaired_question, JSON_UNESCAPED_UNICODE) ?: '', 'post_parse_validation', $jid);
    generationStepTrace(array_merge($trace, [
        'event' => 'post_parse_validation_failed',
        'elapsed_ms' => (int) round((microtime(true) - $requestStart) * 1000),
        'ended_at' => gmdate('c'),
    ]));
    generationFailureResponse(
        $job,
        $jid,
        'Generated question JSON was incomplete or malformed.',
        $question_number,
        $current_skill
    );
}

$style_warnings = array_merge($guard_warnings ?? [], $style_warnings ?? []);
$style_warnings = array_merge($presentation_warnings, $style_warnings);
$high_priority_warning_count = 0;
foreach ($style_warnings as $w) {
    if (is_array($w) && (($w['severity'] ?? '') === 'high')) {
        ++$high_priority_warning_count;
    }
}
generationStepTrace(array_merge($trace, [
    'event' => 'validation_summary',
    'retry_triggered' => (bool) $retry_used,
    'validator_warning_count' => count($style_warnings),
    'high_priority_warning_count' => $high_priority_warning_count,
]));

if (!is_readable($validatorPath)) {
    $style_warnings = $presentation_warnings;
} elseif ($retry_used && $first_pass_warnings_snapshot !== null) {
    $q_banner_ctx = [
        '_current_skill' => (string) $current_skill,
        '_condition_name' => (string) ($job['condition_name'] ?? ''),
        '_tutor_comments' => (string) ($job['tutor_comments'] ?? ''),
        '_batch_prior_questions' => $job['_batch_prior_questions'] ?? [],
    ];
    if (is_array($lockedSepsisEmergencyPlan)) {
        $q_banner_ctx['_sepsis_emergency_scenario_plan'] = $lockedSepsisEmergencyPlan;
    }
    if (is_array($lockedPeEmergencyPlan)) {
        $q_banner_ctx['_pe_emergency_scenario_plan'] = $lockedPeEmergencyPlan;
    }
    $style_warnings = array_merge([
        [
            'code' => 'form_generator.regenerated_once_after_structural_warnings',
            'severity' => 'info',
            'message' => 'This question was automatically regenerated once after structural validation issues. Review **all** warnings below — high-priority items may still be present. First-pass summary: ' . formGeneratorSummarizeWarningsForRetryBanner($first_pass_warnings_snapshot, $q_banner_ctx),
            'field' => null,
        ],
    ], $style_warnings);
}

if ($retry_used && $first_pass_warnings_snapshot !== null && function_exists('sbaStyleWarningsCapRetryFellBackToRepeatedAmoxicillin')) {
    $q_cap_retry = $repaired_question;
    $q_cap_retry['_current_skill'] = (string) $current_skill;
    $q_cap_retry['_condition_name'] = (string) ($job['condition_name'] ?? '');
    $q_cap_retry['_tutor_comments'] = (string) ($job['tutor_comments'] ?? '');
    $q_cap_retry['_batch_prior_questions'] = $job['_batch_prior_questions'] ?? [];
    $cap_retry_warnings = sbaStyleWarningsCapRetryFellBackToRepeatedAmoxicillin($q_cap_retry, $first_pass_warnings_snapshot);
    if ($cap_retry_warnings !== []) {
        $style_warnings = array_merge($style_warnings, $cap_retry_warnings);
    }
}

if ($retry_used && $retry_batch_duplicate_trigger) {
    $q_dup_check = $repaired_question;
    $q_dup_check['_current_skill'] = (string) $current_skill;
    $q_dup_check['_batch_prior_questions'] = $job['_batch_prior_questions'] ?? [];
    if (formGeneratorBatchStillDuplicatesPriorConcept($q_dup_check)) {
        $prior = formGeneratorBatchPriorConflictingAnswerConcept($q_dup_check);
        $detail = $prior !== null
            ? ' Earlier batch item (' . $prior['skill'] . ') keyed “' . $prior['answer'] . '”.'
            : '';
        $style_warnings[] = [
            'code' => 'batch_repetition.retry_failed_duplicate_answer_concept',
            'severity' => 'high',
            'message' => '[High priority] Automatic regeneration did not resolve the batch duplicate correct-answer concept.' . $detail . ' Choose a different keyed concept for this skill.',
            'field' => 'correct_answer',
        ];
    }
}

if ($retry_used && $retry_partial_component_trigger) {
    $q_frag_check = $repaired_question;
    $q_frag_check['_current_skill'] = (string) $current_skill;
    if (formGeneratorCountEmergencyPartialComponentFragmentDistractors($q_frag_check) >= 2) {
        $style_warnings[] = [
            'code' => 'retry_failed_partial_component_distractors',
            'severity' => 'high',
            'message' => '[High priority] Automatic regeneration still produced two or more partial-component distractors (e.g. IV fluids only and antibiotics only alongside a combined fluids-plus-antibiotics key). Replace the option set with whole emergency strategies, not mirror halves of the correct answer.',
            'field' => 'options',
        ];
    }
}

if ($retry_used && $first_pass_warnings_snapshot !== null && function_exists('formGeneratorMergePersistentHighPriorityWarningsAfterRetry')) {
    $q_retry_ctx = $repaired_question;
    $q_retry_ctx['_current_skill'] = (string) $current_skill;
    $q_retry_ctx['_condition_name'] = (string) ($job['condition_name'] ?? '');
    $q_retry_ctx['_tutor_comments'] = (string) ($job['tutor_comments'] ?? '');
    $q_retry_ctx['_batch_prior_questions'] = $job['_batch_prior_questions'] ?? [];
    if (is_array($lockedSepsisEmergencyPlan)) {
        $q_retry_ctx['_sepsis_emergency_scenario_plan'] = $lockedSepsisEmergencyPlan;
    }
    if (is_array($lockedPeEmergencyPlan)) {
        $q_retry_ctx['_pe_emergency_scenario_plan'] = $lockedPeEmergencyPlan;
    }
    $style_warnings = formGeneratorMergePersistentHighPriorityWarningsAfterRetry(
        $style_warnings,
        $first_pass_warnings_snapshot,
        $q_retry_ctx
    );
}

$title = $repaired_question['title'] ?? ('AI SBA Question ' . $question_number);
$stem = $repaired_question['stem'] ?? '';
$lead_in = $repaired_question['lead_in'] ?? '';
$option_a = $repaired_question['option_a'] ?? '';
$option_b = $repaired_question['option_b'] ?? '';
$option_c = $repaired_question['option_c'] ?? '';
$option_d = $repaired_question['option_d'] ?? '';
$option_e = $repaired_question['option_e'] ?? '';
$correct_answer_letter = strtoupper(trim($repaired_question['correct_answer_letter'] ?? ''));
$correct_answer = '';
$answer_options_by_letter = [
    'A' => $option_a,
    'B' => $option_b,
    'C' => $option_c,
    'D' => $option_d,
    'E' => $option_e
];
if (isset($answer_options_by_letter[$correct_answer_letter])) {
    $correct_answer = $answer_options_by_letter[$correct_answer_letter];
}
$justification = $repaired_question['justification'] ?? '';

$sql = "INSERT INTO questions (
    workspace_id,
    source_type,
    title,
    stem,
    lead_in,
    option_a,
    option_b,
    option_c,
    option_d,
    option_e,
    correct_answer,
    correct_answer_letter,
    justification,
    specialty,
    summative_formative,
    condition_name,
    presentation,
    skill,
    year_level,
    tutor_comments,
    tags,
    status
) VALUES (
    :workspace_id,
    :source_type,
    :title,
    :stem,
    :lead_in,
    :option_a,
    :option_b,
    :option_c,
    :option_d,
    :option_e,
    :correct_answer,
    :correct_answer_letter,
    :justification,
    :specialty,
    :summative_formative,
    :condition_name,
    :presentation,
    :skill,
    :year_level,
    :tutor_comments,
    :tags,
    :status
)";

$stmt = $pdo->prepare($sql);
$stmt->execute([
    'workspace_id' => $job['workspace_id'],
    'source_type' => 'generated',
    'title' => $title,
    'stem' => $stem,
    'lead_in' => $lead_in,
    'option_a' => $option_a,
    'option_b' => $option_b,
    'option_c' => $option_c,
    'option_d' => $option_d,
    'option_e' => $option_e,
    'correct_answer' => $correct_answer,
    'correct_answer_letter' => $correct_answer_letter,
    'justification' => $justification,
    'specialty' => $job['specialty'],
    'summative_formative' => $job['summative_formative'],
    'condition_name' => $job['condition_name'],
    'presentation' => $job['presentation'],
    'skill' => $current_skill,
    'year_level' => $job['year_level'],
    'tutor_comments' => $job['tutor_comments'],
    'tags' => buildGeneratorQuestionTags(
        $presentationInferred,
        (string) ($job['presentation'] ?? ''),
        $jid > 0 ? $jid : null,
        is_array($lockedSepsisEmergencyPlan) ? (string) ($lockedSepsisEmergencyPlan['slot'] ?? '') : null,
        is_array($lockedPeEmergencyPlan) ? (string) ($lockedPeEmergencyPlan['slot'] ?? '') : null
    ),
    'status' => 'Draft'
]);
$trace['db_insert_happened'] = true;
generationStepTrace(array_merge($trace, ['event' => 'db_insert_happened']));

$question_id = $pdo->lastInsertId();

$new_completed = $job['questions_completed'] + 1;
$new_status = ($new_completed >= $job['num_questions']) ? 'complete' : 'running';

$sql = "UPDATE generation_jobs
        SET questions_completed = :questions_completed, status = :status
        WHERE id = :job_id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
    'questions_completed' => $new_completed,
    'status' => $new_status,
    'job_id' => $job_id
]);

generationStepTrace(array_merge($trace, [
    'event' => 'request_completed',
    'elapsed_ms' => (int) round((microtime(true) - $requestStart) * 1000),
    'ended_at' => gmdate('c'),
    'retry_triggered' => (bool) $retry_used,
    'validator_warning_count' => count($style_warnings),
    'high_priority_warning_count' => $high_priority_warning_count,
    'openai_returned_before_timeout' => (bool) $trace['openai_call_returned'],
]));

generationJsonResponse([
    'ok' => true,
    'status' => 'success',
    'message' => 'One question generated.',
    'job_id' => $job_id,
    'questions_completed' => $new_completed,
    'num_questions' => $job['num_questions'],
    'job_status' => $new_status,
    'retry_used' => $retry_used,
    'style_warnings' => $style_warnings,
    'question' => [
        'id' => $question_id,
        'title' => $title,
        'stem' => $stem,
        'lead_in' => $lead_in,
        'option_a' => $option_a,
        'option_b' => $option_b,
        'option_c' => $option_c,
        'option_d' => $option_d,
        'option_e' => $option_e,
        'correct_answer_letter' => $correct_answer_letter,
        'correct_answer' => $correct_answer,
        'justification' => $justification,
        'specialty' => $job['specialty'],
	'condition_name' => $job['condition_name'],
	'presentation' => $job['presentation'],
	'skill' => $current_skill
    ]
]);
} catch (Throwable $e) {
    generationStepTrace([
        'event' => 'request_exception',
        'time' => gmdate('c'),
        'error' => $e->getMessage(),
    ]);
    error_log('[generate_next_question] Exception: ' . $e->getMessage());
    generationJsonResponse([
        'ok' => false,
        'status' => 'error',
        'message' => generationSafeFailureMessage(),
        'error' => 'Generation failed',
        'detail' => 'Unexpected server error during question generation.',
    ]);
} finally {
    restore_error_handler();
}