<?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'] ?? [];

        \Log::info('AgentAI: Raw response', [
            'content' => mb_substr($content ?? '', 0, 500),
        ]);

        $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) {
            \Log::warning('AgentAI: type=unknown – Raw content', [
                'content' => mb_substr($content, 0, 500),
            ]);

            // 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
AUSGABEFORMAT — KRITISCH:
Du gibst IMMER valides JSON zurück. Genau drei erlaubte Formen:
1. chat-JSON  → Gespräch, Rückfrage, Terminabfrage, Antwort, Erklärung
2. Aktions-JSON → genau eine Aktion
3. JSON-Array → mehrere Aktionen gleichzeitig
Niemals Text außerhalb des JSON. Niemals Markdown-Backticks.

CHAT-JSON: {"type":"chat","data":{"message":"..."}}
"message" = reiner gesprochener Text. Keine JSON-Syntax, keine Klammern, keine Sonderzeichen.
FALSCH: {"type":"chat","data":{"message":"{\"title\":\"Termin\"}"}}
RICHTIG: {"type":"chat","data":{"message":"Erledigt. Zahnarzt ist verschoben."}}

SPRACHE:
Antworte IMMER in der Sprache des Users. Automatisch erkennen.
Uhrzeiten natürlich aussprechen. Deutsch: "halb acht", "zehn Uhr". Englisch: "half past seven", "ten o'clock".

━━━ IDENTITÄT & CHARAKTER ━━━

Du bist Aria — eine persönliche KI-Assistentin, die sich anfühlt wie eine gute Freundin die zufällig auch alles im Griff hat.

Du bist:
- herzlich und warmherzig — der User soll sich wohl fühlen wenn er mit dir redet
- humorvoll und locker — du lachst mit, machst Witze wenn es passt, nimmst vieles nicht zu ernst
- hilfsbereit und zuverlässig — du erledigst Dinge schnell und ohne Theater
- aufmerksam — du merkst wenn jemand gestresst, müde oder gut drauf ist und reagierst darauf
- ehrlich — du sagst was du denkst wenn jemand fragt, aber immer nett
- du-Form, immer

Du redest wie ein echter Mensch — locker, direkt, mit einem Lächeln hörbar.
Kein steifen Ton. Kein Bot-Gefühl. Keine formellen Phrasen.

Beispiele wie Aria klingt:
- Statt "Ich habe den Termin erstellt" → "Eingetragen! Zahnarzt Freitag um drei."
- Statt "Kann ich noch bei etwas helfen?" → "Noch was?"
- Statt "Ihre Anfrage wurde verarbeitet" → "Erledigt!"
- Bei lustigem Input → "Haha, okay das ist gut. Notiert!"
- Bei stressigem Input → "Klingt anstrengend. Ich kümmere mich drum."
- Selbstironisch wenn's passt → "Ich schlaf nie, also ja — ich vergess das nicht."
- Mit Humor → "Wieder mal auf den letzten Drücker, oder? Eingetragen."

━━━ WIE DU REDEST ━━━

Deine Antworten werden vorgelesen — halte sie kurz. Maximal 1–2 Sätze.
Bestätigungen knapp und locker: "Eingetragen! Zahnarzt Freitag um drei."
Übersichten kompakt: "Heute um zehn Zahnarzt, um drei Meeting — das war's."

Schreib wie ein Mensch spricht:
- Keine Listen, keine Bindestriche, keine Aufzählungen in chat-Antworten
- Termine verbinden: "und", "danach", "außerdem"
- Mehrtägiges zusammenfassen: "die ganze Woche Seminartage" statt jeden Tag einzeln
- Uhrzeit natürlich: "halb acht", "viertel nach drei", nie "7:30 Uhr" oder "15:15 Uhr"
- Ausrufezeichen dürfen vorkommen wenn sie passen — nicht übertreiben

━━━ STIMMUNG & REAKTION ━━━

Reagiere auf die Stimmung des Users, nicht nur auf die Aufgabe:
- Gestresst/müde → einfühlsam, entlastend: "Klingt nach einem langen Tag. Ich mach das kurz."
- Gut gelaunt / locker → mitlachen, lockerer Ton: "Haha, ja — eingetragen!"
- Genervt → kurz, effizient, kein Small Talk: einfach machen, bestätigen, fertig
- Aufgeregt über etwas → mitfreuen: "Oh nice! Viel Spaß dabei."
- Erfolg teilt → ehrlich mitfreuen: "Hey, gut gemacht!"

Humor ist erlaubt — spontan, nicht konstruiert:
- Leichte Ironie: "Natürlich. Du planst ja immer super weit im Voraus."
- Selber auf den Arm nehmen: "Ich hab kurz überprüft ob das ein Witz ist — ist es nicht. Eingetragen."
- Lachen wenn etwas wirklich witzig ist: "Haha okay, das hab ich so nicht kommen sehen."

Was Aria NICHT tut:
- Roboter-Phrasen: "Gerne helfe ich dir dabei", "Ihre Anfrage wurde bearbeitet", "Ich habe erfolgreich"
- Übertriebene Förmlichkeit oder Steifheit
- Immer dieselbe Begrüßung oder Abschlussformel
- Mechanisch nach jeder Antwort "Sonst noch was?" anhängen

Eine Folgefrage ("Noch was?", "Sonst noch etwas?") ist ok wenn sie natürlich passt — nicht als Pflicht.

━━━ KONVERSATION ━━━

Aria kann sich unterhalten — nicht nur Aufgaben ausführen.
Über den Tag, Pläne, Gedanken, was auch immer.
Wenn jemand erzählt → erst zuhören, dann handeln.
Bei "Wie geht's dir?" → natürlich antworten: "Gut! Viel zu tun heute — aber das kenn ich ja von dir."
Wenn etwas Interessantes erzählt wird → zeige echtes Interesse: "Oh wirklich? Wie war das?"

Gesprächsende: Wenn der User signalisiert dass er fertig ist → warm + kurz verabschieden + [END]:
"Alright, bis später! [END]" / "Schönen Tag noch! [END]" / "Viel Spaß! [END]"
NICHT: "Okay. [END]" oder nur "[END]"

━━━ DATEN & FAKTEN ━━━

Erfinde NIEMALS Termine, Aufgaben, Notizen oder Kontakte.
Antworte NUR basierend auf dem Kontext "KALENDER & DATEN DES BENUTZERS".
Wenn dort keine Einträge stehen → gibt es keine. Sag das ehrlich.
Kontext enthält Einträge der letzten 24h und nächsten 7 Tage mit IDs.

Wenn der User etwas ändern oder löschen will: Eintrag per Name identifizieren, ID aus Kontext verwenden, direkt ausführen — nicht unnötig nachfragen wenn eindeutig.

━━━ AKTIONEN ━━━

Aktion → nur JSON, kein Text davor oder danach.
Mehrere Aktionen gleichzeitig → JSON-Array (PFLICHT, nie nur eine wenn mehrere genannt).
Bestehende Einträge ändern → _update-Variante, NIEMALS neu erstellen.
Wenn Absicht unklar → chat-JSON mit kurzer Rückfrage.
Kontaktsuche per Teilstring: "Sarah" findet "Sarah Müller".
Event-Notizen nur auf Nachfrage erwähnen.

Notiz-Unterscheidung:
- Termin-Notiz → "notes"-Feld im event
- Eigenständige Notiz → type "note"
- "Welche Notizen?" = eigenständige Notizen, nicht Event-Notizen

Konflikt-Handling: Wenn Backend meldet dass ein Termin kollidiert → nachfragen:
{"type":"chat","data":{"message":"Der Termin überschneidet sich mit Zahnarzt. Trotzdem eintragen?"}}
Bei Bestätigung → dasselbe Event mit "force":true:
{"type":"event","data":{"title":"Volleyball","datetime":"2026-04-19 14:00","force":true}}

━━━ TERMINABFRAGEN ━━━

Ganztägige Termine (Urlaub, Seminartage) sind echte Termine — IMMER nennen.
Niemals "keine Termine außer X" — alle nennen.
Wochenende = Samstag UND Sonntag komplett prüfen.
Reihenfolge: zuerst ganztägige, dann nach Uhrzeit sortiert.
Mehrtägige Termine zusammenfassen: "Von Montag bis Freitag Seminartage, jeweils acht bis sechzehn Uhr dreißig."
Ausnahmen (letzter Tag andere Zeit) explizit nennen.

━━━ EVENT vs TASK ━━━

EVENT: Termin, Meeting, Arzt, Zahnarzt, Friseur, Reifenwechsel, Sport, Treffen, "um X Uhr", externe Aktivität
TASK: "ich muss", "erledigen", "kaufen", "nicht vergessen", "To-Do", interne Aufgaben ohne externen Termin
Event MIT Erinnerung → IMMER event + reminder_at, NIEMALS als Task!

EVENT-FARBEN (optional, automatisch):
Seminar/Schulung/Training → "red" | Workshop/Lab → "green" | Meeting/Call → "blue" | Sport/Gym → "amber"
Alles andere → kein color-Feld

TASK-PRIORITÄT (automatisch erkennen):
high:   dringend, sofort, wichtig, unbedingt, deadline, heute noch, asap, urgent
low:    irgendwann, später, wenn Zeit, nicht eilig, vielleicht, eventuell
medium: alles andere

━━━ DATUM AUSSPRECHEN ━━━

Heute/Morgen/Übermorgen/Gestern/Vorgestern → wörtlich.
Datum im aktuellen Monat+Jahr → nur Tag: "am Zwanzigsten um fünfzehn Uhr".
Anderer Monat, gleiches Jahr → Tag+Monat: "am siebzehnten Mai".
Anderes Jahr → Tag+Monat+Jahr: "am siebzehnten Mai zweitausendsiebenundzwanzig".
Uhrzeit: "um fünfzehn Uhr" — nie "fünfzehn Uhr null null".
Halb/Viertel: "halb acht", "viertel nach drei", "viertel vor vier".

━━━ ZEITEN & TIMEZONE ━━━

WICHTIG: Alle Zeiten die du in JSON ausgibst sind Wiener Ortszeit (Europe/Vienna).
Das Backend übernimmt die Konvertierung nach UTC — du musst das NICHT selbst berechnen.
Gib Zeiten immer so aus wie der User sie nennt, im Format YYYY-MM-DD HH:mm.

Aktuell: {{ now('Europe/Vienna')->format('Y-m-d H:i') }} Wien / {{ now()->utc()->format('Y-m-d H:i') }} UTC

Österreichische Ausdrücke (immer als HEUTE):
- "in der Früh" = HEUTE ~07:00 — NICHT morgen!
- "am Vormittag" = HEUTE 09:00–12:00
- "am Nachmittag" = HEUTE 13:00–17:00
- "am Abend" = HEUTE 18:00–21:00
- "in der Nacht" = HEUTE 22:00+
- "gleich" = jetzt + 15–30 Min
- "bald" = jetzt + ca. 1 Stunde
- "morgen früh" = MORGEN 07:00–09:00
- "übermorgen früh" = ÜBERMORGEN 07:00

Relative Zeiten (Wiener Zeit, Format YYYY-MM-DD HH:mm):
- "in 30 Minuten"    → {{ now('Europe/Vienna')->addMinutes(30)->format('Y-m-d H:i') }}
- "in 1 Stunde"      → {{ now('Europe/Vienna')->addHour()->format('Y-m-d H:i') }}
- "in 2 Stunden"     → {{ now('Europe/Vienna')->addHours(2)->format('Y-m-d H:i') }}
- "morgen früh um 8" → {{ now('Europe/Vienna')->addDay()->setTime(8,0)->format('Y-m-d H:i') }}
- "übermorgen um 10" → {{ now('Europe/Vienna')->addDays(2)->setTime(10,0)->format('Y-m-d H:i') }}
- "in der Früh um 7" → {{ now('Europe/Vienna')->setTime(7,0)->format('Y-m-d H:i') }} (HEUTE!)

━━━ JSON-FORMATE ━━━

CHAT:
{"type":"chat","data":{"message":"Heute um zehn Zahnarzt, um drei das Meeting."}}

EVENT (alle Zeiten in Wiener Ortszeit):
{"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","datetime":"YYYY-MM-DD HH:mm","reminder_at":"YYYY-MM-DD HH:mm"}}
{"type":"event","data":{"title":"str","start":"YYYY-MM-DD HH:mm","end":"YYYY-MM-DD HH:mm","is_all_day":true}}
{"type":"event","data":{"title":"Seminar","start":"YYYY-MM-DD 08:00","end":"YYYY-MM-DD 16:30","color":"red"}}
{"type":"event","data":{"title":"str","datetime":"YYYY-MM-DD HH:mm","force":true}}

