Editor/Widgets/SuiBindingsWidget.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;
using SboxUiDesigner.Runtime;

namespace SboxUiDesigner.EditorUi.Widgets;

/// <summary>
/// Bindings tab — minimal table of property bindings for the current document
/// (mockup Image 1). Each row: Target | Property | Source | Path | Mode + delete.
/// Add Binding button at the top.
///
/// V1 persists bindings in the .sui (<c>SuiDocument.Bindings</c>) but does
/// NOT emit them to the generated Razor yet. V2 will translate to
/// <c>@bind-X</c> + <c>[Property]</c> code.
/// </summary>
public sealed class SuiBindingsWidget : Widget
{
	private SuiDocument _document;
	private Widget _listHost;
	private LineEdit _searchInput;
	private string _filter = "";

	public SuiBindingsWidget( Widget parent = null ) : base( parent )
	{
		WindowTitle = "Bindings";
		Name = "SuiBindings";

		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 6;

		// Top toolbar — Add Binding + Search.
		var top = new Widget( this );
		top.Layout = Layout.Row();
		top.Layout.Spacing = 6;
		top.FixedHeight = 28;

		var addBtn = new Button( "Add Binding", "add", top );
		addBtn.Clicked += AddBinding;
		top.Layout.Add( addBtn );

		_searchInput = new LineEdit( top );
		_searchInput.PlaceholderText = "Search bindings…";
		_searchInput.TextEdited += s => { _filter = (s ?? "").ToLowerInvariant(); RefreshList(); };
		top.Layout.Add( _searchInput, 1 );

		Layout.Add( top );

		// Header row.
		var header = new Widget( this );
		header.Layout = Layout.Row();
		header.Layout.Spacing = 6;
		header.FixedHeight = 22;
		AddHeaderCell( header, "Target", 1 );
		AddHeaderCell( header, "Property", 1 );
		AddHeaderCell( header, "Binding Source", 1 );
		AddHeaderCell( header, "Path", 1 );
		AddHeaderCell( header, "Mode", 0, 70 );
		var delHdr = new Label( "", header );
		delHdr.FixedWidth = 24;
		header.Layout.Add( delHdr );
		Layout.Add( header );

		// Scrollable list of binding rows.
		var scroll = new ScrollArea( this );
		scroll.Canvas = new Widget( null );
		scroll.Canvas.Layout = Layout.Column();
		scroll.Canvas.Layout.Margin = 0;
		scroll.Canvas.Layout.Spacing = 2;
		_listHost = scroll.Canvas;
		Layout.Add( scroll, 1 );
	}

	public void SetDocument( SuiDocument document )
	{
		_document = document;
		RefreshList();
	}

	private void AddBinding()
	{
		if ( _document == null ) return;
		_document.Bindings.Add( new SuiPropertyBinding
		{
			TargetElementId = _document.GetRoot()?.Id,
			Property = "Value",
			Source = "Player",
			Path = "Health",
			Mode = "OneWay",
		} );
		RefreshList();
	}

	private void RefreshList()
	{
		if ( _listHost?.Layout == null ) return;
		_listHost.Layout.Clear( true );
		if ( _document == null )
		{
			var none = new Label( "(no document)", _listHost );
			none.SetStyles( "color: #6b7280; font-size: 11px;" );
			_listHost.Layout.Add( none );
			return;
		}

		var any = false;
		foreach ( var b in _document.Bindings )
		{
			if ( !MatchesFilter( b ) ) continue;
			any = true;
			BuildRow( b );
		}
		if ( !any )
		{
			var none = new Label( string.IsNullOrEmpty( _filter )
				? "No bindings yet — click 'Add Binding' to create one."
				: "No bindings match your search.", _listHost );
			none.SetStyles( "color: #6b7280; font-size: 11px; padding: 8px;" );
			_listHost.Layout.Add( none );
		}
		_listHost.Layout.AddStretchCell();
	}

	private bool MatchesFilter( SuiPropertyBinding b )
	{
		if ( string.IsNullOrEmpty( _filter ) ) return true;
		var elName = ResolveTargetName( b.TargetElementId )?.ToLowerInvariant() ?? "";
		return elName.Contains( _filter )
			|| (b.Property?.ToLowerInvariant() ?? "").Contains( _filter )
			|| (b.Source?.ToLowerInvariant() ?? "").Contains( _filter )
			|| (b.Path?.ToLowerInvariant() ?? "").Contains( _filter );
	}

	private string ResolveTargetName( string elementId )
	{
		if ( _document == null || string.IsNullOrEmpty( elementId ) ) return null;
		var el = _document.GetElement( elementId );
		return el?.Name;
	}

	private void BuildRow( SuiPropertyBinding b )
	{
		var row = new Widget( _listHost );
		row.Layout = Layout.Row();
		row.Layout.Spacing = 6;
		row.FixedHeight = 26;

		// Target — dropdown of elements in the document.
		var targetBtn = new Button( ResolveTargetName( b.TargetElementId ) ?? "(unset)", "category", row );
		targetBtn.Clicked += () =>
		{
			var menu = new Menu( targetBtn );
			foreach ( var el in _document.Elements )
			{
				if ( string.IsNullOrEmpty( el.ParentId ) ) continue;
				var captured = el;
				menu.AddOption( captured.Name, "category", () => { b.TargetElementId = captured.Id; RefreshList(); } );
			}
			menu.OpenAtCursor( true );
		};
		row.Layout.Add( targetBtn, 1 );

		// Property — text input.
		var propEdit = new LineEdit( row );
		propEdit.Text = b.Property ?? "";
		propEdit.PlaceholderText = "Value";
		propEdit.EditingFinished += () => b.Property = propEdit.Text;
		row.Layout.Add( propEdit, 1 );

		// Source — text input.
		var srcEdit = new LineEdit( row );
		srcEdit.Text = b.Source ?? "";
		srcEdit.PlaceholderText = "Player";
		srcEdit.EditingFinished += () => b.Source = srcEdit.Text;
		row.Layout.Add( srcEdit, 1 );

		// Path — text input.
		var pathEdit = new LineEdit( row );
		pathEdit.Text = b.Path ?? "";
		pathEdit.PlaceholderText = "Health";
		pathEdit.EditingFinished += () => b.Path = pathEdit.Text;
		row.Layout.Add( pathEdit, 1 );

		// Mode dropdown.
		var modeBtn = new Button( b.Mode ?? "OneWay", "swap_horiz", row );
		modeBtn.FixedWidth = 70;
		modeBtn.Clicked += () =>
		{
			var menu = new Menu( modeBtn );
			void Add( string m ) => menu.AddOption( m, "swap_horiz", () => { b.Mode = m; modeBtn.Text = m; } );
			Add( "OneWay" );
			Add( "TwoWay" );
			Add( "OneTime" );
			menu.OpenAtCursor( true );
		};
		row.Layout.Add( modeBtn );

		// Delete.
		var delBtn = new Button( "", "delete", row );
		delBtn.FixedWidth = 24;
		delBtn.ToolTip = "Delete binding";
		delBtn.Clicked += () => { _document.Bindings.Remove( b ); RefreshList(); };
		row.Layout.Add( delBtn );

		_listHost.Layout.Add( row );
	}

	private static void AddHeaderCell( Widget row, string text, int stretch, int fixedWidth = 0 )
	{
		var lbl = new Label( text, row );
		lbl.SetStyles( "color: #9ca3af; font-size: 10px; font-weight: 600; letter-spacing: 0.5px;" );
		if ( fixedWidth > 0 ) lbl.FixedWidth = fixedWidth;
		row.Layout.Add( lbl, stretch );
	}
}