UI/SettingsPanel.razor
@using System;
@using Sandbox;
@using Sandbox.UI;
@inherits Panel

<root class="settings-panel">
	<div class="settings-tabs">
		<div class=@ControlsTabClass onclick=@ShowControls>CONTROLS</div>
		<div class=@PerformanceTabClass onclick=@ShowPerformance>PERFORMANCE</div>
	</div>

	<div class="settings-list">

		@if ( !_performanceTab )
		{
		<div class="setting-row">

			<div class="setting-copy">
				<div class="setting-name">MOUSE SENSITIVITY</div>
				<div class="setting-description">
					One More Round look multiplier applied after your normal s&amp;box sensitivity.
					ADS zoom matching still scales from this value.
				</div>
			</div>

			<div class="setting-control">
				<div class="setting-step" onclick=@SensitivityDown>-</div>
				<div class="setting-value">@SensitivityText</div>
				<div class="setting-step" onclick=@SensitivityUp>+</div>
			</div>

		</div>

		<div class="setting-row">

			<div class="setting-copy">
				<div class="setting-name">FIELD OF VIEW</div>
				<div class="setting-description">
					Horizontal gameplay FOV. Higher values expose more peripheral space,
					but the official viewmodels are designed around more moderate FOVs.
				</div>
			</div>

			<div class="setting-control">
				<div class="setting-step" onclick=@FovDown>-</div>
				<div class="setting-value">@FovText</div>
				<div class="setting-step" onclick=@FovUp>+</div>
			</div>

		</div>

		<div class="setting-row">

			<div class="setting-copy">
				<div class="setting-name">CROUCH INPUT</div>
				<div class="setting-description">
					Choose whether crouch toggles or remains active only while held.
				</div>
			</div>

			<div class="setting-cycle" onclick=@CycleCrouch>
				@CrouchText
			</div>

		</div>

		<div class="setting-row">

			<div class="setting-copy">
				<div class="setting-name">SPRINT INPUT</div>
				<div class="setting-description">
					Hybrid keeps the current tap-to-toggle and hold-to-sprint behavior.
				</div>
			</div>

			<div class="setting-cycle" onclick=@CycleSprint>
				@SprintText
			</div>

		</div>

		}
		else
		{
		<div class="setting-row">
			<div class="setting-copy">
				<div class="setting-name">FIRST PERSON RESOLUTION</div>
				<div class="setting-description">Render budget for your arms and weapon.</div>
			</div>
			<div class="setting-cycle" onclick=@CycleViewModelQuality>@OMRClientPerformance.ViewModelQualityName</div>
		</div>
		<div class="setting-row">
			<div class="setting-copy">
				<div class="setting-name">SCOPE HIPFIRE PREVIEW</div>
				<div class="setting-description">ADS always refreshes every frame.</div>
			</div>
			<div class="setting-cycle" onclick=@CycleScopePreview>@ScopePreviewText</div>
		</div>
		<div class="setting-row">
			<div class="setting-copy">
				<div class="setting-name">BOT DIAGNOSTICS</div>
				<div class="setting-description">Detailed Sandbox AI readout.</div>
			</div>
			<div class="setting-cycle" onclick=@ToggleBotDebug>@(OMRClientPerformance.BotDebugOverlay ? "ON" : "OFF")</div>
		</div>

		}
	</div>

	<div class="settings-panel-actions">
		<div class="settings-reset" onclick=@ResetDefaults>
			RESET DEFAULTS
		</div>
	</div>

</root>

@code
{
	private bool _performanceTab;
	private string ControlsTabClass => !_performanceTab ? "settings-tab selected" : "settings-tab";
	private string PerformanceTabClass => _performanceTab ? "settings-tab selected" : "settings-tab";
	private void ShowControls() => _performanceTab = false;
	private void ShowPerformance() => _performanceTab = true;
	private string ScopePreviewText => OMRClientPerformance.ScopePreviewFrameRate <= 0 ? "FULL RATE" : $"{OMRClientPerformance.ScopePreviewFrameRate} FPS";
	private void CycleViewModelQuality() => OMRClientPerformance.ViewModelQuality = ( Math.Clamp( OMRClientPerformance.ViewModelQuality, 0, 2 ) + 1 ) % 3;
	private void CycleScopePreview() => OMRClientPerformance.ScopePreviewFrameRate = OMRClientPerformance.ScopePreviewFrameRate <= 0 ? 30 : 0;
	private void ToggleBotDebug() => OMRClientPerformance.BotDebugOverlay = !OMRClientPerformance.BotDebugOverlay;

	private string SensitivityText =>
		OMRPlayerSettings
			.Sensitivity
			.ToString(
				"0.00"
			);

	private string FovText =>
		$"{MathF.Round( OMRPlayerSettings.FieldOfView )}°";

	private string CrouchText =>
		OMRPlayerSettings
			.CrouchBehavior
			.ToString()
			.ToUpperInvariant();

	private string SprintText =>
		OMRPlayerSettings
			.SprintBehavior
			.ToString()
			.ToUpperInvariant();

	private void SensitivityDown()
	{
		OMRPlayerSettings.Sensitivity -=
			0.10f;
	}

	private void SensitivityUp()
	{
		OMRPlayerSettings.Sensitivity +=
			0.10f;
	}

	private void FovDown()
	{
		OMRPlayerSettings.FieldOfView -=
			5f;
	}

	private void FovUp()
	{
		OMRPlayerSettings.FieldOfView +=
			5f;
	}

	private void CycleCrouch()
	{
		OMRPlayerSettings.CrouchBehavior =
			OMRPlayerSettings.CrouchBehavior ==
				CrouchBehavior.Toggle
					? CrouchBehavior.Hold
					: CrouchBehavior.Toggle;
	}

	private void CycleSprint()
	{
		OMRPlayerSettings.SprintBehavior =
			OMRPlayerSettings.SprintBehavior switch
			{
				SprintInputBehavior.Hold =>
					SprintInputBehavior.Toggle,

				SprintInputBehavior.Toggle =>
					SprintInputBehavior.Hybrid,

				_ =>
					SprintInputBehavior.Hold
			};
	}

	private void ResetDefaults()
	{
		OMRPlayerSettings.ResetDefaults();
		OMRClientPerformance.ViewModelQuality = 0;
		OMRClientPerformance.ScopePreviewFrameRate = 30;
		OMRClientPerformance.BotDebugOverlay = false;
	}

	protected override int BuildHash()
	{
		HashCode hash =
			new();

		hash.Add(
			MathF.Round(
				OMRPlayerSettings.Sensitivity *
					100f
			)
		);

		hash.Add(
			MathF.Round(
				OMRPlayerSettings.FieldOfView
			)
		);

		hash.Add(
			OMRPlayerSettings.CrouchBehavior
		);

		hash.Add(
			OMRPlayerSettings.SprintBehavior
		);

		hash.Add( _performanceTab );
		hash.Add( OMRClientPerformance.ViewModelQuality );
		hash.Add( OMRClientPerformance.ScopePreviewFrameRate );
		hash.Add( OMRClientPerformance.BotDebugOverlay );
		return hash.ToHashCode();
	}
}