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

<root>
	@if ( ShouldShowScope )
	{
		<div class="pip-stage" style="@ScopeStageStyle">
			<div class="pip-peripheral-dim" style="@PeripheralDimStyle"></div>

			<div class="pip-lens" style="@LensStyle">
				<OMRScopeFeedPanel class="pip-feed" style="@FeedStyle" SourceTexture=@ScopeTexture />
				<div class="pip-lens-transmission" style="@LensTransmissionStyle"></div>
				<div class="pip-lens-tint" style="@LensTintStyle"></div>
				<div class="pip-lens-vignette" style="@LensVignetteStyle"></div>
				<div class="pip-lens-reflection" style="@LensReflectionStyle"></div>
				@if ( ShowScreenSpaceRing )
				{
					<div class="scope-ring" style="@ScopeRingStyle"></div>
				}

				@if ( ShowReticle )
				{
					<OMRScopeReticlePanel class="scope-reticle"
						ReticleStyle=@(Profile?.SimpleReticleStyle ?? OMRScopeReticleStyle.FineCross)
						ReticleColor=@(Profile?.SimpleReticleColor ?? Color.Black)
						Thickness=@(Profile?.SimpleReticleThickness ?? 1.5f)
						Gap=@(Profile?.SimpleReticleGap ?? 8f)
						LineLength=@(Profile?.SimpleReticleLineLength ?? 72f)
						DotSize=@(Profile?.SimpleReticleDotSize ?? 3f)
						ReticleOpacity=@ReticleOpacity
						OutlineWidth=@(Profile?.SimpleReticleOutlineWidth ?? 0f)
						OutlineOpacity=@(Profile?.SimpleReticleOutlineOpacity ?? 0f) />
				}
			</div>
		</div>
	}
</root>

