using System.Text.Json; using System.Text.Json.Serialization; namespace Roboto.Core; public sealed class ProfileManager { private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true, Converters = { new JsonStringEnumConverter() }, }; private readonly string _profilesDir; private readonly string _assignmentsFile; private readonly object _lock = new(); private Dictionary _assignments = new(); // charName → profileName public ProfileManager(string profilesDir = "profiles") { _profilesDir = Path.GetFullPath(profilesDir); _assignmentsFile = Path.Combine(_profilesDir, "_assignments.json"); EnsureDirectory(); LoadAssignments(); } public CharacterProfile LoadForCharacter(string charName) { lock (_lock) { if (_assignments.TryGetValue(charName, out var profileName)) { var profile = LoadProfile(profileName); if (profile is not null) return profile; } // Create default profile for this character var defaultName = $"{charName}_Default"; var defaultProfile = new CharacterProfile { Name = defaultName }; SaveProfile(defaultProfile); _assignments[charName] = defaultName; SaveAssignments(); return defaultProfile; } } public void Save(CharacterProfile profile) { lock (_lock) { profile.LastModified = DateTime.UtcNow; SaveProfile(profile); } } public CharacterProfile? Duplicate(string profileName, string newName) { lock (_lock) { var source = LoadProfile(profileName); if (source is null) return null; source.Name = newName; source.CreatedAt = DateTime.UtcNow; source.LastModified = DateTime.UtcNow; SaveProfile(source); return source; } } public void AssignToCharacter(string charName, string profileName) { lock (_lock) { _assignments[charName] = profileName; SaveAssignments(); } } public void Delete(string profileName) { lock (_lock) { var path = ProfilePath(profileName); if (File.Exists(path)) File.Delete(path); // Remove any assignments pointing to this profile var toRemove = _assignments .Where(kv => kv.Value == profileName) .Select(kv => kv.Key) .ToList(); foreach (var key in toRemove) _assignments.Remove(key); if (toRemove.Count > 0) SaveAssignments(); } } public List ListProfiles() { lock (_lock) { EnsureDirectory(); return Directory.GetFiles(_profilesDir, "*.json") .Select(Path.GetFileNameWithoutExtension) .Where(n => n is not null && !n.StartsWith("_")) .Cast() .OrderBy(n => n) .ToList(); } } public string? GetAssignment(string charName) { lock (_lock) { return _assignments.GetValueOrDefault(charName); } } private CharacterProfile? LoadProfile(string name) { var path = ProfilePath(name); if (!File.Exists(path)) return null; try { var json = File.ReadAllText(path); return JsonSerializer.Deserialize(json, JsonOpts); } catch { return null; } } private void SaveProfile(CharacterProfile profile) { EnsureDirectory(); var json = JsonSerializer.Serialize(profile, JsonOpts); File.WriteAllText(ProfilePath(profile.Name), json); } private void LoadAssignments() { if (!File.Exists(_assignmentsFile)) return; try { var json = File.ReadAllText(_assignmentsFile); _assignments = JsonSerializer.Deserialize>(json, JsonOpts) ?? new(); } catch { _assignments = new(); } } private void SaveAssignments() { EnsureDirectory(); var json = JsonSerializer.Serialize(_assignments, JsonOpts); File.WriteAllText(_assignmentsFile, json); } private void EnsureDirectory() { if (!Directory.Exists(_profilesDir)) Directory.CreateDirectory(_profilesDir); } private string ProfilePath(string name) => Path.Combine(_profilesDir, $"{name}.json"); }