/**
 * Copyright 2022-2025 Theai, Inc. dba Inworld AI
 */

#include "Graph/InworldData.h"

#include "HAL/FileManager.h"
#include "InworldRuntimeSubsystem.h"
#include "InworldRuntimeUtils.h"
#include "JsonObjectConverter.h"
#include "Misc/Optional.h"
#include "Templates/Function.h"

#if WITH_EDITOR && !UE_BUILD_SHIPPING
#include "Audio/InworldAudioDumpUtils.h"
#endif

THIRD_PARTY_INCLUDES_START
#include "InworldRuntimeLibrary/inworld.h"
THIRD_PARTY_INCLUDES_END

FInworldDataHandle::FInworldDataHandle(FInworldData_Bool Bool)
{
	Data = MakeShared<FInworldData_Struct>(Bool);
}

FInworldDataHandle::FInworldDataHandle(FInworldData_Int Int)
{
	Data = MakeShared<FInworldData_Struct>(Int);
}

FInworldDataHandle::FInworldDataHandle(FInworldData_Float Float)
{
	Data = MakeShared<FInworldData_Struct>(Float);
}

FInworldDataHandle::FInworldDataHandle(FInworldData_Object Object)
{
	Data = MakeShared<FInworldData_Struct>(Object);
}

UScriptStruct* FInworldData::GetScriptStruct() const
{
	return FInworldData::StaticStruct();
}

bool FInworldData::IsA(UScriptStruct* Type) const
{
	return FInworldData::StaticStruct() == Type;
}

FInworldDataHandle FInworldData::Wrap()
{
	return {};
}

inworld::BaseData* FInworldData::CreateRuntimeData()
{
	if (ErrorInfo.IsSet())
	{
		return FInworldData_Error(ErrorInfo.GetValue().Get<FString>()).CreateRuntimeData();
	}
	else
	{
		return CreateRuntimeData_Internal();
	}
}

FInworldData_Struct::FInworldData_Struct(const FInworldData_Struct& Other)
{
	const uint64 OtherSize{Other.GetSize()};
	if (OtherSize > 0)
	{
		UScriptStruct* OtherScriptStruct = Other.GetDataScriptStruct();
		void* OtherData = Other.GetData();

		Blob = FMemory::Malloc(OtherSize);

		uint8* Write = (uint8*) Blob;
		FMemory::Memcpy(Write, &OtherSize, sizeof(uint64));
		Write += sizeof(uint64);
		FMemory::Memcpy(Write, &OtherScriptStruct, sizeof(UScriptStruct*));
		Write += sizeof(UScriptStruct*);
		OtherScriptStruct->InitializeDefaultValue(Write);
		OtherScriptStruct->CopyScriptStruct(Write, OtherData);
	}
}

FInworldData_Struct::FInworldData_Struct(UScriptStruct* InScriptStruct, const void* InData)
{
	const uint64 Size{sizeof(uint64) + sizeof(UScriptStruct*) + InScriptStruct->GetStructureSize()};
	Blob = FMemory::Malloc(Size);

	uint8* Write = (uint8*) Blob;
	FMemory::Memcpy(Write, &Size, sizeof(uint64));
	Write += sizeof(uint64);
	FMemory::Memcpy(Write, &InScriptStruct, sizeof(UScriptStruct*));
	Write += sizeof(UScriptStruct*);
	InScriptStruct->InitializeDefaultValue(Write);
	InScriptStruct->CopyScriptStruct(Write, InData);
}

FInworldData_Struct::FInworldData_Struct(void* InBlob)
{
	uint64 Size{};
	FMemory::Memcpy(&Size, InBlob, sizeof(uint64));
	UScriptStruct* ScriptStruct{};
	FMemory::Memcpy(&ScriptStruct, (uint8*) InBlob + sizeof(uint64), sizeof(UScriptStruct*));

	Blob = FMemory::Malloc(Size);

	uint8* Write = (uint8*) Blob;
	FMemory::Memcpy(Write, &Size, sizeof(uint64));
	Write += sizeof(uint64);
	FMemory::Memcpy(Write, &ScriptStruct, sizeof(UScriptStruct*));
	Write += sizeof(UScriptStruct*);
	ScriptStruct->InitializeDefaultValue(Write);
	ScriptStruct->CopyScriptStruct(Write, (uint8*) InBlob + sizeof(uint64) + sizeof(UScriptStruct*));
}

FInworldData_Struct::~FInworldData_Struct()
{
	if (Blob != nullptr)
	{
		UScriptStruct* DataScriptStruct = GetDataScriptStruct();
		void* Data = GetData();
		DataScriptStruct->DestroyStruct(Data);
		FMemory::Free(Blob);
	}
}

