// Copyright HyperTwist, Inc. All Rights Reserved.

#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"

#include "HyperTwistTrainingRepositoryLibraryInternal.h"

namespace HyperTwistTrainingRepositoryLibraryInternal
{
	FHyperTwistTrainingCoachActionClosureSummary DeriveCoachActionClosureSummaryWithCoachMemory(
		const FHyperTwistTrainingRepositoryState& RepositoryState,
		const FHyperTwistTrainingCoachActionPlan& AnchorPlan,
		const FString& ReferenceUtc,
		const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot
	)
	{
		FHyperTwistTrainingCoachActionClosureSummary Summary;
		const FHyperTwistTrainingCoachActionPlan NormalizedAnchorPlan =
			HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(AnchorPlan);
		if (!NormalizedAnchorPlan.IsStructurallyValid())
		{
			return Summary;
		}

		const FHyperTwistTrainingCoachActionSequenceSummary SequenceSummary =
			UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionSequenceSummary(
				RepositoryState,
				NormalizedAnchorPlan,
				ReferenceUtc
			);
		if (!SequenceSummary.IsStructurallyValid())
		{
			return Summary;
		}

		Summary.UserId = SequenceSummary.UserId;
		Summary.ReferenceUtc = SequenceSummary.ReferenceUtc;
		Summary.RootPlanId = SequenceSummary.RootPlanId;
		Summary.LatestPlanId = SequenceSummary.LatestPlanId;
		Summary.FocusDeckId = SequenceSummary.FocusDeckId;
		Summary.FocusMethodSegmentId = SequenceSummary.FocusMethodSegmentId;

		TArray<FHyperTwistTrainingCoachActionPlan> SequencePlans;
		const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans =
			UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
				RepositoryState,
				NormalizedAnchorPlan.UserId
			);
		for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
		{
			const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
				HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
			const FString CandidateRootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
				? NormalizedPlan.RootPlanId
				: NormalizedPlan.PlanId;
			if (CandidateRootPlanId == SequenceSummary.RootPlanId
				|| NormalizedPlan.PlanId == SequenceSummary.RootPlanId
				|| NormalizedPlan.ParentPlanId == SequenceSummary.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;
			}
		);

		TSet<FString> AllFocusCaseIds;
		TSet<FString> ResolvedCaseIds;
		TSet<FString> UnresolvedCaseIds;
		TMap<FString, int32> CaseAttemptCounts;
		TMap<FString, int32> CaseNonSuccessCounts;
		TMap<FString, int32> CaseSuccessCounts;
		bool bAnyRecognition = false;

		for (const FHyperTwistTrainingCoachActionPlan& Plan : SequencePlans)
		{
			const FHyperTwistTrainingCoachActionPlanOutcomeSummary OutcomeSummary =
				UHyperTwistTrainingRepositoryLibrary::DeriveCoachActionPlanOutcomeSummary(
					RepositoryState,
					Plan,
					SequenceSummary.ReferenceUtc
				);
			TSet<FString> PlanFocusCaseIds;
			for (const FString& FocusCaseId : Plan.FocusCaseIds)
			{
				if (!FocusCaseId.IsEmpty())
				{
					PlanFocusCaseIds.Add(FocusCaseId);
					AllFocusCaseIds.Add(FocusCaseId);
				}
			}
			for (const FHyperTwistTrainingCoachActionPlanEntry& Entry : Plan.Entries)
			{
				if (!Entry.CaseId.IsEmpty())
				{
					PlanFocusCaseIds.Add(Entry.CaseId);
					AllFocusCaseIds.Add(Entry.CaseId);
				}
			}

			TSet<FString> PlanOutstandingCaseIds;
			for (const FString& FollowUpCaseId : OutcomeSummary.FollowUpCaseIds)
			{
				if (!FollowUpCaseId.IsEmpty())
				{
					PlanOutstandingCaseIds.Add(FollowUpCaseId);
					UnresolvedCaseIds.Add(FollowUpCaseId);
					ResolvedCaseIds.Remove(FollowUpCaseId);
				}
			}
			for (const FString& DeferredCaseId : OutcomeSummary.DeferredLaunchCaseIds)
			{
				if (!DeferredCaseId.IsEmpty())
				{
					PlanOutstandingCaseIds.Add(DeferredCaseId);
					AllFocusCaseIds.Add(DeferredCaseId);
					UnresolvedCaseIds.Add(DeferredCaseId);
					ResolvedCaseIds.Remove(DeferredCaseId);
				}
			}

			for (const FString& FocusCaseId : PlanFocusCaseIds)
			{
				if (PlanOutstandingCaseIds.Contains(FocusCaseId))
				{
					UnresolvedCaseIds.Add(FocusCaseId);
					ResolvedCaseIds.Remove(FocusCaseId);
				}
				else
				{
					UnresolvedCaseIds.Remove(FocusCaseId);
					ResolvedCaseIds.Add(FocusCaseId);
				}
			}

			FHyperTwistTrainingRunRecord RunRecord;
			if (!Plan.SourceSessionId.IsEmpty()
				&& UHyperTwistTrainingRepositoryLibrary::TryGetRunRecord(
					RepositoryState,
					Plan.SourceSessionId,
					RunRecord
				))
			{
				for (const FHyperTwistTrainingAttemptHistoryEntry& AttemptHistoryEntry : RunRecord.AttemptHistory)
				{
					if (!AllFocusCaseIds.Contains(AttemptHistoryEntry.CaseId))
					{
						continue;
					}

					Summary.TotalAttemptCount += 1;
					CaseAttemptCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
					if (AttemptHistoryEntry.DeliveryMode == EHyperTwistTrainingDeliveryMode::RecognitionAssisted
						|| AttemptHistoryEntry.RecognitionTimeMs > 0)
					{
						bAnyRecognition = true;
					}

					if (AttemptHistoryEntry.Result == EHyperTwistTrainingAttemptResult::Success)
					{
						CaseSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
					}
					else
					{
						CaseNonSuccessCounts.FindOrAdd(AttemptHistoryEntry.CaseId) += 1;
					}
				}
			}
		}

