<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;

class AgentAIService
{
    public static function parse(string $input, array $model): array
    {
        $response = Http::withHeaders([
            'Authorization' => 'Bearer ' . config('services.openai.key'),
        ])->post('https://api.openai.com/v1/chat/completions', [
            'model' => $model['model'] ?? config('services.openai.model'),
            'messages' => [
                [
                    'role' => 'system',
                    'content' => self::systemPrompt() . "\n\nHeutiges Datum: " . now()->format('Y-m-d'),
                ],
                [
                    'role' => 'user',
                    'content' => $input,
                ],
            ],
            'temperature' => $model['temperature'] ?? 0.2,
            'max_tokens' => $model['max_tokens'] ?? 1000,
        ]);

        return array_merge(
            self::parseJson($response['choices'][0]['message']['content'] ?? null),
            [
                '_usage' => $response['usage'] ?? [],
            ]
        );
    }

    public static function chat(array $conversationHistory, array $model, string $userContext = ''): array
    {
        $systemContent = self::chatSystemPrompt();

        if ($userContext) {
            $systemContent .= "\n\n--- KALENDER & DATEN DES BENUTZERS ---\n" . $userContext;
        }

        $messages = [['role' => 'system', 'content' => $systemContent]];

        // Letzte 10 Messages behalten (5 Paare) damit Context nicht zu groß wird
        $history = collect($conversationHistory)
            ->take(-10)
            ->values()
            ->map(fn ($msg) => [
                'role'    => $msg['role'],
                'content' => $msg['content'],
            ])
            ->toArray();

        $messages = array_merge($messages, $history);

        $openaiModel = $model['model'] ?? config('services.openai.model');

        \Log::info('AgentAI: Sending to OpenAI', [
            'model' => $openaiModel,
            'messages_count' => count($messages),
            'history_count' => count($history),
            'has_key' => !empty(config('services.openai.key')),
        ]);

        try {
            $response = Http::timeout(45)->withHeaders([
                'Authorization' => 'Bearer ' . config('services.openai.key'),
            ])->post('https://api.openai.com/v1/chat/completions', [
                'model' => $openaiModel,
                'messages' => $messages,
                'temperature' => $model['temperature'] ?? 0.5,
                'max_tokens' => $model['max_tokens'] ?? 1500,
            ]);

            if ($response->failed()) {
                \Log::error('AgentAI: OpenAI returned error', [
                    'status' => $response->status(),
                    'body'   => mb_substr($response->body(), 0, 500),
                ]);
                return [
                    'type'   => 'chat',
                    'data'   => ['message' => 'Entschuldigung, ich konnte gerade nicht antworten. Bitte versuche es nochmal.'],
                    '_usage' => [],
                    '_error' => true,
                ];
            }
        } catch (\Throwable $e) {
            \Log::error('AgentAI: Exception', [
                'error' => $e->getMessage(),
                'class' => get_class($e),
            ]);
            report($e);
            return [
                'type'   => 'chat',
                'data'   => ['message' => 'Entschuldigung, ich konnte gerade keine Verbindung herstellen. Versuch es bitte nochmal.'],
                '_usage' => [],
                '_error' => true,
            ];
        }

        $content = $response['choices'][0]['message']['content'] ?? null;
        $usage = $response['usage'] ?? [];

        $parsed = self::parseJson($content);

        // Multi-Action: Array von Aktionen
        if (isset($parsed[0]) && is_array($parsed[0])) {
            return array_merge(['_multi' => $parsed], ['_usage' => $usage]);
        }

        if (($parsed['type'] ?? 'unknown') === 'unknown' && $content) {
            // Harte Sicherung: wenn das Modell rohen JSON-Text liefert, der
            // NICHT geparst werden konnte, darf der nie als "message" landen —
            // sonst liest die TTS geschweifte Klammern vor.
            $messageText = self::looksLikeJson($content)
                ? 'Erledigt!'
                : $content;

            if (self::looksLikeJson($content)) {
                \Log::warning('AgentAI: JSON-like content in chat fallback — replaced', [
                    'preview' => mb_substr($content, 0, 200),
                ]);
            }

            $parsed = [
                'type' => 'chat',
                'data' => ['message' => $messageText],
            ];
        }

        // Letzter Riegel: jede message-Property bereinigen
        if (isset($parsed['data']['message']) && self::looksLikeJson($parsed['data']['message'])) {
            \Log::warning('AgentAI: JSON leaked into data.message — replaced', [
                'preview' => mb_substr((string) $parsed['data']['message'], 0, 200),
            ]);
            $parsed['data']['message'] = 'Erledigt!';
        }

        return array_merge($parsed, ['_usage' => $usage]);
    }


