// Copyright HyperTwist, Inc. All Rights Reserved.

#include "HyperTwistTraining/HyperTwistTrainingRepositoryLibrary.h"

#include "HyperTwistTrainingRepositoryLibraryInternal.h"

FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::BuildCoachActionPlan(
	const FHyperTwistCoachBrief& CoachBrief,
	const FHyperTwistTrainingCoachMemorySnapshot& CoachMemorySnapshot,
	const FString& PlanId,
	const FString& ReferenceUtc,
	const int32 RequestedLaunchCaseCount,
	const int32 PlannedLaunchCaseCount,
	const FString& LaunchBudgetReasonLine,
	const TArray<FString>& LaunchedCaseIds
)
{
	FHyperTwistTrainingCoachActionPlan ActionPlan;
	if (CoachBrief.UserId.IsEmpty() || PlanId.IsEmpty())
	{
		return ActionPlan;
	}

	ActionPlan.PlanId = PlanId;
	ActionPlan.UserId = CoachBrief.UserId;
	ActionPlan.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
		!ReferenceUtc.IsEmpty() ? ReferenceUtc : CoachBrief.ReferenceUtc
	);
	ActionPlan.SourceSessionId = CoachBrief.TrainingSessionId;
	ActionPlan.SourceReviewPlanId = CoachBrief.SourceReviewPlanId;
	ActionPlan.ParentPlanId = CoachBrief.CarryForwardPlanId;
	ActionPlan.RootPlanId = !CoachBrief.RootPlanId.IsEmpty()
		? CoachBrief.RootPlanId
		: (!CoachBrief.CarryForwardPlanId.IsEmpty() ? CoachBrief.CarryForwardPlanId : PlanId);
	ActionPlan.FocusDeckId = CoachBrief.FocusDeckId;
	ActionPlan.FocusMethodSegmentId = CoachBrief.FocusMethodSegmentId;
	ActionPlan.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
		static_cast<int64>(CoachBrief.FocusLane)
	);
	ActionPlan.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
		static_cast<int64>(CoachBrief.Priority)
	);
	ActionPlan.PriorityScore = CoachBrief.PriorityScore;
	ActionPlan.Headline = CoachBrief.Headline;
	ActionPlan.PrimaryActionLabel = CoachBrief.PrimaryActionLabel;
	ActionPlan.SuggestedMode = CoachBrief.SuggestedMode;
	ActionPlan.SuggestedSelectionPolicy = CoachBrief.SuggestedSelectionPolicy;
	HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanPreferenceDefaults(
		CoachBrief,
		CoachMemorySnapshot,
		ActionPlan.SuggestedMode,
		ActionPlan.SuggestedSelectionPolicy
	);
	ActionPlan.PreferredGuidanceLane = CoachMemorySnapshot.PreferredGuidanceLane;
	ActionPlan.PreferredGuidanceSourceLabel = CoachMemorySnapshot.PreferredGuidanceSourceLabel;
	ActionPlan.PreferredCoachHandoffKind = CoachMemorySnapshot.PreferredCoachHandoffKind;
	ActionPlan.PreferredCoachHandoffSourceLabel = CoachMemorySnapshot.PreferredCoachHandoffSourceLabel;
	ActionPlan.RequestedLaunchCaseCount = RequestedLaunchCaseCount > 0
		? RequestedLaunchCaseCount
		: FMath::Max(1, CoachBrief.FocusCaseIds.Num());
	ActionPlan.PlannedLaunchCaseCount = PlannedLaunchCaseCount > 0
		? PlannedLaunchCaseCount
		: ActionPlan.RequestedLaunchCaseCount;
	ActionPlan.FocusCaseIds = HyperTwistTrainingRepositoryLibraryInternal::ResolveCoachActionPlanFocusCaseIds(
		CoachBrief,
		LaunchedCaseIds,
		ActionPlan.PlannedLaunchCaseCount
	);
	if (ActionPlan.FocusCaseIds.Num() > 0)
	{
		ActionPlan.PlannedLaunchCaseCount = ActionPlan.FocusCaseIds.Num();
	}
	for (const FString& OriginalFocusCaseId : HyperTwistTrainingRepositoryLibraryInternal::DeduplicateCaseIds(
		CoachBrief.FocusCaseIds))
	{
		if (!ActionPlan.FocusCaseIds.Contains(OriginalFocusCaseId))
		{
			ActionPlan.DeferredLaunchCaseIds.Add(OriginalFocusCaseId);
		}
	}
	ActionPlan.LaunchBudgetReasonLine = LaunchBudgetReasonLine;
	ActionPlan.bBroadenedLaunchBudget =
		ActionPlan.PlannedLaunchCaseCount > ActionPlan.RequestedLaunchCaseCount
		|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetBroadeningHint(
			ActionPlan.LaunchBudgetReasonLine);
	ActionPlan.bTightenedLaunchBudget =
		ActionPlan.PlannedLaunchCaseCount < ActionPlan.RequestedLaunchCaseCount
		|| HyperTwistTrainingRepositoryLibraryInternal::ContainsLaunchBudgetTighteningHint(
			ActionPlan.LaunchBudgetReasonLine);
	if (ActionPlan.FocusCaseIds.Num() == 0)
	{
		ActionPlan.FocusCaseIds = CoachBrief.FocusCaseIds;
	}
	ActionPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Draft;
	ActionPlan.FollowUpDepth = FMath::Max(0, CoachBrief.FollowUpDepth);
	ActionPlan.CreatedAtUtc = ActionPlan.ReferenceUtc;
	ActionPlan.bNeedsCoachReview = CoachBrief.bNeedsCoachReview;
	ActionPlan.bNeedsCadenceRecovery = CoachBrief.bNeedsCadenceRecovery;
	ActionPlan.bNeedsVarietyRotation = CoachBrief.bNeedsVarietyRotation;
	ActionPlan.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;

	for (int32 Index = 0; Index < ActionPlan.FocusCaseIds.Num(); ++Index)
	{
		FHyperTwistTrainingCoachActionPlanEntry Entry;
		Entry.ActionId = FString::Printf(TEXT("%s_entry_%02d"), *PlanId, Index + 1);
		Entry.CaseId = ActionPlan.FocusCaseIds[Index];
		Entry.DeckId = CoachBrief.FocusDeckId;
		Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
		Entry.ActionLabel = CoachBrief.PrimaryActionLabel;
		Entry.Headline = CoachBrief.Headline;
		Entry.PriorityScore = CoachBrief.PriorityScore;
		Entry.SuggestedMode = ActionPlan.SuggestedMode;
		Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
		Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
		if (Entry.IsStructurallyValid())
		{
			ActionPlan.Entries.Add(Entry);
		}
	}

	if (ActionPlan.Entries.Num() == 0)
	{
		FHyperTwistTrainingCoachActionPlanEntry Entry;
		Entry.ActionId = FString::Printf(TEXT("%s_entry_01"), *PlanId);
		Entry.CaseId = TEXT("coach-generic");
		Entry.DeckId = CoachBrief.FocusDeckId;
		Entry.MethodSegmentId = CoachBrief.FocusMethodSegmentId;
		Entry.ActionLabel = CoachBrief.PrimaryActionLabel.IsEmpty()
			? TEXT("coach-focus")
			: CoachBrief.PrimaryActionLabel;
		Entry.Headline = CoachBrief.Headline;
		Entry.PriorityScore = CoachBrief.PriorityScore;
		Entry.SuggestedMode = ActionPlan.SuggestedMode;
		Entry.SuggestedSelectionPolicy = ActionPlan.SuggestedSelectionPolicy;
		Entry.bPreferRecognitionReplayReview = CoachBrief.bPreferRecognitionReplayReview;
		ActionPlan.Entries.Add(Entry);
	}

	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
}

FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::ActivateCoachActionPlan(
	const FHyperTwistTrainingCoachActionPlan& ActionPlan,
	const FString& TrainingSessionId,
	const FString& StartedAtUtc
)
{
	FHyperTwistTrainingCoachActionPlan UpdatedPlan =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
	if (!UpdatedPlan.IsStructurallyValid())
	{
		return UpdatedPlan;
	}

	UpdatedPlan.PlanState = EHyperTwistTrainingCoachActionPlanState::Active;
	UpdatedPlan.SourceSessionId = !TrainingSessionId.IsEmpty() ? TrainingSessionId : UpdatedPlan.SourceSessionId;
	UpdatedPlan.StartedAtUtc = !StartedAtUtc.IsEmpty()
		? StartedAtUtc
		: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
	return UpdatedPlan;
}

FHyperTwistTrainingCoachActionPlan UHyperTwistTrainingRepositoryLibrary::FinalizeCoachActionPlan(
	const FHyperTwistTrainingCoachActionPlan& ActionPlan,
	const FHyperTwistTrainingSession& Session,
	bool bAbortPlan
)
{
	FHyperTwistTrainingCoachActionPlan UpdatedPlan =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
	if (!UpdatedPlan.IsStructurallyValid())
	{
		return UpdatedPlan;
	}

	UpdatedPlan.SourceSessionId = !Session.TrainingSessionId.IsEmpty()
		? Session.TrainingSessionId
		: UpdatedPlan.SourceSessionId;
	UpdatedPlan.CompletedAtUtc = !Session.EndedAtUtc.IsEmpty()
		? Session.EndedAtUtc
		: HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(UpdatedPlan.ReferenceUtc);
	UpdatedPlan.PlanState = bAbortPlan || Session.SessionState == EHyperTwistTrainingSessionState::Aborted
		? EHyperTwistTrainingCoachActionPlanState::Aborted
		: EHyperTwistTrainingCoachActionPlanState::Completed;
	if (UpdatedPlan.PlanState == EHyperTwistTrainingCoachActionPlanState::Completed)
	{
		for (FHyperTwistTrainingCoachActionPlanEntry& Entry : UpdatedPlan.Entries)
		{
			Entry.bCompleted = true;
		}
	}

	return UpdatedPlan;
}

