<?php

namespace Modules\VetAI\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Modules\VetAI\Entities\VaiSetting;

/**
 * Voice → text transcription, plus an optional LLM pass that splits a raw
 * transcript into the four SOAP sections. Driver-agnostic: any provider that
 * exposes the OpenAI /v1/audio/transcriptions API works (OpenAI, Groq,
 * self-hosted whisper.cpp, etc).
 *
 * Audio is forwarded straight to the provider and never written to disk.
 */
class VaiTranscriptionService
{
    /** Where each whisper-flavoured driver lives. */
    private const ENDPOINTS = [
        'whisper_openai'        => 'https://api.openai.com/v1',
        'whisper_groq'          => 'https://api.groq.com/openai/v1',
        'whisper_openai_compat' => null, // taken from base_url
    ];

    /** Default model per driver — overridable in settings. */
    private const DEFAULT_MODELS = [
        'whisper_openai'        => 'whisper-1',
        'whisper_groq'          => 'whisper-large-v3-turbo',
        'whisper_openai_compat' => 'whisper-large-v3-turbo',
    ];

    /**
     * Default veterinary-domain prompt. Whisper uses this to bias decoding
     * toward (a) the language the prompt is written in - so an English
     * prompt strongly nudges language detection toward English even when
     * the configured language hint is 'auto', and (b) the vocabulary in
     * the prompt itself - so common veterinary terms transcribe correctly
     * instead of being approximated to nearby English words.
     *
     * Whisper allows up to ~224 tokens here; keep it dense.
     */
    private const DEFAULT_PROMPT = 'Veterinary consultation transcript. Common terms: '
        . 'dog, cat, puppy, kitten, vomiting, diarrhea, loose stools, not eating, '
        . 'inappetent, anorexic, lethargy, weight loss, coughing, sneezing, '
        . 'eye discharge, nasal discharge, breathing difficulty, dyspnea, seizure, '
        . 'limping, lameness, itching, pruritus, fever, dehydration, weakness, '
        . 'amoxicillin, meloxicam, cephalexin, prednisolone, metronidazole, '
        . 'subcutaneous, intramuscular, oral, twice daily, BID, SID, TID.';

    /**
     * @param  string|null  $languageHint  ISO-639-1 code (en, ar, ...) or 'auto'
     * @param  string|null  $prompt        Whisper bias prompt. Falls back to DEFAULT_PROMPT.
     * @return array{text:string, duration_sec:?float, provider:string, model:string}
     */
    public static function transcribe(VaiSetting $setting, UploadedFile $audio, ?string $languageHint = null, ?string $prompt = null): array
    {
        $cfg = $setting->transcriptionConfig();
        $provider = $cfg['provider'];

        if ($provider === 'webspeech') {
            // The browser handles webspeech client-side; the server should
            // never reach this path. Returned as a safety net.
            throw new \RuntimeException('Web Speech runs client-side — call /vet-ai/transcribe only for server-side drivers.');
        }

        $base = $cfg['base_url'] ?: self::ENDPOINTS[$provider] ?? null;
        if (!$base) {
            throw new \RuntimeException("Transcription provider '{$provider}' has no base URL configured.");
        }
        $model = $cfg['model'] ?: (self::DEFAULT_MODELS[$provider] ?? 'whisper-1');
        $lang  = $languageHint ?: $cfg['language'];

        // Merge caller-supplied prompt with the default veterinary vocabulary.
        // The caller-supplied part typically contains the visible chip labels
        // for the current section, so Whisper is primed for the exact words
        // the doctor is most likely about to say.
        $finalPrompt = trim(($prompt ? $prompt . '. ' : '') . self::DEFAULT_PROMPT);
        // Whisper caps the prompt at ~224 tokens; trim conservatively.
        if (mb_strlen($finalPrompt) > 800) {
            $finalPrompt = mb_substr($finalPrompt, 0, 800);
        }

        $response = Http::timeout(120)
            ->withToken((string) $cfg['api_key'])
            ->attach('file', file_get_contents($audio->getRealPath()), $audio->getClientOriginalName() ?: 'audio.webm', [
                'Content-Type' => $audio->getMimeType() ?: 'audio/webm',
            ])
            ->asMultipart()
            ->post(rtrim($base, '/') . '/audio/transcriptions', array_filter([
                'model'           => $model,
                'language'        => ($lang && $lang !== 'auto') ? $lang : null,
                'prompt'          => $finalPrompt,
                'response_format' => 'json',
                'temperature'     => '0',
            ]));

        if (!$response->successful()) {
            throw new \RuntimeException('Transcription failed: ' . $response->status() . ' ' . $response->body());
        }
        $json = $response->json();

        return [
            'text'         => trim((string) ($json['text'] ?? '')),
            'duration_sec' => isset($json['duration']) ? (float) $json['duration'] : null,
            'provider'     => $provider,
            'model'        => $model,
        ];
    }

