Editor/Widgets/SuiBottomTabsWidget.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;
using SboxUiDesigner.Generation;

namespace SboxUiDesigner.EditorUi.Widgets;

/// <summary>
/// Bottom panel with 4 tabs (Animations / Bindings / Compile Results / Logs).
/// 100% custom paint — left-aligned tabs above a content area, no Editor.TabWidget.
///
/// Per user spec:
/// - panel background:        #141413 (rgba 20, 20, 19)
/// - inactive tab fill:       same as panel bg (no fill)
/// - active tab fill:         #212120 (rgba 33, 33, 32)
/// - active tab top stripe:   #103354 (rgba 16, 51, 84) — 3px line at top
///
/// Lives inside the center column (between the canvas tabs and the right edge
/// of the canvas) — sidebars on the left/right go full height down to the
/// window bottom.
/// </summary>
public sealed class SuiBottomTabsWidget : Widget
{
	private SuiBottomTabStrip _tabs;
	private Widget _stack;

	private SuiAnimationsWidget _animations;
	private SuiBindingsWidget _bindings;
	private SuiCompileResultsWidget _compileResults;
	private SuiLogsWidget _logs;

	public SuiAnimationsWidget Animations => _animations;
	public SuiBindingsWidget Bindings => _bindings;
	public SuiCompileResultsWidget CompileResults => _compileResults;
	public SuiLogsWidget Logs => _logs;

	public SuiBottomTabsWidget( Widget parent = null ) : base( parent )
	{
		WindowTitle = "Bottom Panel";
		Name = "SuiBottomTabs";
		MinimumSize = new Vector2( 400, 160 );

		// Panel background — #141413 per user spec.
		SetStyles( "background-color: rgb(20,20,19); border: none;" );

		Layout = Layout.Column();
		Layout.Margin = 0;
		Layout.Spacing = 0;

		_tabs = new SuiBottomTabStrip( this );
		_tabs.AddTab( "Animations" );
		_tabs.AddTab( "Bindings" );
		_tabs.AddTab( "Compile Results" );
		_tabs.AddTab( "Logs" );
		_tabs.FinishLeftAligned();
		_tabs.ActiveTabChanged += OnTabChanged;
		Layout.Add( _tabs );

		// Content stack — single page visible at a time.
		_stack = new Widget( this );
		_stack.SetStyles( "background-color: rgb(33,33,32); border: none;" );
		_stack.Layout = Layout.Row();
		_stack.Layout.Margin = 0;
		Layout.Add( _stack, 1 );

		_animations = new SuiAnimationsWidget( _stack );
		_bindings = new SuiBindingsWidget( _stack );
		_compileResults = new SuiCompileResultsWidget( _stack );
		_logs = new SuiLogsWidget( _stack );

		// Make all four children fill the stack so the panel height stays
		// constant when switching tabs. Without these the stack collapses
		// to the natural size of whichever child is visible.
		foreach ( var w in new Widget[] { _animations, _bindings, _compileResults, _logs } )
		{
			w.SetSizeMode( SizeMode.CanGrow, SizeMode.CanGrow );
		}

		_stack.Layout.Add( _animations, 1 );
		_stack.Layout.Add( _bindings, 1 );
		_stack.Layout.Add( _compileResults, 1 );
		_stack.Layout.Add( _logs, 1 );

		ApplyVisibility();
	}

	private void OnTabChanged( int index ) => ApplyVisibility();

	private void ApplyVisibility()
	{
		var idx = _tabs.ActiveIndex;
		if ( _animations.IsValid() )     _animations.Visible     = idx == 0;
		if ( _bindings.IsValid() )       _bindings.Visible       = idx == 1;
		if ( _compileResults.IsValid() ) _compileResults.Visible = idx == 2;
		if ( _logs.IsValid() )           _logs.Visible           = idx == 3;
	}

	public void DisplayCompileResult( SuiGenerationResult generation, SboxUiDesigner.EditorUi.SuiCompileResult compile )
	{
		_compileResults?.DisplayCompileResult( generation, compile );
		// Auto-switch to Compile Results tab when a compile finishes.
		_tabs.ActiveIndex = 2;
	}

	public void SetDocument( SboxUiDesigner.Runtime.SuiDocument doc )
	{
		_bindings?.SetDocument( doc );
	}
}

/// <summary>
/// Tab strip for the bottom panel. Same shape as SuiTabStrip but uses the
/// "active = filled with body color + blue top stripe" look the user specified.
/// </summary>
internal sealed class SuiBottomTabStrip : Widget
{
	private readonly List<SuiBottomTabStripItem> _tabs = new();
	private int _active = 0;

	public event Action<int> ActiveTabChanged;