namespace
{
static void ToJsonCallback(void* opaque_this)
{
	FString OutputString;
	FInworldData_Struct Other(opaque_this);
	FJsonObjectConverter::UStructToJsonObjectString(Other.GetDataScriptStruct(), Other.GetData(), OutputString);
	inworld::CustomDataToJsonExecutor::SetNextOutput(TCHAR_TO_UTF8(*OutputString));
}

static void FreeCallback(void* opaque_this)
{
	UScriptStruct* ScriptStruct{nullptr};
	FMemory::Memcpy(&ScriptStruct, ((uint8*) opaque_this) + sizeof(uint64), sizeof(UScriptStruct*));
	void* Data = (uint8*) opaque_this + (sizeof(uint64) + sizeof(UScriptStruct*));
	ScriptStruct->DestroyStruct(Data);
	FMemory::Free(opaque_this);
}

}	 // namespace

inworld::BaseData* FInworldData_Struct::CreateRuntimeData_Internal()
{
	const uint64 Size{GetSize()};
	UScriptStruct* ScriptStruct{GetDataScriptStruct()};
	void* Copy = FMemory::Malloc(Size);

	uint8* Write = (uint8*) Copy;
	FMemory::Memcpy(Write, &Size, sizeof(uint64));
	Write += sizeof(uint64);
	FMemory::Memcpy(Write, &ScriptStruct, sizeof(UScriptStruct*));
	Write += sizeof(UScriptStruct*);
	ScriptStruct->InitializeDefaultValue(Write);
	ScriptStruct->CopyScriptStruct(Write, GetData());

	UInworldRuntimeSubsystem::Get()->SetCustomDataConverter(ScriptStruct->GetPathName(),
		[](void* opaque_this) -> FInworldDataHandle
		{
			return FInworldData_Struct(opaque_this);
		});

	return new inworld::CustomDataWrapper(TCHAR_TO_UTF8(*ScriptStruct->GetPathName()), static_cast<void*>(Copy),
		inworld::CustomDataToJsonExecutor(Copy, reinterpret_cast<void*>(&ToJsonCallback)),
		inworld::CustomDataDestructor(Copy, reinterpret_cast<void*>(&FreeCallback)));
}

uint64 FInworldData_Struct::GetSize() const
{
	uint64 Size{0};
	if (Blob != nullptr)
	{
		FMemory::Memcpy(&Size, Blob, sizeof(uint64));
	}
	return Size;
}

UScriptStruct* FInworldData_Struct::GetDataScriptStruct() const
{
	UScriptStruct* ScriptStruct{nullptr};
	if (Blob != nullptr)
	{
		FMemory::Memcpy(&ScriptStruct, ((uint8*) Blob) + sizeof(uint64), sizeof(UScriptStruct*));
	}
	return ScriptStruct;
}

void* FInworldData_Struct::GetData() const
{
	return Blob == nullptr ? nullptr : (uint8*) Blob + (sizeof(uint64) + sizeof(UScriptStruct*));
}

FInworldData_Error::FInworldData_Error(const inworld::BaseData& Data)
{
	if (inworld::Error inworld_error = inworld::BaseDataAs_Error(Data))
	{
		Reason = UTF8_TO_TCHAR(inworld_error.status().ToString().c_str());
		bOk = inworld_error.status().ok();
	}
}

inworld::BaseData* FInworldData_Error::CreateRuntimeData_Internal()
{
	inworld::StatusCode StatusCode = inworld::StatusCode::kOk;
	if (!bOk)
	{
		StatusCode = inworld::StatusCode::kUnknown;
	}
	inworld::Status status =
		inworld::Status::inworld_absl_Status_createStatus__SWIG(StatusCode, std::string(TCHAR_TO_UTF8(*Reason)));
	return new inworld::Error(status);
}

FInworldData_Text::FInworldData_Text(const inworld::BaseData& Data)
{
	if (inworld::Text inworld_text = inworld::BaseDataAs_Text(Data))
	{
		Text = UTF8_TO_TCHAR(inworld_text.value().c_str());
	}
}

inworld::BaseData* FInworldData_Text::CreateRuntimeData_Internal()
{
	return new inworld::Text(TCHAR_TO_UTF8(*Text));
}