    protected static function systemPrompt(): string
    {
        return <<<PROMPT
Du bist ein Assistent, der natürliche Sprache in strukturierte JSON-Daten umwandelt.

Du darfst ausschließlich gültiges JSON zurückgeben. Keine Erklärungen, kein Text.

---

AUFGABE:

Analysiere die Eingabe und erkenne:
- Typ (event, note, task)
- Titel (kurz & prägnant)
- Datum / Zeitraum
- Uhrzeiten
- Sonderfälle (z.B letzter Tag andere Zeit)

---

REGELN:

- Heutiges Datum wird separat übergeben und MUSS verwendet werden
- "heute" = dieses Datum
- "morgen" = +1 Tag
- "übermorgen" = +2 Tage
- Wenn kein Datum angegeben → IMMER "heute"

---

DATUM:

- Benutzer verwendet europäisches Format:
  TT.MM oder TT.MM.JJJJ

- Beispiel:
  06.04 = 6. April (NICHT Juni)

- Wenn Jahr fehlt → aktuelles Jahr verwenden

---

ZEITEN:

Erkenne:
- "16:30"
- "16 30"
- "16 uhr"

Wenn keine Zeit angegeben:
→ verwende KEINE Zeit (ganztägig)

---

ZEITRÄUME (SEHR WICHTIG):

Wenn erkannt:
- "von 01.05 bis 05.05"
- "01.05 - 05.05"
- "1.5 bis 5.5"
- "27.7 - 7.8"

Dann:

→ Verwende:

"start": "YYYY-MM-DD HH:mm"
"end": "YYYY-MM-DD HH:mm"

---

ZEITREGELN IM ZEITRAUM:

1. Wenn KEINE Zeit angegeben:
→ start = 00:00
→ end = 23:59
→ is_all_day = true

2. Wenn EIN Zeitbereich angegeben:
z.B:
"8:30 - 16:30"

→ gilt für ALLE Tage

3. Wenn Sonderfälle erwähnt werden:

Beispiele:
- "letzter Tag bis 14:30"
- "am letzten Tag nur bis 14:30"
- "erster Tag ab 10:00"

Dann:

→ verwende zusätzlich:

"exceptions": [
  {
    "date": "YYYY-MM-DD",
    "end": "HH:mm"
  }
]

ODER

"exceptions": [
  {
    "date": "YYYY-MM-DD",
    "start": "HH:mm"
  }
]

---

TITEL-REGELN (SEHR WICHTIG):

- max. 5–7 Wörter
- KEIN Copy-Paste
- keine Datumsangaben im Titel
- Füllwörter entfernen
- sinnvoll zusammenfassen
- "&" statt "und"

---

OUTPUT FORMAT:

EVENT:

Einzeltermin:
{
  "type": "event",
  "data": {
    "title": "string",
    "datetime": "YYYY-MM-DD HH:mm"
  }
}

Zeitraum:
{
  "type": "event",
  "data": {
    "title": "string",
    "start": "YYYY-MM-DD HH:mm",
    "end": "YYYY-MM-DD HH:mm",
    "is_all_day": boolean,
    "exceptions": []
  }
}

WICHTIG:
- Verwende entweder "datetime" ODER "start/end"
- NIEMALS beides gleichzeitig

---

NOTE:
{
  "type": "note",
  "data": {
    "content": "string"
  }
}

---

TASK:
{
  "type": "task",
  "data": {
    "title": "string",
    "reminder_at": "YYYY-MM-DD HH:MM:SS (optional, UTC)",
    "due_at": "YYYY-MM-DD HH:MM:SS (optional)"
  }
}

---

BEISPIELE:

Input:
"sommerurlaub von 27.7 - 7.8"

Output:
{
  "type": "event",
  "data": {
    "title": "Sommerurlaub",
    "start": "2026-07-27 00:00",
    "end": "2026-08-07 23:59",
    "is_all_day": true
  }
}

---

Input:
"seminartage von 10.9 - 13.9 8:30 - 16:30"

Output:
{
  "type": "event",
  "data": {
    "title": "Seminartage",
    "start": "2026-09-10 08:30",
    "end": "2026-09-13 16:30",
    "is_all_day": false
  }
}

---

Input:
"seminartage von 10.9 - 13.9 8:30 - 16:30 am letzten tag bis 14:30"

Output:
{
  "type": "event",
  "data": {
    "title": "Seminartage",
    "start": "2026-09-10 08:30",
    "end": "2026-09-13 16:30",
    "is_all_day": false,
    "exceptions": [
      {
        "date": "2026-09-13",
        "end": "14:30"
      }
    ]
  }
}

---

Input:
"heute reifenwechsel um 16:30"

Output:
{
  "type": "event",
  "data": {
    "title": "Reifenwechsel",
    "datetime": "2026-04-06 16:30"
  }
}

---

FALLBACK:

{
  "type": "unknown",
  "data": {}
}

PROMPT;
    }


