#include "HyperTwistTraining/HyperTwistTrainingKnowledgeLibrary.h"

#include "Containers/UnrealString.h"

namespace HyperTwistTrainingKnowledgeLibraryInternal
{
	FHyperTwistTrainingSourceAttribution MakeHypercubingAttribution(
		const FString& TrackId,
		const FString& Notes
	)
	{
		FHyperTwistTrainingSourceAttribution Attribution;
		Attribution.SourceMode = EHyperTwistTrainingSourceMode::DirectDonor;
		Attribution.SourceRepo = TEXT("Hypercubers/hypercubing.xyz");
		Attribution.SourceLicense = TEXT("MIT");
		Attribution.SourceTrackId = TrackId;
		Attribution.SourceNotes = Notes;
		return Attribution;
	}

	FHyperTwistTrainingKnowledgeTaxonomyNode MakeTaxonomyNode(
		const FString& NodeId,
		const EHyperTwistTrainingKnowledgeNodeKind NodeKind,
		const FString& Title,
		const FString& Summary,
		const FString& ParentNodeId,
		const FString& PuzzleFamily,
		const TArray<FString>& Tags,
		const TArray<FString>& RelatedNodeIds,
		const FString& Notes
	)
	{
		FHyperTwistTrainingKnowledgeTaxonomyNode Node;
		Node.NodeId = NodeId;
		Node.NodeKind = NodeKind;
		Node.Title = Title;
		Node.Summary = Summary;
		Node.ParentNodeId = ParentNodeId;
		Node.PuzzleFamily = PuzzleFamily;
		Node.Tags = Tags;
		Node.RelatedNodeIds = RelatedNodeIds;
		Node.SourceAttribution = MakeHypercubingAttribution(TEXT("knowledge-taxonomy"), Notes);
		return Node;
	}

	FHyperTwistTrainingNotationReferenceTerm MakeNotationTerm(
		const FString& TermId,
		const FString& DisplayTerm,
		const FString& Scope,
		const FString& Meaning,
		const FString& Example,
		const TArray<FString>& RelatedTermIds,
		const TArray<FString>& Tags,
		const FString& Notes
	)
	{
		FHyperTwistTrainingNotationReferenceTerm Term;
		Term.TermId = TermId;
		Term.DisplayTerm = DisplayTerm;
		Term.Scope = Scope;
		Term.Meaning = Meaning;
		Term.Example = Example;
		Term.RelatedTermIds = RelatedTermIds;
		Term.Tags = Tags;
		Term.SourceAttribution = MakeHypercubingAttribution(TEXT("notation"), Notes);
		return Term;
	}

	FHyperTwistTrainingProgressionStageReference MakeProgressionStage(
		const FString& StageId,
		const FString& Title,
		const FString& Summary,
		const FString& DisplayGroup,
		const int32 DisplayOrder,
		const TArray<FString>& PrerequisiteStageIds,
		const TArray<FString>& FocusNodeIds,
		const FString& PuzzleFamily,
		const TArray<FString>& Tags,
		const FString& Notes
	)
	{
		FHyperTwistTrainingProgressionStageReference Stage;
		Stage.StageId = StageId;
		Stage.Title = Title;
		Stage.Summary = Summary;
		Stage.DisplayGroup = DisplayGroup;
		Stage.DisplayOrder = DisplayOrder;
		Stage.PrerequisiteStageIds = PrerequisiteStageIds;
		Stage.FocusNodeIds = FocusNodeIds;
		Stage.PuzzleFamily = PuzzleFamily;
		Stage.Tags = Tags;
		Stage.SourceAttribution = MakeHypercubingAttribution(TEXT("progression"), Notes);
		return Stage;
	}

	FHyperTwistTrainingSoftwareReference MakeSoftwareReference(
		const FString& SoftwareId,
		const FString& Title,
		const FString& Summary,
		const TArray<FString>& PlatformTags,
		const TArray<FString>& CapabilityTags,
		const TArray<FString>& FocusNodeIds,
		const TArray<FString>& Notes,
		const FString& AttributionNotes
	)
	{
		FHyperTwistTrainingSoftwareReference Reference;
		Reference.SoftwareId = SoftwareId;
		Reference.Title = Title;
		Reference.Summary = Summary;
		Reference.PlatformTags = PlatformTags;
		Reference.CapabilityTags = CapabilityTags;
		Reference.FocusNodeIds = FocusNodeIds;
		Reference.Notes = Notes;
		Reference.SourceAttribution = MakeHypercubingAttribution(TEXT("software"), AttributionNotes);
		return Reference;
	}

	FHyperTwistTrainingLeaderboardSolverProfileReference MakeSolverProfile(
		const FString& SolverId,
		const FString& DisplayName,
		const FString& Notes
	)
	{
		FHyperTwistTrainingLeaderboardSolverProfileReference Profile;
		Profile.SolverId = SolverId;
		Profile.DisplayName = DisplayName;
		Profile.Notes = Notes;
		Profile.Tags = {TEXT("hypercubing-community"), TEXT("leaderboard-profile")};
		Profile.SourceAttribution = MakeHypercubingAttribution(
			TEXT("leaderboards"),
			TEXT("Solver profile fields preserve stable solver id and display name conventions from hypercubing.xyz leaderboard data.")
		);
		return Profile;
	}

	FHyperTwistTrainingLeaderboardEventReference MakeLeaderboardEvent(
		const FString& EventId,
		const FString& PuzzleId,
		const FString& FormatId,
		const FString& Title,
		const FString& MetricLabel,
		const TArray<FString>& PreferredProgramIds,
		const TArray<FString>& Tags
	)
	{
		FHyperTwistTrainingLeaderboardEventReference Event;
		Event.EventId = EventId;
		Event.PuzzleId = PuzzleId;
		Event.FormatId = FormatId;
		Event.Title = Title;
		Event.MetricLabel = MetricLabel;
		Event.PreferredProgramIds = PreferredProgramIds;
		Event.DisplayRuleIds =
		{
			TEXT("columns:event-rank-solver-time-date-program"),
			TEXT("format:trim-leading-zeroes"),
			TEXT("date:relative-label"),
			TEXT("program:badge-append")
		};
		Event.Tags = Tags;
		Event.SourceAttribution = MakeHypercubingAttribution(
			TEXT("leaderboards"),
			TEXT("Event identity, format naming, and display semantics extracted from hypercubing.xyz leaderboard tabs, formats, and generator code.")
		);
		return Event;
	}

