// Clean-room implementation for HyperTwist algorithm-language subsystem
// Implemented from governance docs only (Phase 0R-D, Phase 1R, Phase 5R-A)
// Date: 2026-05-15
// Bound 4 - versioned structured interchange and URL/share helpers

#include "HyperTwistAlgorithm/HyperTwistAlgorithmShare.h"

#include "Containers/StringConv.h"
#include "Dom/JsonObject.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmParser.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmSerializer.h"
#include "HyperTwistAlgorithm/HyperTwistAlgorithmValidation.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"

namespace HyperTwistAlgorithmShareInternal
{
	const TCHAR* ShareDocumentSchema = TEXT("ht-alg-share/v1");
	const TCHAR* DefaultShareQueryKey = TEXT("htalg");

	bool IsUnreservedUrlByte(const uint8 Byte)
	{
		return (Byte >= 'A' && Byte <= 'Z')
			|| (Byte >= 'a' && Byte <= 'z')
			|| (Byte >= '0' && Byte <= '9')
			|| Byte == '-'
			|| Byte == '_'
			|| Byte == '.'
			|| Byte == '~';
	}

	FString PercentEncodeUtf8(const FString& Input)
	{
		FTCHARToUTF8 Utf8(*Input);
		const uint8* Bytes = reinterpret_cast<const uint8*>(Utf8.Get());
		const int32 ByteCount = Utf8.Length();

		FString Result;
		Result.Reserve(ByteCount * 3);
		for (int32 Index = 0; Index < ByteCount; ++Index)
		{
			const uint8 Byte = Bytes[Index];
			if (IsUnreservedUrlByte(Byte))
			{
				Result.AppendChar(static_cast<TCHAR>(Byte));
			}
			else
			{
				Result.Append(FString::Printf(TEXT("%%%02X"), Byte));
			}
		}

		return Result;
	}

	bool TryDecodeHexDigit(const TCHAR Character, uint8& OutValue)
	{
		if (Character >= TEXT('0') && Character <= TEXT('9'))
		{
			OutValue = static_cast<uint8>(Character - TEXT('0'));
			return true;
		}

		if (Character >= TEXT('A') && Character <= TEXT('F'))
		{
			OutValue = static_cast<uint8>(10 + (Character - TEXT('A')));
			return true;
		}

		if (Character >= TEXT('a') && Character <= TEXT('f'))
		{
			OutValue = static_cast<uint8>(10 + (Character - TEXT('a')));
			return true;
		}

		return false;
	}

	bool TryPercentDecodeUtf8(const FString& Input, FString& OutText, FString& OutError)
	{
		OutText.Empty();
		OutError.Empty();

		TArray<uint8> Bytes;
		Bytes.Reserve(Input.Len());
		for (int32 Index = 0; Index < Input.Len(); ++Index)
		{
			const TCHAR Character = Input[Index];
			if (Character == TEXT('%'))
			{
				if (Index + 2 >= Input.Len())
				{
					OutError = TEXT("Share payload ended with an incomplete percent escape.");
					return false;
				}

				uint8 UpperNibble = 0;
				uint8 LowerNibble = 0;
				if (!TryDecodeHexDigit(Input[Index + 1], UpperNibble)
					|| !TryDecodeHexDigit(Input[Index + 2], LowerNibble))
				{
					OutError = TEXT("Share payload contains an invalid percent escape.");
					return false;
				}

				Bytes.Add(static_cast<uint8>((UpperNibble << 4) | LowerNibble));
				Index += 2;
				continue;
			}

			Bytes.Add(static_cast<uint8>(Character));
		}

		if (Bytes.Num() == 0)
		{
			return true;
		}

		const FUTF8ToTCHAR Utf8(reinterpret_cast<const ANSICHAR*>(Bytes.GetData()), Bytes.Num());
		OutText = FString(Utf8.Length(), Utf8.Get());
		return true;
	}

