#include "HyperTwistTraining/HyperTwistTrainingPublicationLibrary.h"

namespace HyperTwistTrainingPublicationLibraryInternal
{
	const FString PublicationFeatureId = TEXT("feature/training/competitive-publication");
	const FString PublicationGateId = TEXT("gate/training/competitive-publication");

	bool ArrayContainsCaseInsensitive(const TArray<FString>& Values, const FString& Target)
	{
		for (const FString& Value : Values)
		{
			if (Value.Equals(Target, ESearchCase::IgnoreCase))
			{
				return true;
			}
		}

		return false;
	}

	bool ArrayContainsSubstringCaseInsensitive(const TArray<FString>& Values, const FString& Fragment)
	{
		for (const FString& Value : Values)
		{
			if (Value.Contains(Fragment, ESearchCase::IgnoreCase))
			{
				return true;
			}
		}

		return false;
	}

	void AppendUniqueTag(TArray<FString>& Tags, const FString& Tag)
	{
		if (Tag.IsEmpty() || Tags.Contains(Tag))
		{
			return;
		}

		Tags.Add(Tag);
	}

	FString ResolveUserId(
		const FHyperTwistTrainingLearnerProfile& LearnerProfile,
		const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary
	)
	{
		if (!LearnerProfile.UserId.IsEmpty())
		{
			return LearnerProfile.UserId;
		}
		if (!DeckHistorySummary.UserId.IsEmpty())
		{
			return DeckHistorySummary.UserId;
		}
		return PersonalBestSummary.UserId;
	}

	FString ResolveDeckId(
		const FHyperTwistTrainingDeck& Deck,
		const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary
	)
	{
		if (!Deck.DeckId.IsEmpty())
		{
			return Deck.DeckId;
		}
		if (!DeckHistorySummary.DeckId.IsEmpty())
		{
			return DeckHistorySummary.DeckId;
		}
		return PersonalBestSummary.DeckId;
	}

	FString ResolveDeckPuzzleId(const FHyperTwistTrainingDeck& Deck)
	{
		for (const FHyperTwistTrainingCase& TrainingCase : Deck.Cases)
		{
			if (TrainingCase.IsStructurallyValid())
			{
				return TrainingCase.PuzzleId;
			}
		}

		return FString();
	}

	FString ResolveLifecycleStageId(const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract)
	{
		static const TArray<FString> PreferredStageOrder =
		{
			TEXT("general-availability"),
			TEXT("beta"),
			TEXT("alpha"),
			TEXT("concept"),
			TEXT("draft"),
			TEXT("archived")
		};

		for (const FString& StageId : PreferredStageOrder)
		{
			if (ArrayContainsCaseInsensitive(EarlyAccessContract.ActiveStages, StageId))
			{
				return StageId;
			}
		}

		for (const FString& StageId : PreferredStageOrder)
		{
			if (ArrayContainsCaseInsensitive(EarlyAccessContract.LifecycleStages, StageId))
			{
				return StageId;
			}
		}

		return EarlyAccessContract.ActiveStages.Num() > 0
			? EarlyAccessContract.ActiveStages[0]
			: (EarlyAccessContract.LifecycleStages.Num() > 0 ? EarlyAccessContract.LifecycleStages[0] : FString());
	}

	bool IsVisibleLifecycleStage(const FString& LifecycleStageId)
	{
		return LifecycleStageId.Equals(TEXT("alpha"), ESearchCase::IgnoreCase)
			|| LifecycleStageId.Equals(TEXT("beta"), ESearchCase::IgnoreCase)
			|| LifecycleStageId.Equals(TEXT("general-availability"), ESearchCase::IgnoreCase);
	}

