UI/MainMenuHost.razor
@using System;
@using System.Linq;
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.UI.Navigation;
@inherits PanelComponent

<root class="main-menu-host @(IsMenuVisible ? "visible" : "hidden")">

	@if ( IsMenuVisible )
	{
		<div class="menu-screen">
			<img class="menu-background" />
			<div class="menu-overlay"></div>

			<div class="menu-shell">
				<div class="menu-shell-chrome"></div>
				@if ( !string.IsNullOrWhiteSpace( OMRSceneFlow.SessionNotice ) )
				{
					<div class="session-notice">@OMRSceneFlow.SessionNotice</div>
				}

				<div class="menu-brand">
					@if ( ActiveTheme?.HasLogo == true )
					{
						<img class="menu-logo" />
					}

					@if ( ActiveMenuLayout?.ShowTitle != false )
					{
						<label class="menu-title">@MenuTitle</label>
					}

					@if ( ActiveMenuLayout?.ShowSubtitle != false )
					{
						<label class="menu-subtitle">@MenuSubtitle</label>
					}
				</div>

				<NavigationHost class="menu-navigation" DefaultUrl="/" @ref=Navigator>
					<ChildContent>
						<div
							class="navigator-canvas"
							slot="navigator-canvas"
						></div>
					</ChildContent>
				</NavigationHost>

			</div>

		</div>
	}

</root>

