WeaponRuntimeProbe.cs
using Sandbox;
using System.Text;
using System.Linq;

/// <summary>
/// Low-frequency weapon diagnostic snapshots used while the reusable weapon
/// framework is being migrated. This component intentionally does no gameplay
/// work and is safe to remove/disable from production prefabs.
/// </summary>
public sealed class WeaponRuntimeProbe : Component
{
	[Property]
	public BaseCombatWeapon Weapon { get; set; }

	[Property]
	public bool EnabledDiagnostics { get; set; } = true;

	[Property]
	public float FirstSnapshotDelay { get; set; } = 0.25f;

	[Property]
	public float SecondSnapshotDelay { get; set; } = 1.5f;

	[Property]
	public float FinalSnapshotDelay { get; set; } = 4.0f;

	private float _elapsed;
	private bool _firstLogged;
	private bool _secondLogged;
	private bool _finalLogged;

	protected override void OnStart()
	{
		Weapon ??= Components.Get<BaseCombatWeapon>();

		if ( Weapon is null )
		{
			Log.Warning( "WeaponRuntimeProbe could not find BaseCombatWeapon." );
			return;
		}

		Log.Info(
			$"[WEAPON PROBE] Started | Object:{GameObject.Name} | Proxy:{IsProxy} | NetworkOwner:{Network.OwnerId}"
		);
	}

	protected override void OnUpdate()
	{
		if ( !EnabledDiagnostics || Weapon is null )
			return;

		_elapsed += Time.Delta;

		if ( !_firstLogged && _elapsed >= FirstSnapshotDelay )
		{
			_firstLogged = true;
			LogSnapshot( "EARLY" );
		}

		if ( !_secondLogged && _elapsed >= SecondSnapshotDelay )
		{
			_secondLogged = true;
			LogSnapshot( "MID" );
		}

		if ( !_finalLogged && _elapsed >= FinalSnapshotDelay )
		{
			_finalLogged = true;
			LogSnapshot( "FINAL" );
		}
	}