	FHyperTwistTrainingEntitlementGateState BuildEntitlementGateState(
		const FHyperTwistTrainingLearnerProfile& LearnerProfile,
		const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
		const FHyperTwistTrainingFeatureGovernanceContract& FeatureGovernanceContract,
		const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract
	)
	{
		FHyperTwistTrainingEntitlementGateState Gate;
		Gate.GateId = PublicationGateId;
		Gate.FeatureId = PublicationFeatureId;
		Gate.Title = TEXT("Competitive publication and leaderboard access");
		Gate.FeatureGovernanceContractId = FeatureGovernanceContract.ContractId;
		Gate.EarlyAccessContractId = EarlyAccessContract.ContractId;
		Gate.LifecycleStageId = ResolveLifecycleStageId(EarlyAccessContract);
		Gate.EnrollmentModeLabel =
			ArrayContainsSubstringCaseInsensitive(EarlyAccessContract.EnrollmentRules, TEXT("opt-in"))
				? TEXT("opt-in")
				: TEXT("automatic");
		Gate.ApprovedWorkflowTags = FeatureGovernanceContract.ApprovedWorkflowTags;
		Gate.bRequiresOptIn = Gate.EnrollmentModeLabel.Equals(TEXT("opt-in"), ESearchCase::CaseSensitive);

		const bool bHasGovernanceSupport =
			ArrayContainsCaseInsensitive(FeatureGovernanceContract.ApprovedWorkflowTags, TEXT("feature-governance"));
		const bool bHasVisibleLifecycleStage = IsVisibleLifecycleStage(Gate.LifecycleStageId);
		const bool bHasActivity = LearnerProfile.TotalSessionCount > 0 || DeckHistorySummary.SessionCount > 0;
		const bool bHasEarnedPublication =
			LearnerProfile.CompletedSessionCount > 0
			|| DeckHistorySummary.CompletedSessionCount > 0
			|| PersonalBestSummary.bHasTimedPersonalBest;

		Gate.bVisible = bHasGovernanceSupport && bHasVisibleLifecycleStage && bHasActivity;
		Gate.bEntitled = Gate.bVisible && bHasEarnedPublication;
		Gate.VisibilityLabel = Gate.bEntitled
			? TEXT("published")
			: (Gate.bVisible ? TEXT("preview") : TEXT("hidden"));
		return Gate;
	}

	EHyperTwistTrainingPublicationVisibility ResolvePublicationVisibility(
		const FHyperTwistTrainingEntitlementGateState& GateState
	)
	{
		if (GateState.bEntitled)
		{
			return EHyperTwistTrainingPublicationVisibility::Published;
		}
		if (GateState.bVisible)
		{
			return EHyperTwistTrainingPublicationVisibility::Preview;
		}
		return EHyperTwistTrainingPublicationVisibility::Hidden;
	}

