using LocalAI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WikipediaRagWinForms.Services;

namespace WikipediaRagWinForms.Rag
{
    public class RagEngine
    {
        private readonly RagConfig _cfg = RagConfig.Load(); // Wichtige Parameter

        private readonly WikipediaOnlineClient _wiki;
        private readonly OllamaClient _ollama;

        // Cache: Text → Embedding
        private readonly Dictionary<string, List<float>> _embeddingCache =
            new Dictionary<string, List<float>>(StringComparer.OrdinalIgnoreCase);

        // Neuer Typ für Chunks
        private class ArticleChunk
        {
            public string Title { get; }
            public string Text { get; }

            public ArticleChunk(string title, string text)
            {
                Title = title;
                Text = text;
            }
        }


        public RagEngine(WikipediaOnlineClient wiki, OllamaClient ollama)
        {
            _wiki = wiki;
            _ollama = ollama;
            System.Diagnostics.Debug.WriteLine(_cfg.Dump());
        }
                
        private async Task<string> TranslateToGermanAsync(string text, string model, CancellationToken token)
        {
            // Parameter model wird ignoriert.
            // Das ist nicht falsch, nur redundant.

            string prompt =
            "Du bist ein Übersetzer. Übersetze den folgenden Text präzise ins Deutsche.\n" +
            "Regeln:\n" +
            "- Erhalte jeden Bulletpoint (Beginn mit '•') als separate Zeile.\n" +
            "- Keine Erklärungen.\n" +
            "- Keine zusätzlichen Wörter.\n\n" +
            "Text:\n" +
            text + "\n\n" +
            "Übersetzung (nur Deutsch):";


            string raw = await _ollama.GenerateAsync(prompt, token);
            return string.IsNullOrWhiteSpace(raw) ? text : raw.Trim();
        }

        private async Task<string> TranslateFromGermanAsync(
        string germanText, string targetIso, CancellationToken token)
        {
            // Zielsprachenname vereinfachen (Mini-Modelle hassen Klammern)
            string targetLang;
            switch (targetIso)
            {
                case "es": targetLang = "Spanisch"; break;
                case "en": targetLang = "Englisch"; break;
                case "de": targetLang = "Deutsch"; break;
                default: targetLang = targetIso; break;
            }

            string prompt =
            "Du bist ein Übersetzer. Übersetze den folgenden Text präzise in " + targetLang + ".\n" +
            "Regeln:\n" +
            "- Erhalte jeden Bulletpoint (Beginn mit '•') als separate Zeile.\n" +
            "- Keine Erklärungen.\n" +
            "- Keine Umschreibungen.\n" +
            "- Keine zusätzlichen Wörter.\n\n" +
            "Text:\n" +
            germanText + "\n\n" +
            "Übersetzung (nur " + targetLang + "):";

            string raw = await _ollama.GenerateAsync(prompt, token);
            return string.IsNullOrWhiteSpace(raw) ? germanText : raw.Trim();
        }