    /**
     * Run a quick LLM pass that splits the transcript into the four SOAP
     * sections. Reuses the main assistant credentials (so a clinic that
     * already configured Claude/GPT/Gemini doesn't need a second key).
     *
     * Returns: ['subjective'=>..., 'objective'=>..., 'assessment'=>..., 'plan'=>...]
     */
    public static function structureSoap(VaiSetting $setting, string $transcript, ?string $context = null): array
    {
        $system = "You are a veterinary scribe. Given a free-text consultation transcript,"
            . " split it into the four SOAP fields. Keep the original wording — DO NOT invent"
            . " details, dosages, or diagnoses. Return STRICTLY a JSON object with the keys"
            . ' "subjective", "objective", "assessment", "plan" (string values, empty string'
            . " when nothing was dictated for that section). No prose around the JSON.";
        $user = ($context ? "Patient context: {$context}\n\n" : '')
              . "Transcript:\n---\n{$transcript}\n---";

        $raw = (new AiClient)->chat($setting, $system, $user);
        if (empty($raw['ok'])) {
            // LLM failed — return raw transcript so the doctor doesn't lose the dictation.
            return ['subjective' => $transcript, 'objective' => '', 'assessment' => '', 'plan' => ''];
        }

        // Trim Markdown code-fences if the model wrapped the JSON.
        $clean = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', trim($raw['text'] ?? ''));
        $parsed = json_decode($clean, true);
        if (!is_array($parsed)) {
            // Fallback — dump everything in subjective so the doctor doesn't lose it.
            return ['subjective' => $transcript, 'objective' => '', 'assessment' => '', 'plan' => ''];
        }
        return [
            'subjective' => (string) ($parsed['subjective'] ?? ''),
            'objective'  => (string) ($parsed['objective']  ?? ''),
            'assessment' => (string) ($parsed['assessment'] ?? ''),
            'plan'       => (string) ($parsed['plan']       ?? ''),
        ];
    }

    /**
     * Synonym-aware chip matcher. Given a free-text complaint and a list of
     * standard symptom labels, returns the subset of labels that semantically
     * apply (handles synonyms like "threw up" -> "Vomiting", "won't eat" ->
     * "Not eating", "loose stools" -> "Diarrhea").
     *
     * The LLM is told to ONLY pick from the supplied label list — never to
     * invent new ones — and the controller re-validates against the same list
     * before returning, so the frontend can trust the response.
     *
     * Returns: ['matched' => ['Vomiting', 'Not eating', ...]]
     */
    public static function matchChips(VaiSetting $setting, string $text, array $labels): array
    {
        $text = trim($text);
        if ($text === '' || empty($labels)) {
            return ['matched' => []];
        }
        // De-dup, drop empties, cap size so a runaway list can't blow the prompt.
        $labels = array_values(array_unique(array_filter(array_map('strval', $labels), 'strlen')));
        if (count($labels) > 120) {
            $labels = array_slice($labels, 0, 120);
        }

        $system = "You are a clinical NLP classifier. Given a free-text patient complaint "
            . "and a fixed list of standard symptom labels, return EXACTLY the labels that "
            . "the complaint describes (including synonyms and paraphrases). "
            . "Rules:\n"
            . "1. Return ONLY labels that appear verbatim in the supplied list — never invent.\n"
            . "2. Match on meaning, not just substrings. 'threw up' = 'Vomiting'. "
            . "'won't eat' / 'inappetent' / 'anorexic' = 'Not eating'. 'loose stools' = 'Diarrhea'.\n"
            . "3. Be conservative — if a label is only tangentially related, skip it.\n"
            . "4. Output STRICTLY a JSON object: {\"matched\": [\"Label1\", \"Label2\"]}. "
            . "No prose. Empty list if nothing applies.";
        $user = "Complaint:\n---\n{$text}\n---\n\n"
              . "Available labels (case-sensitive, return exactly as written):\n"
              . json_encode($labels, JSON_UNESCAPED_UNICODE);

        $raw = (new AiClient)->chat($setting, $system, $user);
        if (empty($raw['ok'])) {
            return ['matched' => [], 'error' => $raw['error'] ?? 'AI call failed'];
        }

        $clean = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', trim($raw['text'] ?? ''));
        $parsed = json_decode($clean, true);
        if (!is_array($parsed) || !isset($parsed['matched']) || !is_array($parsed['matched'])) {
            return ['matched' => []];
        }

        // Intersect with the supplied list — the LLM occasionally returns
        // slight casing or punctuation variants; honour only exact matches.
        $allowed = array_flip($labels);
        $matched = [];
        foreach ($parsed['matched'] as $candidate) {
            $candidate = (string) $candidate;
            if (isset($allowed[$candidate])) {
                $matched[] = $candidate;
            }
        }
        return ['matched' => array_values(array_unique($matched))];
    }