TArray<FHyperTwistTrainingCoachActionPlan> UHyperTwistTrainingRepositoryLibrary::ListCoachActionPlansForUser(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId
)
{
	TArray<FHyperTwistTrainingCoachActionPlan> ActionPlans;
	if (UserId.IsEmpty())
	{
		return ActionPlans;
	}

	for (const FHyperTwistTrainingCoachActionPlan& ActionPlan : RepositoryState.StoredCoachActionPlans)
	{
		if (ActionPlan.UserId == UserId && ActionPlan.IsStructurallyValid())
		{
			ActionPlans.Add(ActionPlan);
		}
	}

	ActionPlans.Sort([](const FHyperTwistTrainingCoachActionPlan& Left, const FHyperTwistTrainingCoachActionPlan& Right)
	{
		const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
			Left.CreatedAtUtc,
			Right.CreatedAtUtc
		);
		if (UtcCompare != 0)
		{
			return UtcCompare > 0;
		}

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

bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachActionPlan(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& PlanId,
	FHyperTwistTrainingCoachActionPlan& OutActionPlan
)
{
	OutActionPlan = FHyperTwistTrainingCoachActionPlan();
	if (PlanId.IsEmpty())
	{
		return false;
	}

	const FHyperTwistTrainingCoachActionPlan* MatchingPlan =
		RepositoryState.StoredCoachActionPlans.FindByPredicate(
			[&PlanId](const FHyperTwistTrainingCoachActionPlan& Candidate)
			{
				return Candidate.PlanId == PlanId && Candidate.IsStructurallyValid();
			}
		);
	if (MatchingPlan == nullptr)
	{
		return false;
	}

	OutActionPlan = *MatchingPlan;
	return true;
}

FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachActionPlan(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FHyperTwistTrainingCoachActionPlan& ActionPlan
)
{
	FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
	const FHyperTwistTrainingCoachActionPlan NormalizedPlan =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachActionPlan(ActionPlan);
	if (!NormalizedPlan.IsStructurallyValid())
	{
		return UpdatedRepositoryState;
	}

	FHyperTwistTrainingCoachActionPlan* ExistingPlan =
		UpdatedRepositoryState.StoredCoachActionPlans.FindByPredicate(
			[&NormalizedPlan](const FHyperTwistTrainingCoachActionPlan& Candidate)
			{
				return Candidate.UserId == NormalizedPlan.UserId
					&& Candidate.PlanId == NormalizedPlan.PlanId;
			}
		);
	if (ExistingPlan != nullptr)
	{
		*ExistingPlan = NormalizedPlan;
	}
	else
	{
		UpdatedRepositoryState.StoredCoachActionPlans.Add(NormalizedPlan);
	}

	return UpdatedRepositoryState;
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::BuildCoachSessionQueueState(
	const FHyperTwistCoachSessionQueueSummary& QueueSummary,
	const FString& QueueId,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachSessionQueueState QueueState;
	if (!QueueSummary.IsStructurallyValid())
	{
		return QueueState;
	}

	QueueState.QueueId = QueueId.IsEmpty() ? TEXT("coach_queue_active") : QueueId;
	QueueState.UserId = QueueSummary.UserId;
	QueueState.ReferenceUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
		ReferenceUtc.IsEmpty() ? QueueSummary.ReferenceUtc : ReferenceUtc
	);
	QueueState.LastUpdatedAtUtc = QueueState.ReferenceUtc;
	QueueState.FocusDeckId = QueueSummary.FocusDeckId;
	QueueState.FocusMethodSegmentId = QueueSummary.FocusMethodSegmentId;
	QueueState.PrimaryQueueLabel = QueueSummary.PrimaryQueueLabel;
	QueueState.NextQueueLabel = QueueSummary.NextQueueLabel;
	QueueState.bHasImmediateQueue = QueueSummary.bHasImmediateQueue;
	QueueState.bNeedsArchiveAwareSequencing = QueueSummary.bNeedsArchiveAwareSequencing;

	for (const FHyperTwistCoachSessionQueueEntry& SummaryEntry : QueueSummary.Entries)
	{
		FHyperTwistTrainingCoachSessionQueueStateEntry QueueEntry;
		QueueEntry.EntryId = SummaryEntry.EntryId;
		QueueEntry.SourceLabel = SummaryEntry.SourceLabel;
		QueueEntry.SourceBriefTrainingSessionId = SummaryEntry.SourceBriefTrainingSessionId;
		QueueEntry.SourceReviewPlanId = SummaryEntry.SourceReviewPlanId;
		QueueEntry.CarryForwardPlanId = SummaryEntry.CarryForwardPlanId;
		QueueEntry.RootPlanId = SummaryEntry.RootPlanId;
		QueueEntry.FollowUpDepth = FMath::Max(0, SummaryEntry.FollowUpDepth);
		QueueEntry.SlotIndex = SummaryEntry.SlotIndex;
		QueueEntry.PriorityLabel = StaticEnum<EHyperTwistCoachPriority>()->GetNameStringByValue(
			static_cast<int64>(SummaryEntry.Priority)
		);
		QueueEntry.PriorityScore = SummaryEntry.PriorityScore;
		QueueEntry.FocusLaneLabel = StaticEnum<EHyperTwistCoachFocusLane>()->GetNameStringByValue(
			static_cast<int64>(SummaryEntry.FocusLane)
		);
		QueueEntry.Headline = SummaryEntry.Headline;
		QueueEntry.PrimaryActionLabel = SummaryEntry.PrimaryActionLabel;
		QueueEntry.SuggestedMode = SummaryEntry.SuggestedMode;
		QueueEntry.SuggestedSelectionPolicy = SummaryEntry.SuggestedSelectionPolicy;
		QueueEntry.FocusDeckId = SummaryEntry.FocusDeckId;
		QueueEntry.FocusMethodSegmentId = SummaryEntry.FocusMethodSegmentId;
		QueueEntry.FocusCaseIds = SummaryEntry.FocusCaseIds;
		QueueEntry.DeferredLaunchCaseCount = FMath::Max(0, SummaryEntry.DeferredLaunchCaseCount);
		QueueEntry.bNeedsDeferredLaunchRecovery = SummaryEntry.bNeedsDeferredLaunchRecovery;
		QueueEntry.bHasTightenedLaunchOverflow = SummaryEntry.bHasTightenedLaunchOverflow;
		QueueEntry.bRequiresFollowUp = SummaryEntry.bRequiresFollowUp;
		QueueEntry.bRequiresArchiveRetentionTuning = SummaryEntry.bRequiresArchiveRetentionTuning;
		QueueEntry.bNeedsCoachReview = SummaryEntry.bNeedsCoachReview;
		QueueEntry.bNeedsCadenceRecovery = SummaryEntry.bNeedsCadenceRecovery;
		QueueEntry.bNeedsVarietyRotation = SummaryEntry.bNeedsVarietyRotation;
		QueueEntry.bPreferRecognitionReplayReview = SummaryEntry.bPreferRecognitionReplayReview;
		QueueEntry.bPinned = QueueEntry.bRequiresFollowUp || QueueEntry.bRequiresArchiveRetentionTuning;
		QueueEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
		QueueEntry.WorkFingerprint =
			HyperTwistTrainingRepositoryLibraryInternal::MakeCoachSessionQueueEntryFingerprint(QueueEntry);
		QueueState.Entries.Add(QueueEntry);
	}

	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::ResolveCoachSessionQueueState(
	const FHyperTwistTrainingCoachSessionQueueState& ExistingQueueState,
	const FHyperTwistCoachSessionQueueSummary& QueueSummary,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachSessionQueueState ResolvedQueueState = BuildCoachSessionQueueState(
		QueueSummary,
		ExistingQueueState.QueueId,
		ReferenceUtc
	);
	if (!ResolvedQueueState.IsStructurallyValid() || !ExistingQueueState.IsStructurallyValid())
	{
		return ResolvedQueueState;
	}

	for (FHyperTwistTrainingCoachSessionQueueStateEntry& QueueEntry : ResolvedQueueState.Entries)
	{
		const FHyperTwistTrainingCoachSessionQueueStateEntry* ExistingEntry =
			ExistingQueueState.Entries.FindByPredicate(
				[&QueueEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
				{
					return Candidate.EntryId == QueueEntry.EntryId
						&& Candidate.WorkFingerprint == QueueEntry.WorkFingerprint
						&& Candidate.IsStructurallyValid();
				}
			);
		if (ExistingEntry == nullptr)
		{
			continue;
		}

		QueueEntry.EntryState = ExistingEntry->EntryState;
		QueueEntry.SourceTrainingSessionId = ExistingEntry->SourceTrainingSessionId;
		QueueEntry.ScheduledAtUtc = ExistingEntry->ScheduledAtUtc;
		QueueEntry.DeferredUntilUtc = ExistingEntry->DeferredUntilUtc;
		QueueEntry.DeferredAtUtc = ExistingEntry->DeferredAtUtc;
		QueueEntry.DeferralReasonLabel = ExistingEntry->DeferralReasonLabel;
		QueueEntry.StartedAtUtc = ExistingEntry->StartedAtUtc;
		QueueEntry.CompletedAtUtc = ExistingEntry->CompletedAtUtc;
		QueueEntry.RescheduleCount = ExistingEntry->RescheduleCount;
		QueueEntry.bPinned = QueueEntry.bPinned || ExistingEntry->bPinned;
		QueueEntry.bCanStartImmediately = ExistingEntry->bCanStartImmediately;
		QueueEntry.bWasManuallyRescheduled = ExistingEntry->bWasManuallyRescheduled;
	}

	ResolvedQueueState.LastUpdatedAtUtc = HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
		ReferenceUtc.IsEmpty() ? ResolvedQueueState.ReferenceUtc : ReferenceUtc
	);
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(ResolvedQueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryBySourceLabel(
	const FHyperTwistTrainingCoachSessionQueueState& QueueState,
	const FString& SourceLabel,
	const FString& TrainingSessionId,
	const FString& StartedAtUtc,
	const FString& StartActionSourceLabel
)
{
	FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!UpdatedQueueState.IsStructurallyValid() || SourceLabel.IsEmpty())
	{
		return UpdatedQueueState;
	}

	const FString ResolvedStartedAtUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
	int32 ActivatedIndex = INDEX_NONE;
	for (int32 EntryIndex = 0; EntryIndex < UpdatedQueueState.Entries.Num(); ++EntryIndex)
	{
		const FHyperTwistTrainingCoachSessionQueueStateEntry& Entry = UpdatedQueueState.Entries[EntryIndex];
		if (Entry.SourceLabel == SourceLabel
			&& Entry.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
			&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Entry.EntryState))
		{
			ActivatedIndex = EntryIndex;
			break;
		}
	}
	if (ActivatedIndex == INDEX_NONE)
	{
		return UpdatedQueueState;
	}

	for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
	{
		if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
		{
			Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
			Entry.SourceTrainingSessionId.Reset();
			Entry.StartActionSourceLabel.Reset();
			Entry.StartedAtUtc.Reset();
			Entry.bCanStartImmediately = false;
		}
	}

	FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
		UpdatedQueueState.Entries[ActivatedIndex];
	ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
	ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
	ActiveEntry.StartActionSourceLabel = !StartActionSourceLabel.IsEmpty()
		? StartActionSourceLabel
		: SourceLabel;
	ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
		? ActiveEntry.ScheduledAtUtc
		: ResolvedStartedAtUtc;
	ActiveEntry.DeferredUntilUtc.Reset();
	ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
	ActiveEntry.bCanStartImmediately = false;
	UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::StartCoachSessionQueueEntryById(
	const FHyperTwistTrainingCoachSessionQueueState& QueueState,
	const FString& EntryId,
	const FString& TrainingSessionId,
	const FString& StartedAtUtc
)
{
	FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty())
	{
		return UpdatedQueueState;
	}

	const FString ResolvedStartedAtUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(StartedAtUtc);
	const int32 ActivatedIndex = UpdatedQueueState.Entries.IndexOfByPredicate(
		[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
		{
			return Candidate.EntryId == EntryId
				&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::Deferred
				&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
		}
	);
	if (ActivatedIndex == INDEX_NONE)
	{
		return UpdatedQueueState;
	}

	for (FHyperTwistTrainingCoachSessionQueueStateEntry& Entry : UpdatedQueueState.Entries)
	{
		if (Entry.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress)
		{
			Entry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Pending;
			Entry.SourceTrainingSessionId.Reset();
			Entry.StartedAtUtc.Reset();
			Entry.bCanStartImmediately = false;
		}
	}

	FHyperTwistTrainingCoachSessionQueueStateEntry& ActiveEntry =
		UpdatedQueueState.Entries[ActivatedIndex];
	ActiveEntry.EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
	ActiveEntry.SourceTrainingSessionId = TrainingSessionId;
	ActiveEntry.ScheduledAtUtc = !ActiveEntry.ScheduledAtUtc.IsEmpty()
		? ActiveEntry.ScheduledAtUtc
		: ResolvedStartedAtUtc;
	ActiveEntry.DeferredUntilUtc.Reset();
	ActiveEntry.StartedAtUtc = ResolvedStartedAtUtc;
	ActiveEntry.bCanStartImmediately = false;
	UpdatedQueueState.LastUpdatedAtUtc = ResolvedStartedAtUtc;
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::FinalizeCoachSessionQueueEntry(
	const FHyperTwistTrainingCoachSessionQueueState& QueueState,
	const FString& TrainingSessionId,
	const FString& CompletedAtUtc,
	bool bAbortEntry
)
{
	FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!UpdatedQueueState.IsStructurallyValid())
	{
		return UpdatedQueueState;
	}

	const FString ResolvedCompletedAtUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
			CompletedAtUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : CompletedAtUtc
		);
	FHyperTwistTrainingCoachSessionQueueStateEntry* ActiveEntry =
		UpdatedQueueState.Entries.FindByPredicate(
			[&TrainingSessionId, &UpdatedQueueState](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
			{
				if (!TrainingSessionId.IsEmpty() && Candidate.SourceTrainingSessionId == TrainingSessionId)
				{
					return true;
				}

				return Candidate.EntryId == UpdatedQueueState.ActiveEntryId
					&& Candidate.EntryState == EHyperTwistTrainingCoachSessionQueueEntryState::InProgress;
			}
		);
	if (ActiveEntry == nullptr)
	{
		return UpdatedQueueState;
	}

	ActiveEntry->EntryState = bAbortEntry
		? EHyperTwistTrainingCoachSessionQueueEntryState::Skipped
		: EHyperTwistTrainingCoachSessionQueueEntryState::Completed;
	ActiveEntry->CompletedAtUtc = ResolvedCompletedAtUtc;
	ActiveEntry->bCanStartImmediately = false;
	UpdatedQueueState.ActiveEntryId.Reset();
	UpdatedQueueState.ActiveTrainingSessionId.Reset();
	UpdatedQueueState.LastUpdatedAtUtc = ResolvedCompletedAtUtc;
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::DeferCoachSessionQueueEntry(
	const FHyperTwistTrainingCoachSessionQueueState& QueueState,
	const FString& EntryId,
	const FString& DeferredUntilUtc,
	const FString& DeferralReasonLabel,
	bool bManualReschedule,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty() || DeferredUntilUtc.IsEmpty())
	{
		return UpdatedQueueState;
	}

	const FString ResolvedReferenceUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
			ReferenceUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : ReferenceUtc
		);
	FHyperTwistTrainingCoachSessionQueueStateEntry* EntryToDefer =
		UpdatedQueueState.Entries.FindByPredicate(
			[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
			{
				return Candidate.EntryId == EntryId
					&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::InProgress
					&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
			}
		);
	if (EntryToDefer == nullptr)
	{
		return UpdatedQueueState;
	}

	EntryToDefer->EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Deferred;
	EntryToDefer->DeferredUntilUtc = DeferredUntilUtc;
	EntryToDefer->DeferredAtUtc = ResolvedReferenceUtc;
	EntryToDefer->DeferralReasonLabel = !DeferralReasonLabel.IsEmpty()
		? DeferralReasonLabel
		: (bManualReschedule ? TEXT("manual-reschedule") : TEXT("deferred-window"));
	EntryToDefer->SourceTrainingSessionId.Reset();
	EntryToDefer->StartedAtUtc.Reset();
	EntryToDefer->CompletedAtUtc.Reset();
	EntryToDefer->bCanStartImmediately = false;
	EntryToDefer->RescheduleCount += 1;
	EntryToDefer->bWasManuallyRescheduled = EntryToDefer->bWasManuallyRescheduled || bManualReschedule;
	UpdatedQueueState.ActiveEntryId.Reset();
	UpdatedQueueState.ActiveTrainingSessionId.Reset();
	UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
}

FHyperTwistTrainingCoachSessionQueueState UHyperTwistTrainingRepositoryLibrary::PromoteCoachSessionQueueEntry(
	const FHyperTwistTrainingCoachSessionQueueState& QueueState,
	const FString& EntryId,
	const FString& StartActionSourceLabel,
	const FString& ReferenceUtc
)
{
	FHyperTwistTrainingCoachSessionQueueState UpdatedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!UpdatedQueueState.IsStructurallyValid() || EntryId.IsEmpty())
	{
		return UpdatedQueueState;
	}

	const FString ResolvedReferenceUtc =
		HyperTwistTrainingRepositoryLibraryInternal::ResolveReferenceUtc(
			ReferenceUtc.IsEmpty() ? UpdatedQueueState.ReferenceUtc : ReferenceUtc
		);
	FHyperTwistTrainingCoachSessionQueueStateEntry* EntryToPromote =
		UpdatedQueueState.Entries.FindByPredicate(
			[&EntryId](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
			{
				return Candidate.EntryId == EntryId
					&& Candidate.EntryState != EHyperTwistTrainingCoachSessionQueueEntryState::InProgress
					&& !HyperTwistTrainingRepositoryLibraryInternal::IsTerminalCoachQueueEntryState(Candidate.EntryState);
			}
		);
	if (EntryToPromote == nullptr)
	{
		return UpdatedQueueState;
	}

	EntryToPromote->EntryState = EHyperTwistTrainingCoachSessionQueueEntryState::Ready;
	EntryToPromote->ScheduledAtUtc = ResolvedReferenceUtc;
	EntryToPromote->DeferredUntilUtc.Reset();
	EntryToPromote->bCanStartImmediately = true;
	EntryToPromote->SlotIndex = -1;
	if (!StartActionSourceLabel.IsEmpty())
	{
		EntryToPromote->StartActionSourceLabel = StartActionSourceLabel;
	}
	UpdatedQueueState.LastUpdatedAtUtc = ResolvedReferenceUtc;
	return HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(UpdatedQueueState);
}

TArray<FHyperTwistTrainingCoachSessionQueueState> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueStatesForUser(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId
)
{
	TArray<FHyperTwistTrainingCoachSessionQueueState> QueueStates;
	if (UserId.IsEmpty())
	{
		return QueueStates;
	}

	for (const FHyperTwistTrainingCoachSessionQueueState& QueueState : RepositoryState.StoredCoachSessionQueues)
	{
		if (QueueState.UserId == UserId && QueueState.IsStructurallyValid())
		{
			QueueStates.Add(QueueState);
		}
	}

	QueueStates.Sort([](
		const FHyperTwistTrainingCoachSessionQueueState& Left,
		const FHyperTwistTrainingCoachSessionQueueState& Right
	)
	{
		const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
			Left.LastUpdatedAtUtc,
			Right.LastUpdatedAtUtc
		);
		if (UtcCompare != 0)
		{
			return UtcCompare > 0;
		}

		return Left.QueueId < Right.QueueId;
	});
	return QueueStates;
}

TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> UHyperTwistTrainingRepositoryLibrary::ListCoachSessionQueueHistoryEntries(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId,
	const FString& QueueId
)
{
	TArray<FHyperTwistTrainingCoachSessionQueueHistoryEntry> HistoryEntries;
	if (UserId.IsEmpty() || QueueId.IsEmpty())
	{
		return HistoryEntries;
	}

	for (const FHyperTwistTrainingCoachSessionQueueHistoryEntry& HistoryEntry : RepositoryState.StoredCoachSessionQueueHistory)
	{
		if (HistoryEntry.UserId == UserId
			&& HistoryEntry.QueueId == QueueId
			&& HistoryEntry.IsStructurallyValid())
		{
			HistoryEntries.Add(HistoryEntry);
		}
	}

	HistoryEntries.Sort([](
		const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Left,
		const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Right
	)
	{
		const int32 UtcCompare = HyperTwistTrainingRepositoryLibraryInternal::CompareUtcStrings(
			Left.EventAtUtc,
			Right.EventAtUtc
		);
		if (UtcCompare != 0)
		{
			return UtcCompare < 0;
		}

		return Left.HistoryEntryId < Right.HistoryEntryId;
	});
	return HistoryEntries;
}

bool UHyperTwistTrainingRepositoryLibrary::TryGetCoachSessionQueueState(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId,
	const FString& QueueId,
	FHyperTwistTrainingCoachSessionQueueState& OutQueueState
)
{
	OutQueueState = FHyperTwistTrainingCoachSessionQueueState();
	if (UserId.IsEmpty() || QueueId.IsEmpty())
	{
		return false;
	}

	const FHyperTwistTrainingCoachSessionQueueState* MatchingQueueState =
		RepositoryState.StoredCoachSessionQueues.FindByPredicate(
			[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
			{
				return Candidate.UserId == UserId
					&& Candidate.QueueId == QueueId
					&& Candidate.IsStructurallyValid();
			}
		);
	if (MatchingQueueState == nullptr)
	{
		return false;
	}

	OutQueueState = *MatchingQueueState;
	return true;
}

FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::UpsertCoachSessionQueueState(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FHyperTwistTrainingCoachSessionQueueState& QueueState
)
{
	FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
	const FHyperTwistTrainingCoachSessionQueueState NormalizedQueueState =
		HyperTwistTrainingRepositoryLibraryInternal::NormalizeCoachSessionQueueState(QueueState);
	if (!NormalizedQueueState.IsStructurallyValid())
	{
		return UpdatedRepositoryState;
	}

	const FHyperTwistTrainingCoachSessionQueueState* ExistingQueueState =
		UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
			[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
			{
				return Candidate.UserId == NormalizedQueueState.UserId
					&& Candidate.QueueId == NormalizedQueueState.QueueId;
			}
		);

	for (const FHyperTwistTrainingCoachSessionQueueStateEntry& CurrentEntry : NormalizedQueueState.Entries)
	{
		const FHyperTwistTrainingCoachSessionQueueStateEntry* PreviousEntry = ExistingQueueState != nullptr
			? ExistingQueueState->Entries.FindByPredicate(
				[&CurrentEntry](const FHyperTwistTrainingCoachSessionQueueStateEntry& Candidate)
				{
					return Candidate.EntryId == CurrentEntry.EntryId;
				}
			)
			: nullptr;
		if (PreviousEntry != nullptr && PreviousEntry->WorkFingerprint != CurrentEntry.WorkFingerprint)
		{
			PreviousEntry = nullptr;
		}
		if (PreviousEntry != nullptr
			&& !HyperTwistTrainingRepositoryLibraryInternal::DidCoachSessionQueueEntryChange(
				*PreviousEntry,
				CurrentEntry
			))
		{
			continue;
		}

		const FHyperTwistTrainingCoachSessionQueueHistoryEntry HistoryEntry =
			HyperTwistTrainingRepositoryLibraryInternal::BuildCoachSessionQueueHistoryEntry(
				NormalizedQueueState,
				CurrentEntry,
				PreviousEntry
			);
		if (!HistoryEntry.IsStructurallyValid())
		{
			continue;
		}

		const bool bAlreadyExists =
			UpdatedRepositoryState.StoredCoachSessionQueueHistory.ContainsByPredicate(
				[&HistoryEntry](const FHyperTwistTrainingCoachSessionQueueHistoryEntry& Candidate)
				{
					return Candidate.HistoryEntryId == HistoryEntry.HistoryEntryId;
				}
			);
		if (!bAlreadyExists)
		{
			UpdatedRepositoryState.StoredCoachSessionQueueHistory.Add(HistoryEntry);
		}
	}

	FHyperTwistTrainingCoachSessionQueueState* MutableExistingQueueState =
		UpdatedRepositoryState.StoredCoachSessionQueues.FindByPredicate(
			[&NormalizedQueueState](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
			{
				return Candidate.UserId == NormalizedQueueState.UserId
					&& Candidate.QueueId == NormalizedQueueState.QueueId;
			}
		);
	if (MutableExistingQueueState != nullptr)
	{
		*MutableExistingQueueState = NormalizedQueueState;
	}
	else
	{
		UpdatedRepositoryState.StoredCoachSessionQueues.Add(NormalizedQueueState);
	}

	return UpdatedRepositoryState;
}

FHyperTwistTrainingRepositoryState UHyperTwistTrainingRepositoryLibrary::RemoveCoachSessionQueueState(
	const FHyperTwistTrainingRepositoryState& RepositoryState,
	const FString& UserId,
	const FString& QueueId
)
{
	if (UserId.IsEmpty() || QueueId.IsEmpty())
	{
		return RepositoryState;
	}

	FHyperTwistTrainingRepositoryState UpdatedRepositoryState = RepositoryState;
	UpdatedRepositoryState.StoredCoachSessionQueues.RemoveAll(
		[&UserId, &QueueId](const FHyperTwistTrainingCoachSessionQueueState& Candidate)
		{
			return Candidate.UserId == UserId && Candidate.QueueId == QueueId;
		}
	);
	return UpdatedRepositoryState;
}