        // ============================================================
        // 1) Suchbegriffe vom KI-Moderator erzeugen
        // ============================================================
        private async Task<List<string>> GetSearchTermsFromModeratorAsync(
        string question,
        string model,
        CancellationToken token)
        {
            bool isQwen = model.IndexOf("qwen", StringComparison.OrdinalIgnoreCase) >= 0;

            // --- QWEN: einfacher, robuster Suchbegriff-Extraktor ---
            if (isQwen)
            {
                // Personenname → immer direkt übernehmen
                // z.B. "Friedrich Merz" → "Friedrich Merz"
                return new List<string> { question };
            }

            // --- Schritt D4: Regelbasiertes Rollen-Mapping ---
            string q = question.ToLowerInvariant();

            // Liste bekannter politischer Rollen
            var roleMap = new Dictionary<string, List<string>>
            {
                { "außenminister", new List<string>
                    {
                        "Bundesminister des Auswärtigen",
                        "Auswärtiges Amt",
                        "Außenminister"
                    }
                },
                { "innenminister", new List<string>
                    {
                        "Bundesminister des Innern",
                        "Bundesministerium des Innern",
                        "Innenminister"
                    }
                },
                { "finanzminister", new List<string>
                    {
                        "Bundesminister der Finanzen",
                        "Bundesministerium der Finanzen",
                        "Finanzminister"
                    }
                },
                { "verkehrsminister", new List<string>
                    {
                        "Bundesminister für Verkehr",
                        "Bundesministerium für Verkehr",
                        "Verkehrsminister"
                    }
                }
            };

            // Prüfen, ob Frage eine bekannte Rolle enthält
            foreach (var kv in roleMap)
            {
                if (q.Contains(kv.Key))
                {
                    return kv.Value;
                }
            }

            // Erklärung für das Modell, wie es arbeiten soll:
            string prompt =
            "Du bist ein präziser \"Wikipedia-Begriff-Extraktor\".\r\n" +
            "Du erzeugst IMMER 1–3 Suchbegriffe:\r\n" +
            "  1) den zentralen Kernbegriff (z.B. Bundeskanzler)\r\n" +
            "  2) falls ein Land genannt wird: zusätzlich die offizielle Rollenform mit Land,\r\n" +
            "       z.B. 'Bundeskanzler (Deutschland)' oder 'Präsident der Vereinigten Staaten'\r\n" +
            "  3) falls Thema Politik/Regierung: optional einen passenden Oberbegriff,\r\n" +
            "       z.B. 'Regierung Deutschlands', 'Politisches System Deutschlands'\r\n" +
            "       → nur falls relevant und eindeutig.\r\n\r\n" +

            "REGELN:\r\n" +
            "1. Gib pro Zeile GENAU EINEN Begriff aus. Maximal 3 Zeilen.\r\n" +
            "2. KEINE Sätze, KEINE Erklärungen, KEINE Zusatzwörter.\r\n" +
            "3. Wenn die Frage mit 'wer ist', 'wer war', 'wer wurde' beginnt:\r\n" +
            "      → extrahiere NUR die Rolle (Bundeskanzler, Präsident, Papst).\r\n" +
            "4. Wenn ein Land genannt wird:\r\n" +
            "      → erstelle zusätzlich exakt diesen Begriff: <Rolle> (<Land>)\r\n" +
            "5. Füge nur dann einen Oberbegriff hinzu, wenn die Frage inhaltlich klar politisch ist.\r\n" +
            "6. Das Land DARF NICHT allein als Suchbegriff erscheinen.\r\n" +
            "7. Keine Vermutungen, kein zusätzliches Wissen.\r\n" +
            "8. Bei Zweifel: lieber 2 statt 3 Begriffe.\r\n\r\n" +

            "Frage: " + question + "\r\n\r\n" +
            "Gib jetzt NUR die Suchbegriffe aus:";


            // KI aufrufen (mit nicht-streaming)
            string raw = await _ollama.GenerateAsync(prompt, token);

            // >>> DEBUG-LOG hinzufügen
            System.Diagnostics.Debug.WriteLine("MODERATOR RAW OUTPUT:");
            System.Diagnostics.Debug.WriteLine(raw);

            // Zusätzlich zur Analyse im UI ausgeben:
            if (raw != null)
            {
                // Zeigt Moderator-Output oben in der Antwortbox an
                // (Optional – falls du das willst)
                // onChunk kann hier NICHT genutzt werden – daher im finalen AskStreamAsync setzen.
            }

            var results = new List<string>();

            if (string.IsNullOrWhiteSpace(raw))
                return new List<string> { question };

            var lines = raw.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);

            foreach (var line in lines)
            {
                string term = line.Trim();

                // Bereinigen
                term = term.Trim('.', '-', ':', '–', '—', '"', '\'', '„', '“');

                // Ausschluss von Müll
                if (term.Length < 2)
                    continue;

                if (term.Contains(" "))
                {
                    // Mehrwortbegriffe erlauben, aber keine Sätze
                    // Beispiel: "Magnus Carlsen" ist ok, "bester Schachspieler der Welt" NICHT
                    if (term.Count(c => c == ' ') >= 5) // zu lang --> wahrscheinlich ein Satz
                        continue;
                }

                // keine Duplikate
                if (!results.Contains(term, StringComparer.OrdinalIgnoreCase))
                    results.Add(term);

                if (results.Count >= 3)
                    break;
            }
            