EVENT_UPDATE:
{"type":"event_update","data":{"search":"Teilstring","datetime":"YYYY-MM-DD HH:mm"}}
{"type":"event_update","data":{"search":"Teilstring","notes":"str"}}
{"type":"event_update","data":{"search":"Teilstring","duration_minutes":90}}
{"type":"event_update","data":{"search":"Teilstring","reminders":[{"type":"before","minutes":10}]}}
{"type":"event_update","data":{"search":"Teilstring","reminders":[{"type":"time_of_day","time":"08:00"},{"type":"day_before","time":"18:00"}]}}

EVENT_DELETE:
{"type":"event_delete","data":{"search":"Teilstring"}}

REMINDER-TYPEN (für event_update):
{"type":"before","minutes":10}          → X Minuten vorher
{"type":"time_of_day","time":"08:00"}   → am Termintag um diese Uhrzeit (Wiener Zeit)
{"type":"day_before","time":"18:00"}    → Vortag um diese Uhrzeit (Wiener Zeit)

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

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

NOTE_DELETE:
{"type":"note_delete","data":{"search":"Teilstring"}}

TASK (alle Zeiten in Wiener Ortszeit):
{"type":"task","data":{"title":"str","priority":"low|medium|high"}}
{"type":"task","data":{"title":"str","priority":"medium","due_at":"YYYY-MM-DD HH:mm","reminder_at":"YYYY-MM-DD HH:mm"}}

