using OpenNest;
namespace OpenNest.Data;
/// One library drawing a nest would add to a set, with the nest's required quantity.
public sealed class NestSetCandidate
{
internal NestSetCandidate(DrawingLibraryLink link, string name, int quantity)
{
Link = link;
Name = name;
Quantity = quantity;
}
/// The link of the first nest drawing copied from this library drawing.
public DrawingLibraryLink Link { get; }
public Guid DrawingId => Link.Id;
/// The nest drawing's name.
public string Name { get; }
/// The required quantity, summed over every nest drawing copied from this library drawing.
public int Quantity { get; }
}
/// A nest drawing left out of the set, and why.
public sealed record NestSetSkip(string Name, string Reason);
/// A drawing the set already lists: its quantity there is kept, not replaced or added to.
public sealed record NestSetKept(string Name, int SetQuantity, int NestQuantity);
/// What changed in the set editor.
public sealed class NestSetMergeResult
{
internal NestSetMergeResult(IReadOnlyList added, IReadOnlyList kept,
IReadOnlyList skipped, IReadOnlyList warnings)
{
Added = added;
Kept = kept;
Skipped = skipped;
Warnings = warnings;
}
/// The drawings appended to the set, in order.
public IReadOnlyList Added { get; }
public IReadOnlyList Kept { get; }
/// Every nest drawing left out: those the plan skipped, then those the library no longer holds.
public IReadOnlyList Skipped { get; }
/// Added drawings the operator should know about (archived, or newer in the library).
public IReadOnlyList Warnings { get; }
/// True when the operator only needs to review the editor, with nothing else to read first.
public bool IsClean => Kept.Count == 0 && Skipped.Count == 0 && Warnings.Count == 0;
/// The operator-facing account of what was added, kept, skipped and why.
public string Describe(string setName)
{
var lines = new List();
var target = string.IsNullOrWhiteSpace(setName) ? "the new set" : $"set '{setName}'";
lines.Add(Added.Count == 0
? $"No drawings were added to {target}."
: $"Added {Added.Count} drawing{(Added.Count == 1 ? "" : "s")} to {target}.");
if (Kept.Count > 0)
{
lines.Add("");
lines.Add("Already in the set (its quantity is kept; edit it there to change it):");
lines.AddRange(Kept.Select(k => $"- {k.Name}: set {k.SetQuantity}, nest {k.NestQuantity}"));
}
if (Skipped.Count > 0)
{
lines.Add("");
lines.Add("Skipped:");
lines.Add(Lines(Skipped));
}
if (Warnings.Count > 0)
{
lines.Add("");
lines.Add("Check:");
lines.AddRange(Warnings.Select(w => "- " + w));
}
return string.Join(Environment.NewLine, lines);
}
internal static string Lines(IEnumerable skipped) =>
string.Join(Environment.NewLine, skipped.Select(s => $"- {s.Name}: {s.Reason}"));
}
///
/// Adds a nest's library drawings to a drawing set (Slice 6). is a snapshot of
/// the nest taken before any I/O: each library drawing on this server once, with the required
/// quantities of its nest copies summed. Drawings that were never saved to the library, came from
/// another server or have no required quantity are skipped with a reason; cutoffs are not parts
/// and are left out silently. then reads each drawing's current library
/// record and appends it to a , which the operator reviews and
/// saves. A drawing the set already lists keeps the set's quantity: a nest released from a set
/// carries its multiplier, so copying its quantities back would scale the kit.
///
public sealed class NestSetMerge
{
private const string UnlinkedReason = "not in the drawing library (save it to the library first)";
private NestSetMerge(IReadOnlyList candidates, IReadOnlyList skipped)
{
Candidates = candidates;
Skipped = skipped;
}
/// The library drawings to add, in the drawing list's (name) order.
public IReadOnlyList Candidates { get; }
public IReadOnlyList Skipped { get; }
/// The skipped drawings, one "- name: reason" line each.
public string DescribeSkipped() => NestSetMergeResult.Lines(Skipped);
public static NestSetMerge Plan(Nest nest, string serverUrl)
{
ArgumentNullException.ThrowIfNull(nest);
var serverKey = ServerUrl.Key(serverUrl);
if (serverKey.Length == 0)
throw new ArgumentException("A nest server URL is required.", nameof(serverUrl));
var skipped = new List();
var linked = new List<(DrawingLibraryLink Link, string Name, int Quantity)>();
foreach (var drawing in nest.Drawings.Where(d => d != null && !d.IsCutOff).OrderBy(d => d.Name))
{
var name = drawing.Name ?? "";
var link = drawing.Library;
if (link == null)
skipped.Add(new NestSetSkip(name, UnlinkedReason));
else if (!string.Equals(link.ServerKey, serverKey, StringComparison.Ordinal))
skipped.Add(new NestSetSkip(name, $"from the drawing library on another server ({link.ServerKey})"));
else if (drawing.Quantity.Required < 1)
skipped.Add(new NestSetSkip(name, "has no required quantity"));
else
linked.Add((link, name, drawing.Quantity.Required));
}
var candidates = new List();
foreach (var group in linked.GroupBy(l => l.Link.Id))
{
var first = group.First();
var quantity = group.Sum(l => (long)l.Quantity);
if (quantity > DrawingSetRules.MaxQuantity)
{
skipped.Add(new NestSetSkip(first.Name,
$"quantity {quantity:N0} is more than a set member allows ({DrawingSetRules.MaxQuantity:N0})"));
continue;
}
candidates.Add(new NestSetCandidate(first.Link, first.Name, (int)quantity));
}
return new NestSetMerge(candidates, skipped);
}
///
/// Reads each candidate's current library record and appends the drawings
/// does not already list. Nothing is saved; a read failure throws
/// before changes.
///
public async Task ApplyAsync(IDrawingLibraryStore library, DrawingSetEditSession edit,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(library);
ArgumentNullException.ThrowIfNull(edit);
var skipped = new List(Skipped);
var kept = new List();
var found = new List<(NestSetCandidate Candidate, DrawingLibraryRecord Record)>();
foreach (var candidate in Candidates)
{
var existing = edit.Members.FirstOrDefault(m => m.DrawingId == candidate.DrawingId);
if (existing != null)
{
kept.Add(new NestSetKept(candidate.Name, existing.Quantity, candidate.Quantity));
continue;
}
var record = await library.GetAsync(candidate.DrawingId, cancellationToken);
if (record == null)
skipped.Add(new NestSetSkip(candidate.Name, "no longer in the drawing library on this server"));
else
found.Add((candidate, record));
}
var added = new List();
var warnings = new List();
foreach (var (candidate, record) in found)
{
edit.AddDrawings(new[] { record }, candidate.Quantity);
added.Add(candidate);
if (record.Archived)
warnings.Add($"'{record.Name}' is archived in the library; releases skip it unless it is ticked back in.");
else if (record.Revision > candidate.Link.Revision)
warnings.Add($"'{record.Name}' is at revision {record.Revision} in the library; this nest has revision "
+ $"{candidate.Link.Revision}. Sets release the latest revision.");
}
return new NestSetMergeResult(added, kept, skipped, warnings);
}
}