	TArray<FHyperTwistTrainingKnowledgeTaxonomyNode> BuildTaxonomyNodes()
	{
		return
		{
			MakeTaxonomyNode(
				TEXT("puzzle/classic-cube"),
				EHyperTwistTrainingKnowledgeNodeKind::PuzzleFamily,
				TEXT("Classic Cube"),
				TEXT("3D cubing foundation work remains the prerequisite base for higher-dimensional and hypercubing progression."),
				TEXT(""),
				TEXT("ClassicCube"),
				{TEXT("3d"), TEXT("foundation"), TEXT("beginner")},
				{TEXT("method/blockbuilding"), TEXT("method/commutators")},
				TEXT("Preserves the progression rule that 3D intuition and at least beginner-level 3x3 solving precede hypercubing lanes.")
			),
			MakeTaxonomyNode(
				TEXT("puzzle/hypercube-4d"),
				EHyperTwistTrainingKnowledgeNodeKind::PuzzleFamily,
				TEXT("4D Hypercubes"),
				TEXT("Virtual 3x3x3x3 is the main entry puzzle for hypercubing, with 2x2x2x2 and larger hypercubes following from that base."),
				TEXT("puzzle/classic-cube"),
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("hypercube"), TEXT("progression-anchor")},
				{TEXT("software/hyperspeedcube"), TEXT("software/magiccube4d")},
				TEXT("Preserves the donor's virtual 3^4 first doctrine and the branch into 2^4, 4^4, physical 2^4, and larger hypercubes.")
			),
			MakeTaxonomyNode(
				TEXT("puzzle/hypercuboid-4d"),
				EHyperTwistTrainingKnowledgeNodeKind::PuzzleFamily,
				TEXT("4D Hypercuboids"),
				TEXT("Hypercuboids are a later 4D specialization that builds on core hypercube intuition and commutator competence."),
				TEXT("puzzle/hypercube-4d"),
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("hypercuboid"), TEXT("specialization")},
				{TEXT("method/commutators")},
				TEXT("Preserves the hypercuboid branch from the donor progression map rather than flattening all 4D families into one track.")
			),
			MakeTaxonomyNode(
				TEXT("puzzle/non-euclidean"),
				EHyperTwistTrainingKnowledgeNodeKind::PuzzleFamily,
				TEXT("Non-Euclidean Puzzles"),
				TEXT("Topology and non-Euclidean geometry puzzles stay as a later specialization under the MagicTile family rather than a first-step runtime owner."),
				TEXT("puzzle/hypercube-4d"),
				TEXT("Other"),
				{TEXT("non-euclidean"), TEXT("topology"), TEXT("specialization")},
				{TEXT("software/magictile")},
				TEXT("Preserves MagicTile's role as the main non-Euclidean comparison lane and keeps it downstream of baseline hypercube understanding.")
			),
			MakeTaxonomyNode(
				TEXT("puzzle/higher-dimensional"),
				EHyperTwistTrainingKnowledgeNodeKind::PuzzleFamily,
				TEXT("5D and Higher"),
				TEXT("5D+ progression remains a separate later lane after stable 4D understanding, starting with 3^5 and then widening outward."),
				TEXT("puzzle/hypercube-4d"),
				TEXT("Hypercube"),
				{TEXT("5d"), TEXT("higher-dimensional"), TEXT("late-lane")},
				{TEXT("software/magicpuzzleultimate"), TEXT("software/magiccube7d")},
				TEXT("Preserves the donor rule that 5D entry should follow 4D familiarity rather than replacing the core 4D teaching order.")
			),
			MakeTaxonomyNode(
				TEXT("method/commutators"),
				EHyperTwistTrainingKnowledgeNodeKind::Method,
				TEXT("Commutators"),
				TEXT("Commutators remain the main reasoning tool for understanding new puzzle families, notation compression, and later specializations."),
				TEXT("puzzle/classic-cube"),
				TEXT("Hypercube"),
				{TEXT("method"), TEXT("theory"), TEXT("notation")},
				{TEXT("method/blockbuilding"), TEXT("theory/turn-metrics")},
				TEXT("Preserves the donor emphasis that new puzzle families often require commutator understanding rather than rote algorithm reuse.")
			),
			MakeTaxonomyNode(
				TEXT("method/blockbuilding"),
				EHyperTwistTrainingKnowledgeNodeKind::Method,
				TEXT("Blockbuilding and Intuitive F2L"),
				TEXT("Blockbuilding and intuitive F2L stay as supportive 3D skills that make the jump into hypercubing easier."),
				TEXT("puzzle/classic-cube"),
				TEXT("ClassicCube"),
				{TEXT("method"), TEXT("3d"), TEXT("foundation")},
				{TEXT("puzzle/hypercube-4d")},
				TEXT("Preserves the donor's recommendation that stronger 3D technique improves the 4D transition without forcing one fixed 3D method.")
			),
			MakeTaxonomyNode(
				TEXT("theory/turn-metrics"),
				EHyperTwistTrainingKnowledgeNodeKind::Theory,
				TEXT("Turn Metrics"),
				TEXT("ATM, ETM, STM, BTM, OBTM, and MC4DTM remain separate reporting lenses rather than one flattened move-count statistic."),
				TEXT("puzzle/hypercube-4d"),
				TEXT("Hypercube"),
				{TEXT("metrics"), TEXT("notation"), TEXT("analysis")},
				{TEXT("software/hyperspeedcube"), TEXT("software/magiccube4d")},
				TEXT("Preserves the donor metric distinctions used by the notation guide and leaderboard/report lane.")
			),
			MakeTaxonomyNode(
				TEXT("software/hyperspeedcube"),
				EHyperTwistTrainingKnowledgeNodeKind::Software,
				TEXT("Hyperspeedcube"),
				TEXT("Primary beginner-friendly hypercubing software with cross-platform coverage, customizable controls, piece filters, and strong 3D/4D workflow support."),
				TEXT(""),
				TEXT("Hypercube"),
				{TEXT("software"), TEXT("primary"), TEXT("4d"), TEXT("web")},
				{TEXT("puzzle/hypercube-4d"), TEXT("puzzle/higher-dimensional")},
				TEXT("Preserves the donor recommendation that Hyperspeedcube is the main software starting point.")
			),
			MakeTaxonomyNode(
				TEXT("software/magiccube4d"),
				EHyperTwistTrainingKnowledgeNodeKind::Software,
				TEXT("Magic Cube 4D"),
				TEXT("Legacy 4D simulator that still matters for macro support, alternative interaction grammar, and broad 4D workflow comparison."),
				TEXT(""),
				TEXT("Hypercube"),
				{TEXT("software"), TEXT("4d"), TEXT("macro")},
				{TEXT("puzzle/hypercube-4d"), TEXT("theory/turn-metrics")},
				TEXT("Preserves the donor role of MC4D as a strong comparative tool rather than discarding it because Hyperspeedcube is newer.")
			),
			MakeTaxonomyNode(
				TEXT("software/magictile"),
				EHyperTwistTrainingKnowledgeNodeKind::Software,
				TEXT("MagicTile"),
				TEXT("Primary non-Euclidean software anchor for torus, Klein bottle, hyperbolic, and other topology-heavy puzzle families."),
				TEXT(""),
				TEXT("Other"),
				{TEXT("software"), TEXT("non-euclidean"), TEXT("topology")},
				{TEXT("puzzle/non-euclidean")},
				TEXT("Preserves the donor recommendation that MagicTile owns the non-Euclidean lane.")
			),
			MakeTaxonomyNode(
				TEXT("software/magicpuzzleultimate"),
				EHyperTwistTrainingKnowledgeNodeKind::Software,
				TEXT("Magic Puzzle Ultimate"),
				TEXT("Later specialized 3D+ and 5D+ software comparison lane with doctrinaire puzzle breadth and custom definition value."),
				TEXT(""),
				TEXT("Other"),
				{TEXT("software"), TEXT("5d"), TEXT("custom-puzzles")},
				{TEXT("puzzle/higher-dimensional"), TEXT("puzzle/hypercuboid-4d")},
				TEXT("Preserves the donor framing that MPU belongs to later specialization and puzzle-definition exploration.")
			),
			MakeTaxonomyNode(
				TEXT("community/hypercubing-reference"),
				EHyperTwistTrainingKnowledgeNodeKind::Community,
				TEXT("Hypercubing Reference Layer"),
				TEXT("Community taxonomy, notation, software comparison, and leaderboard conventions form one coherent reference plane rather than isolated notes."),
				TEXT(""),
				TEXT("Hypercube"),
				{TEXT("community"), TEXT("knowledge"), TEXT("reference")},
				{TEXT("theory/turn-metrics"), TEXT("software/hyperspeedcube")},
				TEXT("Preserves the donor's value as a knowledge hub, not just as a bundle of unrelated pages.")
			)
		};
	}

	TArray<FHyperTwistTrainingNotationReferenceTerm> BuildNotationTerms()
	{
		return
		{
			MakeNotationTerm(
				TEXT("notation/4d-cells"),
				TEXT("4D Cell Letters"),
				TEXT("4D hypercube"),
				TEXT("The eight 4D hypercube cells use R, L, U, D, F, B, O, and I, with O treated as the positive w-side and I as the hidden opposite."),
				TEXT("R L U D F B O I"),
				{TEXT("notation/4d-rotations"), TEXT("notation/5d-axes")},
				{TEXT("4d"), TEXT("cells"), TEXT("axes")},
				TEXT("Preserves the donor cell naming and positive-side conventions that later notation rules depend on.")
			),
			MakeNotationTerm(
				TEXT("notation/piece-labels"),
				TEXT("Piece Labels"),
				TEXT("4D hypercube"),
				TEXT("Pieces are named by the cells they touch, so a piece on the inside, right, and up cells is labeled IUR."),
				TEXT("IUR"),
				{TEXT("notation/twist-clicks")},
				{TEXT("pieces"), TEXT("labels"), TEXT("4d")},
				TEXT("Preserves the donor rule that piece labels are cell-derived rather than arbitrary aliases.")
			),
			MakeNotationTerm(
				TEXT("notation/twist-clicks"),
				TEXT("Twist Click Notation"),
				TEXT("4D hypercube"),
				TEXT("Twists are named from the piece being clicked, with the first letter telling which sticker is clicked and direction inferred from left or right click conventions."),
				TEXT("RO', IUR, FRUI"),
				{TEXT("notation/piece-labels"), TEXT("notation/wide-moves")},
				{TEXT("twists"), TEXT("4d"), TEXT("execution")},
				TEXT("Preserves the donor twist naming semantics used by MC4D-style cell-turning workflows.")
			),
			MakeNotationTerm(
				TEXT("notation/slice-layers"),
				TEXT("Slice Layer Letters"),
				TEXT("4D hypercube"),
				TEXT("Slice layers use M, E, S, and P, each following the direction convention of the nearer outer side on its axis."),
				TEXT("M follows L, E follows D, S follows F, P follows O"),
				{TEXT("notation/twist-clicks"), TEXT("notation/wide-moves")},
				{TEXT("slices"), TEXT("4d"), TEXT("layers")},
				TEXT("Preserves the donor slice-letter conventions and directional reasoning instead of flattening everything into generic masks.")
			),
			MakeNotationTerm(
				TEXT("notation/wide-moves"),
				TEXT("Wide Moves and Layer Masks"),
				TEXT("4D and 5D+"),
				TEXT("Wide moves use held-layer or mask notation rather than a reused w suffix, so higher-dimensional layer selection stays explicit."),
				TEXT("{2}RO, {2-3}UO', {1-2}Oxz2, {1-2}Gamma-yw2"),
				{TEXT("notation/slice-layers"), TEXT("notation/5d-cell-turns")},
				{TEXT("wide-moves"), TEXT("layers"), TEXT("higher-dimensional")},
				TEXT("Preserves the donor workaround for wide moves once w is already taken by the fourth dimension axis.")
			),
			MakeNotationTerm(
				TEXT("notation/algorithm-shorthand"),
				TEXT("Algorithm Shorthand"),
				TEXT("4D hypercube"),
				TEXT("Algorithm shorthand collapses common O-cell variants and allows star suffixes for any-direction cell twists when exact execution is secondary."),
				TEXT("R U R' U', R*"),
				{TEXT("notation/twist-clicks"), TEXT("notation/commutators")},
				{TEXT("algorithms"), TEXT("shorthand"), TEXT("4d")},
				TEXT("Preserves the donor's compact algorithm-writing conventions without conflating them with full execution notation.")
			),
			MakeNotationTerm(
				TEXT("notation/commutators"),
				TEXT("Commutator Brackets"),
				TEXT("General"),
				TEXT("Bracketed commutator and conjugate notation remains the compact way to express many higher-dimensional algorithms."),
				TEXT("[A, B] and related conjugate forms"),
				{TEXT("notation/algorithm-shorthand")},
				{TEXT("commutators"), TEXT("algorithms"), TEXT("general")},
				TEXT("Preserves the donor's link between higher-dimensional algorithms and commutator-centric reasoning.")
			),
			MakeNotationTerm(
				TEXT("notation/4d-rotations"),
				TEXT("Plane Rotation Notation"),
				TEXT("4D and higher"),
				TEXT("Whole-puzzle rotations are expressed as ordered plane pairs that map one positive axis side to another, avoiding overloaded 3D x/y/z shorthand."),
				TEXT("yw, xz, wx2"),
				{TEXT("notation/4d-cells"), TEXT("notation/5d-axes")},
				{TEXT("rotations"), TEXT("planes"), TEXT("higher-dimensional")},
				TEXT("Preserves the donor plane-based rotation system that generalizes beyond 3D.")
			),
			MakeNotationTerm(
				TEXT("notation/turn-metrics"),
				TEXT("Turn Metrics"),
				TEXT("Analysis"),
				TEXT("ATM, ETM, STM, BTM, OBTM, and MC4DTM remain distinct move-counting systems with different treatment of slices and rotations."),
				TEXT("STM counts slice twists as one move; ETM counts executed twists including rotations"),
				{TEXT("theory/turn-metrics")},
				{TEXT("metrics"), TEXT("analysis"), TEXT("leaderboards")},
				TEXT("Preserves the donor's metric distinctions for analysis, solving, and reporting.")
			),
			MakeNotationTerm(
				TEXT("notation/5d-axes"),
				TEXT("5D and Higher Axis Letters"),
				TEXT("5D and higher"),
				TEXT("5D adds A and P for the v-axis, then continues with Greek-letter side pairs for higher axes so each dimension remains explicit."),
				TEXT("A/P, Gamma/Delta, Theta/Lambda"),
				{TEXT("notation/4d-cells"), TEXT("notation/5d-cell-turns")},
				{TEXT("5d"), TEXT("axes"), TEXT("letters")},
				TEXT("Preserves the donor standardized axis-letter table for 5D through 10D notation.")
			),
			MakeNotationTerm(
				TEXT("notation/5d-cell-turns"),
				TEXT("5D Cell-Turn Notation"),
				TEXT("5D and higher"),
				TEXT("In 5D and higher, a cell twist must name both the cell and the plane within that cell, since two-letter cell names are no longer sufficient."),
				TEXT("Rzy, Gamma-yw2, {1-2}Gamma-yw2"),
				{TEXT("notation/5d-axes"), TEXT("notation/wide-moves")},
				{TEXT("5d"), TEXT("twists"), TEXT("planes")},
				TEXT("Preserves the donor escalation from 4D click notation to explicit plane-qualified cell turns in 5D+.")
			)
		};
	}

	TArray<FHyperTwistTrainingProgressionStageReference> BuildProgressionStages()
	{
		return
		{
			MakeProgressionStage(
				TEXT("progression/getting-started"),
				TEXT("Getting Started with State Manipulation"),
				TEXT("Introductory state-manipulation puzzles like k-Card Game and Loopover remain the soft entry into permutation reasoning before cube notation arrives."),
				TEXT("Getting Started"),
				10,
				{},
				{TEXT("puzzle/classic-cube")},
				TEXT("ClassicCube"),
				{TEXT("entry"), TEXT("permutation"), TEXT("intuition")},
				TEXT("Preserves the donor's early-state reasoning ramp instead of dropping new learners directly into hypercube software.")
			),
			MakeProgressionStage(
				TEXT("progression/beginner-3d-cube"),
				TEXT("Beginner 3x3 Foundation"),
				TEXT("A beginner 3x3 solve, plus stronger optional 3D technique like blockbuilding or intuitive F2L, remains the main prerequisite for hypercubing."),
				TEXT("Getting Started"),
				20,
				{TEXT("progression/getting-started")},
				{TEXT("puzzle/classic-cube"), TEXT("method/blockbuilding")},
				TEXT("ClassicCube"),
				{TEXT("3d"), TEXT("foundation"), TEXT("required")},
				TEXT("Preserves the donor's requirement that learners bring at least beginner 3x3 competency into the hypercubing lane.")
			),
			MakeProgressionStage(
				TEXT("progression/virtual-3x3x3x3"),
				TEXT("Virtual 3x3x3x3"),
				TEXT("Virtual 3x3x3x3 remains the main first real hypercubing stage because it builds 4D intuition without the extra ambiguity of later branches."),
				TEXT("4D Foundations"),
				30,
				{TEXT("progression/beginner-3d-cube")},
				{TEXT("puzzle/hypercube-4d"), TEXT("software/hyperspeedcube")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("entry"), TEXT("anchor")},
				TEXT("Preserves the donor's strongest progression claim: start with virtual 3^4 rather than jumping elsewhere.")
			),
			MakeProgressionStage(
				TEXT("progression/2x2x2x2"),
				TEXT("2x2x2x2"),
				TEXT("2x2x2x2 remains the natural next branch after virtual 3x3x3x3, especially for exploring compact hypercube structure and physical variants."),
				TEXT("4D Foundations"),
				40,
				{TEXT("progression/virtual-3x3x3x3")},
				{TEXT("puzzle/hypercube-4d")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("branch"), TEXT("physical-bridge")},
				TEXT("Preserves the donor flow from 3^4 into 2^4 before more exotic branches.")
			),
			MakeProgressionStage(
				TEXT("progression/4d-larger-hypercubes"),
				TEXT("Larger 4D Hypercubes"),
				TEXT("4x4x4x4 and larger n^4 sizes stay as the deeper hypercube branch after stable virtual 3x3x3x3 understanding."),
				TEXT("4D Foundations"),
				50,
				{TEXT("progression/virtual-3x3x3x3")},
				{TEXT("puzzle/hypercube-4d")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("large-n"), TEXT("advanced")},
				TEXT("Preserves the donor split from 3^4 into both 2^4 and larger hypercubes.")
			),
			MakeProgressionStage(
				TEXT("progression/physical-2x2x2x2"),
				TEXT("Physical 2x2x2x2"),
				TEXT("Physical 2x2x2x2 remains a later derivative stage after virtual 2x2x2x2, not the first exposure to 4D puzzles."),
				TEXT("4D Foundations"),
				60,
				{TEXT("progression/2x2x2x2")},
				{TEXT("puzzle/hypercube-4d")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("physical"), TEXT("derivative")},
				TEXT("Preserves the donor sequencing that puts the physical 2^4 after its virtual counterpart.")
			),
			MakeProgressionStage(
				TEXT("progression/virtual-physical-3x3x3x3"),
				TEXT("Virtual Physical 3x3x3x3"),
				TEXT("Virtualized physical 3x3x3x3 remains a follow-on from physical 2x2x2x2 rather than the main first hypercube entry."),
				TEXT("4D Foundations"),
				70,
				{TEXT("progression/physical-2x2x2x2")},
				{TEXT("puzzle/hypercube-4d"), TEXT("software/hyperspeedcube")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("physical"), TEXT("specialized")},
				TEXT("Preserves the donor physical-to-virtual-physical bridge instead of flattening physical puzzle study into the main entry path.")
			),
			MakeProgressionStage(
				TEXT("progression/other-4d-families"),
				TEXT("Other 4D Families"),
				TEXT("Hypercuboids, duoprisms, and regular 4D polytopes remain a later family branch once the learner can reason with commutators on new puzzles."),
				TEXT("Specializations"),
				80,
				{TEXT("progression/virtual-3x3x3x3")},
				{TEXT("puzzle/hypercuboid-4d"), TEXT("method/commutators")},
				TEXT("Hypercube"),
				{TEXT("4d"), TEXT("specialization"), TEXT("families")},
				TEXT("Preserves the donor split between core hypercubes and other 4D puzzle families.")
			),
			MakeProgressionStage(
				TEXT("progression/non-euclidean"),
				TEXT("Non-Euclidean Puzzles"),
				TEXT("Torus, Klein bottle, hyperbolic, and related topology-heavy puzzles remain a later specialization under the MagicTile lane."),
				TEXT("Specializations"),
				90,
				{TEXT("progression/virtual-3x3x3x3")},
				{TEXT("puzzle/non-euclidean"), TEXT("software/magictile")},
				TEXT("Other"),
				{TEXT("non-euclidean"), TEXT("topology"), TEXT("specialization")},
				TEXT("Preserves the donor non-Euclidean lane as a distinct branch, not a side note inside generic hypercube progression.")
			),
			MakeProgressionStage(
				TEXT("progression/5d-plus"),
				TEXT("5D and Higher"),
				TEXT("5D+ remains a late-stage widening after the learner is comfortable in 4D, starting with 3^5 and then expanding into more dimensions and families."),
				TEXT("Higher Dimensions"),
				100,
				{TEXT("progression/virtual-3x3x3x3")},
				{TEXT("puzzle/higher-dimensional"), TEXT("software/magicpuzzleultimate")},
				TEXT("Hypercube"),
				{TEXT("5d"), TEXT("higher-dimensional"), TEXT("late-lane")},
				TEXT("Preserves the donor rule that 5D entry is conceptually similar to the jump from 3D to 4D and should happen later.")
			)
		};
	}

	TArray<FHyperTwistTrainingSoftwareReference> BuildSoftwareReferences()
	{
		return
		{
			MakeSoftwareReference(
				TEXT("software/hyperspeedcube"),
				TEXT("Hyperspeedcube"),
				TEXT("Primary cross-platform 3D and 4D simulator recommendation with beginner-friendly workflow, customizable controls, and strong filter support."),
				{TEXT("windows"), TEXT("linux"), TEXT("macos"), TEXT("web")},
				{TEXT("mouse-controls"), TEXT("custom-keybinds"), TEXT("piece-finding"), TEXT("piece-filters"), TEXT("color-customization")},
				{TEXT("puzzle/hypercube-4d"), TEXT("puzzle/classic-cube")},
				{
					TEXT("Recommended first download for hypercubing."),
					TEXT("Strong default lane for virtual 3x3x3x3 progression."),
					TEXT("Relevant to both beginner and advanced speedsolving workflows.")
				},
				TEXT("Preserves the donor's first-choice recommendation and capability taxonomy for Hyperspeedcube.")
			),
			MakeSoftwareReference(
				TEXT("software/magiccube4d"),
				TEXT("Magic Cube 4D"),
				TEXT("Legacy 4D simulator retained for macro support, custom puzzle breadth, and workflow comparison against newer tooling."),
				{TEXT("windows"), TEXT("linux"), TEXT("macos")},
				{TEXT("mouse-controls"), TEXT("macros"), TEXT("custom-puzzles"), TEXT("color-customization")},
				{TEXT("puzzle/hypercube-4d")},
				{
					TEXT("Still useful when macro support matters."),
					TEXT("Good comparative lane for legacy interaction grammar."),
					TEXT("Not the primary beginner recommendation, but not discarded.")
				},
				TEXT("Preserves the donor feature-matrix and recommendation nuance around MC4D.")
			),
			MakeSoftwareReference(
				TEXT("software/magictile"),
				TEXT("MagicTile"),
				TEXT("Primary non-Euclidean puzzle software lane with custom puzzle breadth and topology-centric families."),
				{TEXT("windows"), TEXT("linux"), TEXT("macos")},
				{TEXT("mouse-controls"), TEXT("macros"), TEXT("custom-puzzles"), TEXT("color-customization")},
				{TEXT("puzzle/non-euclidean")},
				{
					TEXT("Main software reference for non-Euclidean puzzles."),
					TEXT("Important for torus, Klein bottle, and hyperbolic branches."),
					TEXT("Belongs under topology expansion, not as a runtime foundation owner.")
				},
				TEXT("Preserves the donor MagicTile role and feature-matrix value.")
			),
			MakeSoftwareReference(
				TEXT("software/magicpuzzleultimate"),
				TEXT("Magic Puzzle Ultimate"),
				TEXT("Later specialized simulator for 3D+ and higher-dimensional custom-definition breadth, especially once learners move beyond core hypercubes."),
				{TEXT("windows")},
				{TEXT("mouse-controls"), TEXT("piece-filters"), TEXT("timer"), TEXT("macros"), TEXT("custom-puzzles"), TEXT("color-customization")},
				{TEXT("puzzle/higher-dimensional"), TEXT("puzzle/hypercuboid-4d")},
				{
					TEXT("Relevant once the learner widens into 5D+ or custom families."),
					TEXT("Retained for breadth, not as the default early software lane."),
					TEXT("Supports doctrinaire puzzle-definition comparison work.")
				},
				TEXT("Preserves the donor feature matrix and software-role framing for MPU.")
			),
			MakeSoftwareReference(
				TEXT("software/magiccube7d"),
				TEXT("Magic Cube 7D"),
				TEXT("Specialized large-dimensional hypercube software comparison lane for 4D through 7D breadth and later advanced workflows."),
				{TEXT("windows")},
				{TEXT("mouse-controls"), TEXT("piece-finding"), TEXT("piece-filters"), TEXT("timer"), TEXT("macros"), TEXT("color-customization")},
				{TEXT("puzzle/higher-dimensional")},
				{
					TEXT("Relevant for later 5D+ and large-dimensional branches."),
					TEXT("Feature matrix value includes timer, filters, and macro coverage."),
					TEXT("Not the main beginner lane.")
				},
				TEXT("Preserves the donor comparison role for MC7D in advanced dimensions.")
			)
		};
	}

	TArray<FHyperTwistTrainingLeaderboardSolverProfileReference> BuildSolverProfiles()
	{
		return
		{
			MakeSolverProfile(TEXT("hactar"), TEXT("Andrew Farkas (Hactar)"), TEXT("Core community profile reference retained for stable solver-id and display-name conventions.")),
			MakeSolverProfile(TEXT("grant"), TEXT("Grant Staten"), TEXT("Example solver profile from the donor leaderboard data.")),
			MakeSolverProfile(TEXT("rowan"), TEXT("Rowan Fortier"), TEXT("Example solver profile from the donor leaderboard data.")),
			MakeSolverProfile(TEXT("tetrian"), TEXT("Tetrian"), TEXT("Example solver profile from the donor leaderboard data."))
		};
	}

	TArray<FHyperTwistTrainingLeaderboardEventReference> BuildLeaderboardEvents()
	{
		return
		{
			MakeLeaderboardEvent(
				TEXT("event/3x3x3x3/single"),
				TEXT("3x3x3x3"),
				TEXT("single"),
				TEXT("3x3x3x3 Single"),
				TEXT("Time"),
				{TEXT("HSC"), TEXT("MC4D")},
				{TEXT("4d"), TEXT("hypercube"), TEXT("single")}
			),
			MakeLeaderboardEvent(
				TEXT("event/3x3x3x3/ao5"),
				TEXT("3x3x3x3"),
				TEXT("ao5"),
				TEXT("3x3x3x3 Ao5"),
				TEXT("Time"),
				{TEXT("HSC"), TEXT("MC4D")},
				{TEXT("4d"), TEXT("hypercube"), TEXT("average")}
			),
			MakeLeaderboardEvent(
				TEXT("event/2x2x2x2/single"),
				TEXT("2x2x2x2"),
				TEXT("single"),
				TEXT("2x2x2x2 Single"),
				TEXT("Time"),
				{TEXT("HSC"), TEXT("MC4D")},
				{TEXT("4d"), TEXT("hypercube"), TEXT("single")}
			),
			MakeLeaderboardEvent(
				TEXT("event/2x2x2x2/bld"),
				TEXT("2x2x2x2"),
				TEXT("bld"),
				TEXT("2x2x2x2 Blindfolded"),
				TEXT("Time"),
				{TEXT("HSC"), TEXT("MC4D")},
				{TEXT("4d"), TEXT("blindfolded")}
			),
			MakeLeaderboardEvent(
				TEXT("event/4x4x4x4/single"),
				TEXT("4x4x4x4"),
				TEXT("single"),
				TEXT("4x4x4x4 Single"),
				TEXT("Time"),
				{TEXT("HSC"), TEXT("MC7D")},
				{TEXT("4d"), TEXT("large-n")}
			),
			MakeLeaderboardEvent(
				TEXT("event/3x3x3x3x3/single"),
				TEXT("3x3x3x3x3"),
				TEXT("single"),
				TEXT("3x3x3x3x3 Single"),
				TEXT("Time"),
				{TEXT("MC7D"), TEXT("MPU")},
				{TEXT("5d"), TEXT("higher-dimensional")}
			),
			MakeLeaderboardEvent(
				TEXT("event/hemimegaminx/single"),
				TEXT("hemimegaminx"),
				TEXT("single"),
				TEXT("Hemimegaminx Single"),
				TEXT("Time"),
				{TEXT("MT")},
				{TEXT("non-euclidean"), TEXT("magictile")}
			),
			MakeLeaderboardEvent(
				TEXT("event/phys_2x2x2x2/single"),
				TEXT("phys_2x2x2x2"),
				TEXT("single"),
				TEXT("Physical 2x2x2x2 Single"),
				TEXT("Time"),
				{},
				{TEXT("physical"), TEXT("4d")}
			)
		};
	}

	FHyperTwistTrainingLeaderboardDisplayPolicy BuildLeaderboardDisplayPolicy()
	{
		FHyperTwistTrainingLeaderboardDisplayPolicy Policy;
		Policy.ColumnOrder = {TEXT("event"), TEXT("rank"), TEXT("solver"), TEXT("time"), TEXT("date"), TEXT("program")};
		Policy.TimeFormattingRule =
			TEXT("Times expose both a compact primary label and a verbose secondary label; fewest-moves entries stay numeric instead of duration-formatted.");
		Policy.RelativeDateRule =
			TEXT("Dates retain ISO ordering plus a relative label such as Today, Yesterday, n weeks ago, n months ago, or n years ago.");
		Policy.SolverProfileRule =
			TEXT("Solver profiles preserve a stable solver id and display name so leaderboard rows and profile pages resolve consistently.");
		Policy.ProgramBadgeRule =
			TEXT("Known programs append compact badges for MT, HSC, MPU, MC7D, and MC4D while unknown programs remain plain text.");
		Policy.SourceAttribution = MakeHypercubingAttribution(
			TEXT("leaderboards"),
			TEXT("Leaderboard display policy extracted from hypercubing.xyz generate_leaderboards.py, formats.yml, tabs.yml, and solvers.yml.")
		);
		return Policy;
	}

	bool TryFindTaxonomyNodeById(const FString& NodeId, FHyperTwistTrainingKnowledgeTaxonomyNode& OutNode)
	{
		for (const FHyperTwistTrainingKnowledgeTaxonomyNode& Node : BuildTaxonomyNodes())
		{
			if (Node.NodeId.Equals(NodeId, ESearchCase::CaseSensitive))
			{
				OutNode = Node;
				return true;
			}
		}

		OutNode = FHyperTwistTrainingKnowledgeTaxonomyNode();
		return false;
	}

	bool TryFindNotationTermById(const FString& TermId, FHyperTwistTrainingNotationReferenceTerm& OutTerm)
	{
		for (const FHyperTwistTrainingNotationReferenceTerm& Term : BuildNotationTerms())
		{
			if (Term.TermId.Equals(TermId, ESearchCase::CaseSensitive))
			{
				OutTerm = Term;
				return true;
			}
		}

		OutTerm = FHyperTwistTrainingNotationReferenceTerm();
		return false;
	}

	bool TryFindProgressionStageById(const FString& StageId, FHyperTwistTrainingProgressionStageReference& OutStage)
	{
		for (const FHyperTwistTrainingProgressionStageReference& Stage : BuildProgressionStages())
		{
			if (Stage.StageId.Equals(StageId, ESearchCase::CaseSensitive))
			{
				OutStage = Stage;
				return true;
			}
		}

		OutStage = FHyperTwistTrainingProgressionStageReference();
		return false;
	}

	bool TryFindSoftwareReferenceById(const FString& SoftwareId, FHyperTwistTrainingSoftwareReference& OutReference)
	{
		for (const FHyperTwistTrainingSoftwareReference& Reference : BuildSoftwareReferences())
		{
			if (Reference.SoftwareId.Equals(SoftwareId, ESearchCase::CaseSensitive))
			{
				OutReference = Reference;
				return true;
			}
		}

		OutReference = FHyperTwistTrainingSoftwareReference();
		return false;
	}

	bool TryFindLeaderboardEventById(const FString& EventId, FHyperTwistTrainingLeaderboardEventReference& OutEvent)
	{
		for (const FHyperTwistTrainingLeaderboardEventReference& Event : BuildLeaderboardEvents())
		{
			if (Event.EventId.Equals(EventId, ESearchCase::CaseSensitive))
			{
				OutEvent = Event;
				return true;
			}
		}

		OutEvent = FHyperTwistTrainingLeaderboardEventReference();
		return false;
	}

	FString FormatTwoDigits(const int64 Value)
	{
		return Value < 10 ? FString::Printf(TEXT("0%lld"), Value) : FString::Printf(TEXT("%lld"), Value);
	}

	FString FormatThreeDigits(const int64 Value)
	{
		if (Value < 10)
		{
			return FString::Printf(TEXT("00%lld"), Value);
		}
		if (Value < 100)
		{
			return FString::Printf(TEXT("0%lld"), Value);
		}

		return FString::Printf(TEXT("%lld"), Value);
	}

	bool TryParseIsoCalendarDate(const FString& IsoDate, FDateTime& OutDate)
	{
		if (IsoDate.Len() < 10)
		{
			return false;
		}

		const FString YearString = IsoDate.Mid(0, 4);
		const FString MonthString = IsoDate.Mid(5, 2);
		const FString DayString = IsoDate.Mid(8, 2);

		int32 Year = 0;
		int32 Month = 0;
		int32 Day = 0;
		if (!LexTryParseString(Year, *YearString)
			|| !LexTryParseString(Month, *MonthString)
			|| !LexTryParseString(Day, *DayString))
		{
			return false;
		}

		if (Year <= 0 || Month <= 0 || Month > 12 || Day <= 0 || Day > 31)
		{
			return false;
		}

		OutDate = FDateTime(Year, Month, Day);
		return true;
	}

	FHyperTwistTrainingLeaderboardFormattedValue FormatDurationMilliseconds(const int32 DurationMilliseconds)
	{
		FHyperTwistTrainingLeaderboardFormattedValue Value;
		const int64 ClampedMilliseconds = FMath::Max(DurationMilliseconds, 0);
		const int64 TotalSeconds = ClampedMilliseconds / 1000;
		const int64 Milliseconds = ClampedMilliseconds % 1000;
		const int64 Days = TotalSeconds / 86400;
		const int64 Hours = (TotalSeconds % 86400) / 3600;
		const int64 Minutes = (TotalSeconds % 3600) / 60;
		const int64 Seconds = TotalSeconds % 60;
		const int64 Centiseconds = Milliseconds / 10;

		if (Days > 0)
		{
			Value.PrimaryLabel = FString::Printf(
				TEXT("%lld:%s:%s.%s"),
				Days * 24 + Hours,
				*FormatTwoDigits(Minutes),
				*FormatTwoDigits(Seconds),
				*FormatTwoDigits(Centiseconds)
			);
		}
		else if (Hours > 0)
		{
			Value.PrimaryLabel = FString::Printf(
				TEXT("%lld:%s:%s.%s"),
				Hours,
				*FormatTwoDigits(Minutes),
				*FormatTwoDigits(Seconds),
				*FormatTwoDigits(Centiseconds)
			);
		}
		else if (Minutes > 0)
		{
			Value.PrimaryLabel = FString::Printf(
				TEXT("%lld:%s.%s"),
				Minutes,
				*FormatTwoDigits(Seconds),
				*FormatTwoDigits(Centiseconds)
			);
		}
		else
		{
			Value.PrimaryLabel = FString::Printf(TEXT("%lld.%s"), Seconds, *FormatTwoDigits(Centiseconds));
		}

		const FString MillisecondsLabel = FString::Printf(TEXT("%s ms"), *FormatThreeDigits(Milliseconds));
		if (Days > 0)
		{
			Value.SecondaryLabel = FString::Printf(
				TEXT("%lld d %lld h %s m %s s %s"),
				Days,
				Hours,
				*FormatTwoDigits(Minutes),
				*FormatTwoDigits(Seconds),
				*MillisecondsLabel
			);
		}
		else if (Hours > 0)
		{
			Value.SecondaryLabel = FString::Printf(
				TEXT("%lld h %s m %s s %s"),
				Hours,
				*FormatTwoDigits(Minutes),
				*FormatTwoDigits(Seconds),
				*MillisecondsLabel
			);
		}
		else if (Minutes > 0)
		{
			Value.SecondaryLabel = FString::Printf(
				TEXT("%lld m %s s %s"),
				Minutes,
				*FormatTwoDigits(Seconds),
				*MillisecondsLabel
			);
		}
		else
		{
			Value.SecondaryLabel = FString::Printf(TEXT("%lld s %s"), Seconds, *MillisecondsLabel);
		}

		return Value;
	}
}

