using FactorioCalc.Domain;
namespace FactorioCalc.Solver;
///
/// Classifies resources into targets (user-requested), craftable (have producers),
/// and raw (must be extracted from external sources).
///
public sealed class ResourceClassifier
{
///
/// Resources that have at least one recipe producing them.
///
public IReadOnlySet Craftable { get; private set; } = default!;
///
/// Resources explicitly requested by the user.
///
public IReadOnlySet Targets { get; private set; } = default!;
///
/// Craftable resources not directly targeted — produced only as intermediates.
///
public IReadOnlySet Intermediate { get; private set; } = default!;
///
/// Resources with no known producer — must come from mining, gathering, etc.
///
public IReadOnlySet Raw { get; private set; } = default!;
///
/// All resource IDs touched by the problem (targets + every ingredient in relevant recipes).
///
public IReadOnlySet AllResourceIds { get; private set; } = default!;
public void Classify(
IReadOnlyDictionary recipes,
IReadOnlyCollection targets)
{
var targetIds = new HashSet(targets.Select(t => t.ResourceId));
// Find every resource that some recipe produces
var craftable = new HashSet();
foreach (var recipe in recipes.Values)
{
foreach (var product in recipe.Products)
craftable.Add(product.ResourceId);
}
var raw = new HashSet();
var allIds = new HashSet(targetIds);
foreach (var rid in targetIds)
{
if (!craftable.Contains(rid))
raw.Add(rid);
}
// Walk ingredients of every recipe that produces a target or craftable resource
// to discover all resources involved
var visited = new HashSet();
var queue = new Queue(targetIds);
while (queue.Count > 0)
{
var rid = queue.Dequeue();
if (visited.Contains(rid))
continue;
visited.Add(rid);
allIds.Add(rid);
if (craftable.Contains(rid))
{
foreach (var recipe in recipes.Values)
{
if (recipe.Products.Any(p => p.ResourceId == rid))
{
foreach (var ing in recipe.Ingredients)
{
allIds.Add(ing.ResourceId);
if (!visited.Contains(ing.ResourceId) && !craftable.Contains(ing.ResourceId))
raw.Add(ing.ResourceId);
queue.Enqueue(ing.ResourceId);
}
}
}
}
}
var intermediate = craftable.Where(r => !targetIds.Contains(r) && allIds.Contains(r)).ToHashSet();
Craftable = craftable.AsReadOnly();
Targets = targetIds.AsReadOnly();
Intermediate = intermediate.AsReadOnly();
Raw = raw.AsReadOnly();
AllResourceIds = allIds.AsReadOnly();
}
}