	bool TryConvertJsonTextToObject(
		const FString& JsonText,
		TSharedPtr<FJsonObject>& OutObject,
		FString& OutError)
	{
		OutObject.Reset();
		OutError.Empty();

		const TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonText);
		if (!FJsonSerializer::Deserialize(Reader, OutObject) || !OutObject.IsValid())
		{
			OutError = TEXT("JSON payload could not be parsed.");
			return false;
		}

		return true;
	}

	bool TryConvertObjectToJsonText(
		const TSharedPtr<FJsonObject>& Object,
		FString& OutJson,
		FString& OutError)
	{
		OutJson.Empty();
		OutError.Empty();

		if (!Object.IsValid())
		{
			OutError = TEXT("JSON object was not valid.");
			return false;
		}

		const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutJson);
		if (!FJsonSerializer::Serialize(Object.ToSharedRef(), Writer))
		{
			OutError = TEXT("JSON object could not be serialized.");
			return false;
		}

		return true;
	}

	bool TryGetRequiredStringField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		FString& OutValue,
		FString& OutError)
	{
		if (!Object.IsValid() || !Object->TryGetStringField(FieldName, OutValue))
		{
			OutError = FString::Printf(TEXT("Missing string field '%s'"), FieldName);
			return false;
		}

		return true;
	}

	void ReadOptionalStringField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		FString& OutValue)
	{
		OutValue.Empty();
		if (!Object.IsValid())
		{
			return;
		}

		Object->TryGetStringField(FieldName, OutValue);
	}

	bool TryGetRequiredObjectField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		TSharedPtr<FJsonObject>& OutValue,
		FString& OutError)
	{
		OutValue.Reset();

		if (!Object.IsValid())
		{
			OutError = FString::Printf(TEXT("Missing object field '%s'"), FieldName);
			return false;
		}

		const TSharedPtr<FJsonValue>* FieldValue = Object->Values.Find(FieldName);
		if (FieldValue == nullptr || !FieldValue->IsValid() || (*FieldValue)->Type != EJson::Object)
		{
			OutError = FString::Printf(TEXT("Missing object field '%s'"), FieldName);
			return false;
		}

		OutValue = (*FieldValue)->AsObject();
		if (!OutValue.IsValid())
		{
			OutError = FString::Printf(TEXT("Object field '%s' could not be read"), FieldName);
			return false;
		}

		return true;
	}

	bool TryGetOptionalObjectField(
		const TSharedPtr<FJsonObject>& Object,
		const TCHAR* FieldName,
		TSharedPtr<FJsonObject>& OutValue)
	{
		OutValue.Reset();
		if (!Object.IsValid())
		{
			return false;
		}

		const TSharedPtr<FJsonValue>* FieldValue = Object->Values.Find(FieldName);
		if (FieldValue == nullptr || !FieldValue->IsValid() || (*FieldValue)->Type != EJson::Object)
		{
			return false;
		}

		OutValue = (*FieldValue)->AsObject();
		return OutValue.IsValid();
	}

	FString BuildValidationSummary(const FHyperTwistAlgorithmValidationResult& Result)
	{
		if (Result.Issues.Num() <= 0)
		{
			return FString::Printf(TEXT("profile '%s' rejected the sequence"), *Result.ProfileName);
		}

		const FHyperTwistAlgorithmValidationIssue& Issue = Result.Issues[0];
		if (Issue.NodePath.IsEmpty())
		{
			return Issue.Message;
		}

		return FString::Printf(TEXT("%s at %s"), *Issue.Message, *Issue.NodePath);
	}

	bool TryValidateSequenceForShare(
		const FHyperTwistAlgorithmSequence& Sequence,
		const FString& NotationProfile,
		const FString& Label,
		FString& OutError)
	{
		if (NotationProfile.IsEmpty())
		{
			return true;
		}

		const FHyperTwistAlgorithmValidationResult Result =
			UHyperTwistAlgorithmValidationLibrary::ValidateSequenceForProfile(Sequence, NotationProfile);
		if (Result.bIsValid)
		{
			return true;
		}

		OutError = FString::Printf(
			TEXT("%s failed validation for profile '%s': %s"),
			*Label,
			*Result.ProfileName,
			*BuildValidationSummary(Result));
		return false;
	}

	bool TryBuildStructuredAlgorithmObject(
		const FHyperTwistAlgorithmSequence& Sequence,
		TSharedPtr<FJsonObject>& OutStructuredObject,
		FString& OutError)
	{
		FString StructuredJson;
		if (!UHyperTwistAlgorithmSerializer::TrySerializeToJson(Sequence, StructuredJson))
		{
			OutError = TEXT("Owned structured JSON serialization failed.");
			return false;
		}

		if (!TryConvertJsonTextToObject(StructuredJson, OutStructuredObject, OutError))
		{
			OutError = FString::Printf(TEXT("Owned structured JSON could not be reloaded: %s"), *OutError);
			return false;
		}

		return true;
	}

	bool TryParseStructuredAlgorithmObject(
		const TSharedPtr<FJsonObject>& StructuredObject,
		FHyperTwistAlgorithmSequence& OutSequence,
		FString& OutError)
	{
		FString StructuredJson;
		if (!TryConvertObjectToJsonText(StructuredObject, StructuredJson, OutError))
		{
			return false;
		}

		if (!UHyperTwistAlgorithmParser::TryParseFromJson(StructuredJson, OutSequence, OutError))
		{
			OutError = FString::Printf(TEXT("Owned structured JSON could not be parsed: %s"), *OutError);
			return false;
		}

		return true;
	}

	bool TryBuildAlgorithmEntryObject(
		const FString& Label,
		const FHyperTwistAlgorithmSequence& Sequence,
		const FString& ProvidedCanonicalText,
		const FString& NotationProfile,
		TSharedPtr<FJsonObject>& OutEntryObject,
		FString& OutCanonicalText,
		FString& OutError)
	{
		if (!TryValidateSequenceForShare(Sequence, NotationProfile, Label, OutError))
		{
			return false;
		}

		OutCanonicalText = UHyperTwistAlgorithmSerializer::SerializeAlgorithm(Sequence);
		if (!ProvidedCanonicalText.IsEmpty() && ProvidedCanonicalText != OutCanonicalText)
		{
			OutError = FString::Printf(
				TEXT("%s canonical text does not match the owned canonical serializer result."),
				*Label);
			return false;
		}

		TSharedPtr<FJsonObject> StructuredObject;
		if (!TryBuildStructuredAlgorithmObject(Sequence, StructuredObject, OutError))
		{
			OutError = FString::Printf(TEXT("%s could not build structured JSON: %s"), *Label, *OutError);
			return false;
		}

		OutEntryObject = MakeShared<FJsonObject>();
		OutEntryObject->SetStringField(TEXT("canonical_text"), OutCanonicalText);
		OutEntryObject->SetObjectField(TEXT("structured"), StructuredObject);
		return true;
	}

	bool TryParseAlgorithmEntryObject(
		const TSharedPtr<FJsonObject>& EntryObject,
		const FString& Label,
		const FString& NotationProfile,
		FHyperTwistAlgorithmSequence& OutSequence,
		FString& OutCanonicalText,
		FString& OutError)
	{
		FString StoredCanonicalText;
		if (!TryGetRequiredStringField(EntryObject, TEXT("canonical_text"), StoredCanonicalText, OutError))
		{
			OutError = FString::Printf(TEXT("%s is missing canonical text: %s"), *Label, *OutError);
			return false;
		}

		TSharedPtr<FJsonObject> StructuredObject;
		if (!TryGetRequiredObjectField(EntryObject, TEXT("structured"), StructuredObject, OutError))
		{
			OutError = FString::Printf(TEXT("%s is missing structured JSON: %s"), *Label, *OutError);
			return false;
		}

		if (!TryParseStructuredAlgorithmObject(StructuredObject, OutSequence, OutError))
		{
			OutError = FString::Printf(TEXT("%s structured JSON failed to parse: %s"), *Label, *OutError);
			return false;
		}

		if (!TryValidateSequenceForShare(OutSequence, NotationProfile, Label, OutError))
		{
			return false;
		}

		OutCanonicalText = UHyperTwistAlgorithmSerializer::SerializeAlgorithm(OutSequence);
		if (StoredCanonicalText != OutCanonicalText)
		{
			OutError = FString::Printf(
				TEXT("%s canonical text does not match structured algorithm content."),
				*Label);
			return false;
		}

		return true;
	}

	bool ShouldIncludeSetup(const FHyperTwistAlgorithmShareDocument& Document)
	{
		return Document.bHasSetupSequence
			|| !Document.SetupCanonicalText.IsEmpty()
			|| Document.SetupSequence.Nodes.Num() > 0;
	}

	void PopulateContextObject(
		const FHyperTwistAlgorithmShareDocument& Document,
		const TSharedPtr<FJsonObject>& RootObject)
	{
		if (!RootObject.IsValid())
		{
			return;
		}

		TSharedPtr<FJsonObject> ContextObject = MakeShared<FJsonObject>();
		if (!Document.Title.IsEmpty())
		{
			ContextObject->SetStringField(TEXT("title"), Document.Title);
		}
		if (!Document.PuzzleScope.IsEmpty())
		{
			ContextObject->SetStringField(TEXT("puzzle"), Document.PuzzleScope);
		}
		if (!Document.StageScope.IsEmpty())
		{
			ContextObject->SetStringField(TEXT("stage"), Document.StageScope);
		}
		if (!Document.ViewMode.IsEmpty())
		{
			ContextObject->SetStringField(TEXT("view_mode"), Document.ViewMode);
		}
		if (!Document.InterpretationMode.IsEmpty())
		{
			ContextObject->SetStringField(TEXT("interpretation_mode"), Document.InterpretationMode);
		}

		if (ContextObject->Values.Num() > 0)
		{
			RootObject->SetObjectField(TEXT("context"), ContextObject);
		}
	}

	void ReadContextObject(
		const TSharedPtr<FJsonObject>& RootObject,
		FHyperTwistAlgorithmShareDocument& OutDocument)
	{
		OutDocument.Title.Empty();
		OutDocument.PuzzleScope.Empty();
		OutDocument.StageScope.Empty();
		OutDocument.ViewMode.Empty();
		OutDocument.InterpretationMode.Empty();

		TSharedPtr<FJsonObject> ContextObject;
		if (!TryGetOptionalObjectField(RootObject, TEXT("context"), ContextObject))
		{
			return;
		}

		ReadOptionalStringField(ContextObject, TEXT("title"), OutDocument.Title);
		ReadOptionalStringField(ContextObject, TEXT("puzzle"), OutDocument.PuzzleScope);
		ReadOptionalStringField(ContextObject, TEXT("stage"), OutDocument.StageScope);
		ReadOptionalStringField(ContextObject, TEXT("view_mode"), OutDocument.ViewMode);
		ReadOptionalStringField(ContextObject, TEXT("interpretation_mode"), OutDocument.InterpretationMode);
	}

	bool TryExtractSharePayloadFromUrl(
		const FString& ShareUrl,
		FString& OutPayload,
		FString& OutError)
	{
		OutPayload.Empty();
		OutError.Empty();

		FString UrlWithoutFragment = ShareUrl;
		const int32 FragmentIndex = UrlWithoutFragment.Find(TEXT("#"));
		if (FragmentIndex != INDEX_NONE)
		{
			UrlWithoutFragment = UrlWithoutFragment.Left(FragmentIndex);
		}

		const int32 QueryIndex = UrlWithoutFragment.Find(TEXT("?"));
		if (QueryIndex == INDEX_NONE || QueryIndex + 1 >= UrlWithoutFragment.Len())
		{
			OutError = TEXT("Share URL does not contain a query string.");
			return false;
		}

		const FString Query = UrlWithoutFragment.Mid(QueryIndex + 1);
		TArray<FString> QueryPairs;
		Query.ParseIntoArray(QueryPairs, TEXT("&"), true);

		for (const FString& Pair : QueryPairs)
		{
			FString Key = Pair;
			FString Value;

			int32 EqualsIndex = INDEX_NONE;
			if (Pair.FindChar(TEXT('='), EqualsIndex))
			{
				Key = Pair.Left(EqualsIndex);
				Value = Pair.Mid(EqualsIndex + 1);
			}

			if (Key == DefaultShareQueryKey)
			{
				if (Value.IsEmpty())
				{
					OutError = TEXT("Share URL contains an empty HyperTwist algorithm payload.");
					return false;
				}

				OutPayload = Value;
				return true;
			}
		}

		OutError = TEXT("Share URL does not contain a HyperTwist algorithm payload.");
		return false;
	}
}

