using System.Text;
using System.Text.RegularExpressions;
using GerbilManagerWebAPI.Ai;
using GerbilManagerWebAPI.Models;
using GerbilManagerWebAPI.SaleAd;
using Microsoft.Extensions.Options;
namespace GerbilManagerWebAPI.Inbox
{
///
/// INBOX-2: AI reply DRAFT for an incoming inquiry (human-in-the-loop — the
/// breeder edits and sends herself, NEVER auto-send; send is INBOX-3).
///
/// PRIVACY / data minimization (INBOX-architecture.md §privacy): the prompt
/// carries ONLY the inquiry's own text (signatures/quoted history stripped),
/// the sender's display name for the greeting, a short German context, and
/// optionally the current ForSale animals (name + Farbschlag). NOT the
/// contact DB, NOT other requests, NOT any addresses.
///
public sealed class DraftReplyService(HttpClient http, IOptions options)
{
private readonly OpenAiChatClient _client = new(http, options);
public sealed record ForSaleAnimal(string Name, string? Farbschlag);
public Task DraftAsync(
Request request, IReadOnlyList forSale, CancellationToken ct = default)
=> _client.CompleteAsync(BuildSystemPrompt(), BuildUserPrompt(request, forSale), ct);
internal static string BuildSystemPrompt() => """
Du hilfst einer Hobby-Rennmaus-Züchterin („Zucht der kleinen Chaoten“), eine
freundliche deutsche Antwort auf eine Anfrage zu ENTWERFEN. Sie liest den
Entwurf, passt ihn an und versendet selbst.
Regeln:
- Ton: warm, persönlich, hilfsbereit — wie eine erfahrene Hobby-Züchterin,
nicht wie ein Unternehmen. Anrede per „du“, Gruß mit dem Namen der
anfragenden Person, falls bekannt.
- KEINE Fakten erfinden: Verfügbarkeit, Tiere und Eigenschaften NUR aus den
mitgelieferten Daten. Ist keine Abgabetier-Liste mitgeliefert oder passt
nichts, verweise freundlich darauf, dass sie aktuelle Infos persönlich gibt.
- Keine Preise/Schutzgebühren nennen und keine Adressen oder sonstige
persönliche Daten — solche Details klärt sie selbst im Gespräch.
- Gib NUR den Antworttext aus (ohne Betreff, ohne Erklärungen) und beende
mit einem herzlichen Gruß ohne Namens-Signatur.
""";
internal static string BuildUserPrompt(Request request, IReadOnlyList forSale)
{
var sb = new StringBuilder();
sb.AppendLine("Entwirf eine Antwort auf folgende Anfrage:");
sb.AppendLine();
if (!string.IsNullOrWhiteSpace(request.FromName))
sb.AppendLine($"Von: {request.FromName}");
if (!string.IsNullOrWhiteSpace(request.Subject))
sb.AppendLine($"Betreff: {request.Subject}");
sb.AppendLine();
sb.AppendLine("Anfrage-Text:");
sb.AppendLine(StripQuotedText(request.BodyText ?? ""));
if (forSale.Count > 0)
{
sb.AppendLine();
sb.AppendLine("Aktuell abzugebende Tiere (einzige zulässige Quelle für Verfügbarkeits-Aussagen):");
foreach (var a in forSale)
{
sb.AppendLine(string.IsNullOrWhiteSpace(a.Farbschlag)
? $"- {a.Name}"
: $"- {a.Name} ({a.Farbschlag})");
}
}
return sb.ToString();
}
// ── Signatur-/Zitat-Stripping (best effort, bewusst konservativ) ────
private static readonly Regex QuoteIntro = new(
@"^Am .+ schrieb .+:\s*$", RegexOptions.Compiled);
private static readonly Regex OutlookHeader = new(
@"^(Von|Gesendet|An|Betreff):\s", RegexOptions.Compiled);
///
/// Drops quoted history ('>'-lines, the German Gmail "Am … schrieb …:"
/// intro, Outlook-style forwarded headers) and everything below a signature
/// delimiter ("-- "). Keeps the sender's own words untouched.
///
internal static string StripQuotedText(string body)
{
var lines = body.Replace("\r\n", "\n").Split('\n');
var kept = new List();
foreach (var line in lines)
{
var trimmed = line.TrimEnd();
if (trimmed == "--" || trimmed == "-- ")
break; // signature delimiter: everything below is signature
if (QuoteIntro.IsMatch(trimmed))
break; // quoted history follows
if (trimmed.StartsWith('>'))
continue;
if (OutlookHeader.IsMatch(trimmed))
continue;
kept.Add(line);
}
// collapse the whitespace the stripping may have left behind
return string.Join("\n", kept).Trim();
}
}
}