<?php

declare(strict_types=1);

require_once __DIR__ . '/prompt_variant.php';
require_once __DIR__ . '/prompt_variant_review.php';

/**
 * Variant-only optional tutor review model (mirrors main SBA 5.1; read-only).
 */
function variantOpenAiOptionalReviewModel(): string
{
    if (defined('OPENAI_VARIANT_OPTIONAL_REVIEW_MODEL')) {
        $m = trim((string) OPENAI_VARIANT_OPTIONAL_REVIEW_MODEL);
        if ($m !== '') {
            return $m;
        }
    }

    return variantOpenAiReviewModel();
}

/**
 * @param array<string, mixed> $sourceQuestion may include _optional_review_repair_context from client
 * @return array{repair_applied: bool, repair_failed: bool, repair_error: string}
 */
function variantOptionalReviewRepairContextFromSource(array $sourceQuestion): array
{
    $ctx = is_array($sourceQuestion['_optional_review_repair_context'] ?? null)
        ? $sourceQuestion['_optional_review_repair_context']
        : [];
    $error = trim((string) ($ctx['repair_error'] ?? ''));
    $failed = !empty($ctx['repair_failed']) || $error !== '';

    return [
        'repair_applied' => !empty($ctx['repair_applied']),
        'repair_failed' => $failed,
        'repair_error' => $error,
    ];
}

/**
 * @param array<string, mixed> $out normalized optional review
 * @param array{repair_applied: bool, repair_failed: bool, repair_error: string} $repairCtx
 * @return array<string, mixed>
 */
function variantOptionalReviewApplyRepairGuards(array $out, array $repairCtx): array
{
    if (empty($repairCtx['repair_failed'])) {
        return $out;
    }

    if (($out['overall_review'] ?? '') === 'good') {
        $out['overall_review'] = 'major_edit_needed';
    }

    $checks = is_array($out['checks'] ?? null) ? $out['checks'] : [];
    if (($checks['single_best_answer'] ?? '') === 'pass') {
        $checks['single_best_answer'] = 'concern';
    }
    if (($checks['clinical_realism'] ?? '') === 'pass') {
        $checks['clinical_realism'] = 'concern';
    }
    $out['checks'] = $checks;

    $note = 'Automatic repair was unavailable; the unrepaired draft may lack key clinical evidence.';
    $summary = trim((string) ($out['summary'] ?? ''));
    if ($summary !== '' && stripos($summary, 'automatic repair') === false) {
        $out['summary'] = $summary . ' ' . $note;
    } elseif ($summary === '') {
        $out['summary'] = $note;
    }

    return $out;
}

/**
 * @param array<string, mixed> $sourceQuestion
 * @param array<string, mixed> $variant DB row (repaired Draft)
 */
