// Copyright Pacificao. All Rights Reserved.

#include "AgrarianCampfire.h"
#include "AgrarianFoliagePatch.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianGameState.h"
#include "AgrarianInventoryComponent.h"
#include "AgrarianPersistentActorComponent.h"
#include "AgrarianResourceNode.h"
#include "AgrarianShelterActor.h"
#include "AgrarianSurvivalComponent.h"
#include "Particles/ParticleSystemComponent.h"
#include "Components/AudioComponent.h"
#include "Components/PointLightComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/StaticMesh.h"
#include "Kismet/GameplayStatics.h"
#include "Materials/MaterialInterface.h"
#include "Net/UnrealNetwork.h"
#include "UObject/ConstructorHelpers.h"

namespace
{
	void ConfigureCampfireProxyComponent(UStaticMeshComponent* Component, UStaticMesh* MeshAsset, UMaterialInterface* MaterialAsset)
	{
		if (!Component)
		{
			return;
		}

		Component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		Component->SetGenerateOverlapEvents(false);
		if (MeshAsset)
		{
			Component->SetStaticMesh(MeshAsset);
		}
		if (MaterialAsset)
		{
			Component->SetMaterial(0, MaterialAsset);
		}
	}
}

AAgrarianCampfire::AAgrarianCampfire()
{
	PrimaryActorTick.bCanEverTick = true;
	bReplicates = true;
	SetNetCullDistanceSquared(FMath::Square(6000.0f));

	Mesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Mesh"));
	RootComponent = Mesh;
	Mesh->SetCollisionProfileName(TEXT("BlockAll"));

	static ConstructorHelpers::FObjectFinder<UStaticMesh> CylinderMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cylinder.SM_AGR_Placeholder_Cylinder"));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> ChamferCubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_ChamferCube.SM_AGR_Placeholder_ChamferCube"));
	static ConstructorHelpers::FObjectFinder<UStaticMesh> CubeMesh(TEXT("/Game/Agrarian/Environment/PlaceholderMeshes/SM_AGR_Placeholder_Cube.SM_AGR_Placeholder_Cube"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> StoneMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Stone_Sandstone.M_AGR_GZ_Stone_Sandstone"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> WoodMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Wood_Resource.M_AGR_GZ_Wood_Resource"));
	static ConstructorHelpers::FObjectFinder<UMaterialInterface> FiberMaterial(TEXT("/Game/Agrarian/Materials/M_AGR_GZ_Fiber_Resource.M_AGR_GZ_Fiber_Resource"));

	if (CylinderMesh.Succeeded())
	{
		Mesh->SetStaticMesh(CylinderMesh.Object);
		Mesh->SetRelativeScale3D(FVector(0.72f, 0.72f, 0.08f));
	}
	if (StoneMaterial.Succeeded())
	{
		Mesh->SetMaterial(0, StoneMaterial.Object);
	}

	StoneRingProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StoneRingProxy"));
	StoneRingProxy->SetupAttachment(RootComponent);
	ConfigureCampfireProxyComponent(StoneRingProxy, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, StoneMaterial.Succeeded() ? StoneMaterial.Object : nullptr);
	StoneRingProxy->SetRelativeScale3D(FVector(1.05f, 1.05f, 0.12f));

	LogProxyA = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LogProxyA"));
	LogProxyA->SetupAttachment(RootComponent);
	ConfigureCampfireProxyComponent(LogProxyA, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, WoodMaterial.Succeeded() ? WoodMaterial.Object : nullptr);
	LogProxyA->SetRelativeLocation(FVector(0.0f, -12.0f, 18.0f));
	LogProxyA->SetRelativeRotation(FRotator(0.0f, 90.0f, 90.0f));
	LogProxyA->SetRelativeScale3D(FVector(0.16f, 0.16f, 0.72f));

	LogProxyB = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LogProxyB"));
	LogProxyB->SetupAttachment(RootComponent);
	ConfigureCampfireProxyComponent(LogProxyB, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, WoodMaterial.Succeeded() ? WoodMaterial.Object : nullptr);
	LogProxyB->SetRelativeLocation(FVector(-12.0f, 10.0f, 20.0f));
	LogProxyB->SetRelativeRotation(FRotator(0.0f, 28.0f, 90.0f));
	LogProxyB->SetRelativeScale3D(FVector(0.16f, 0.16f, 0.68f));

	LogProxyC = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("LogProxyC"));
	LogProxyC->SetupAttachment(RootComponent);
	ConfigureCampfireProxyComponent(LogProxyC, CylinderMesh.Succeeded() ? CylinderMesh.Object : nullptr, WoodMaterial.Succeeded() ? WoodMaterial.Object : nullptr);
	LogProxyC->SetRelativeLocation(FVector(12.0f, 10.0f, 22.0f));
	LogProxyC->SetRelativeRotation(FRotator(0.0f, -28.0f, 90.0f));
	LogProxyC->SetRelativeScale3D(FVector(0.16f, 0.16f, 0.68f));

	EmberProxy = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("EmberProxy"));
	EmberProxy->SetupAttachment(RootComponent);
	ConfigureCampfireProxyComponent(EmberProxy, ChamferCubeMesh.Succeeded() ? ChamferCubeMesh.Object : (CubeMesh.Succeeded() ? CubeMesh.Object : nullptr), FiberMaterial.Succeeded() ? FiberMaterial.Object : nullptr);
	EmberProxy->SetRelativeLocation(FVector(0.0f, 0.0f, 13.0f));
	EmberProxy->SetRelativeScale3D(FVector(0.38f, 0.38f, 0.09f));

	FireLight = CreateDefaultSubobject<UPointLightComponent>(TEXT("FireLight"));
	FireLight->SetupAttachment(RootComponent);
	FireLight->SetIntensity(0.0f);
	FireLight->SetAttenuationRadius(WarmthRadius);
	FireLight->SetLightColor(FLinearColor(1.0f, 0.45f, 0.18f));

	SmokeEffect = CreateDefaultSubobject<UParticleSystemComponent>(TEXT("SmokeEffect"));
	SmokeEffect->SetupAttachment(RootComponent);
	SmokeEffect->bAutoActivate = false;
	SmokeEffect->SetRelativeLocation(FVector(0.0f, 0.0f, 80.0f));
	SmokeEffect->SetVisibility(false);

	FireLoopAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("FireLoopAudioComponent"));
	FireLoopAudioComponent->SetupAttachment(RootComponent);
	FireLoopAudioComponent->bAutoActivate = false;
	FireLoopAudioComponent->bAllowSpatialization = true;

	FireEventAudioComponent = CreateDefaultSubobject<UAudioComponent>(TEXT("FireEventAudioComponent"));
	FireEventAudioComponent->SetupAttachment(RootComponent);
	FireEventAudioComponent->bAutoActivate = false;
	FireEventAudioComponent->bAllowSpatialization = true;

	PersistentActorComponent = CreateDefaultSubobject<UAgrarianPersistentActorComponent>(TEXT("PersistentActorComponent"));
	PersistentActorComponent->ActorTypeId = TEXT("campfire");
}