FInworldData_LLMChatRequest::FInworldData_LLMChatRequest(const inworld::BaseData& Data)
{
	static const FString roleUser("user");
	static const FString roleAssistant("assistant");
	static const FString roleTool("tool");
	static const FString roleSystem("system");
	static const FString roleDeveloper("developer");

	if (inworld::LLMChatRequest llm_chat_request = inworld::BaseDataAs_LLMChatRequest(Data))
	{
		// vector_map_string_string& messages = inworld_chat_messages->messages();
		// const inworld::vector_map_string_string& messages = inworld_chat_messages->messages();
		inworld::Messages messages{llm_chat_request.messages()};
		for (int i = 0; i < messages.size(); ++i)
		{
			FInworldLLMMessage& LLMMessage = ChatMessages.AddDefaulted_GetRef();

			FString role = UTF8_TO_TCHAR(messages.get(i).role().c_str());

			if (role.Equals(roleUser, ESearchCase::IgnoreCase))
			{
				LLMMessage.Role = EInworldLLMMessageRole::USER;
			}
			else if (role.Equals(roleAssistant, ESearchCase::IgnoreCase))
			{
				LLMMessage.Role = EInworldLLMMessageRole::ASSISTANT;
			}
			else if (role.Equals(roleTool, ESearchCase::IgnoreCase))
			{
				LLMMessage.Role = EInworldLLMMessageRole::TOOL;
			}
			else if (role.Equals(roleSystem, ESearchCase::IgnoreCase))
			{
				LLMMessage.Role = EInworldLLMMessageRole::SYSTEM;
			}
			else if (role.Equals(roleDeveloper, ESearchCase::IgnoreCase))
			{
				LLMMessage.Role = EInworldLLMMessageRole::DEVELOPER;
			}
			LLMMessage.Content = UTF8_TO_TCHAR(messages.get(i).GetTextContent().c_str());
		}

		if (llm_chat_request.response_format() == inworld::ResponseFormat::kText)
		{
			ResponseFormat = EInworldLLMResponseFormat::TEXT;
		}
		else if (llm_chat_request.response_format() == inworld::ResponseFormat::kJson)
		{
			ResponseFormat = EInworldLLMResponseFormat::JSON;
		}
		else if (llm_chat_request.response_format() == inworld::ResponseFormat::kJsonSchema)
		{
			ResponseFormat = EInworldLLMResponseFormat::JSONSCHEMA;
		}
	}
}

inworld::BaseData* FInworldData_LLMChatRequest::CreateRuntimeData_Internal()
{
	inworld::Messages messages;
	inworld::Convert(ChatMessages, messages);
	inworld::ResponseFormat response_format = inworld::ResponseFormat::kText;
	switch (ResponseFormat)
	{
		case EInworldLLMResponseFormat::TEXT:
			response_format = inworld::ResponseFormat::kText;
			break;
		case EInworldLLMResponseFormat::JSON:
			response_format = inworld::ResponseFormat::kJson;
			break;
		case EInworldLLMResponseFormat::JSONSCHEMA:
			response_format = inworld::ResponseFormat::kJsonSchema;
			break;
	}

	return new inworld::LLMChatRequest(messages, {}, {}, response_format);
}

FInworldData_LLMChatResponse::FInworldData_LLMChatResponse(const inworld::BaseData& Data)
{
	if (inworld::LLMChatResponse inworld_chat_response = inworld::BaseDataAs_LLMChatResponse(Data))
	{
		bIsStreaming = inworld_chat_response.is_streaming();
		if (bIsStreaming)
		{
			InputStream = MakeShared<inworld::InputStream_Content>(inworld_chat_response.get_response_stream());
		}
		else
		{
			Content = UTF8_TO_TCHAR(inworld_chat_response.get_response_content().content().c_str());
		}
	}
}

bool FInworldData_LLMChatResponse::HasNext() const
{
	return InputStream.IsValid() && InputStream->HasNext();
}

FInworldDataHandle FInworldData_LLMChatResponse::GetNext()
{
	inworld::StatusOr_Content next = HasNext() ? InputStream->Read() : inworld::StatusOr_Content{};
	if (!next.ok())
	{
		return FInworldData_Error(TEXT("Failed to read from LLM Chat Response Stream"));
	}
	FInworldData_Text Text;
	Text.Text = UTF8_TO_TCHAR(next.value().content().c_str());
	Content.Append(Text.Text);
	return Text;
}

inworld::BaseData* FInworldData_LLMChatResponse::CreateRuntimeData_Internal()
{
	// If this is streaming, drain the stream to content.
	// TODO: Support creating streams
	if (bIsStreaming)
	{
		while (HasNext())
		{
			GetNext();
		}
	}
	inworld::Content response;
	response.content(TCHAR_TO_UTF8(*Content));
	return new inworld::LLMChatResponse(response);
}

FInworldData_Audio::FInworldData_Audio(const inworld::BaseData& Data)
{
	if (inworld::Audio inworld_audio = inworld::BaseDataAs_Audio(Data))
	{
		// TODO: memcpy from library
		Waveform->SetNumUninitialized(inworld_audio.data().size() * sizeof(float));
		for (int i = 0; i < inworld_audio.data().size(); ++i)
		{
			const float chunk{inworld_audio.data().get(i)};
			FMemory::Memcpy(Waveform->GetData() + (i * sizeof(float)), &chunk, sizeof(float));
		}
		SampleRate = inworld_audio.sample_rate();
	}
}

