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

<root>
	@if ( ShouldShowCrosshair )
	{
		<div
			class="omr-crosshair"
			style="@CrosshairRootStyle"
		>
			<div
				class="omr-crosshair-line horizontal"
				style="@CrosshairLeftStyle"
			></div>

			<div
				class="omr-crosshair-line horizontal"
				style="@CrosshairRightStyle"
			></div>

			<div
				class="omr-crosshair-line vertical"
				style="@CrosshairUpStyle"
			></div>

			<div
				class="omr-crosshair-line vertical"
				style="@CrosshairDownStyle"
			></div>
		</div>
	}
</root>

@code
{
	private readonly OMRSceneReferences _sceneReferences = new();
	private OMRSceneReferences SceneReferences
	{
		get { _sceneReferences.Refresh( Scene ); return _sceneReferences; }
	}
	private PlayerState LocalPlayerState => SceneReferences.LocalPlayer;

	private BaseInventoryComponent LocalInventory => SceneReferences.LocalInventory;

	private OneMoreRoundWeapon LocalWeapon =>
		LocalInventory?
			.ActiveItem as OneMoreRoundWeapon;

	private RoundManager LocalRoundManager => SceneReferences.Rounds;

	/// <summary>
	/// Crosshair visibility follows actual gameplay context rather than only
	/// weapon state.
	///
	/// Visible:
	/// - Sandbox gameplay
	/// - Online warmup
	/// - Active Fighting phase
	///
	/// Hidden:
	/// - pause/settings
	/// - pre-match lobby/ready screen
	/// - countdown
	/// - round end
	/// - winner/modifier decisions
	/// - match end/menu
	/// </summary>
	private bool IsCrosshairGameplayContext
	{
		get
		{
			if ( PauseMenu.IsAnyPauseOpen )
				return false;

			RoundManager roundManager =
				LocalRoundManager;

			if ( roundManager is null )
				return false;

			return roundManager.Mode switch
			{
				SessionMode.Sandbox =>
					true,

				SessionMode.Match =>
					roundManager.Phase ==
						RoundPhase.Fighting,

				SessionMode.Menu =>
					LocalPlayerState?.
						PreMatchState ==
							PreMatchPlayerState.Warmup,

				_ =>
					false
			};
		}
	}

	private bool ShouldShowCrosshair =>
		IsCrosshairGameplayContext &&
		LocalWeapon is not null &&
		LocalWeapon.IsValid &&
		LocalWeapon.ShouldShowScreenCrosshair;

	private Vector2 Gap =>
		LocalWeapon?
			.ScreenCrosshairGap ??
		Vector2.Zero;

	private float LineLength =>
		MathF.Max(
			LocalWeapon?
				.CrosshairLineLength ??
			0f,
			0f
		);

	private float LineWidth =>
		MathF.Max(
			LocalWeapon?
				.CrosshairLineWidth ??
			0.5f,
			0.5f
		);

	private string CrosshairRootStyle
	{
		get
		{
			Color color =
				LocalWeapon?
					.CrosshairColor ??
				Color.White;

			return $"color: {color.Rgba};";
		}
	}

	private string CrosshairLeftStyle =>
		$"width: {Css( LineLength )}px; " +
		$"height: {Css( LineWidth )}px; " +
		$"left: {Css( -(Gap.x + LineLength) )}px; " +
		$"top: {Css( -LineWidth * 0.5f )}px;";

	private string CrosshairRightStyle =>
		$"width: {Css( LineLength )}px; " +
		$"height: {Css( LineWidth )}px; " +
		$"left: {Css( Gap.x )}px; " +
		$"top: {Css( -LineWidth * 0.5f )}px;";

	private string CrosshairUpStyle =>
		$"width: {Css( LineWidth )}px; " +
		$"height: {Css( LineLength )}px; " +
		$"left: {Css( -LineWidth * 0.5f )}px; " +
		$"top: {Css( -(Gap.y + LineLength) )}px;";

	private string CrosshairDownStyle =>
		$"width: {Css( LineWidth )}px; " +
		$"height: {Css( LineLength )}px; " +
		$"left: {Css( -LineWidth * 0.5f )}px; " +
		$"top: {Css( Gap.y )}px;";

	private static string Css(
		float value
	)
	{
		return value.ToString(
			"0.###",
			System.Globalization.CultureInfo.InvariantCulture
		);
	}

	protected override int BuildHash()
	{
		OneMoreRoundWeapon weapon =
			LocalWeapon;

		RoundManager roundManager =
			LocalRoundManager;

		PlayerState player =
			LocalPlayerState;

		HashCode hash =
			new();

		//
		// UI context belongs in the hash too. Without this, opening Pause or
		// entering a decision phase can change ShouldShowCrosshair without
		// giving Razor a weapon-property change to rebuild from.
		//
		hash.Add(
			PauseMenu.IsAnyPauseOpen
		);

		hash.Add(
			roundManager?.
				Mode ??
			SessionMode.Menu
		);

		hash.Add(
			roundManager?.
				Phase ??
			RoundPhase.Waiting
		);

		hash.Add(
			player?.
				PreMatchState ??
			PreMatchPlayerState.Lobby
		);

		if (
			weapon is null ||
			!weapon.IsValid
		)
		{
			return hash.ToHashCode();
		}

		Vector2 gap =
			weapon.ScreenCrosshairGap;

		hash.Add(
			weapon.IsAiming
		);

		hash.Add(
			gap.x
		);

		hash.Add(
			gap.y
		);

		hash.Add(
			weapon.CrosshairLineLength
		);

		hash.Add(
			weapon.CrosshairLineWidth
		);

		hash.Add(
			weapon.CrosshairColor
		);

		return hash.ToHashCode();
	}
}