		for (const FString& UnresolvedCaseId : UnresolvedCaseIds)
		{
			ResolvedCaseIds.Remove(UnresolvedCaseId);
		}

		TSet<FString> PersistentWeakCaseIds;
		for (const TPair<FString, int32>& Pair : CaseNonSuccessCounts)
		{
			const int32 SuccessCount = CaseSuccessCounts.FindRef(Pair.Key);
			if (Pair.Value >= 2
				|| (UnresolvedCaseIds.Contains(Pair.Key) && Pair.Value >= 1)
				|| (SuccessCount == 0 && CaseAttemptCounts.FindRef(Pair.Key) >= 2))
			{
				PersistentWeakCaseIds.Add(Pair.Key);
			}
		}

		Summary.ResolvedCaseIds = ResolvedCaseIds.Array();
		Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
		Summary.UnresolvedCaseIds = UnresolvedCaseIds.Array();
		Summary.ResolvedCaseIds.Sort();
		Summary.PersistentWeakCaseIds.Sort();
		Summary.UnresolvedCaseIds.Sort();
		Summary.ResolvedCaseCount = Summary.ResolvedCaseIds.Num();
		Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
		Summary.UnresolvedCaseCount = Summary.UnresolvedCaseIds.Num();
		Summary.RepeatedFailureCaseCount = Summary.PersistentWeakCaseCount;

		Summary.bSequenceClosed =
			!SequenceSummary.bHasOpenPlan
			&& !SequenceSummary.bNeedsMoreFollowUp
			&& Summary.UnresolvedCaseCount == 0
			&& SequenceSummary.CompletedPlanCount > 0
			&& SequenceSummary.AbortedPlanCount == 0;
		Summary.bNeedsEscalation =
			SequenceSummary.AbortedPlanCount > 0
			|| (SequenceSummary.MaxFollowUpDepth >= 2 && Summary.UnresolvedCaseCount > 0)
			|| Summary.PersistentWeakCaseCount >= 2
			|| SequenceSummary.AverageOutcomeScore < 30.0f;
		const bool bPrefersFollowUpLane =
			CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::FollowUp
			|| CoachMemorySnapshot.FollowUpGuidanceContinueBias > CoachMemorySnapshot.RecommendedGuidanceContinueBias;
		const bool bPrefersRecommendedLane =
			CoachMemorySnapshot.PreferredGuidanceLane == EHyperTwistTrainingCoachGuidanceLane::Recommended
			|| CoachMemorySnapshot.RecommendedGuidanceContinueBias > CoachMemorySnapshot.FollowUpGuidanceContinueBias;
		const bool bHasRawFollowUpPressure =
			Summary.UnresolvedCaseCount > 0 || Summary.PersistentWeakCaseCount > 0;
		Summary.bSpawnFollowUpFromSuppressionMemory =
			!bHasRawFollowUpPressure
			&& SequenceSummary.bNeedsMoreFollowUp
			&& SequenceSummary.CompletedPlanCount > 0
			&& !Summary.bNeedsEscalation
			&& bPrefersFollowUpLane
			&& CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount
				>= CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount;
		Summary.bSuppressedBorderlineFollowUpByRecoveryHistory =
			bHasRawFollowUpPressure
			&& Summary.PersistentWeakCaseCount == 0
			&& Summary.UnresolvedCaseCount <= 1
			&& !SequenceSummary.bNeedsMoreFollowUp
			&& !Summary.bNeedsEscalation
			&& bPrefersRecommendedLane
			&& CoachMemorySnapshot.SuppressedFollowUpQueuePressureCount
				>= CoachMemorySnapshot.SuppressedPrimaryQueuePressureCount + 2;
		Summary.bShouldSpawnFollowUp =
			!Summary.bSequenceClosed
			&& !SequenceSummary.bHasOpenPlan
			&& !Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
			&& (bHasRawFollowUpPressure || Summary.bSpawnFollowUpFromSuppressionMemory);
		Summary.bShouldArchiveResolvedCases =
			Summary.ResolvedCaseCount > 0
			&& (Summary.bSequenceClosed || (!Summary.bShouldSpawnFollowUp && Summary.PersistentWeakCaseCount == 0));