            System.Diagnostics.Debug.WriteLine("QUESTION (q): " + q);

            // Robuster Fallback: Schachfragen IMMER direkt auf die klaren Artikel mappen
            if (q.Contains("schach") || q.Contains("schachwelt"))
            {
                return new List<string>
                {
                    "Schachweltmeister",
                    "Schachweltmeisterschaft",
                    "Liste der Schachweltmeister"
                };
            }

            // Falls Moderator nichts Brauchbares geliefert hat: einfache Rückfallstrategie
            if (results.Count == 0)
            {
                return new List<string> { question };
            }
            
            System.Diagnostics.Debug.WriteLine("SEARCH TERMS (FINAL):");
            foreach (var t in results)
                System.Diagnostics.Debug.WriteLine(" - " + t);
            
            return results;
        }


        // ============================================================
        // 2) Artikel-Titel + Summaries holen
        // ============================================================
        private async Task<List<(string Title, string Summary)>> RetrieveArticlesAsync(
    string question,
    List<string> searchTerms,
    CancellationToken token)
        {
            var results = new List<(string Title, string Summary)>();

            foreach (var term in searchTerms)
            {
                // --- Wikipedia-Suche ---
                var title = await _wiki.SearchAsync(term);
                System.Diagnostics.Debug.WriteLine($"WIKI SEARCH for term '{term}': title='{title}'");

                if (string.IsNullOrWhiteSpace(title))
                {
                    System.Diagnostics.Debug.WriteLine($"FILTER DROP: term='{term}', title='{title}'");
                    continue;
                }

                // Duplikate vermeiden
                if (results.Any(r => string.Equals(r.Title, title, StringComparison.OrdinalIgnoreCase)))
                {
                    System.Diagnostics.Debug.WriteLine($"FILTER DROP (duplicate): term='{term}', title='{title}'");
                    continue;
                }

                // --- Summary laden ---
                var sum = await _wiki.GetSummaryAsyncExtended(title);
                string summary = sum.Extract ?? "";

                string q = question.ToLowerInvariant();
                string s = summary.ToLowerInvariant();

                // Frage-Wörter
                var qWords = q.Split(' ')
                              .Where(w => w.Length >= 4)
                              .Select(w => w.Trim('.', ',', ':', ';', '?', '!'))
                              .ToList();

                // --- Thematische Treffer ---
                int thematicMatches = 0;
                foreach (var w in qWords)
                {
                    if (s.Contains(w))
                        thematicMatches++;
                }
                bool thematicOk = thematicMatches > 0;

                // --- SoftMatch ---
                bool softMatch = false;
                if (!thematicOk)
                {
                    var summaryTokens = s.Split(' ')
                                         .Select(t => t.Trim('.', ',', ':', ';', '?', '!', '„', '“', '"', '\''))
                                         .Where(t => t.Length >= 3)
                                         .ToList();

                    foreach (var qw in qWords)
                    {
                        foreach (var st in summaryTokens)
                        {
                            if (LevenshteinDistance(qw, st) <= 2)
                            {
                                softMatch = true;
                                break;
                            }
                        }
                        if (softMatch) break;
                    }
                }

                // --- N-Gram-Match ---
                bool ngramMatch = false;
                if (!thematicOk && !softMatch)
                {
                    var qN = BuildNGrams(q, new[] { 2, 3, 4 });

                    foreach (var ng in qN)
                    {
                        if (s.Contains(ng))
                        {
                            ngramMatch = true;
                            break;
                        }
                    }
                }

                // --- Domain-Prio: Schach → ALLES akzeptieren ---
                bool domainMatch = false;
                if (q.Contains("schach"))
                {
                    domainMatch = true;
                }

                // --- Artikel verwerfen, wenn GAR NICHTS passt ---
                if (!thematicOk && !softMatch && !ngramMatch && !domainMatch)
                {
                    System.Diagnostics.Debug.WriteLine($"FILTER DROP: term='{term}', title='{title}'");
                    continue;
                }

                // --- Volltext laden ---
                var extract = await _wiki.GetFullExtractAsync(title);

                // Fallback für Listenartikel wie „Liste der Schachweltmeister“
                bool isList = title.StartsWith("Liste der", StringComparison.OrdinalIgnoreCase);

                if (string.IsNullOrWhiteSpace(extract))
                {
                    if (!isList)
                    {
                        System.Diagnostics.Debug.WriteLine($"FILTER DROP (no extract): term='{term}', title='{title}'");
                        continue;
                    }

                    // Listenartikel haben oft keinen Voll-Extract → Summary als Ersatz
                    extract = summary;
                }

                System.Diagnostics.Debug.WriteLine($"ARTICLE ACCEPTED: {title}");
                results.Add((title, extract));
            }

            return results;
        }


