namespace WallstopStudios.DataVisualizer.Editor.Data
{
using System;
using System.Collections.Generic;
///
/// Pure evaluation of a against an object's Unity asset
/// labels. The AND/OR combination rules previously lived in two copies (the live filter loop and
/// a dead helper) which drifted apart; consolidating them here fixes that and lets the rules be
/// unit tested without an editor window or real assets.
///
/// Public because the package's editor test assembly cannot see internals of the editor
/// assembly (InternalsVisibleTo is not honored for that pair in Unity's compilation).
///
public static class LabelFilterEvaluator
{
///
/// Returns whether satisfy . An
/// empty AND or OR clause is treated as "no constraint". In OR mode an object must satisfy at
/// least one active clause; an empty clause never counts as a match — otherwise every
/// object would pass whenever only the other clause was populated (the historical bug where
/// OR-mode with only OR labels matched everything).
///
public static bool Matches(IReadOnlyList objectLabels, TypeLabelFilterConfig config)
{
if (config == null)
{
return true;
}
List andLabels = config.andLabels;
List orLabels = config.orLabels;
bool hasAnd = andLabels is { Count: > 0 };
bool hasOr = orLabels is { Count: > 0 };
if (!hasAnd && !hasOr)
{
return true;
}
IReadOnlyList present = objectLabels ?? Array.Empty();
bool andSatisfied = hasAnd && AllPresent(andLabels, present);
bool orSatisfied = hasOr && AnyPresent(orLabels, present);
return config.combinationType == LabelCombinationType.Or
? andSatisfied || orSatisfied // at least one ACTIVE clause must match
: (!hasAnd || andSatisfied) && (!hasOr || orSatisfied); // every active clause
// Local, non-capturing helpers keep this allocation-free: Matches runs once per object
// during filtering, so a per-call HashSet or closure would add real GC pressure. Asset
// label sets are tiny, so linear membership over the provided list is cheap.
static bool AllPresent(List required, IReadOnlyList labels)
{
for (int i = 0; i < required.Count; i++)
{
if (!Contains(labels, required[i]))
{
return false;
}
}
return true;
}
static bool AnyPresent(List candidates, IReadOnlyList labels)
{
for (int i = 0; i < candidates.Count; i++)
{
if (Contains(labels, candidates[i]))
{
return true;
}
}
return false;
}
static bool Contains(IReadOnlyList labels, string target)
{
for (int i = 0; i < labels.Count; i++)
{
if (string.Equals(labels[i], target, StringComparison.Ordinal))
{
return true;
}
}
return false;
}
}
}
}