void AAgrarianCampfire::Tick(float DeltaSeconds)
{
	Super::Tick(DeltaSeconds);

	if (HasAuthority() && bLit)
	{
		FuelSeconds = FMath::Max(0.0f, FuelSeconds - (DeltaSeconds * GetWeatherFuelDrainMultiplier()));
		if (FuelSeconds <= 0.0f || (bWetWeatherCanExtinguish && IsWetWeatherActive() && FuelSeconds <= WetWeatherExtinguishFuelThresholdSeconds))
		{
			Extinguish();
		}

		if (CanCook())
		{
			CookingProgressSeconds = FMath::Min(CookingSecondsRequired, CookingProgressSeconds + DeltaSeconds);
		}

		UpdateFireRisk(DeltaSeconds);
		UpdateVegetationIgnitionRisk(DeltaSeconds);
		UpdateStructureIgnitionRisk(DeltaSeconds);
		UpdateServerAuthoritativeFireSpread(DeltaSeconds);
		WarmNearbyCharacters(DeltaSeconds);
	}
}

void AAgrarianCampfire::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	DOREPLIFETIME(AAgrarianCampfire, bLit);
	DOREPLIFETIME(AAgrarianCampfire, FuelSeconds);
	DOREPLIFETIME(AAgrarianCampfire, bCookingPlaceholderEnabled);
	DOREPLIFETIME(AAgrarianCampfire, CookingSecondsRequired);
	DOREPLIFETIME(AAgrarianCampfire, CookingProgressSeconds);
	DOREPLIFETIME(AAgrarianCampfire, FireRiskScore);
	DOREPLIFETIME(AAgrarianCampfire, LitDurationSeconds);
	DOREPLIFETIME(AAgrarianCampfire, SecondsSinceMaintenance);
	DOREPLIFETIME(AAgrarianCampfire, bFireAreaCleared);
	DOREPLIFETIME(AAgrarianCampfire, bFireContained);
	DOREPLIFETIME(AAgrarianCampfire, GrassIgnitionRiskScore);
	DOREPLIFETIME(AAgrarianCampfire, ForestIgnitionRiskScore);
	DOREPLIFETIME(AAgrarianCampfire, bGrassOrBrushIgnited);
	DOREPLIFETIME(AAgrarianCampfire, bForestFuelIgnited);
	DOREPLIFETIME(AAgrarianCampfire, StructureIgnitionRiskScore);
	DOREPLIFETIME(AAgrarianCampfire, bStructureIgnited);
	DOREPLIFETIME(AAgrarianCampfire, GrassFireIntensity);
	DOREPLIFETIME(AAgrarianCampfire, ForestFireIntensity);
	DOREPLIFETIME(AAgrarianCampfire, StructureFireIntensity);
	DOREPLIFETIME(AAgrarianCampfire, ActiveFireSpreadRadius);
}

