using System.Text.Json;
using System.Text.Json.Serialization;
namespace GerbilManagerWebAPI.Import.Rpro3
{
///
/// Manuelle Entscheidungen der Züchterin zu mehrdeutigen RPRO3-Namensdubletten
/// (aus den „Fehler melden"-Tickets, Kontext „rpro3-import"). Der automatische
/// ist konservativ und legt nur bei positiver Evidenz zusammen;
/// diese Overrides erlauben der Züchterin, gezielt zu korrigieren:
///
/// • Same — Gruppen von RPRO3-Nummern (rids), die DASSELBE Tier sind
/// (zwingt ein Merge über Varianten hinweg; ein rid je Variante genügt,
/// der ganze Cluster wird mitgezogen).
/// • Different— Gruppen, die VERSCHIEDENE Tiere sind (verhindert ein automatisches
/// Zusammenlegen über Gruppengrenzen).
/// • Fields — Feld-Korrekturen am resultierenden Tier (Farbe/Geburtsdatum/Herkunft/
/// Bestands-Flag/Zusatznotiz), adressiert über IRGENDEINE rid des Clusters.
///
/// Datenquelle: Import/Rpro3/rpro3-decisions.json (siehe ).
/// Stabiler Schlüssel ist die RPRO3-Nummer (rid), nicht der Variantenbuchstabe — Buchstaben
/// verschieben sich, sobald sich die Clusterbildung ändert.
///
public sealed class Rpro3Decisions
{
public List Decisions { get; set; } = new();
public static Rpro3Decisions Empty { get; } = new();
/// Lädt die Entscheidungen aus JSON; fehlt die Datei oder ist sie leer/kaputt,
/// kommt eine leere Menge zurück (Import läuft dann mit reinem Auto-Dedup).
public static Rpro3Decisions Load(string? path)
{
if (string.IsNullOrWhiteSpace(path) || !File.Exists(path))
return Empty;
try
{
var json = File.ReadAllText(path);
var d = JsonSerializer.Deserialize(json, JsonOpts);
return d ?? Empty;
}
catch (JsonException)
{
return Empty;
}
}
private static readonly JsonSerializerOptions JsonOpts = new(JsonSerializerDefaults.Web)
{
ReadCommentHandling = JsonCommentHandling.Skip,
AllowTrailingCommas = true,
};
/// Alle „Same"-rid-Gruppen (für Force-Merge).
public IEnumerable> SameGroups()
{
foreach (var d in Decisions)
foreach (var g in d.Same)
if (g.Count >= 2) yield return g;
}
/// rid → numerische Split-Gruppe je Entscheidung. Zwei rids mit unterschiedlicher
/// Split-Gruppe (gleiche Entscheidung) dürfen NICHT automatisch zusammengelegt werden.
public Dictionary BuildSplitGroups()
{
var map = new Dictionary(StringComparer.Ordinal);
int gid = 0;
foreach (var d in Decisions)
{
if (d.Different.Count < 2) continue;
foreach (var grp in d.Different)
{
gid++;
foreach (var rid in grp)
map[rid] = gid;
}
}
return map;
}
/// rid → Feld-Override (jede rid eines Clusters zeigt auf denselben Override).
public Dictionary BuildFieldIndex()
{
var map = new Dictionary(StringComparer.Ordinal);
foreach (var d in Decisions)
foreach (var (rid, ov) in d.Fields)
map[rid] = ov;
return map;
}
}
public sealed class Rpro3Decision
{
/// Anzeigename (nur zur Lesbarkeit der JSON; Logik nutzt rids).
public string? Name { get; set; }
/// Ticket-Id (Rückverfolgbarkeit).
public string? Ticket { get; set; }
public List> Same { get; set; } = new();
public List> Different { get; set; } = new();
public Dictionary Fields { get; set; } = new();
}
public sealed class Rpro3FieldOverride
{
public string? Color { get; set; }
public string? Dob { get; set; } // ISO yyyy-MM-dd
public string? Origin { get; set; }
public bool? Resident { get; set; }
/// Zusatz, der an die Notizen des Tiers angehängt wird (z. B. abweichendes DOB).
public string? Note { get; set; }
}
}