	private void LogSnapshot( string stage )
	{
		var owner = Weapon.Owner;
		var holder = Weapon.HolderRenderer;

		bool hasHoldBoneTransform = false;
		if ( holder is not null && !string.IsNullOrWhiteSpace( Weapon.HoldBone ) )
		{
			hasHoldBoneTransform = holder.TryGetBoneTransform( Weapon.HoldBone, out _ );
		}

		string ownerName = owner is not null ? owner.GameObject.Name : "NULL";
		string holderName = holder is not null ? holder.GameObject.Name : "NULL";
		string worldPrefabName = Weapon.WorldModelPrefab is not null ? Weapon.WorldModelPrefab.Name : "NULL";
		string worldModelName = Weapon.WorldModel is not null ? Weapon.WorldModel.Name : "NULL";
		string viewPrefabName = Weapon.ViewModelPrefab is not null ? Weapon.ViewModelPrefab.Name : "NULL";
		string viewModelName = Weapon.ViewModel is not null ? Weapon.ViewModel.Name : "NULL";
		string weaponModelName = Weapon.WeaponModel is not null ? Weapon.WeaponModel.GameObject.Name : "NULL";

		Log.Info( $"[WEAPON PROBE] ===== {stage} =====" );
		Log.Info( $"[WEAPON PROBE] WeaponObject:{GameObject.Name} | Proxy:{IsProxy} | NetworkOwner:{Network.OwnerId}" );
		Log.Info( $"[WEAPON PROBE] Owner:{ownerName} | IsHeld:{Weapon.IsHeld} | Clip:{Weapon.Clip1}/{Weapon.ClipMaxSize} | Reloading:{Weapon.IsReloading}" );
		Log.Info( $"[WEAPON PROBE] Fire | Automatic:{Weapon.PrimaryAutomatic} | Delay:{Weapon.PrimaryDelay:0.###} | Damage:{Weapon.Ballistics.Damage:0.##} | Pellets:{Weapon.Ballistics.Pellets} | Range:{Weapon.Ballistics.Range:0.##}" );
		Log.Info( $"[WEAPON PROBE] Spread | Current:{Weapon.CurrentSpread} | Base:{Weapon.Ballistics.SpreadBase} | Growth:{Weapon.Ballistics.SpreadGrowth} | Recovery:{Weapon.Ballistics.SpreadRecovery:0.###}" );

		if ( Weapon is OneMoreRoundWeapon omrWeapon )
		{
			string definitionName = omrWeapon.UsesWeaponDefinition
				? omrWeapon.Definition.ResourceName
				: "LEGACY PREFAB VALUES";

			Log.Info( $"[WEAPON PROBE] OMR | ID:{omrWeapon.WeaponId} | Name:{omrWeapon.DisplayName} | Archetype:{omrWeapon.WeaponArchetype}" );
			Log.Info( $"[WEAPON PROBE] Definition:{definitionName} | Revision:{omrWeapon.StatRevision} | ADS:{omrWeapon.IsAiming} | Action:{omrWeapon.ActionState} | AccuracyFlags:{omrWeapon.AccuracyFlags}" );
			Log.Info( $"[WEAPON PROBE] Runtime | ShotsSinceReset:{omrWeapon.PrimaryShotsFiredSinceReset} | ClaimAccepted:{omrWeapon.AcceptedShotClaims} | ClaimRejected:{omrWeapon.RejectedShotClaims}" );
			string lastPresentationEvent = omrWeapon.LastPresentationEvent?.ToString() ?? "None";
			Log.Info( $"[WEAPON PROBE] Presentation | Events:{omrWeapon.PresentationEventCount} | Last:{lastPresentationEvent} | LastAt:{omrWeapon.LastPresentationEventTime:0.###}" );
			Log.Info( $"[WEAPON PROBE] Modifiers:{DescribeModifiers( omrWeapon )}" );

			OMRWeaponStats baseStats = omrWeapon.BaseStats;
			OMRWeaponStats resolvedStats = omrWeapon.ResolvedStats;

			if ( baseStats is not null && resolvedStats is not null )
			{
				Log.Info(
					$"[WEAPON PROBE] Base -> Resolved | Damage:{baseStats.Damage:0.##}->{resolvedStats.Damage:0.##} | Delay:{baseStats.PrimaryDelay:0.###}->{resolvedStats.PrimaryDelay:0.###} | Clip:{baseStats.ClipMaxSize}->{resolvedStats.ClipMaxSize} | Reload:{baseStats.ReloadTime:0.###}->{resolvedStats.ReloadTime:0.###}"
				);
				Log.Info(
					$"[WEAPON PROBE] Base -> Resolved Spread | Base:{baseStats.SpreadBase}->{resolvedStats.SpreadBase} | Growth:{baseStats.SpreadGrowth}->{resolvedStats.SpreadGrowth} | ADS:{baseStats.AimSpreadMultiplier:0.###}->{resolvedStats.AimSpreadMultiplier:0.###}"
				);
				Log.Info(
					$"[WEAPON PROBE] Base -> Resolved Recoil | Pitch:{baseStats.RecoilPitch}->{resolvedStats.RecoilPitch} | Yaw:{baseStats.RecoilYaw}->{resolvedStats.RecoilYaw}"
				);
			}

			Log.Info( $"[WEAPON PROBE] Handling | ADS FOV Scale:{omrWeapon.AimFovScale:0.###} | ADS Time:{omrWeapon.AimFovTransitionTime:0.###} | SprintToFire:{omrWeapon.SprintToFireDelay:0.###}" );

			string opticName = omrWeapon.HasOpticProfile
				? omrWeapon.OpticProfile.ResourceName
				: "NONE";
			string opticPresentation = omrWeapon.HasOpticProfile
				? omrWeapon.OpticProfile.PresentationMode.ToString()
				: "None";
			Log.Info( $"[WEAPON PROBE] Optic | Profile:{opticName} | Presentation:{opticPresentation} | AimFraction:{omrWeapon.AimFraction:0.###} | AccuracyBlend:{omrWeapon.AimAccuracyBlend:0.###} | ScopeActive:{omrWeapon.IsScopePresentationActive} | SensScale:{omrWeapon.CurrentOpticSensitivityScale:0.###} | PeripheralFovScale:{omrWeapon.CurrentPeripheralAimFovScale:0.###} | ViewModelFovScale:{omrWeapon.CurrentViewModelAimFovScale:0.###}" );

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

			if ( viewModelCamera is not null )
			{
				Log.Info(
					$"[WEAPON PROBE] ViewModelCamera | FOV:{viewModelCamera.CurrentViewModelFieldOfView:0.##} | AimScale:{viewModelCamera.CurrentViewModelAimFovScale:0.###} | MatchSource:{viewModelCamera.MatchSourceFieldOfView}"
				);
			}

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

			if ( pipCamera is not null )
			{
				CameraComponent scopeCamera = pipCamera.ScopeCamera;
				Texture scopeTexture = pipCamera.OutputTexture;

				Vector4? clipBounds = pipCamera.CurrentClipSpaceBounds;
				string clipText = clipBounds.HasValue
					? $"L{clipBounds.Value.x:0.###},D{clipBounds.Value.y:0.###},R{clipBounds.Value.z:0.###},U{clipBounds.Value.w:0.###}"
					: "NONE";

				Log.Info(
					$"[WEAPON PROBE] ScopePiP | Rendering:{pipCamera.IsRendering} | " +
					$"Texture:{scopeTexture?.Index ?? -1} | Resolution:{pipCamera.OutputResolution} | " +
					$"OpticalRefFOV:{pipCamera.CurrentOpticalReferenceFieldOfView:0.##} | LensProjectionFOV:{pipCamera.CurrentLensProjectionFieldOfView:0.##} | " +
					$"OpticalFOV:{pipCamera.CurrentScopeFieldOfView:0.##} | RenderFOV:{pipCamera.CurrentRenderFieldOfView:0.##} | " +
					$"OpticalMagnification:{pipCamera.CurrentScopeMagnification:0.##}x | LensFraction:{pipCamera.CurrentLensScreenFraction:0.###} | " +
					$"LensMeasured:{pipCamera.IsLensScreenFractionMeasured} | LensSource:{pipCamera.CurrentLensMeasurementSource} | LensRaw:{pipCamera.CurrentRawMeasuredLensScreenFraction:0.###} | " +
					$"TargetApparent:{pipCamera.CurrentTargetApparentMagnification:0.##}x | RenderCrop:{pipCamera.CurrentRenderCropMagnification:0.##}x | " +
					$"ExpectedApparent:{pipCamera.CurrentExpectedApparentMagnification:0.##}x | PresentationZoom:{pipCamera.PresentationZoomScale:0.##}x | " +
					$"ClipBounds:{clipText} | EyeToPiP:{pipCamera.CurrentEyeToScopeCameraDistance:0.##} | EyeToOptic:{pipCamera.CurrentEyeToPhysicalOpticDistance:0.##} | " +
					$"PiPPosBlend:{pipCamera.CurrentPiPPositionBlend:0.###} | EyeBoxAngle:{pipCamera.CurrentEyeBoxAngularError:0.##}deg | " +
					$"EyeBoxAlign:{pipCamera.CurrentEyeBoxAlignment:0.###} | EyeBoxOffset:{pipCamera.CurrentEyeBoxOffset} | " +
					$"ManualRT:{pipCamera.LastManualRenderSucceeded} | CameraEnabled:{scopeCamera?.Enabled ?? false} | Priority:{scopeCamera?.Priority ?? -1} | " +
					$"CameraRawFOV:{(scopeCamera?.FieldOfView ?? 0f):0.##} | CameraViewFOV:{(scopeCamera?.View.FieldOfView ?? 0f):0.##} | " +
					$"Anchor:{pipCamera.ResolvedCameraAnchor?.Name ?? "MAIN_CAMERA"}"
				);
			}
			Log.Info( $"[WEAPON PROBE] FireAnim | EffectiveScale:{omrWeapon.EffectiveViewModelFireScale:0.###} | Locked:{omrWeapon.IsViewModelFireScaleLocked} | Remaining:{omrWeapon.ViewModelFireScaleLockRemaining:0.###}" );

			string cycleName = omrWeapon.HasCycleProfile
				? omrWeapon.CycleProfile.ResourceName
				: "NONE";
			string cycleAimPolicy = omrWeapon.HasCycleProfile
				? omrWeapon.CycleProfile.AimPolicy.ToString()
				: "None";
			Log.Info( $"[WEAPON PROBE] Cycle | Profile:{cycleName} | AimPolicy:{cycleAimPolicy} | StartedADS:{omrWeapon.CycleStartedWhileAiming} | AimAllowedNow:{omrWeapon.CanAimDuringCurrentCycle()} | Chambered:{omrWeapon.IsChambered} | Pending:{omrWeapon.IsCyclePendingOrActive} | MechanicalProgress:{omrWeapon.CycleProgress:0.###} | PresentationActive:{omrWeapon.IsCyclePresentationActive} | PresentationProgress:{omrWeapon.CyclePresentationProgress:0.###}" );

			if ( omrWeapon.TryGetLocomotionSnapshot( out WeaponLocomotionSnapshot locomotion ) )
			{
				Log.Info(
					$"[WEAPON PROBE] Locomotion | Ground:{locomotion.IsGrounded} Air:{locomotion.IsAirborne} Crouch:{locomotion.IsCrouched} Slide:{locomotion.IsSliding} WantsSprint:{locomotion.WantsSprint} Sprint:{locomotion.IsSprinting}"
				);
				Log.Info(
					$"[WEAPON PROBE] Motion | Speed:{locomotion.HorizontalSpeed:0.0}/{locomotion.ReferenceMoveSpeed:0.0} Normalized:{locomotion.NormalizedMoveSpeed:0.###} Vertical:{locomotion.VerticalVelocity:0.0} Accel:{locomotion.Acceleration} Landing:{locomotion.LandingImpulse:0.0}"
				);
			}

			Log.Info( $"[WEAPON PROBE] Accuracy | Model:{omrWeapon.MovementAccuracyModel} | MoveBlend:{omrWeapon.CurrentMovementAccuracyBlend:0.###} | Effective:{omrWeapon.CurrentAccuracyMultiplier:0.###} | Move:{omrWeapon.MovingSpreadMultiplier:0.###} | Crouch:{omrWeapon.CrouchedSpreadMultiplier:0.###} | Air:{omrWeapon.AirborneSpreadMultiplier:0.###} | Slide:{omrWeapon.SlidingSpreadMultiplier:0.###} | ADS:{omrWeapon.AimSpreadMultiplier:0.###}" );
			string recoilPatternSource = omrWeapon.RecoilPatternAsset is not null && omrWeapon.RecoilPatternAsset.IsValid
				? $"Asset:{omrWeapon.RecoilPatternAsset.ResourceName}({omrWeapon.RecoilPatternAsset.ShotCount} shots)"
				: $"LegacySteps:{omrWeapon.RecoilPattern?.Count ?? 0}";

			Log.Info( $"[WEAPON PROBE] Recoil | Model:{omrWeapon.RecoilModel} | Source:{recoilPatternSource} | Scale:{omrWeapon.RecoilPatternScale} | Index:{omrWeapon.CurrentRecoilPatternIndex} | LastKick:{omrWeapon.LastGameplayRecoilKick}" );
			Log.Info( $"[WEAPON PROBE] Recoil Recovery | Enabled:{omrWeapon.RecoilRecoveryEnabled} | Active:{omrWeapon.IsRecoveringGameplayRecoil} | Remaining:{omrWeapon.RemainingGameplayRecoilRecovery} | Fraction:{omrWeapon.RecoilRecoveryFraction:0.###} | Delay:{omrWeapon.RecoilRecoveryDelay:0.###} | PitchSpeed:{omrWeapon.RecoilRecoveryPitchSpeed:0.###} | YawSpeed:{omrWeapon.RecoilRecoveryYawSpeed:0.###}" );
		}

		Log.Info( $"[WEAPON PROBE] HoldBone:{Weapon.HoldBone} | HolderRenderer:{holderName} | CreateBoneObjects:{(holder is not null ? holder.CreateBoneObjects : false)} | HoldBoneTransform:{hasHoldBoneTransform}" );
		Log.Info( $"[WEAPON PROBE] WorldModelPrefab:{worldPrefabName} | WorldModel:{worldModelName}" );
		Log.Info( $"[WEAPON PROBE] ViewModelPrefab:{viewPrefabName} | ViewModel:{viewModelName} | WeaponModel:{weaponModelName}" );
		Log.Info( "[WEAPON PROBE] ==================" );
	}

	private static string DescribeModifiers( OneMoreRoundWeapon weapon )
	{
		if ( weapon.ActiveStatModifiers is null || weapon.ActiveStatModifiers.Count == 0 )
			return "NONE";

		StringBuilder builder = new();

		for ( int i = 0; i < weapon.ActiveStatModifiers.Count; i++ )
		{
			IOMRWeaponStatModifier modifier = weapon.ActiveStatModifiers[i];
			if ( modifier is null )
				continue;

			if ( builder.Length > 0 )
				builder.Append( " -> " );

			builder.Append( modifier.ModifierId );
			builder.Append( "@" );
			builder.Append( modifier.Priority );
		}

		return builder.Length > 0
			? builder.ToString()
			: "NONE";
	}
}