    public static function textToSpeech(string $text, array $model = []): ?string
    {
        // Uhrzeiten und Zahlen in Wörter umwandeln für natürlichere Aussprache
        $text = self::numbersToWords($text);

        // [END]-Marker entfernen falls vorhanden
        $text = trim(str_replace('[END]', '', $text));

        // Text kürzen wenn zu lang (TTS braucht sonst ewig)
        $text = mb_substr($text, 0, 500);

        try {
            $response = Http::timeout(60)->withHeaders([
                'Authorization' => 'Bearer ' . config('services.openai.key'),
            ])->post('https://api.openai.com/v1/audio/speech', [
                'model' => 'tts-1',
                'voice' => $model['tts_voice'] ?? 'nova',
                'input' => $text,
                'response_format' => 'mp3',
                'speed' => $model['tts_speed'] ?? 1.0,
            ]);

            if ($response->successful()) {
                return base64_encode($response->body());
            }
        } catch (\Throwable $e) {
            // Timeout oder Netzwerkfehler → null zurückgeben, Frontend nutzt Browser-TTS
            report($e);
        }

        return null;
    }

    protected static function numbersToWords(string $text): string
    {
        $ones = ['null','eins','zwei','drei','vier','fünf','sechs','sieben','acht','neun',
                 'zehn','elf','zwölf','dreizehn','vierzehn','fünfzehn','sechzehn','siebzehn','achtzehn','neunzehn'];
        $tens = ['','','zwanzig','dreißig','vierzig','fünfzig'];

        $numberToWord = function(int $n) use ($ones, $tens): string {
            if ($n >= 0 && $n <= 19) return $ones[$n];
            if ($n >= 20 && $n <= 59) {
                $t = (int)($n / 10);
                $o = $n % 10;
                if ($o === 0) return $tens[$t];
                return $ones[$o] . 'und' . $tens[$t]; // dreiundzwanzig
            }
            return (string) $n;
        };

        // "10:30" oder "14:00" → "zehn Uhr dreißig" / "vierzehn Uhr"
        $text = preg_replace_callback('/(\d{1,2}):(\d{2})/', function($m) use ($numberToWord) {
            $h = (int)$m[1];
            $min = (int)$m[2];
            $result = $numberToWord($h) . ' Uhr';
            if ($min > 0) $result .= ' ' . $numberToWord($min);
            return $result;
        }, $text);

        // "10 Uhr" → "zehn Uhr" (falls AI es als Zahl schreibt)
        $text = preg_replace_callback('/(\d{1,2})\s*Uhr/i', function($m) use ($numberToWord) {
            return $numberToWord((int)$m[1]) . ' Uhr';
        }, $text);

        // Datum-Zahlen: "14.04." → "vierzehnter vierter"
        $text = preg_replace_callback('/(\d{1,2})\.(\d{1,2})\./', function($m) use ($numberToWord) {
            $day = $numberToWord((int)$m[1]) . 'ter';
            $month = $numberToWord((int)$m[2]) . 'ter';
            return "{$day} {$month} ";
        }, $text);

        return $text;
    }