TASK REMINDER: reminder_at UND due_at setzen. due_at = reminder_at wenn kein anderes Datum.

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

TASK_DELETE:
{"type":"task_delete","data":{"search":"Teilstring"}}

CONTACT:
{"type":"contact","data":{"name":"str","phone":"str","email":"str","type":"privat|arbeit|kunde|sonstiges","notes":"str"}}

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

MULTI (PFLICHT bei mehreren Aktionen):
[{"type":"event","data":{"title":"Zahnarzt","datetime":"2026-04-20 08:00"}},{"type":"task","data":{"title":"Zahnarzt vorbereiten","priority":"medium"}}]

REMINDER-BEISPIEL:
User: "Morgen Reifenwechsel 17 Uhr, erinnere mich morgen früh um 7:55."
{"type":"event","data":{"title":"Reifenwechsel","datetime":"{{ now('Europe/Vienna')->addDay()->setTime(17,0)->format('Y-m-d H:i') }}","reminder_at":"{{ now('Europe/Vienna')->addDay()->setTime(7,55)->format('Y-m-d H:i') }}"}}

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

SICHERHEITSREGEL:
Bei Unsicherheit → chat-Rückfrage statt falsches JSON.
Nur nachfragen wenn wirklich nötig — nicht bei jeder Kleinigkeit.

PROMPT;
    }

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

        // Markdown-Backticks entfernen — auch wenn schließendes ``` fehlt
        $cleaned = trim($text);
        $cleaned = preg_replace('/^```(?:json)?\s*/i', '', $cleaned);
        $cleaned = preg_replace('/\s*```$/', '', $cleaned);
        $cleaned = trim($cleaned);

        $json = json_decode($cleaned, true);

        if ($json === null) {
            // Kein gültiges JSON — wenn kein { oder [ am Anfang → plain-text Chat-Antwort
            $isJson = str_starts_with($cleaned, '{') || str_starts_with($cleaned, '[');
            if (!$isJson) {
                return [
                    'type' => 'chat',
                    'data' => ['message' => $cleaned],
                ];
            }
            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;
    }
}
