using Recyclarr.ServarrApi.QualityProfile; using Spectre.Console; using Spectre.Console.Rendering; namespace Recyclarr.Cli.Pipelines.QualityProfile.PipelinePhases; public class QualityProfilePreviewPhase(IAnsiConsole console) { public void Execute(QualityProfileTransactionData transactions) { var tree = new Tree("Quality Profile Changes [red](Preview)[/]"); foreach (var profile in transactions.UpdatedProfiles) { var profileTree = new Tree(Markup.FromInterpolated( $"[yellow]{profile.ProfileName}[/] (Change Reason: [green]{profile.UpdateReason}[/])")); profileTree.AddNode(new Rows( new Markup("[b]Profile Updates[/]"), SetupProfileTable(profile))); if (profile.ProfileConfig.Profile.Qualities.Any()) { profileTree.AddNode(SetupQualityItemTable(profile)); } profileTree.AddNode(new Rows( new Markup("[b]Score Updates[/]"), SetupScoreTable(profile))); tree.AddNode(profileTree); } console.WriteLine(); console.Write(tree); console.WriteLine(); } private static Table SetupProfileTable(UpdatedQualityProfile profile) { var table = new Table() .AddColumn("[bold]Profile Field[/]") .AddColumn("[bold]Current[/]") .AddColumn("[bold]New[/]"); var oldDto = profile.ProfileDto; var newDto = profile.BuildUpdatedDto(); table.AddRow("Name", oldDto.Name, newDto.Name); table.AddRow("Upgrades Allowed?", YesNo(oldDto.UpgradeAllowed), YesNo(newDto.UpgradeAllowed)); table.AddRow("Minimum Format Score", Null(oldDto.MinFormatScore), Null(newDto.MinFormatScore)); // ReSharper disable once InvertIf if (newDto.UpgradeAllowed is true) { table.AddRow("Upgrade Until Quality", Null(oldDto.Items.FindCutoff(oldDto.Cutoff)), Null(newDto.Items.FindCutoff(newDto.Cutoff))); table.AddRow("Upgrade Until Score", Null(oldDto.CutoffFormatScore), Null(newDto.CutoffFormatScore)); } return table; static string YesNo(bool? val) => val is true ? "Yes" : "No"; static string Null(T? val) => val is null ? "" : val.ToString() ?? ""; } private static IRenderable SetupQualityItemTable(UpdatedQualityProfile profile) { static IRenderable BuildName(ProfileItemDto item) { var allowedChar = item.Allowed is true ? ":check_mark:" : ":cross_mark:"; var name = item.Quality?.Name ?? item.Name ?? "NO NAME!"; return Markup.FromInterpolated($"{allowedChar} {name}"); } static IRenderable BuildTree(ProfileItemDto item) { var tree = new Tree(BuildName(item)); foreach (var childItem in item.Items) { tree.AddNode(BuildName(childItem)); } return tree; } static IRenderable MakeNode(ProfileItemDto item) { return item.Quality is not null ? BuildName(item) : BuildTree(item); } static IRenderable MakeTree(IEnumerable items, string header) { var headerMarkup = Markup.FromInterpolated($"[bold][underline]{header}[/][/]"); var rows = new Rows(new[] {headerMarkup}.Concat(items.Select(MakeNode))); var panel = new Panel(rows).NoBorder(); panel.Width = 23; return panel; } var table = new Columns( MakeTree(profile.ProfileDto.Items, "Current"), MakeTree(profile.UpdatedQualities.Items, "New") ); table.Collapse(); var sortMode = profile.ProfileConfig.Profile.QualitySort; return new Rows( Markup.FromInterpolated($"[b]Quality Updates (Sort Mode: [green]{sortMode}[/])[/]"), table); } private static IRenderable SetupScoreTable(UpdatedQualityProfile profile) { var updatedScores = profile.UpdatedScores .Where(x => x.Reason != FormatScoreUpdateReason.NoChange && x.Dto.Score != x.NewScore) .ToList(); if (!updatedScores.Any()) { return new Markup("[hotpink]No score changes[/]"); } var table = new Table() .AddColumn("[bold]Custom Format[/]") .AddColumn("[bold]Current[/]") .AddColumn("[bold]New[/]") .AddColumn("[bold]Reason[/]"); foreach (var score in updatedScores) { table.AddRow( score.Dto.Name, score.Dto.Score.ToString(), score.NewScore.ToString(), score.Reason.ToString()); } return table; } }