inworld::BaseData* FInworldData_Audio::CreateRuntimeData_Internal()
{
	return new inworld::Audio(
		inworld::memcpy_to_vector_float((float*) Waveform->GetData(), Waveform->Num() / sizeof(float)), SampleRate);
}

FInworldData_KnowledgeRecords::FInworldData_KnowledgeRecords(const inworld::BaseData& Data)
{
	if (inworld::KnowledgeRecords inworld_knowledge_records = inworld::BaseDataAs_KnowledgeRecords(Data))
	{
		inworld::vector_string records{inworld_knowledge_records.records()};
		for (int i = 0; i < records.size(); ++i)
		{
			Records.Add(UTF8_TO_TCHAR(records.get(i).c_str()));
		}
	}
}

inworld::BaseData* FInworldData_KnowledgeRecords::CreateRuntimeData_Internal()
{
	inworld::vector_string records;
	for (const FString& Record : Records)
	{
		records.push_back({TCHAR_TO_UTF8(*Record)});
	}
	return new inworld::KnowledgeRecords(records);
}

FInworldData_TTSOutput::FInworldData_TTSOutput(const inworld::BaseData& Data)
{
	// TODO: Voice should be a type of base data - TTSOutput(?)
}

inworld::BaseData* FInworldData_TTSOutput::CreateRuntimeData_Internal()
{
	return new inworld::Audio(
		inworld::memcpy_to_vector_float((float*) Audio.Waveform->GetData(), Audio.Waveform->Num() / sizeof(float)),
		Audio.SampleRate);
}

FInworldData_ClassificationResult::FInworldData_ClassificationResult(const inworld::BaseData& Data)
{
	if (inworld::ClassificationResult classification_result = inworld::BaseDataAs_ClassificationResult(Data))
	{
		Classes.Reserve(classification_result.classes().size());
		for (int i = 0; i < classification_result.classes().size(); ++i)
		{
			FString& Classification = Classes.AddDefaulted_GetRef();
			Classification = UTF8_TO_TCHAR(classification_result.classes().get(i).c_str());
		}
	}
}

inworld::BaseData* FInworldData_ClassificationResult::CreateRuntimeData_Internal()
{
	inworld::ClassificationResult classification_result;
	classification_result.classes().reserve(Classes.Num());
	for (int i = 0; i < Classes.Num(); ++i)
	{
		classification_result.classes().push_back(TCHAR_TO_UTF8(*Classes[i]));
	}
	return new inworld::ClassificationResult(classification_result);
}

FInworldData_MatchedKeywords::FInworldData_MatchedKeywords(const inworld::BaseData& Data)
{
	if (inworld::MatchedKeywords matched_keywords = inworld::BaseDataAs_MatchedKeywords(Data))
	{
		Matches.Reserve(matched_keywords.matches().size());
		for (int i = 0; i < matched_keywords.matches().size(); ++i)
		{
			FInworldKeywordMatch& KeywordMatch = Matches.AddDefaulted_GetRef();
			KeywordMatch.GroupName = UTF8_TO_TCHAR(matched_keywords.matches().get(i).group_name().c_str());
			KeywordMatch.Keyword = UTF8_TO_TCHAR(matched_keywords.matches().get(i).keyword().c_str());
		}
	}
}

inworld::BaseData* FInworldData_MatchedKeywords::CreateRuntimeData_Internal()
{
	inworld::MatchedKeywords matched_keywords;
	matched_keywords.matches().reserve(Matches.Num());
	for (int i = 0; i < Matches.Num(); ++i)
	{
		inworld::KeywordMatch keyword_match;
		keyword_match.group_name(TCHAR_TO_UTF8(*Matches[i].GroupName));
		keyword_match.keyword(TCHAR_TO_UTF8(*Matches[i].Keyword));
		matched_keywords.matches().push_back(keyword_match);
	}
	return new inworld::MatchedKeywords(matched_keywords);
}

FInworldData_MatchedIntents::FInworldData_MatchedIntents(const inworld::BaseData& Data)
{
	if (inworld::MatchedIntents matched_intents = inworld::BaseDataAs_MatchedIntents(Data))
	{
		for (int i = 0; i < matched_intents.intent_matches().size(); ++i)
		{
			FInworldIntentMatch& IntentMatch = IntentMatches.AddDefaulted_GetRef();
			IntentMatch.IntentId = UTF8_TO_TCHAR(matched_intents.intent_matches().get(i).intent_name().c_str());
			IntentMatch.Score = matched_intents.intent_matches().get(i).score();
		}
	}
}

inworld::BaseData* FInworldData_MatchedIntents::CreateRuntimeData_Internal()
{
	inworld::vector_IntentMatch intent_matches;
	for (const FInworldIntentMatch& IntentMatch : IntentMatches)
	{
		inworld::IntentMatch intent_match;
		intent_match.intent_name(TCHAR_TO_UTF8(*IntentMatch.IntentId.ToString()));
		intent_match.score(IntentMatch.Score);
		intent_matches.push_back(intent_match);
	}
	return new inworld::MatchedIntents(intent_matches);
}

