Editor/PanelList.cs
using Editor;
using Sandbox;
using Sandbox.Internal;
using Sandbox.UI;
using System;
using System.Linq;

namespace Panelize;

public class PanelList : Widget
{
	TreeView tree;
	Panel selectedPanel;
	LineEdit filter;
	object hoveredPanel;
	int lastHash;
	public PanelList()
	{
		WindowTitle = "Panels";

		Layout = Layout.Column();
		Layout.Spacing = 4;

		CreateToolBar();

		tree = new TreeView( this );
		tree.ExpandForSelection = true;

		Layout.Add( tree, 1 );

		tree.SelectionOverride = () => Properties.SelectedObject;
		tree.ItemSelected += OnItemSelected;

		tree.ItemHoverEnter += OnHoverNode;
		tree.ItemHoverLeave += o => OnHoverNode( null );
	}

	[EditorEvent.Frame]
	public void Frame()
	{
		if ( !Visible )
			return;

		var session = PanelEditorSession.Current;
		if( session == null)
		{
			lastHash = 0;
			tree.Clear();
			return;
		}

		int hash = HashCode.Combine(session);
		if ( hash == lastHash )
			return;

		lastHash = hash;
		tree.Clear();
		tree.AddItem( new PanelTreeNode( session.Panel, session ) );
	}

	private void CreateToolBar()
	{
		return;

		var toolbar = new ToolBar( this );
		toolbar.SetIconSize( 18 );

		filter = new LineEdit( this );
		filter.PlaceholderText = "Filter Panels..";
		filter.TextEdited += OnFilterEdited;
		toolbar.AddWidget( filter );

		Layout.Add( toolbar );
	}

	private void OnFilterEdited( string text )
	{
		tree.Dirty();
	}

	private void OnItemSelected( object obj )
	{
		if ( obj is Panel panel )
		{
			selectedPanel = panel;
			Properties.SelectedObject = panel;
		}
	}
	private void OnHoverNode( object target )
	{
		hoveredPanel = null;

		if ( target is PanelTreeNode node )
			hoveredPanel = node.Value;
	}
}

public class PanelTreeNode : TreeNode<Panel>
{
	private PanelEditorSession session;
	private bool isEditorPanel = true;
	public PanelTreeNode( Panel panel, PanelEditorSession session )
	{
		Value = panel;
		this.session = session;
		isEditorPanel = session.IsEditorPanel( panel );
	}
	public override int ValueHash
	{
		get
		{
			if ( !Value.IsValid() ) return 0;

			return HashCode.Combine( Value.Classes, Value.IsVisible, Value.PseudoClass, Value.Children.Sum( x => x.GetHashCode() ) );
		}
	}

	protected override void BuildChildren()
	{
		SetChildren( Value.Children, x => new PanelTreeNode( x, session ) );
	}

	protected override bool HasDescendant( object obj )
	{
		if ( obj is not IPanel iPanel ) return false;
		return iPanel.IsAncestor( Value );
	}