FHyperTwistTrainingKnowledgeReferenceBundle UHyperTwistTrainingKnowledgeLibrary::MakeHypercubingKnowledgeReferenceBundle()
{
	FHyperTwistTrainingKnowledgeReferenceBundle Bundle;
	Bundle.TaxonomyNodes = ListHypercubingTaxonomyNodes();
	Bundle.NotationTerms = ListHypercubingNotationTerms();
	Bundle.ProgressionStages = ListHypercubingProgressionStages();
	Bundle.SoftwareReferences = ListHypercubingSoftwareReferences();
	Bundle.SolverProfiles = ListHypercubingLeaderboardSolverProfiles();
	Bundle.LeaderboardEvents = ListHypercubingLeaderboardEvents();
	Bundle.LeaderboardDisplayPolicy = GetHypercubingLeaderboardDisplayPolicy();
	return Bundle;
}

TArray<FHyperTwistTrainingKnowledgeTaxonomyNode> UHyperTwistTrainingKnowledgeLibrary::ListHypercubingTaxonomyNodes()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildTaxonomyNodes();
}

TArray<FHyperTwistTrainingNotationReferenceTerm> UHyperTwistTrainingKnowledgeLibrary::ListHypercubingNotationTerms()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildNotationTerms();
}

TArray<FHyperTwistTrainingProgressionStageReference> UHyperTwistTrainingKnowledgeLibrary::ListHypercubingProgressionStages()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildProgressionStages();
}

