// Copyright HyperTwist, Inc. All Rights Reserved.

#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"

#include "HyperTwistTrainingRepositoryLibraryInternal.h"

FHyperTwistTrainingCoachActionPlanSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanSummary(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachActionPlanSummary Summary;
	if (UserId.IsEmpty())
	{
		return Summary;
	}

	Summary.UserId = UserId;
	Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);

	const TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans = ListCoachActionPlansForUser(
		RepositoryState,
		UserId
	);
	if (ActionPlans.Num() == 0)
	{
		return Summary;
	}

	Summary.TotalPlanCount = ActionPlans.Num();
	int32 SuggestedModeCounts[5] = {0, 0, 0, 0, 0};
	int32 HandoffKindCounts[4] = {0, 0, 0, 0};
	TMap<FString, int32> FocusLaneCounts;
	TMap<FString, int32> ActionLabelCounts;
	TMap<FString, int32> HandoffSourceCounts;
	int32 CompletedPlanDurationAccumulator = 0;
	int32 CompletedPlanDurationCount = 0;
	float PriorityScoreAccumulator = 0.0f;
	FString LatestOpenPlanUtc;
	FString LatestCompletedUtc;

	for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : ActionPlans)
	{
		PriorityScoreAccumulator += ActionPlan.PriorityScore;
		Summary.TotalEntryCount += ActionPlan.Entries.Num();
		FocusLaneCounts.FindOrAdd(ActionPlan.FocusLaneLabel) += 1;
		ActionLabelCounts.FindOrAdd(ActionPlan.PrimaryActionLabel) += 1;

		const int32 SuggestedModeIndex = static_cast<int32>(ActionPlan.SuggestedMode);
		if (SuggestedModeIndex >= 0 && SuggestedModeIndex < UE_ARRAY_COUNT(SuggestedModeCounts))
		{
			SuggestedModeCounts[SuggestedModeIndex] += 1;
		}
		const int32 HandoffKindIndex = static_cast<int32>(ActionPlan.PreferredCoachHandoffKind);
		if (HandoffKindIndex >= 0 && HandoffKindIndex < UE_ARRAY_COUNT(HandoffKindCounts))
		{
			HandoffKindCounts[HandoffKindIndex] += 1;
		}
		if (!ActionPlan.PreferredCoachHandoffSourceLabel.IsEmpty())
		{
			HandoffSourceCounts.FindOrAdd(ActionPlan.PreferredCoachHandoffSourceLabel) += 1;
		}

		int32 LocalCompletedEntries = 0;
		for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : ActionPlan.Entries)
		{
			if (Entry.bCompleted)
			{
				LocalCompletedEntries += 1;
			}
			if (Entry.bPreferRecognitionReplayReview)
			{
				Summary.bHasRecognitionBiasedPlans = true;
			}
		}

		Summary.CompletedEntryCount += LocalCompletedEntries;
		Summary.OpenEntryCount += ActionPlan.Entries.Num() - LocalCompletedEntries;
		Summary.bHasRecognitionBiasedPlans =
			Summary.bHasRecognitionBiasedPlans || ActionPlan.bPreferRecognitionReplayReview;

		switch (ActionPlan.PlanState)
		{
		case EHyperTwistTrainingCoachActionPlanState::Draft:
			Summary.DraftPlanCount += 1;
			break;
		case EHyperTwistTrainingCoachActionPlanState::Active:
			Summary.ActivePlanCount += 1;
			break;
		case EHyperTwistTrainingCoachActionPlanState::Completed:
			Summary.CompletedPlanCount += 1;
			break;
		case EHyperTwistTrainingCoachActionPlanState::Aborted:
			Summary.AbortedPlanCount += 1;
			break;
		default:
			break;
		}

		if (ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Active
			|| ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Draft)
		{
			const FString SortUtc = !ActionPlan.StartedAtUtc.IsEmpty()
				? ActionPlan.StartedAtUtc
				: ActionPlan.CreatedAtUtc;
			if (Summary.CurrentPlanId.IsEmpty()
				|| HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestOpenPlanUtc) > 0)
			{
				Summary.CurrentPlanId = ActionPlan.PlanId;
				Summary.FocusDeckId = ActionPlan.FocusDeckId;
				Summary.FocusMethodSegmentId = ActionPlan.FocusMethodSegmentId;
				LatestOpenPlanUtc = SortUtc;
			}
		}

		if (ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed
			&& !ActionPlan.CompletedAtUtc.IsEmpty())
		{
			const int32 DurationMs = HyperTwistTrainingRepositoryLibraryInternal::ComputeElapsedMilliseconds(
				ActionPlan.CreatedAtUtc,
				ActionPlan.CompletedAtUtc
			);
			if (DurationMs > 0)
			{
				CompletedPlanDurationAccumulator += DurationMs;
				CompletedPlanDurationCount += 1;
				if (Summary.BestCompletedPlanDurationMs == 0 || DurationMs < Summary.BestCompletedPlanDurationMs)
				{
					Summary.BestCompletedPlanDurationMs = DurationMs;
				}
			}

			if (Summary.LastCompletedPlanId.IsEmpty()
				|| HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(ActionPlan.CompletedAtUtc, LatestCompletedUtc) > 0)
			{
				Summary.LastCompletedPlanId = ActionPlan.PlanId;
				LatestCompletedUtc = ActionPlan.CompletedAtUtc;
				if (Summary.CurrentPlanId.IsEmpty())
				{
					Summary.FocusDeckId = ActionPlan.FocusDeckId;
					Summary.FocusMethodSegmentId = ActionPlan.FocusMethodSegmentId;
				}
			}
		}

		if (ActionPlan.bNeedsCoachReview
			|| ActionPlan.bNeedsCadenceRecovery
			|| ActionPlan.bNeedsVarietyRotation
			|| ActionPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted)
		{
			Summary.bNeedsCoachIntervention = true;
		}
	}

	Summary.AveragePriorityScore = PriorityScoreAccumulator / static_cast<float>(Summary.TotalPlanCount);
	if (CompletedPlanDurationCount > 0)
	{
		Summary.AverageCompletedPlanDurationMs =
			CompletedPlanDurationAccumulator / CompletedPlanDurationCount;
	}

	int32 BestModeCount = 0;
	for (int32 Index = 0; Index < UE_ARRAY_COUNT(SuggestedModeCounts); ++Index)
	{
		if (SuggestedModeCounts[Index] > BestModeCount)
		{
			BestModeCount = SuggestedModeCounts[Index];
			Summary.DominantSuggestedMode = static_cast<EHyperTwistTrainingDeliveryMode>(Index);
		}
	}

	int32 BestHandoffKindCount = 0;
	for (int32 Index = 0; Index < UE_ARRAY_COUNT(HandoffKindCounts); ++Index)
	{
		if (HandoffKindCounts[Index] > BestHandoffKindCount)
		{
			BestHandoffKindCount = HandoffKindCounts[Index];
			Summary.DominantPreferredCoachHandoffKind =
				static_cast<EHyperTwistTrainingCoachHandoffKind>(Index);
		}
	}

	int32 BestFocusLaneCount = 0;
	for (const TPair<FString, int32>& Pair : FocusLaneCounts)
	{
		if (Pair.Value > BestFocusLaneCount)
		{
			BestFocusLaneCount = Pair.Value;
			Summary.DominantFocusLaneLabel = Pair.Key;
		}
	}

	int32 BestActionLabelCount = 0;
	for (const TPair<FString, int32>& Pair : ActionLabelCounts)
	{
		if (Pair.Value > BestActionLabelCount)
		{
			BestActionLabelCount = Pair.Value;
			Summary.DominantActionLabel = Pair.Key;
		}
	}

	int32 BestHandoffSourceCount = 0;
	for (const TPair<FString, int32>& Pair : HandoffSourceCounts)
	{
		if (Pair.Value > BestHandoffSourceCount)
		{
			BestHandoffSourceCount = Pair.Value;
			Summary.DominantPreferredCoachHandoffSourceLabel = Pair.Key;
		}
	}

	Summary.ActionPressureScore =
		static_cast<float>(Summary.ActivePlanCount * 3)
		+ static_cast<float>(Summary.DraftPlanCount * 2)
		+ static_cast<float>(Summary.AbortedPlanCount * 2)
		+ (static_cast<float>(Summary.OpenEntryCount) * 0.5f)
		+ (Summary.bNeedsCoachIntervention ? 5.0f : 0.0f);

	return Summary;
}

