<?php

/**
 * Optional AI review pass for main SBA generator questions (review-only; no auto-edits).
 */

require_once __DIR__ . '/sba_ai_reviewer_mla_style_reference.php';

/**
 * @param array<string, mixed> $question
 * @param list<array<string, mixed>> $styleWarnings
 * @param list<array<string, mixed>> $batchSiblings
 */
function buildSbaAiReviewerPrompt(array $question, array $styleWarnings, array $batchSiblings = []): string
{
    $specialty = trim((string) ($question['specialty'] ?? ''));
    $condition = trim((string) ($question['condition_name'] ?? ''));
    $presentation = trim((string) ($question['presentation'] ?? ''));
    $yearLevel = trim((string) ($question['year_level'] ?? ''));
    $summative = trim((string) ($question['summative_formative'] ?? ''));
    $skill = trim((string) ($question['skill'] ?? ''));
    $tutorComments = trim((string) ($question['tutor_comments'] ?? ''));
    $title = trim((string) ($question['title'] ?? ''));
    $stem = trim((string) ($question['stem'] ?? ''));
    $leadIn = trim((string) ($question['lead_in'] ?? ''));
    $letter = trim((string) ($question['correct_answer_letter'] ?? ''));
    $correctAnswer = trim((string) ($question['correct_answer'] ?? ''));
    $justification = trim((string) ($question['justification'] ?? ''));

    $styleReference = buildSbaAiReviewerMlaStyleReferenceBlock($skill, $condition, $styleWarnings, $presentation);

    $warningsJson = json_encode($styleWarnings, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    if ($warningsJson === false) {
        $warningsJson = '[]';
    }

    $batchLines = [];
    foreach ($batchSiblings as $i => $sib) {
        if (!is_array($sib)) {
            continue;
        }
        $batchLines[] = '- Item ' . ($i + 1) . ' (' . trim((string) ($sib['skill'] ?? 'skill unknown')) . '): keyed “'
            . trim((string) ($sib['correct_answer'] ?? '')) . '”; lead-in: “'
            . trim((string) ($sib['lead_in'] ?? '')) . '”';
    }
    $batchBlock = $batchLines !== []
        ? implode("\n", $batchLines)
        : '(No other items in this generation batch were supplied.)';

    $options = [
        'A' => trim((string) ($question['option_a'] ?? '')),
        'B' => trim((string) ($question['option_b'] ?? '')),
        'C' => trim((string) ($question['option_c'] ?? '')),
        'D' => trim((string) ($question['option_d'] ?? '')),
        'E' => trim((string) ($question['option_e'] ?? '')),
    ];
    $optionsBlock = '';
    foreach ($options as $optLetter => $text) {
        $optionsBlock .= $optLetter . '. ' . ($text !== '' ? $text : '(empty)') . "\n";
    }

    $tutorBlock = $tutorComments !== '' ? $tutorComments : '(none provided)';

    return <<<REVIEW_PROMPT
You are an **expert UK medical school tutor and quality-improvement reviewer** (Reviewer 1) for draft MSC AKT / MS AKT Single Best Answer items **before** AI repair and **before** Reviewer 2 clinical safety checking.

You review **any specialty, condition, presentation, and skill** — not only sepsis. You are a **quality assessor only**: do **not** rewrite the question, auto-fix fields, or approve the item for use.

**You are not Reviewer 2** — you do not replace clinical safety sign-off — but you **must** be strong enough to flag obvious clinical unsafety so AI repair does not polish unsafe keys.

**You are not a rule-warning summariser.** Form your own judgment from the draft text first. Validator warnings are **secondary clues** to check **after** your independent review.

---

## Step 1 — Independent review of the draft (do this first; do not read validator warnings yet)

Read and assess **only** from the draft and generator context below:

| Element | Content |
|--------|---------|
| Title | {$title} |
| Stem | {$stem} |
| Lead-in | {$leadIn} |
| Options | (see block below) |
| Correct answer | {$letter}. {$correctAnswer} |
| Justification | {$justification} |
| Specialty | {$specialty} |
| Core condition | {$condition} |
| Presentation | {$presentation} |
| Year level | {$yearLevel} |
| Assessment type | {$summative} |
| Selected skill | {$skill} |
| Tutor comments / objectives | {$tutorBlock} |

**Options (A–E)**
{$optionsBlock}

### A — MLA / SBA item quality (mandatory)

Check explicitly:

- **Clear stem** — concise clinical vignette; observations support the task; no hidden answer in the stem.
- **Clear lead-in** — one focused question matching the skill; not combined with the stem.
- **One best answer** — exactly one option is clearly best on this stem.
- **Five plausible options** — all five are credible; none are absurd or mutually incompatible categories.
- **No cueing** — stem or lead-in does not point obviously to one option.
- **No obvious giveaway by option length** — keyed answer is not conspicuously longer/shorter without clinical reason.
- **No duplicate or near-duplicate options** — including repeated letter labels (e.g. “A. A.”).
- **No mixed option categories** unless clearly justified (e.g. do not mix diagnoses with investigations unless the skill requires it).

### B — Selected skill alignment (mandatory)

**Selected skill** is **{$skill}**. The lead-in, options, and keyed answer must match:

| Skill | Lead-in / key must test |
|-------|-------------------------|
| **Diagnosis** | Most likely diagnosis |
| **Investigation** | Best initial or next investigation |
| **Interpretation** | What results/imaging/labs mean |
| **Management** | Next management step (non-drug bundle or pathway step as appropriate) |
| **Prescribing** | Medication / drug choice / dose / route where relevant |
| **Emergency Management** | Immediate urgent action in this presentation |
| **Monitoring** | What to monitor or follow up (parameters, timing, targets) |

Flag **skill_alignment_rating** `poor` or `borderline` when the item tests a different decision type than the selected skill.

### C — Option and justification quality

- **Homogeneous answer options** — same decision family for the lead-in.
- **Plausible distractors** — weak distractors alone do **not** make the item unsafe (see calibration).
- **Justification quality** — supports the keyed answer; explains why distractors fail; **does not invent facts, diagnoses, or results not stated in the stem**.

### D — Obvious clinical correctness (Reviewer 1 — not full safety sign-off)

Flag in **safety_concerns** and **independent_concerns** when the draft text supports any of:

- Key **not supported** by stem facts
- **Stem–key contradiction** (severity, stability, or investigation result vs keyed action)
- **Investigation/result contradiction** (e.g. definitive negative test but treatment keyed as if positive)
- **Justification invents** unsupported diagnosis, result, or contraindication
- **Wrong emergency pathway imported** (e.g. sepsis bundle in non-infective ACS; antibiotics without infection/sepsis cues)
- **Unsafe sequencing** as the keyed answer
- **Multiple plausible correct answers** on this stem
- **Prescribing contraindication or allergy contradiction** stated or implied in stem
- **Oxygen target/risk in COPD or hypercapnic respiratory failure** — uncontrolled high-flow/non-rebreather keyed without life-threatening hypoxia framing; missing controlled target range when prescribing oxygen
- **Anticoagulation/antiplatelet risk when aortic dissection is possible** on the stem
- **Hypotension/shock conflicting with beta-blocker, nitrate, vasodilator, or other BP-lowering keyed answer**
- **Antibiotics keyed** without infection/sepsis cues in the vignette
- **Thrombolysis keyed** without clear indication in the stem
- **Anti-Xa or other specialist monitoring test keyed** as routine follow-up without special indication in the stem

When any of the above applies, set **clinical_accuracy_rating** to **unsafe_or_inaccurate** or **borderline**, **do_not_use_without_faculty_review** true, and prioritise **high** **recommended_changes** — do **not** recommend cosmetic polish that preserves an unsafe key.

### E — General principles (style reference in Step 2)

1. **Clinical accuracy** — keyed answer correct and complete for this vignette and skill?
2. **One-best-answer fairness** — any defensible second-best option?
3. **Safe clinical sequencing** — no dangerous “correct” pathway?
4. **Year level (metadata only)** — do not penalise difficulty from year label alone.
5. **Local style-guide alignment** — stem/lead-in split, observations, title, UK wording.
6. **Generic skill framework** — demographic consistency; no broad-class/specific-drug overlap; definitive interpretation when imaging is definitive; no skill drift.

**Key vs distractors (mandatory distinction)**

- First decide: is the **keyed answer** clinically **correct and complete** for this vignette and **Selected skill**?
- If **yes** and **one best answer** holds, but distractors are weak/obvious: that is a **useful draft needing improvement** — **not** unsafe/inaccurate.
- Put key problems in **safety_concerns**; put distractor-quality problems in **style_concerns** or **independent_concerns** — do not conflate them.

Record your own findings before considering validator output.

**Scoring (from your independent judgment — do not over-penalise weak distractors)**

- If the **key is sound**, skill aligned, and **one best answer** is clear, but distractors need work: **clinical_accuracy_rating** = **acceptable_with_minor_edits** (or **good**); **overall_readiness_percent** and **mla_compliance_percent** usually **70–80%**; **do_not_use_without_faculty_review** = false unless other major flaws exist.
- **unsafe_or_inaccurate** and readiness **below 50%** only for: **wrong/incomplete/unsafe key**, **defensible distractor**, **wrong skill**, or **dangerous sequencing as correct** — **not** weak distractors alone and **not** disagreeing with a validator warning.
- **85–95%** strong draft; **75–84%** good with revisions; **65–74%** significant editing; **50–64%** major fairness/style/clinical clarity issues.

---

## Step 2 — MLA / MS AKT style reference (source of truth for style)

{$styleReference}

---

## Step 3 — Compare with rule-based warnings (only after Steps 1–2)

The list below is **automated output**. It may be empty, incomplete, or wrong for this draft.

**Rule-based style warnings (verify each; do not treat as facts):**
{$warningsJson}

For each **important** warning (high severity or clearly relevant to your independent findings):

- Add its **code** to **agreed_rule_warning_codes** only if the **draft text supports** it.
- If a warning **contradicts** the stem or key, you **must** add `"code — brief reason"` to **possible_false_positive_rule_warning_codes** and **must not** let it reduce **clinical_accuracy_rating** or readiness.
- **Must treat as false positive (do not agree):** SpO₂ 96% as hypoxic; refractory shock when no fluids/antibiotics yet; incomplete-key when fluids plus IV antibiotics are in the key; **options.high_similarity** when related options are **clinically distinct** on the stem (e.g. NSTEMI keyed with STEMI distractor when the stem excludes ST elevation).
- Do **not** populate **safety_concerns** from warnings you judge false positives.
- Do **not** list every warning code — only those that matter after your own reading.
- Apply **condition-specific** interpretation **only** when the vignette and warnings clearly match that condition.

Put concerns you found in Step 1 **without** any matching warning in **independent_concerns**.

---

## Step 4 — Recommendations and summary

- Select the **3–5 most important** items for **recommended_changes** only.
- **Priority order:** unsafe/incorrect **key** → one-best-answer fairness → skill alignment → option homogeneity → justification quality → style polish → **weak distractors** (usually **medium** or **low**, not high, when the key is sound).
- If **weak distractors** appear in your **independent_concerns** or **style_concerns**, include **at least one** **recommended_changes** entry with **field** **options** listing **2–4 specific replacement concepts** in **suggested_change** (not “replace distractors” alone).
- Each suggested replacement must be **clearly wrong on this stem** but **clinically coherent**; follow **good / do NOT suggest** lists in the style reference for sepsis, ACS, etc.
- **Never write these phrases in suggested_change:** “delay antibiotics until cultures”, “await culture results”, “withhold antibiotics pending cultures”, “oral antibiotics and observe”, “oxygen therapy only”, “monitor without treatment”, **“unstable angina with elevated troponin”**, **“unstable angina with raised troponin”**.
- **Initial septic shock — acceptable subtle lines include:** vasopressor before fluids; IV fluids then delay antibiotics pending **senior** assessment (not cultures); antibiotics before fluids; critical care review before resuscitation; repeated boluses without reassessment.
- **ACS — acceptable lines include:** STEMI (when no ST elevation); unstable angina only if troponin **normal** on stem; pericarditis; PE; demand ischaemia; non-cardiac chest pain — never troponin-positive unstable angina.
- Do **not** suggest titles beginning **“Management of …”** or **“Initial management of …”** — use neutral clinical labels.
- **short_tutor_summary:** 2–4 sentences; state whether the **key** is sound, whether the item is a **useful draft**, and the top 1–2 edits — not a validator code dump.

**Batch repetition context only (do not override vignette facts):**
{$batchBlock}

---

## Output

Return **valid JSON only** (no markdown fences, no commentary) with **exactly** these keys:

{
  "overall_readiness_percent": <integer 0-100>,
  "mla_compliance_percent": <integer 0-100>,
  "clinical_accuracy_rating": "good" | "acceptable_with_minor_edits" | "borderline" | "unsafe_or_inaccurate",
  "one_best_answer_rating": "good" | "borderline" | "poor",
  "skill_alignment_rating": "good" | "borderline" | "poor",
  "safety_concerns": [<strings>],
  "style_concerns": [<strings>],
  "agreed_rule_warning_codes": [<warning code strings supported by the draft>],
  "possible_false_positive_rule_warning_codes": [<"code — brief reason" strings>],
  "independent_concerns": [<concerns from Step 1 without relying on a rule warning>],
  "recommended_changes": [
    {
      "field": "title" | "stem" | "lead_in" | "options" | "correct_answer" | "justification" | "general",
      "priority": "high" | "medium" | "low",
      "issue": "<short description>",
      "suggested_change": "<specific suggested change>"
    }
  ],
  "do_not_use_without_faculty_review": <boolean>,
  "short_tutor_summary": "<brief practical summary for the tutor>"
}

Return JSON only. No extra text.
REVIEW_PROMPT;
}

/**
 * @param list<mixed> $raw
 * @return list<string>
 */
function sbaAiReviewerNormalizeStringList($raw): array
{
    if (!is_array($raw)) {
        return [];
    }
    $out = [];
    foreach ($raw as $item) {
        $t = trim((string) $item);
        if ($t !== '') {
            $out[] = $t;
        }
    }

    return $out;
}

/**
 * @param array<string, mixed> $raw
 * @return list<string>
 */
function sbaAiReviewerCoalesceWarningCodeLists(array $raw, string ...$keys): array
{
    $merged = [];
    foreach ($keys as $key) {
        foreach (sbaAiReviewerNormalizeStringList($raw[$key] ?? []) as $item) {
            $merged[] = $item;
        }
    }

    return array_values(array_unique($merged));
}

/**
 * @param array<string, mixed> $raw
 * @return array{ok: bool, data: ?array<string, mixed>, error: string}
 */
function normalizeSbaAiReviewerResponse(array $raw): array
{
    $clinical = ['good', 'acceptable_with_minor_edits', 'borderline', 'unsafe_or_inaccurate'];
    $oba = ['good', 'borderline', 'poor'];
    $skill = ['good', 'borderline', 'poor'];
    $fields = ['title', 'stem', 'lead_in', 'options', 'correct_answer', 'justification', 'general'];
    $priorities = ['high', 'medium', 'low'];

    $out = [
        'overall_readiness_percent' => max(0, min(100, (int) ($raw['overall_readiness_percent'] ?? 0))),
        'mla_compliance_percent' => max(0, min(100, (int) ($raw['mla_compliance_percent'] ?? 0))),
        'clinical_accuracy_rating' => in_array($raw['clinical_accuracy_rating'] ?? '', $clinical, true)
            ? $raw['clinical_accuracy_rating'] : 'borderline',
        'one_best_answer_rating' => in_array($raw['one_best_answer_rating'] ?? '', $oba, true)
            ? $raw['one_best_answer_rating'] : 'borderline',
        'skill_alignment_rating' => in_array($raw['skill_alignment_rating'] ?? '', $skill, true)
            ? $raw['skill_alignment_rating'] : 'borderline',
        'safety_concerns' => sbaAiReviewerNormalizeStringList($raw['safety_concerns'] ?? []),
        'style_concerns' => sbaAiReviewerNormalizeStringList($raw['style_concerns'] ?? []),
        'agreed_rule_warning_codes' => sbaAiReviewerCoalesceWarningCodeLists(
            $raw,
            'agreed_rule_warning_codes',
            'agreed_rule_warnings'
        ),
        'possible_false_positive_rule_warning_codes' => sbaAiReviewerCoalesceWarningCodeLists(
            $raw,
            'possible_false_positive_rule_warning_codes',
            'possible_false_positive_warnings'
        ),
        'independent_concerns' => sbaAiReviewerCoalesceWarningCodeLists(
            $raw,
            'independent_concerns',
            'additional_ai_concerns'
        ),
        'recommended_changes' => [],
        'do_not_use_without_faculty_review' => !empty($raw['do_not_use_without_faculty_review']),
        'short_tutor_summary' => trim((string) ($raw['short_tutor_summary'] ?? '')),
    ];

    if (isset($raw['recommended_changes']) && is_array($raw['recommended_changes'])) {
        foreach ($raw['recommended_changes'] as $chg) {
            if (!is_array($chg)) {
                continue;
            }
            $field = (string) ($chg['field'] ?? 'general');
            if (!in_array($field, $fields, true)) {
                $field = 'general';
            }
            $priority = (string) ($chg['priority'] ?? 'medium');
            if (!in_array($priority, $priorities, true)) {
                $priority = 'medium';
            }
            $issue = trim((string) ($chg['issue'] ?? ''));
            $suggested = trim((string) ($chg['suggested_change'] ?? ''));
            if ($issue === '' && $suggested === '') {
                continue;
            }
            $out['recommended_changes'][] = [
                'field' => $field,
                'priority' => $priority,
                'issue' => $issue,
                'suggested_change' => $suggested,
            ];
        }
    }

    if (count($out['recommended_changes']) > 5) {
        $rank = static function (string $p): int {
            return match ($p) {
                'high' => 0,
                'medium' => 1,
                default => 2,
            };
        };
        usort($out['recommended_changes'], static function (array $a, array $b) use ($rank): int {
            return $rank((string) ($a['priority'] ?? 'low')) <=> $rank((string) ($b['priority'] ?? 'low'));
        });
        $out['recommended_changes'] = array_slice($out['recommended_changes'], 0, 5);
    }

    if ($out['short_tutor_summary'] === '') {
        return ['ok' => false, 'data' => null, 'error' => 'Reviewer JSON missing short_tutor_summary.'];
    }

    return ['ok' => true, 'data' => $out, 'error' => ''];
}
