#include "UIInput_Base.h"

#include "ZMMOThemeSubsystem.h"
#include "Engine/GameInstance.h"
#include "Engine/DataTable.h"
#include "Components/EditableTextBox.h"
#include "Components/SizeBox.h"
#include "Components/Image.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateBrush.h"
#include "UI/UIStyleRow.h"

namespace
{
	// Mesmo helper das outras bases: runtime usa o tema ativo; design-time
	// carrega a row "Default" de DT_UI_Styles para o Designer ver igual.
	FUIStyle ResolveInputStyle(const UUserWidget* Widget, const FUIStyle& Fallback)
	{
		if (Widget)
		{
			if (const UGameInstance* GI = Widget->GetGameInstance())
			{
				if (const UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
				{
					return Theme->GetActiveUIStyle();
				}
			}
		}
		FUIStyle DesignStyle;
		bool bHas = false;
		if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
			TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
		{
			if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
				FName(TEXT("Default")), TEXT("UIInputDesign"), false))
			{
				DesignStyle = Row->Style;
				bHas = true;
			}
		}
		return bHas ? DesignStyle : Fallback;
	}

	// Pinta um UImage com o FSlateBrush vindo do DT (já tem Image, ImageSize,
	// TintColor, DrawAs, Tiling, Margin/9-slice, UV — tudo). Brush vazio
	// (sem ResourceObject e DrawAs!=NoDrawType) força Image procedural com
	// Tint da própria struct — evita brush nulo que vira magenta.
	void PaintImage(UImage* Img, const FSlateBrush& Brush)
	{
		if (!IsValid(Img)) { return; }
		FSlateBrush B = Brush;
		if (!B.GetResourceObject() && B.DrawAs != ESlateBrushDrawType::NoDrawType)
		{
			// Sem textura: pintura procedural (cor sólida).
			B.DrawAs = ESlateBrushDrawType::Image;
		}
		Img->SetBrush(B);
		// SetColorAndOpacity = identidade; cor já vem do Brush.TintColor.
		Img->SetColorAndOpacity(FLinearColor::White);
	}
}

TArray<FString> UUIInput_Base::CollectVariantOptions()
{
	TArray<FString> Options;
	for (const TCHAR* N : { TEXT("Box"), TEXT("Outline"), TEXT("Underline"), TEXT("Search") })
	{
		Options.Add(N);
	}
	if (const UDataTable* DT = LoadObject<UDataTable>(nullptr,
		TEXT("/Game/ZMMO/Data/UI/DT_UI_Styles.DT_UI_Styles")))
	{
		if (const FUIStyleRow* Row = DT->FindRow<FUIStyleRow>(
			FName(TEXT("Default")), TEXT("UIInputVariantOptions"), false))
		{
			for (const TPair<FName, FUIStyleInputBackground>& P : Row->Style.Input.Backgrounds) { Options.AddUnique(P.Key.ToString()); }
			for (const TPair<FName, FUIStyleInputStroke>&     P : Row->Style.Input.Strokes)     { Options.AddUnique(P.Key.ToString()); }
			for (const TPair<FName, FUIStyleInputTypography>& P : Row->Style.Input.Typography)  { Options.AddUnique(P.Key.ToString()); }
		}
	}
	return Options;
}

TArray<FString> UUIInput_Base::GetVariantOptions() const
{
	return CollectVariantOptions();
}

FUIStyleInputVariant UUIInput_Base::ResolveVariant(const FUIStyleInput& I) const
{
	// Padrão multi-map (Hyper-style): compõe a variante buscando o nome
	// (Variant FName) em CADA sub-mapa independente. Se um sub-mapa não
	// tiver a chave, cai nos Defaults inline (sempre GC-safe — só
	// cores/floats no constructor de FUIStyleInput).
	static const FUIStyleInput Defaults;
	FUIStyleInputVariant V;

	if (const FUIStyleInputBackground* E = I.Backgrounds.Find(Variant))      { V.Background = *E; }
	else if (const FUIStyleInputBackground* E2 = Defaults.Backgrounds.Find(Variant)) { V.Background = *E2; }
	else { V.Background = Defaults.Backgrounds.FindRef(TEXT("Box")); }

	if (const FUIStyleInputStroke* E = I.Strokes.Find(Variant))      { V.Stroke = *E; }
	else if (const FUIStyleInputStroke* E2 = Defaults.Strokes.Find(Variant)) { V.Stroke = *E2; }
	else { V.Stroke = Defaults.Strokes.FindRef(TEXT("Box")); }

	if (const FUIStyleInputTypography* E = I.Typography.Find(Variant))      { V.Typography = *E; }
	else if (const FUIStyleInputTypography* E2 = Defaults.Typography.Find(Variant)) { V.Typography = *E2; }
	else { V.Typography = Defaults.Typography.FindRef(TEXT("Box")); }

	return V;
}