TArray<FHyperTwistTrainingSoftwareReference> UHyperTwistTrainingKnowledgeLibrary::ListHypercubingSoftwareReferences()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildSoftwareReferences();
}

TArray<FHyperTwistTrainingLeaderboardSolverProfileReference>
UHyperTwistTrainingKnowledgeLibrary::ListHypercubingLeaderboardSolverProfiles()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildSolverProfiles();
}

TArray<FHyperTwistTrainingLeaderboardEventReference> UHyperTwistTrainingKnowledgeLibrary::ListHypercubingLeaderboardEvents()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildLeaderboardEvents();
}

FHyperTwistTrainingLeaderboardDisplayPolicy UHyperTwistTrainingKnowledgeLibrary::GetHypercubingLeaderboardDisplayPolicy()
{
	return HyperTwistTrainingKnowledgeLibraryInternal::BuildLeaderboardDisplayPolicy();
}

bool UHyperTwistTrainingKnowledgeLibrary::TryGetHypercubingTaxonomyNode(
	const FString& NodeId,
	FHyperTwistTrainingKnowledgeTaxonomyNode& OutNode
)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::TryFindTaxonomyNodeById(NodeId, OutNode);
}

bool UHyperTwistTrainingKnowledgeLibrary::TryGetHypercubingNotationTerm(
	const FString& TermId,
	FHyperTwistTrainingNotationReferenceTerm& OutTerm
)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::TryFindNotationTermById(TermId, OutTerm);
}

