using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using RobotNet10.MapManager.Data; namespace RobotNet10.MapManager.Services; /// /// Service for generating unique node and edge names using 8-character GUIDs /// Concurrent-safe and optimized for Import/Export scenarios /// public class LayoutLevelNamingService(MapDbContext context) { private readonly MapDbContext _context = context; private const int GUID_LENGTH = 8; private const int MAX_RETRIES = 5; /// /// Generate unique node name using 8-character GUID /// Format: "Node_a7f2e3b1" /// /// Layout level ID /// Generated node name or empty string if auto-generate is disabled /// If cannot generate unique name after retries public async Task GenerateNodeNameAsync(Guid levelId) { var settings = await GetOrCreateSettingsAsync(levelId); if (!settings.NodeNameAutoGenerate) return string.Empty; // Try to generate unique name with retries for (int attempt = 0; attempt < MAX_RETRIES; attempt++) { var guid = Guid.NewGuid().ToString("N"); // No hyphens var shortGuid = guid[..GUID_LENGTH]; var nodeName = $"N_{shortGuid}"; // Check uniqueness (indexed query, very fast) bool exists = await _context.Nodes .AnyAsync(n => n.LevelId == levelId && n.NodeName == nodeName); if (!exists) return nodeName; } // Extremely unlikely to reach here (probability < 0.00001%) throw new InvalidOperationException( $"Failed to generate unique node name after {MAX_RETRIES} attempts. " + "This is extremely unlikely. Please contact system administrator."); } /// /// Generate unique edge name using 8-character GUID /// Format: "Edge_a7f2e3b1" /// /// Layout level ID /// Generated edge name or empty string if auto-generate is disabled /// If cannot generate unique name after retries public async Task GenerateEdgeNameAsync(Guid levelId) { var settings = await GetOrCreateSettingsAsync(levelId); if (!settings.EdgeNameAutoGenerate) return string.Empty; // Try to generate unique name with retries for (int attempt = 0; attempt < MAX_RETRIES; attempt++) { var guid = Guid.NewGuid().ToString("N"); // No hyphens var shortGuid = guid.Substring(0, GUID_LENGTH); var edgeName = $"E_{shortGuid}"; // Check uniqueness (indexed query, very fast) bool exists = await _context.Edges .AnyAsync(e => e.LevelId == levelId && e.EdgeName == edgeName); if (!exists) return edgeName; } // Extremely unlikely to reach here (probability < 0.00001%) throw new InvalidOperationException( $"Failed to generate unique edge name after {MAX_RETRIES} attempts. " + "This is extremely unlikely. Please contact system administrator."); } /// /// Preview example generated names /// /// Number of examples to generate /// Array of example names public static string[] PreviewNodeNames(int count = 5) { var examples = new string[count]; for (int i = 0; i < count; i++) { var guid = Guid.NewGuid().ToString("N"); examples[i] = $"Node_{guid.Substring(0, GUID_LENGTH)}"; } return examples; } /// /// Preview example generated edge names /// /// Number of examples to generate /// Array of example names public static string[] PreviewEdgeNames(int count = 5) { var examples = new string[count]; for (int i = 0; i < count; i++) { var guid = Guid.NewGuid().ToString("N"); examples[i] = $"Edge_{guid.Substring(0, GUID_LENGTH)}"; } return examples; } /// /// Get or create editor settings for a layout level /// private async Task GetOrCreateSettingsAsync(Guid levelId) { var settings = await _context.LayoutLevelEditorSettings .FirstOrDefaultAsync(s => s.LevelId == levelId); if (settings == null) { // Auto-create settings with defaults if not exists settings = new LayoutLevelEditorSettings { LevelId = levelId, // Defaults are set in the entity class }; _context.LayoutLevelEditorSettings.Add(settings); await _context.SaveChangesAsync(); } return settings; } /// /// Update editor settings for a layout level /// public async Task UpdateSettingsAsync(Guid levelId, Action updateAction) { var settings = await GetOrCreateSettingsAsync(levelId); updateAction(settings); settings.ModifiedDate = DateTime.UtcNow; await _context.SaveChangesAsync(); } /// /// Get current editor settings (read-only) /// public async Task GetSettingsAsync(Guid levelId) { return await _context.LayoutLevelEditorSettings .AsNoTracking() .FirstOrDefaultAsync(s => s.LevelId == levelId); } /// /// Get collision statistics (for monitoring) /// public async Task<(int TotalNodes, int TotalEdges)> GetLevelStatisticsAsync(Guid levelId) { var nodeCount = await _context.Nodes.CountAsync(n => n.LevelId == levelId); var edgeCount = await _context.Edges.CountAsync(e => e.LevelId == levelId); return (nodeCount, edgeCount); } }