Code/Runtime/SuiElement.cs
using System.Collections.Generic;

namespace SboxUiDesigner.Runtime;

/// <summary>
/// One node in a .sui document tree. The full document is a flat list of these
/// — parent/child relationships are stored via <see cref="ParentId"/> and
/// <see cref="Children"/>. The validator keeps both in sync.
/// </summary>
public sealed class SuiElement
{
	/// <summary>Stable internal id. Generated at element creation, never changed on rename.</summary>
	public string Id { get; set; }

	/// <summary>User-facing name, shown in the Hierarchy widget.</summary>
	public string Name { get; set; }

	public SuiElementType Type { get; set; }

	/// <summary>Parent element id. Null for the root canvas.</summary>
	public string ParentId { get; set; }

	/// <summary>Ordered list of child element ids. Mirrors the document tree order.</summary>
	public List<string> Children { get; set; } = new();

	public SuiElementFlags Flags { get; set; } = new();
	public SuiLayoutData Layout { get; set; } = new();
	public SuiStyleData Style { get; set; } = new();
	public SuiElementProps Props { get; set; } = new();

	/// <summary>Optional designer-only notes attached to the element.</summary>
	public string Notes { get; set; }

	/// <summary>Tooltip shown when the user hovers the element at runtime.</summary>
	public string TooltipText { get; set; }

	/// <summary>True if the element should be reachable from generated C# as a [Property] field.</summary>
	public bool IsVisible { get; set; } = true;

	/// <summary>
	/// Optional widget class override — when set, the generator emits a custom
	/// PanelComponent type instead of the default per-Type behavior. V2 will
	/// surface this as a dropdown of registered classes; V1 is free-text.
	/// </summary>
	public string ClassOverride { get; set; }

	/// <summary>
	/// Optional reusable style reference — name of a shared style block
	/// applied to this element. V2 will surface this as a dropdown of styles
	/// defined in the document; V1 is free-text.
	/// </summary>
	public string StyleRef { get; set; }

	public SuiElement Clone() => new()
	{
		Id = Id,
		Name = Name,
		Type = Type,
		ParentId = ParentId,
		Children = new List<string>( Children ?? new() ),
		Flags = Flags?.Clone() ?? new(),
		Layout = Layout?.Clone() ?? new(),
		Style = Style?.Clone() ?? new(),
		Props = Props?.Clone() ?? new(),
		Notes = Notes,
		TooltipText = TooltipText,
		IsVisible = IsVisible,
		ClassOverride = ClassOverride,
		StyleRef = StyleRef,
	};

	/// <summary>
	/// Apply the per-element-type defaults (pointer-events, etc.) to a freshly
	/// created element. Called by the controller when adding a new element via
	/// the Palette so the user doesn't have to flip these manually.
	/// </summary>
	public void ApplyTypeDefaults()
	{
		// Per PRD doc 07: interactive elements default to PointerEvents.All;
		// passive elements default to None (matches runtime default).
		Style.PointerEvents = Type switch
		{
			SuiElementType.Button => SuiPointerEvents.All,
			SuiElementType.InventorySlot => SuiPointerEvents.All,
			SuiElementType.ScrollPanel => SuiPointerEvents.All,
			_ => SuiPointerEvents.None,
		};

		// Boxes + grids use Flex layout by default, everything else starts Absolute.
		// Grids (Grid, InventoryGrid) need flex+wrap or children stack at (0,0)
		// — the canvas solver doesn't have a dedicated grid-pass, but flex-row+wrap
		// produces the correct visual since the SCSS generator already maps Grid
		// to wrapped-flex (PRD doc 08 strategy A).
		Layout.Mode = Type switch
		{
			SuiElementType.HorizontalBox
				or SuiElementType.VerticalBox
				or SuiElementType.Hotbar
				or SuiElementType.Grid
				or SuiElementType.InventoryGrid
				=> SuiLayoutMode.Flex,
			_ => SuiLayoutMode.Absolute,
		};

		// Boxes pick the right flex direction.
		Layout.FlexDirection = Type switch
		{
			SuiElementType.VerticalBox => SuiFlexDirection.Column,
			SuiElementType.HorizontalBox
				or SuiElementType.Hotbar
				or SuiElementType.Grid
				or SuiElementType.InventoryGrid
				=> SuiFlexDirection.Row,
			_ => Layout.FlexDirection,
		};

		// Grid + InventoryGrid wrap on overflow so multi-row layouts work.
		// Hotbar is a single row of fixed slots → no wrap.
		Layout.FlexWrap = Type switch
		{
			SuiElementType.Grid or SuiElementType.InventoryGrid => SuiFlexWrap.Wrap,
			SuiElementType.Hotbar => SuiFlexWrap.NoWrap,
			_ => Layout.FlexWrap,
		};

		// Hotbar implies a single row.
		if ( Type == SuiElementType.Hotbar )
		{
			Props.Rows = 1;
		}

		// Default placeholder content so newly-created text elements are
		// immediately visible on the canvas (and the user has something to
		// type over). Only applied when the field is empty so existing
		// elements keep their content on Clone() / re-defaulting.
		switch ( Type )
		{
			case SuiElementType.Text:
				if ( string.IsNullOrEmpty( Props.Text ) ) Props.Text = "Text";
				break;
			case SuiElementType.Button:
				if ( string.IsNullOrEmpty( Props.ButtonText ) ) Props.ButtonText = "Button";
				break;
		}
	}
}