using System.Text.Json.Serialization;
namespace Theriapolis.Core.Data;
///
/// Phase 7 M0 — per-dungeon-type rules for assembling rooms into a complete
/// dungeon. Loaded from Content/Data/dungeon_layouts/*.json.
///
/// Each layout declares: which dungeon type + size band it covers, the
/// room-count band, branching policy (linear / branching / loop), required
/// vs optional special-room roles (entry / narrative / boss / loot /
/// dead-end), and the mapping from PoI level-band → loot-table tier.
///
/// validates ranges,
/// branching enum, and loot-table-per-band references against the loaded
/// loot_tables.json.
///
/// Anchor-locked dungeons (Old Howl mine, Imperium Ruin showcase) ship as
/// special layouts whose field is set — these
/// override the procedural pipeline so the experience is identical across
/// seeds.
///
public sealed record DungeonLayoutDef
{
[JsonPropertyName("id")]
public string Id { get; init; } = "";
///
/// Dungeon type: ImperiumRuin, AbandonedMine,
/// CultDen, NaturalCave, OvergrownSettlement.
/// Matches the enum names exactly.
///
[JsonPropertyName("dungeon_type")]
public string DungeonType { get; init; } = "";
/// Size band: small / medium / large.
[JsonPropertyName("size_band")]
public string SizeBand { get; init; } = "small";
///
/// Optional anchor id. When set, this layout is the canonical fixed
/// layout for the named anchor (Old Howl mine, Imperium Ruin showcase).
/// The procedural pipeline never picks anchor-locked layouts via
/// + ; only the anchor
/// resolver consumes them.
///
[JsonPropertyName("anchor")]
public string Anchor { get; init; } = "";
[JsonPropertyName("room_count_min")]
public int RoomCountMin { get; init; } = 3;
[JsonPropertyName("room_count_max")]
public int RoomCountMax { get; init; } = 5;
///
/// Branching policy: linear (each room connects to the previous;
/// chain), branching (each room past entry connects to one prior
/// room — variable degree), loop (branching plus one extra
/// connection that closes a loop).
///
[JsonPropertyName("branching")]
public string Branching { get; init; } = "linear";
/// Special-room roles that must be present in any successful assembly.
[JsonPropertyName("required_roles")]
public string[] RequiredRoles { get; init; } = Array.Empty();
/// Special-room roles eligible for inclusion if there's room left over.
[JsonPropertyName("optional_roles")]
public string[] OptionalRoles { get; init; } = Array.Empty();
///
/// Map from loot-table band (t1/t2/t3) to a real
/// loot-table id (e.g. loot_dungeon_imperium_t2). Looked up by
/// the dungeon populator when filling container slots.
///
[JsonPropertyName("loot_table_per_band")]
public Dictionary LootTablePerBand { get; init; } = new();
///
/// Spawn-kind distribution for filling generic encounter slots — keys
/// are spawn-kind names (PoiGuard / WildAnimal /
/// Brigand), values are weights that sum to ~1.0.
///
[JsonPropertyName("spawn_kind_distribution")]
public Dictionary SpawnKindDistribution { get; init; } = new();
///
/// Map from PoI LevelBand (0..3) to a loot-table band
/// (t1/t2/t3). Keys are stringified ints
/// because rejects integer dictionary
/// keys without a custom converter.
///
[JsonPropertyName("level_band_to_loot_band")]
public Dictionary LevelBandToLootBand { get; init; } = new();
///
/// Optional anchor-pinned room sequence: when
/// is set, this array names the exact templates to use, in order.
/// Empty for non-anchor layouts (procedural pipeline picks instead).
///
[JsonPropertyName("pinned_rooms")]
public PinnedRoomEntry[] PinnedRooms { get; init; } = Array.Empty();
}
public sealed record PinnedRoomEntry
{
/// Room template id. Must reference a real .
[JsonPropertyName("template")]
public string Template { get; init; } = "";
/// Role assigned to this room slot: entry / transit / narrative / loot / boss / dead-end.
[JsonPropertyName("role")]
public string Role { get; init; } = "transit";
}