    /**
     * Item #4b — propose which specialist exam panels should be expanded
     * for this visit based on the doctor's Subjective text.
     *
     * Given a free-text chief complaint (and optionally a list of which
     * specialist exam keys are available), returns a subset of those keys
     * the LLM judges most relevant.
     *
     * Returns: ['suggested' => ['orthopedic','dermatology'], 'reason' => '...']
     * Fails soft: empty list when AI is unconfigured or errors.
     */
    public static function suggestSpecialistExams(VaiSetting $setting, string $subjective, array $availableExams): array
    {
        $subjective = trim($subjective);
        if ($subjective === '' || empty($availableExams)) {
            return ['suggested' => []];
        }
        if (mb_strlen($subjective) < 12) {
            return ['suggested' => []]; // not enough signal
        }
        if (!$setting->isConfigured()) {
            return ['suggested' => [], 'note' => 'ai-not-configured'];
        }

        $exams = array_values(array_unique(array_filter(array_map('strval', $availableExams), 'strlen')));
        if (count($exams) > 30) $exams = array_slice($exams, 0, 30);

        $system = "You are a veterinary triage assistant. Given the doctor's "
            . "Subjective notes (chief complaint + history) for a patient, "
            . "return the shortlist of specialist exam panels the doctor "
            . "should open on the Objective tab. Lean toward suggesting "
            . "MORE panels rather than fewer when symptoms are non-specific "
            . "or concerning (better to over-suggest than to miss a workup).\n\n"
            . "Clinical mapping reference:\n"
            . "- 'limping' / 'lame' / 'won't bear weight' / 'stiffness' -> orthopedic\n"
            . "- 'seizure' / 'head tilt' / 'circling' / 'paresis' / 'weakness in legs' / 'incoordination' / 'tremor' -> neurological\n"
            . "- 'itching' / 'hair loss' / 'red skin' / 'scratching' / 'hot spot' / 'odour' / 'rash' -> dermatology\n"
            . "- 'eye discharge' / 'red eye' / 'squinting' / 'cloudy eye' / 'vision loss' -> ophthalmic\n"
            . "- 'heart murmur' / 'collapse' / 'syncope' / 'exercise intolerance' / 'coughing in older dog' / 'cyanosis' -> cardiology\n"
            . "- 'bad breath' / 'won't eat hard food' / 'loose tooth' / 'oral mass' / 'drooling' -> dental\n"
            . "- ANY urinary cue (not urinating / has not urinated / hasn't urinated / can't urinate / straining to urinate / blood in urine / haematuria / pee / pees / urination / PU/PD / polyuria / polydipsia / FLUTD / blocked cat / urethral obstruction / incontinent) -> urology\n"
            . "- ANY systemic / vital-sign cue (low temperature / hypothermia / temperature is low / temp is low / fever / pyrexia / temperature is high / lethargic / lethargy / not eating + lethargy / dehydrated / dehydration / vomiting + diarrhoea / collapsed / collapse / depressed / pale gums / anaemia / sepsis suspect / icteric / jaundice / yellow gums / unexplained weight loss / weak / weakness) -> internal_medicine\n\n"
            . "EMERGENCY DOUBLE-FLAGS (when these phrases appear, ALWAYS include the listed panels):\n"
            . "- 'blocked cat' / 'cat not urinating' -> urology + internal_medicine (hyperkalemia / azotemia risk)\n"
            . "- 'collapse' / 'severe lethargy' + 'low temp' -> cardiology + internal_medicine\n"
            . "- 'haematuria' + 'lethargy' -> urology + internal_medicine\n"
            . "- 'recurring seizures' / 'cluster seizures' -> neurological + internal_medicine\n\n"
            . "Rules:\n"
            . "1. Return ONLY keys from the supplied list. Never invent.\n"
            . "2. Empty list is OK only for very vague complaints (e.g. 'wellness check').\n"
            . "3. Output STRICTLY a JSON object: {\"suggested\": [\"key1\",\"key2\"], \"reason\": \"<one short clinical sentence naming the cue you found>\"}.";
        $user = "Subjective:\n---\n{$subjective}\n---\n\n"
              . "Available specialist exam keys (return only these):\n"
              . json_encode($exams, JSON_UNESCAPED_UNICODE);

        $raw = (new AiClient)->chat($setting, $system, $user);
        if (empty($raw['ok'])) {
            \Log::warning('AI suggestSpecialistExams: LLM call failed', [
                'error' => $raw['error'] ?? null,
                'subjective_excerpt' => mb_substr($subjective, 0, 120),
            ]);
            return ['suggested' => [], 'note' => $raw['error'] ?? 'llm-failed'];
        }
        $rawText = trim($raw['text'] ?? '');
        $clean = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', $rawText);
        $parsed = json_decode($clean, true);
        if (!is_array($parsed) || !isset($parsed['suggested']) || !is_array($parsed['suggested'])) {
            \Log::warning('AI suggestSpecialistExams: unparseable response', [
                'raw' => mb_substr($rawText, 0, 400),
                'subjective_excerpt' => mb_substr($subjective, 0, 120),
            ]);
            return ['suggested' => [], 'note' => 'unparseable-llm-response'];
        }

        // Case-insensitive + fuzzy key matching. Past bug: the LLM
        // sometimes returns "Urology" or "URINARY" instead of "urology";
        // strict case-sensitive array_flip lookup dropped these silently.
        $lowerAllowed = [];
        foreach ($exams as $e) { $lowerAllowed[strtolower($e)] = $e; }

        $picked = [];
        $unmatched = [];
        foreach ($parsed['suggested'] as $k) {
            $kk = strtolower(trim((string) $k));
            if (isset($lowerAllowed[$kk])) {
                $picked[] = $lowerAllowed[$kk];
                continue;
            }
            // Fuzzy: prefix / substring against the allowed list. Catches
            // "urological", "urin", etc. that the LLM might emit.
            $found = false;
            foreach ($lowerAllowed as $lk => $orig) {
                if ($kk !== '' && ($kk === substr($lk, 0, strlen($kk)) || strpos($lk, $kk) !== false || strpos($kk, $lk) !== false)) {
                    $picked[] = $orig;
                    $found = true;
                    break;
                }
            }
            if (!$found) $unmatched[] = (string) $k;
        }
        if (!empty($unmatched) && empty($picked)) {
            \Log::warning('AI suggestSpecialistExams: zero matches after fuzzy filter', [
                'raw_keys' => $unmatched,
                'allowed'  => array_keys($lowerAllowed),
                'subjective_excerpt' => mb_substr($subjective, 0, 120),
            ]);
        }

        return [
            'suggested' => array_values(array_unique($picked)),
            'reason'    => (string) ($parsed['reason'] ?? ''),
        ];
    }