bool UHyperTwistAlgorithmShareLibrary::TrySerializeShareDocumentToJson(
	const FHyperTwistAlgorithmShareDocument& Document,
	FString& OutJson,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	OutJson.Empty();
	OutError.Empty();

	TSharedPtr<FJsonObject> RootObject = MakeShared<FJsonObject>();
	RootObject->SetStringField(TEXT("schema"), ShareDocumentSchema);

	if (!Document.NotationProfile.IsEmpty())
	{
		RootObject->SetStringField(TEXT("notation_profile"), Document.NotationProfile);
	}

	TSharedPtr<FJsonObject> MainObject;
	FString MainCanonicalText;
	if (!TryBuildAlgorithmEntryObject(
			TEXT("main"),
			Document.MainSequence,
			Document.MainCanonicalText,
			Document.NotationProfile,
			MainObject,
			MainCanonicalText,
			OutError))
	{
		return false;
	}

	RootObject->SetObjectField(TEXT("main"), MainObject);

	if (ShouldIncludeSetup(Document))
	{
		TSharedPtr<FJsonObject> SetupObject;
		FString SetupCanonicalText;
		if (!TryBuildAlgorithmEntryObject(
				TEXT("setup"),
				Document.SetupSequence,
				Document.SetupCanonicalText,
				Document.NotationProfile,
				SetupObject,
				SetupCanonicalText,
				OutError))
		{
			return false;
		}

		RootObject->SetObjectField(TEXT("setup"), SetupObject);
	}

	PopulateContextObject(Document, RootObject);
	return TryConvertObjectToJsonText(RootObject, OutJson, OutError);
}

