#include "HyperTwistTraining/HyperTwistTrainingCompanionLibrary.h"

namespace HyperTwistTrainingCompanionLibraryInternal
{
	FHyperTwistTrainingSourceAttribution MakeAttribution(
		const FString& TrackId,
		const FString& Notes
	)
	{
		FHyperTwistTrainingSourceAttribution Attribution;
		Attribution.SourceMode = EHyperTwistTrainingSourceMode::DirectDonor;
		Attribution.SourceRepo = TEXT("met4citizen/TalkingHead");
		Attribution.SourceLicense = TEXT("MIT");
		Attribution.SourceTrackId = TrackId;
		Attribution.SourceNotes = Notes;
		return Attribution;
	}

	FHyperTwistTrainingCompanionSpeechInputReference MakeSpeechInput(
		const FString& InputId,
		const FString& Label,
		const TArray<FString>& InputKinds,
		const TArray<FString>& ConfigFields,
		const FString& Summary,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionSpeechInputReference Input;
		Input.InputId = InputId;
		Input.Label = Label;
		Input.InputKinds = InputKinds;
		Input.ConfigFields = ConfigFields;
		Input.Summary = Summary;
		Input.SourceAttribution = SourceAttribution;
		return Input;
	}

	FHyperTwistTrainingCompanionQueueLifecycleState MakeQueueState(
		const FString& StateId,
		const FString& Label,
		const FString& Trigger,
		const FString& OutputSurface,
		const TArray<FString>& MetricLabels,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionQueueLifecycleState State;
		State.StateId = StateId;
		State.Label = Label;
		State.Trigger = Trigger;
		State.OutputSurface = OutputSurface;
		State.MetricLabels = MetricLabels;
		State.SourceAttribution = SourceAttribution;
		return State;
	}

	FHyperTwistTrainingCompanionSubtitleCueReference MakeSubtitleCueReference(
		const FString& CueTemplateId,
		const FString& Label,
		const TArray<FString>& RequiredFields,
		const TArray<FString>& SyncSignals,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionSubtitleCueReference Cue;
		Cue.CueTemplateId = CueTemplateId;
		Cue.Label = Label;
		Cue.RequiredFields = RequiredFields;
		Cue.SyncSignals = SyncSignals;
		Cue.SourceAttribution = SourceAttribution;
		return Cue;
	}

	FHyperTwistTrainingCompanionVisemeReference MakeVisemeReference(
		const FString& VisemeReferenceId,
		const FString& Label,
		const TArray<FString>& TimingFields,
		const TArray<FString>& BlendshapeTargets,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionVisemeReference Reference;
		Reference.VisemeReferenceId = VisemeReferenceId;
		Reference.Label = Label;
		Reference.TimingFields = TimingFields;
		Reference.BlendshapeTargets = BlendshapeTargets;
		Reference.SourceAttribution = SourceAttribution;
		return Reference;
	}

	FHyperTwistTrainingCompanionGesturePresetReference MakeGesturePresetReference(
		const FString& GestureId,
		const FString& Label,
		const FString& Summary,
		const TArray<FString>& GestureTags,
		const TArray<FString>& RequiredStateTags,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionGesturePresetReference Reference;
		Reference.GestureId = GestureId;
		Reference.Label = Label;
		Reference.Summary = Summary;
		Reference.GestureTags = GestureTags;
		Reference.RequiredStateTags = RequiredStateTags;
		Reference.SourceAttribution = SourceAttribution;
		return Reference;
	}

	FHyperTwistTrainingCompanionMoodPresetReference MakeMoodPresetReference(
		const FString& MoodId,
		const FString& Label,
		const FString& Summary,
		const TArray<FString>& ActivationSignals,
		const FHyperTwistTrainingSourceAttribution& SourceAttribution
	)
	{
		FHyperTwistTrainingCompanionMoodPresetReference Reference;
		Reference.MoodId = MoodId;
		Reference.Label = Label;
		Reference.Summary = Summary;
		Reference.ActivationSignals = ActivationSignals;
		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]);
		}
	}
}