	void ResolveHighlightValues(
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
		FString& OutPrimaryValue,
		FString& OutSecondaryValue
	)
	{
		OutPrimaryValue.Reset();
		OutSecondaryValue.Reset();
		if (PersonalBestSummary.bHasTimedPersonalBest && PersonalBestSummary.BestSingleTotalTimeMs > 0)
		{
			const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
				UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
					PersonalBestSummary.BestSingleTotalTimeMs
				);
			OutPrimaryValue = FormattedValue.PrimaryLabel;
			OutSecondaryValue = FormattedValue.SecondaryLabel;
		}
	}

	FHyperTwistTrainingProfilePublication BuildProfilePublication(
		const FHyperTwistTrainingLearnerProfile& LearnerProfile,
		const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
		const FHyperTwistTrainingDeck& Deck,
		const FHyperTwistTrainingEntitlementGateState& GateState
	)
	{
		FHyperTwistTrainingProfilePublication Publication;
		Publication.UserId = ResolveUserId(LearnerProfile, DeckHistorySummary, PersonalBestSummary);
		Publication.DeckId = ResolveDeckId(Deck, DeckHistorySummary, PersonalBestSummary);
		Publication.DeckTitle = Deck.Title;
		Publication.PublicationId = FString::Printf(TEXT("publication/%s/%s"), *Publication.UserId, *Publication.DeckId);
		Publication.DisplayName = Publication.UserId;
		Publication.Visibility = ResolvePublicationVisibility(GateState);
		Publication.Headline = Publication.Visibility == EHyperTwistTrainingPublicationVisibility::Published
			? FString::Printf(
				TEXT("%d completed sessions, %d day streak"),
				LearnerProfile.CompletedSessionCount,
				LearnerProfile.ActiveTrainingDayStreak
			)
			: (Publication.Visibility == EHyperTwistTrainingPublicationVisibility::Preview
				? TEXT("Preview publication is ready for local review")
				: TEXT("Complete training to unlock publication"));
		Publication.SummaryLine = FString::Printf(
			TEXT("%d deck sessions, %d attempts, %.0f%% success rate"),
			DeckHistorySummary.SessionCount,
			DeckHistorySummary.TotalAttemptCount,
			LearnerProfile.SuccessRate
		);
		Publication.HighlightLabel = PersonalBestSummary.bHasTimedPersonalBest ? TEXT("Best single") : TEXT("Activity");
		ResolveHighlightValues(
			PersonalBestSummary,
			Publication.HighlightPrimaryValue,
			Publication.HighlightSecondaryValue
		);
		if (Publication.HighlightPrimaryValue.IsEmpty())
		{
			Publication.HighlightPrimaryValue = FString::Printf(
				TEXT("%d sessions"),
				LearnerProfile.TotalSessionCount
			);
		}
		Publication.LastPublishedAtUtc = !PersonalBestSummary.LastPersonalBestAtUtc.IsEmpty()
			? PersonalBestSummary.LastPersonalBestAtUtc
			: LearnerProfile.LastTrainingAtUtc;
		for (const FString& Tag : Deck.Tags)
		{
			AppendUniqueTag(Publication.Tags, Tag);
		}
		AppendUniqueTag(Publication.Tags, GateState.LifecycleStageId);
		AppendUniqueTag(Publication.Tags, TEXT("competitive-publication"));
		return Publication;
	}

	bool TryBuildEntryMetric(
		const FHyperTwistTrainingLeaderboardEventReference& EventReference,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
		FString& OutPrimaryValue,
		FString& OutSecondaryValue
	)
	{
		OutPrimaryValue.Reset();
		OutSecondaryValue.Reset();

		if (EventReference.FormatId.Equals(TEXT("single"), ESearchCase::IgnoreCase)
			&& PersonalBestSummary.bHasTimedPersonalBest
			&& PersonalBestSummary.BestSingleTotalTimeMs > 0)
		{
			const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
				UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
					PersonalBestSummary.BestSingleTotalTimeMs
				);
			OutPrimaryValue = FormattedValue.PrimaryLabel;
			OutSecondaryValue = FormattedValue.SecondaryLabel;
			return !OutPrimaryValue.IsEmpty();
		}

		if ((EventReference.FormatId.Contains(TEXT("ao"), ESearchCase::IgnoreCase)
				|| EventReference.FormatId.Contains(TEXT("mean"), ESearchCase::IgnoreCase))
			&& PersonalBestSummary.BestSessionAverageTotalTimeMs > 0)
		{
			const FHyperTwistTrainingLeaderboardFormattedValue FormattedValue =
				UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(
					PersonalBestSummary.BestSessionAverageTotalTimeMs
				);
			OutPrimaryValue = FormattedValue.PrimaryLabel;
			OutSecondaryValue = FormattedValue.SecondaryLabel;
			return !OutPrimaryValue.IsEmpty();
		}

		return false;
	}

	FHyperTwistTrainingLeaderboardBundle BuildLeaderboardBundle(
		const FHyperTwistTrainingProfilePublication& Publication,
		const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
		const FHyperTwistTrainingDeck& Deck,
		const FHyperTwistTrainingKnowledgeReferenceBundle& KnowledgeBundle,
		const FHyperTwistTrainingEntitlementGateState& GateState
	)
	{
		FHyperTwistTrainingLeaderboardBundle Leaderboard;
		Leaderboard.BundleId = FString::Printf(TEXT("leaderboard/%s/%s"), *Publication.UserId, *Publication.DeckId);
		Leaderboard.DeckId = Publication.DeckId;
		Leaderboard.DeckTitle = Publication.DeckTitle;
		Leaderboard.ColumnOrder = KnowledgeBundle.LeaderboardDisplayPolicy.ColumnOrder;
		Leaderboard.TimeFormattingRule = KnowledgeBundle.LeaderboardDisplayPolicy.TimeFormattingRule;
		Leaderboard.RelativeDateRule = KnowledgeBundle.LeaderboardDisplayPolicy.RelativeDateRule;
		Leaderboard.SolverProfileRule = KnowledgeBundle.LeaderboardDisplayPolicy.SolverProfileRule;
		Leaderboard.ProgramBadgeRule = KnowledgeBundle.LeaderboardDisplayPolicy.ProgramBadgeRule;
		Leaderboard.bEntitlementVisible = GateState.bVisible;

		const FString DeckPuzzleId = ResolveDeckPuzzleId(Deck);
		const FString RecordedAtUtc = !PersonalBestSummary.LastPersonalBestAtUtc.IsEmpty()
			? PersonalBestSummary.LastPersonalBestAtUtc
			: Publication.LastPublishedAtUtc;

		for (const FHyperTwistTrainingLeaderboardEventReference& EventReference : KnowledgeBundle.LeaderboardEvents)
		{
			if (!EventReference.PuzzleId.Equals(DeckPuzzleId, ESearchCase::IgnoreCase))
			{
				continue;
			}

			++Leaderboard.MatchedEventCount;
			if (Leaderboard.PrimaryEventId.IsEmpty())
			{
				Leaderboard.PrimaryEventId = EventReference.EventId;
				Leaderboard.PrimaryEventTitle = EventReference.Title;
			}

			FString MetricPrimaryValue;
			FString MetricSecondaryValue;
			if (!TryBuildEntryMetric(
					EventReference,
					PersonalBestSummary,
					MetricPrimaryValue,
					MetricSecondaryValue))
			{
				continue;
			}

			FHyperTwistTrainingLeaderboardEntry Entry;
			Entry.EntryId = FString::Printf(TEXT("leaderboard-entry/%s/%s"), *EventReference.EventId, *Publication.UserId);
			Entry.EventId = EventReference.EventId;
			Entry.EventTitle = EventReference.Title;
			Entry.MetricLabel = EventReference.MetricLabel;
			Entry.SolverDisplayName = Publication.DisplayName;
			Entry.ProgramLabel = EventReference.PreferredProgramIds.Num() > 0
				? EventReference.PreferredProgramIds[0]
				: TEXT("Local");
			Entry.RecordedAtUtc = RecordedAtUtc;
			Entry.RelativeDateLabel = !RecordedAtUtc.IsEmpty()
				? UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardRelativeDateLabel(RecordedAtUtc)
				: TEXT("n/a");
			Entry.RankLabel = GateState.bEntitled ? TEXT("#1 local") : TEXT("preview");
			Entry.MetricPrimaryValue = MetricPrimaryValue;
			Entry.MetricSecondaryValue = MetricSecondaryValue;
			Entry.DeckId = Publication.DeckId;
			Entry.bEligible = GateState.bVisible;
			Entry.bPublished = GateState.bEntitled;
			Leaderboard.Entries.Add(Entry);
		}

		for (const FHyperTwistTrainingLeaderboardEntry& Entry : Leaderboard.Entries)
		{
			if (Entry.bPublished)
			{
				Leaderboard.bHasPublishedEntries = true;
				break;
			}
		}

		return Leaderboard;
	}
}