FText AAgrarianCampfire::GetInteractionText_Implementation(const AAgrarianGameCharacter* Interactor) const
{
	return bLit ? FText::FromString(TEXT("Maintain fire")) : FText::FromString(TEXT("Light fire"));
}

bool AAgrarianCampfire::CanInteract_Implementation(const AAgrarianGameCharacter* Interactor) const
{
	return Interactor != nullptr;
}

void AAgrarianCampfire::Interact_Implementation(AAgrarianGameCharacter* Interactor)
{
	if (!HasAuthority() || !Interactor)
	{
		return;
	}

	UAgrarianInventoryComponent* Inventory = Interactor->GetInventoryComponent();
	if (Inventory && Inventory->RemoveItem(TEXT("wood"), 1))
	{
		AddFuel(90.0f);
	}
	else if (bLit)
	{
		WatchFire();
	}
}

void AAgrarianCampfire::CapturePersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent) const
{
	if (!PersistentComponent)
	{
		return;
	}

	PersistentComponent->NumberState.Add(TEXT("lit"), bLit ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("fuel_seconds"), FuelSeconds);
	PersistentComponent->NumberState.Add(TEXT("cooking_placeholder_enabled"), bCookingPlaceholderEnabled ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("cooking_seconds_required"), CookingSecondsRequired);
	PersistentComponent->NumberState.Add(TEXT("cooking_progress_seconds"), CookingProgressSeconds);
	PersistentComponent->NumberState.Add(TEXT("fire_risk_score"), FireRiskScore);
	PersistentComponent->NumberState.Add(TEXT("lit_duration_seconds"), LitDurationSeconds);
	PersistentComponent->NumberState.Add(TEXT("seconds_since_maintenance"), SecondsSinceMaintenance);
	PersistentComponent->NumberState.Add(TEXT("fire_area_cleared"), bFireAreaCleared ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("fire_contained"), bFireContained ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("grass_ignition_risk_score"), GrassIgnitionRiskScore);
	PersistentComponent->NumberState.Add(TEXT("forest_ignition_risk_score"), ForestIgnitionRiskScore);
	PersistentComponent->NumberState.Add(TEXT("grass_or_brush_ignited"), bGrassOrBrushIgnited ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("forest_fuel_ignited"), bForestFuelIgnited ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("structure_ignition_risk_score"), StructureIgnitionRiskScore);
	PersistentComponent->NumberState.Add(TEXT("structure_ignited"), bStructureIgnited ? 1.0f : 0.0f);
	PersistentComponent->NumberState.Add(TEXT("grass_fire_intensity"), GrassFireIntensity);
	PersistentComponent->NumberState.Add(TEXT("forest_fire_intensity"), ForestFireIntensity);
	PersistentComponent->NumberState.Add(TEXT("structure_fire_intensity"), StructureFireIntensity);
	PersistentComponent->NumberState.Add(TEXT("active_fire_spread_radius"), ActiveFireSpreadRadius);
	PersistentComponent->NumberState.Add(TEXT("fire_suppression_pressure"), FireSuppressionPressure);
}