bool UHyperTwistAlgorithmShareLibrary::TryParseShareDocumentFromJson(
	const FString& JsonText,
	FHyperTwistAlgorithmShareDocument& OutDocument,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	OutDocument = FHyperTwistAlgorithmShareDocument();
	OutError.Empty();

	TSharedPtr<FJsonObject> RootObject;
	if (!TryConvertJsonTextToObject(JsonText, RootObject, OutError))
	{
		return false;
	}

	FString SchemaName;
	if (!TryGetRequiredStringField(RootObject, TEXT("schema"), SchemaName, OutError))
	{
		return false;
	}

	if (SchemaName != ShareDocumentSchema)
	{
		OutError = FString::Printf(TEXT("Unsupported share document schema '%s'"), *SchemaName);
		return false;
	}

	ReadOptionalStringField(RootObject, TEXT("notation_profile"), OutDocument.NotationProfile);

	TSharedPtr<FJsonObject> MainObject;
	if (!TryGetRequiredObjectField(RootObject, TEXT("main"), MainObject, OutError))
	{
		return false;
	}

	if (!TryParseAlgorithmEntryObject(
			MainObject,
			TEXT("main"),
			OutDocument.NotationProfile,
			OutDocument.MainSequence,
			OutDocument.MainCanonicalText,
			OutError))
	{
		return false;
	}

	TSharedPtr<FJsonObject> SetupObject;
	OutDocument.bHasSetupSequence = TryGetOptionalObjectField(RootObject, TEXT("setup"), SetupObject);
	if (OutDocument.bHasSetupSequence)
	{
		if (!TryParseAlgorithmEntryObject(
				SetupObject,
				TEXT("setup"),
				OutDocument.NotationProfile,
				OutDocument.SetupSequence,
				OutDocument.SetupCanonicalText,
				OutError))
		{
			return false;
		}
	}

	ReadContextObject(RootObject, OutDocument);
	return true;
}