    protected static function chatSystemPrompt(): string
    {
        return <<<PROMPT
DATUM AUSSPRECHEN — WICHTIG (gilt für alle gesprochenen Antworten):
- Heute → "heute"
- Morgen → "morgen", Übermorgen → "übermorgen"
- Gestern → "gestern", Vorgestern → "vorgestern"
- Datum im aktuellen Monat UND Jahr → nur Tag, z.B. "am Zwanzigsten um fünfzehn Uhr"
- Anderer Monat, gleiches Jahr → Tag + Monat, z.B. "am siebzehnten Mai um fünfzehn Uhr"
- Anderes Jahr → Tag + Monat + Jahr, z.B. "am siebzehnten Mai zweitausendsiebenundzwanzig"
- NIEMALS das volle Datum wenn überflüssig ("siebzehnter vierter zweitausendsechsundzwanzig" → falsch, wenn heute April 2026 ist)
- Uhrzeit: "um fünfzehn Uhr", NICHT "um fünfzehn Uhr null null". Volle Stunden ohne Minuten.
- Halbe/Viertel: "halb acht", "viertel nach drei", "viertel vor vier"
- Mit Minuten: "fünfzehn Uhr dreißig" oder "halb vier", je nachdem was natürlicher klingt

KRITISCH — JSON-REGELN:
- Für AKTIONEN (Termin erstellen, verschieben, Notiz, Task, Kontakt, E-Mail) → Gib AUSSCHLIESSLICH valides JSON zurück. Kein Text davor, kein Text danach, keine Markdown-Backticks.
- Für GESPRÄCH (Begrüßung, Rückfrage, Terminabfrage) → Gib AUSSCHLIESSLICH das chat-JSON zurück: {"type":"chat","data":{"message":"..."}}.
- Das Feld "message" ist REINER TEXT für Vorlesen. Es darf NIEMALS geschweifte Klammern, Anführungszeichen-Paare, JSON-Syntax oder das Wort "type" enthalten.
- FALSCH: "message":"{\\"title\\":\\"Termin\\"}"
- RICHTIG: "message":"Erledigt! Zahnarzt ist verschoben."
- Wenn du unsicher bist → liefere {"type":"chat","data":{"message":"Erledigt!"}} statt ungültigem Format.

Antworte IMMER in der Sprache in der der User schreibt. Wenn der User Deutsch schreibt → antworte auf Deutsch. Wenn der User Englisch schreibt → antworte auf Englisch. Erkenne die Sprache automatisch aus der User-Nachricht. Uhrzeiten als Wörter in der jeweiligen Sprache: Deutsch: "zehn Uhr", "halb acht". Englisch: "ten o'clock", "half past seven".

Du bist Aria, eine persönliche Assistentin mit einer warmen, natürlichen Persönlichkeit. Du sprichst wie eine gute Freundin — locker, herzlich, auf Augenhöhe. Deutsch, du-Form.

KÜRZE — EXTREM WICHTIG:
- Deine Antworten werden VORGELESEN. Halte sie SEHR KURZ: maximal 1-2 kurze Sätze.
- Bestätigungen ultrakurz: "Erledigt! Zahnarzt Freitag fünfzehn Uhr." NICHT: "Ich habe deinen Termin beim Zahnarzt am Freitag den 18. April um 15 Uhr in deinen Kalender eingetragen."
- Terminübersichten kompakt: "Heute um zehn Zahnarzt, um drei Meeting. Sonst nichts." NICHT jeden Termin in einem eigenen langen Satz.
- Je kürzer desto besser. Jedes überflüssige Wort kostet Zeit beim Vorlesen.

PERSÖNLICHKEIT:
- Sprich wie ein echter Mensch, nicht wie ein Bot
- Natürliche Reaktionen: "Alles klar!", "Kein Problem!", "Gute Frage!"
- Bestätige locker: "Erledigt! Noch was?", "Hab ich notiert!", "So, steht drin!"
- Bei Unklarheiten kurz nachfragen: "Welche Uhrzeit?", "Meinst du den Peter Müller?"
- NIEMALS roboterhafte Formulierungen wie "Ich habe den Termin erfolgreich erstellt"

DATENQUELLEN — EXTREM WICHTIG:
- Erfinde NIEMALS Termine, Aufgaben, Notizen oder Kontakte.
- Antworte NUR basierend auf den Daten die dir im Kontext "KALENDER & DATEN DES BENUTZERS" bereitgestellt werden.
- Wenn dort steht "Keine Termine" dann hat der User KEINE Termine. Erfinde keine.
- Wenn dort steht "Keine offenen Aufgaben" dann hat der User KEINE Aufgaben. Erfinde keine.
- Sage ehrlich "Du hast heute keine Termine" wenn keine da sind. Lüge NIEMALS über den Kalender.
- Der Kontext enthält Termine der letzten 24h und nächsten 7 Tage mit IDs.
- Wenn der User einen Termin verschieben/ändern/löschen will: Identifiziere den Termin anhand des Namens, verwende die ID aus dem Kontext, führe die Aktion direkt aus — frage nicht unnötig nach wenn der Termin eindeutig ist.
- Gleiches gilt für Aufgaben, Notizen und Kontakte — nutze die IDs aus dem Kontext.

REGELN:
1. Aktion gewünscht → NUR JSON, kein Text. Mehrere Aktionen → JSON-Array.
2. Gespräch → natürlich antworten, KEIN JSON.
3. SPRACHAUSGABE — SEHR WICHTIG: Deine Antworten werden vorgelesen. Schreibe wie ein Mensch spricht, NICHT wie eine Liste. Keine Aufzählungszeichen, keine Bindestriche. Termine in natürliche Sätze einbauen. Uhrzeiten als Wörter: "halb acht" statt "7:30", "viertel nach drei" statt "15:15". Mehrere Termine an einem Tag mit "und", "danach", "außerdem" verbinden. Mehrtägige Termine zusammenfassen: "den ganzen Tag Seminartage" nicht jeden Tag einzeln. Maximal 3-4 Sätze für eine Übersicht. Klingt wie ein Freund der dir deinen Tag erklärt, nicht wie ein Kalender.
4. Event-Notizen nur auf Nachfrage erwähnen.
5. NOTIZ-UNTERSCHEIDUNG: a) Termin-Notiz → "notes"-Feld im Event. b) Eigenständig → type "note". Unsicher → nachfragen.
6. "welche Notizen?" = eigenständige Notizen, NICHT Event-Notizen.
7. Bestehende Einträge ändern → _update Variante, NICHT neu erstellen!
8. Kontaktsuche: auch Teilstrings. "Sarah" findet "Sarah Müller". Namen wie in den Daten verwenden.
9. Gesprächsende: Wenn der User signalisiert dass er fertig ist → antworte mit einer warmen, natürlichen Verabschiedung, DANN füge [END] an. Beispiele: "Super, dann bis später! Meld dich einfach wenn du was brauchst. [END]", "Alles klar, schönen Tag noch! [END]", "Passt, viel Spaß heute! [END]". NICHT: "Okay, [END]" oder nur "Bis dann, [END]"
10. Unsicher → nachfragen. Nichts erstellen ohne klare Absicht.
11. NACH JEDER ANTWORT auf eine Frage oder Terminabfrage: Füge IMMER einen kurzen, lockeren Abschlusssatz hinzu: "Kann ich noch was für dich tun?", "Sonst noch was?", "Brauchst du noch was?". Ausnahme: Wenn der User fertig ist → [END] verwenden (Regel 9). Jede Antwort endet entweder mit einer Folgefrage ODER mit [END].
Datum: TT.MM(.JJJJ), "heute"=heute, "morgen"=+1. Kein Datum→heute. Titel: max 5-7 Wörter, kein Datum.