function buildVariantOptionalReviewPrompt(array $sourceQuestion, array $variant): string
{
    $repairCtx = variantOptionalReviewRepairContextFromSource($sourceQuestion);
    $GLOBALS['__variant_optional_review_repair_ctx'] = $repairCtx;

    $data = is_array($sourceQuestion['data'] ?? null) ? $sourceQuestion['data'] : [];
    $rowNumber = (string) ($sourceQuestion['row_number'] ?? '');

    $questionTitle = embedVariantField($data['Question title'] ?? '');
    $questionText = embedVariantField($data['Question text'] ?? '');
    $correctAnswer = embedVariantField($data['Correct answer'] ?? '');
    $correctAnswerLetter = embedVariantField($data['Correct answer letter'] ?? '');
    $justificationSeed = embedVariantField($data['Justification'] ?? $data['justification'] ?? '');
    $learningOutcome = embedVariantField(
        $data['Learning outcome'] ?? $data['Learning outcomes'] ?? $data['Learning Outcome'] ?? ''
    );

    $seedOptA = embedVariantField($data['Option A'] ?? '', 2000);
    $seedOptB = embedVariantField($data['Option B'] ?? '', 2000);
    $seedOptC = embedVariantField($data['Option C'] ?? '', 2000);
    $seedOptD = embedVariantField($data['Option D'] ?? '', 2000);
    $seedOptE = embedVariantField($data['Option E'] ?? '', 2000);

    $metaBlock = implode("\n", [
        variantPromptMetaLine('Learning outcome(s)', $learningOutcome),
        variantPromptMetaLine('Core condition', embedVariantField($data['Core condition'] ?? '')),
        variantPromptMetaLine('Presentation', embedVariantField($data['Presentation'] ?? '')),
        variantPromptMetaLine('Skill', embedVariantField($data['Skill'] ?? '')),
        variantPromptMetaLine('Specialty', embedVariantField($data['Specialty'] ?? '')),
    ]);

    $vTitle = embedVariantField($variant['title'] ?? '');
    $vStem = embedVariantField($variant['stem'] ?? '');
    $vLeadIn = embedVariantField($variant['lead_in'] ?? '');
    $vJust = embedVariantField($variant['justification'] ?? '');
    $vLetter = embedVariantField($variant['correct_answer_letter'] ?? '');
    $vAnswer = embedVariantField($variant['correct_answer'] ?? '');
    $vTutor = embedVariantField($variant['tutor_comments'] ?? '');
    $vA = embedVariantField($variant['option_a'] ?? '', 2000);
    $vB = embedVariantField($variant['option_b'] ?? '', 2000);
    $vC = embedVariantField($variant['option_c'] ?? '', 2000);
    $vD = embedVariantField($variant['option_d'] ?? '', 2000);
    $vE = embedVariantField($variant['option_e'] ?? '', 2000);

    $repairFailed = !empty($repairCtx['repair_failed']);
    $repairErrorLine = $repairCtx['repair_error'] !== ''
        ? variantPromptMetaLine('Repair error (client)', embedVariantField($repairCtx['repair_error']))
        : '';

    $baselineIntro = $repairFailed
        ? 'You are reviewing the **current unrepaired Draft variant** below. **Automatic repair was unavailable** for this variant — treat the draft as unverified and be **especially cautious**.'
        : 'You are reviewing a **generated variant** that has already been through automatic repair (current Draft below).';

    $repairFailedBlock = $repairFailed
        ? <<<'REPAIR_BLOCK'

**AUTOMATIC REPAIR UNAVAILABLE — STRICT REVIEW REQUIRED**
- Automatic repair did **not** apply. The current Draft may still contain gaps from generation.
- Do **not** set `overall_review` to **good** unless the current unrepaired draft is **fully defensible** on its own (clinical correctness, single best answer, MLA style, and **sufficient stem evidence**).
- Specifically check whether the **stem** contains enough clinical evidence to support the correct diagnosis, management decision, or investigation choice implied by the source and options.
- If key evidence from the source question is **missing** from the stem (investigations, examination findings, lab results, or other answer-supporting details), rate at least **major_edit_needed** or **regenerate** — not **good**.
- **HUS / haemolytic uraemic syndrome items:** if the question tests HUS microbiology or diagnosis, the stem should usually include enough evidence for HUS (e.g. bloody diarrhoea plus anaemia, thrombocytopenia, red cell fragments, and acute kidney injury). If these are absent, treat this as a **major** issue and do not rate **good**.
- Prefer `field`: **general** guidance over risky field replacements when the draft may be incomplete.
- Use `checks.single_best_answer` or `checks.clinical_realism` as **concern** when stem evidence is insufficient.

REPAIR_BLOCK
        : '';

    $conservativeBaseline = $repairFailed
        ? 'Automatic repair did **not** run successfully — treat the current Draft as provisional. Suggest edits **only when they clearly improve** clinical quality, stem evidence, single-best-answer clarity, or MLA style.'
        : 'The automatic repair step has already produced a coherent Draft. Suggest edits **only when they clearly improve** clinical quality, single-best-answer clarity, or MLA style.';

    return <<<PROMPT
{$baselineIntro}
{$repairFailedBlock}
{$repairErrorLine}

**OPTIONAL TUTOR REVIEW**

**CRITICAL — ADVISORY SUGGESTIONS ONLY**
- Compare the **current repaired Draft variant** below with the **original source** question.
- Provide structured suggestions for a human tutor. You must **not** rewrite or replace the full question in your output.
- Set `do_not_auto_apply` and `tutor_review_required` to true in your JSON.
- Prefer **at most 6** high-value suggestions; omit empty, cosmetic or low-value items.

**OVERALL REVIEW = GOOD — VERY FEW FIELD REPLACEMENTS**
- If you set `overall_review` to **good**, the repaired draft is already acceptable.
- For **good** items: usually return **no** field-specific suggestions (`suggestions` empty or only `field`: **general** / **tags** advisory notes with empty `suggested_text`).
- Do **not** offer one-click field replacements for good items unless there is a **clear** clinical, patient-safety, single-best-answer, or MLA style improvement — not preference, not synonym swap, not “sounds better”.
- If the item is good, you may still add brief **general** comments in `summary` or a `general` suggestion with guidance in `reason` only (no `suggested_text` for partial edits).
- **Exception — title on good items:** you may still suggest a **title** replacement when the current title is vague, too broad, misleading, inconsistent with the repaired variant, or not useful for item-bank organisation (see **TITLE SUGGESTIONS**). Use `priority`: **low** unless the existing title is misleading (then **medium**). Do not suggest title changes when the existing title is already clear and accurate.

**ASSESS**
- Same learning point (or clearly inferable equivalent) as the source
- Correct answer **concept** preserved (formal diagnosis ≠ symptom label, e.g. Laryngitis ≠ Hoarseness of voice)
- Clinical correctness and clinical realism
- Guideline-sensitive details (age thresholds, first-line choices, investigation timing)
- MLA AKT / MS AKT style: concise stem, clear lead-in, units, EDI wording, examination tone
- One clearly best answer; distractors same class and stem-supported
- **Sufficient difference** from the original (not a near-copy); variant should test the same learning point differently

**SOURCE (row {$rowNumber})**
{$metaBlock}
- Title: {$questionTitle}
- Question text: {$questionText}
- Correct answer letter: {$correctAnswerLetter}
- Correct answer: {$correctAnswer}
- Justification: {$justificationSeed}
- Options:
  A. {$seedOptA}
  B. {$seedOptB}
  C. {$seedOptC}
  D. {$seedOptD}
  E. {$seedOptE}

**CURRENT REPAIRED DRAFT VARIANT**
- Title: {$vTitle}
- Stem: {$vStem}
- Lead-in: {$vLeadIn}
- Options:
  A. {$vA}
  B. {$vB}
  C. {$vC}
  D. {$vD}
  E. {$vE}
- Correct answer letter: {$vLetter}
- Correct answer text: {$vAnswer}
- Justification: {$vJust}
- Tutor comments: {$vTutor}

**SUGGESTION RULES — FULL-FIELD REPLACEMENT ONLY**
- Use `field` exactly: title | stem | lead_in | option_a | option_b | option_c | option_d | option_e | correct_answer | justification | tutor_comments | tags | general
- For any field-specific suggestion (title, stem, lead_in, option_a–e, correct_answer, justification, tutor_comments), `suggested_text` must be the **complete replacement text for that entire field** as it should appear after the edit.
- **Never** return a partial sentence, clause, or mid-vignette fragment as `suggested_text` for stem, lead_in, or title.
- **Never** return a partial stem that drops essential clinical context already in the current draft (age, sex, presenting problem, duration, examination findings, investigation results, or other answer-supporting details).
- If you only want to suggest a **partial** improvement (e.g. add examination findings, tighten one phrase), put that guidance in `reason` and use `field`: **general** — do **not** use field-specific replacement with incomplete `suggested_text`.
- `current_text`: quote the **full** relevant text from the **current repaired draft** for that field
- `suggested_text`: the **full** replacement for that field only (not vague advice, not a diff, not an excerpt)
- Every field-level `suggested_text` must be: (a) **complete** for that field, (b) **grammatically correct** in context, (c) **no worse** than the current wording, and (d) **demonstrably improving** the item (clinical, safety, SBA structure, or clear MLA style fix). If you cannot meet all four, use `field`: **general** instead.
- Do **not** return field-level `suggested_text` unless it is a **complete, grammatical replacement for the whole field** — never a truncated or broken sentence (e.g. do **not** change “his observations are normal” to “his normal”, or end a stem with “and his normal.”).
- Do **not** remove nouns required for grammatical meaning, such as **observations**, **examination**, **pulse**, **BP**, **blood pressure**, **respiratory rate**, **visual acuity**, **heart sounds**, **chest**, **mass** or **masses**.
- If the suggested improvement is only **minor wording** or formatting, provide it as a **`general`** advisory note in `reason` (no `suggested_text`) rather than a field-level replacement.
- `reason`: brief plain-English reason (may describe a partial improvement when `field` is general). For field-specific suggestions, `reason` must state the concrete improvement, not “preference” or “clarity” alone.
- `priority`: low | medium | high
- **stem / lead_in**: `suggested_text` must read as a complete stem or lead-in on its own; preserve vignette structure (opening presentation → relevant findings → task-ready context)
- **stem — observation wording (IMPORTANT):** Do **not** use `field`: **stem** with `suggested_text` when your only change is observation/vital-sign formatting (pulse, heart rate, BP, blood pressure, respiratory rate, oxygen saturation). If observation wording is already clinically clear and grammatical, **leave it unchanged**. Observation-format improvements should usually be `field`: **general** with guidance in `reason` only (no `suggested_text`), because the tutor UI will not offer one-click Accept for stem replacements that change observation wording unless they clearly improve the item without introducing clipped grammar.
- **option_a–option_e**: `suggested_text` must be **one option’s text only** (not a list of A–E options)
- **correct_answer**: `suggested_text` should be a single letter A–E and/or a brief answer-concept clarification (not a full vignette)
- **justification**: `suggested_text` must be a complete justification paragraph (not empty or a fragment)
- **tags**, **general**, **tutor_comments** (when advisory only): use when no safe full-field replacement is possible; tutor reads these in the general suggestions area

**CONSERVATIVE STYLE — DO NOT INTRODUCE REGRESSIONS**
{$conservativeBaseline} Do **not** rewrite large parts of the question for preference alone. Avoid changing option wording unless there is a clear clinical or style problem.

1. **Grammar and observation phrasing (stems especially) — NO CLIPPED SENTENCES; AVOID STEM FIELD REPLACEMENTS FOR OBSERVATIONS**
   - **Avoid** `field`: **stem** suggestions that only reformat observations. Use `field`: **general** instead.
   - If the current stem already has acceptable, grammatical observation wording (e.g. “His pulse is 52 bpm, blood pressure is 102/62 mmHg”), do **not** suggest a stem replacement — omit the suggestion or comment in `general` only.
   - Do **not** suggest field replacements that create **clipped sentence grammar** for observations.
   - Do **not** change correct observation wording into clipped shorthand (e.g. do **not** change “pulse **is** 70 bpm” to “pulse 70 bpm”, or “blood pressure **is** 120/80 mmHg” to “BP 120/80 mmHg”, in sentence-style text).
   - Do **not** replace a grammatically correct observation sentence with clipped list-style fragments (e.g. do **not** change “His pulse **is** 52 bpm, blood pressure **is** 110/68 mmHg” into “His pulse 52 bpm, BP 110/68 mmHg”).
   - If the current stem already has acceptable observation wording, **prefer leaving it unchanged**.
   - If you change observation wording, use a **complete grammatical sentence** (with “is”/“are”) **or** a **complete list-style** observation phrase — not a hybrid.
   - **Never** use clipped forms such as “His pulse 60 bpm”, “Her pulse 80 bpm”, “His BP 110/70 mmHg”, “Her BP 145/85 mmHg”, “His blood pressure 110/70 mmHg”, or “Her blood pressure 145/85 mmHg” (possessive + vital + number **without** “is”/“are”).
   - **Never** start a stem with “His/Her/Their …” and then use bare list vitals (e.g. “His pulse 52 bpm, blood pressure 110/68 mmHg” or “Her pulse 80 bpm and BP 135/85 mmHg”).
   - Use **either** full **sentence style** **or** **list-style** observation phrasing — **do not mix** them in the same stem.
     - **Sentence style** (example): “His pulse is 60 bpm and his blood pressure is 110/70 mmHg.”
     - **List style** (example): “temperature 36.7°C, pulse 60 bpm, BP 110/70 mmHg, respiratory rate 7 breaths per minute and oxygen saturation is 86% breathing air.” (bare vitals in a comma-separated list — **not** “His pulse 60 bpm” inside the list; list style must **not** begin with His/Her/Their)
   - Do **not** mix sentence style (“His pulse is…”) with bare list chunks (“BP 110/70 mmHg” or “blood pressure 110/70 mmHg” without “is”) in the same stem.
   - Do not drop “is” / “are” after “His/Her pulse”, “His/Her BP”, “blood pressure”, “heart rate”, “respiratory rate”, “oxygen saturation”, etc. when using sentence style.

2. **Lead-ins**
   - Prefer **“What?”** for MLA AKT lead-ins.
   - Do **not** change “What” to “Which” unless there is a clear, specific reason (e.g. genuine “which investigation next” when multiple investigations are listed). If unsure, keep “What?”.

3. **Scientific and medical capitalisation**
   - Use **“E. coli”**, never “E. Coli”.
   - Use lower case for disease/condition names mid-sentence (**mastitis**, not **Mastitis**) unless a proper noun or start of sentence.
   - Preserve standard UK medical spelling and unit style already in the draft unless correcting a clear error.

4. **Only suggest with clear value**
   - Do **not** suggest cosmetic rewrites that risk new errors.
   - If current wording is acceptable, **omit** the suggestion.
   - Do **not** change terminology for synonym preference alone.
   - Do **not** suggest field replacements for minor punctuation unless fixing a real error.

5. **Respect the repaired Draft**
   - Treat the current repaired variant as the baseline.
   - Field-specific `suggested_text` must remain a **complete safe replacement** for that field (see rules above).
   - Partial improvement ideas → `field`: **general** with explanation in `reason`, not incomplete `suggested_text`.

**TITLE SUGGESTIONS (ADVISORY ONLY)**
- Title suggestions are **low priority** unless the existing title is **misleading** (then use `priority`: **medium**).
- Suggest a new title **only** when the current title is vague, too broad, misleading, inconsistent with the repaired variant, or not useful for item-bank organisation.
- **Do not** suggest a title change when the existing title is already clear, accurate, and describes the tested clinical task or concept.
- Keep titles **concise** (typically under ~12 words).
- Prefer titles that describe the **tested clinical task or concept**, for example:
  - "Initial management of ductal carcinoma in situ"
  - "Mechanism of opioid-induced post-operative hypoxia"
  - "Initial management of benign-appearing breast lump"
  - "First-line treatment for uncomplicated hypertension"
- Title suggestions must **not** change the learning point, answer concept, status, tags, export logic, or approval logic — wording for organisation/clarity only.
- Return title suggestions using:
  - `field`: **title**
  - `current_text`: the current title from the repaired draft
  - `suggested_text`: the proposed **complete replacement** title (not a fragment or diff)
  - `reason`: brief reason (e.g. vague, too broad, misleading, inconsistent with vignette)
  - `priority`: **low** (or **medium** if misleading)

OUTPUT — return this JSON object only:

{
  "overall_review": "good | minor_edit_needed | major_edit_needed | regenerate",
  "summary": "short plain-English summary",
  "checks": {
    "clinical_equivalence": "pass | concern | unclear",
    "correct_answer_concept": "pass | concern | unclear",
    "guideline_sensitive_details": "pass | concern | unclear",
    "mla_style": "pass | minor_issues | major_issues",
    "single_best_answer": "pass | concern | unclear",
    "distractor_quality": "good | minor_issues | major_issues | unclear",
    "clinical_realism": "pass | concern | unclear"
  },
  "suggestions": [
    {
      "field": "title",
      "current_text": "string",
      "suggested_text": "string",
      "reason": "string",
      "priority": "low | medium | high"
    }
  ],
  "do_not_auto_apply": true,
  "tutor_review_required": true
}
PROMPT;
}