FHyperTwistTrainingCoachActionPlanOutcomeSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FHyperTwistTrainingCoachActionPlan& ActionPlan,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachActionPlanOutcomeSummary Summary;
	const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
	if (!NormalizedPlan.IsStructurallyValid())
	{
		return Summary;
	}

	Summary.UserId = NormalizedPlan.UserId;
	Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
		!ReferenceUtc.IsEmpty() ? ReferenceUtc : NormalizedPlan.ReferenceUtc
	);
	Summary.PlanId = NormalizedPlan.PlanId;
	Summary.SourceSessionId = NormalizedPlan.SourceSessionId;
	Summary.FocusDeckId = NormalizedPlan.FocusDeckId;
	Summary.FocusMethodSegmentId = NormalizedPlan.FocusMethodSegmentId;
	Summary.PrimaryActionLabel = NormalizedPlan.PrimaryActionLabel;
	Summary.PlanState = NormalizedPlan.PlanState;
	Summary.SuggestedMode = NormalizedPlan.SuggestedMode;
	Summary.PreferredCoachHandoffKind = NormalizedPlan.PreferredCoachHandoffKind;
	Summary.PreferredCoachHandoffSourceLabel = NormalizedPlan.PreferredCoachHandoffSourceLabel;
	Summary.RequestedLaunchCaseCount = NormalizedPlan.RequestedLaunchCaseCount;
	Summary.PlannedLaunchCaseCount = NormalizedPlan.PlannedLaunchCaseCount;
	Summary.bBroadenedLaunchBudget = NormalizedPlan.bBroadenedLaunchBudget;
	Summary.bTightenedLaunchBudget = NormalizedPlan.bTightenedLaunchBudget;
	Summary.LaunchBudgetReasonLine = NormalizedPlan.LaunchBudgetReasonLine;
	Summary.DeferredLaunchCaseIds = NormalizedPlan.DeferredLaunchCaseIds;

	TSet<FString> FocusCaseIds;
	for (const FString& FocusCaseId : NormalizedPlan.FocusCaseIds)
	{
		if (!FocusCaseId.IsEmpty())
		{
			FocusCaseIds.Add(FocusCaseId);
		}
	}

	TSet<FString> CompletedCaseIds;
	for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : NormalizedPlan.Entries)
	{
		if (!Entry.CaseId.IsEmpty() && FocusCaseIds.Num() == 0)
		{
			FocusCaseIds.Add(Entry.CaseId);
		}

		if (Entry.bCompleted && !Entry.CaseId.IsEmpty())
		{
			CompletedCaseIds.Add(Entry.CaseId);
		}
	}

	Summary.FocusCaseCount = FocusCaseIds.Num();

	FHyperTwistTrainingRunRecord RunRecord;
	if (!NormalizedPlan.SourceSessionId.IsEmpty()
		&& TryGetRunRecord(RepositoryState, NormalizedPlan.SourceSessionId, RunRecord))
	{
		TSet<FString> TouchedCaseIds;
		int32 TotalTimeAccumulator = 0;
		int32 TotalTimeSampleCount = 0;
		int32 RecognitionTimeAccumulator = 0;
		int32 RecognitionTimeSampleCount = 0;

		for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory)
		{
			if (FocusCaseIds.Num() > 0 && !FocusCaseIds.Contains(AttemptHistoryEntry.CaseId))
			{
				continue;
			}

			Summary.AttemptCount += 1;
			TouchedCaseIds.Add(AttemptHistoryEntry.CaseId);

			if (!AttemptHistoryEntry.ReplayId.IsEmpty())
			{
				Summary.ReplayBackedAttemptCount += 1;
			}

			if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted
				|| AttemptHistoryEntry.RecognitionTimeMs > 0)
			{
				Summary.RecognitionAttemptCount += 1;
			}

			if (AttemptHistoryEntry.TotalTimeMs > 0)
			{
				TotalTimeAccumulator += AttemptHistoryEntry.TotalTimeMs;
				TotalTimeSampleCount += 1;
				if (Summary.BestTotalTimeMs == 0 || AttemptHistoryEntry.TotalTimeMs < Summary.BestTotalTimeMs)
				{
					Summary.BestTotalTimeMs = AttemptHistoryEntry.TotalTimeMs;
				}
			}

			if (AttemptHistoryEntry.RecognitionTimeMs > 0)
			{
				RecognitionTimeAccumulator += AttemptHistoryEntry.RecognitionTimeMs;
				RecognitionTimeSampleCount += 1;
			}

			switch (AttemptHistoryEntry.Result)
			{
			case EHyperTwistTrainingAttemptResult::Success:
				Summary.SuccessCount += 1;
				if (!AttemptHistoryEntry.CaseId.IsEmpty())
				{
					CompletedCaseIds.Add(AttemptHistoryEntry.CaseId);
				}
				break;

			case EHyperTwistTrainingAttemptResult::Failure:
				Summary.FailureCount += 1;
				break;

			case EHyperTwistTrainingAttemptResult::Timeout:
				Summary.TimeoutCount += 1;
				break;

			case EHyperTwistTrainingAttemptResult::DNF:
				Summary.DNFCount += 1;
				break;

			case EHyperTwistTrainingAttemptResult::Aborted:
				Summary.AbortedCount += 1;
				break;

			default:
				break;
			}
		}

		Summary.TouchedFocusCaseCount = TouchedCaseIds.Num();
		if (TotalTimeSampleCount > 0)
		{
			Summary.AverageTotalTimeMs = TotalTimeAccumulator / TotalTimeSampleCount;
		}
		if (RecognitionTimeSampleCount > 0)
		{
			Summary.AverageRecognitionTimeMs = RecognitionTimeAccumulator / RecognitionTimeSampleCount;
		}
	}

	Summary.CompletedFocusCaseCount = CompletedCaseIds.Num();
	Summary.UntouchedFocusCaseCount = FMath::Max(0, Summary.FocusCaseCount - Summary.TouchedFocusCaseCount);
	for (const FString& FocusCaseId : NormalizedPlan.FocusCaseIds)
	{
		if (!FocusCaseId.IsEmpty() && !CompletedCaseIds.Contains(FocusCaseId))
		{
			Summary.FollowUpCaseIds.Add(FocusCaseId);
		}
	}
	if (Summary.FollowUpCaseIds.Num() == 0)
	{
		for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : NormalizedPlan.Entries)
		{
			if (!Entry.CaseId.IsEmpty()
				&& !CompletedCaseIds.Contains(Entry.CaseId)
				&& !Summary.FollowUpCaseIds.Contains(Entry.CaseId))
			{
				Summary.FollowUpCaseIds.Add(Entry.CaseId);
			}
		}
	}
	if (Summary.FocusCaseCount > 0)
	{
		Summary.CompletionRate = static_cast<float>(Summary.CompletedFocusCaseCount)
			/ static_cast<float>(Summary.FocusCaseCount);
	}

	const FString PlanStartUtc = !NormalizedPlan.StartedAtUtc.IsEmpty()
		? NormalizedPlan.StartedAtUtc
		: NormalizedPlan.CreatedAtUtc;
	const FString PlanEndUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
		? NormalizedPlan.CompletedAtUtc
		: Summary.ReferenceUtc;
	if (!PlanStartUtc.IsEmpty() && !PlanEndUtc.IsEmpty())
	{
		Summary.PlanDurationMs = HyperTwistTrainingRepositoryLibraryInternal::ComputeElapsedMilliseconds(
			PlanStartUtc,
			PlanEndUtc
		);
	}

	const bool bPlanTerminal =
		NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed
		|| NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted;
	Summary.bMetPrimaryGoal =
		bPlanTerminal
		&& NormalizedPlan.PlanState != EHyperTwistTrainingCoachActionPlanState::Aborted
		&& (Summary.FocusCaseCount == 0
			? Summary.SuccessCount > 0
			: Summary.CompletedFocusCaseCount >= Summary.FocusCaseCount);

	Summary.OutcomeScore =
		(Summary.CompletionRate * 100.0f)
		+ (static_cast<float>(Summary.SuccessCount) * 4.0f)
		- (static_cast<float>(Summary.FailureCount) * 2.0f)
		- (static_cast<float>(Summary.TimeoutCount) * 3.0f)
		- (static_cast<float>(Summary.DNFCount) * 4.0f)
		- (static_cast<float>(Summary.AbortedCount) * 5.0f)
		- (static_cast<float>(Summary.UntouchedFocusCaseCount) * 4.0f)
		+ (static_cast<float>(Summary.ReplayBackedAttemptCount) * 0.5f);

	Summary.bNeedsFollowUpPlan =
		Summary.FollowUpCaseIds.Num() > 0
		|| Summary.DeferredLaunchCaseIds.Num() > 0
		|| (
			bPlanTerminal
			&& (
				NormalizedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Aborted
				|| !Summary.bMetPrimaryGoal
				|| Summary.FailureCount > 0
				|| Summary.TimeoutCount > 0
				|| Summary.DNFCount > 0
				|| (NormalizedPlan.bNeedsCoachReview && Summary.ReplayBackedAttemptCount == 0)
			)
		);

	return Summary;
}