FInworldData_EventHistory::FInworldData_EventHistory(const inworld::BaseData& Data)
{
	if (inworld::EventHistory event_history = inworld::BaseDataAs_EventHistory(Data))
	{
		EventHistory = MakeShared<inworld::vector_Event>(event_history.events());
		for (int i{0}; i < EventHistory->size(); ++i)
		{
			inworld::Event event = EventHistory->get(i);
			if (inworld::AgentSpeech* Speech = event.speech(); Speech != nullptr)
			{
				FInworldEventSpeech SpeechEvent{
					UTF8_TO_TCHAR(Speech->agent_name().c_str()), UTF8_TO_TCHAR(Speech->utterance().c_str())};
				if (!SpeechEvent.AgentName.IsEmpty() && !SpeechEvent.Utterance.IsEmpty())
				{
					SpeechEvents.Add(MoveTemp(SpeechEvent));
				}
			}
		}
	}
}

inworld::BaseData* FInworldData_EventHistory::CreateRuntimeData_Internal()
{
	if (EventHistory == nullptr)
	{
		EventHistory = MakeShared<inworld::vector_Event>();
	}
	while (EventHistory->size() < SpeechEvents.Num())
	{
		const int32 NextIndex = (EventHistory->size() + (SpeechEvents.Num() - EventHistory->size())) - 1;
		inworld::Event event;
		event.speech()->agent_name(TCHAR_TO_UTF8(*SpeechEvents[NextIndex].AgentName));
		event.speech()->utterance(TCHAR_TO_UTF8(*SpeechEvents[NextIndex].Utterance));
		EventHistory->push_back(event);
	}
	return new inworld::EventHistory(*EventHistory);
}

FInworldData_Json::FInworldData_Json(const inworld::BaseData& Data)
{
	if (inworld::Json json = inworld::BaseDataAs_Json(Data))
	{
		JsonString = FString(UTF8_TO_TCHAR(json.ToString().c_str()));
	}
}

inworld::BaseData* FInworldData_Json::CreateRuntimeData_Internal()
{
	return new inworld::Json(
		inworld::Json::inworld_inworld_graphs_Json_FromString__SWIG(std::string(TCHAR_TO_UTF8(*JsonString))).value());
}

FInworldDataHandle FInworldData_Stream::GetNext() PURE_VIRTUAL(FInworldData_Stream::GetNext, return {};);

FInworldData_DataStream_String::FInworldData_DataStream_String(const inworld::BaseData& Data)
{
	if (inworld::DataStream_String text_stream = inworld::BaseDataAs_DataStream_String(Data))
	{
		InputStream = MakeShared<inworld::InputStream_string>(text_stream.stream());
	}
}

inworld::BaseData* FInworldData_DataStream_String::CreateRuntimeData_Internal()
{
	return new inworld::DataStream_String(*InputStream.Get());
}

bool FInworldData_DataStream_String::HasNext() const
{
	return InputStream.IsValid() && InputStream->HasNext();
}

FInworldDataHandle FInworldData_DataStream_String::GetNext()
{
	inworld::StatusOr_string next = HasNext() ? InputStream->Read() : inworld::StatusOr_string{};
	if (!next.ok())
	{
		return FInworldData_Error(TEXT("Failed to read from String Stream"));
	}
	return FInworldData_Text(UTF8_TO_TCHAR(next.value().c_str()));
}

FInworldData_DataStream_TTSOutput::FInworldData_DataStream_TTSOutput(const inworld::BaseData& Data)
{
	if (inworld::DataStream_TTSOutput tts_output_stream = inworld::BaseDataAs_DataStream_TTSOutput(Data))
	{
		InputStream = MakeShared<inworld::InputStream_TTSOutput>(tts_output_stream.stream());
	}
}

inworld::BaseData* FInworldData_DataStream_TTSOutput::CreateRuntimeData_Internal()
{
	return new inworld::DataStream_TTSOutput(*InputStream.Get());
}

bool FInworldData_DataStream_TTSOutput::HasNext() const
{
	return InputStream.IsValid() && InputStream->HasNext();
}

