AI/Agents/AgentThought.cs
using System;
using System.Text.Json.Nodes;

namespace HC3;

/// <summary>
/// Essentially a log of an agent's thoughts and experiences.
/// </summary>
public class AgentThought
{
	/// <summary>
	/// When this thought occurred
	/// </summary>
	public DateTimeOffset TimeStamp { get; set; }

	/// <summary>
	/// The message describing what the agent thought/experienced
	/// </summary>  
	public string Message { get; set; }

	/// <summary>
	/// Optional world position where this thought occurred
	/// </summary>
	public Vector3? Location { get; set; }

	/// <summary>
	/// Tags associated with this thought (e.g. "good", "bad", "purchase", "hunt", etc)
	/// </summary>
	public HashSet<string> Tags { get; set; } = new();

	public AgentThought( string message )
	{
		TimeStamp = DateTimeOffset.UtcNow;
		Message = message;
	}

	// Add serialization support
	public JsonObject ToJson()
	{
		var json = new JsonObject
		{
			["timestamp"] = TimeStamp.ToString( "O" ),
			["message"] = Message,
			["tags"] = new JsonArray( Tags.Select( t => JsonValue.Create( t ) ).ToArray() )
		};

		if ( Location.HasValue )
		{
			json["location"] = Location.Value.ToString();
		}

		return json;
	}

	public static AgentThought FromJson( JsonNode node )
	{
		if ( node == null ) return null;

		var thought = new AgentThought( node["message"]?.GetValue<string>() ?? "" )
		{
			TimeStamp = DateTimeOffset.Parse( node["timestamp"]?.GetValue<string>() ?? DateTimeOffset.UtcNow.ToString( "O" ) )
		};

		if ( node["location"] != null )
		{
			thought.Location = Vector3.Parse( node["location"].GetValue<string>() );
		}

		if ( node["tags"] is JsonArray tags )
		{
			foreach ( var tag in tags )
			{
				thought.Tags.Add( tag.GetValue<string>() );
			}
		}

		return thought;
	}
}

/// <summary>
/// Component that tracks an agent's recent thoughts and experiences
/// </summary>
public class AgentThoughtLog : Component
{
	/// <summary>
	/// Maximum number of thought entries to keep in history
	/// </summary>
	[Property] public int MaxEntries { get; set; } = 10;

	/// <summary>
	/// List of recent thoughts, most recent first
	/// </summary>
	private List<AgentThought> _thoughts = new();
	public IReadOnlyList<AgentThought> GetAll() => _thoughts;

	/// <summary>
	/// Number of thoughts to save to persistence
	/// </summary>
	private const int SavedThoughtCount = 3;

	/// <summary>
	/// Add a new thought entry to the log
	/// </summary>
	public AgentThought AddThought( string message, Vector3? location = null )
	{
		var thought = new AgentThought( message );
		if ( location.HasValue )
		{
			thought.Location = location.Value;
		}
		_thoughts.Insert( 0, thought );

		// Trim to max entries
		while ( _thoughts.Count > MaxEntries )
		{
			_thoughts.RemoveAt( _thoughts.Count - 1 );
		}

		return thought;
	}

	/// <summary>
	/// Get most recent thoughts with specific tags
	/// </summary>
	public IEnumerable<AgentThought> GetThoughtsByTags( params string[] tags )
	{
		return _thoughts.Where( t => tags.All( tag => t.Tags.Contains( tag ) ) );
	}

	/// <summary>
	/// Clear all logged thoughts
	/// </summary>
	public void Clear()
	{
		_thoughts.Clear();
	}

	/// <summary>
	/// Serialize the last few thoughts to JSON for persistence
	/// </summary>
	public JsonObject ToJson()
	{
		var thoughts = new JsonArray();
		foreach ( var thought in _thoughts.Take( SavedThoughtCount ) )
		{
			thoughts.Add( thought.ToJson() );
		}

		var json = new JsonObject
		{
			["thoughts"] = thoughts
		};

		return json;
	}

	/// <summary>
	/// Load thoughts from JSON persistence data
	/// </summary>
	public void FromJson( JsonNode node )
	{
		if ( node?["thoughts"] is JsonArray thoughts )
		{
			_thoughts.Clear();
			foreach ( var thoughtNode in thoughts )
			{
				var thought = AgentThought.FromJson( thoughtNode );
				if ( thought != null )
				{
					_thoughts.Add( thought );
				}
			}
		}
	}
}