		if (Summary.bSequenceClosed)
		{
			Summary.ClosureLabel = TEXT("sequence-closed");
			Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
		}
		else if (Summary.bNeedsEscalation)
		{
			Summary.ClosureLabel = TEXT("needs-escalation");
			Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
		}
		else if (Summary.bShouldSpawnFollowUp)
		{
			Summary.ClosureLabel = Summary.bSpawnFollowUpFromSuppressionMemory
				? TEXT("follow-up-memory-ready")
				: TEXT("follow-up-ready");
			Summary.RecommendedNextMode = bAnyRecognition
				? EHyperTwistTrainingDeliveryMode::RecognitionAssisted
				: EHyperTwistTrainingDeliveryMode::VirtualCube;
		}
		else if (SequenceSummary.bHasOpenPlan)
		{
			Summary.ClosureLabel = TEXT("open-active");
			Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::CoachReviewed;
		}
		else
		{
			Summary.ClosureLabel = Summary.bSuppressedBorderlineFollowUpByRecoveryHistory
				? TEXT("recommended-recovery-stable")
				: TEXT("sequence-stable");
			Summary.RecommendedNextMode = EHyperTwistTrainingDeliveryMode::Timer;
		}

		Summary.ClosureScore = FMath::Clamp(
			SequenceSummary.AverageOutcomeScore
			+ (static_cast<float>(Summary.ResolvedCaseCount) * 6.0f)
			- (static_cast<float>(Summary.PersistentWeakCaseCount) * 8.0f)
			- (static_cast<float>(Summary.UnresolvedCaseCount) * 10.0f)
			- (Summary.bNeedsEscalation ? 20.0f : 0.0f)
			- (Summary.bSpawnFollowUpFromSuppressionMemory ? 6.0f : 0.0f)
			+ (Summary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 4.0f : 0.0f),
			0.0f,
			100.0f
		);

