using Prefab.Catalog.Domain.Entities; namespace Prefab.Catalog.Domain.Services; /// /// Result of variant lookup based on the shopper's selections. /// public sealed class VariantResolutionResult { public VariantResolutionResult(Product? variant, IReadOnlyCollection errors) { Variant = variant; Errors = errors; } public Product? Variant { get; } public IReadOnlyCollection Errors { get; } public bool IsSuccess => Errors.Count == 0; } /// /// Resolves a model variant using axis selections or SKU. /// public sealed class VariantResolver { public VariantResolutionResult Resolve(Product model, IReadOnlyCollection selections, string? sku = null) { ArgumentNullException.ThrowIfNull(model); ArgumentNullException.ThrowIfNull(selections); var errors = new List(); if (!string.IsNullOrWhiteSpace(sku)) { var variant = model.Variants .Where(v => v.DeletedOn is null) .FirstOrDefault(v => string.Equals(v.Sku, sku, StringComparison.OrdinalIgnoreCase)); if (variant is null) { errors.Add($"Variant with SKU '{sku}' was not found for product '{model.Name}'."); } return new VariantResolutionResult(variant, errors); } var axisDefinitions = model.Options .Where(o => o.DeletedOn is null && o.IsVariantAxis) .ToList(); if (axisDefinitions.Count == 0) { // No variant axes means the base model should be used. return new VariantResolutionResult(null, errors); } var selectionMap = selections .Where(s => s.OptionValueId.HasValue) .ToDictionary(s => s.OptionDefinitionId, s => s.OptionValueId!.Value); foreach (var axis in axisDefinitions) { if (!selectionMap.ContainsKey(axis.Id)) { errors.Add($"Selection for variant axis '{axis.Name}' is required."); } } if (errors.Count > 0) { return new VariantResolutionResult(null, errors); } var matches = new List(); foreach (var variant in model.Variants.Where(v => v.DeletedOn is null)) { var axisValues = GetAxisValuesForVariant(model, variant); if (axisValues.Count == 0) { continue; } var isMatch = true; foreach (var axis in axisDefinitions) { var expectedValue = selectionMap[axis.Id]; if (!axisValues.TryGetValue(axis.Id, out var actualValue) || actualValue != expectedValue) { isMatch = false; break; } } if (isMatch) { matches.Add(variant); } } if (matches.Count == 1) { return new VariantResolutionResult(matches[0], errors); } if (matches.Count == 0) { errors.Add("No variant matches the provided selections."); } else { errors.Add("Multiple variants match the provided selections."); } return new VariantResolutionResult(null, errors); } private static IDictionary GetAxisValuesForVariant(Product model, Product variant) { var axisValues = variant.AxisValues? .ToDictionary(av => av.OptionDefinitionId, av => av.OptionValueId); if (axisValues is not null && axisValues.Count > 0) { return axisValues; } axisValues = model.AxisValues? .Where(av => av.ProductVariantId == variant.Id) .ToDictionary(av => av.OptionDefinitionId, av => av.OptionValueId); return axisValues ?? new Dictionary(); } }