using System;
using System.Collections.Generic;
using UnityEngine;
namespace IdleSpectator;
/// World-session immutable observation store. Persistence is added in Phase 5.
public static class NarrativeEventStore
{
private static readonly Dictionary Events =
new Dictionary(StringComparer.Ordinal);
private static readonly Dictionary> ChildEventsByParent =
new Dictionary>();
private static readonly Dictionary> ChildEventsByChild =
new Dictionary>();
public static int Count => Events.Count;
public static void Clear()
{
Events.Clear();
ChildEventsByParent.Clear();
ChildEventsByChild.Clear();
}
public static NarrativeEventRecord Get(string id)
{
if (string.IsNullOrEmpty(id))
{
return null;
}
Events.TryGetValue(id, out NarrativeEventRecord record);
return record;
}
public static bool Add(NarrativeEventRecord record)
{
if (record == null || string.IsNullOrEmpty(record.Id) || Events.ContainsKey(record.Id))
{
return false;
}
Events[record.Id] = record;
Index(record);
NarrativePersistence.MarkDirty();
return true;
}
public static IEnumerable All() => Events.Values;
internal static bool Import(NarrativeEventRecord record)
{
if (record == null || string.IsNullOrEmpty(record.Id) || Events.ContainsKey(record.Id))
{
return false;
}
Events[record.Id] = record;
Index(record);
return true;
}
public static IReadOnlyList ChildrenOf(long parentId)
{
return parentId != 0 && ChildEventsByParent.TryGetValue(parentId, out List records)
? records
: Array.Empty();
}
public static IReadOnlyList ParentsOf(long childId)
{
return childId != 0 && ChildEventsByChild.TryGetValue(childId, out List records)
? records
: Array.Empty();
}
///
/// Keep the event side of the runtime graph aligned with retained developments.
/// Recent unprojected observations fill any remaining room so dedupe remains useful.
///
internal static int Compact(HashSet retainedDevelopmentIds, int max)
{
if (Events.Count <= max || max <= 0)
{
return 0;
}
var retained = new HashSet(StringComparer.Ordinal);
var ranked = new List(Events.Values);
ranked.Sort((a, b) =>
{
bool aLinked = a != null
&& retainedDevelopmentIds != null
&& retainedDevelopmentIds.Contains(a.DevelopmentId ?? "");
bool bLinked = b != null
&& retainedDevelopmentIds != null
&& retainedDevelopmentIds.Contains(b.DevelopmentId ?? "");
if (aLinked != bLinked) return aLinked ? -1 : 1;
float aAt = a?.ObservedAt ?? float.MinValue;
float bAt = b?.ObservedAt ?? float.MinValue;
int observed = bAt.CompareTo(aAt);
if (observed != 0) return observed;
return (b?.WorldTime ?? double.MinValue).CompareTo(
a?.WorldTime ?? double.MinValue);
});
for (int i = 0; i < ranked.Count && retained.Count < max; i++)
{
NarrativeEventRecord record = ranked[i];
if (record != null && !string.IsNullOrEmpty(record.Id))
{
retained.Add(record.Id);
}
}
int before = Events.Count;
var remove = new List();
foreach (string id in Events.Keys)
{
if (!retained.Contains(id)) remove.Add(id);
}
for (int i = 0; i < remove.Count; i++) Events.Remove(remove[i]);
if (remove.Count > 0)
{
RebuildRelationIndexes();
}
return Mathf.Max(0, before - Events.Count);
}
private static void Index(NarrativeEventRecord record)
{
if (record == null || record.Kind != NarrativeEventKind.ChildBorn)
{
return;
}
AddIndexed(ChildEventsByParent, record.SubjectId, record);
AddIndexed(ChildEventsByChild, record.OtherId, record);
}
private static void AddIndexed(
Dictionary> index,
long id,
NarrativeEventRecord record)
{
if (id == 0)
{
return;
}
if (!index.TryGetValue(id, out List records))
{
records = new List(2);
index[id] = records;
}
records.Add(record);
}
private static void RebuildRelationIndexes()
{
ChildEventsByParent.Clear();
ChildEventsByChild.Clear();
foreach (NarrativeEventRecord record in Events.Values)
{
Index(record);
}
}
}