	public int ActiveIndex
	{
		get => _active;
		set
		{
			if ( _active == value ) return;
			_active = value;
			foreach ( var t in _tabs ) t.Update();
			ActiveTabChanged?.Invoke( _active );
		}
	}

	public SuiBottomTabStrip( Widget parent = null ) : base( parent )
	{
		FixedHeight = 30;
		SetStyles( "background-color: rgb(20,20,19); border: none;" );
		Layout = Layout.Row();
		Layout.Margin = 0;
		Layout.Spacing = 0;
	}

	public void AddTab( string label )
	{
		var idx = _tabs.Count;
		// Insert a subtle vertical 1px line between consecutive tabs (same
		// look as the top-bar group separators).
		if ( _tabs.Count > 0 )
		{
			Layout.Add( new SuiBottomTabSeparator() );
		}
		var tab = new SuiBottomTabStripItem( this, idx, label );
		_tabs.Add( tab );
		Layout.Add( tab );
	}

	public void FinishLeftAligned() => Layout.AddStretchCell();

	internal bool IsActive( int idx ) => idx == _active;

	internal void OnTabClicked( int idx ) => ActiveIndex = idx;
}

internal sealed class SuiBottomTabStripItem : Widget
{
	private readonly SuiBottomTabStrip _strip;
	private readonly int _index;
	public string Label;

	private bool _hover;
	private bool _sized;

	private const int FontSize = 11;
	private const int PadH = 16;

	public SuiBottomTabStripItem( SuiBottomTabStrip strip, int index, string label ) : base( strip )
	{
		_strip = strip;
		_index = index;
		Label = label ?? "";
		Cursor = CursorShape.Finger;
		FixedHeight = 30;
		SetStyles( "background-color: transparent; border: none;" );

		FixedWidth = PadH + (Label.Length * 8) + PadH;
	}

	protected override void OnPaint()
	{
		Paint.SetDefaultFont( FontSize );

		if ( !_sized )
		{
			var labelW = string.IsNullOrEmpty( Label ) ? 0 : Paint.MeasureText( Label ).x;
			int newW = (int)(PadH + labelW + 2 + PadH);
			_sized = true;
			if ( newW != FixedWidth )
			{
				FixedWidth = newW;
				return;
			}
		}

		var rect = LocalRect;
		bool active = _strip.IsActive( _index );

		if ( active )
		{
			// Active tab fill — #212120 (same as content body, so the tab
			// reads as a flap of the body sticking up).
			Paint.SetBrushAndPen( new Color( 33 / 255f, 33 / 255f, 32 / 255f ) );
			Paint.DrawRect( rect );

			// Top stripe — #103354 (rgba 16, 51, 84), 3px high.
			Paint.SetBrushAndPen( new Color( 16 / 255f, 51 / 255f, 84 / 255f ) );
			Paint.DrawRect( new Rect( 0, 0, Width, 3 ) );
		}
		else if ( _hover )
		{
			Paint.SetBrushAndPen( Color.White.WithAlpha( 0.04f ) );
			Paint.DrawRect( rect );
		}

		// Label.
		var textColor = active
			? new Color( 240 / 255f, 244 / 255f, 250 / 255f )
			: new Color( 165 / 255f, 170 / 255f, 178 / 255f );
		Paint.SetPen( textColor );

		var labelRect = new Rect( PadH, 0, Width - PadH * 2, Height );
		Paint.DrawText( labelRect, Label, TextFlag.LeftCenter );
	}

	protected override void OnMouseEnter() { _hover = true; Update(); }
	protected override void OnMouseLeave() { _hover = false; Update(); }
	protected override void OnMousePress( MouseEvent e )
	{
		if ( e.LeftMouseButton ) _strip.OnTabClicked( _index );
	}
}

/// <summary>
/// Subtle 1px vertical line between consecutive bottom-panel tabs. Same look
/// as the top-bar group separators (rgba(255,255,255,0.08), centered, 55%
/// of strip height).
/// </summary>
internal sealed class SuiBottomTabSeparator : Widget
{
	public SuiBottomTabSeparator( Widget parent = null ) : base( parent )
	{
		FixedWidth = 11;
		FixedHeight = 30;
		SetStyles( "background-color: transparent; border: none;" );
	}

	protected override void OnPaint()
	{
		float lineHeight = Height * 0.55f;
		float yTop = (Height - lineHeight) / 2f;
		float xCenter = Width / 2f;

		Paint.SetPen( Color.White.WithAlpha( 0.08f ) );
		Paint.DrawLine( new Vector2( xCenter, yTop ), new Vector2( xCenter, yTop + lineHeight ) );
	}
}