FHyperTwistTrainingPublicationBundle UHyperTwistTrainingPublicationLibrary::BuildPublicationBundle(
	const FHyperTwistTrainingLearnerProfile& LearnerProfile,
	const FHyperTwistTrainingDeckHistorySummary& DeckHistorySummary,
	const FHyperTwistTrainingPersonalBestSummary& PersonalBestSummary,
	const FHyperTwistTrainingDeck& Deck,
	const FHyperTwistTrainingKnowledgeReferenceBundle& KnowledgeBundle,
	const FHyperTwistTrainingFeatureGovernanceContract& FeatureGovernanceContract,
	const FHyperTwistTrainingEarlyAccessRolloutContract& EarlyAccessContract
)
{
	FHyperTwistTrainingPublicationBundle Bundle;
	Bundle.UserId = HyperTwistTrainingPublicationLibraryInternal::ResolveUserId(
		LearnerProfile,
		DeckHistorySummary,
		PersonalBestSummary
	);
	Bundle.DeckId = HyperTwistTrainingPublicationLibraryInternal::ResolveDeckId(
		Deck,
		DeckHistorySummary,
		PersonalBestSummary
	);
	Bundle.DeckTitle = Deck.Title;
	Bundle.LearnerProfile = LearnerProfile;
	Bundle.DeckHistorySummary = DeckHistorySummary;
	Bundle.PersonalBestSummary = PersonalBestSummary;

	if (Bundle.UserId.IsEmpty()
		|| Bundle.DeckId.IsEmpty()
		|| !Deck.IsStructurallyValid()
		|| !KnowledgeBundle.LeaderboardDisplayPolicy.IsStructurallyValid()
		|| !FeatureGovernanceContract.IsStructurallyValid()
		|| !EarlyAccessContract.IsStructurallyValid())
	{
		return Bundle;
	}

	Bundle.EntitlementGate = HyperTwistTrainingPublicationLibraryInternal::BuildEntitlementGateState(
		LearnerProfile,
		DeckHistorySummary,
		PersonalBestSummary,
		FeatureGovernanceContract,
		EarlyAccessContract
	);
	Bundle.ProfilePublication = HyperTwistTrainingPublicationLibraryInternal::BuildProfilePublication(
		LearnerProfile,
		DeckHistorySummary,
		PersonalBestSummary,
		Deck,
		Bundle.EntitlementGate
	);
	Bundle.Leaderboard = HyperTwistTrainingPublicationLibraryInternal::BuildLeaderboardBundle(
		Bundle.ProfilePublication,
		PersonalBestSummary,
		Deck,
		KnowledgeBundle,
		Bundle.EntitlementGate
	);
	Bundle.bHasMatchedLeaderboardEvents = Bundle.Leaderboard.MatchedEventCount > 0;
	Bundle.ProfilePublication.bLeaderboardVisible =
		Bundle.EntitlementGate.bVisible && Bundle.Leaderboard.Entries.Num() > 0;
	return Bundle;
}