FHyperTwistTrainingCompanionReferenceBundle
UHyperTwistTrainingCompanionLibrary::BuildBundledEmbodiedCompanionReferenceBundle()
{
	using namespace HyperTwistTrainingCompanionLibraryInternal;

	FHyperTwistTrainingCompanionReferenceBundle Bundle;
	Bundle.ReferenceUtc = TEXT("2026-05-13T00:00:00Z");
	Bundle.PrimaryNarrationContractId = TEXT("embodied-companion-narration");
	Bundle.PrimaryAvatarEmbedContractId = TEXT("avatar-only-embed");
	Bundle.PrimaryListeningContractId = TEXT("listening-threshold-lifecycle");
	Bundle.PrimaryVendorAdapterBoundaryId = TEXT("tts-and-asset-adapter-boundary");

	Bundle.SpeechInputs =
	{
		MakeSpeechInput(
			TEXT("speech-input/text-script-and-voice-settings"),
			TEXT("Scripted text and voice settings"),
			{TEXT("coach-script"), TEXT("voice-parameters"), TEXT("subtitle-seed")},
			{
				TEXT("tts-lang"),
				TEXT("tts-voice"),
				TEXT("tts-rate"),
				TEXT("tts-pitch"),
				TEXT("tts-volume")
			},
			TEXT("Structured narration payload for queued coach speech, subtitle generation, and mood or gesture alignment."),
			MakeAttribution(
				TEXT("narration-queue"),
				TEXT("Preserves text-to-speech queueing, parameterized voice settings, and subtitle-seeded narration behavior from talkinghead.mjs.")
			)
		),
		MakeSpeechInput(
			TEXT("speech-input/streamed-audio-packets"),
			TEXT("Streamed audio packets"),
			{TEXT("streamed-audio"), TEXT("viseme-stream"), TEXT("marker-events")},
			{
				TEXT("jwt-get"),
				TEXT("tts-endpoint"),
				TEXT("tts-api-key"),
				TEXT("stream-buffer-ms")
			},
			TEXT("Streaming speech input for low-latency narration, live viseme scheduling, and queue underrun handling."),
			MakeAttribution(
				TEXT("streaming-audio"),
				TEXT("Preserves low-latency streaming playback, queue buffering, and live viseme alignment from the donor streaming examples and runtime queue.")
			)
		),
		MakeSpeechInput(
			TEXT("speech-input/audio-marker-overrides"),
			TEXT("Audio marker overrides"),
			{TEXT("marker-audio"), TEXT("prebuilt-audio"), TEXT("gesture-sync")},
			{
				TEXT("marker-id"),
				TEXT("audio-offset-ms"),
				TEXT("viseme-offset-ms"),
				TEXT("gesture-cue-id")
			},
			TEXT("Audio-marker input for externally prepared clips, marker callbacks, and deterministic gesture or subtitle sync."),
			MakeAttribution(
				TEXT("audio-markers"),
				TEXT("Preserves speakAudio, speakMarker, and marker-aligned callback behavior from the donor narration queue.")
			)
		)
	};

	Bundle.QueueLifecycleStates =
	{
		MakeQueueState(
			TEXT("queue/ready"),
			TEXT("Ready"),
			TEXT("avatar is loaded and no narration item is active"),
			TEXT("companion ready surface"),
			{TEXT("queue-depth"), TEXT("avatar-ready"), TEXT("voice-route-ready")},
			MakeAttribution(
				TEXT("queue-ready"),
				TEXT("Preserves the donor's idle-ready posture before narration or listening begins.")
			)
		),
		MakeQueueState(
			TEXT("queue/speaking"),
			TEXT("Speaking"),
			TEXT("queued narration or audio playback is active"),
			TEXT("speech, subtitle, and viseme surface"),
			{TEXT("queue-depth"), TEXT("subtitle-cue-count"), TEXT("viseme-frame-count")},
			MakeAttribution(
				TEXT("queue-speaking"),
				TEXT("Preserves queued speech playback, subtitle timing, and viseme emission behavior from talkinghead.mjs.")
			)
		),
		MakeQueueState(
			TEXT("queue/streaming"),
			TEXT("Streaming"),
			TEXT("audio stream packets are still arriving while playback is active"),
			TEXT("streaming playback surface"),
			{TEXT("stream-buffer-ms"), TEXT("underrun-count"), TEXT("pending-chunk-count")},
			MakeAttribution(
				TEXT("queue-streaming"),
				TEXT("Preserves live streaming, audio buffering, and underrun-aware queue handling from the donor streaming runtime.")
			)
		),
		MakeQueueState(
			TEXT("queue/listening"),
			TEXT("Listening"),
			TEXT("narration is paused or complete and microphone capture is active"),
			TEXT("speech-input and listening surface"),
			{TEXT("energy-threshold"), TEXT("speech-start-ms"), TEXT("speech-end-ms")},
			MakeAttribution(
				TEXT("queue-listening"),
				TEXT("Preserves listening lifecycle, threshold gating, and microphone-state expectations from the donor runtime.")
			)
		)
	};

	FHyperTwistTrainingCompanionNarrationContract NarrationContract;
	NarrationContract.ContractId = TEXT("embodied-companion-narration");
	NarrationContract.Title = TEXT("Embodied companion narration contract");
	NarrationContract.Summary = TEXT("First-party coach narration boundary for queued speech, streaming playback, viseme alignment, subtitles, markers, and coach-driven gesture or mood cues.");
	NarrationContract.SupportedSpeechInputIds =
	{
		TEXT("speech-input/text-script-and-voice-settings"),
		TEXT("speech-input/streamed-audio-packets"),
		TEXT("speech-input/audio-marker-overrides")
	};
	NarrationContract.QueueLifecycleStateIds =
	{
		TEXT("queue/ready"),
		TEXT("queue/speaking"),
		TEXT("queue/streaming"),
		TEXT("queue/listening")
	};
	NarrationContract.OutputSurfaceTags =
	{
		TEXT("audio-playback"),
		TEXT("subtitle-sync"),
		TEXT("viseme-envelope"),
		TEXT("marker-callback"),
		TEXT("gesture-cue"),
		TEXT("mood-cue")
	};
	NarrationContract.ExplicitExclusions =
	{
		TEXT("external TTS vendor product shell"),
		TEXT("sample avatar picker UI"),
		TEXT("donor example page chrome"),
		TEXT("generic speech-assistant platform ownership")
	};
	NarrationContract.SourceAttribution = MakeAttribution(
		TEXT("narration-contract"),
		TEXT("Preserves queued narration, subtitle synchronization, viseme timing, and gesture-aware coach speech behavior from the retained TalkingHead lane.")
	);
	Bundle.NarrationContracts = {NarrationContract};

	Bundle.SubtitleCueReferences =
	{
		MakeSubtitleCueReference(
			TEXT("subtitle/inline-word-cues"),
			TEXT("Inline word subtitle cues"),
			{TEXT("cue-id"), TEXT("start-ms"), TEXT("end-ms"), TEXT("text"), TEXT("emphasis-tags")},
			{TEXT("audio-playhead"), TEXT("word-boundary"), TEXT("gesture-cue")},
			MakeAttribution(
				TEXT("subtitle-inline"),
				TEXT("Preserves word-level subtitle timing and emphasis alignment from the donor queue and lipsync modules.")
			)
		),
		MakeSubtitleCueReference(
			TEXT("subtitle/marker-and-viseme-sync"),
			TEXT("Marker and viseme synchronized subtitles"),
			{TEXT("marker-id"), TEXT("caption-span"), TEXT("audio-offset-ms"), TEXT("viseme-offset-ms")},
			{TEXT("marker-callback"), TEXT("viseme-envelope"), TEXT("stream-chunk")},
			MakeAttribution(
				TEXT("subtitle-markers"),
				TEXT("Preserves subtitle, marker, and viseme co-scheduling behavior from streamed and prebuilt donor audio paths.")
			)
		)
	};

	Bundle.VisemeReferences =
	{
		MakeVisemeReference(
			TEXT("viseme/blendshape-envelope"),
			TEXT("Blendshape viseme envelope"),
			{TEXT("start-ms"), TEXT("end-ms"), TEXT("viseme-id"), TEXT("weight")},
			{TEXT("aa"), TEXT("oh"), TEXT("ee"), TEXT("sil"), TEXT("blink")},
			MakeAttribution(
				TEXT("viseme-envelope"),
				TEXT("Preserves blendshape-target viseme timing and envelope semantics from the donor lipsync modules.")
			)
		),
		MakeVisemeReference(
			TEXT("viseme/stream-frame"),
			TEXT("Streaming viseme frame"),
			{TEXT("chunk-index"), TEXT("frame-time-ms"), TEXT("viseme-id"), TEXT("strength")},
			{TEXT("aa"), TEXT("oh"), TEXT("ee"), TEXT("sil")},
			MakeAttribution(
				TEXT("viseme-stream"),
				TEXT("Preserves live streaming viseme-frame scheduling from the donor audio-streaming pathway.")
			)
		)
	};

	Bundle.GesturePresets =
	{
		MakeGesturePresetReference(
			TEXT("gesture/acknowledge"),
			TEXT("Acknowledge"),
			TEXT("Compact acknowledgement motion used when the coach confirms a learner action or successful state."),
			{TEXT("confirmation"), TEXT("micro-gesture"), TEXT("coach-feedback")},
			{TEXT("avatar-ready"), TEXT("gesture-enabled")},
			MakeAttribution(
				TEXT("gesture-acknowledge"),
				TEXT("Preserves gesture-trigger behavior and compact coach presentation patterns from the donor runtime.")
			)
		),
		MakeGesturePresetReference(
			TEXT("gesture/focus-point"),
			TEXT("Focus point"),
			TEXT("Pointing or emphasis gesture used when the coach highlights a case detail, scene element, or training target."),
			{TEXT("focus"), TEXT("pointing"), TEXT("scene-callout")},
			{TEXT("avatar-ready"), TEXT("scene-anchor-available")},
			MakeAttribution(
				TEXT("gesture-focus"),
				TEXT("Preserves pointing and focus-callout choreography from the donor gesture and pose orchestration surfaces.")
			)
		),
		MakeGesturePresetReference(
			TEXT("gesture/celebrate"),
			TEXT("Celebrate"),
			TEXT("Positive coach gesture used for completion, personal-best moments, or successful recognition review."),
			{TEXT("celebration"), TEXT("completion"), TEXT("positive-feedback")},
			{TEXT("avatar-ready"), TEXT("gesture-enabled")},
			MakeAttribution(
				TEXT("gesture-celebrate"),
				TEXT("Preserves celebratory motion and timed coach feedback patterns from the donor animation surface.")
			)
		)
	};

	Bundle.MoodPresets =
	{
		MakeMoodPresetReference(
			TEXT("mood/neutral"),
			TEXT("Neutral"),
			TEXT("Default coach presence for passive review, playback, and non-interruptive idle states."),
			{TEXT("idle"), TEXT("reset"), TEXT("no-guidance-escalation")},
			MakeAttribution(
				TEXT("mood-neutral"),
				TEXT("Preserves avatar mood-state defaults and reset behavior from talkinghead.mjs.")
			)
		),
		MakeMoodPresetReference(
			TEXT("mood/focused"),
			TEXT("Focused"),
			TEXT("Concentrated coach presence for error correction, inspection, and case-specific guidance."),
			{TEXT("analysis"), TEXT("error-review"), TEXT("focus-callout")},
			MakeAttribution(
				TEXT("mood-focused"),
				TEXT("Preserves focused pose and expression switching for inspection-heavy coaching states.")
			)
		),
		MakeMoodPresetReference(
			TEXT("mood/encouraging"),
			TEXT("Encouraging"),
			TEXT("Positive coach presence for affirmation, milestone completion, and motivational guidance."),
			{TEXT("success"), TEXT("encouragement"), TEXT("completion")},
			MakeAttribution(
				TEXT("mood-encouraging"),
				TEXT("Preserves positive mood and encouragement state changes from the donor avatar presentation layer.")
			)
		)
	};

	FHyperTwistTrainingCompanionAvatarEmbedContract EmbedContract;
	EmbedContract.ContractId = TEXT("avatar-only-embed");
	EmbedContract.Title = TEXT("Avatar-only embed contract");
	EmbedContract.Summary = TEXT("First-party external-scene avatar boundary for coach presence, camera-safe embedding, retargeted motion, and companion overlays without inheriting the donor demo shell.");
	EmbedContract.RequiredStateTags =
	{
		TEXT("avatar-only"),
		TEXT("external-scene"),
		TEXT("external-camera"),
		TEXT("retargeting-ready"),
		TEXT("transparent-background-ready")
	};
	EmbedContract.SceneBindingFields =
	{
		TEXT("scene-root"),
		TEXT("avatar-anchor"),
		TEXT("lighting-profile"),
		TEXT("posture-anchor"),
		TEXT("subtitle-anchor")
	};
	EmbedContract.CameraBindingFields =
	{
		TEXT("camera-target"),
		TEXT("camera-orbit"),
		TEXT("field-of-view"),
		TEXT("framing-policy"),
		TEXT("distance-bias")
	};
	EmbedContract.SupportedGestureIds =
	{
		TEXT("gesture/acknowledge"),
		TEXT("gesture/focus-point"),
		TEXT("gesture/celebrate")
	};
	EmbedContract.SupportedMoodIds =
	{
		TEXT("mood/neutral"),
		TEXT("mood/focused"),
		TEXT("mood/encouraging")
	};
	EmbedContract.ExplicitExclusions =
	{
		TEXT("donor demo shell"),
		TEXT("sample avatar bundles"),
		TEXT("donor camera-page assumptions"),
		TEXT("generic character-platform ownership")
	};
	EmbedContract.SourceAttribution = MakeAttribution(
		TEXT("avatar-only-embedding"),
		TEXT("Preserves avatarOnly external-scene embedding, camera-safe composition, and retargeting-aware companion presence from the retained TalkingHead lane.")
	);
	Bundle.AvatarEmbedContracts = {EmbedContract};

	FHyperTwistTrainingCompanionListeningContract ListeningContract;
	ListeningContract.ContractId = TEXT("listening-threshold-lifecycle");
	ListeningContract.Title = TEXT("Listening threshold and lifecycle contract");
	ListeningContract.Summary = TEXT("First-party speech-input contract for microphone activation, silence gating, queue handoff, and coach listening readiness beneath the companion lane.");
	ListeningContract.StartConditions =
	{
		TEXT("microphone-permission-granted"),
		TEXT("narration-queue-empty-or-paused"),
		TEXT("vad-ready")
	};
	ListeningContract.StopConditions =
	{
		TEXT("silence-timeout"),
		TEXT("manual-stop"),
		TEXT("speech-commit"),
		TEXT("narration-resume")
	};
	ListeningContract.ThresholdFields =
	{
		TEXT("energy-threshold"),
		TEXT("speech-start-ms"),
		TEXT("speech-end-ms"),
		TEXT("silence-gap-ms")
	};
	ListeningContract.MetricLabels =
	{
		TEXT("energy-threshold"),
		TEXT("speech-duration-ms"),
		TEXT("queue-underrun-count"),
		TEXT("stream-buffer-ms")
	};
	ListeningContract.SourceAttribution = MakeAttribution(
		TEXT("listening-thresholds"),
		TEXT("Preserves listening thresholds, speech detection timing, and queue handoff behavior from the donor runtime.")
	);
	Bundle.ListeningContracts = {ListeningContract};

	FHyperTwistTrainingCompanionVendorAdapterBoundary Boundary;
	Boundary.BoundaryId = TEXT("tts-and-asset-adapter-boundary");
	Boundary.Title = TEXT("TTS and asset adapter boundary");
	Boundary.Summary = TEXT("Separates shippable first-party companion behavior from donor example pages, sample avatar assumptions, and external TTS or CDN wiring.");
	Boundary.ReplaceableVendorFields =
	{
		TEXT("jwt-get"),
		TEXT("tts-endpoint"),
		TEXT("tts-api-key"),
		TEXT("avatar-uri"),
		TEXT("audio-stream-provider")
	};
	Boundary.FirstPartyOwnedFields =
	{
		TEXT("coach-script"),
		TEXT("subtitle-text"),
		TEXT("mood-policy"),
		TEXT("gesture-policy"),
		TEXT("session-cues"),
		TEXT("audio-route-policy")
	};
	Boundary.DemoOnlyBehaviors =
	{
		TEXT("sample avatar picker"),
		TEXT("vendor-specific tutorial pages"),
		TEXT("donor example-site framing"),
		TEXT("direct third-party service marketing copy")
	};
	Boundary.SourceAttribution = MakeAttribution(
		TEXT("adapter-boundary"),
		TEXT("Preserves configurable vendor and asset seams while keeping voice-service wiring, sample avatars, and donor demo framing outside the shippable companion owner.")
	);
	Bundle.VendorAdapterBoundaries = {Boundary};

	return Bundle;
}

