using System; using System.IO; using System.Threading; namespace StellaOps.ElkSharp; internal sealed class ElkLayoutDiagnosticsCapture : IDisposable { private readonly ElkLayoutRunDiagnostics? previous; internal ElkLayoutDiagnosticsCapture(ElkLayoutRunDiagnostics diagnostics, ElkLayoutRunDiagnostics? previous) { Diagnostics = diagnostics; this.previous = previous; } internal ElkLayoutRunDiagnostics Diagnostics { get; } public void Dispose() { ElkLayoutDiagnostics.Restore(previous); } } internal sealed class ElkLayoutRunDiagnostics { internal object SyncRoot { get; } = new(); public EdgeRoutingScore? InitialScore { get; set; } public EdgeRoutingScore? FinalScore { get; set; } public int BaselineBrokenShortHighwayCount { get; set; } public int FinalBrokenShortHighwayCount { get; set; } public int CompletedPasses { get; set; } public List Attempts { get; } = []; public List IterativeStrategies { get; } = []; public int SelectedStrategyIndex { get; set; } = -1; public EdgeRoutingScore? IterativeBaselineScore { get; set; } public List DetectedHighways { get; } = []; public List ProgressLog { get; } = []; public string? ProgressLogPath { get; set; } } internal sealed class ElkHighwayDiagnostics { public required string TargetNodeId { get; init; } public required string SharedAxis { get; init; } public required double SharedCoord { get; init; } public required string[] EdgeIds { get; init; } public required double MinRatio { get; init; } public required bool WasBroken { get; init; } public required string Reason { get; init; } } internal sealed class ElkIterativeStrategyDiagnostics { public required int StrategyIndex { get; init; } public required string OrderingName { get; init; } public int Attempts { get; set; } public double TotalDurationMs { get; set; } public EdgeRoutingScore? BestScore { get; set; } public required string Outcome { get; init; } public double BendPenalty { get; init; } public double DiagonalPenalty { get; init; } public double SoftObstacleWeight { get; init; } [System.Text.Json.Serialization.JsonIgnore] public ElkRoutedEdge[]? BestEdges { get; set; } public List AttemptDetails { get; } = []; } internal sealed class ElkIterativeAttemptDiagnostics { public required int Attempt { get; init; } public double TotalDurationMs { get; init; } public required EdgeRoutingScore Score { get; init; } public required string Outcome { get; init; } public ElkIterativeRouteDiagnostics? RouteDiagnostics { get; init; } public List PhaseTimings { get; } = []; [System.Text.Json.Serialization.JsonIgnore] public ElkRoutedEdge[]? Edges { get; init; } } internal sealed class ElkIterativeRouteDiagnostics { public required string Mode { get; init; } public int TotalEdges { get; init; } public int RoutedEdges { get; init; } public int SkippedEdges { get; init; } public int RoutedSections { get; init; } public int FallbackSections { get; init; } public int SoftObstacleSegments { get; init; } public IReadOnlyCollection RepairedEdgeIds { get; init; } = []; public IReadOnlyCollection RepairReasons { get; init; } = []; } internal sealed class ElkIterativePhaseDiagnostics { public required string Phase { get; init; } public double DurationMs { get; init; } } internal sealed class ElkEdgeRefinementAttemptDiagnostics { public required int PassIndex { get; init; } public required string EdgeId { get; init; } public required int Severity { get; init; } public required EdgeRoutingScore BaselineScore { get; init; } public int AttemptCount { get; set; } public int? AcceptedTrialIndex { get; set; } public EdgeRoutingScore? AcceptedScore { get; set; } public List Trials { get; } = []; } internal sealed class ElkEdgeRefinementTrialDiagnostics { public required int TrialIndex { get; init; } public required double Margin { get; init; } public required double BendPenalty { get; init; } public required double SoftObstacleWeight { get; init; } public required string Outcome { get; init; } public EdgeRoutingScore? CandidateScore { get; init; } public bool Accepted { get; set; } public IReadOnlyCollection Sections { get; init; } = []; } internal sealed class ElkDiagnosticSectionPath { public required ElkPoint StartPoint { get; init; } public required IReadOnlyCollection BendPoints { get; init; } public required ElkPoint EndPoint { get; init; } } internal static class ElkLayoutDiagnostics { private static readonly AsyncLocal CurrentDiagnostics = new(); internal static ElkLayoutRunDiagnostics? Current => CurrentDiagnostics.Value; internal static ElkLayoutDiagnosticsCapture BeginCapture() { var previous = CurrentDiagnostics.Value; var diagnostics = new ElkLayoutRunDiagnostics(); CurrentDiagnostics.Value = diagnostics; return new ElkLayoutDiagnosticsCapture(diagnostics, previous); } internal static ElkLayoutDiagnosticsCapture Attach(ElkLayoutRunDiagnostics diagnostics) { var previous = CurrentDiagnostics.Value; CurrentDiagnostics.Value = diagnostics; return new ElkLayoutDiagnosticsCapture(diagnostics, previous); } internal static void Restore(ElkLayoutRunDiagnostics? previous) { CurrentDiagnostics.Value = previous; } internal static void LogProgress(string message) { var diagnostics = CurrentDiagnostics.Value; if (diagnostics is null) { return; } var line = $"[{DateTime.UtcNow:O}] {message}"; lock (diagnostics.SyncRoot) { diagnostics.ProgressLog.Add(line); Console.WriteLine(line); if (!string.IsNullOrWhiteSpace(diagnostics.ProgressLogPath)) { File.AppendAllText(diagnostics.ProgressLogPath, line + Environment.NewLine); } } } internal static void AddDetectedHighway(ElkHighwayDiagnostics diagnostic) { var diagnostics = CurrentDiagnostics.Value; if (diagnostics is null) { return; } lock (diagnostics.SyncRoot) { diagnostics.DetectedHighways.Add(diagnostic); } } internal static void AddRefinementAttempt(ElkEdgeRefinementAttemptDiagnostics attempt) { var diagnostics = CurrentDiagnostics.Value; if (diagnostics is null) { return; } lock (diagnostics.SyncRoot) { diagnostics.Attempts.Add(attempt); } } }