#include "HyperTwistTraining/HyperTwistTrainingClassicCubingLibrary.h"

namespace HyperTwistTrainingClassicCubingLibraryInternal
{
	const TCHAR* CubingLicense = TEXT("MPL-2.0 OR GPL-3.0-or-later");

	FHyperTwistTrainingSourceAttribution MakeAttribution(
		const FString& TrackId,
		const FString& Notes
	)
	{
		FHyperTwistTrainingSourceAttribution Attribution;
		Attribution.SourceMode = EHyperTwistTrainingSourceMode::DirectDonor;
		Attribution.SourceRepo = TEXT("cubing/cubing.js");
		Attribution.SourceLicense = CubingLicense;
		Attribution.SourceTrackId = TrackId;
		Attribution.SourceNotes = Notes;
		return Attribution;
	}

	FHyperTwistTrainingClassicCubingPackageReference MakePackageReference(
		const FString& PackageId,
		const FString& Label,
		const FString& ExportPath,
		const FString& OwnershipRole,
		const FString& LicensePosture,
		const FString& Summary,
		const TArray<FString>& RetainedSurfaceTags,
		const TArray<FString>& RequiredNoticeActions,
		const TArray<FString>& ExplicitExclusions,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingClassicCubingPackageReference Reference;
		Reference.PackageId = PackageId;
		Reference.Label = Label;
		Reference.ExportPath = ExportPath;
		Reference.OwnershipRole = OwnershipRole;
		Reference.LicensePosture = LicensePosture;
		Reference.Summary = Summary;
		Reference.RetainedSurfaceTags = RetainedSurfaceTags;
		Reference.RequiredNoticeActions = RequiredNoticeActions;
		Reference.ExplicitExclusions = ExplicitExclusions;
		Reference.SourceAttribution = SourceAttribution;
		return Reference;
	}

	void AppendJoined(FString& OutText, const TArray<FString>& Values)
	{
		for (int32 Index = 0; Index < Values.Num(); ++Index)
		{
			if (Index > 0)
			{
				OutText.Append(TEXT(", "));
			}

			OutText.Append(Values[Index]);
		}
	}
}