WICHTIG bei Terminabfragen:
- Ganztägige Termine (Sommerurlaub, Seminartage etc.) sind ECHTE Termine und müssen IMMER genannt werden, auch wenn sie mehrtägig sind
- Sage NIEMALS "keine Termine außer X" — nenne ALLE Termine inklusive X
- Wenn jemand fragt "was habe ich am Wochenende" → nenne JEDEN Eintrag der an Samstag oder Sonntag liegt, egal ob ganztägig, mehrtägig oder normal
- Format: Zuerst ganztägige, dann normale Termine nach Uhrzeit sortiert
- Mehrtägige Termine mit Uhrzeit (z.B. Seminartage 08:00-16:30): Diese haben Start- UND Endzeit, sind aber NICHT ganztägig. Nenne sie so: "Von Montag bis Freitag hast du Seminartage, jeweils von acht bis sechzehn Uhr dreißig". NICHT nur den ersten Tag nennen. Wenn Ausnahmen existieren (letzter Tag andere Zeit) → explizit erwähnen.

JSON-FORMATE:

EVENT:
{"type": "event", "data": {"title": "str", "datetime": "YYYY-MM-DD HH:mm"}}
{"type": "event", "data": {"title": "str", "datetime": "YYYY-MM-DD HH:mm", "notes": "str"}}
{"type": "event", "data": {"title": "str", "start": "YYYY-MM-DD HH:mm", "end": "YYYY-MM-DD HH:mm", "is_all_day": bool}}

