using System.Globalization;
using OpenNest;
namespace OpenNest.Data;
/// Operator-facing wording for set releases, shared by the release views and Nest Info.
public static class SetReleaseText
{
///
/// Where a nest came from, such as "Released from set 'Kit A' revision 3 ×2 by J. Smith",
/// "Released from drawing 'Bracket' ×4", or "Merged from sets 'Kit A' revision 3 ×2 and
/// 'Kit B' revision 1 ×1 ..." for a release that added to rather
/// than creating it.
///
public static string Origin(SetReleaseRecord release, Guid nestId = default)
{
ArgumentNullException.ThrowIfNull(release);
var merged = release.Nests?.FirstOrDefault(n => n.NestId == nestId)?.Kind == SetReleaseNestKind.Merged;
var sets = release.Sets ?? Array.Empty();
var drawings = release.Drawings ?? Array.Empty();
var sources = new List();
if (sets.Count > 0)
sources.Add($"{(sets.Count == 1 ? "set" : "sets")} {Sets(sets)}");
if (drawings.Count > 0)
sources.Add(Drawings(drawings));
var text = $"{(merged ? "Merged" : "Released")} from {string.Join(" and ", sources)}";
if (release.ReleasedAt != default)
text += $" on {release.ReleasedAt.ToLocalTime().ToString("d", CultureInfo.CurrentCulture)}";
if (!string.IsNullOrWhiteSpace(release.ReleasedBy))
text += $" by {release.ReleasedBy}";
return release.IsUndone ? $"{text} (undone)" : text;
}
/// The sets of a release, such as "'Kit A' revision 3 ×2 and 'Kit B' revision 1 ×1".
public static string Sets(IReadOnlyList sets)
{
ArgumentNullException.ThrowIfNull(sets);
var names = sets.Select(s => $"'{s.SetName}' revision {s.SetRevision} ×{s.Quantity}").ToList();
return names.Count <= 1
? string.Concat(names)
: string.Join(", ", names.Take(names.Count - 1)) + " and " + names[^1];
}
///
/// The drawings a release had on their own: "drawing 'Bracket' ×4", "drawings 'A' ×1 and
/// 'B' ×2", or "5 drawings" when there are more than three.
///
public static string Drawings(IReadOnlyList drawings)
{
ArgumentNullException.ThrowIfNull(drawings);
if (drawings.Count > 3)
return $"{drawings.Count} drawings";
var names = drawings.Select(d => $"'{d.DrawingName}' ×{d.Quantity}").ToList();
var text = names.Count <= 1
? string.Concat(names)
: string.Join(", ", names.Take(names.Count - 1)) + " and " + names[^1];
return $"{(drawings.Count == 1 ? "drawing" : "drawings")} {text}";
}
///
/// How far the release's nests have got, such as "2 of 3 cut" or "1 of 2 cut, 1 deleted",
/// or "Undone". Deleted nests are left out of the count.
///
public static string Progress(SetReleaseRecord release)
{
ArgumentNullException.ThrowIfNull(release);
if (release.IsUndone)
return "Undone";
var nests = release.Nests ?? Array.Empty();
var deleted = nests.Count(IsDeleted);
var active = nests.Count - deleted;
if (active == 0)
return nests.Count == 1 ? "Its nest was deleted" : "All nests deleted";
var cut = nests.Count(n => !IsDeleted(n) && n.Nest?.Status == NestStatus.HasBeenCut);
var text = $"{cut} of {active} cut";
return deleted == 0 ? text : $"{text}, {deleted} deleted";
}
///
/// Why an undo was refused, for the operator: the release was undone before, or the nests
/// that changed since it, each with what happened to it, so they can be skipped.
///
public static string UndoRefusal(SetReleaseUndoConflict conflict)
{
ArgumentNullException.ThrowIfNull(conflict);
if (conflict.Reason == SetReleaseUndoRefusal.AlreadyUndone)
return "This release was already undone.";
var lines = conflict.ChangedNests.Select(n =>
{
var name = string.IsNullOrWhiteSpace(n.Name) ? n.Id.ToString() : n.Name;
return n.Deleted
? $"- {name}: deleted since the release"
: $"- {name}: saved since the release (now revision {n.Revision}, {NestStatuses.DisplayName(n.Status)})";
});
return "These nests changed after the release, so undoing would overwrite that work:\n"
+ string.Join("\n", lines)
+ "\n\nUndo the rest and leave these as they are, or restore them from their history.";
}
///
/// What the release did to one of its nests, and what undoing it did: "Created",
/// "Merged (revision 3 → 4)", "Created; undo deleted it".
///
public static string Change(SetReleaseNest nest)
{
ArgumentNullException.ThrowIfNull(nest);
var text = nest.Kind == SetReleaseNestKind.Merged
? $"Merged (revision {nest.RevisionBefore} → {nest.RevisionAfter})"
: "Created";
return nest.Undo switch
{
SetReleaseNestUndo.Deleted => text + "; undo deleted it",
SetReleaseNestUndo.Restored => text + $"; undo restored revision {nest.RevisionBefore}",
SetReleaseNestUndo.Skipped => text + "; undo left it as it was",
_ => text,
};
}
/// The state of one nest of a release: its status name, or "Deleted".
public static string NestState(SetReleaseNest nest)
{
ArgumentNullException.ThrowIfNull(nest);
if (IsDeleted(nest))
return "Deleted";
return nest.Nest is { } record ? NestStatuses.DisplayName(record.Status) : "";
}
/// The material group a release nest was made for, such as "Carbon Steel A36 0.25 in laser".
public static string Group(SetReleaseNest nest)
{
ArgumentNullException.ThrowIfNull(nest);
var parts = new[]
{
nest.MaterialName,
nest.MaterialGrade,
$"{nest.Thickness.ToString("0.####", CultureInfo.CurrentCulture)} {UnitsHelper.GetShortString(nest.Units)}",
nest.CutType.ToString().ToLowerInvariant(),
};
return string.Join(" ", parts.Where(p => !string.IsNullOrWhiteSpace(p)).Select(p => p.Trim()));
}
// A nest the server no longer joins is as gone as a deleted one.
private static bool IsDeleted(SetReleaseNest nest) => nest.Nest is null || nest.Nest.Deleted;
}