// Copyright Zeus Server Engine. All rights reserved.

#include "ZeusFrontierOverlayActor.h"

#include "ZMMONetLog.h" // LogZeusAOI (categoria do modulo ZMMO)

#include "ProceduralMeshComponent.h"
#include "Materials/MaterialInterface.h"
#include "Engine/World.h"
#include "DrawDebugHelpers.h"
#include "UObject/ConstructorHelpers.h"

namespace
{
	const TCHAR* kWallMaterialPath =
		TEXT("/Game/ZMMO/Materials/VFX/Portal/GridBoundary/Instancia/MI_VFX_Portal_GridBoundary_01.MI_VFX_Portal_GridBoundary_01");

	const FColor kLabelAlive(93, 213, 255); // ciano  -- server online
	const FColor kLabelDead(255, 90, 90);   // vermelho -- server offline / cell vazia

	// Decodifica o cellId na notacao "raiz fora, splits no array" da k-d tree
	// binaria: raiz pura -> "0"; derivada -> "0,[0,1]". Espelha o layout
	// bit-packed de Server/ZeusServices/Shared/types/src/cell-id.ts:
	//   bit 30      = flag (1 = cell derivada de split)
	//   bits 29..26 = depth (profundidade de split)
	//   bits 25..15 = rootId (cell raiz do baseGrid)
	//   bits 14..0  = path (1 bit por nivel, MSB-first)
	FString DecodeCellPath(uint32 CellId)
	{
		constexpr uint32 kFlagBit = 1u << 30;
		if ((CellId & kFlagBit) == 0u)
		{
			return FString::Printf(TEXT("%u"), CellId); // raiz pura
		}
		const uint32 Depth = (CellId >> 26) & 0xFu;
		const uint32 Root = (CellId >> 15) & 0x7FFu;
		const uint32 Packed = CellId & 0x7FFFu;
		FString Path;
		for (uint32 i = 0; i < Depth; ++i)
		{
			if (i > 0)
			{
				Path += TEXT(",");
			}
			Path += ((Packed >> (14u - i)) & 1u) ? TEXT("1") : TEXT("0");
		}
		return FString::Printf(TEXT("%u,[%s]"), Root, *Path);
	}

	// Aresta compartilhada entre dois retangulos axis-aligned (no plano XY).
	bool SharedEdge(float aMinX, float aMinY, float aMaxX, float aMaxY,
	                float bMinX, float bMinY, float bMaxX, float bMaxY,
	                float Eps, FVector2D& OutA, FVector2D& OutB)
	{
		const float yLo = FMath::Max(aMinY, bMinY), yHi = FMath::Min(aMaxY, bMaxY);
		const float xLo = FMath::Max(aMinX, bMinX), xHi = FMath::Min(aMaxX, bMaxX);

		if (FMath::Abs(aMaxX - bMinX) <= Eps && yHi > yLo) { OutA = {aMaxX, yLo}; OutB = {aMaxX, yHi}; return true; }
		if (FMath::Abs(aMinX - bMaxX) <= Eps && yHi > yLo) { OutA = {aMinX, yLo}; OutB = {aMinX, yHi}; return true; }
		if (FMath::Abs(aMaxY - bMinY) <= Eps && xHi > xLo) { OutA = {xLo, aMaxY}; OutB = {xHi, aMaxY}; return true; }
		if (FMath::Abs(aMinY - bMaxY) <= Eps && xHi > xLo) { OutA = {xLo, aMinY}; OutB = {xHi, aMinY}; return true; }
		return false;
	}
}

AZeusFrontierOverlayActor::AZeusFrontierOverlayActor()
{
	PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;

	WallMesh = CreateDefaultSubobject<UProceduralMeshComponent>(TEXT("WallMesh"));
	SetRootComponent(WallMesh);
	WallMesh->bUseAsyncCooking = false;
	WallMesh->SetCollisionEnabled(ECollisionEnabled::NoCollision);
	WallMesh->SetCastShadow(false);

	static ConstructorHelpers::FObjectFinder<UMaterialInterface> MatFinder(kWallMaterialPath);
	if (MatFinder.Succeeded())
	{
		WallMaterial = MatFinder.Object;
	}
}