void AAgrarianCampfire::ApplyPersistentState_Implementation(UAgrarianPersistentActorComponent* PersistentComponent)
{
	if (!HasAuthority() || !PersistentComponent)
	{
		return;
	}

	const float* SavedFuelSeconds = PersistentComponent->NumberState.Find(TEXT("fuel_seconds"));
	const float* SavedCookingEnabled = PersistentComponent->NumberState.Find(TEXT("cooking_placeholder_enabled"));
	const float* SavedCookingRequired = PersistentComponent->NumberState.Find(TEXT("cooking_seconds_required"));
	const float* SavedCookingProgress = PersistentComponent->NumberState.Find(TEXT("cooking_progress_seconds"));
	const float* SavedLit = PersistentComponent->NumberState.Find(TEXT("lit"));
	const float* SavedRiskScore = PersistentComponent->NumberState.Find(TEXT("fire_risk_score"));
	const float* SavedLitDuration = PersistentComponent->NumberState.Find(TEXT("lit_duration_seconds"));
	const float* SavedSecondsSinceMaintenance = PersistentComponent->NumberState.Find(TEXT("seconds_since_maintenance"));
	const float* SavedAreaCleared = PersistentComponent->NumberState.Find(TEXT("fire_area_cleared"));
	const float* SavedContained = PersistentComponent->NumberState.Find(TEXT("fire_contained"));
	const float* SavedGrassIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("grass_ignition_risk_score"));
	const float* SavedForestIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("forest_ignition_risk_score"));
	const float* SavedGrassIgnited = PersistentComponent->NumberState.Find(TEXT("grass_or_brush_ignited"));
	const float* SavedForestIgnited = PersistentComponent->NumberState.Find(TEXT("forest_fuel_ignited"));
	const float* SavedStructureIgnitionRisk = PersistentComponent->NumberState.Find(TEXT("structure_ignition_risk_score"));
	const float* SavedStructureIgnited = PersistentComponent->NumberState.Find(TEXT("structure_ignited"));
	const float* SavedGrassFireIntensity = PersistentComponent->NumberState.Find(TEXT("grass_fire_intensity"));
	const float* SavedForestFireIntensity = PersistentComponent->NumberState.Find(TEXT("forest_fire_intensity"));
	const float* SavedStructureFireIntensity = PersistentComponent->NumberState.Find(TEXT("structure_fire_intensity"));
	const float* SavedActiveFireSpreadRadius = PersistentComponent->NumberState.Find(TEXT("active_fire_spread_radius"));
	const float* SavedFireSuppressionPressure = PersistentComponent->NumberState.Find(TEXT("fire_suppression_pressure"));

	if (SavedFuelSeconds)
	{
		FuelSeconds = FMath::Max(0.0f, *SavedFuelSeconds);
	}

	if (SavedCookingEnabled)
	{
		bCookingPlaceholderEnabled = *SavedCookingEnabled > 0.5f;
	}

	if (SavedCookingRequired)
	{
		CookingSecondsRequired = FMath::Max(0.0f, *SavedCookingRequired);
	}

	if (SavedCookingProgress)
	{
		CookingProgressSeconds = FMath::Clamp(*SavedCookingProgress, 0.0f, CookingSecondsRequired);
	}

	if (SavedRiskScore)
	{
		FireRiskScore = FMath::Clamp(*SavedRiskScore, 0.0f, 100.0f);
	}

	if (SavedLitDuration)
	{
		LitDurationSeconds = FMath::Max(0.0f, *SavedLitDuration);
	}

	if (SavedSecondsSinceMaintenance)
	{
		SecondsSinceMaintenance = FMath::Max(0.0f, *SavedSecondsSinceMaintenance);
	}

	if (SavedAreaCleared)
	{
		bFireAreaCleared = *SavedAreaCleared > 0.5f;
	}

	if (SavedContained)
	{
		bFireContained = *SavedContained > 0.5f;
	}

	if (SavedGrassIgnitionRisk)
	{
		GrassIgnitionRiskScore = FMath::Clamp(*SavedGrassIgnitionRisk, 0.0f, 100.0f);
	}

	if (SavedForestIgnitionRisk)
	{
		ForestIgnitionRiskScore = FMath::Clamp(*SavedForestIgnitionRisk, 0.0f, 100.0f);
	}

	if (SavedGrassIgnited)
	{
		bGrassOrBrushIgnited = *SavedGrassIgnited > 0.5f;
	}

	if (SavedForestIgnited)
	{
		bForestFuelIgnited = *SavedForestIgnited > 0.5f;
	}

	if (SavedStructureIgnitionRisk)
	{
		StructureIgnitionRiskScore = FMath::Clamp(*SavedStructureIgnitionRisk, 0.0f, 100.0f);
	}

	if (SavedStructureIgnited)
	{
		bStructureIgnited = *SavedStructureIgnited > 0.5f;
	}

	if (SavedGrassFireIntensity)
	{
		GrassFireIntensity = FMath::Clamp(*SavedGrassFireIntensity, 0.0f, 100.0f);
	}

	if (SavedForestFireIntensity)
	{
		ForestFireIntensity = FMath::Clamp(*SavedForestFireIntensity, 0.0f, 100.0f);
	}

	if (SavedStructureFireIntensity)
	{
		StructureFireIntensity = FMath::Clamp(*SavedStructureFireIntensity, 0.0f, 100.0f);
	}

	if (SavedActiveFireSpreadRadius)
	{
		ActiveFireSpreadRadius = FMath::Clamp(*SavedActiveFireSpreadRadius, 0.0f, MaxFireSpreadRadius);
	}

	if (SavedFireSuppressionPressure)
	{
		FireSuppressionPressure = FMath::Clamp(*SavedFireSuppressionPressure, 0.0f, 1.0f);
	}

	SetLit(SavedLit && *SavedLit > 0.5f && FuelSeconds > 0.0f);
}

void AAgrarianCampfire::AddFuel(float Seconds)
{
	if (HasAuthority())
	{
		FuelSeconds += FMath::Max(0.0f, Seconds);
		if (FuelSeconds > 0.0f)
		{
			SetLit(true);
		}
		else
		{
			UpdateVisualState();
		}
	}
}

void AAgrarianCampfire::Extinguish()
{
	if (HasAuthority())
	{
		FuelSeconds = 0.0f;
		FireRiskScore = 0.0f;
		GrassIgnitionRiskScore = 0.0f;
		ForestIgnitionRiskScore = 0.0f;
		StructureIgnitionRiskScore = 0.0f;
		GrassFireIntensity = 0.0f;
		ForestFireIntensity = 0.0f;
		StructureFireIntensity = 0.0f;
		ActiveFireSpreadRadius = 0.0f;
		LitDurationSeconds = 0.0f;
		SecondsSinceMaintenance = 0.0f;
		SetLit(false);
	}
}