void UUIInput_Base::ApplyStyle(const FUIStyleInputVariant& V, float InPadding,
	const FSlateFontInfo& ThemeFont, float FontSizePx)
{
	// Background e Stroke (UImages) — seguro em design-time e runtime.
	if (IsValid(Background))
	{
		PaintImage(Background, V.Background.Brush);
	}
	if (IsValid(Stroke))
	{
		PaintImage(Stroke, V.Stroke.Brush);
	}

	if (!IsValid(Input))
	{
		return;
	}

	// IMPORTANTE: NÃO mexer no UEditableTextBox em design-time. Qualquer
	// chamada (SetForegroundColor / SetIsPassword / SetHintText / SetWidgetStyle)
	// dispara SEditableText::SynchronizeTextStyle → DetermineFont via FAttribute
	// delegate dangling → crash de "FSlateFontInfo::FSlateFontInfo() reading
	// 0xff..." nesta build da engine. Em design-time só pintamos Background/
	// Stroke (UImages, path estável). Preview do HintText/Password só em PIE.
	if (IsDesignTime())
	{
		return;
	}

	// SÓ chamadas pontuais — NÃO chamar SetWidgetStyle (copia FEditableTextBoxStyle
	// inteiro incluindo TextStyle.Font). DetermineFont callback durante Prepass
	// do Slate dereferencia FSlateFontInfo dangling se a Font veio do DT com
	// FontObject UObject que ficou em estado intermediário → crash em
	// FSlateFontInfo::FSlateFontInfo() durante SEditableText::SynchronizeTextStyle.
	// Background brushes do EditableTextBox: configurar transparente direto no
	// asset UI_Input_Master no Designer (one-time setup) — o Background/Stroke
	// (UImages) atrás do EditableTextBox é quem pinta o visual via DT.
	Input->SetForegroundColor(V.Typography.TextColor);
	Input->SetIsPassword(bIsPassword);
	if (!HintText.IsEmpty())
	{
		Input->SetHintText(HintText);
	}

	// Suprime warnings de parâmetros não usados (assinatura mantida pra
	// compatibilidade — InPadding/ThemeFont/FontSizePx podem ser usados
	// futuramente quando o caminho de SetWidgetStyle for re-habilitado de
	// forma segura).
	(void)InPadding;
	(void)ThemeFont;
	(void)FontSizePx;
}

void UUIInput_Base::RefreshUIStyle()
{
	const FUIStyle Fallback;
	const FUIStyle AS = ResolveInputStyle(this, Fallback);
	const FUIStyleInput& I = AS.Input;
	const FUIStyleInputVariant V = ResolveVariant(I);

	// Tamanho via Width/Height Override (mais agressivo que MinDesired — força
	// tamanho exato). Resolução em 3 camadas:
	//   1. PreferredWidth/Height UPROPERTY > 0  → override explícito (designer)
	//   2. User mexeu DIRETO no Size_Root.WidthOverride (Designer)  → preserva
	//   3. Senão  → aplica do DT (I.MinWidth/MinHeight)
	if (IsValid(Size_Root))
	{
		auto ApplyDimension = [](USizeBox* Box, bool bIsWidth, float PreferredVal,
			float ThemeVal, float& LastThemeApplied)
		{
			const bool bHasManualFlag = bIsWidth ? Box->IsWidthOverride() : Box->IsHeightOverride();
			const float CurrentVal    = bIsWidth ? Box->GetWidthOverride() : Box->GetHeightOverride();
			auto Setter = [Box, bIsWidth](float V)
			{
				if (bIsWidth) { Box->SetWidthOverride(V); }
				else          { Box->SetHeightOverride(V); }
			};

			if (PreferredVal > 0.f)
			{
				// (1) Override explícito via UPROPERTY.
				Setter(PreferredVal);
				LastThemeApplied = -1.f; // user controla via UPROPERTY a partir daqui
			}
			else if (bHasManualFlag && LastThemeApplied >= 0.f && CurrentVal != LastThemeApplied)
			{
				// (2) User editou direto no SizeBox no Designer — preserva.
			}
			else if (bHasManualFlag && LastThemeApplied < 0.f)
			{
				// (2b) Flag manual ativo mas C++ nunca aplicou nesta instância
				// (fresh load): assume que é override do user no asset — preserva.
			}
			else
			{
				// (3) Aplica do DT e marca como "C++ aplicou".
				Setter(ThemeVal);
				LastThemeApplied = ThemeVal;
			}
		};

		ApplyDimension(Size_Root, /*bIsWidth=*/true,  PreferredWidth,  I.MinWidth,  LastThemeWidthApplied);
		ApplyDimension(Size_Root, /*bIsWidth=*/false, PreferredHeight, I.MinHeight, LastThemeHeightApplied);
	}

	// Size: 0 na instância → usa V.Typography.Size (do DT da variante).
	const float ChosenSize = (FontSize > 0.f) ? FontSize : V.Typography.Size;
	// Font: V.Typography.FontOverride se válida; senão FUIStyle.Text.BodyFont.
	const FSlateFontInfo& ThemeFont = V.Typography.FontOverride.HasValidFont()
		? V.Typography.FontOverride : AS.Text.BodyFont;
	ApplyStyle(V, I.Padding, ThemeFont, ChosenSize);

	BP_ApplyInputStyle(I);
}