FInworldDataHandle FInworldData_DataStream_TTSOutput::GetNext()
{
	auto tts_output_or = HasNext() ? InputStream->Read() : inworld::StatusOr_TTSOutput{};
	if (!tts_output_or.ok())
	{
		const FString Reason = UTF8_TO_TCHAR(tts_output_or.status().ToString().c_str());
		return FInworldData_Error(FString::Printf(TEXT("Failed to read from TTS stream: %s"), *Reason));
	}

	FInworldData_TTSOutput Voice;
	inworld::TTSOutput tts_output = tts_output_or.value();

	Voice.Text += UTF8_TO_TCHAR(tts_output.text().c_str());

	// getting phonemes, converting to visime
	// inworld::vector_string* phonemes_codes = tts_output.phonemes();
	// inworld::vector_float* phonemes_timestamps = tts_output.timestamps();
	// float ChunkTimeOffset = 0.0f;
	// if (phonemes_codes && phonemes_timestamps)
	//{
	//	const int PhonemesSize = FMath::Min(phonemes_codes->size(), phonemes_timestamps->size());
	//	for (int i = 0; i < PhonemesSize; ++i)
	//	{
	//		if (i > 0 && phonemes_timestamps->get(i) < phonemes_timestamps->get(i - 1))
	//		{
	//			ChunkTimeOffset += phonemes_timestamps->get(i - 1);
	//		}

	//		const std::string Viseme = FInworldRuntimeUtils::PhonemeToViseme(phonemes_codes->get(i));
	//		if (!Viseme.empty())
	//		{
	//			float PhonemesTimestamp = phonemes_timestamps->get(i) + ChunkTimeOffset;
	//			Voice.VisemeInfos.Add({UTF8_TO_TCHAR(Viseme.c_str()), PhonemesTimestamp});
	//		}
	//	}
	//}
	// delete phonemes_codes;
	// phonemes_codes = nullptr;
	// delete phonemes_timestamps;
	// phonemes_timestamps = nullptr;

	if (inworld::Audio audio = tts_output.safe_audio(); audio.is_valid())
	{
		Voice.Audio.SampleRate = audio.sample_rate();
		Voice.Audio.Waveform->SetNumUninitialized(audio.data().size() * sizeof(float));

		for (int32 i = 0; i < audio.data().size(); i++)
		{
			float sample = audio.data().get(i);
			FMemory::Memcpy(Voice.Audio.Waveform->GetData() + (i * sizeof(float)), &sample, sizeof(float));
		}
	}
	return Voice;
}

FInworldData_TTSRequest::FInworldData_TTSRequest(const inworld::BaseData& Data)
{
	if (inworld::TTSRequest inworld_tts_request = inworld::BaseDataAs_TTSRequest(Data))
	{
		if (inworld_tts_request.has_text_content())
		{
			bHasTextContent = true;
			Text = FString(UTF8_TO_TCHAR(inworld_tts_request.get_text_content().value().c_str()));
		}
		if (inworld_tts_request.has_stream_content())
		{
			bHasStreamContent = true;
			InputStream = MakeShared<inworld::InputStream_string>(inworld_tts_request.get_content_stream());
		}
		inworld::optional_Voice optional_voice = inworld_tts_request.get_voice();
		if (optional_voice.has_value())
		{
			inworld::Convert(optional_voice.get_value(), Voice);
		}
	}
}

bool FInworldData_TTSRequest::HasNext() const
{
	return InputStream.IsValid() && InputStream->HasNext();
}

FInworldDataHandle FInworldData_TTSRequest::GetNext()
{
	inworld::StatusOr_string next = HasNext() ? InputStream->Read() : inworld::StatusOr_string{};
	if (!next.ok())
	{
		const FString Reason = UTF8_TO_TCHAR(next.status().ToString().c_str());
		return FInworldData_Error(FString::Printf(TEXT("Failed to read from TTS stream: %s"), *Reason));
	}
	return FInworldData_Text(UTF8_TO_TCHAR(next.value().c_str()));
}

inworld::BaseData* FInworldData_TTSRequest::CreateRuntimeData_Internal()
{
	inworld::Voice voice;
	inworld::Convert(Voice, voice);
	if (bHasTextContent)
	{
		return new inworld::TTSRequest(TCHAR_TO_UTF8(*Text), voice);
	}
	if (bHasStreamContent)
	{
		inworld::DataStream_String* datastream_string = new inworld::DataStream_String(*InputStream.Get());
		return new inworld::TTSRequest(*datastream_string, voice);
	}
	inworld::Status status = inworld::Status::inworld_absl_Status_createStatus__SWIG(
		inworld::StatusCode::kInvalidArgument, "Has neither text or stream content.");
	return new inworld::Error(status);
}

FInworldData_DataStream_AudioChunk::FInworldData_DataStream_AudioChunk()
{
	Stream.Set<TSharedPtr<inworld::BlockingStream_AudioChunk>>(
		MakeShared<inworld::BlockingStream_AudioChunk>(inworld::BlockingStream_AudioChunk::Create()));
}

FInworldData_DataStream_AudioChunk::FInworldData_DataStream_AudioChunk(const inworld::BaseData& Data)
{
	if (inworld::DataStream_AudioChunk audio_chunk_stream = inworld::BaseDataAs_DataStream_AudioChunk(Data))
	{
		Stream.Set<TSharedPtr<inworld::InputStream_AudioChunk>>(
			MakeShared<inworld::InputStream_AudioChunk>(audio_chunk_stream.stream()));
	}
}