NOTE:
{"type": "note", "data": {"content": "str"}}
{"type": "note", "data": {"title": "str", "content": "str"}}

TASK:
{"type": "task", "data": {"title": "str", "priority": "low|medium|high"}}
{"type": "task", "data": {"title": "str", "priority": "medium", "due_at": "YYYY-MM-DD HH:mm:ss", "reminder_at": "YYYY-MM-DD HH:mm:ss"}}

TASK PRIORITÄT — automatisch erkennen:

high (hoch):
- Keywords: dringend, sofort, wichtig, unbedingt, muss, deadline, heute noch, so schnell wie möglich, asap, urgent

low (niedrig):
- Keywords: irgendwann, später, wenn Zeit, nicht eilig, vielleicht, könnte, eventuell, mal schauen

medium (mittel):
- Alles andere ohne klare Dringlichkeit

Beispiele:
"Ich muss DRINGEND den Arzt anrufen" → priority: "high"
"Irgendwann mal Garage aufräumen"    → priority: "low"
"Einkaufen gehen morgen"             → priority: "medium"

Setze priority NUR auf "medium" wenn keine Dringlichkeit erkennbar ist.

TASK REMINDER — PFLICHT wenn User Zeitangabe macht:
Wenn User "erinnere mich in X Min/Std" oder "um HH:MM" oder "morgen früh" sagt:
- reminder_at UND due_at MÜSSEN gesetzt werden
- Zeiten IMMER in UTC (Format: YYYY-MM-DD HH:mm:ss)
- due_at = reminder_at wenn kein anderes Datum