FText UUIInput_Base::GetText() const
{
	return IsValid(Input) ? Input->GetText() : FText::GetEmpty();
}

void UUIInput_Base::SetText(FText InText)
{
	if (IsValid(Input))
	{
		Input->SetText(InText);
	}
}

void UUIInput_Base::SetHintTextRuntime(FText InHint)
{
	HintText = InHint;
	if (IsValid(Input)) { Input->SetHintText(InHint); }
}

void UUIInput_Base::SetIsPasswordRuntime(bool bInIsPassword)
{
	bIsPassword = bInIsPassword;
	if (IsValid(Input)) { Input->SetIsPassword(bInIsPassword); }
}

void UUIInput_Base::NativePreConstruct()
{
	Super::NativePreConstruct();
	RefreshUIStyle();
}

void UUIInput_Base::SynchronizeProperties()
{
	Super::SynchronizeProperties();
	RefreshUIStyle();
}

void UUIInput_Base::NativeConstruct()
{
	Super::NativeConstruct();

	if (!bInputBound && IsValid(Input))
	{
		Input->OnTextChanged.AddDynamic(this, &UUIInput_Base::HandleTextChanged);
		Input->OnTextCommitted.AddDynamic(this, &UUIInput_Base::HandleTextCommitted);
		bInputBound = true;
	}

	if (!bThemeBound)
	{
		if (const UGameInstance* GI = GetGameInstance())
		{
			if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
			{
				Theme->OnThemeChanged.AddDynamic(this, &UUIInput_Base::HandleThemeChanged);
				bThemeBound = true;
			}
		}
	}
	RefreshUIStyle();
}

void UUIInput_Base::NativeDestruct()
{
	if (bInputBound && IsValid(Input))
	{
		Input->OnTextChanged.RemoveDynamic(this, &UUIInput_Base::HandleTextChanged);
		Input->OnTextCommitted.RemoveDynamic(this, &UUIInput_Base::HandleTextCommitted);
		bInputBound = false;
	}
	if (bThemeBound)
	{
		if (const UGameInstance* GI = GetGameInstance())
		{
			if (UZMMOThemeSubsystem* Theme = GI->GetSubsystem<UZMMOThemeSubsystem>())
			{
				Theme->OnThemeChanged.RemoveDynamic(this, &UUIInput_Base::HandleThemeChanged);
			}
		}
		bThemeBound = false;
	}
	Super::NativeDestruct();
}

void UUIInput_Base::HandleThemeChanged(FName /*NewThemeId*/)
{
	RefreshUIStyle();
}

void UUIInput_Base::HandleTextChanged(const FText& Text)
{
	OnTextChanged.Broadcast(Text);
}

void UUIInput_Base::HandleTextCommitted(const FText& Text, ETextCommit::Type CommitMethod)
{
	OnTextCommitted.Broadcast(Text, CommitMethod);
}