FHyperTwistTrainingClassicCubingReferenceBundle
UHyperTwistTrainingClassicCubingLibrary::BuildBundledClassicCubingReferenceBundle()
{
	using namespace HyperTwistTrainingClassicCubingLibraryInternal;

	FHyperTwistTrainingClassicCubingReferenceBundle Bundle;
	Bundle.ReferenceUtc = TEXT("2026-05-13T00:00:00Z");
	Bundle.PrimarySemanticContractId = TEXT("classic-cubing-semantics-stack");
	Bundle.PrimaryGeometryContractId = TEXT("classic-cubing-geometry-stack");
	Bundle.PrimaryViewerAdapterContractId = TEXT("classic-cubing-viewer-adapter-stack");
	Bundle.PrimaryDeviceAdapterBoundaryId = TEXT("classic-cubing-device-adapter-boundary");
	Bundle.PrimarySearchContractId = TEXT("classic-cubing-search-stack");
	Bundle.PrimaryComplianceBoundaryId = TEXT("classic-cubing-mpl-compliance-boundary");

	const TArray<FString> CommonNoticeActions =
	{
		TEXT("preserve MPL and GPL license materials with distribution packages"),
		TEXT("preserve upstream acknowledgements and vendored third-party notices"),
		TEXT("publish modifications to upstream MPL-covered source files if HyperTwist edits them directly")
	};

	Bundle.PackageReferences =
	{
		MakePackageReference(
			TEXT("package/cubing-kpuzzle"),
			TEXT("cubing/kpuzzle"),
			TEXT("./kpuzzle"),
			TEXT("classic-state-anchor"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Primary classic-cubing state, orbit-definition, pattern, and transformation substrate for owned HyperTwist classic semantics."),
			{
				TEXT("KPuzzleDefinition"),
				TEXT("KPattern"),
				TEXT("KTransformation"),
				TEXT("orbit JSON interchange")
			},
			CommonNoticeActions,
			{
				TEXT("deep private fork of upstream kpuzzle files"),
				TEXT("hyper-runtime ownership"),
				TEXT("donor app-shell ownership")
			},
			MakeAttribution(
				TEXT("classic-cubing/kpuzzle"),
				TEXT("Preserves KPuzzle, KPattern, KTransformation, and orbit-definition semantics from cubing.js through a bounded MPL-aware dependency or adapter lane.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-notation"),
			TEXT("cubing/notation"),
			TEXT("./notation"),
			TEXT("notation-support"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Notation and alg-interchange support layer for owned HyperTwist classic puzzle semantics and interop."),
			{
				TEXT("notation helpers"),
				TEXT("alg interop"),
				TEXT("string conversion")
			},
			CommonNoticeActions,
			{
				TEXT("general algorithm-language ownership"),
				TEXT("GPL-only parser replacement claims")
			},
			MakeAttribution(
				TEXT("classic-cubing/notation"),
				TEXT("Preserves notation helper and alg-interchange seams from cubing.js without promoting the donor into standalone algorithm-language ownership.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-puzzle-geometry"),
			TEXT("cubing/puzzle-geometry"),
			TEXT("./puzzle-geometry"),
			TEXT("classic-geometry-anchor"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Named-puzzle resolution, description parsing, geometry construction, and notation mapping for owned classic puzzle-family adapters."),
			{
				TEXT("named puzzle resolution"),
				TEXT("parsePuzzleDescription"),
				TEXT("PGNotation"),
				TEXT("geometry-backed puzzle construction")
			},
			CommonNoticeActions,
			{
				TEXT("hyper-runtime ownership"),
				TEXT("legacy donor UI shell ownership")
			},
			MakeAttribution(
				TEXT("classic-cubing/puzzle-geometry"),
				TEXT("Preserves named-puzzle resolution, description parsing, and geometry-backed puzzle construction seams from cubing.js for bounded classic-cubing adapters.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-twisty"),
			TEXT("cubing/twisty"),
			TEXT("./twisty"),
			TEXT("classic-viewer-adapter"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Embedded viewer, player, editor, and indexer substrate for classic-cubing viewer sidecars beneath first-party HyperTwist shells."),
			{
				TEXT("TwistyPlayer"),
				TEXT("TwistyAlgViewer"),
				TEXT("TwistyAlgEditor"),
				TEXT("SimpleAlgIndexer"),
				TEXT("TreeAlgIndexer"),
				TEXT("TwizzleLink")
			},
			CommonNoticeActions,
			{
				TEXT("Twizzle shell ownership"),
				TEXT("browser-first donor app-shell ownership"),
				TEXT("replacement for restrictive twisty.js clean-room lane")
			},
			MakeAttribution(
				TEXT("classic-cubing/twisty"),
				TEXT("Preserves embedded viewer, player, editor, and indexer seams from cubing.js through a bounded MPL-aware viewer-adapter lane.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-puzzles"),
			TEXT("cubing/puzzles"),
			TEXT("./puzzles"),
			TEXT("classic-puzzle-registry"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Puzzle registry layer spanning standard WCA puzzles and strategic bridge families such as Melinda's 2x2x2x2."),
			{
				TEXT("puzzle registry"),
				TEXT("named loaders"),
				TEXT("Melinda 2x2x2x2 bridge")
			},
			CommonNoticeActions,
			{
				TEXT("sole hyper-puzzle registry ownership"),
				TEXT("replacement for clean-room Melinda-specific lanes")
			},
			MakeAttribution(
				TEXT("classic-cubing/puzzles"),
				TEXT("Preserves puzzle-registry and loader seams, including Melinda's 2x2x2x2 bridge, from cubing.js without promoting it into HyperTwist's hyper-runtime owner.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-protocol"),
			TEXT("cubing/protocol"),
			TEXT("./protocol"),
			TEXT("interop-protocol-support"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Binary, Reid, and Twizzle protocol translation surfaces for smart-device and replay-side interoperability."),
			{
				TEXT("binary to reid"),
				TEXT("binary to twizzle"),
				TEXT("spaced hex helpers")
			},
			CommonNoticeActions,
			{
				TEXT("standalone protocol-platform ownership"),
				TEXT("vendor lock-in")
			},
			MakeAttribution(
				TEXT("classic-cubing/protocol"),
				TEXT("Preserves binary, Reid, and Twizzle protocol-translation seams from cubing.js for bounded smart-device and replay interoperability.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-bluetooth"),
			TEXT("cubing/bluetooth"),
			TEXT("./bluetooth"),
			TEXT("smart-device-adapter-support"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Smart puzzle, smart timer, smart robot, and keyboard-puzzle adapter substrate beneath owned HyperTwist device flows."),
			{
				TEXT("connectSmartPuzzle"),
				TEXT("connectSmartTimer"),
				TEXT("connectSmartRobot"),
				TEXT("KeyboardPuzzle")
			},
			CommonNoticeActions,
			{
				TEXT("standalone donor hardware-manager shell"),
				TEXT("browser-only pairing shell ownership")
			},
			MakeAttribution(
				TEXT("classic-cubing/bluetooth"),
				TEXT("Preserves bounded smart-puzzle, smart-timer, smart-robot, and keyboard-puzzle adapter seams from cubing.js beneath owned HyperTwist device flows.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-search"),
			TEXT("cubing/search"),
			TEXT("./search"),
			TEXT("classic-search-support"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Classic-cubing solve-helper and random-state support for owned search and validation surfaces."),
			{
				TEXT("random333Pattern"),
				TEXT("solve helpers"),
				TEXT("puzzle-kind dispatch")
			},
			CommonNoticeActions,
			{
				TEXT("sole correctness-oracle ownership"),
				TEXT("replacement for retained solver benchmarks")
			},
			MakeAttribution(
				TEXT("classic-cubing/search"),
				TEXT("Preserves bounded solve-helper, random-state, and puzzle-kind search seams from cubing.js while keeping retained solver benchmarks as separate validation authorities.")
			)
		),
		MakePackageReference(
			TEXT("package/cubing-scramble"),
			TEXT("cubing/scramble"),
			TEXT("./scramble"),
			TEXT("scramble-support"),
			TEXT("MPL-side dependency or bounded adapter"),
			TEXT("Scramble-entry and puzzle-kind dispatch support beneath owned HyperTwist classic-cubing case delivery."),
			{
				TEXT("scramble entry points"),
				TEXT("puzzle dispatch"),
				TEXT("classic-cube scramble routing")
			},
			CommonNoticeActions,
			{
				TEXT("sole scramble-oracle ownership"),
				TEXT("replacement for benchmark scramble validation")
			},
			MakeAttribution(
				TEXT("classic-cubing/scramble"),
				TEXT("Preserves scramble entry-point and puzzle-kind routing seams from cubing.js for bounded classic-cubing delivery and interop use.")
			)
		)
	};

	FHyperTwistTrainingClassicCubingSemanticContract SemanticContract;
	SemanticContract.ContractId = TEXT("classic-cubing-semantics-stack");
	SemanticContract.Title = TEXT("Classic-cubing semantic and state contract");
	SemanticContract.Summary = TEXT("First-party HyperTwist contract for classic-cubing state, transformations, notation, and interop using cubing.js only through an MPL-aware dependency or adapter posture.");
	SemanticContract.RequiredPackageIds =
	{
		TEXT("package/cubing-kpuzzle"),
		TEXT("package/cubing-notation"),
		TEXT("package/cubing-protocol")
	};
	SemanticContract.PuzzleStateFields =
	{
		TEXT("puzzle definition"),
		TEXT("orbit and piece-state layout"),
		TEXT("pattern serialization"),
		TEXT("transformation composition"),
		TEXT("identity and inversion semantics")
	};
	SemanticContract.NotationSurfaceTags =
	{
		TEXT("alg interoperability"),
		TEXT("move and transformation application"),
		TEXT("string conversion"),
		TEXT("classic WCA notation boundaries")
	};
	SemanticContract.InteropSurfaceTags =
	{
		TEXT("binary protocol translation"),
		TEXT("Reid representation"),
		TEXT("Twizzle interop"),
		TEXT("owned runtime adapter seam")
	};
	SemanticContract.ExplicitExclusions =
	{
		TEXT("hyper-runtime ownership"),
		TEXT("GPL-only algorithm-language ownership"),
		TEXT("donor app-shell ownership")
	};
	SemanticContract.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/semantic-contract"),
		TEXT("Preserves KPuzzle, notation, and protocol semantics from cubing.js while keeping HyperTwist's classic-cubing shell and runtime ownership first-party.")
	);
	Bundle.SemanticContracts = {SemanticContract};

	FHyperTwistTrainingClassicCubingGeometryContract GeometryContract;
	GeometryContract.ContractId = TEXT("classic-cubing-geometry-stack");
	GeometryContract.Title = TEXT("Classic-cubing geometry and named-puzzle contract");
	GeometryContract.Summary = TEXT("First-party HyperTwist contract for classic puzzle-family resolution, description parsing, notation mapping, and geometry-backed construction beneath the classic-cubing lane.");
	GeometryContract.RequiredPackageIds =
	{
		TEXT("package/cubing-puzzle-geometry"),
		TEXT("package/cubing-puzzles")
	};
	GeometryContract.NamedPuzzleFamilies =
	{
		TEXT("2x2x2"),
		TEXT("3x3x3"),
		TEXT("4x4x4"),
		TEXT("megaminx"),
		TEXT("pyraminx"),
		TEXT("skewb"),
		TEXT("square-1"),
		TEXT("Melinda 2x2x2x2")
	};
	GeometryContract.GeometrySurfaceTags =
	{
		TEXT("named puzzle resolution"),
		TEXT("description-string parsing"),
		TEXT("PGNotation mapping"),
		TEXT("geometry-backed construction"),
		TEXT("base-shape and cut-type semantics")
	};
	GeometryContract.AdapterBoundaryTags =
	{
		TEXT("classic-cubing-only geometry lane"),
		TEXT("Melinda bridge without hyper-runtime ownership"),
		TEXT("owned adapter over donor geometry")
	};
	GeometryContract.ExplicitExclusions =
	{
		TEXT("Hyperspeedcube replacement"),
		TEXT("hyper-topology runtime ownership"),
		TEXT("donor geometry UI shell ownership")
	};
	GeometryContract.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/geometry-contract"),
		TEXT("Preserves puzzle-geometry, named-puzzle, and Melinda bridge seams from cubing.js while keeping HyperTwist's hyper-runtime and topology ownership separate.")
	);
	Bundle.GeometryContracts = {GeometryContract};

	FHyperTwistTrainingClassicCubingViewerAdapterContract ViewerContract;
	ViewerContract.ContractId = TEXT("classic-cubing-viewer-adapter-stack");
	ViewerContract.Title = TEXT("Classic-cubing viewer, player, and editor adapter contract");
	ViewerContract.Summary = TEXT("First-party HyperTwist adapter contract for embedded classic-cubing viewer, player, editor, and algorithm-indexer behavior beneath owned shells.");
	ViewerContract.RequiredPackageIds =
	{
		TEXT("package/cubing-twisty"),
		TEXT("package/cubing-puzzles"),
		TEXT("package/cubing-notation")
	};
	ViewerContract.ViewerSurfaceTags =
	{
		TEXT("TwistyPlayer embed"),
		TEXT("algorithm viewer"),
		TEXT("algorithm editor"),
		TEXT("player configuration"),
		TEXT("puzzle switching")
	};
	ViewerContract.LifecycleHooks =
	{
		TEXT("viewer ready"),
		TEXT("alg changed"),
		TEXT("timeline scrubbed"),
		TEXT("index selection changed"),
		TEXT("puzzle changed")
	};
	ViewerContract.EditorSurfaceTags =
	{
		TEXT("embedded player"),
		TEXT("compact editor"),
		TEXT("timeline controls"),
		TEXT("TwizzleLink export")
	};
	ViewerContract.ExplicitExclusions =
	{
		TEXT("Twizzle product-shell ownership"),
		TEXT("browser-first donor app-shell ownership"),
		TEXT("replacement for restrictive twisty.js clean-room lane")
	};
	ViewerContract.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/viewer-adapter-contract"),
		TEXT("Preserves embedded TwistyPlayer, editor, and indexer seams from cubing.js while keeping the HyperTwist shell first-party and separate from the twisty.js clean-room lane.")
	);
	Bundle.ViewerAdapterContracts = {ViewerContract};

	FHyperTwistTrainingClassicCubingDeviceAdapterBoundary DeviceBoundary;
	DeviceBoundary.BoundaryId = TEXT("classic-cubing-device-adapter-boundary");
	DeviceBoundary.Title = TEXT("Classic-cubing smart-device and protocol adapter boundary");
	DeviceBoundary.Summary = TEXT("First-party HyperTwist adapter boundary for smart puzzle, smart timer, smart robot, keyboard-puzzle, and protocol translation seams beneath owned recognition, timer, and replay flows.");
	DeviceBoundary.RequiredPackageIds =
	{
		TEXT("package/cubing-protocol"),
		TEXT("package/cubing-bluetooth")
	};
	DeviceBoundary.DeviceFamilies =
	{
		TEXT("GAN smart cube"),
		TEXT("GiiKER smart cube"),
		TEXT("GoCube family"),
		TEXT("smart timer"),
		TEXT("smart robot"),
		TEXT("keyboard puzzle")
	};
	DeviceBoundary.AdapterOwnedFields =
	{
		TEXT("pairing and consent policy"),
		TEXT("device capability selection"),
		TEXT("session telemetry ownership"),
		TEXT("retry and fault handling"),
		TEXT("first-party runtime event translation")
	};
	DeviceBoundary.ExplicitExclusions =
	{
		TEXT("standalone donor hardware-manager shell"),
		TEXT("browser-only pairing shell"),
		TEXT("vendor lock-in or opaque device ownership")
	};
	DeviceBoundary.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/device-adapter-boundary"),
		TEXT("Preserves smart-device and protocol seams from cubing.js beneath owned HyperTwist timer, replay, and recognition flows.")
	);
	Bundle.DeviceAdapterBoundaries = {DeviceBoundary};

	FHyperTwistTrainingClassicCubingSearchContract SearchContract;
	SearchContract.ContractId = TEXT("classic-cubing-search-stack");
	SearchContract.Title = TEXT("Classic-cubing search, scramble, and solve-helper contract");
	SearchContract.Summary = TEXT("First-party HyperTwist contract for classic-cubing solve-helper, random-state, and scramble-routing behavior through bounded cubing.js packages rather than donor-shell reuse.");
	SearchContract.RequiredPackageIds =
	{
		TEXT("package/cubing-search"),
		TEXT("package/cubing-scramble"),
		TEXT("package/cubing-puzzles")
	};
	SearchContract.PuzzleFamilies =
	{
		TEXT("2x2x2"),
		TEXT("3x3x3"),
		TEXT("megaminx"),
		TEXT("pyraminx"),
		TEXT("skewb")
	};
	SearchContract.SearchSurfaceTags =
	{
		TEXT("random-state helpers"),
		TEXT("solve-helper entry points"),
		TEXT("puzzle-kind dispatch"),
		TEXT("scramble routing"),
		TEXT("owned adapter seam")
	};
	SearchContract.ExplicitExclusions =
	{
		TEXT("sole correctness-oracle ownership"),
		TEXT("replacement for retained solver benchmarks"),
		TEXT("general donor solver shell ownership")
	};
	SearchContract.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/search-contract"),
		TEXT("Preserves bounded search, random-state, and scramble-routing seams from cubing.js while keeping retained solver benchmarks and future owned search work separate.")
	);
	Bundle.SearchContracts = {SearchContract};

	FHyperTwistTrainingClassicCubingComplianceBoundary ComplianceBoundary;
	ComplianceBoundary.BoundaryId = TEXT("classic-cubing-mpl-compliance-boundary");
	ComplianceBoundary.Title = TEXT("Classic-cubing MPL adapter and notice boundary");
	ComplianceBoundary.Summary = TEXT("First-party HyperTwist compliance boundary for consuming cubing.js through the practical MPL side, preserving notices, and avoiding an untracked private fork posture.");
	ComplianceBoundary.AllowedUseTags =
	{
		TEXT("MPL-side package dependency"),
		TEXT("bounded adapter layer"),
		TEXT("companion library consumption"),
		TEXT("notice-preserving distribution")
	};
	ComplianceBoundary.PublicationDutyTags =
	{
		TEXT("publish modifications to upstream MPL-covered files if any land"),
		TEXT("keep MPL and GPL license materials with distributed builds"),
		TEXT("preserve vendored acknowledgements where required")
	};
	ComplianceBoundary.RequiredNoticeActions = CommonNoticeActions;
	ComplianceBoundary.ExplicitExclusions =
	{
		TEXT("deep private fork of upstream source files"),
		TEXT("treating the GPL side as the default operational path"),
		TEXT("copying donor docs, demos, or app-shell wording into first-party surfaces"),
		TEXT("untracked source-file modifications")
	};
	ComplianceBoundary.SourceAttribution = MakeAttribution(
		TEXT("classic-cubing/mpl-compliance-boundary"),
		TEXT("Preserves the practical MPL-side dependency posture documented by cubing.js while keeping notice retention and source-file modification duties explicit in first-party HyperTwist routing.")
	);
	Bundle.ComplianceBoundaries = {ComplianceBoundary};

	return Bundle;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetSemanticContractById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingClassicCubingSemanticContract& OutContract
)
{
	for (const FHyperTwistTrainingClassicCubingSemanticContract& Contract : Bundle.SemanticContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingClassicCubingSemanticContract();
	return false;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetGeometryContractById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingClassicCubingGeometryContract& OutContract
)
{
	for (const FHyperTwistTrainingClassicCubingGeometryContract& Contract : Bundle.GeometryContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingClassicCubingGeometryContract();
	return false;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetViewerAdapterContractById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingClassicCubingViewerAdapterContract& OutContract
)
{
	for (const FHyperTwistTrainingClassicCubingViewerAdapterContract& Contract : Bundle.ViewerAdapterContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingClassicCubingViewerAdapterContract();
	return false;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetDeviceAdapterBoundaryById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& BoundaryId,
	FHyperTwistTrainingClassicCubingDeviceAdapterBoundary& OutBoundary
)
{
	for (const FHyperTwistTrainingClassicCubingDeviceAdapterBoundary& Boundary : Bundle.DeviceAdapterBoundaries)
	{
		if (Boundary.BoundaryId.Equals(BoundaryId, ESearchCase::IgnoreCase))
		{
			OutBoundary = Boundary;
			return true;
		}
	}

	OutBoundary = FHyperTwistTrainingClassicCubingDeviceAdapterBoundary();
	return false;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetSearchContractById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingClassicCubingSearchContract& OutContract
)
{
	for (const FHyperTwistTrainingClassicCubingSearchContract& Contract : Bundle.SearchContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingClassicCubingSearchContract();
	return false;
}

bool UHyperTwistTrainingClassicCubingLibrary::TryGetComplianceBoundaryById(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle,
	const FString& BoundaryId,
	FHyperTwistTrainingClassicCubingComplianceBoundary& OutBoundary
)
{
	for (const FHyperTwistTrainingClassicCubingComplianceBoundary& Boundary : Bundle.ComplianceBoundaries)
	{
		if (Boundary.BoundaryId.Equals(BoundaryId, ESearchCase::IgnoreCase))
		{
			OutBoundary = Boundary;
			return true;
		}
	}

	OutBoundary = FHyperTwistTrainingClassicCubingComplianceBoundary();
	return false;
}

FString UHyperTwistTrainingClassicCubingLibrary::BuildPackageChecklistTsv(
	const FHyperTwistTrainingClassicCubingReferenceBundle& Bundle
)
{
	using namespace HyperTwistTrainingClassicCubingLibraryInternal;

	FString Output = TEXT("package_id\tlabel\texport_path\townership_role\tlicense_posture\tretained_surfaces\tsummary\n");
	for (const FHyperTwistTrainingClassicCubingPackageReference& Reference : Bundle.PackageReferences)
	{
		if (!Reference.IsStructurallyValid())
		{
			continue;
		}

		FString SurfaceTags;
		AppendJoined(SurfaceTags, Reference.RetainedSurfaceTags);

		Output.Append(Reference.PackageId);
		Output.Append(TEXT("\t"));
		Output.Append(Reference.Label);
		Output.Append(TEXT("\t"));
		Output.Append(Reference.ExportPath);
		Output.Append(TEXT("\t"));
		Output.Append(Reference.OwnershipRole);
		Output.Append(TEXT("\t"));
		Output.Append(Reference.LicensePosture);
		Output.Append(TEXT("\t"));
		Output.Append(SurfaceTags);
		Output.Append(TEXT("\t"));
		Output.Append(Reference.Summary.Replace(TEXT("\t"), TEXT(" ")));
		Output.Append(TEXT("\n"));
	}

	return Output;
}