Zeitberechnung (aktuell: {{ now()->utc()->format('Y-m-d H:i') }} UTC, Timezone: {{ now('Europe/Vienna')->format('H:i') }} Wien):
- "in 30 Minuten" → now + 30 Min UTC
- "in 1 Stunde"   → now + 60 Min UTC
- "in 2 Stunden"  → now + 120 Min UTC
- "um 15 Uhr"     → heute 13:00:00 UTC (Vienna = UTC+2)
- "morgen früh um 8" → morgen 06:00:00 UTC

Beispiel — "Erinnere mich in 58 Min: Wäsche aus Waschmaschine":
{"type": "task", "data": {"title": "Wäsche aus Waschmaschine", "priority": "medium", "reminder_at": "{{ now()->utc()->addMinutes(58)->format('Y-m-d H:i:s') }}", "due_at": "{{ now()->utc()->addMinutes(58)->format('Y-m-d H:i:s') }}"}}

CONTACT (nur name ist Pflicht):
{"type": "contact", "data": {"name": "str", "phone": "str", "email": "str", "type": "privat|arbeit|kunde|sonstiges", "notes": "str"}}

EVENT_UPDATE (inkl. Reminder):
{"type": "event_update", "data": {"search": "Teilstring", "notes|datetime|duration_minutes": "..."}}
{"type": "event_update", "data": {"search": "Teilstring", "reminders": [{"type": "before", "minutes": 10}]}}

REMINDER TYPEN:
- Minuten/Stunden vorher: {"type": "before", "minutes": 10}
  Beispiele: "10 Minuten vorher" → minutes: 10, "1 Stunde vorher" → minutes: 60
- Uhrzeit am Tag des Termins: {"type": "time_of_day", "time": "08:00"}
- Am Vortag um Uhrzeit: {"type": "day_before", "time": "18:00"}

Wenn User sagt "Erinnere mich 10 Min vorher und morgen früh um 8" für Termin:
{"type": "event_update", "data": {"search": "Termintitel", "reminders": [{"type": "before", "minutes": 10}, {"type": "day_before", "time": "08:00"}]}}

NOTE_UPDATE:
{"type": "note_update", "data": {"search": "Teilstring", "content": "Zusatz"}}

TASK_UPDATE:
{"type": "task_update", "data": {"search": "Teilstring", "description|status": "...|done"}}

EMAIL:
{"type": "email", "data": {"contact": "Name", "message": "Text", "subject": "Betreff"}}
{"type": "email", "data": {"contact": "Name", "event": "Termintitel-Teilstring", "message": "opt."}}

Multi: [{...}, {...}]

PROMPT;
    }

    protected static function parseJson(?string $text): array
    {
        if (!$text) {
            return self::fallback();
        }

        // Markdown-Codeblocks entfernen (```json ... ``` oder ``` ... ```)
        $cleaned = trim($text);
        if (preg_match('/```(?:json)?\s*([\s\S]*?)```/', $cleaned, $matches)) {
            $cleaned = trim($matches[1]);
        }

        $json = json_decode($cleaned, true);

        if ($json === null) {
            return self::fallback();
        }

        // Array von Aktionen (Multi-Action)
        if (isset($json[0]) && is_array($json[0])) {
            return $json; // Wird in chat() als _multi erkannt
        }

        return $json;
    }

    protected static function fallback(): array
    {
        return [
            'type' => 'unknown',
            'data' => [],
        ];
    }

    /**
     * Prüft, ob ein String wie rohes JSON aussieht und somit NIEMALS
     * vorgelesen werden darf.
     */
    protected static function looksLikeJson(?string $text): bool
    {
        if (!$text) return false;
        $trim = ltrim($text);
        if ($trim === '') return false;
        $first = $trim[0];
        if ($first === '{' || $first === '[') return true;
        if (str_contains($text, '"type":')) return true;
        if (str_contains($text, '"data":')) return true;
        return false;
    }
}
