Editor/widgets/SandGitDock.cs
using Editor;
using Sandbox.git;
using Sandbox.widgets;
using System.Threading;
using System.Threading.Tasks;

namespace Sandbox;

[Dock("Editor", "SandGit", "account_tree")]
public class SandGitDock : Widget {
	const float OuterPadding = 4f;
	const float DockWidth = 300f;
	const int SandGitLoadDelay = 600;

	internal static SandGitDock Instance { get; private set; }

	readonly GitStore _gitStore;
	readonly Widget _mainContent;
	readonly SandGitSettingsWidget _settingsWidget;

	[Shortcut("sandgit.toggle-dock", "CTRL+G")]
	static void ToggleSandGitDock() {
		var currentlyOpen = EditorWindow.DockManager.IsDockOpen("SandGit");
		EditorWindow.DockManager.SetDockState("SandGit", !currentlyOpen);
		if ( !currentlyOpen ) {
			_ = Task.Delay(50).ContinueWith(_ => PlaceSandGitBelowHierarchy(),
				TaskScheduler.FromCurrentSynchronizationContext());
		}
	}

	[Shortcut("sandgit.refresh", "CTRL+R")]
	static void RefreshGitStore() {
		if ( Instance?._gitStore != null )
			Instance._gitStore.RequestDebouncedRefresh("shortcut");
	}

	/// <summary>
	/// When the editor starts, place SandGit on the left below Hierarchy if it is not already in the layout.
	/// </summary>
	[Event("editor.created")]
	static void OnEditorCreated(EditorMainWindow editorWindow) {
		_ = Task.Delay(SandGitLoadDelay).ContinueWith(_ => {
			if ( EditorWindow.DockManager.IsDockOpen("SandGit") )
				return;
			PlaceSandGitBelowHierarchy();
		}, TaskScheduler.FromCurrentSynchronizationContext());
	}

	public SandGitDock(Widget parent) : base(parent) {
		Instance = this;
		FixedWidth = DockWidth;
		MaximumWidth = DockWidth;
		MinimumHeight = 400f;
		Layout = Layout.Column();
		Layout.Spacing = 4f;

		var rootPath = Project.Current.GetRootPath();
		_gitStore = new GitStore(rootPath, SynchronizationContext.Current);
		EditorEvent.Register(_gitStore);

		var eventsManager = new EditorEventsManager();
		EditorEvent.Register(eventsManager);

		var topPad = new Widget(this) { FixedHeight = OuterPadding / 2 };
		var bottomPad = new Widget(this) { FixedHeight = OuterPadding / 2 };
		var leftPad = new Widget(this) { FixedWidth = OuterPadding };

		var contentWidth = DockWidth - 2f * OuterPadding;
		var content = new Widget(this) {
			Layout = Layout.Column(),
			FixedWidth = contentWidth,
			MaximumWidth = contentWidth,
			MinimumWidth = contentWidth
		};
		content.Layout.Spacing = 4f;

		_mainContent = new Widget(content) { Layout = Layout.Column() };
		_mainContent.Layout.Spacing = 4f;

		_settingsWidget = new SandGitSettingsWidget(content, ShowMainView) { Visible = false };

		content.Layout.Add(new RepositoryStatusWidget(content, _gitStore, OpenSettings));
		content.Layout.Add(_mainContent, 1);
		content.Layout.Add(_settingsWidget, 1);

		_mainContent.Layout.Add(new BranchWidget(_mainContent, _gitStore));

		var changesWidget = new ChangesWidget(_mainContent, _gitStore);
		var historyWidget = new HistoryWidget(_mainContent, _gitStore);
		historyWidget.Visible = false;

		var pageSelect = new SegmentedControl(_mainContent);
		pageSelect.AddOption("Changes", "edit");
		pageSelect.AddOption("History", "history");
		pageSelect.OnSelectedChanged = _ => {
			changesWidget.Visible = pageSelect.SelectedIndex == 0;
			historyWidget.Visible = pageSelect.SelectedIndex == 1;
			if ( pageSelect.SelectedIndex == 1 )
				historyWidget.EnsureHistoryLoaded();
		};

		var tabRow = new Widget(_mainContent) { Layout = Layout.Row() };
		tabRow.Layout.Spacing = 8f;
		tabRow.Layout.Add(pageSelect);

		var tabContent = new Widget(_mainContent) { Layout = Layout.Column() };
		tabContent.Layout.Spacing = 4f;
		tabContent.Layout.Add(changesWidget, 1);
		tabContent.Layout.Add(historyWidget, 1);

		var tabbedSection = new Widget(_mainContent) { Layout = Layout.Column() };
		tabbedSection.Layout.Spacing = 4f;
		tabbedSection.Layout.Add(tabRow);
		tabbedSection.Layout.Add(tabContent, 1);

		_mainContent.Layout.Add(tabbedSection, 1);
		_mainContent.Layout.Add(new CommitWidget(_mainContent, _gitStore));

		var midRow = new Widget(this) { Layout = Layout.Row(), FixedWidth = DockWidth, MaximumWidth = DockWidth };
		midRow.Layout.Add(leftPad);
		midRow.Layout.Add(content);

		Layout.Add(topPad);
		Layout.Add(midRow, 1);
		Layout.Add(bottomPad);

		// Use debounced refresh so we don't run a second refresh (and second rev-parse) when an editor event fires shortly after open.
		_gitStore.RequestDebouncedRefresh("dock opened");
	}

	void OpenSettings() {
		if ( !IsValid || _mainContent == null || _settingsWidget == null )
			return;
		_mainContent.Visible = false;
		_settingsWidget.Visible = true;
	}

	void ShowMainView() {
		if ( !IsValid || _mainContent == null || _settingsWidget == null )
			return;
		_settingsWidget.Visible = false;
		_mainContent.Visible = true;
	}

	/// <summary>
	/// Try placing dock SandGit below Hierarchy.
	/// </summary>
	static bool PlaceSandGitBelowHierarchy() {
		var hierarchy = EditorWindow.DockManager.GetDockWidget("Hierarchy");
		if ( hierarchy == null || !hierarchy.IsValid )
			return false;

		var sandGit = EditorWindow.DockManager.GetDockWidget("SandGit");
		if ( sandGit == null || (sandGit is Widget w && !w.IsValid) )
			sandGit = EditorWindow.DockManager.Create<SandGitDock>();

		EditorWindow.DockManager.AddDock(hierarchy, sandGit, DockArea.Bottom);
		EditorWindow.DockManager.RaiseDock("SandGit");
		return true;
	}

	protected override void OnClosed() {
		ShowMainView();
		if ( Instance == this )
			Instance = null;
		base.OnClosed();
	}
}