using System.ComponentModel.DataAnnotations;
namespace GerbilManagerWebAPI.Models
{
///
/// A gerbil (Rennmaus). Pedigree is traversed via Litter → Father/Mother (no redundant
/// parent FKs here). Genotype is one compact frozen-contract string (e.g.
/// "Aa CC Dd EE GG Pp Spsp rere", "?" wildcards allowed); never SQL-filtered.
///
public class Gerbil
{
[Key]
public Guid Id { get; set; }
public required string Name { get; set; }
public Gender Gender { get; set; }
public GerbilStatus Status { get; set; } = GerbilStatus.Breeding;
// Birth litter (the litter this gerbil was born in).
public Guid? LitterId { get; set; }
public Litter? Litter { get; set; }
// Contacts: Herkunft (origin) and Abnehmer (receiver, when given away).
public Guid? OriginContactId { get; set; }
public Contact? OriginContact { get; set; }
public Guid? ReceiverContactId { get; set; }
public Contact? ReceiverContact { get; set; }
// Current home.
public Guid? EnclosureId { get; set; }
public Enclosure? Enclosure { get; set; }
// Farbschlag.
public Guid? ColorVarietyId { get; set; }
public ColorVariety? ColorVariety { get; set; }
public DateOnly? DateOfBirth { get; set; }
public DateOnly? DateOfDeath { get; set; }
public string? CauseOfDeath { get; set; }
public DateOnly? GoHomeDate { get; set; }
/// Compact genotype string (frozen GEN-1 contract). Null = not genotyped.
public string? Genotype { get; set; }
public string? SpottingType { get; set; }
public string? Notes { get; set; }
// Provenance (for the FEAT-8 spreadsheet import).
public string? ImportSource { get; set; }
public string? ExternalRef { get; set; }
/// Data-provenance / traceability for the import: a JSON object describing
/// WHICH source information produced this entry (sourceFiles, mergedRecordCount,
/// fromWurfchronik, parentMethod/parentConfidence, notes). Written by the Python
/// merge_and_resolve step and surfaced read-only in the Rennmausakte
/// ("Nachverfolgungsinformationen"). Null = manually-added animal / no import data.
public string? Provenance { get; set; }
/// Raw import payload preserved verbatim (rawGenotype + unmappedTokens like
/// the Uw locus / WFNZ markers) so nothing from the spreadsheets is lost. JSON text.
public string? RawImportData { get; set; }
/// Breeder/Herkunft (cattery/Zucht) as free text — set by the FEAT-8 import from
/// the source Zucht (imported animals have no OriginContact). Gridify-filterable; the
/// distinct values back the Tiere "Herkunft" dropdown (GET /gerbils/breeders).
public string? OriginBreeder { get; set; }
/// Separator-insensitive search key: Name lowercased with whitespace/.-_ stripped.
/// Kept in sync automatically on save (see ApplicationContext.SaveChanges). Gridify-filterable
/// so "clan kleine chaoten" matches "Clan-Kleine-Chaoten" (client strips separators too).
public string? NameSearch { get; set; }
/// FEAT-14: Charakterbogen trait labels (opaque to the backend — the
/// {key,label} vocabulary lives frontend-side). Stored as a JSON text column.
public List CharacterTraits { get; set; } = new();
/// FEAT-14: free-text character note; feeds the AI Verkaufstext.
public string? CharacterNote { get; set; }
/// GEN-3b: hearing/deaf phenotype flag (NOT a genotype locus — it's the
/// downstream effect of high white load / Sp×Sls). null = not stated, true = deaf
/// (dea/taub), false = hearing (Dea/hörend). Set by the FEAT-8 import from the
/// after-spsp deafness annotation; see hive/agents/god/GENETIK-notation.md.
public bool? IsDeaf { get; set; }
/// Residency/ownership (ORIGIN, distinct from Abgabe location): true = part of
/// the Clan-kleine-Chaoten Bestand, false = external pedigree ancestor (bred elsewhere).
/// Rule (a) Zuchtname matches the Clan kennel, OR (b) it's a parent of a Clan offspring.
/// Defaults true (manually-added animals are own stock); the FEAT-8 import classifies
/// imported animals. See hive/agents/god/OWNERSHIP-residency.md. Gridify-filterable.
public bool IsResident { get; set; } = true;
public bool IsCastrated { get; set; }
/// true = manually created in the UI (POST /gerbils); false = produced by an import
/// path. The ingest re-import wipe deletes ONLY IsManual=false rows, so manually-added animals
/// (and their sub-records) are never wiped or overwritten. All import paths leave this false.
public bool IsManual { get; set; }
/// Reihenfolge der Jungtiere innerhalb ihres Wurfs (aus der Wurfchronik, sortiert nach
/// Geburtsgewicht). null = unbekannt → alphabetische Sortierung als Fallback. Wird im Import aus
/// conflict-decisions.json (`litterOrder`) gesetzt; von der Wurf- und Akten-Anzeige genutzt.
public int? BirthOrder { get; set; }
}
/// Shared normalisation for the separator-insensitive name search.
public static class GerbilSearch
{
public static string Normalize(string? name)
{
if (string.IsNullOrEmpty(name)) return "";
var chars = name.ToLowerInvariant()
.Where(c => !char.IsWhiteSpace(c) && c != '-' && c != '.' && c != '_');
return new string(chars.ToArray());
}
}
}