void AAgrarianCampfire::MaintainFire(bool bClearArea, bool bContainFire)
{
	if (!HasAuthority())
	{
		return;
	}

	SecondsSinceMaintenance = 0.0f;
	if (bClearArea)
	{
		bFireAreaCleared = true;
	}
	if (bContainFire)
	{
		bFireContained = true;
	}

	float RiskReduction = WatchedMaintenanceRiskReduction;
	if (bClearArea)
	{
		RiskReduction += ClearedAreaRiskReduction;
	}
	if (bContainFire)
	{
		RiskReduction += ContainedFireRiskReduction;
	}

	ReduceFireRisks(RiskReduction);
}

void AAgrarianCampfire::WatchFire()
{
	MaintainFire(false, false);
}

void AAgrarianCampfire::ClearAreaAroundFire()
{
	MaintainFire(true, false);
}

void AAgrarianCampfire::ContainFire()
{
	MaintainFire(false, true);
}

void AAgrarianCampfire::ApplyFireSuppression(float SuppressionAmount, FName SuppressionSource)
{
	if (!HasAuthority())
	{
		return;
	}

	const float SafeSuppressionAmount = FMath::Max(0.0f, SuppressionAmount);
	if (SafeSuppressionAmount <= 0.0f)
	{
		return;
	}

	FireSuppressionPressure = FMath::Clamp(FireSuppressionPressure + (SafeSuppressionAmount / 100.0f), 0.0f, 1.0f);
	ReduceFireRisks(SafeSuppressionAmount);
	ReduceActiveFireIntensity(SafeSuppressionAmount);

	if (SuppressionSource == TEXT("rain") || SuppressionSource == TEXT("water"))
	{
		FuelSeconds = FMath::Max(0.0f, FuelSeconds - SafeSuppressionAmount);
	}

	const float TotalActiveFire = GrassFireIntensity + ForestFireIntensity + StructureFireIntensity;
	if (FuelSeconds <= 0.0f && TotalActiveFire <= 1.0f)
	{
		Extinguish();
	}
}

void AAgrarianCampfire::ApplyWaterSuppression()
{
	ApplyFireSuppression(WaterSuppressionStrength, TEXT("water"));
}

void AAgrarianCampfire::ApplyDirtSandSuppression()
{
	ApplyFireSuppression(DirtSandSuppressionStrength, TEXT("dirt_sand"));
}

void AAgrarianCampfire::ApplyFirebreakSuppression()
{
	ApplyFireSuppression(FirebreakSuppressionStrength, TEXT("firebreak"));
}

void AAgrarianCampfire::ApplyToolSuppression()
{
	ApplyFireSuppression(ToolSuppressionStrength, TEXT("tool"));
}

float AAgrarianCampfire::GetFireRiskRatio() const
{
	return FMath::Clamp(FireRiskScore / 100.0f, 0.0f, 1.0f);
}

bool AAgrarianCampfire::CanCook() const
{
	return bLit && bCookingPlaceholderEnabled && CookingSecondsRequired > 0.0f;
}

float AAgrarianCampfire::GetCookingProgressRatio() const
{
	if (CookingSecondsRequired <= 0.0f)
	{
		return 0.0f;
	}

	return FMath::Clamp(CookingProgressSeconds / CookingSecondsRequired, 0.0f, 1.0f);
}

float AAgrarianCampfire::GetWeatherFuelDrainMultiplier() const
{
	switch (GetCurrentWeather())
	{
	case EAgrarianWeatherType::Rain:
		return FMath::Max(1.0f, RainFuelDrainMultiplier);
	case EAgrarianWeatherType::Storm:
		return FMath::Max(1.0f, StormFuelDrainMultiplier);
	default:
		return 1.0f;
	}
}

bool AAgrarianCampfire::IsWetWeatherActive() const
{
	const EAgrarianWeatherType CurrentWeather = GetCurrentWeather();
	return CurrentWeather == EAgrarianWeatherType::Rain || CurrentWeather == EAgrarianWeatherType::Storm;
}

void AAgrarianCampfire::OnRep_FireState()
{
	UpdateVisualState();
}

void AAgrarianCampfire::MulticastPlayFireEventSound_Implementation(bool bIgnited)
{
	if (GetNetMode() == NM_DedicatedServer || !FireEventAudioComponent)
	{
		return;
	}

	USoundBase* EventSound = bIgnited ? IgniteSound : ExtinguishSound;
	if (!EventSound)
	{
		return;
	}

	FireEventAudioComponent->SetSound(EventSound);
	FireEventAudioComponent->Play();
}

