using VigilCare.Simulation; public interface IScenarioCatalog { IReadOnlyList ListScenarios(); ScenarioFile? GetById(string scenarioId); } public sealed class ScenarioCatalog : IScenarioCatalog { private readonly string _directory; private readonly object _gate = new(); private IReadOnlyList<(ScenarioFile Scenario, string Path, DateTime LastWriteUtc)> _entries = Array.Empty<(ScenarioFile, string, DateTime)>(); public ScenarioCatalog(Microsoft.Extensions.Options.IOptions options) { _directory = options.Value.ScenarioDirectory; } public IReadOnlyList ListScenarios() { RefreshIfNeeded(); return _entries.Select(e => e.Scenario).ToList(); } public ScenarioFile? GetById(string scenarioId) { RefreshIfNeeded(); return _entries .Select(e => e.Scenario) .FirstOrDefault(s => string.Equals( s.Scenario.Id, scenarioId, StringComparison.OrdinalIgnoreCase)); } private void RefreshIfNeeded() { lock (_gate) { if (!Directory.Exists(_directory)) { _entries = Array.Empty<(ScenarioFile, string, DateTime)>(); return; } var disk = Directory.EnumerateFiles(_directory, "*.json") .Where(p => !string.Equals( Path.GetFileName(p), "schema.json", StringComparison.OrdinalIgnoreCase)) .Select(p => (Path: p, LastWriteUtc: File.GetLastWriteTimeUtc(p))) .OrderBy(x => x.Path, StringComparer.OrdinalIgnoreCase) .ToList(); var unchanged = _entries.Count == disk.Count && _entries.Zip(disk, (cached, onDisk) => cached.Path == onDisk.Path && cached.LastWriteUtc == onDisk.LastWriteUtc) .All(eq => eq); if (unchanged) return; var loaded = new List<(ScenarioFile Scenario, string Path, DateTime LastWriteUtc)>(); foreach (var file in disk) { try { loaded.Add((ScenarioLoader.Load(file.Path), file.Path, file.LastWriteUtc)); } catch { // Skip corrupt files — catalogue must stay resilient. } } _entries = loaded .OrderBy(e => e.Scenario.Scenario.Id, StringComparer.OrdinalIgnoreCase) .ToList(); } } }