@code
{
	public static MainMenuHost Current { get; private set; }

	[Property]
	public RoundManager RoundManager { get; set; }

	[Property, Group( "UI Foundation" ), Title( "Theme" )]
	public OMRUITheme Theme { get; set; }

	[Property, Group( "UI Foundation" ), Title( "Menu Layout" )]
	public OMRMainMenuLayout MenuLayout { get; set; }

	[Property, Group( "Scene Flow" ), Title( "Standalone Startup Menu" )]
	public bool IsStandaloneMenuScene { get; set; }

	[Property, Group( "Scene Flow" ), ShowIf( nameof( IsStandaloneMenuScene ), true ), ResourceType( "scene" )]
	public string GameplayScenePath { get; set; } = "scenes/main.scene";

	[Property, Group( "Developer Preview" ), Title( "Force Menu Visible" )]
	public bool EnableDeveloperPreview { get; set; }

	[Property, Group( "Developer Preview" ), ShowIf( nameof( EnableDeveloperPreview ), true ), Title( "Preview Page" )]
	public OMRMenuPreviewPage DeveloperPreviewPage { get; set; } =
		OMRMenuPreviewPage.Home;

	public OMRUITheme ActiveTheme =>
		Theme ?? OMRUIResources.DefaultTheme;

	public OMRMainMenuLayout ActiveMenuLayout =>
		MenuLayout ?? OMRUIResources.DefaultMenuLayout;

	private string MenuTitle =>
		ActiveTheme?.GameTitle ??
		"ONE MORE ROUND";

	private string MenuSubtitle =>
		ActiveTheme?.Subtitle ??
		"Dev Build Alpha 1.6 · BOT 9";

	public NavigationHost Navigator { get; private set; }

	public GameFlowManager FlowManager =>
		RoundManager?.
			GameObject?.
			Components
			.Get<GameFlowManager>();

	public bool IsMenuVisible =>
		EnableDeveloperPreview ||
		IsStandaloneMenuScene ||
		(
			RoundManager is not null &&
			RoundManager.Mode ==
				SessionMode.Menu &&
			(
				LocalPlayerState is null ||
				LocalPlayerState.PreMatchState !=
					PreMatchPlayerState.Warmup
			)
		);

	private SessionMode _lastMode =
		SessionMode.Menu;

	private GameObject _lastSettingsPlayer;

	private string _nextMenuRoute =
		"/";

	private OMRGameplayEntry _pendingGameplayEntry =
		OMRGameplayEntry.None;

	private bool _sceneChangeRequested;

	protected override void OnEnabled()
	{
		base.OnEnabled();

		Current =
			this;
	}

	protected override void OnDisabled()
	{
		if (
			Current ==
			this
		)
		{
			Current =
				null;
		}

		base.OnDisabled();
	}

	protected override void OnStart()
	{
		base.OnStart();

		RoundManager ??=
			Components.Get<RoundManager>();

		if ( RoundManager is not null )
		{
			_lastMode =
				RoundManager.Mode;

			if ( !IsStandaloneMenuScene )
			{
				_pendingGameplayEntry =
					OMRSceneFlow.ConsumeGameplayEntry();
			}
		}
	}

	protected override void OnUpdate()
	{
		base.OnUpdate();

		RoundManager ??=
			Components.Get<RoundManager>();

		if ( IsMenuVisible )
		{
			OMRUIRuntimeStyle.ApplyMenu(
				Panel,
				ActiveTheme,
				ActiveMenuLayout
			);

			Mouse.Visibility =
				MouseVisibility.Visible;

			Input.EnableVirtualCursor =
				true;
		}

		ApplyDeveloperPreviewRoute();
		ApplyPendingGameplayEntry();

		ApplySettingsToNewLocalPlayer();

		if ( RoundManager is null )
			return;

		if (
			RoundManager.Mode !=
			_lastMode
		)
		{
			if (
				RoundManager.Mode ==
					SessionMode.Menu
			)
			{
				string route =
					string.IsNullOrWhiteSpace(
						_nextMenuRoute
					)
						? "/"
						: _nextMenuRoute;

				_nextMenuRoute =
					"/";

				Navigate(
					route
				);
			}

			_lastMode =
				RoundManager.Mode;
		}

		if ( !IsMenuVisible )
			return;

		if (
			IsOnlineFlowVisible &&
			Navigator is not null &&
			Navigator.CurrentUrl !=
				"/online"
		)
		{
			Navigate(
				"/online"
			);
		}
	}

	public void Navigate(
		string url
	)
	{
		Navigator?.
			Navigate(
				url,
				true
			);
	}

	public void GoBack()
	{
		if (
			Navigator is null ||
			!Navigator.GoBack()
		)
		{
			Navigate(
				"/"
			);
		}
	}

	public void FindMatch()
	{
		if ( FlowManager?.IsLeavingSession == true ) return;
		if ( IsStandaloneMenuScene )
		{
			LoadGameplayScene(
				OMRGameplayEntry.FindMatch
			);

			return;
		}

		if ( FlowManager is null )
			return;

		Navigate(
			"/online"
		);

		FlowManager.FindMatch();
	}

	public void CancelMatchmaking()
	{
		if ( FlowManager is null )
			return;

		FlowManager.CancelMatchmaking();

		Navigate(
			"/play"
		);
	}

	public void StartBotMatch()
	{
		if ( FlowManager?.IsLeavingSession == true ) return;
		if ( IsStandaloneMenuScene )
		{
			LoadGameplayScene( OMRGameplayEntry.BotMatch );
			return;
		}

		RoundManager?.SetMatchBotDifficulty( OMRSceneFlow.BotMatchDifficulty );

		if ( FlowManager is not null )
			FlowManager.StartBotMatchSession();
		else if ( RoundManager is not null )
		{
			RoundManager.SetMatchBotEnabled( true );
			RoundManager.StartMatchMode();
		}
	}

	public void StartSandbox()
	{
		if ( FlowManager?.IsLeavingSession == true ) return;
		if ( IsStandaloneMenuScene )
		{
			LoadGameplayScene(
				OMRGameplayEntry.SandboxSetup
			);

			return;
		}

		if ( RoundManager is null )
			return;

		if ( FlowManager is not null )
		{
			FlowManager.StartSandboxSession();
		}
		else
		{
			RoundManager.StartSandboxMode();
		}
	}

	public void OpenSandboxSetup()
	{
		if ( IsStandaloneMenuScene )
		{
			LoadGameplayScene(
				OMRGameplayEntry.SandboxSetup
			);

			return;
		}

		Navigate( "/practice/sandbox" );
	}

	private void LoadGameplayScene(
		OMRGameplayEntry entry
	)
	{
		if (
			_sceneChangeRequested ||
			string.IsNullOrWhiteSpace( GameplayScenePath ) ||
			Scene is null
		)
		{
			Log.Warning(
				"UI | Gameplay scene path is not configured"
			);

			return;
		}

		if ( Networking.IsActive )
		{
			Log.Warning(
				"UI | Refusing a local startup-menu scene change while networking is active"
			);

			return;
		}

		_sceneChangeRequested = true;

		OMRSceneFlow.RequestGameplayEntry( entry );

		if ( !Scene.LoadFromFile( GameplayScenePath ) )
		{
			_sceneChangeRequested = false;
			OMRSceneFlow.RequestGameplayEntry( OMRGameplayEntry.None );

			Log.Error(
				$"UI | Failed to load gameplay scene '{GameplayScenePath}'"
			);
		}
	}

	private void ApplyPendingGameplayEntry()
	{
		if (
			_pendingGameplayEntry == OMRGameplayEntry.None ||
			Navigator is null ||
			RoundManager is null
		)
			return;

		OMRGameplayEntry entry =
			_pendingGameplayEntry;

		_pendingGameplayEntry =
			OMRGameplayEntry.None;

		switch ( entry )
		{
			case OMRGameplayEntry.FindMatch:
				Navigate( "/online" );
				FlowManager?.FindMatch();
				break;

			case OMRGameplayEntry.BotMatch:
				RoundManager?.SetMatchBotDifficulty( OMRSceneFlow.BotMatchDifficulty );
				FlowManager?.StartBotMatchSession();
				break;

			case OMRGameplayEntry.SandboxSetup:
				Navigate( "/practice/sandbox" );
				break;
		}
	}

	private void ApplyDeveloperPreviewRoute()
	{
		if (
			!EnableDeveloperPreview ||
			Navigator is null
		)
			return;

		string route = DeveloperPreviewPage switch
		{
			OMRMenuPreviewPage.Play => "/play",
			OMRMenuPreviewPage.Practice => "/practice",
			OMRMenuPreviewPage.Settings => "/settings",
			_ => "/"
		};

		if ( Navigator.CurrentUrl != route )
			Navigate( route );
	}


	/// <summary>
	/// Opens Sandbox Setup while preserving the current network
	/// session and existing player objects.
	/// </summary>
	public void ReturnToSandboxSetup()
	{
		if (
			RoundManager is null ||
			!Networking.IsHost
		)
			return;

		_nextMenuRoute =
			"/practice/sandbox";

		if ( FlowManager is not null )
		{
			FlowManager.ReturnToSandboxSetup();
		}
		else
		{
			RoundManager.ReturnToMenu();
		}
	}

	/// <summary>
	/// Fully leaves the current network session and returns to the
	/// root menu.
	/// </summary>
	public void ReturnToMainMenu()
	{
		_nextMenuRoute =
			"/";

		if ( FlowManager is not null )
		{
			FlowManager.ReturnToMainMenu();
		}
		else
		{
			RoundManager?.ReturnToMenu();
		}
	}

	public void ApplySettingsNow()
	{
		OMRPlayerSettings.ApplyToLocalPlayer();
	}

	private void ApplySettingsToNewLocalPlayer()
	{
		if (
			RoundManager is null ||
			RoundManager.Scene is null
		)
			return;

		PlayerState localPlayer =
			RoundManager.Scene
				.GetAllComponents<PlayerState>()
				.FirstOrDefault(
					x =>
						!x.IsProxy &&
					!x.IsBot
				);

		GameObject playerObject =
			localPlayer?.
				GameObject;

		if (
			playerObject is null ||
			playerObject ==
				_lastSettingsPlayer
		)
			return;

		_lastSettingsPlayer =
			playerObject;

		OMRPlayerSettings.ApplyToPlayer(
			playerObject
		);
	}

	public PlayerState LocalPlayerState
	{
		get
		{
			if (
				RoundManager is null ||
				RoundManager.Scene is null
			)
				return null;

			return RoundManager.Scene
				.GetAllComponents<PlayerState>()
				.FirstOrDefault(
					x =>
						!x.IsProxy &&
					!x.IsBot
				);
		}
	}

	public PlayerState OpponentPlayerState
	{
		get
		{
			if (
				RoundManager is null ||
				RoundManager.Scene is null
			)
				return null;

			PlayerState local =
				LocalPlayerState;

			return RoundManager.Scene
				.GetAllComponents<PlayerState>()
				.FirstOrDefault(
					x =>
						x.IsReady &&
						x != local
				);
		}
	}

	public void TogglePreMatchReady()
	{
		RoundManager?.
			RequestPreMatchReady();
	}

	public void TogglePreMatchWarmup()
	{
		RoundManager?.
			RequestPreMatchWarmup();
	}

	public void CyclePreMatchWeapon(
		int direction
	)
	{
		RoundManager?.
			RequestCyclePreMatchWeapon(
				direction
			);
	}

	public string GetPreMatchWeaponName(
		PlayerState player
	)
	{
		return RoundManager?.
			GetPreMatchWeaponName(
				player
			) ??
			"NO WEAPON";
	}

	public int ConnectedPlayerCount
	{
		get
		{
			if (
				RoundManager is null ||
				RoundManager.Scene is null
			)
				return 0;

			return RoundManager.Scene
				.GetAllComponents<PlayerState>()
				.Count(
					x =>
						x.IsReady
				);
		}
	}

	public bool IsOnlineFlowVisible
	{
		get
		{
			if ( FlowManager is null )
				return false;

			return
				FlowManager.OnlineState !=
					OnlineFlowState.Idle &&
				FlowManager.OnlineState !=
					OnlineFlowState.InMatch;
		}
	}

	public int MatchmakingSeconds
	{
		get
		{
			if ( FlowManager is null )
				return 0;

			return (int)MathF.Ceiling(
				MathF.Max(
					0f,
					FlowManager.MatchStartsAt -
						Time.Now
				)
			);
		}
	}

	public string LocalConnectionName
	{
		get
		{
			string name =
				Connection.Local?.
					DisplayName;

			return string.IsNullOrWhiteSpace(
				name
			)
				? "YOU"
				: name;
		}
	}

	public string OpponentConnectionName
	{
		get
		{
			if ( !Networking.IsActive )
				return "OPPONENT";

			Connection opponent;

			if ( Networking.IsHost )
			{
				opponent =
					Connection.All
						.FirstOrDefault(
							x =>
								x.IsActive &&
								!x.IsHost
						);
			}
			else
			{
				opponent =
					Connection.Host;
			}

			return string.IsNullOrWhiteSpace(
				opponent?.
					DisplayName
			)
				? "OPPONENT"
				: opponent.DisplayName;
		}
	}

	protected override int BuildHash()
	{
		HashCode hash =
			new();
		hash.Add( OMRSceneFlow.SessionNotice );

		hash.Add(
			RoundManager?.
				Mode
		);

		hash.Add(
			Navigator?.
				CurrentUrl
		);

		hash.Add( EnableDeveloperPreview );
		hash.Add( DeveloperPreviewPage );
		hash.Add( IsStandaloneMenuScene );
		hash.Add( ActiveTheme?.ContentHash() ?? 0 );
		hash.Add( ActiveMenuLayout?.ContentHash() ?? 0 );

		hash.Add(
			ConnectedPlayerCount
		);

		hash.Add(
			LocalConnectionName
		);

		hash.Add(
			OpponentConnectionName
		);

		hash.Add(
			LocalPlayerState?.
				PreMatchState
		);

		hash.Add(
			LocalPlayerState?.
				WarmupWeaponIndex
		);

		hash.Add(
			OpponentPlayerState?.
				PreMatchState
		);

		hash.Add(
			OpponentPlayerState?.
				WarmupWeaponIndex
		);

		if ( FlowManager is not null )
		{
			hash.Add(
				FlowManager.OnlineState
			);

			hash.Add(
				FlowManager.ErrorMessage
			);

			hash.Add(
				MatchmakingSeconds
			);
		}

		return hash.ToHashCode();
	}
}