        // ============================================================
        // X) Chunking: lange Artikel in kleinere Textblöcke zerlegen
        // ============================================================
        private List<ArticleChunk> ChunkArticles(
        List<(string Title, string Text)> articles,
        int maxChunkSize,
        int maxChunksPerArticle,
        int maxTotalChunks)
        {
            var chunks = new List<ArticleChunk>();

            foreach (var article in articles)
            {
                string title = article.Title;
                string text = article.Text ?? string.Empty;

                int start = 0;
                int chunksForArticle = 0;

                while (start < text.Length &&
                       chunksForArticle < maxChunksPerArticle &&
                       chunks.Count < maxTotalChunks)
                {
                    int remaining = text.Length - start;
                    int length = Math.Min(maxChunkSize, remaining);
                    int end = start + length - 1;

                    int bestEnd = text.LastIndexOfAny(new[] { '.', '!', '?', '\n' }, end);
                    if (bestEnd <= start)
                    {
                        bestEnd = end;
                    }

                    int chunkLength = bestEnd - start + 1;
                    if (chunkLength <= 0)
                        break;

                    string chunkText = text.Substring(start, chunkLength).Trim();

                    if (chunkText.Length >= 200)
                    {
                        chunks.Add(new ArticleChunk(title, chunkText));
                        chunksForArticle++;
                    }

                    start = bestEnd + 1;
                }

                if (chunks.Count >= maxTotalChunks)
                    break;
            }

            return chunks;
        }

        // ============================================================
        // X) Cosine Similarity
        // ============================================================
        private static float CosineSimilarity(List<float> a, List<float> b)
        {
            if (a == null || b == null || a.Count != b.Count)
                return 0f;

            float dot = 0f;
            float normA = 0f;
            float normB = 0f;

            for (int i = 0; i < a.Count; i++)
            {
                dot += a[i] * b[i];
                normA += a[i] * a[i];
                normB += b[i] * b[i];
            }

            float denom = (float)(Math.Sqrt(normA) * Math.Sqrt(normB));
            if (denom == 0f) return 0f;

            return dot / denom;
        }

        // ============================================================
        // X) Chunks nach Relevanz sortieren
        // ============================================================
        private async Task<List<(string Title, string Text)>> RankChunksAsync(
        string question,
        List<ArticleChunk> chunks,
        int topK,
        CancellationToken token)
        {
            var queryEmbedding = await GetCachedEmbeddingAsync(question, token);
            if (queryEmbedding == null || queryEmbedding.Count == 0)
                return chunks
                    .Take(topK)
                    .Select(c => (c.Title, c.Text))
                    .ToList();

            var scored = new List<(float Score, ArticleChunk Chunk)>();

            foreach (var c in chunks)
            {
                var emb = await GetCachedEmbeddingAsync(c.Text, token);
                if (emb != null && emb.Count == queryEmbedding.Count)
                {
                    float score = CosineSimilarity(queryEmbedding, emb);
                    scored.Add((score, c));
                }
            }

            float minScore = _cfg.MinRelevanceScore / 100f;

            var filtered = scored
                .Where(x => x.Score >= minScore)
                .OrderByDescending(x => x.Score)
                .Take(topK)
                .Select(x => (x.Chunk.Title, x.Chunk.Text))
                .ToList();

            // Fallback: mindestens ein Chunk
            if (filtered.Count == 0)
            {
                return scored
                    .OrderByDescending(x => x.Score)
                    .Take(1)
                    .Select(x => (x.Chunk.Title, x.Chunk.Text))
                    .ToList();
            }

            return filtered;

        }