/**
 * @param array<string, mixed> $raw
 * @return array{ok: bool, data: ?array<string, mixed>, error: string}
 */
function normalizeVariantOptionalReviewResponse(array $raw): array
{
    $overall = ['good', 'minor_edit_needed', 'major_edit_needed', 'regenerate'];
    $ternary = ['pass', 'concern', 'unclear'];
    $style = ['pass', 'minor_issues', 'major_issues'];
    $distractor = ['good', 'minor_issues', 'major_issues', 'unclear'];
    $priority = ['low', 'medium', 'high'];
    $fields = [
        'title', 'stem', 'lead_in', 'option_a', 'option_b', 'option_c', 'option_d', 'option_e',
        'correct_answer', 'justification', 'tutor_comments', 'tags', 'general',
    ];

    $pick = static function (array $allowed, $value, string $default) {
        $v = strtolower(trim((string) $value));

        return in_array($v, $allowed, true) ? $v : $default;
    };

    $summary = trim((string) ($raw['summary'] ?? ''));
    if ($summary === '') {
        return ['ok' => false, 'data' => null, 'error' => 'Optional reviewer JSON missing summary.'];
    }

    $suggestions = [];
    if (isset($raw['suggestions']) && is_array($raw['suggestions'])) {
        foreach ($raw['suggestions'] as $item) {
            if (!is_array($item)) {
                continue;
            }
            $field = $pick($fields, $item['field'] ?? '', 'general');
            $current = trim((string) ($item['current_text'] ?? ''));
            $suggested = trim((string) ($item['suggested_text'] ?? ''));
            $reason = trim((string) ($item['reason'] ?? ''));
            if ($reason === '' && $suggested === '' && $current === '') {
                continue;
            }
            $suggestions[] = [
                'field' => $field,
                'current_text' => $current,
                'suggested_text' => $suggested,
                'reason' => $reason,
                'priority' => $pick($priority, $item['priority'] ?? '', 'medium'),
            ];
            if (count($suggestions) >= 12) {
                break;
            }
        }
    }

    $overallReview = $pick($overall, $raw['overall_review'] ?? '', 'minor_edit_needed');
    $checksRaw = is_array($raw['checks'] ?? null) ? $raw['checks'] : [];

    if ($overallReview === 'good') {
        $fieldSpecific = ['title', 'stem', 'lead_in', 'option_a', 'option_b', 'option_c', 'option_d', 'option_e', 'correct_answer', 'justification', 'tutor_comments'];
        $kept = [];
        foreach ($suggestions as $item) {
            $field = (string) ($item['field'] ?? 'general');
            $suggested = trim((string) ($item['suggested_text'] ?? ''));
            if (!in_array($field, $fieldSpecific, true) || $suggested === '') {
                $kept[] = $item;
                continue;
            }
            if ($field === 'title' && trim((string) ($item['reason'] ?? '')) !== '') {
                $kept[] = $item;
                continue;
            }
            if (($item['priority'] ?? '') === 'high' && $suggested !== '') {
                $kept[] = $item;
            }
        }
        $suggestions = $kept;
    }

    if (
        ($checksRaw['correct_answer_concept'] ?? '') === 'concern'
        || ($checksRaw['guideline_sensitive_details'] ?? '') === 'concern'
        || ($checksRaw['clinical_equivalence'] ?? '') === 'concern'
    ) {
        if ($overallReview === 'good') {
            $overallReview = 'minor_edit_needed';
        }
    }
    if (
        ($checksRaw['mla_style'] ?? '') === 'major_issues'
        || ($checksRaw['single_best_answer'] ?? '') === 'concern'
        || ($checksRaw['distractor_quality'] ?? '') === 'major_issues'
    ) {
        if ($overallReview === 'good') {
            $overallReview = 'major_edit_needed';
        }
    }

    $out = [
        'overall_review' => $overallReview,
        'summary' => $summary,
        'checks' => [
            'clinical_equivalence' => $pick($ternary, $checksRaw['clinical_equivalence'] ?? '', 'unclear'),
            'correct_answer_concept' => $pick($ternary, $checksRaw['correct_answer_concept'] ?? '', 'unclear'),
            'guideline_sensitive_details' => $pick($ternary, $checksRaw['guideline_sensitive_details'] ?? '', 'unclear'),
            'mla_style' => $pick($style, $checksRaw['mla_style'] ?? '', 'minor_issues'),
            'single_best_answer' => $pick($ternary, $checksRaw['single_best_answer'] ?? '', 'unclear'),
            'distractor_quality' => $pick($distractor, $checksRaw['distractor_quality'] ?? '', 'unclear'),
            'clinical_realism' => $pick($ternary, $checksRaw['clinical_realism'] ?? '', 'unclear'),
        ],
        'suggestions' => $suggestions,
        'do_not_auto_apply' => true,
        'tutor_review_required' => true,
    ];

    $repairCtx = is_array($GLOBALS['__variant_optional_review_repair_ctx'] ?? null)
        ? $GLOBALS['__variant_optional_review_repair_ctx']
        : [];
    unset($GLOBALS['__variant_optional_review_repair_ctx']);
    if (!empty($repairCtx['repair_failed'])) {
        $out = variantOptionalReviewApplyRepairGuards($out, $repairCtx);
    }

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