using PlotLine.Models; namespace PlotLine.ViewModels; public sealed class StoryIntelligenceGlobalProgressViewModel { public IReadOnlyList Books { get; init; } = []; public bool HasActiveWork => Books.Any(book => book.HasActiveRuns); public int ActiveBookCount => Books.Count(book => book.HasActiveRuns); public int ActiveRunCount => Books.Sum(book => book.PendingRunCount + book.RunningRunCount); public int TotalExpectedScenes => Books.Sum(book => book.TotalExpectedScenes); public int TotalDetectedScenes => TotalExpectedScenes; public int CompletedScenes => Books.Sum(book => book.CompletedScenes); public int FailedScenes => Books.Sum(book => book.FailedScenes); public int ActiveScenes => Books.Sum(book => book.ActiveScenes); public int ProgressPercent => TotalExpectedScenes <= 0 ? 0 : Math.Clamp(Convert.ToInt32(Math.Round(CompletedScenes * 100m / TotalExpectedScenes)), 0, 100); public string Status => Books.Any(book => book.Status == "Finalising") ? "Finalising" : Books.Any(book => book.HasFailures) ? StoryIntelligenceRunStatuses.CompletedWithWarnings : Books.Any(book => book.HasActiveRuns) ? StoryIntelligenceRunStatuses.Running : StoryIntelligenceRunStatuses.Completed; public string SummaryText => ActiveBookCount switch { 0 => string.Empty, 1 => Books.First(book => book.HasActiveRuns).SummaryText, _ => $"{ActiveBookCount:N0} books analysing" }; } public sealed class StoryIntelligenceGlobalProgressBookViewModel { public int? ProjectID { get; init; } public int? BookID { get; init; } public string BookTitle { get; init; } = "Book"; public int RunCount { get; init; } public int PendingRunCount { get; init; } public int RunningRunCount { get; init; } public int CompletedRunCount { get; init; } public int FailedRunCount { get; init; } public int TotalExpectedScenes { get; init; } public int TotalDetectedScenes => TotalExpectedScenes; public int CompletedScenes { get; init; } public int FailedScenes { get; init; } public int ActiveScenes { get; init; } public string Status { get; init; } = StoryIntelligenceRunStatuses.Pending; public long TotalDurationMs { get; init; } public DateTime UpdatedUtc { get; init; } public bool HasActiveRuns => PendingRunCount > 0 || RunningRunCount > 0; public bool HasFailures => FailedRunCount > 0 || FailedScenes > 0; public int ProgressPercent => TotalExpectedScenes <= 0 ? 0 : Math.Clamp(Convert.ToInt32(Math.Round(CompletedScenes * 100m / TotalExpectedScenes)), 0, 100); public string SummaryText => $"{BookTitle}: {CompletedScenes:N0}/{Math.Max(TotalExpectedScenes, CompletedScenes):N0} scenes"; public string EstimatedRemaining => Status == "Finalising" ? "Finalising" : EstimateRemaining(TotalDurationMs, CompletedScenes, FailedScenes, TotalExpectedScenes); private static string EstimateRemaining(long elapsedMs, int completedScenes, int failedScenes, int totalScenes) { if (totalScenes <= 0 || completedScenes <= 0) { return "Calculating"; } var remainingScenes = Math.Max(0, totalScenes - completedScenes - failedScenes); if (remainingScenes == 0) { return "Finalising"; } var averageSceneMs = elapsedMs / Math.Max(1, completedScenes); var remaining = TimeSpan.FromMilliseconds(averageSceneMs * remainingScenes); if (remaining.TotalMinutes >= 1) { return $"{Math.Max(1, (int)Math.Round(remaining.TotalMinutes)):N0} min"; } return $"{Math.Max(1, (int)Math.Round(remaining.TotalSeconds)):N0} sec"; } }