EAgrarianWeatherType AAgrarianCampfire::GetCurrentWeather() const
{
	if (const UWorld* World = GetWorld())
	{
		if (const AAgrarianGameState* GameState = World->GetGameState<AAgrarianGameState>())
		{
			return GameState->Weather;
		}
	}

	return EAgrarianWeatherType::Clear;
}

void AAgrarianCampfire::SetLit(bool bNewLit)
{
	const bool bChanged = bLit != bNewLit;
	if (bLit != bNewLit)
	{
		bLit = bNewLit;
		if (bLit)
		{
			SecondsSinceMaintenance = 0.0f;
		}
	}

	if (HasAuthority() && bChanged)
	{
		MulticastPlayFireEventSound(bLit);
	}

	UpdateVisualState();
}

void AAgrarianCampfire::UpdateVisualState()
{
	if (FireLight)
	{
		FireLight->SetIntensity(bLit ? 4200.0f : 0.0f);
	}

	if (SmokeEffect)
	{
		SmokeEffect->SetVisibility(bLit);
		if (bLit)
		{
			SmokeEffect->ActivateSystem();
		}
		else
		{
			SmokeEffect->DeactivateSystem();
		}
	}

	if (FireLoopAudioComponent)
	{
		if (bLit && FireLoopSound)
		{
			if (FireLoopAudioComponent->Sound != FireLoopSound)
			{
				FireLoopAudioComponent->SetSound(FireLoopSound);
			}
			if (!FireLoopAudioComponent->IsPlaying())
			{
				FireLoopAudioComponent->Play();
			}
		}
		else if (FireLoopAudioComponent->IsPlaying())
		{
			FireLoopAudioComponent->Stop();
		}
	}
}

void AAgrarianCampfire::WarmNearbyCharacters(float DeltaSeconds)
{
	TArray<AActor*> Characters;
	UGameplayStatics::GetAllActorsOfClass(this, AAgrarianGameCharacter::StaticClass(), Characters);

	for (AActor* Actor : Characters)
	{
		AAgrarianGameCharacter* Character = Cast<AAgrarianGameCharacter>(Actor);
		if (!Character || FVector::DistSquared(Character->GetActorLocation(), GetActorLocation()) > FMath::Square(WarmthRadius))
		{
			continue;
		}

		if (UAgrarianSurvivalComponent* SurvivalComponent = Character->GetSurvivalComponent())
		{
			SurvivalComponent->AddWarmth(WarmthPerSecond * DeltaSeconds);
		}
	}
}

void AAgrarianCampfire::UpdateFireRisk(float DeltaSeconds)
{
	if (!HasAuthority() || !bLit)
	{
		return;
	}

	LitDurationSeconds += DeltaSeconds;
	SecondsSinceMaintenance += DeltaSeconds;
	if (IsWetWeatherActive())
	{
		const float RainSuppression = GetCurrentWeather() == EAgrarianWeatherType::Storm ? 0.2f : 0.1f;
		FireSuppressionPressure = FMath::Clamp(FireSuppressionPressure + (RainSuppression * DeltaSeconds), 0.0f, 1.0f);
	}

	const float RiskGrowth = GetFireRiskGrowthPerSecond();
	FireRiskScore = FMath::Clamp(FireRiskScore + (RiskGrowth * DeltaSeconds), 0.0f, 100.0f);
}

float AAgrarianCampfire::GetFireRiskGrowthPerSecond() const
{
	float RiskGrowth = 0.0f;

	if (SecondsSinceMaintenance >= UnmaintainedRiskDelaySeconds)
	{
		RiskGrowth += PoorMaintenanceRiskPerSecond;
	}

	if (FuelSeconds >= HighFuelRiskThresholdSeconds)
	{
		RiskGrowth += HighFuelRiskPerSecond;
	}

	if (bFireAreaCleared)
	{
		RiskGrowth *= FMath::Clamp(ClearedAreaRiskMultiplier, 0.0f, 1.0f);
	}

	if (bFireContained)
	{
		RiskGrowth *= FMath::Clamp(ContainedFireRiskMultiplier, 0.0f, 1.0f);
	}

	if (IsWetWeatherActive())
	{
		RiskGrowth *= 0.25f;
	}

	return FMath::Max(0.0f, RiskGrowth);
}