#if WITH_EDITOR && !UE_BUILD_SHIPPING
void FInworldData_DataStream_AudioChunk::SetDebugDumpFileName(const FString& InDebugDumpFileName)
{
	DebugDumpFileName = InDebugDumpFileName;
	IFileManager::Get().Delete(*DebugDumpFileName.GetValue());
}
#endif

void FInworldData_DataStream_AudioChunk::Write(const FInworldData_Audio& Audio)
{
	if (Stream.IsType<TSharedPtr<inworld::BlockingStream_AudioChunk>>())
	{
#if WITH_EDITOR && !UE_BUILD_SHIPPING
		if (DebugDumpFileName.IsSet())
		{
			inworld::audio::debug::DumpAudio(DebugDumpFileName.GetValue(), Audio);
		}
#endif

		inworld::AudioChunk audio_chunk;
		// Not a 1:1 type conversion, implement in-line
#if defined(INWORLD_IMPLEMENTATION)
		audio_chunk.sample_rate(Audio.SampleRate);
		inworld::vector_float vector_float =
			inworld::memcpy_to_vector_float((float*) Audio.Waveform->GetData(), Audio.Waveform->Num() / sizeof(float));
		audio_chunk.data(&vector_float);
#endif
		Stream.Get<TSharedPtr<inworld::BlockingStream_AudioChunk>>()->Write(audio_chunk);
	}
}

void FInworldData_DataStream_AudioChunk::Close()
{
	if (Stream.IsType<TSharedPtr<inworld::BlockingStream_AudioChunk>>())
	{
		Stream.Get<TSharedPtr<inworld::BlockingStream_AudioChunk>>()->Close();
	}
}

inworld::BaseData* FInworldData_DataStream_AudioChunk::CreateRuntimeData_Internal()
{
	if (Stream.IsType<TSharedPtr<inworld::InputStream_AudioChunk>>())
	{
		return new inworld::DataStream_AudioChunk(*Stream.Get<TSharedPtr<inworld::InputStream_AudioChunk>>().Get());
	}
	else if (Stream.IsType<TSharedPtr<inworld::BlockingStream_AudioChunk>>())
	{
		return new inworld::DataStream_AudioChunk(*Stream.Get<TSharedPtr<inworld::BlockingStream_AudioChunk>>().Get());
	}
	else
	{
		inworld::Status status = inworld::Status::inworld_absl_Status_createStatus__SWIG(
			inworld::StatusCode::kInvalidArgument, "Has neither readable or writable stream.");
		return new inworld::Error(status);
	}
}

FInworldData_ListToolsResponse::FInworldData_ListToolsResponse(const inworld::BaseData& Data)
{
	if (const inworld::ListToolsResponse inworld_list_tools_response = inworld::BaseDataAs_ListToolsResponse(Data))
	{
		Tools.Reserve(inworld_list_tools_response.list_tools().size());
		for (int i = 0; i < inworld_list_tools_response.list_tools().size(); ++i)
		{
			const inworld::Tool inworld_tool = inworld_list_tools_response.list_tools().get(i);
			FInworldTool& Tool = Tools.AddDefaulted_GetRef();
			Tool.Name = UTF8_TO_TCHAR(inworld_tool.name().c_str());
			Tool.Description = UTF8_TO_TCHAR(inworld_tool.description().c_str());
			Tool.Properties = UTF8_TO_TCHAR(inworld_tool.GetPropertiesAsString().c_str());
		}
	}
}

inworld::BaseData* FInworldData_ListToolsResponse::CreateRuntimeData_Internal()
{
	inworld::Vector_Tool inworld_tools;
	for (const FInworldTool& Tool : Tools)
	{
		inworld::Tool inworld_tool;
		inworld_tool.name(TCHAR_TO_UTF8(*Tool.Name));
		inworld_tool.description(TCHAR_TO_UTF8(*Tool.Description));
		inworld_tool.SetPropertiesFromString(TCHAR_TO_UTF8(*Tool.Properties));
		inworld_tools.push_back(inworld_tool);
	}
	return new inworld::ListToolsResponse(inworld_tools);
}

FInworldData_ToolCallRequest::FInworldData_ToolCallRequest(const inworld::BaseData& Data)
{
	if (const inworld::ToolCallRequest inworld_tool_call_request = inworld::BaseDataAs_ToolCallRequest(Data))
	{
		ToolCalls.Reserve(inworld_tool_call_request.tool_calls().size());
		for (int i = 0; i < inworld_tool_call_request.tool_calls().size(); ++i)
		{
			const inworld::ToolCall inworld_tool_call = inworld_tool_call_request.tool_calls().get(i);
			FInworldToolCall& ToolCall = ToolCalls.AddDefaulted_GetRef();
			ToolCall.Id = UTF8_TO_TCHAR(inworld_tool_call.id().c_str());
			ToolCall.Name = UTF8_TO_TCHAR(inworld_tool_call.name().c_str());
			ToolCall.Args = UTF8_TO_TCHAR(inworld_tool_call.args().c_str());
		}
	}
}