FHyperTwistTrainingCoachActionSequenceSummary UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachActionSequenceSummary Summary;
	const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
	if (!NormalizedAnchorPlan.IsStructurallyValid())
	{
		return Summary;
	}

	const FString RootPlanId = !NormalizedAnchorPlan.RootPlanId.IsEmpty()
		? NormalizedAnchorPlan.RootPlanId
		: NormalizedAnchorPlan.PlanId;
	Summary.UserId = NormalizedAnchorPlan.UserId;
	Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
		!ReferenceUtc.IsEmpty() ? ReferenceUtc : NormalizedAnchorPlan.ReferenceUtc
	);
	Summary.RootPlanId = RootPlanId;

	const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(
		RepositoryState,
		NormalizedAnchorPlan.UserId
	);
	TArray<FHyperTwistTrainingCoachActionPlan> SequencePlans;
	for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
	{
		const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
			HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
		const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
			? NormalizedPlan.RootPlanId
			: NormalizedPlan.PlanId;
		if (CandidateRootPlanId == RootPlanId
			|| NormalizedPlan.PlanId == RootPlanId
			|| NormalizedPlan.ParentPlanId == RootPlanId)
		{
			SequencePlans.Add(NormalizedPlan);
		}
	}
	if (SequencePlans.Num() == 0)
	{
		SequencePlans.Add(NormalizedAnchorPlan);
	}
	const auto ResolvePlanSortUtc = [](const FHyperTwistTrainingCoachActionPlan& Plan) -> FString
	{
		return !Plan.CompletedAtUtc.IsEmpty()
			? Plan.CompletedAtUtc
			: (!Plan.StartedAtUtc.IsEmpty() ? Plan.StartedAtUtc : Plan.CreatedAtUtc);
	};
	SequencePlans.Sort(
		[&ResolvePlanSortUtc](const FHyperTwistTrainingCoachActionPlan& Left,
			const FHyperTwistTrainingCoachActionPlan& Right)
		{
			const FString LeftSortUtc = ResolvePlanSortUtc(Left);
			const FString RightSortUtc = ResolvePlanSortUtc(Right);
			const int32 CompareResult =
				HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(LeftSortUtc, RightSortUtc);
			if (CompareResult != 0)
			{
				return CompareResult < 0;
			}
			if (Left.FollowUpDepth != Right.FollowUpDepth)
			{
				return Left.FollowUpDepth < Right.FollowUpDepth;
			}

			return Left.PlanId < Right.PlanId;
		}
	);

	FString LatestSortUtc;
	TSet<FString> OutstandingFollowUpCases;
	float CompletionRateAccumulator = 0.0f;
	float OutcomeScoreAccumulator = 0.0f;
	bool bHasGenericFollowUpPressure = false;

	for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans)
	{
		Summary.PlanCount += 1;
		Summary.CarryForwardPlanCount += Plan.FollowUpDepth > 0 ? 1 : 0;
		Summary.MaxFollowUpDepth = FMath::Max(Summary.MaxFollowUpDepth, Plan.FollowUpDepth);

		switch (Plan.PlanState)
		{
		case EHyperTwistTrainingCoachActionPlanState::Active:
			Summary.ActivePlanCount += 1;
			Summary.bHasOpenPlan = true;
			break;
		case EHyperTwistTrainingCoachActionPlanState::Completed:
			Summary.CompletedPlanCount += 1;
			break;
		case EHyperTwistTrainingCoachActionPlanState::Aborted:
			Summary.AbortedPlanCount += 1;
			break;
		default:
			Summary.bHasOpenPlan = true;
			break;
		}

		const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary =
			DeriveCoachActionPlanOutcomeSummary(RepositoryState, Plan, Summary.ReferenceUtc);
		Summary.TotalFocusCaseCount +=
			OutcomeSummary.FocusCaseCount + OutcomeSummary.DeferredLaunchCaseIds.Num();
		CompletionRateAccumulator += OutcomeSummary.CompletionRate;
		OutcomeScoreAccumulator += OutcomeSummary.OutcomeScore;
		TSet<FString> PlanFocusCaseIds;
		for (const FString& FocusCaseId : Plan.FocusCaseIds)
		{
			if (!FocusCaseId.IsEmpty())
			{
				PlanFocusCaseIds.Add(FocusCaseId);
			}
		}
		for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : Plan.Entries)
		{
			if (!Entry.CaseId.IsEmpty())
			{
				PlanFocusCaseIds.Add(Entry.CaseId);
			}
		}
		TSet<FString> PlanOutstandingCaseIds;
		for (const FString& CaseId : OutcomeSummary.FollowUpCaseIds)
		{
			if (!CaseId.IsEmpty())
			{
				PlanOutstandingCaseIds.Add(CaseId);
				OutstandingFollowUpCases.Add(CaseId);
			}
		}
		for (const FString& CaseId : OutcomeSummary.DeferredLaunchCaseIds)
		{
			if (!CaseId.IsEmpty())
			{
				PlanOutstandingCaseIds.Add(CaseId);
				OutstandingFollowUpCases.Add(CaseId);
			}
		}
		for (const FString& FocusCaseId : PlanFocusCaseIds)
		{
			if (!PlanOutstandingCaseIds.Contains(FocusCaseId))
			{
				OutstandingFollowUpCases.Remove(FocusCaseId);
			}
		}
		bHasGenericFollowUpPressure =
			OutcomeSummary.bNeedsFollowUpPlan && PlanOutstandingCaseIds.Num() == 0;

		const FString SortUtc = ResolvePlanSortUtc(Plan);
		if (Summary.LatestPlanId.IsEmpty()
			|| HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestSortUtc) > 0
			|| (HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, LatestSortUtc) == 0
				&& Plan.FollowUpDepth >= Summary.MaxFollowUpDepth))
		{
			Summary.LatestPlanId = Plan.PlanId;
			Summary.FocusDeckId = Plan.FocusDeckId;
			Summary.FocusMethodSegmentId = Plan.FocusMethodSegmentId;
			Summary.LatestOutcomeScore = OutcomeSummary.OutcomeScore;
			LatestSortUtc = SortUtc;
		}
	}

	if (Summary.PlanCount > 0)
	{
		Summary.AverageCompletionRate = CompletionRateAccumulator / static_cast<float>(Summary.PlanCount);
		Summary.AverageOutcomeScore = OutcomeScoreAccumulator / static_cast<float>(Summary.PlanCount);
	}
	Summary.UnresolvedFollowUpCaseCount = OutstandingFollowUpCases.Num();
	Summary.bNeedsMoreFollowUp =
		Summary.UnresolvedFollowUpCaseCount > 0 || bHasGenericFollowUpPressure || Summary.bHasOpenPlan;

	return Summary;
}