@code
{
	private OMRScopePiPCamera _pipCamera;

	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 IWeaponOpticState LocalOpticState =>
		LocalInventory?.ActiveItem as IWeaponOpticState;

	private OMROpticProfile Profile =>
		LocalOpticState?.ActiveOpticProfile;

	private Texture ScopeTexture =>
		PiPCamera?.OutputTexture;

	private OMRScopePiPCamera PiPCamera
	{
		get
		{
			if ( _pipCamera is not null && _pipCamera.IsValid )
				return _pipCamera;

			_pipCamera =
				Scene?
					.GetAllComponents<OMRScopePiPCamera>()
					.FirstOrDefault(
						x =>
							x is not null &&
							x.IsValid
					);

			return _pipCamera;
		}
	}

	private RoundManager LocalRoundManager => SceneReferences.Rounds;

	private bool IsGameplayContext
	{
		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 UsesScreenSpacePiPOverlay
	{
		get
		{
			if ( Profile is null || !Profile.IsValid )
				return false;

			return Profile.PresentationMode == OMROpticPresentationMode.PictureInPicture ||
				(
					Profile.PresentationMode == OMROpticPresentationMode.ModelLensPictureInPicture &&
					Profile.UseScreenSpaceHybridOverlay
				);
		}
	}

	private bool IsHybridModelLensOverlay =>
		Profile is not null &&
		Profile.IsValid &&
		Profile.PresentationMode == OMROpticPresentationMode.ModelLensPictureInPicture &&
		Profile.UseScreenSpaceHybridOverlay;

	private bool ShowReticle =>
		Profile?.ShowSimpleReticle ?? true;

	private float ReticleOpacity =>
		Profile is null || !Profile.IsValid
			? 1f
			: MathX.Lerp(
				MathX.Clamp( Profile.ReticleHipOpacity, 0f, 1f ),
				MathX.Clamp( Profile.ReticleAdsOpacity, 0f, 1f ),
				LensClarityBlend
			);

	private bool ShouldShowScope =>
		IsGameplayContext &&
		LocalOpticState is not null &&
		Profile is not null &&
		Profile.IsValid &&
		UsesScreenSpacePiPOverlay &&
		ScopePresentationBlend > 0.001f &&
		PiPCamera is not null &&
		PiPCamera.IsRendering;

	private float ScopePresentationBlend
	{
		get
		{
			if ( LocalOpticState is null || Profile is null )
				return 0f;

			float start;
			float full;

			if ( IsHybridModelLensOverlay )
			{
				start = MathX.Clamp( Profile.HybridOverlayFadeStart, 0f, 1f );
				full = MathX.Clamp( Profile.HybridOverlayFadeFull, start, 1f );
			}
			else
			{
				start = MathX.Clamp( Profile.ScopePresentationThreshold, 0f, 1f );
				full = 1f;
			}

			if ( full <= start + 0.0001f )
				return LocalOpticState.AimFraction >= full ? 1f : 0f;

			return MathX.Clamp(
				(LocalOpticState.AimFraction - start) / (full - start),
				0f,
				1f
			);
		}
	}

	private string ScopeStageStyle =>
		$"opacity: {Css( ScopePresentationBlend )};";

	private string PeripheralDimStyle
	{
		get
		{
			float opacity = MathX.Clamp(
				Profile?.PiPPeripheralDimOpacity ?? 0.12f,
				0f,
				0.85f
			);

			return $"background-color: rgba(0, 0, 0, {Css( opacity )});";
		}
	}

	private float DiameterVh
	{
		get
		{
			float authored = IsHybridModelLensOverlay
				? Profile?.HybridOverlayDiameterVh ?? 40f
				: Profile?.PiPLensDiameterVh ?? 64f;

			return MathX.Clamp( authored, 10f, 95f );
		}
	}

	private string LensStyle =>
		$"width: {Css( DiameterVh )}vh; " +
		$"height: {Css( DiameterVh )}vh;";

	private float FeedZoomScale =>
		MathX.Clamp( PiPCamera?.PresentationZoomScale ?? 1f, 1f, 20f );

	private string FeedStyle =>
		$"transform:scale({Css( FeedZoomScale )},{Css( FeedZoomScale )});";

	private float LensClarityBlend
	{
		get
		{
			if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
				return 1f;

			float start = MathX.Clamp( Profile.LensClarityStart, 0f, 1f );
			float full = MathX.Clamp( Profile.LensClarityFull, start, 1f );

			if ( full <= start + 0.0001f )
				return LocalOpticState?.AimFraction >= full ? 1f : 0f;

			return MathX.Clamp(
				((LocalOpticState?.AimFraction ?? 0f) - start) / (full - start),
				0f,
				1f
			);
		}
	}

	private float OpticalLerp( float hip, float ads ) =>
		MathX.Lerp( hip, ads, LensClarityBlend );

	private string LensTransmissionStyle
	{
		get
		{
			if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
				return "opacity:0;";

			// Do not put a CSS filter directly on OMRScopeFeedPanel. The panel draws a
			// live GPU render target in OnDraw; forcing that through an off-screen UI
			// filter pass proved unreliable and could collapse the lens to a flat gray
			// surface. Simulate light transmission with a simple black veil here, while
			// blur/saturation/contrast remain authored for the eventual lens shader.
			float brightness = MathX.Clamp(
				OpticalLerp( Profile.LensHipBrightness, Profile.LensAdsBrightness ),
				0f,
				1f
			);

			float opacity = 1f - brightness;
			return $"background-color:rgba(0,0,0,1);opacity:{Css( opacity )};";
		}
	}

	private string LensTintStyle
	{
		get
		{
			if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
				return "opacity:0;";

			float opacity = OpticalLerp( Profile.LensHipTintOpacity, Profile.LensAdsTintOpacity );
			return $"background-color:{Profile.LensTintColor.Rgba};opacity:{Css( opacity )};";
		}
	}

	private string LensVignetteStyle
	{
		get
		{
			if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
				return "opacity:0;";

			float darkness = OpticalLerp( Profile.LensHipEdgeDarkening, Profile.LensAdsEdgeDarkening );
			return $"background-image:radial-gradient(rgba(0,0,0,0),rgba(0,0,0,{Css( darkness )}));";
		}
	}

	private string LensReflectionStyle
	{
		get
		{
			if ( Profile is null || !Profile.IsValid || !Profile.EnableLensSimulation )
				return "opacity:0;";

			float opacity = OpticalLerp( Profile.LensHipReflectionOpacity, Profile.LensAdsReflectionOpacity );
			return $"opacity:{Css( opacity )};";
		}
	}

	private float RingThickness =>
		MathF.Max( Profile?.PiPRingThickness ?? 3f, 0f );

	private bool ShowScreenSpaceRing =>
		!IsHybridModelLensOverlay || (Profile?.HybridOverlayDrawRing ?? false);

	private string ScopeRingStyle
	{
		get
		{
			Color ringColor = Profile?.PiPRingColor ?? Color.Black;
			return $"border: {Css( RingThickness )}px solid {ringColor.Rgba};";
		}
	}

	private static string Css( float value ) =>
		value.ToString(
			"0.###",
			CultureInfo.InvariantCulture
		);

	protected override int BuildHash()
	{
		HashCode hash = new();
		IWeaponOpticState optic = LocalOpticState;
		OMROpticProfile profile = Profile;
		Texture texture = PiPCamera?.OutputTexture;

		hash.Add( PauseMenu.IsAnyPauseOpen );
		hash.Add( optic?.IsScopePresentationActive ?? false );
		hash.Add( optic?.AimFraction ?? 0f );
		hash.Add( PiPCamera?.IsRendering ?? false );
		hash.Add( texture?.Index ?? -1 );
		hash.Add( PiPCamera?.PresentationZoomScale ?? 1f );

		if ( profile is null || !profile.IsValid )
			return hash.ToHashCode();

		hash.Add( profile.PresentationMode );
		hash.Add( profile.UseScreenSpaceHybridOverlay );
		hash.Add( profile.HybridOverlayDiameterVh );
		hash.Add( profile.HybridOverlayDrawRing );
		hash.Add( profile.HybridOverlayFadeStart );
		hash.Add( profile.HybridOverlayFadeFull );
		hash.Add( profile.PiPLensDiameterVh );
		hash.Add( profile.PiPPeripheralDimOpacity );
		hash.Add( profile.PiPRingThickness );
		hash.Add( profile.PiPRingColor );
		hash.Add( profile.EnableLensSimulation );
		hash.Add( profile.LensClarityStart );
		hash.Add( profile.LensClarityFull );
		hash.Add( profile.LensHipBlur );
		hash.Add( profile.LensAdsBlur );
		hash.Add( profile.LensHipBrightness );
		hash.Add( profile.LensAdsBrightness );
		hash.Add( profile.LensHipSaturation );
		hash.Add( profile.LensAdsSaturation );
		hash.Add( profile.LensHipContrast );
		hash.Add( profile.LensAdsContrast );
		hash.Add( profile.LensTintColor );
		hash.Add( profile.LensHipTintOpacity );
		hash.Add( profile.LensAdsTintOpacity );
		hash.Add( profile.LensHipEdgeDarkening );
		hash.Add( profile.LensAdsEdgeDarkening );
		hash.Add( profile.LensHipReflectionOpacity );
		hash.Add( profile.LensAdsReflectionOpacity );
		hash.Add( profile.ShowSimpleReticle );
		hash.Add( profile.ReticleHipOpacity );
		hash.Add( profile.ReticleAdsOpacity );
		hash.Add( profile.SimpleReticleOutlineWidth );
		hash.Add( profile.SimpleReticleOutlineOpacity );
		hash.Add( profile.SimpleReticleStyle );
		hash.Add( profile.SimpleReticleColor );
		hash.Add( profile.SimpleReticleThickness );
		hash.Add( profile.SimpleReticleGap );
		hash.Add( profile.SimpleReticleLineLength );
		hash.Add( profile.SimpleReticleDotSize );

		return hash.ToHashCode();
	}
}