	public override void OnPaint( VirtualWidget item )
	{
		PaintSelection( item );

		var hovered = Value.PseudoClass.HasFlag( PseudoClass.Hover );
		var a = Value.IsVisible ? 1.0f : 0.5f;
		var rect = item.Rect;

		void Write( Color color, string text, ref Rect r )
		{
			Paint.SetPen( color.WithAlphaMultiplied( a ) );
			var size = Paint.DrawText( r, text, TextFlag.LeftCenter );
			r.Left += size.Width;
		}

		{
			//	Paint.SetPen( Theme.Yellow.WithAlpha( alpha ) );
			//	Paint.DrawIcon( r, info.Icon ?? "window", 18, TextFlag.LeftCenter );
			//r.Left += Theme.RowHeight;
		}

		var brackets = Theme.Yellow.WithAlpha( 0.7f );
		var element = Theme.White.WithAlpha( 0.9f );
		var keyword = Theme.White.WithAlpha( 0.7f );
		var valueColor = Theme.Blue;

		if( !isEditorPanel )
		{
			brackets = Theme.Grey.WithAlpha( 0.7f );
			element = Theme.Grey.WithAlpha( 0.9f );
			keyword = Theme.Grey.WithAlpha( 0.7f );
			valueColor = Theme.White;
		}
		else if ( hovered )
		{
			element = Theme.Green.WithAlpha( 0.9f );
			keyword = Theme.Green.WithAlpha( 0.6f );
		}

		Paint.SetDefaultFont();

		{
			Write( brackets, $"<", ref rect );
			Paint.SetDefaultFont( 8, 500 );
			Write( element, $"{Value.ElementName}", ref rect );
			Paint.SetDefaultFont();
		}

		if ( !string.IsNullOrEmpty( Value?.Id ) )
		{
			Write( keyword, $" id=\"", ref rect );
			//Paint.SetDefaultFont( 8, 500 );
			Write( valueColor, Value?.Id, ref rect );
			Paint.SetDefaultFont();
			Write( keyword, $"\"", ref rect );
		}

		if ( !string.IsNullOrEmpty( Value?.Classes ) )
		{
			Write( keyword, $" class=\"", ref rect );
			Write( valueColor, Value?.Classes, ref rect );
			Write( keyword, $"\"", ref rect );
		}

		Write( brackets, $">", ref rect );

		if ( !string.IsNullOrEmpty( Value.SourceFile ) )
		{
			var localFile = System.IO.Path.GetFileName( Value.SourceFile );
			Write( Theme.Green.WithAlpha( 0.5f ), $" {localFile}:{Value.SourceLine} ", ref rect );
		}
	}

	
	public override bool OnContextMenu()
	{
		if ( !isEditorPanel ) return false;

		var menu = new ContextMenu( null );

		menu.AddOption( "Create Child", "add", OpenCreateChildMenu );

		menu.AddOption( "Delete", "delete", DeletePanel );
		var o = menu.AddOption( "Go To Source", action: () => CodeEditor.OpenFile( Value.SourceFile, Value.SourceLine ) );
		o.Enabled = !string.IsNullOrWhiteSpace( Value.SourceFile );
		
		menu.OpenAtCursor();
		return true;
	}

	const float NAME_POPUP_WIDTH = 200f;
	const float NAME_POPUP_HEIGHT = 60f;
	const float NAME_HEADER_HEIGHT = 20f;
	const float NAME_ENTRY_HEIGHT = 20f;
	private void OpenCreateChildMenu()
	{
		float width = NAME_POPUP_WIDTH;

		PopupWidget namePopup = new( TreeView );
		namePopup.Layout = Layout.Column();
		namePopup.FixedWidth = width;
		namePopup.FixedHeight = NAME_POPUP_HEIGHT;

		ScrollArea scrollArea = namePopup.Layout.Add( new ScrollArea( TreeView ), 1 );
		scrollArea.Canvas = new Widget( scrollArea )
		{
			Layout = Layout.Column(),
			HorizontalSizeMode = SizeMode.Expand | SizeMode.CanGrow
		};
		scrollArea.Canvas.Layout.Margin = 8f;
		scrollArea.Canvas.Layout.Spacing = 4f;

		Editor.Label header = new( "Enter panel type:", scrollArea.Canvas )
		{
			FixedHeight = NAME_HEADER_HEIGHT
		};

		LineEdit textInput = new( scrollArea )
		{
			FixedHeight = NAME_ENTRY_HEIGHT
		};
		textInput.PlaceholderText = "div";
		textInput.EditingFinished += namePopup.Close;
		textInput.ReturnPressed += () => CreateChild( textInput.Text );

		scrollArea.Canvas.Layout.Add( header );
		scrollArea.Canvas.Layout.Add( textInput );

		namePopup.Visible = true;
		namePopup.AdjustSize();
		namePopup.ConstrainToScreen();
		namePopup.OpenAtCursor();
		textInput.Focus();
	}

	private void CreateChild( string tag )
	{
		if ( string.IsNullOrEmpty( tag ) )
			tag = "div";

		Panel panel = null;
		if ( tag == "div" || tag == "p" || tag == "span" ) panel ??= new Panel();
		else panel = EditorTypeLibrary.Create<Panel>( tag, false );
		panel ??= new Panel();
		session.AddEditorPanel(panel);
		panel.ElementName = tag;
		panel.Parent = Value;

		TreeView.Open( this );
		//Log.Info( $"Create child with type {tag} for {Value}" );
	}

	private void DeletePanel()
	{
		Value.Delete();
	}
}