inworld::BaseData* FInworldData_ToolCallRequest::CreateRuntimeData_Internal()
{
	inworld::ToolCalls inworld_tool_calls;
	for (const FInworldToolCall& ToolCall : ToolCalls)
	{
		inworld::ToolCall inworld_tool_call;
		inworld_tool_call.id(TCHAR_TO_UTF8(*ToolCall.Id));
		inworld_tool_call.name(TCHAR_TO_UTF8(*ToolCall.Name));
		inworld_tool_call.args(TCHAR_TO_UTF8(*ToolCall.Args));
		inworld_tool_calls.push_back(inworld_tool_call);
	}
	return new inworld::ToolCallRequest(inworld_tool_calls);
}

FInworldData_ToolCallResult::FInworldData_ToolCallResult(const inworld::BaseData& Data)
{
	if (const inworld::ToolCallResult inworld_tool_call_request = inworld::BaseDataAs_ToolCallResult(Data))
	{
		ToolCallId = UTF8_TO_TCHAR(inworld_tool_call_request.tool_call_id().c_str());
		Result = UTF8_TO_TCHAR(inworld_tool_call_request.result().c_str());
		ExtractResult();
	}
}

void FInworldData_ToolCallResult::ExtractResult()
{
	if (Result.IsEmpty())
	{
		return;
	}

	// The raw result JSON has the following format:
	/*
	{
		"content": [
			{
				"text":"...",
				"type":"text"
			}
		],
		"isError": false,
		"structuredContent": {
			"result": ...
		}
	}
	*/

	TSharedPtr<FJsonObject> JsonObject;
	TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Result);
	if (FJsonSerializer::Deserialize(Reader, JsonObject))
	{
		const TArray<TSharedPtr<FJsonValue>>* ContentObjects{nullptr};
		if (JsonObject->TryGetArrayField(TEXT("content"), ContentObjects))
		{
			ExtractedResults.Reserve(ContentObjects->Num());
			for (const TSharedPtr<FJsonValue>& Content : *ContentObjects)
			{
				const TSharedPtr<FJsonObject>* ContentObject{nullptr};
				if (Content->TryGetObject(ContentObject) && ContentObject->IsValid())
				{
					FString& ExtractedResult = ExtractedResults.AddDefaulted_GetRef();
					ContentObject->Get()->TryGetStringField(TEXT("text"), ExtractedResult);
				}
			}
		}
	}
}

inworld::BaseData* FInworldData_ToolCallResult::CreateRuntimeData_Internal()
{
	return new inworld::ToolCallResult(TCHAR_TO_UTF8(*ToolCallId), TCHAR_TO_UTF8(*Result));
}

FInworldData_ToolCallResponse::FInworldData_ToolCallResponse(const inworld::BaseData& Data)
{
	if (const inworld::ToolCallResponse inworld_tool_call_response = inworld::BaseDataAs_ToolCallResponse(Data))
	{
		inworld::vector_ToolCallResult inworld_tool_call_results{inworld_tool_call_response.tool_call_results_swig()};
		ToolCallResults.Reserve(inworld_tool_call_results.size());
		for (int i = 0; i < inworld_tool_call_results.size(); ++i)
		{
			inworld::ToolCallResult inworld_tool_call_result{inworld_tool_call_results.get(i)};
			inworld_tool_call_result.swig_owns_self_ = false;

			FInworldData_ToolCallResult& ToolCallResult = ToolCallResults.AddDefaulted_GetRef();
			ToolCallResult.ToolCallId = UTF8_TO_TCHAR(inworld_tool_call_result.tool_call_id().c_str());
			ToolCallResult.Result = UTF8_TO_TCHAR(inworld_tool_call_result.result().c_str());
			ToolCallResult.ExtractResult();
		}
	}
}

inworld::BaseData* FInworldData_ToolCallResponse::CreateRuntimeData_Internal()
{
	inworld::vector_ToolCallResult inworld_tool_call_results;
	for (const FInworldData_ToolCallResult& ToolCallResult : ToolCallResults)
	{
		inworld::ToolCallResult inworld_tool_call_result(
			TCHAR_TO_UTF8(*ToolCallResult.ToolCallId), TCHAR_TO_UTF8(*ToolCallResult.Result));
		inworld_tool_call_results.push_back(inworld_tool_call_result);
	}
	return new inworld::ToolCallResponse(inworld_tool_call_results);
}