void AAgrarianCampfire::UpdateVegetationIgnitionRisk(float DeltaSeconds)
{
	if (!HasAuthority() || !bLit || bFireAreaCleared)
	{
		return;
	}

	float GrassFuelScore = 0.0f;
	float ForestFuelScore = 0.0f;
	const float TotalFuelScore = GetVegetationFuelScoreNearFire(GrassFuelScore, ForestFuelScore);
	if (TotalFuelScore < VegetationIgnitionFuelScoreThreshold)
	{
		return;
	}

	const float BurnDurationMultiplier = FMath::GetMappedRangeValueClamped(
		FVector2D(30.0f, 300.0f),
		FVector2D(0.35f, 1.4f),
		LitDurationSeconds);
	const float RiskMultiplier = GetVegetationIgnitionWeatherMultiplier() * BurnDurationMultiplier * FMath::Max(0.0f, GetFireRiskRatio());
	const float BaseRisk = VegetationIgnitionRiskPerSecond * DeltaSeconds * RiskMultiplier;

	if (GrassFuelScore > 0.0f && !bGrassOrBrushIgnited)
	{
		GrassIgnitionRiskScore = FMath::Clamp(GrassIgnitionRiskScore + (BaseRisk * GrassFuelScore), 0.0f, 100.0f);
		bGrassOrBrushIgnited = GrassIgnitionRiskScore >= 100.0f;
	}

	if (ForestFuelScore > 0.0f && !bForestFuelIgnited)
	{
		ForestIgnitionRiskScore = FMath::Clamp(ForestIgnitionRiskScore + (BaseRisk * ForestFuelScore * 0.6f), 0.0f, 100.0f);
		bForestFuelIgnited = ForestIgnitionRiskScore >= 100.0f;
	}
}

float AAgrarianCampfire::GetVegetationFuelScoreNearFire(float& OutGrassFuelScore, float& OutForestFuelScore) const
{
	OutGrassFuelScore = 0.0f;
	OutForestFuelScore = 0.0f;

	TArray<AActor*> FoliageActors;
	UGameplayStatics::GetAllActorsOfClass(this, AAgrarianFoliagePatch::StaticClass(), FoliageActors);
	for (AActor* Actor : FoliageActors)
	{
		const AAgrarianFoliagePatch* FoliagePatch = Cast<AAgrarianFoliagePatch>(Actor);
		if (!FoliagePatch)
		{
			continue;
		}

		int32 GrassCount = 0;
		int32 ShrubCount = 0;
		int32 TreeCount = 0;
		FoliagePatch->GetFuelCountsNearLocation(GetActorLocation(), VegetationIgnitionCheckRadius, GrassCount, ShrubCount, TreeCount);
		OutGrassFuelScore += static_cast<float>(GrassCount) + (static_cast<float>(ShrubCount) * 1.5f);
		OutForestFuelScore += static_cast<float>(TreeCount) * 3.0f;
	}

	return OutGrassFuelScore + OutForestFuelScore;
}

float AAgrarianCampfire::GetVegetationIgnitionWeatherMultiplier() const
{
	float Multiplier = 1.0f;
	if (const UWorld* World = GetWorld())
	{
		if (const AAgrarianGameState* GameState = World->GetGameState<AAgrarianGameState>())
		{
			if (GameState->ActiveWeatherInputs.bHasProviderData)
			{
				Multiplier *= FMath::GetMappedRangeValueClamped(
					FVector2D(0.0f, 55.0f),
					FVector2D(0.85f, 1.65f),
					GameState->ActiveWeatherInputs.WindSpeedKmh);
			}

			switch (GameState->Weather)
			{
			case EAgrarianWeatherType::Rain:
				Multiplier *= 0.2f;
				break;
			case EAgrarianWeatherType::Storm:
				Multiplier *= 0.1f;
				break;
			case EAgrarianWeatherType::ColdWind:
				Multiplier *= 1.25f;
				break;
			default:
				break;
			}
		}
	}

	return FMath::Max(0.0f, Multiplier);
}

void AAgrarianCampfire::UpdateStructureIgnitionRisk(float DeltaSeconds)
{
	if (!HasAuthority() || !bLit || bFireContained || bStructureIgnited)
	{
		return;
	}

	const float StructureFuelScore = GetStructureFuelScoreNearFire();
	if (StructureFuelScore < StructureIgnitionFuelScoreThreshold)
	{
		return;
	}

	const float BurnDurationMultiplier = FMath::GetMappedRangeValueClamped(
		FVector2D(15.0f, 240.0f),
		FVector2D(0.45f, 1.5f),
		LitDurationSeconds);
	const float RiskMultiplier = BurnDurationMultiplier * GetVegetationIgnitionWeatherMultiplier() * FMath::Max(0.0f, GetFireRiskRatio());
	StructureIgnitionRiskScore = FMath::Clamp(
		StructureIgnitionRiskScore + (StructureIgnitionRiskPerSecond * StructureFuelScore * RiskMultiplier * DeltaSeconds),
		0.0f,
		100.0f);
	bStructureIgnited = StructureIgnitionRiskScore >= 100.0f;
}