bool UHyperTwistTrainingCompanionLibrary::TryGetNarrationContractById(
	const FHyperTwistTrainingCompanionReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingCompanionNarrationContract& OutContract
)
{
	for (const FHyperTwistTrainingCompanionNarrationContract& Contract : Bundle.NarrationContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingCompanionNarrationContract();
	return false;
}

bool UHyperTwistTrainingCompanionLibrary::TryGetAvatarEmbedContractById(
	const FHyperTwistTrainingCompanionReferenceBundle& Bundle,
	const FString& ContractId,
	FHyperTwistTrainingCompanionAvatarEmbedContract& OutContract
)
{
	for (const FHyperTwistTrainingCompanionAvatarEmbedContract& Contract : Bundle.AvatarEmbedContracts)
	{
		if (Contract.ContractId.Equals(ContractId, ESearchCase::IgnoreCase))
		{
			OutContract = Contract;
			return true;
		}
	}

	OutContract = FHyperTwistTrainingCompanionAvatarEmbedContract();
	return false;
}

bool UHyperTwistTrainingCompanionLibrary::TryGetVendorAdapterBoundaryById(
	const FHyperTwistTrainingCompanionReferenceBundle& Bundle,
	const FString& BoundaryId,
	FHyperTwistTrainingCompanionVendorAdapterBoundary& OutBoundary
)
{
	for (const FHyperTwistTrainingCompanionVendorAdapterBoundary& Boundary : Bundle.VendorAdapterBoundaries)
	{
		if (Boundary.BoundaryId.Equals(BoundaryId, ESearchCase::IgnoreCase))
		{
			OutBoundary = Boundary;
			return true;
		}
	}

	OutBoundary = FHyperTwistTrainingCompanionVendorAdapterBoundary();
	return false;
}

FString UHyperTwistTrainingCompanionLibrary::BuildSubtitleCueChecklistTsv(
	const FHyperTwistTrainingCompanionReferenceBundle& Bundle
)
{
	using namespace HyperTwistTrainingCompanionLibraryInternal;

	FString Output = TEXT("CueTemplateId\tLabel\tRequiredFields\tSyncSignals\tSourceRepo\n");
	for (const FHyperTwistTrainingCompanionSubtitleCueReference& Cue : Bundle.SubtitleCueReferences)
	{
		FString RequiredFields;
		AppendJoined(RequiredFields, Cue.RequiredFields);

		FString SyncSignals;
		AppendJoined(SyncSignals, Cue.SyncSignals);

		Output.Appendf(
			TEXT("%s\t%s\t%s\t%s\t%s\n"),
			*Cue.CueTemplateId,
			*Cue.Label,
			*RequiredFields,
			*SyncSignals,
			*Cue.SourceAttribution.SourceRepo
		);
	}

	return Output;
}