bool UHyperTwistTrainingKnowledgeLibrary::TryGetHypercubingProgressionStage(
	const FString& StageId,
	FHyperTwistTrainingProgressionStageReference& OutStage
)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::TryFindProgressionStageById(StageId, OutStage);
}

bool UHyperTwistTrainingKnowledgeLibrary::TryGetHypercubingSoftwareReference(
	const FString& SoftwareId,
	FHyperTwistTrainingSoftwareReference& OutReference
)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::TryFindSoftwareReferenceById(SoftwareId, OutReference);
}

bool UHyperTwistTrainingKnowledgeLibrary::TryGetHypercubingLeaderboardEvent(
	const FString& EventId,
	FHyperTwistTrainingLeaderboardEventReference& OutEvent
)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::TryFindLeaderboardEventById(EventId, OutEvent);
}

FHyperTwistTrainingLeaderboardFormattedValue
UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardDurationMilliseconds(const int32 DurationMilliseconds)
{
	return HyperTwistTrainingKnowledgeLibraryInternal::FormatDurationMilliseconds(DurationMilliseconds);
}

FHyperTwistTrainingLeaderboardFormattedValue
UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardMoveCount(const int32 MoveCount)
{
	FHyperTwistTrainingLeaderboardFormattedValue Value;
	Value.PrimaryLabel = FString::FromInt(FMath::Max(MoveCount, 0));
	Value.SecondaryLabel = TEXT("moves");
	Value.bIsMoveCount = true;
	return Value;
}

