Editor/RoadToolWindow/RoadToolWindow.UI.cs
using Sandbox;
using Editor;

namespace RedSnail.RoadTool.Editor;

public partial class RoadToolWindow
{
	private const int HEADER_HEIGHT = 32;



	private void Rebuild()
	{
		Layout.Clear(true);
		Layout.Margin = 0;
		Icon = _isClosed ? "" : "route";
		UpdateWindowTitle();
		IsGrabbable = !_isClosed;

		if (_isClosed)
		{
			BuildClosedState();
			return;
		}

		MinimumWidth = 400;
		BuildHeader();

		if (_targetComponent.IsValid())
		{
			BuildControlSheet();
		}

		Layout.Margin = 4;
	}



	private void BuildClosedState()
	{
		var closedRow = Layout.AddRow();

		closedRow.Add(new IconButton("route", () => { _isClosed = false; Rebuild(); })
		{
			ToolTip = "Open Spline Point Editor",
			FixedHeight = HEADER_HEIGHT,
			FixedWidth = HEADER_HEIGHT,
			Background = Color.Transparent
		});

		MinimumWidth = 0;
	}



	private void BuildHeader()
	{
		var headerRow = Layout.AddRow();

		headerRow.AddStretchCell();

		headerRow.Add(new IconButton("info")
		{
			ToolTip = GetInfoTooltip(),
			FixedHeight = HEADER_HEIGHT,
			FixedWidth = HEADER_HEIGHT,
			Background = Color.Transparent
		});

		headerRow.Add(new IconButton("close", CloseWindow)
		{
			ToolTip = "Close Editor",
			FixedHeight = HEADER_HEIGHT,
			FixedWidth = HEADER_HEIGHT,
			Background = Color.Transparent
		});
	}



	private string GetInfoTooltip()
	{
		return "Controls to edit the spline points.\n" +
			   "In addition to modifying the properties in the control sheet, you can also use the 3D Gizmos.\n" +
			   "Clicking on the spline between points will split the spline at that position.\n" +
			   "Holding shift while dragging a point's position will drag out a new point.";
	}



	private void BuildControlSheet()
	{
		var serialized = this.GetSerialized();
		var controlSheet = new ControlSheet();

		// Add property rows
		controlSheet.AddRow(serialized.GetProperty(nameof(_selectedPointTangentMode)));
		controlSheet.AddRow(serialized.GetProperty(nameof(_selectedPointPosition)));
		_inTangentControl = controlSheet.AddRow(serialized.GetProperty(nameof(_selectedPointIn)));
		_outTangentControl = controlSheet.AddRow(serialized.GetProperty(nameof(_selectedPointOut)));

		// Add advanced group
		var roll = serialized.GetProperty(nameof(_selectedPointRoll));
		var scale = serialized.GetProperty(nameof(_selectedPointScale));
		var up = serialized.GetProperty(nameof(_selectedPointUp));
		controlSheet.AddGroup("Advanced", [roll, scale, up]);

		// Add control buttons
		controlSheet.AddLayout(BuildControlButtons());

		Layout.Add(controlSheet);
		ToggleTangentInput();
	}



	private Layout BuildControlButtons()
	{
		var row = Layout.Row();
		row.Spacing = 16;
		row.Margin = 8;

		row.Add(CreateNavigationButton("skip_previous", -1, "Go to previous point"));
		row.Add(CreateNavigationButton("skip_next", 1, "Go to next point"));
		row.Add(CreateDeleteButton());
		row.Add(CreateAddButton());

		return row;
	}



	private IconButton CreateNavigationButton(string icon, int direction, string tooltip)
	{
		return new IconButton(icon, () =>
		{
			if (direction < 0)
				SelectedPointIndex = int.Max(0, SelectedPointIndex - 1);
			else
				SelectedPointIndex = int.Min(_targetComponent.Spline.PointCount - 1, SelectedPointIndex + 1);

			UpdateWindowTitle();
			Focus();
		})
		{ ToolTip = tooltip };
	}



	private IconButton CreateDeleteButton()
	{
		return new IconButton("delete", () =>
		{
			using (CreateUndoScope("Delete Spline Point"))
			{
				_targetComponent.Spline.RemovePoint(SelectedPointIndex);
				SelectedPointIndex = int.Max(0, SelectedPointIndex - 1);
			}
			UpdateWindowTitle();
			Focus();
		})
		{ ToolTip = "Delete point" };
	}



	private IconButton CreateAddButton()
	{
		return new IconButton("add", () =>
		{
			using (CreateUndoScope("Added Spline Point"))
			{
				InsertNewPoint();
			}
			SelectedPointIndex++;
			UpdateWindowTitle();
			Focus();
		})
		{
			ToolTip = "Insert point after current point.\n" +
					  "You can also hold shift while dragging a point to create a new point."
		};
	}



	private void InsertNewPoint()
	{
		var spline = _targetComponent.Spline;

		if (SelectedPointIndex == spline.PointCount - 1)
		{
			var distance = spline.GetDistanceAtPoint(SelectedPointIndex);
			var tangent = spline.SampleAtDistance(distance).Tangent;
			var newPosition = _selectedPoint.Position + tangent * 200;

			spline.InsertPoint(SelectedPointIndex + 1, _selectedPoint with { Position = newPosition });
		}
		else
		{
			var currentDist = spline.GetDistanceAtPoint(SelectedPointIndex);
			var nextDist = spline.GetDistanceAtPoint(SelectedPointIndex + 1);
			var midDist = (currentDist + nextDist) / 2;

			spline.AddPointAtDistance(midDist, true);
		}
	}



	private void UpdateWindowTitle()
	{
		WindowTitle = _isClosed ? "" : $"Spline Point [{SelectedPointIndex}] Editor - {_targetComponent?.GameObject?.Name ?? ""}";
	}



	private void CloseWindow()
	{
		_isClosed = true;
		Rebuild();
		Position = Parent.Size - 32;
	}
}