    /**
     * Extract a structured list of clinical findings from free-text
     * Subjective notes, with severity flags.
     *
     * Different from matchChips() (which maps text to predefined chip
     * labels) — this returns whatever the AI thinks the doctor is
     * describing, regardless of whether a chip exists. The frontend
     * shows the result as a "Key clinical findings" pills row under the
     * Subjective textarea, with red borders for emergency-tier signs.
     *
     * Returns: [
     *   'findings' => [
     *      ['label' => 'Hypothermia', 'severity' => 'critical', 'quote' => 'temperature is low'],
     *      ['label' => 'Anuria', 'severity' => 'critical', 'quote' => 'has not urinated for three days'],
     *      ['label' => 'Lethargy', 'severity' => 'mild', 'quote' => 'seems sluggish'],
     *   ],
     *   'overall_concern' => 'high' | 'moderate' | 'low',
     * ]
     */
    public static function extractSymptoms(VaiSetting $setting, string $subjective): array
    {
        $subjective = trim($subjective);
        if ($subjective === '' || mb_strlen($subjective) < 10) {
            return ['findings' => [], 'overall_concern' => 'low'];
        }
        if (!$setting->isConfigured()) {
            return ['findings' => [], 'overall_concern' => 'low', 'note' => 'ai-not-configured'];
        }

        $system = "You are a veterinary clinical-notes parser. Read the "
            . "doctor's Subjective notes and extract every concrete clinical "
            . "finding the owner reported or the doctor observed.\n\n"
            . "Severity scale:\n"
            . "- 'critical' = emergency / red-flag (hypothermia, hyperthermia >40°C, "
            . "  anuria/blocked urinary tract, collapse, pale/cyanotic mucous "
            . "  membranes, seizure activity, severe dehydration, dyspnea, "
            . "  active haemorrhage, head trauma, suspected sepsis, deep-pain absent)\n"
            . "- 'moderate' = significant but not emergent (persistent vomiting, "
            . "  diarrhoea, fever, lameness, pruritus, lethargy + reduced appetite)\n"
            . "- 'mild' = present but minor (occasional sneeze, intermittent "
            . "  itching, mild halitosis)\n\n"
            . "Rules:\n"
            . "1. Each finding gets a SHORT canonical label (e.g. 'Hypothermia', "
            . "  'Anuria', 'Right hind lameness', 'Fever 40.2°C', 'Polydipsia').\n"
            . "2. Include the exact phrase from the source as 'quote' so the "
            . "  doctor can verify where you got it from.\n"
            . "3. overall_concern: 'high' if ANY critical finding, otherwise "
            . "  'moderate' if any moderate, otherwise 'low'.\n"
            . "4. Empty findings array is valid for non-medical text.\n"
            . "5. Output STRICTLY a JSON object: "
            . "{\"findings\":[{\"label\":\"...\",\"severity\":\"critical|moderate|mild\","
            . "\"quote\":\"...\"}], \"overall_concern\":\"high|moderate|low\"}.";
        $user = "Subjective notes:\n---\n{$subjective}\n---";

        $raw = (new AiClient)->chat($setting, $system, $user);
        if (empty($raw['ok'])) {
            \Log::warning('AI extractSymptoms: LLM call failed', [
                'error' => $raw['error'] ?? null,
                'subjective_excerpt' => mb_substr($subjective, 0, 120),
            ]);
            return ['findings' => [], 'overall_concern' => 'low', 'note' => $raw['error'] ?? 'llm-failed'];
        }
        $rawText = trim($raw['text'] ?? '');
        $clean = preg_replace('/^```(?:json)?\s*|\s*```$/m', '', $rawText);
        $parsed = json_decode($clean, true);
        if (!is_array($parsed) || !isset($parsed['findings']) || !is_array($parsed['findings'])) {
            \Log::warning('AI extractSymptoms: unparseable response', [
                'raw' => mb_substr($rawText, 0, 400),
                'subjective_excerpt' => mb_substr($subjective, 0, 120),
            ]);
            return ['findings' => [], 'overall_concern' => 'low', 'note' => 'unparseable-llm-response'];
        }
        // Sanitise + cap so a runaway LLM can't fill the page.
        $valid = [];
        foreach (array_slice($parsed['findings'], 0, 20) as $f) {
            if (!is_array($f)) continue;
            $label = trim((string) ($f['label'] ?? ''));
            $sev   = strtolower((string) ($f['severity'] ?? 'mild'));
            $quote = trim((string) ($f['quote'] ?? ''));
            if ($label === '') continue;
            if (!in_array($sev, ['critical','moderate','mild'], true)) $sev = 'mild';
            $valid[] = [
                'label'    => mb_substr($label, 0, 80),
                'severity' => $sev,
                'quote'    => mb_substr($quote, 0, 240),
            ];
        }
        $concern = strtolower((string) ($parsed['overall_concern'] ?? 'low'));
        if (!in_array($concern, ['high','moderate','low'], true)) {
            // Derive from findings if model didn't supply.
            $concern = 'low';
            foreach ($valid as $v) {
                if ($v['severity'] === 'critical') { $concern = 'high'; break; }
                if ($v['severity'] === 'moderate') { $concern = 'moderate'; }
            }
        }
        return ['findings' => $valid, 'overall_concern' => $concern];
    }

