Park/Progression/Stats/Stats.cs
using HC3.Persistence;

namespace HC3;

/// <summary>
/// A collection of events for stats.
/// </summary>
public interface IStatEvents : ISceneEvent<IStatEvents>
{
	/// <summary>
	/// Called when a stat changes
	/// </summary>
	/// <param name="identifier"></param>
	/// <param name="value"></param>
	public void OnStatChanged( string identifier, float value );

	/// <summary>
	/// Called when stats are loaded from a save game
	/// </summary>
	public void OnStatsLoaded() { }
}

/// <summary>
/// This is a stats wrapper that allows us to store stats local to a save game. It's a GameObjectSystem so it always exists.
/// </summary>
public partial class Stats : GameObjectSystem<Stats>, ISaveDataProperty<Stats.SaveData>
{
	[ConVar( "hc3.debug.stats" )]
	public static bool Debug { get; set; } = false;

	public Stats( Scene scene ) : base( scene ) { }

	// ISaveDataProperty
	public sealed record SaveData( Dictionary<string, float> Store );
	string ISaveDataProperty.PropertyName => "Stats";
	int ISaveDataProperty.PropertyOrder => 1_000;

	/// <summary>
	/// A list of stats that we'll store.
	/// </summary>
	[Sync( SyncFlags.FromHost )]
	public NetDictionary<string, float> Values { get; private set; } = new();

	SaveData ISaveDataProperty<SaveData>.WriteValue( Scene scene )
	{
		return new( Values.ToDictionary() );
	}

	void ISaveDataProperty<SaveData>.ReadValue( Scene scene, SaveData value )
	{
		Values = [.. value.Store];

		IStatEvents.Post( x => x.OnStatsLoaded() );
	}

	/// <summary>
	/// Get a stat
	/// </summary>
	/// <param name="identifier"></param>
	/// <param name="defaultValue"></param>
	/// <returns></returns>
	public static float Get( string identifier, float defaultValue = 0f )
	{
		if ( !Current.Values.TryGetValue( identifier, out var value ) )
		{
			value = defaultValue;
			Current.Values[identifier] = value;
		}
		return value;
	}

	/// <summary>
	/// Set a stat
	/// </summary>
	/// <param name="identifier"></param>
	/// <param name="value"></param>
	public static void Set( string identifier, float value )
	{
		if ( !Networking.IsHost ) return;

		if ( Current.Values.ContainsKey( identifier ) )
		{
			Current.Values[identifier] = value;
		}
		else
		{
			Current.Values.Add( identifier, value );
		}

		Sandbox.Services.Stats.SetValue( identifier, value );

		IStatEvents.Post( x => x.OnStatChanged( identifier, value ) );
	}

	/// <summary>
	/// Increment a stat
	/// </summary>
	/// <param name="identifier"></param>
	/// <param name="value"></param>
	public static void Increment( string identifier, float value = 1f )
	{
		if ( !Networking.IsHost ) return;

		if ( Debug ) Log.Info( $"Incrementing {identifier} by {value}" );

		if ( Current.Values.ContainsKey( identifier ) )
		{
			Current.Values[identifier] += value;
		}
		else
		{
			Current.Values.Add( identifier, value );
		}

		Sandbox.Services.Stats.Increment( identifier, value );

		IStatEvents.Post( x => x.OnStatChanged( identifier, value ) );
	}

	/// <summary>
	/// Clear a stat
	/// </summary>
	/// <param name="identifier"></param>
	public static void Clear( string identifier )
	{
		if ( !Networking.IsHost ) return;

		if ( Current.Values.ContainsKey( identifier ) )
		{
			Current.Values.Remove( identifier );
		}
		else
		{
			Log.Warning( $"Tried to clear stat {identifier} but it doesn't exist." );
		}

		IStatEvents.Post( x => x.OnStatChanged( identifier, 0 ) );
	}

	[ConCmd( "hc3.debug.clearstats", ConVarFlags.Cheat )]
	public static void Clear()
	{
		if ( !Networking.IsHost ) return;

		// So all events are fired
		foreach ( var kv in Current.Values )
		{
			Clear( kv.Key );
		}
	}
}