// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Core.Attributes
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using Extension;
using Helper;
using Tags;
using UnityEngine;
using WallstopStudios.UnityHelpers.Utils;
///
/// Base class for relational component attributes that provides common functionality
/// for finding and assigning components based on hierarchy relationships.
///
///
///
/// Used by , , and
/// to control search behavior, filtering, and assignment.
///
///
/// Available Properties
///
/// - Treat fields as required (false) or optional (true)
/// - Include/exclude disabled components or inactive GameObjects
/// - Skip assigning when a field is already populated
/// - Limit results for collections (ignored for single fields)
/// - Filter by tag (exact match)
/// - Filter by name (substring match)
/// - Allow interface/base-type searches
///
///
/// Filter Interactions
///
/// TagFilter and NameFilter can be combined - both must match (AND logic)
/// When IncludeInactive is false, filters are applied AFTER excluding inactive components
/// MaxCount is applied last, after all other filters
///
///
/// Parameter Validation
///
/// MaxCount: Negative values are treated as 0 (unlimited)
/// MaxDepth (Parent/Child only): Negative values are treated as 0 (unlimited)
///
///
/// Notes
///
/// Tag filtering uses for efficient exact matches
/// Name filtering performs a case-sensitive substring match on
/// When is false, only enabled components on active-in-hierarchy GameObjects are considered
/// For single fields, has no effect
///
///
public abstract class BaseRelationalComponentAttribute : System.Attribute
{
///
/// When true, no error is logged when a matching component cannot be found.
/// When false (default), a descriptive error is logged identifying the field and expected type.
///
public bool Optional { get; set; } = false;
///
/// When true (default), includes disabled s and components on inactive GameObjects.
/// When false, only enabled components on active-in-hierarchy GameObjects are assigned.
///
public bool IncludeInactive { get; set; } = true;
///
/// When true, skips assignment if the field already has a non-null value (for single components)
/// or a non-empty collection (for arrays/lists). Default: false.
/// Useful to avoid stomping values set manually or from prior initialization.
///
public bool SkipIfAssigned { get; set; } = false;
///
/// Maximum number of components to assign to collection fields. 0 means unlimited (default).
/// Applies to arrays, lists, and hash sets. Ignored for single component fields.
///
///
/// Negative values are treated as 0 (unlimited). For single-field assignments, this property
/// has no effect since only one component can be assigned.
///
public int MaxCount
{
get => _maxCount;
set => _maxCount = value < 0 ? 0 : value;
}
private int _maxCount;
///
/// If set, only finds components on GameObjects with this tag.
/// Uses for matching.
///
public string TagFilter { get; set; } = null;
///
/// If set, only finds components on GameObjects whose names contain this string (case-sensitive substring).
///
public string NameFilter { get; set; } = null;
///
/// When true (default), allows searching by interface or base type and resolves matching components.
/// Set to false to restrict assignment to exact concrete component types only.
///
public bool AllowInterfaces { get; set; } = true;
}
///
/// Shared infrastructure for relational component attribute processing.
///
internal static class RelationalComponentProcessor
{
private static readonly MethodInfo CreateFieldAccessorGenericMethod =
typeof(RelationalComponentProcessor).GetMethod(
nameof(CreateFieldAccessorGeneric),
BindingFlags.NonPublic | BindingFlags.Static
);
internal enum FieldKind : byte
{
Single = 0,
Array = 1,
List = 2,
HashSet = 3,
}
internal readonly struct FilterParameters
{
internal readonly bool _checkHierarchy;
internal readonly bool _checkTag;
internal readonly bool _checkName;
internal readonly string _tag;
internal readonly string _nameSubstring;
internal FilterParameters(BaseRelationalComponentAttribute attribute)
{
_checkHierarchy = !attribute.IncludeInactive;
_tag = attribute.TagFilter;
_nameSubstring = attribute.NameFilter;
_checkTag = _tag != null;
_checkName = _nameSubstring != null;
}
internal bool RequiresPostProcessing => _checkHierarchy || _checkTag || _checkName;
}
// Map from cache enum to processor enum
private static FieldKind MapFieldKind(AttributeMetadataCache.FieldKind cacheKind)
{
return cacheKind switch
{
#pragma warning disable CS0618 // Type or member is obsolete
AttributeMetadataCache.FieldKind.None => FieldKind.Single,
#pragma warning restore CS0618
AttributeMetadataCache.FieldKind.Single => FieldKind.Single,
AttributeMetadataCache.FieldKind.Array => FieldKind.Array,
AttributeMetadataCache.FieldKind.List => FieldKind.List,
AttributeMetadataCache.FieldKind.HashSet => FieldKind.HashSet,
_ => FieldKind.Single,
};
}
private static FieldKind GetFieldKind(Type fieldType, out Type elementType)
{
if (fieldType == null)
{
elementType = null;
return FieldKind.Single;
}
if (fieldType.IsArray)
{
elementType = fieldType.GetElementType();
return FieldKind.Array;
}
if (fieldType.IsGenericType)
{
Type genericType = fieldType.GetGenericTypeDefinition();
if (genericType == typeof(List<>))
{
elementType = fieldType.GenericTypeArguments[0];
return FieldKind.List;
}
if (genericType == typeof(HashSet<>))
{
elementType = fieldType.GenericTypeArguments[0];
return FieldKind.HashSet;
}
}
elementType = fieldType;
return FieldKind.Single;
}
internal readonly struct FieldMetadata
where TAttribute : BaseRelationalComponentAttribute
{
public readonly FieldInfo field;
public readonly TAttribute attribute;
private readonly FieldAccessor accessor;
private readonly FilterParameters filters;
public readonly FieldKind kind;
public readonly Type elementType;
public readonly Func arrayCreator;
public readonly Func listCreator;
public readonly Func hashSetCreator;
public readonly Action