void AZeusFrontierOverlayActor::RebuildFromFrontier(const ZeusV1::FDebugFrontierInfo& Info, float AnchorZCm)
{
	if (!WallMesh) { return; }
	AnchorZ_ = AnchorZCm;

	// 1) Junta own + neighbors numa lista unica de cells (grid inteiro). Dedup por cellId.
	Cells_.Reset();
	auto AddCell = [this](uint32 CellId, int32 MinX, int32 MinY, int32 MaxX, int32 MaxY,
	                      const FString& InOwner, bool bIsDead)
	{
		for (const FCellView& Ex : Cells_) { if (Ex.CellId == CellId) { return; } }
		FCellView V;
		V.CellId = CellId;
		V.MinX = static_cast<float>(MinX); V.MinY = static_cast<float>(MinY);
		V.MaxX = static_cast<float>(MaxX); V.MaxY = static_cast<float>(MaxY);
		V.Owner = InOwner; V.bDead = bIsDead;
		Cells_.Add(MoveTemp(V));
	};
	for (const ZeusV1::FFrontierOwnCell& C : Info.OwnedCells)
	{
		AddCell(C.CellId, C.MinXCm, C.MinYCm, C.MaxXCm, C.MaxYCm, C.OwnerName, false);
	}
	for (const ZeusV1::FFrontierNeighborCell& Nb : Info.NeighborCells)
	{
		AddCell(Nb.CellId, Nb.MinXCm, Nb.MinYCm, Nb.MaxXCm, Nb.MaxYCm, Nb.OwnerName, Nb.bDead);
	}

	// 2) Paredes: 1 quad por aresta compartilhada entre QUALQUER par (i<j -> dedup).
	const float BaseZ = AnchorZCm - WallHeightCm * 0.5f;
	const float TopZ  = AnchorZCm + WallHeightCm * 0.5f;

	TArray<FVector> Verts;
	TArray<int32> Tris;
	TArray<FVector> Normals;
	TArray<FVector2D> UVs;
	TArray<FProcMeshTangent> Tangents;
	TArray<FLinearColor> Colors;

	for (int32 i = 0; i < Cells_.Num(); ++i)
	{
		for (int32 j = i + 1; j < Cells_.Num(); ++j)
		{
			const FCellView& A = Cells_[i];
			const FCellView& B = Cells_[j];
			FVector2D P0, P1;
			if (!SharedEdge(A.MinX, A.MinY, A.MaxX, A.MaxY,
			                B.MinX, B.MinY, B.MaxX, B.MaxY, EdgeEpsilonCm, P0, P1))
			{
				continue;
			}

			const int32 Base = Verts.Num();
			Verts.Add(FVector(P0.X, P0.Y, BaseZ));
			Verts.Add(FVector(P1.X, P1.Y, BaseZ));
			Verts.Add(FVector(P1.X, P1.Y, TopZ));
			Verts.Add(FVector(P0.X, P0.Y, TopZ));

			const FVector2D Dir2D = (P1 - P0).GetSafeNormal();
			const FVector N(-Dir2D.Y, Dir2D.X, 0.f);
			Normals.Add(N); Normals.Add(N); Normals.Add(N); Normals.Add(N);

			UVs.Add(FVector2D(0.f, 1.f));
			UVs.Add(FVector2D(1.f, 1.f));
			UVs.Add(FVector2D(1.f, 0.f));
			UVs.Add(FVector2D(0.f, 0.f));

			const FProcMeshTangent Tan(FVector(Dir2D.X, Dir2D.Y, 0.f), false);
			Tangents.Add(Tan); Tangents.Add(Tan); Tangents.Add(Tan); Tangents.Add(Tan);

			const FLinearColor C = (A.bDead || B.bDead) ? FLinearColor(kLabelDead) : FLinearColor(kLabelAlive);
			Colors.Add(C); Colors.Add(C); Colors.Add(C); Colors.Add(C);

			Tris.Add(Base + 0); Tris.Add(Base + 1); Tris.Add(Base + 2);
			Tris.Add(Base + 0); Tris.Add(Base + 2); Tris.Add(Base + 3);
			Tris.Add(Base + 0); Tris.Add(Base + 2); Tris.Add(Base + 1);
			Tris.Add(Base + 0); Tris.Add(Base + 3); Tris.Add(Base + 2);
		}
	}

	WallMesh->ClearAllMeshSections();
	WallMesh->SetVisibility(true);
	if (Verts.Num() > 0)
	{
		WallMesh->CreateMeshSection_LinearColor(
			/*SectionIndex=*/0, Verts, Tris, Normals, UVs, Colors, Tangents, /*bCreateCollision=*/false);
		if (WallMaterial) { WallMesh->SetMaterial(0, WallMaterial); }
	}
	bHasOverlay_ = (Cells_.Num() > 0);

	UE_LOG(LogZeusAOI, Display,
		TEXT("Frontier overlay rebuilt: cells=%d walls=%d anchorZ=%.0f%s"),
		Cells_.Num(), Verts.Num() / 4, AnchorZCm,
		WallMaterial ? TEXT("") : TEXT(" [WARN: material nula]"));
}

void AZeusFrontierOverlayActor::ClearOverlay()
{
	if (WallMesh)
	{
		WallMesh->ClearAllMeshSections();
		WallMesh->SetVisibility(false);
	}
	Cells_.Reset();
	bHasOverlay_ = false;
}

void AZeusFrontierOverlayActor::DrawCellLabels()
{
	if (!bHasOverlay_ || Cells_.Num() == 0) { return; }
	UWorld* World = GetWorld();
	if (!World) { return; }

	for (const FCellView& C : Cells_)
	{
		const float Cx = (C.MinX + C.MaxX) * 0.5f;
		const float Cy = (C.MinY + C.MaxY) * 0.5f;
		const FVector Pos(Cx, Cy, AnchorZ_ + LabelHeightCm);

		// "Cell 0" / "Cell 0,2" (notacao da quadtree) + nome do server dono.
		FString Txt = FString::Printf(TEXT("Cell %s"), *DecodeCellPath(C.CellId));
		if (!C.Owner.IsEmpty()) { Txt += TEXT("\n") + C.Owner; }
		const FColor Col = C.bDead ? kLabelDead : kLabelAlive;

		// DrawDebugString: billboard de tela (visivel de qualquer lado, nao gira,
		// tamanho de tela constante). Re-desenhado a cada frame (Duration 0).
		DrawDebugString(World, Pos, Txt, /*TestBaseActor=*/nullptr, Col,
		                /*Duration=*/0.0f, /*bDrawShadow=*/true, LabelFontScale);
	}
}

void AZeusFrontierOverlayActor::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);
	DrawCellLabels();
}
