Editor/Commands/SuiReparentElementCommand.cs
using SboxUiDesigner.Runtime;

namespace SboxUiDesigner.EditorUi.Commands;

/// <summary>
/// Reparent an element to a new parent at a specific child index. Refuses to
/// move root and refuses to create cycles (new parent cannot be a descendant
/// of the moved element). Both checks fail silently — the validator surfaces
/// any inconsistency via the standard validate flow.
/// </summary>
public sealed class SuiReparentElementCommand : ISuiCommand
{
	private readonly string _elementId;
	private readonly string _newParentId;
	private readonly int _newInsertIndex;

	private bool _applied;
	private string _oldParentId;
	private int _oldIndex;

	public string Description => "Reparent element";

	public SuiReparentElementCommand( string elementId, string newParentId, int insertIndex )
	{
		_elementId = elementId;
		_newParentId = newParentId;
		_newInsertIndex = insertIndex;
	}

	public void Apply( SuiDocument doc )
	{
		if ( doc == null ) return;
		var el = doc.GetElement( _elementId );
		if ( el == null || string.IsNullOrEmpty( el.ParentId ) ) return; // refuse root
		if ( _elementId == _newParentId ) return; // refuse self-parent

		var oldParent = doc.GetElement( el.ParentId );
		var newParent = doc.GetElement( _newParentId );
		if ( oldParent == null || newParent == null ) return;

		// Refuse cycle: new parent cannot be a descendant of the moved element.
		if ( IsDescendantOf( newParent.Id, el.Id, doc ) ) return;

		_oldParentId = oldParent.Id;
		_oldIndex = oldParent.Children.IndexOf( _elementId );

		oldParent.Children.Remove( _elementId );

		var idx = _newInsertIndex;
		if ( idx < 0 ) idx = 0;
		if ( idx > newParent.Children.Count ) idx = newParent.Children.Count;
		newParent.Children.Insert( idx, _elementId );

		el.ParentId = _newParentId;
		_applied = true;
	}

	public void Undo( SuiDocument doc )
	{
		if ( !_applied || doc == null ) return;
		var el = doc.GetElement( _elementId );
		if ( el == null ) return;
		var newParent = doc.GetElement( _newParentId );
		var oldParent = doc.GetElement( _oldParentId );
		if ( newParent == null || oldParent == null ) return;

		newParent.Children.Remove( _elementId );
		var idx = _oldIndex;
		if ( idx < 0 ) idx = 0;
		if ( idx > oldParent.Children.Count ) idx = oldParent.Children.Count;
		oldParent.Children.Insert( idx, _elementId );

		el.ParentId = _oldParentId;
	}

	/// <summary>Returns true if <paramref name="candidateId"/> is the same as or a descendant of <paramref name="ancestorId"/>.</summary>
	private static bool IsDescendantOf( string candidateId, string ancestorId, SuiDocument doc )
	{
		var safety = 1024;
		var currentId = candidateId;
		while ( !string.IsNullOrEmpty( currentId ) && --safety > 0 )
		{
			if ( currentId == ancestorId ) return true;
			var current = doc.GetElement( currentId );
			if ( current == null ) return false;
			currentId = current.ParentId;
		}
		return false;
	}
}