using RobotNet10.ScriptEngine.Shared; using static System.Runtime.InteropServices.JavaScript.JSType; namespace RobotNet10.ScriptEditor.Models; public class ScriptFolder(ScriptFolderDto data, ScriptFolder? parent = null) : IHierarchyItem { public ScriptFolder? Parent => parent; public string Path => System.IO.Path.Combine(parent?.Path ?? "", Name); public int Level { get; } = data.Level; public bool IsModified { get; private set; } public int WarningCount { get; private set; } public int ErrorCount { get; private set; } public bool IsExpanded { get; set; } = false; public event Action? ChildrenChanged; public event Action? Modified; public event Action? NameChanged; public event Action? DiagnosticsChanged; public IEnumerable Folders => WorkspaceFolders; public IEnumerable Files => WorkspaceFiles; private string _name = data.Name; public string Name { get => _name; set { if (string.IsNullOrEmpty(value)) throw new ArgumentNullException($"Tên file {_name} không được để trống"); if (_name == value) return; _name = value; NameChanged?.Invoke(); } } private readonly List WorkspaceFolders = []; private readonly List WorkspaceFiles = []; public void AddFiles(params IEnumerable files) { WorkspaceFiles.AddRange(files); WorkspaceFiles.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); // Recalculate totals including new files var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount); var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount); var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified); var warningChanged = WarningCount != totalWarningCount; var errorChanged = ErrorCount != totalErrorCount; var modifiedChanged = IsModified != newIsModified; WarningCount = totalWarningCount; ErrorCount = totalErrorCount; IsModified = newIsModified; foreach (var file in files) { file.Modified += UpdateModified; file.DiagnosticsChanged += OnDiagnosticsChanged; } // Notify changes if (warningChanged || errorChanged) { DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } if (modifiedChanged) { Modified?.Invoke(); } ChildrenChanged?.Invoke(); } public void AddFolders(params IEnumerable folders) { WorkspaceFolders.AddRange(folders); WorkspaceFolders.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); // Recalculate totals including new folders var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount); var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount); var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified); var warningChanged = WarningCount != totalWarningCount; var errorChanged = ErrorCount != totalErrorCount; var modifiedChanged = IsModified != newIsModified; WarningCount = totalWarningCount; ErrorCount = totalErrorCount; IsModified = newIsModified; foreach (var folder in folders) { folder.Modified += UpdateModified; folder.DiagnosticsChanged += OnDiagnosticsChanged; } // Notify changes if (warningChanged || errorChanged) { DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } if (modifiedChanged) { Modified?.Invoke(); } ChildrenChanged?.Invoke(); } public void RemoveFile(ScriptFile file) { if (WorkspaceFiles.Remove(file)) { // Unsubscribe from file events file.Modified -= UpdateModified; file.DiagnosticsChanged -= OnDiagnosticsChanged; // Recalculate totals after removal var totalWarningCount = Files.Sum(f => f.WarningCount) + Folders.Sum(folder => folder.WarningCount); var totalErrorCount = Files.Sum(f => f.ErrorCount) + Folders.Sum(folder => folder.ErrorCount); var newIsModified = Files.Any(f => f.IsModified) || Folders.Any(folder => folder.IsModified); var warningChanged = WarningCount != totalWarningCount; var errorChanged = ErrorCount != totalErrorCount; var modifiedChanged = IsModified != newIsModified; WarningCount = totalWarningCount; ErrorCount = totalErrorCount; IsModified = newIsModified; // Dispose the removed file file.Dispose(); // Notify changes if (warningChanged || errorChanged) { DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } if (modifiedChanged) { Modified?.Invoke(); } ChildrenChanged?.Invoke(); } } public void RemoveFolder(ScriptFolder folder) { if (WorkspaceFolders.Remove(folder)) { // Unsubscribe from folder events folder.Modified -= UpdateModified; folder.DiagnosticsChanged -= OnDiagnosticsChanged; // Recalculate totals after removal var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(f => f.WarningCount); var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(f => f.ErrorCount); var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(f => f.IsModified); var warningChanged = WarningCount != totalWarningCount; var errorChanged = ErrorCount != totalErrorCount; var modifiedChanged = IsModified != newIsModified; WarningCount = totalWarningCount; ErrorCount = totalErrorCount; IsModified = newIsModified; // Dispose the removed folder (will handle its children) folder.Dispose(); // Notify changes if (warningChanged || errorChanged) { DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } if (modifiedChanged) { Modified?.Invoke(); } ChildrenChanged?.Invoke(); } } private void UpdateModified() { var newIsModified = Files.Any(file => file.IsModified) || Folders.Any(folder => folder.IsModified); // Only update and notify if the value actually changed if (IsModified != newIsModified) { IsModified = newIsModified; Modified?.Invoke(); } } private void OnDiagnosticsChanged(int warningCount, int errorCount) { // Recalculate totals from all children // Note: The parameters (warningCount, errorCount) are the absolute values from the child that changed, // but we recalculate from all children to ensure accuracy, especially when multiple files change simultaneously var totalWarningCount = Files.Sum(file => file.WarningCount) + Folders.Sum(folder => folder.WarningCount); var totalErrorCount = Files.Sum(file => file.ErrorCount) + Folders.Sum(folder => folder.ErrorCount); if (WarningCount != totalWarningCount || ErrorCount != totalErrorCount) { WarningCount = totalWarningCount; ErrorCount = totalErrorCount; DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } } /// /// Recalculates WarningCount, ErrorCount, and IsModified from all children. /// This is useful after diagnostics have been updated for all children. /// internal void RecalculateTotals() { // First, recalculate all child folders recursively foreach (var subFolder in Folders) { subFolder.RecalculateTotals(); } // Then calculate totals from all children var totalWarningCount = Files.Sum(f => f.WarningCount) + Folders.Sum(f => f.WarningCount); var totalErrorCount = Files.Sum(f => f.ErrorCount) + Folders.Sum(f => f.ErrorCount); var isModified = Files.Any(f => f.IsModified) || Folders.Any(f => f.IsModified); // Update properties if changed var warningChanged = WarningCount != totalWarningCount; var errorChanged = ErrorCount != totalErrorCount; var modifiedChanged = IsModified != isModified; if (warningChanged || errorChanged || modifiedChanged) { WarningCount = totalWarningCount; ErrorCount = totalErrorCount; IsModified = isModified; if (warningChanged || errorChanged) { DiagnosticsChanged?.Invoke(WarningCount, ErrorCount); } if (modifiedChanged) { Modified?.Invoke(); } } } public void Dispose() { foreach (var file in WorkspaceFiles) { file.Dispose(); } WorkspaceFiles.Clear(); foreach (var folder in Folders) { folder.Dispose(); } WorkspaceFolders.Clear(); GC.SuppressFinalize(this); } }