bool UHyperTwistAlgorithmShareLibrary::TryEncodeShareDocumentPayload(
	const FHyperTwistAlgorithmShareDocument& Document,
	FString& OutPayload,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	FString ShareJson;
	if (!TrySerializeShareDocumentToJson(Document, ShareJson, OutError))
	{
		OutPayload.Empty();
		return false;
	}

	OutPayload = PercentEncodeUtf8(ShareJson);
	return true;
}

bool UHyperTwistAlgorithmShareLibrary::TryDecodeShareDocumentPayload(
	const FString& Payload,
	FHyperTwistAlgorithmShareDocument& OutDocument,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	FString ShareJson;
	if (!TryPercentDecodeUtf8(Payload, ShareJson, OutError))
	{
		return false;
	}

	return TryParseShareDocumentFromJson(ShareJson, OutDocument, OutError);
}

bool UHyperTwistAlgorithmShareLibrary::TryBuildShareUrl(
	const FString& BaseUrl,
	const FHyperTwistAlgorithmShareDocument& Document,
	FString& OutUrl,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	OutUrl.Empty();

	FString Payload;
	if (!TryEncodeShareDocumentPayload(Document, Payload, OutError))
	{
		return false;
	}

	FString QueryBase = BaseUrl;
	FString FragmentSuffix;
	const int32 FragmentIndex = QueryBase.Find(TEXT("#"));
	if (FragmentIndex != INDEX_NONE)
	{
		FragmentSuffix = QueryBase.Mid(FragmentIndex);
		QueryBase = QueryBase.Left(FragmentIndex);
	}

	FString Separator = TEXT("?");
	if (QueryBase.Contains(TEXT("?")))
	{
		Separator = (QueryBase.EndsWith(TEXT("?")) || QueryBase.EndsWith(TEXT("&")))
			? TEXT("")
			: TEXT("&");
	}

	OutUrl = FString::Printf(
		TEXT("%s%s%s=%s%s"),
		*QueryBase,
		*Separator,
		DefaultShareQueryKey,
		*Payload,
		*FragmentSuffix);
	return true;
}

bool UHyperTwistAlgorithmShareLibrary::TryParseShareUrl(
	const FString& ShareUrl,
	FHyperTwistAlgorithmShareDocument& OutDocument,
	FString& OutError)
{
	using namespace HyperTwistAlgorithmShareInternal;

	FString Payload;
	if (!TryExtractSharePayloadFromUrl(ShareUrl, Payload, OutError))
	{
		return false;
	}

	return TryDecodeShareDocumentPayload(Payload, OutDocument, OutError);
}

FString UHyperTwistAlgorithmShareLibrary::GetShareDocumentSchema()
{
	return HyperTwistAlgorithmShareInternal::ShareDocumentSchema;
}

FString UHyperTwistAlgorithmShareLibrary::GetDefaultShareQueryKey()
{
	return HyperTwistAlgorithmShareInternal::DefaultShareQueryKey;
}
