using e_suite.Database.Core.Models; using e_suite.Database.Core.Tables.Activity; using e_suite.Workflow.Core.Attributes; using e_suite.Workflow.Core.Interfaces; using eSuite.Core.Clock; using eSuite.Core.Enums; using System.Collections; using System.Reflection; using System.Text.Json; namespace e_suite.Workflow.Core.Extensions; public static class TaskExtensions { extension(ITask task) { private Dictionary ToConfigDictionary() { var dictionary = new Dictionary(); var type = task.GetType(); var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { if (!prop.CanRead) continue; if (!prop.CanWrite) continue; var value = prop.GetValue(task); dictionary[prop.Name] = value; } return dictionary; } } public static ITask ToTask(this TaskDefinition definition, JsonSerializerOptions jsonSerializerOptions) { var type = Type.GetType(definition.Type); if (type == null) throw new InvalidOperationException($"Unknown task type '{definition.Type}'."); var instance = Activator.CreateInstance(type) as ITask; if (instance == null) throw new InvalidOperationException($"Type '{definition.Type}' does not implement ITask."); FromConfigDictionary(instance, definition.Config, jsonSerializerOptions); return instance; } public static void FromConfigDictionary(this object obj, Dictionary dict, JsonSerializerOptions jsonSerializerOptions) { var type = obj.GetType(); foreach (var kvp in dict) { var prop = type.GetProperty( kvp.Key, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase ); if (prop == null || !prop.CanWrite) continue; var targetType = prop.PropertyType; var value = kvp.Value; // If the value is a JsonElement, convert it to the target type if (value is JsonElement je) { if (targetType.IsEnum) { // Enums come through as strings value = Enum.Parse(targetType, je.GetString()!, ignoreCase: true); } else { // Deserialize JSON into the target type (handles lists, objects, primitives) value = JsonSerializer.Deserialize(je.GetRawText(), targetType, jsonSerializerOptions); } } prop.SetValue(obj, value); } } private static Dictionary ExtractConfig(object task) { var dict = new Dictionary(); foreach (var prop in task.GetType().GetProperties()) { if (!prop.CanRead) continue; if (prop.GetIndexParameters().Length > 0) continue; // skip indexers if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true)) continue; var value = prop.GetValue(task); if (value != null) dict[prop.Name] = value; } return dict; } extension(ITask task) { public TaskDefinition ToDefinition() { if (task is null) throw new NullReferenceException(); return new TaskDefinition { Type = task.GetType().FullName!, Config = task.ToConfigDictionary()! }; } private (Type outcomeType, IEnumerable outcomeList)? GetOutcomeList() { var outcomeInterface = task.GetType() .GetInterfaces() .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IOutcome<>)); if (outcomeInterface == null) return null; var outcomeType = outcomeInterface.GetGenericArguments()[0]; var prop = outcomeInterface.GetProperty("OutcomeActions"); var list = (IEnumerable)prop!.GetValue(task)!; return (outcomeType, list); } public IEnumerable GetTargetGuids() { var info = task.GetOutcomeList(); if (info == null) yield break; foreach (var item in info.Value.outcomeList.Cast()) yield return item.Task; } public IEnumerable GetTargetGuids(IEnumerable outcomes) { var info = task.GetOutcomeList(); if (info == null) return Enumerable.Empty(); var (outcomeType, list) = info.Value; var typedOutcomes = outcomes.Select(o => outcomeType.IsEnum ? Enum.Parse(outcomeType, o, true) : Convert.ChangeType(o, outcomeType) ).ToHashSet(); return list .Cast() .Where(item => { var outcomeProp = item.GetType().GetProperty("Outcome"); var outcomeValue = outcomeProp!.GetValue(item); return typedOutcomes.Contains(outcomeValue); }) .Select(item => ((IOutcomeAction)item).Task); } public async Task StartTask(ActivityTask activityTask, IClock clock) { var now = clock.GetNow; activityTask.StartDateTime = now; activityTask.SetState(ActivityState.Active); foreach (var assignment in activityTask.Assignments) { assignment.StartDateTime = now; assignment.SetState(ActivityState.Active); } await task.OnStartedAsync(activityTask); } public async Task ProgressTask(ActivityTask activityTask, ICollection tasks, IStage stage, IClock clock) { var hasCompletableTask = false; if (activityTask.ActivityState == ActivityState.ReadyToComplete) { activityTask.FinishDateTime = clock.GetNow; activityTask.SetState(ActivityState.Completed); await task.OnCompleteAsync(activityTask); var targetGuids = task.GetTargetGuids(activityTask.Outcomes).ToList(); foreach (var t in tasks) { if (targetGuids.Contains(t.TaskGuid)) { var taskDefinition = stage.FindTask(t.TaskGuid)!; await taskDefinition.StartTask(t, clock); if (t.ActivityState == ActivityState.ReadyToComplete) { hasCompletableTask = true; } } } } return hasCompletableTask; } } //public static ITask ToTask(this TaskDefinition def) //{ // var type = Type.GetType(def.Type, throwOnError: true)!; // var task = (ITask)Activator.CreateInstance(type)!; // if (def.Config != null) // foreach (var kvp in def.Config) // { // var prop = type.GetProperty(kvp.Key); // if (prop == null || !prop.CanWrite) continue; // if (prop.IsDefined(typeof(RuntimeOnlyAttribute), inherit: true)) // continue; // object? converted = ConvertValue(kvp.Value, prop.PropertyType); // prop.SetValue(task, converted); // } // if (task is ITemplateValidatable v) // { // var errors = v.ValidateForTemplate().ToList(); // if (errors.Count != 0) // throw new InvalidOperationException( // $"Task {task.GetType().Name} is invalid: {string.Join("; ", errors)}"); // } // return task; //} //private static object? ConvertValue(object? value, Type targetType) //{ // if (value == null) return null; // // Handle enums // if (targetType.IsEnum) // return Enum.Parse(targetType, value.ToString()!); // // Handle Guid // if (targetType == typeof(Guid)) // return Guid.Parse(value.ToString()!); // // Handle nullable types // var underlying = Nullable.GetUnderlyingType(targetType); // if (underlying != null) // return Convert.ChangeType(value, underlying); // return Convert.ChangeType(value, targetType); //} //Todo can only be on a run-time instance //public static bool IsCompleted(this ITask task) //{ // return task.TaskState.In(TaskState.Completed, TaskState.Cancelled); //} //public static bool ReadyToActivate(this ITask task) //{ // foreach (var predecessor in task.Predecessors) // { // if (!predecessor.IsCompleted()) // { // return false; // } // } // return true; //} }