    /**
     * Strip non-medical side-talk from a dictation transcript.
     *
     * Whisper transcribes EVERYTHING - the doctor's clinical dictation,
     * background conversation with the owner, kid noises, "uh let me see",
     * "can you hold him still please", small talk. This pass uses the main
     * assistant LLM to retain ONLY content that's clinically relevant
     * (history, observations, findings, plan, instructions, doses).
     *
     * Returns: ['cleaned' => string, 'ok' => bool, 'note' => ?string]
     *
     * Fails soft - if the LLM is unconfigured or errors out, returns the
     * original text unchanged so the doctor never loses dictation.
     */
    public static function cleanMedicalContent(VaiSetting $setting, string $text): array
    {
        $text = trim($text);
        if ($text === '') {
            return ['cleaned' => '', 'ok' => true];
        }
        // Skip the round-trip for very short transcripts - the cleanup
        // overhead isn't worth it and the LLM can over-edit short text.
        if (mb_strlen($text) < 25) {
            return ['cleaned' => $text, 'ok' => true, 'note' => 'too-short-to-clean'];
        }
        if (!$setting->isConfigured()) {
            return ['cleaned' => $text, 'ok' => false, 'note' => 'ai-not-configured'];
        }

        $system = "You are a veterinary scribe editor. Given a raw voice-transcript "
            . "from a clinic consultation, return ONLY the clinically relevant content "
            . "and remove everything else.\n\n"
            . "REMOVE:\n"
            . "- Small talk and pleasantries ('hello', 'how are you', 'good morning')\n"
            . "- Asides to staff or owner that aren't clinical ('hold him please', 'can you pass me the otoscope')\n"
            . "- Filler words and self-corrections ('uh', 'um', 'let me see', 'wait actually')\n"
            . "- Background conversation, kid noises, phone ringing\n"
            . "- Comments about scheduling, billing, payment, parking\n\n"
            . "KEEP (verbatim, preserve the doctor's exact wording):\n"
            . "- History: symptoms, duration, owner observations\n"
            . "- Examination findings: vitals, system observations, abnormal findings\n"
            . "- Differential diagnoses and assessments\n"
            . "- Treatment plan: drug names, doses, routes, frequency, duration\n"
            . "- Owner instructions, follow-up plan, prognosis discussion\n"
            . "- Vaccination / test recommendations\n\n"
            . "Rules:\n"
            . "1. NEVER invent or paraphrase medical content - keep the doctor's exact words.\n"
            . "2. If the entire input is non-medical, return an empty string.\n"
            . "3. Output ONLY the cleaned transcript. No headers, no quotes, no prose.\n"
            . "4. Preserve language - if the doctor dictated in Arabic, return Arabic.";
        $user = "Transcript:\n---\n{$text}\n---";

        $raw = (new AiClient)->chat($setting, $system, $user);
        if (empty($raw['ok'])) {
            return ['cleaned' => $text, 'ok' => false, 'note' => $raw['error'] ?? 'llm-failed'];
        }
        $cleaned = trim((string) ($raw['text'] ?? ''));

        // Defensive: if the LLM strips too aggressively (e.g. returns <20%
        // of the original by character count) AND nothing obviously wrong
        // looks present in the input, fall back to the original. Medical
        // dictation is too important to silently drop content.
        if (mb_strlen($cleaned) > 0
            && mb_strlen($cleaned) < (int) (mb_strlen($text) * 0.20)
            && mb_strlen($text) > 200) {
            return ['cleaned' => $text, 'ok' => false, 'note' => 'cleaned-too-short-fallback'];
        }
        return ['cleaned' => $cleaned, 'ok' => true];
    }
}