FString UHyperTwistTrainingKnowledgeLibrary::FormatHypercubingLeaderboardRelativeDateLabel(const FString& IsoDate)
{
	FDateTime ParsedDate;
	if (!HyperTwistTrainingKnowledgeLibraryInternal::TryParseIsoCalendarDate(IsoDate, ParsedDate))
	{
		return IsoDate;
	}

	const FDateTime TodayUtc = FDateTime::UtcNow().GetDate();
	const FTimespan Difference = TodayUtc - ParsedDate;
	const int64 Days = Difference.GetDays();
	if (Days < 0)
	{
		return TEXT("Future");
	}
	if (Days == 0)
	{
		return TEXT("Today");
	}
	if (Days == 1)
	{
		return TEXT("Yesterday");
	}
	if (Days >= 7 && Days < 30)
	{
		const int64 Weeks = Days / 7;
		return FString::Printf(TEXT("%lld week%s ago"), Weeks, Weeks == 1 ? TEXT("") : TEXT("s"));
	}
	if (Days >= 30 && Days < 365)
	{
		const int64 Months = Days / 30;
		return FString::Printf(TEXT("%lld month%s ago"), Months, Months == 1 ? TEXT("") : TEXT("s"));
	}
	if (Days >= 365)
	{
		const int64 Years = Days / 365;
		return FString::Printf(TEXT("%lld year%s ago"), Years, Years == 1 ? TEXT("") : TEXT("s"));
	}

	return FString::Printf(TEXT("%lld days ago"), Days);
}

FString UHyperTwistTrainingKnowledgeLibrary::ResolveHypercubingProgramBadge(const FString& ProgramId)
{
	if (ProgramId.Equals(TEXT("MT"), ESearchCase::IgnoreCase))
	{
		return TEXT("programs-MT");
	}
	if (ProgramId.Equals(TEXT("HSC"), ESearchCase::IgnoreCase))
	{
		return TEXT("programs-HSC1");
	}
	if (ProgramId.Equals(TEXT("MPU"), ESearchCase::IgnoreCase))
	{
		return TEXT("programs-MPU");
	}
	if (ProgramId.Equals(TEXT("MC7D"), ESearchCase::IgnoreCase)
		|| ProgramId.Equals(TEXT("MC7D-KB"), ESearchCase::IgnoreCase)
		|| ProgramId.Equals(TEXT("MC7D+MKB"), ESearchCase::IgnoreCase))
	{
		return TEXT("programs-MC7D");
	}
	if (ProgramId.Equals(TEXT("MC4D"), ESearchCase::IgnoreCase))
	{
		return TEXT("programs-MC4D");
	}

	return FString();
}
