namespace Theriapolis.Core.Data;
///
/// Pre-loaded content lookup tables. Constructing one calls every loader
/// exactly once and indexes results by id, so subsequent
/// resolver.Clades["canidae"] lookups are O(1).
///
/// Used by character creation, save/load (id → def resolution), and Phase 5 M5
/// NPC instantiation. Shared across screens that need any combination of
/// these tables.
///
public sealed class ContentResolver
{
public IReadOnlyDictionary Clades { get; }
public IReadOnlyDictionary Species { get; }
public IReadOnlyDictionary Classes { get; }
public IReadOnlyDictionary Subclasses { get; }
public IReadOnlyDictionary Backgrounds { get; }
public IReadOnlyDictionary Items { get; }
public IReadOnlyDictionary LootTables { get; }
public NpcTemplateContent Npcs { get; }
/// Phase 6 M0 — building templates + settlement layouts.
public SettlementContent Settlements { get; }
/// Phase 6 M1 — pre-meeting bias profiles per reputation.md §I-1.
public IReadOnlyDictionary BiasProfiles { get; }
/// Phase 6 M2 — faction definitions including opposition matrix entries.
public IReadOnlyDictionary Factions { get; }
/// Phase 6 M1 — generic + named friendly/neutral resident templates.
public IReadOnlyDictionary Residents { get; }
///
/// Phase 6 M1 — fast lookup of named residents by anchor-prefixed role
/// tag (e.g. "millhaven.innkeeper" → ResidentTemplateDef). Generic
/// templates live in ; this index only holds
/// the entries with named: true.
///
public IReadOnlyDictionary ResidentsByRoleTag { get; }
/// Phase 6 M3 — dialogue trees indexed by id.
public IReadOnlyDictionary Dialogues { get; }
/// Phase 6 M4 — quest trees indexed by id.
public IReadOnlyDictionary Quests { get; }
/// Phase 7 M0 — room templates indexed by id (every dungeon type).
public IReadOnlyDictionary RoomTemplates { get; }
///
/// Phase 7 M0 — room templates indexed by dungeon type (e.g. imperium
/// → IList of all imperium-typed templates). Used by the layout matcher
/// to filter candidates without a linear scan.
///
public IReadOnlyDictionary> RoomTemplatesByType { get; }
/// Phase 7 M0 — dungeon layouts indexed by id.
public IReadOnlyDictionary DungeonLayouts { get; }
///
/// Phase 7 M0 — anchor-locked layouts indexed by anchor id (e.g.
/// OldHowlMine → the pinned 3-room layout). Procedural pipeline
/// never picks anchor-locked layouts; the anchor resolver consults this
/// dict directly.
///
public IReadOnlyDictionary DungeonLayoutsByAnchor { get; }
public ContentResolver(ContentLoader loader)
{
var clades = loader.LoadClades();
Clades = clades.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
var speciesArr = loader.LoadSpecies(clades);
Species = speciesArr.ToDictionary(s => s.Id, StringComparer.OrdinalIgnoreCase);
var classes = loader.LoadClasses();
Classes = classes.ToDictionary(c => c.Id, StringComparer.OrdinalIgnoreCase);
Subclasses = loader.LoadSubclasses(classes).ToDictionary(s => s.Id, StringComparer.OrdinalIgnoreCase);
Backgrounds = loader.LoadBackgrounds().ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var items = loader.LoadItems();
Items = items.ToDictionary(i => i.Id, StringComparer.OrdinalIgnoreCase);
LootTables = loader.LoadLootTables(items).ToDictionary(t => t.Id, StringComparer.OrdinalIgnoreCase);
// Phase 6 M5 — factions loaded early so NpcTemplates can validate
// their faction field references against the canonical list.
var factionsArr = loader.LoadFactions();
Factions = factionsArr.ToDictionary(f => f.Id, StringComparer.OrdinalIgnoreCase);
Npcs = loader.LoadNpcTemplates(items, factionsArr);
// Phase 6 M0 — building/layout content.
var buildings = loader.LoadBuildingTemplates();
var layouts = loader.LoadSettlementLayouts(buildings);
var byId = buildings.ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var preset = layouts.Where(l => l.Kind == "preset")
.ToDictionary(l => l.Anchor, StringComparer.OrdinalIgnoreCase);
var proc = layouts.Where(l => l.Kind == "procedural")
.ToDictionary(l => l.Tier);
Settlements = new SettlementContent(byId, preset, proc);
// Phase 6 M1 — bias profiles + resident templates.
// (factionsArr already loaded above for NpcTemplates validation.)
var biasArr = loader.LoadBiasProfiles(clades, factionsArr);
BiasProfiles = biasArr.ToDictionary(b => b.Id, StringComparer.OrdinalIgnoreCase);
var residentArr = loader.LoadResidentTemplates(biasArr, clades, speciesArr, factionsArr);
Residents = residentArr.ToDictionary(r => r.Id, StringComparer.OrdinalIgnoreCase);
ResidentsByRoleTag = residentArr
.Where(r => r.Named)
.ToDictionary(r => r.RoleTag, StringComparer.OrdinalIgnoreCase);
// Phase 6 M3 — dialogue trees.
Dialogues = loader.LoadDialogues(items)
.ToDictionary(d => d.Id, StringComparer.OrdinalIgnoreCase);
// Phase 6 M4 — quest trees.
Quests = loader.LoadQuests(items)
.ToDictionary(q => q.Id, StringComparer.OrdinalIgnoreCase);
// Phase 7 M0 — room templates + dungeon layouts.
var roomArr = loader.LoadRoomTemplates();
RoomTemplates = roomArr.ToDictionary(r => r.Id, StringComparer.OrdinalIgnoreCase);
RoomTemplatesByType = roomArr
.GroupBy(r => r.Type, StringComparer.OrdinalIgnoreCase)
.ToDictionary(
g => g.Key,
g => (IReadOnlyList)g.ToArray(),
StringComparer.OrdinalIgnoreCase);
var layoutArr = loader.LoadDungeonLayouts(roomArr, LootTables.Values.ToArray());
DungeonLayouts = layoutArr.ToDictionary(l => l.Id, StringComparer.OrdinalIgnoreCase);
DungeonLayoutsByAnchor = layoutArr
.Where(l => !string.IsNullOrEmpty(l.Anchor))
.ToDictionary(l => l.Anchor, StringComparer.OrdinalIgnoreCase);
}
}