// 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 UnityEngine;
///
/// Draws an integer field as a dropdown list using a predefined set of numeric options.
/// Designed for authoring config objects, ScriptableObjects, and MonoBehaviours that should be restricted to a known set of valid identifiers (layer masks, level IDs, refresh rates, etc.).
///
///
///
/// The attribute supports inline value lists, static providers resolved through reflection, and instance method providers.
/// Late-binding enables values to track data sets (for example, enums translated to int IDs or editor preferences stored elsewhere).
///
///
/// This attribute is implemented using the same underlying infrastructure as ,
/// specialized for integer values with a type-safe API.
///
///
///
/// Inline list:
///
/// [IntDropDown(0, 30, 60, 120)]
/// public int frameRate;
///
/// Static provider:
///
/// [IntDropDown(typeof(FrameRateLibrary), nameof(FrameRateLibrary.GetSupportedFrameRates))]
/// public int frameRate;
///
/// private static class FrameRateLibrary
/// {
/// internal static IEnumerable<int> GetSupportedFrameRates() => new[] { 30, 60, 120 };
/// }
///
/// Instance provider:
///
/// [IntDropDown(nameof(GetAvailableIds))]
/// public int selectedId;
///
/// private IEnumerable<int> GetAvailableIds() => cachedIds;
///
///
public sealed class IntDropDownAttribute : PropertyAttribute
{
private static readonly int[] Empty = Array.Empty();
private readonly WValueDropDownAttribute _backingAttribute;
private object[] _cachedSourceOptions;
private int[] _cachedIntOptions;
///
/// Gets the underlying that powers this attribute.
/// This enables sharing of infrastructure between both attribute types.
///
internal WValueDropDownAttribute BackingAttribute => _backingAttribute;
///
/// Initializes the attribute with an inline list of integer values that should be exposed in the inspector.
///
/// One or more selectable integer values. The order defined here becomes the dropdown order.
public IntDropDownAttribute(params int[] options)
{
_backingAttribute = new WValueDropDownAttribute(options ?? Array.Empty());
}
///
/// Initializes the attribute using a static provider method that supplies integer values.
///
///
/// The provider must be a parameterless static method that returns either [] or of .
/// The method is evaluated each time the inspector needs to render the property, allowing the dropdown contents to stay in sync with external state.
///
/// Type containing the static provider method (for example, typeof(SettingsCache)).
/// Name of the parameterless static method returning the options (for example, nameof(SettingsCache.GetDifficultyIds)).
public IntDropDownAttribute(Type providerType, string methodName)
{
_backingAttribute = new WValueDropDownAttribute(providerType, methodName, typeof(int));
}
///
/// Uses a method on the decorated object's type to obtain the allowed integers.
/// The method can be instance or static, must be parameterless, and return either int[] or IEnumerable<int>.
///
/// Method name declared on the target object's type.
public IntDropDownAttribute(string methodName)
{
_backingAttribute = new WValueDropDownAttribute(methodName, typeof(int));
}
///
/// Gets the set of allowed integer values that the dropdown will display without context.
/// Note: when the attribute targets an instance method, this returns an empty array.
/// The array is fetched from the configured provider whenever the inspector requests it.
///
public int[] Options => GetOptions(null);
///
/// Retrieves the allowed integer options for the supplied context object.
///
/// The object declaring the field/property. Required for instance providers.
/// Resolved option list (never null).
public int[] GetOptions(object context)
{
if (_backingAttribute == null)
{
return Empty;
}
object[] options = _backingAttribute.GetOptions(context);
if (options == null || options.Length == 0)
{
return Empty;
}
if (ReferenceEquals(options, _cachedSourceOptions) && _cachedIntOptions != null)
{
return _cachedIntOptions;
}
int[] result = new int[options.Length];
for (int i = 0; i < options.Length; i++)
{
if (options[i] is int intValue)
{
result[i] = intValue;
}
else if (options[i] != null)
{
try
{
result[i] = Convert.ToInt32(options[i]);
}
catch
{
result[i] = 0;
}
}
}
_cachedSourceOptions = options;
_cachedIntOptions = result;
return result;
}
///
/// Indicates whether this attribute uses an instance method provider.
///
internal bool RequiresInstanceContext =>
_backingAttribute?.RequiresInstanceContext ?? false;
}
}