        public async Task AskStreamAsync(
        string question,
        string model,
        string targetLang,      // "de", "en", "es"
        Action<string> onChunk,
        CancellationToken token)
        {
            // Sprache des Users = targetLang (von Radiobuttons)

            // Frage für RAG immer auf Deutsch übersetzen (außer DE)
            string questionDe = question;

            if (targetLang != "de")
                questionDe = await TranslateToGermanAsync(question, model, token);

            // --- Bulletpoints aus der Übersetzung vollständig entfernen ---
            questionDe = questionDe
                .Replace("•", " ")   // Bullet löschen
                .Replace("\r", " ")
                .Replace("\n", " ")
                .Trim();

            // === Datum einfügen (für RAG-Prompt) ===
            string today = DateTime.Now.ToString("dd.MM.yyyy");
            string qFinal = $"[Datum: {today}] {questionDe}";

            System.Diagnostics.Debug.WriteLine("qFinal = " + qFinal);

            // === WIKIDATA-FAKTEN ABRUFEN ===
            // --- Wikidata braucht sauberen Text (keine Bullets aus dem Übersetzer!) ---
            string qClean = questionDe.Replace("•", "").Trim();
            var wikidata = new WikipediaRagWinForms.Services.WikidataClient();
            var facts = await wikidata.GetFactsAsync(questionDe, token);

            // --- Ausgabe der strukturierten Fakten ---
            var sbFacts = new StringBuilder();
            sbFacts.AppendLine("--- Strukturierte Fakten (Wikidata) ---");

            string lbl;
            switch (targetLang)
            {
                case "en":
                    lbl = facts.LabelEn;
                    break;
                case "es":
                    lbl = facts.LabelEs;
                    break;
                default:
                    lbl = facts.LabelDe;
                    break;
            }

            string desc;
            switch (targetLang)
            {
                case "en":
                    desc = facts.DescriptionEn;
                    break;
                case "es":
                    desc = facts.DescriptionEs;
                    break;
                default:
                    desc = facts.DescriptionDe;
                    break;
            }

            if (!string.IsNullOrWhiteSpace(lbl))
                sbFacts.AppendLine("Bezeichnung: " + lbl);

            if (!string.IsNullOrWhiteSpace(desc))
                sbFacts.AppendLine("Beschreibung: " + desc);


            if (facts.Positions.Count > 0)
            {
                sbFacts.AppendLine("Position(en):");
                foreach (var p in facts.Positions)
                    sbFacts.AppendLine(" - " + p);
            }

            if (!string.IsNullOrWhiteSpace(facts.StartTime))
                sbFacts.AppendLine("Amtsbeginn: " + WikidataClient.FormatGermanDate(facts.StartTime));

            if (!string.IsNullOrWhiteSpace(facts.EndTime))
                sbFacts.AppendLine("Amtsende: " + WikidataClient.FormatGermanDate(facts.EndTime));

            if (!string.IsNullOrWhiteSpace(facts.Replaces))
                sbFacts.AppendLine("Vorgänger: " + facts.Replaces);

            if (!string.IsNullOrWhiteSpace(facts.ReplacedBy))
                sbFacts.AppendLine("Nachfolger: " + facts.ReplacedBy);

            if (!string.IsNullOrWhiteSpace(facts.BirthDate))
                sbFacts.AppendLine("Geburtsdatum: " + WikidataClient.FormatGermanDate(facts.BirthDate));

            // Nur ausgeben, wenn etwas vorhanden ist
            if (sbFacts.ToString().Trim().Length > 30)
            {
                sbFacts.AppendLine();
                onChunk(sbFacts.ToString());
            }
            else
            {
                onChunk("--- Strukturierte Fakten (Wikidata): Keine Daten gefunden ---\r\n\r\n");
            }


            // === Modell-Wissens-Check (nur Logging) ===
            bool sure = await AskModelIfSureAsync(qFinal, model, token);
            System.Diagnostics.Debug.WriteLine("=== MODEL_SURE (STREAM) ===");
            System.Diagnostics.Debug.WriteLine("RESULT: " + sure);
            System.Diagnostics.Debug.WriteLine("============================");

            var swTotal = System.Diagnostics.Stopwatch.StartNew();
            var swWiki = new System.Diagnostics.Stopwatch();
            var swLLM = new System.Diagnostics.Stopwatch();
            var swFactCheck = new System.Diagnostics.Stopwatch();

            // --- Wikipedia-Phase ---
            swWiki.Start();
            var terms = await GetSearchTermsFromModeratorAsync(qFinal, model, token);
            var articles = await RetrieveArticlesAsync(qFinal, terms, token);
            swWiki.Stop();

            if (articles.Count == 0)
            {
                onChunk($"\r\nWikipedia: Keine passenden Artikel gefunden. ({swWiki.ElapsedMilliseconds} ms)");
                return;
            }

            var chunks = ChunkArticles(
            articles,
            _cfg.MaxChunkSize,
            _cfg.MaxChunksPerArticle,
            _cfg.MaxTotalChunks);

            if (chunks.Count == 0)
            {
                onChunk($"\r\nWikipedia: Keine nutzbaren Textpassagen gefunden. ({swWiki.ElapsedMilliseconds} ms)");
                return;
            }

            // Dynamisches TopK: Artikelanzahl × max Chunks pro Artikel
            int dynamicTopK = Math.Max(1,articles.Count * _cfg.MaxChunksPerArticle);

            // Begrenzen, falls kleiner als Konfig-TopK
            dynamicTopK = Math.Max(dynamicTopK, _cfg.TopK);

            System.Diagnostics.Debug.WriteLine(
            $"RAG dynamicTopK = {dynamicTopK} (articles={articles.Count}, cfg.TopK={_cfg.TopK}, maxChunksPerArticle={_cfg.MaxChunksPerArticle})"
            );

            // Ranking durchführen
            var ranked = await RankChunksAsync(questionDe, chunks, dynamicTopK, token);

            // Header ausgeben
            var header = new StringBuilder();
            header.AppendLine("Verwendete Artikel:");
            foreach (var title in ranked.Select(r => r.Title).Distinct(StringComparer.OrdinalIgnoreCase))
                header.AppendLine(" - " + title);

            header.AppendLine();
            header.AppendLine($"Wikipedia-Suche: {swWiki.ElapsedMilliseconds} ms");
            header.AppendLine();
            header.AppendLine("Antwort:\r\n");

            onChunk(header.ToString());

            // Kontext bauen
            string context =
                string.Join("\n\n", ranked.Select(r => "Artikel: " + r.Title + "\n" + r.Text));

            // Prompt (Frage jetzt immer auf Deutsch)
            string prompt;
            bool isQwen = model.IndexOf("qwen", StringComparison.OrdinalIgnoreCase) >= 0;

            if (isQwen)
            {
                prompt =
                "Du erhältst einen Wikipedia-Kontext und eine Nutzerfrage.\n\n" +
                "AUFGABE:\n" +
                "- Antworte präzise.\n" +
                "- Verwende ausschließlich Informationen aus dem bereitgestellten Wikipedia-Kontext.\n" +
                "- Wenn die Information nicht im Kontext steht, sage: \"Im bereitgestellten Text steht darüber nichts.\".\n" +
                "- Schreibe 1–3 kurze Absätze.\n" +
                "- Keine Meta-Erklärungen.\n" +
                "- Kein Rollenspiel.\n\n" +
                "KONTEXT:\n" + context + "\n\n" +
                "FRAGE:\n" + questionDe + "\n\n" +
                "ANTWORT:";
            }
            else
            {
                prompt =
                "Du erhältst eine Nutzerfrage und Auszüge aus Wikipedia.\n\n" +
                "REGELN:\n" +
                "- Antworte ausschließlich basierend auf dem bereitgestellten Text.\n" +
                "- Maximal {_cfg.MaxSentences} Sätze.\n" +
                "- Formuliere die Informationen knapp und sachlich, aber NICHT wortwörtlich aus dem Original.\n" +
                "- Verwende keine Sätze oder Formulierungen wortwörtlich aus dem Kontext.\n" +
                "- Schreibe genau EINEN klaren, kompakten Absatz.\n" +
                "- Keine Listen, keine Bulletpoints, keine Gliederungen.\n" +
                "- Wenn die benötigte Information im Text fehlt, sage: \"Dazu enthält der bereitgestellte Text keine Angaben.\"\n\n" +
                "Wikipedia-Kontext:\n" + context + "\n\n" +
                "Frage: " + questionDe + "\n\n" +
                "Antwort:";
            }

            // --- KI-Streaming ---
            swLLM.Start();

            // KI-Streaming mit Sammelpuffer (für FactGuard)
            var answerCollector = new StringBuilder();

            await _ollama.GenerateStreamAsync(
                prompt,
                tok =>
                {
                    // Deutsch streamen (Rohantwort)
                    answerCollector.Append(tok);  // Puffer für FactGuard
                    onChunk(tok);                 // Direkt in UI
                },
                token);

            swLLM.Stop();

            string rawAnswer = answerCollector.ToString();

            // === FACT GUARD (auf deutscher Antwort + deutschem Kontext) ===
            swFactCheck.Start();
            string verified = await FactGuardAsync(rawAnswer, context, token);
            swFactCheck.Stop();

            onChunk("\r\n\n--- Faktenprüfung (Deutsch) ---\r\n");
            onChunk(verified);

            if (targetLang != "de" && verified.Length > 0)
            {
                string translated = await TranslateFromGermanAsync(verified, targetLang, token);
                onChunk("\r\n\n--- Übersetzung/Translation/Traducción ---\r\n");
                onChunk(translated);
            }

            onChunk($"\r\n\r\nKI-Antwort: {swLLM.ElapsedMilliseconds} ms\r\n");
            onChunk($"FactCheck: {swFactCheck.ElapsedMilliseconds} ms\r\n");
            onChunk($"Gesamt: {swTotal.ElapsedMilliseconds} ms\r\n\r\n");
        }