		return Summary;
	}
}

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

	const FString ResolvedReferenceUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
	const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
		DeriveCoachMemorySnapshot(RepositoryState, NormalizedAnchorPlan.UserId, ResolvedReferenceUtc);
	return HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
		RepositoryState,
		NormalizedAnchorPlan,
		ResolvedReferenceUtc,
		CoachMemorySnapshot
	);
}

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

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

	const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
	TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
	TMap<FString, FString> LatestSortUtcByRootId;

	for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
	{
		const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
			HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
		if (!NormalizedPlan.IsStructurallyValid())
		{
			continue;
		}

		const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
			? NormalizedPlan.RootPlanId
			: NormalizedPlan.PlanId;
		const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
			? NormalizedPlan.CompletedAtUtc
			: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);

		FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
		FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
		if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
		{
			LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
			LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
			continue;
		}

		const int32 CompareResult =
			HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
		if (CompareResult > 0
			|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
		{
			*ExistingPlan = NormalizedPlan;
			*ExistingSortUtc = SortUtc;
		}
	}

	float ClosureScoreAccumulator = 0.0f;
	int32 FocusPressureScore = -1;
	FString FocusSortUtc;
	TSet<FString> ArchivedResolvedCaseIds;
	TSet<FString> PersistentWeakCaseIds;
	TSet<FString> EscalationCaseIds;
	const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
		HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
			RepositoryState,
			UserId,
			Summary.ReferenceUtc
		);

	for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
	{
		const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
			HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
				RepositoryState,
				Pair.Value,
				Summary.ReferenceUtc,
				CoachMemorySnapshot
			);
		if (!ClosureSummary.IsStructurallyValid())
		{
			continue;
		}

		Summary.SequenceCount += 1;
		Summary.ClosedSequenceCount += ClosureSummary.bSequenceClosed ? 1 : 0;
		Summary.EscalatedSequenceCount += ClosureSummary.bNeedsEscalation ? 1 : 0;
		Summary.FollowUpReadySequenceCount += ClosureSummary.bShouldSpawnFollowUp ? 1 : 0;
		Summary.RecoverySustainedFollowUpSequenceCount +=
			ClosureSummary.bSpawnFollowUpFromSuppressionMemory ? 1 : 0;
		Summary.RecoverySuppressedBorderlineFollowUpSequenceCount +=
			ClosureSummary.bSuppressedBorderlineFollowUpByRecoveryHistory ? 1 : 0;
		ClosureScoreAccumulator += ClosureSummary.ClosureScore;

		if (ClosureSummary.bShouldArchiveResolvedCases)
		{
			for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
			{
				if (!CaseId.IsEmpty())
				{
					ArchivedResolvedCaseIds.Add(CaseId);
				}
			}
		}
		for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
		{
			if (!CaseId.IsEmpty())
			{
				PersistentWeakCaseIds.Add(CaseId);
			}
		}
		if (ClosureSummary.bNeedsEscalation)
		{
			for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
			{
				if (!CaseId.IsEmpty())
				{
					EscalationCaseIds.Add(CaseId);
				}
			}
			for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
			{
				if (!CaseId.IsEmpty())
				{
					EscalationCaseIds.Add(CaseId);
				}
			}
		}

		const int32 CurrentPressureScore =
			(ClosureSummary.bNeedsEscalation ? 300 : 0)
			+ (ClosureSummary.bShouldSpawnFollowUp ? 200 : 0)
			+ (ClosureSummary.PersistentWeakCaseCount * 10)
			+ ClosureSummary.UnresolvedCaseCount;
		const FString SortUtc = !ClosureSummary.ReferenceUtc.IsEmpty()
			? ClosureSummary.ReferenceUtc
			: Summary.ReferenceUtc;
		const int32 TimeCompare = FocusSortUtc.IsEmpty()
			? 1
			: HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, FocusSortUtc);
		if (CurrentPressureScore > FocusPressureScore
			|| (CurrentPressureScore == FocusPressureScore && TimeCompare > 0))
		{
			FocusPressureScore = CurrentPressureScore;
			FocusSortUtc = SortUtc;
			Summary.FocusRootPlanId = ClosureSummary.RootPlanId;
			Summary.FocusDeckId = ClosureSummary.FocusDeckId;
			Summary.FocusMethodSegmentId = ClosureSummary.FocusMethodSegmentId;
			Summary.FocusClosureLabel = ClosureSummary.ClosureLabel;
		}
	}

	if (Summary.SequenceCount == 0)
	{
		return FHyperTwistTrainingCoachClosureMemorySummary();
	}

	Summary.ArchivedResolvedCaseIds = ArchivedResolvedCaseIds.Array();
	Summary.PersistentWeakCaseIds = PersistentWeakCaseIds.Array();
	Summary.EscalationCaseIds = EscalationCaseIds.Array();
	Summary.ArchivedResolvedCaseIds.Sort();
	Summary.PersistentWeakCaseIds.Sort();
	Summary.EscalationCaseIds.Sort();
	Summary.ArchivedResolvedCaseCount = Summary.ArchivedResolvedCaseIds.Num();
	Summary.PersistentWeakCaseCount = Summary.PersistentWeakCaseIds.Num();
	Summary.AverageClosureScore = ClosureScoreAccumulator / static_cast<float>(Summary.SequenceCount);
	Summary.bHasEscalationPressure = Summary.EscalatedSequenceCount > 0 || Summary.EscalationCaseIds.Num() > 0;
	Summary.bHasFollowUpPressure = Summary.FollowUpReadySequenceCount > 0;

	return Summary;
}

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

	const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
		DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
	const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
		DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
	if (!ClosureMemory.IsStructurallyValid() && !RecommendationHistory.IsStructurallyValid())
	{
		return Summary;
	}

	Summary.UserId = UserId;
	Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
	Summary.FocusDeckId = !RecommendationHistory.FocusDeckId.IsEmpty()
		? RecommendationHistory.FocusDeckId
		: ClosureMemory.FocusDeckId;
	Summary.FocusMethodSegmentId = !RecommendationHistory.FocusMethodSegmentId.IsEmpty()
		? RecommendationHistory.FocusMethodSegmentId
		: ClosureMemory.FocusMethodSegmentId;
	Summary.DominantSuggestedMode = RecommendationHistory.IsStructurallyValid()
		? RecommendationHistory.DominantSuggestedMode
		: EHyperTwistTrainingDeliveryMode::Timer;
	Summary.DominantSelectionPolicy = RecommendationHistory.IsStructurallyValid()
		? RecommendationHistory.DominantSelectionPolicy
		: EHyperTwistTrainingSelectionPolicy::Weighted;

	TSet<FString> RecurringCarryForwardCaseIds;
	TSet<FString> EscalationCarryForwardCaseIds;
	TSet<FString> PinnedCarryForwardCaseIds;
	TSet<FString> SuppressedResolvedCaseIds;

	for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			RecurringCarryForwardCaseIds.Add(CaseId);
			PinnedCarryForwardCaseIds.Add(CaseId);
		}
	}
	for (const FString& CaseId : ClosureMemory.EscalationCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			EscalationCarryForwardCaseIds.Add(CaseId);
			PinnedCarryForwardCaseIds.Add(CaseId);
		}
	}
	for (const FString& CaseId : RecommendationHistory.EscalationCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			EscalationCarryForwardCaseIds.Add(CaseId);
			PinnedCarryForwardCaseIds.Add(CaseId);
		}
	}
	for (const FString& CaseId : ClosureMemory.PersistentWeakCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			PinnedCarryForwardCaseIds.Add(CaseId);
		}
	}
	for (const FString& CaseId : ClosureMemory.ArchivedResolvedCaseIds)
	{
		if (!CaseId.IsEmpty() && !PinnedCarryForwardCaseIds.Contains(CaseId))
		{
			SuppressedResolvedCaseIds.Add(CaseId);
		}
	}

	Summary.RecurringCarryForwardCaseIds = RecurringCarryForwardCaseIds.Array();
	Summary.EscalationCarryForwardCaseIds = EscalationCarryForwardCaseIds.Array();
	Summary.PinnedCarryForwardCaseIds = PinnedCarryForwardCaseIds.Array();
	Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
	Summary.RecurringCarryForwardCaseIds.Sort();
	Summary.EscalationCarryForwardCaseIds.Sort();
	Summary.PinnedCarryForwardCaseIds.Sort();
	Summary.SuppressedResolvedCaseIds.Sort();
	Summary.RecurringCarryForwardCaseCount = Summary.RecurringCarryForwardCaseIds.Num();
	Summary.EscalationCarryForwardCaseCount = Summary.EscalationCarryForwardCaseIds.Num();
	Summary.PinnedCarryForwardCaseCount = Summary.PinnedCarryForwardCaseIds.Num();
	Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();

	Summary.bSuppressResolvedResurfacing = Summary.SuppressedResolvedCaseCount > 0;
	Summary.bPinRecurringCases = Summary.PinnedCarryForwardCaseCount > 0;
	Summary.bNeedsEscalationHandOff =
		ClosureMemory.bHasEscalationPressure || Summary.EscalationCarryForwardCaseCount > 0;
	Summary.bBiasCoachReviewed =
		Summary.bNeedsEscalationHandOff
		|| RecommendationHistory.bShouldBiasCoachReviewed
		|| (Summary.PinnedCarryForwardCaseCount > 0
			&& Summary.DominantSuggestedMode == EHyperTwistTrainingDeliveryMode::CoachReviewed);
	Summary.bHasActiveCarryForwardPressure =
		Summary.bNeedsEscalationHandOff
		|| Summary.bPinRecurringCases
		|| RecommendationHistory.bHasRepeatRecommendationPressure
		|| ClosureMemory.bHasFollowUpPressure;

	Summary.CarryForwardIntensityScore = FMath::Clamp(
		(static_cast<float>(Summary.EscalationCarryForwardCaseCount) * 25.0f)
		+ (static_cast<float>(Summary.RecurringCarryForwardCaseCount) * 12.0f)
		+ (static_cast<float>(RecommendationHistory.RepeatRecommendationSequenceCount) * 8.0f)
		+ (ClosureMemory.bHasFollowUpPressure ? 10.0f : 0.0f)
		- (static_cast<float>(Summary.SuppressedResolvedCaseCount) * 2.0f),
		0.0f,
		100.0f
	);

	return Summary;
}

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

	const FHyperTwistTrainingCoachClosureMemorySummary ClosureMemory =
		DeriveCoachClosureMemorySummary(RepositoryState, UserId, ReferenceUtc);
	const FHyperTwistTrainingCoachRecommendationHistorySummary RecommendationHistory =
		DeriveCoachRecommendationHistorySummary(RepositoryState, UserId, ReferenceUtc);
	const FHyperTwistTrainingCoachCarryForwardPolicySummary CarryForwardPolicy =
		DeriveCoachCarryForwardPolicySummary(RepositoryState, UserId, ReferenceUtc);
	if (!ClosureMemory.IsStructurallyValid()
		&& !RecommendationHistory.IsStructurallyValid()
		&& !CarryForwardPolicy.IsStructurallyValid())
	{
		return Summary;
	}

	Summary.UserId = UserId;
	Summary.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(ReferenceUtc);
	Summary.FocusDeckId = CarryForwardPolicy.IsStructurallyValid()
		? CarryForwardPolicy.FocusDeckId
		: (!RecommendationHistory.FocusDeckId.IsEmpty() ? RecommendationHistory.FocusDeckId : ClosureMemory.FocusDeckId);
	Summary.FocusMethodSegmentId = CarryForwardPolicy.IsStructurallyValid()
		? CarryForwardPolicy.FocusMethodSegmentId
		: (!RecommendationHistory.FocusMethodSegmentId.IsEmpty()
			? RecommendationHistory.FocusMethodSegmentId
			: ClosureMemory.FocusMethodSegmentId);
	Summary.DominantSuggestedMode = CarryForwardPolicy.IsStructurallyValid()
		? CarryForwardPolicy.DominantSuggestedMode
		: RecommendationHistory.DominantSuggestedMode;
	Summary.DominantSelectionPolicy = CarryForwardPolicy.IsStructurallyValid()
		? CarryForwardPolicy.DominantSelectionPolicy
		: RecommendationHistory.DominantSelectionPolicy;

	const TArray<FHyperTwistTrainingCoachActionPlan> UserPlans = ListCoachActionPlansForUser(RepositoryState, UserId);
	TMap<FString, FHyperTwistTrainingCoachActionPlan> LatestPlanByRootId;
	TMap<FString, FString> LatestSortUtcByRootId;

	for (const FHyperTwistTrainingCoachActionPlan& Plan : UserPlans)
	{
		const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
			HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(Plan);
		if (!NormalizedPlan.IsStructurallyValid())
		{
			continue;
		}

		const FString RootPlanId = !NormalizedPlan.RootPlanId.IsEmpty()
			? NormalizedPlan.RootPlanId
			: NormalizedPlan.PlanId;
		const FString SortUtc = !NormalizedPlan.CompletedAtUtc.IsEmpty()
			? NormalizedPlan.CompletedAtUtc
			: (!NormalizedPlan.StartedAtUtc.IsEmpty() ? NormalizedPlan.StartedAtUtc : NormalizedPlan.CreatedAtUtc);

		FHyperTwistTrainingCoachActionPlan* ExistingPlan = LatestPlanByRootId.Find(RootPlanId);
		FString* ExistingSortUtc = LatestSortUtcByRootId.Find(RootPlanId);
		if (ExistingPlan == nullptr || ExistingSortUtc == nullptr)
		{
			LatestPlanByRootId.Add(RootPlanId, NormalizedPlan);
			LatestSortUtcByRootId.Add(RootPlanId, SortUtc);
			continue;
		}

		const int32 CompareResult =
			HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(SortUtc, *ExistingSortUtc);
		if (CompareResult > 0
			|| (CompareResult == 0 && NormalizedPlan.FollowUpDepth >= ExistingPlan->FollowUpDepth))
		{
			*ExistingPlan = NormalizedPlan;
			*ExistingSortUtc = SortUtc;
		}
	}

	TMap<FString, int32> ResolvedArchiveHitCounts;
	TMap<FString, int32> OpenCarryForwardHitCounts;
	TMap<FString, int32> EscalationHitCounts;
	TMap<FString, int32> StableArchiveHitCounts;
	const FHyperTwistTrainingCoachMemorySnapshot CoachMemorySnapshot =
		HyperTwistTrainingRepositoryLibraryInternal::DeriveQueueRecoveryBiasSnapshot(
			RepositoryState,
			UserId,
			Summary.ReferenceUtc
		);

	for (const TPair<FString, FHyperTwistTrainingCoachActionPlan>& Pair : LatestPlanByRootId)
	{
		const FHyperTwistTrainingCoachActionClosureSummary ClosureSummary =
			HyperTwistTrainingRepositoryLibraryInternal::DeriveCoachActionClosureSummaryWithCoachMemory(
				RepositoryState,
				Pair.Value,
				Summary.ReferenceUtc,
				CoachMemorySnapshot
			);
		if (!ClosureSummary.IsStructurallyValid())
		{
			continue;
		}

		if (ClosureSummary.bShouldArchiveResolvedCases)
		{
			for (const FString& CaseId : ClosureSummary.ResolvedCaseIds)
			{
				if (CaseId.IsEmpty())
				{
					continue;
				}

				ResolvedArchiveHitCounts.FindOrAdd(CaseId) += 1;
				if (ClosureSummary.bSequenceClosed
					&& !ClosureSummary.bNeedsEscalation
					&& !ClosureSummary.bShouldSpawnFollowUp)
				{
					StableArchiveHitCounts.FindOrAdd(CaseId) += 1;
				}
			}
		}

		for (const FString& CaseId : ClosureSummary.PersistentWeakCaseIds)
		{
			if (!CaseId.IsEmpty())
			{
				OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
				if (ClosureSummary.bNeedsEscalation)
				{
					EscalationHitCounts.FindOrAdd(CaseId) += 1;
				}
			}
		}
		for (const FString& CaseId : ClosureSummary.UnresolvedCaseIds)
		{
			if (!CaseId.IsEmpty())
			{
				OpenCarryForwardHitCounts.FindOrAdd(CaseId) += 1;
				if (ClosureSummary.bNeedsEscalation)
				{
					EscalationHitCounts.FindOrAdd(CaseId) += 1;
				}
			}
		}
	}

	TSet<FString> RecurringCaseIds;
	for (const FString& CaseId : RecommendationHistory.RecurringCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			RecurringCaseIds.Add(CaseId);
		}
	}

	TSet<FString> LongTermArchivedCaseIds;
	TSet<FString> ExpiredCarryForwardCaseIds;
	TSet<FString> RetainedCarryForwardCaseIds;
	TSet<FString> EscalationRetainedCaseIds;
	TSet<FString> SuppressedResolvedCaseIds;
	TSet<FString> StableLongTermArchivedCaseIds;
	TSet<FString> ProvisionalArchivedCaseIds;
	TSet<FString> ReopenRiskArchivedCaseIds;

	for (const FString& CaseId : CarryForwardPolicy.SuppressedResolvedCaseIds)
	{
		if (!CaseId.IsEmpty())
		{
			LongTermArchivedCaseIds.Add(CaseId);
			SuppressedResolvedCaseIds.Add(CaseId);

			const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
			const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
			const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);
			if (StableArchiveHits > 0 && OpenCarryForwardHits == 0 && !bRecurringCase)
			{
				StableLongTermArchivedCaseIds.Add(CaseId);
			}
			else
			{
				ProvisionalArchivedCaseIds.Add(CaseId);
				if (bRecurringCase || OpenCarryForwardHits > 0)
				{
					ReopenRiskArchivedCaseIds.Add(CaseId);
				}
			}
		}
	}

	const bool bStableClosureMemory =
		ClosureMemory.AverageClosureScore >= 70.0f
		&& !ClosureMemory.bHasEscalationPressure
		&& !ClosureMemory.bHasFollowUpPressure;

	for (const FString& CaseId : CarryForwardPolicy.PinnedCarryForwardCaseIds)
	{
		if (CaseId.IsEmpty())
		{
			continue;
		}

		const int32 ResolvedArchiveHits = ResolvedArchiveHitCounts.FindRef(CaseId);
		const int32 OpenCarryForwardHits = OpenCarryForwardHitCounts.FindRef(CaseId);
		const int32 EscalationHits = EscalationHitCounts.FindRef(CaseId);
		const int32 StableArchiveHits = StableArchiveHitCounts.FindRef(CaseId);
		const bool bRecurringCase = RecurringCaseIds.Contains(CaseId);

		const bool bCanExpireResolvedCarryForward =
			EscalationHits == 0
			&& ResolvedArchiveHits > 0
			&& OpenCarryForwardHits <= ResolvedArchiveHits
			&& (StableArchiveHits > 0
				|| bStableClosureMemory
				|| (ResolvedArchiveHits >= 2 && !bRecurringCase)
				|| (ResolvedArchiveHits > OpenCarryForwardHits
					&& RecommendationHistory.RepeatRecommendationSequenceCount <= 1));

		if (bCanExpireResolvedCarryForward)
		{
			ExpiredCarryForwardCaseIds.Add(CaseId);
			LongTermArchivedCaseIds.Add(CaseId);
			if (StableArchiveHits > 0 && !bRecurringCase && OpenCarryForwardHits == 0)
			{
				StableLongTermArchivedCaseIds.Add(CaseId);
			}
			else
			{
				ProvisionalArchivedCaseIds.Add(CaseId);
				if (bRecurringCase
					|| RecommendationHistory.bHasRepeatRecommendationPressure
					|| OpenCarryForwardHits > 0)
				{
					ReopenRiskArchivedCaseIds.Add(CaseId);
				}
			}
			continue;
		}

		RetainedCarryForwardCaseIds.Add(CaseId);
		if (EscalationHits > 0)
		{
			EscalationRetainedCaseIds.Add(CaseId);
		}
	}

	Summary.LongTermArchivedCaseIds = LongTermArchivedCaseIds.Array();
	Summary.ExpiredCarryForwardCaseIds = ExpiredCarryForwardCaseIds.Array();
	Summary.RetainedCarryForwardCaseIds = RetainedCarryForwardCaseIds.Array();
	Summary.EscalationRetainedCaseIds = EscalationRetainedCaseIds.Array();
	Summary.SuppressedResolvedCaseIds = SuppressedResolvedCaseIds.Array();
	Summary.StableLongTermArchivedCaseIds = StableLongTermArchivedCaseIds.Array();
	Summary.ProvisionalArchivedCaseIds = ProvisionalArchivedCaseIds.Array();
	Summary.ReopenRiskArchivedCaseIds = ReopenRiskArchivedCaseIds.Array();
	Summary.LongTermArchivedCaseIds.Sort();
	Summary.ExpiredCarryForwardCaseIds.Sort();
	Summary.RetainedCarryForwardCaseIds.Sort();
	Summary.EscalationRetainedCaseIds.Sort();
	Summary.SuppressedResolvedCaseIds.Sort();
	Summary.StableLongTermArchivedCaseIds.Sort();
	Summary.ProvisionalArchivedCaseIds.Sort();
	Summary.ReopenRiskArchivedCaseIds.Sort();

	Summary.LongTermArchivedCaseCount = Summary.LongTermArchivedCaseIds.Num();
	Summary.ExpiredCarryForwardCaseCount = Summary.ExpiredCarryForwardCaseIds.Num();
	Summary.RetainedCarryForwardCaseCount = Summary.RetainedCarryForwardCaseIds.Num();
	Summary.EscalationRetainedCaseCount = Summary.EscalationRetainedCaseIds.Num();
	Summary.SuppressedResolvedCaseCount = Summary.SuppressedResolvedCaseIds.Num();
	Summary.StableLongTermArchivedCaseCount = Summary.StableLongTermArchivedCaseIds.Num();
	Summary.ProvisionalArchivedCaseCount = Summary.ProvisionalArchivedCaseIds.Num();
	Summary.ReopenRiskArchivedCaseCount = Summary.ReopenRiskArchivedCaseIds.Num();

	Summary.bShouldExpireResolvedCarryForward = Summary.ExpiredCarryForwardCaseCount > 0;
	Summary.bShouldRetainEscalationCarryForward = Summary.EscalationRetainedCaseCount > 0;
	Summary.ArchiveStabilityScore = FMath::Clamp(
		ClosureMemory.AverageClosureScore
		+ (static_cast<float>(Summary.LongTermArchivedCaseCount) * 5.0f)
		+ (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 4.0f)
		+ (bStableClosureMemory ? 10.0f : 0.0f)
		- (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 3.0f)
		- (static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 8.0f)
		- (static_cast<float>(Summary.RetainedCarryForwardCaseCount) * 4.0f)
		- (static_cast<float>(Summary.EscalationRetainedCaseCount) * 10.0f)
		- (RecommendationHistory.bHasRepeatRecommendationPressure ? 6.0f : 0.0f),
		0.0f,
		100.0f
	);
	Summary.ArchiveRetentionTuningScore = FMath::Clamp(
		(static_cast<float>(Summary.ReopenRiskArchivedCaseCount) * 18.0f)
		+ (static_cast<float>(Summary.ProvisionalArchivedCaseCount) * 9.0f)
		+ (RecommendationHistory.bHasRepeatRecommendationPressure ? 12.0f : 0.0f)
		+ (Summary.bShouldRetainEscalationCarryForward ? 14.0f : 0.0f)
		- (static_cast<float>(Summary.StableLongTermArchivedCaseCount) * 6.0f),
		0.0f,
		100.0f
	);
	Summary.bHasStableArchiveMemory =
		Summary.LongTermArchivedCaseCount > 0
		&& Summary.ArchiveStabilityScore >= 55.0f
		&& Summary.EscalationRetainedCaseCount == 0;
	Summary.bNeedsArchiveRetentionTuning =
		Summary.ReopenRiskArchivedCaseCount > 0
		|| Summary.ArchiveRetentionTuningScore >= 25.0f
		|| (Summary.ProvisionalArchivedCaseCount > Summary.StableLongTermArchivedCaseCount
			&& Summary.LongTermArchivedCaseCount > 0);
	Summary.bHasActiveArchivePressure =
		Summary.RetainedCarryForwardCaseCount > 0
		|| Summary.EscalationRetainedCaseCount > 0
		|| Summary.bNeedsArchiveRetentionTuning;

	return Summary;
}