float AAgrarianCampfire::GetStructureFuelScoreNearFire() const
{
	float StructureFuelScore = 0.0f;

	TArray<AActor*> ShelterActors;
	UGameplayStatics::GetAllActorsOfClass(this, AAgrarianShelterActor::StaticClass(), ShelterActors);
	for (const AActor* Actor : ShelterActors)
	{
		if (Actor && Actor != this && FVector::DistSquared2D(Actor->GetActorLocation(), GetActorLocation()) <= FMath::Square(StructureIgnitionCheckRadius))
		{
			StructureFuelScore += 4.0f;
		}
	}

	TArray<AActor*> ResourceActors;
	UGameplayStatics::GetAllActorsOfClass(this, AAgrarianResourceNode::StaticClass(), ResourceActors);
	for (const AActor* Actor : ResourceActors)
	{
		const AAgrarianResourceNode* ResourceNode = Cast<AAgrarianResourceNode>(Actor);
		if (!ResourceNode || FVector::DistSquared2D(ResourceNode->GetActorLocation(), GetActorLocation()) > FMath::Square(StructureIgnitionCheckRadius))
		{
			continue;
		}

		const FName YieldId = ResourceNode->YieldItem.ItemId;
		if (YieldId == TEXT("wood") || YieldId == TEXT("fiber"))
		{
			StructureFuelScore += 1.5f;
		}
	}

	return StructureFuelScore;
}

void AAgrarianCampfire::UpdateServerAuthoritativeFireSpread(float DeltaSeconds)
{
	if (!HasAuthority())
	{
		return;
	}

	const bool bAnyActiveFire = bGrassOrBrushIgnited || bForestFuelIgnited || bStructureIgnited;
	if (!bAnyActiveFire)
	{
		return;
	}

	const float WeatherMultiplier = GetFireSpreadWeatherMultiplier();
	const float SuppressionMultiplier = FMath::Clamp(1.0f - FireSuppressionPressure, 0.0f, 1.0f);
	const float FuelScore = FMath::Max(1.0f, GetActiveBurningFuelScore());
	const float IntensityDelta = FireSpreadIntensityPerSecond * WeatherMultiplier * SuppressionMultiplier * FuelScore * DeltaSeconds;

	if (bGrassOrBrushIgnited)
	{
		GrassFireIntensity = FMath::Clamp(GrassFireIntensity + IntensityDelta, 0.0f, 100.0f);
	}

	if (bForestFuelIgnited)
	{
		ForestFireIntensity = FMath::Clamp(ForestFireIntensity + (IntensityDelta * 0.75f), 0.0f, 100.0f);
	}

	if (bStructureIgnited)
	{
		StructureFireIntensity = FMath::Clamp(StructureFireIntensity + (IntensityDelta * 0.9f), 0.0f, 100.0f);
	}

	const float TotalIntensity = GrassFireIntensity + ForestFireIntensity + StructureFireIntensity;
	ActiveFireSpreadRadius = FMath::Clamp(
		BaseFireSpreadRadius + (TotalIntensity * 12.0f * WeatherMultiplier),
		0.0f,
		MaxFireSpreadRadius);
}

float AAgrarianCampfire::GetFireSpreadWeatherMultiplier() const
{
	float Multiplier = GetVegetationIgnitionWeatherMultiplier();
	if (IsWetWeatherActive())
	{
		Multiplier *= 0.5f;
	}

	return FMath::Max(0.0f, Multiplier);
}

float AAgrarianCampfire::GetActiveBurningFuelScore() const
{
	float GrassFuelScore = 0.0f;
	float ForestFuelScore = 0.0f;
	const float VegetationFuelScore = GetVegetationFuelScoreNearFire(GrassFuelScore, ForestFuelScore);
	const float StructureFuelScore = GetStructureFuelScoreNearFire();

	return FMath::Max(0.0f, VegetationFuelScore + StructureFuelScore);
}

void AAgrarianCampfire::ReduceFireRisks(float Amount)
{
	const float SafeAmount = FMath::Max(0.0f, Amount);
	FireRiskScore = FMath::Clamp(FireRiskScore - SafeAmount, 0.0f, 100.0f);
	GrassIgnitionRiskScore = FMath::Clamp(GrassIgnitionRiskScore - (SafeAmount * 0.75f), 0.0f, 100.0f);
	ForestIgnitionRiskScore = FMath::Clamp(ForestIgnitionRiskScore - (SafeAmount * 0.5f), 0.0f, 100.0f);
	StructureIgnitionRiskScore = FMath::Clamp(StructureIgnitionRiskScore - (SafeAmount * 0.75f), 0.0f, 100.0f);
}

void AAgrarianCampfire::ReduceActiveFireIntensity(float Amount)
{
	const float SafeAmount = FMath::Max(0.0f, Amount);
	GrassFireIntensity = FMath::Clamp(GrassFireIntensity - SafeAmount, 0.0f, 100.0f);
	ForestFireIntensity = FMath::Clamp(ForestFireIntensity - (SafeAmount * 0.75f), 0.0f, 100.0f);
	StructureFireIntensity = FMath::Clamp(StructureFireIntensity - (SafeAmount * 0.85f), 0.0f, 100.0f);
	const float TotalIntensity = GrassFireIntensity + ForestFireIntensity + StructureFireIntensity;
	ActiveFireSpreadRadius = TotalIntensity > 0.0f
		? FMath::Clamp(BaseFireSpreadRadius + (TotalIntensity * 12.0f), 0.0f, MaxFireSpreadRadius)
		: 0.0f;
}