        private async Task<List<float>> GetCachedEmbeddingAsync(string text, CancellationToken token)
        {
            if (_embeddingCache.TryGetValue(text, out var cached))
                return cached;

            var emb = await _ollama.GetEmbeddingAsync(text, token);

            if (emb != null && emb.Count > 0)
                _embeddingCache[text] = emb;

            return emb;
        }

        // ==============================================================================
        // Prüft, ob das Modell sich seiner Antwort sicher ist. Leider Overconfidence!
        // ==============================================================================
        private async Task<bool> AskModelIfSureAsync(
        string question,
        string model,
        CancellationToken token)
        {
            // nutzt deutsche Prompts
            // Das ist ok, weil die Frage für RAG immer auf Deutsch vorliegt. Nur Hinweis.

            // 1) Harte Filter gegen zeitbasierte oder offene Fragen
            //    → verhindert Overconfidence ohne LLM
            string q = question.ToLowerInvariant();

            string[] unsafePatterns =
            {
            "aktuell", "derzeit", "momentan", "jetzt",
            "wer ist", "wie viele", "wieviele",
            "wann", "datum",
            "stand ", "jahr ", "202", "203",
            "nächste", "zukünftig"
    };

            foreach (var p in unsafePatterns)
            {
                if (q.Contains(p))
                    return false; // automatische Schutzblockade
            }

            // 2) LLM-Prompt: maximal strenge Formulierung
            string prompt =
                "Beantworte die folgende Frage ausschließlich mit JA oder NEIN.\r\n" +
                "\r\n" +
                "DEFINITIONEN:\r\n" +
                "JA = Du besitzt die exakte, zuverlässige und eindeutig richtige Antwort sicher aus deinem internen Trainingswissen.\r\n" +
                "NEIN = Du bist dir nicht absolut sicher ODER die Antwort könnte sich seit deinem Trainingszeitpunkt geändert haben.\r\n" +
                "Wenn du rätst oder vermutest: IMMER NEIN.\r\n" +
                "\r\n" +
                "WICHTIG:\r\n" +
                "- Du darfst nur JA sagen, wenn du die Antwort *wortwörtlich aus deinem Trainingswissen kennst*.\r\n" +
                "- Bei jeder Unsicherheit: NEIN.\r\n" +
                "- Keine Erklärungen. Nur JA oder NEIN.\r\n" +
                "\r\n" +
                "Frage: " + question + "\r\n" +
                "Antwort:";

            string raw = await _ollama.GenerateAsync(prompt, token);
            if (raw == null)
                return false;

            raw = raw.Trim().ToUpperInvariant();

            if (raw.StartsWith("JA"))
                return true;

            return false;
        }

