using System;
using System.Collections.Generic;
namespace IslaApocalypse.Core
{
///
/// The two material schemas, as APPEND-ONLY registries. → `Design - Data - Material Schema.md`, D-054.
///
/// ⭐ NO MATERIAL LOGIC IS HARDCODED ANYWHERE — everything reads from here.
/// This is the specific discipline that makes deferring the full material list SAFE, unlike
/// biomes, which were hardcoded and coupled. Understand the shape now; fill the rows forever.
///
/// ⚠ THE SEED ROWS BELOW ARE NOT THE MATERIAL LIST. They are the few that are certain
/// (limestone is in — karst substrate). Do NOT enumerate the full list now; design materials
/// incrementally as building proceeds. Adding one is one new row, appended, nothing else.
///
/// ⚠ REGISTRATION ORDER IS NOT MEANINGFUL. It sets RuntimeIndex, which is never
/// serialized. Reordering these lines must not change a single persisted byte, now or ever.
///
/// Phase 0: rows are seeded in code. Loading them from a data file is a later phase — the shape
/// here (identity + properties, no logic) is what makes that a loader change and nothing more.
///
public static class MaterialRegistry
{
// ---- terrain --------------------------------------------------------
private static readonly Dictionary _terrain = new();
private static readonly List _terrainByIndex = new();
// ---- building -------------------------------------------------------
private static readonly Dictionary _building = new();
private static readonly List _buildingByIndex = new();
///
/// AIR. The one key the column model leans on structurally: everything above the surface is
/// the top AIR run, and a cave is an air run mid-column. It is a material row like any
/// other — there is no "is air" branch anywhere. → `Design - Data - Column Model.md`.
///
public static readonly MaterialKey Air = new("core.air");
static MaterialRegistry()
{
// ===== TERRAIN — seed rows, NOT final =====
// key display hardness yield per-voxel support
RegisterTerrain("core.air", "Air", 0.0f, MaterialKey.None, 0, 0.0f);
RegisterTerrain("core.sand", "Sand", 0.2f, "core.sand", 1, 0.1f);
RegisterTerrain("core.dirt", "Dirt", 0.3f, "core.dirt", 1, 0.2f);
RegisterTerrain("core.clay", "Clay", 0.4f, "core.clay", 1, 0.3f);
RegisterTerrain("core.limestone", "Limestone", 0.7f, "core.limestone", 1, 0.7f); // karst substrate
RegisterTerrain("core.stone", "Stone", 0.9f, "core.stone", 1, 0.9f);
RegisterTerrain("core.bedrock", "Bedrock", 1.0f, MaterialKey.None, 0, 1.0f); // unmineable floor
// ===== BUILDING — seed rows, NOT final =====
// key display reach decay weight durability
RegisterBuilding("build.thatch", "Thatch/Palm", 1.5f, 0.60f, 0.2f, 0.2f);
RegisterBuilding("build.wood", "Wood Plank", 4.0f, 0.30f, 0.5f, 0.5f);
RegisterBuilding("build.stone", "Worked Stone", 6.0f, 0.20f, 1.0f, 0.8f);
RegisterBuilding("build.concrete", "Concrete", 9.0f, 0.12f, 1.1f, 0.9f);
RegisterBuilding("build.steel", "Steel", 14.0f, 0.06f, 0.9f, 1.0f);
}
// ---- registration (append-only; no removal, by design) --------------
private static void RegisterTerrain(string key, string name, float hardness, MaterialKey yieldKey, int yieldPer, float support)
=> RegisterTerrain(new TerrainMaterial(new MaterialKey(key), name, hardness, yieldKey, yieldPer, support));
private static void RegisterTerrain(string key, string name, float hardness, string yieldKey, int yieldPer, float support)
=> RegisterTerrain(key, name, hardness, new MaterialKey(yieldKey), yieldPer, support);
private static void RegisterBuilding(string key, string name, float reach, float decay, float weight, float durability)
=> RegisterBuilding(new BuildingMaterial(new MaterialKey(key), name, reach, decay, weight, durability));
/// Append a terrain material. Public so mods and data loaders add rows the same way seeds do.
public static void RegisterTerrain(TerrainMaterial material)
{
if (material == null) throw new ArgumentNullException(nameof(material));
if (_terrain.ContainsKey(material.Key))
throw new InvalidOperationException($"Terrain material '{material.Key}' is already registered. The registry is append-only; keys are unique.");
material.RuntimeIndex = _terrainByIndex.Count;
_terrainByIndex.Add(material);
_terrain.Add(material.Key, material);
}
/// Append a building material.
public static void RegisterBuilding(BuildingMaterial material)
{
if (material == null) throw new ArgumentNullException(nameof(material));
if (_building.ContainsKey(material.Key))
throw new InvalidOperationException($"Building material '{material.Key}' is already registered. The registry is append-only; keys are unique.");
material.RuntimeIndex = _buildingByIndex.Count;
_buildingByIndex.Add(material);
_building.Add(material.Key, material);
}
// ---- lookup ---------------------------------------------------------
public static TerrainMaterial Terrain(MaterialKey key)
=> _terrain.TryGetValue(key, out var m)
? m
: throw new KeyNotFoundException($"No terrain material registered for key '{key}'.");
public static BuildingMaterial Building(MaterialKey key)
=> _building.TryGetValue(key, out var m)
? m
: throw new KeyNotFoundException($"No building material registered for key '{key}'.");
public static bool TryTerrain(MaterialKey key, out TerrainMaterial material) => _terrain.TryGetValue(key, out material);
public static bool TryBuilding(MaterialKey key, out BuildingMaterial material) => _building.TryGetValue(key, out material);
public static bool IsRegisteredTerrain(MaterialKey key) => _terrain.ContainsKey(key);
public static IReadOnlyList AllTerrain => _terrainByIndex;
public static IReadOnlyList AllBuilding => _buildingByIndex;
public static int TerrainCount => _terrainByIndex.Count;
public static int BuildingCount => _buildingByIndex.Count;
}
}