// Copyright © Gamesmiths Guild.
#if TOOLS
using System;
using System.Collections.Generic;
using Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers.Bases;
using Gamesmiths.Forge.Godot.Resources.Statescript;
using Gamesmiths.Forge.Godot.Resources.Statescript.Resolvers;
using Gamesmiths.Forge.Statescript.Properties;
using Godot;
using ForgeVariant128 = Gamesmiths.Forge.Statescript.Variant128;
namespace Gamesmiths.Forge.Godot.Editor.Statescript.Resolvers;
///
/// Resolver editor that compares two nested numeric resolvers and produces a boolean result. Supports nesting any
/// numeric-compatible resolver as left/right operands, enabling powerful comparisons like "Attribute > Constant".
///
[Tool]
internal sealed partial class ComparisonResolverEditor : NodeEditorProperty
{
private static readonly Type[] _numericExpectedTypes = [typeof(int), typeof(float), typeof(double)];
private StatescriptGraph? _graph;
private Action? _onChanged;
private OptionButton? _operationDropdown;
private VBoxContainer? _leftContainer;
private VBoxContainer? _rightContainer;
private FoldableContainer? _leftFoldable;
private FoldableContainer? _rightFoldable;
private OptionButton? _leftResolverDropdown;
private OptionButton? _rightResolverDropdown;
private NodeEditorProperty? _leftEditor;
private NodeEditorProperty? _rightEditor;
private List> _numericFactories = [];
private ComparisonOperation _operation;
private VBoxContainer? _leftEditorContainer;
private VBoxContainer? _rightEditorContainer;
///
public override string DisplayName => "Comparison";
///
public override string ResolverTypeId => "Comparison";
///
public override bool IsCompatibleWith(Type expectedType)
{
return expectedType == typeof(bool) || expectedType == typeof(ForgeVariant128);
}
///
public override void Setup(
StatescriptGraph graph,
StatescriptNodeProperty? property,
Type expectedType,
Action onChanged,
bool isArray)
{
_graph = graph;
_onChanged = onChanged;
SizeFlagsHorizontal = SizeFlags.ExpandFill;
var vBox = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
AddChild(vBox);
_numericFactories = ResolverEditorFactoryCatalog.GetCompatibleFactories(_numericExpectedTypes);
_numericFactories.RemoveAll(x =>
{
using NodeEditorProperty temp = x();
return temp.ResolverTypeId == "Comparison";
});
var comparisonResolver = property?.Resolver as ComparisonResolverResource;
if (comparisonResolver is not null)
{
_operation = comparisonResolver.Operation;
}
_leftFoldable = new FoldableContainer
{
Title = "Left:",
Folded = comparisonResolver?.LeftFolded ?? true,
};
_leftFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(_leftFoldable);
_leftContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_leftFoldable.AddChild(_leftContainer);
_leftEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_leftResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Left);
_leftContainer.AddChild(_leftResolverDropdown);
_leftContainer.AddChild(_leftEditorContainer);
ShowNestedEditor(
GetSelectedIndex(comparisonResolver?.Left),
comparisonResolver?.Left,
_leftEditorContainer,
x => _leftEditor = x);
_leftResolverDropdown.ItemSelected += OnLeftResolverDropdownItemSelected;
var opRow = new HBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
vBox.AddChild(opRow);
opRow.AddChild(new Label { Text = "Op:" });
_operationDropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_operationDropdown.AddItem("==");
_operationDropdown.AddItem("!=");
_operationDropdown.AddItem("<");
_operationDropdown.AddItem("<=");
_operationDropdown.AddItem(">");
_operationDropdown.AddItem(">=");
_operationDropdown.Selected = (int)_operation;
_operationDropdown.ItemSelected += OnOperationDropdownItemSelected;
opRow.AddChild(_operationDropdown);
_rightFoldable = new FoldableContainer
{
Title = "Right:",
Folded = comparisonResolver?.RightFolded ?? true,
};
_rightFoldable.FoldingChanged += OnFoldingChanged;
vBox.AddChild(_rightFoldable);
_rightContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_rightFoldable.AddChild(_rightContainer);
_rightEditorContainer = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill };
_rightResolverDropdown = CreateResolverDropdownControl(comparisonResolver?.Right);
_rightContainer.AddChild(_rightResolverDropdown);
_rightContainer.AddChild(_rightEditorContainer);
ShowNestedEditor(
GetSelectedIndex(comparisonResolver?.Right),
comparisonResolver?.Right,
_rightEditorContainer,
x => _rightEditor = x);
_rightResolverDropdown.ItemSelected += OnRightResolverDropdownItemSelected;
UpdateFoldableTitles();
}
///
public override void SaveTo(StatescriptNodeProperty property)
{
var comparisonResolver = new ComparisonResolverResource
{
Operation = _operation,
LeftFolded = _leftFoldable?.Folded ?? false,
RightFolded = _rightFoldable?.Folded ?? false,
};
if (_leftEditor is not null)
{
var leftProperty = new StatescriptNodeProperty();
_leftEditor.SaveTo(leftProperty);
comparisonResolver.Left = leftProperty.Resolver;
}
if (_rightEditor is not null)
{
var rightProperty = new StatescriptNodeProperty();
_rightEditor.SaveTo(rightProperty);
comparisonResolver.Right = rightProperty.Resolver;
}
property.Resolver = comparisonResolver;
}
///
public override void ClearCallbacks()
{
base.ClearCallbacks();
_onChanged = null;
_leftEditor?.ClearCallbacks();
_rightEditor?.ClearCallbacks();
_operationDropdown = null;
_leftContainer = null;
_rightContainer = null;
_leftFoldable = null;
_rightFoldable = null;
_leftResolverDropdown = null;
_rightResolverDropdown = null;
_leftEditor = null;
_rightEditor = null;
_leftEditorContainer = null;
_rightEditorContainer = null;
}
private void OnFoldingChanged(bool isFolded)
{
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private void OnOperationDropdownItemSelected(long x)
{
_operation = (ComparisonOperation)(int)x;
_onChanged?.Invoke();
}
private void OnLeftResolverDropdownItemSelected(long x)
{
HandleResolverDropdownChanged((int)x, _leftEditorContainer, editor => _leftEditor = editor);
}
private void OnRightResolverDropdownItemSelected(long x)
{
HandleResolverDropdownChanged((int)x, _rightEditorContainer, editor => _rightEditor = editor);
}
private void HandleResolverDropdownChanged(
int selectedIndex,
VBoxContainer? editorContainer,
Action setEditor)
{
if (editorContainer is null)
{
return;
}
foreach (Node child in editorContainer.GetChildren())
{
editorContainer.RemoveChild(child);
child.Free();
}
setEditor(null);
ShowNestedEditor(selectedIndex, null, editorContainer, setEditor);
UpdateFoldableTitles();
_onChanged?.Invoke();
RaiseLayoutSizeChanged();
}
private int GetSelectedIndex(StatescriptResolverResource? existingResolver)
{
return ResolverEditorFactoryCatalog.GetDefaultFactoryIndex(_numericFactories, existingResolver, "Variant");
}
private OptionButton CreateResolverDropdownControl(StatescriptResolverResource? existingResolver)
{
var dropdown = new OptionButton { SizeFlagsHorizontal = SizeFlags.ExpandFill };
foreach (Func factory in _numericFactories)
{
dropdown.AddItem(StatescriptResolverRegistry.GetDisplayName(factory));
}
dropdown.Selected = GetSelectedIndex(existingResolver);
return dropdown;
}
private void ShowNestedEditor(
int factoryIndex,
StatescriptResolverResource? existingResolver,
VBoxContainer container,
Action setEditor)
{
if (_graph is null || factoryIndex < 0 || factoryIndex >= _numericFactories.Count)
{
return;
}
NodeEditorProperty? editor = NestedResolverEditorUtilities.CreateNestedEditor(
_graph,
_numericFactories,
factoryIndex,
existingResolver,
_numericExpectedTypes,
OnNestedEditorChanged,
RaiseLayoutSizeChanged);
if (editor is null)
{
return;
}
container.AddChild(editor);
setEditor(editor);
}
private void OnNestedEditorChanged()
{
UpdateFoldableTitles();
_onChanged?.Invoke();
}
private void UpdateFoldableTitles()
{
if (_leftFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Left:", _leftFoldable, _leftEditor);
}
if (_rightFoldable is not null)
{
InlineConstantSummaryFormatter.ApplyFoldableTitle("Right:", _rightFoldable, _rightEditor);
}
}
}
#endif