        private Task<string> FactGuardAsync(
     string rawAnswer,
     string context,
     CancellationToken token)
        {
            if (string.IsNullOrWhiteSpace(rawAnswer))
                return Task.FromResult("");

            var parts = rawAnswer
                .Split(new[] { "\r\n\r\n", "\n\n" }, StringSplitOptions.RemoveEmptyEntries)
                .Select(p => p.Trim())
                .Where(p => p.Length > 0)
                .ToList();

            if (parts.Count == 0)
                parts.Add(rawAnswer.Trim());

            string ctx = context.ToLowerInvariant();
            var approved = new List<string>();

            foreach (var segment in parts)
            {
                string lower = segment.ToLowerInvariant();

                var words = lower
                    .Split(' ')
                    .Where(w => w.Length > 4)
                    .Select(w => w.Trim('.', ',', ':', ';', '?', '!', '"'))
                    .ToList();

                int matches = 0;
                foreach (var w in words)
                {
                    if (ctx.Contains(w))
                    {
                        matches++;
                    }
                }

                if (matches > 0)
                {
                    approved.Add(segment);
                }
            }

            if (approved.Count == 0)
                return Task.FromResult("");

            return Task.FromResult(string.Join("\r\n\r\n", approved));
        }


        private static int LevenshteinDistance(string a, string b)
        {
            if (string.IsNullOrEmpty(a)) return b?.Length ?? 0;
            if (string.IsNullOrEmpty(b)) return a.Length;

            var d = new int[a.Length + 1, b.Length + 1];

            for (int i = 0; i <= a.Length; i++)
                d[i, 0] = i;
            for (int j = 0; j <= b.Length; j++)
                d[0, j] = j;

            for (int i = 1; i < d.GetLength(0); i++)
            {
                for (int j = 1; j < d.GetLength(1); j++)
                {
                    int cost = (a[i - 1] == b[j - 1]) ? 0 : 1;
                    d[i, j] = Math.Min(
                        Math.Min(
                            d[i - 1, j] + 1,
                            d[i, j - 1] + 1
                        ),
                        d[i - 1, j - 1] + cost
                    );
                }
            }

            return d[a.Length, b.Length];
        }

        private static List<string> BuildNGrams(string text, int[] sizes)
        {
            var clean = text.Replace(" ", "").ToLowerInvariant();
            var list = new List<string>();

            foreach (var n in sizes)
            {
                if (n < 2) continue;
                if (clean.Length < n) continue;

                for (int i = 0; i <= clean.Length - n; i++)
                    list.Add(clean.Substring(i, n));
            }

            return list;
        }
    }
}
