#pragma once

#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "Misc/Paths.h"
#include "Misc/PackageName.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "HAL/FileManager.h"
#include "UObject/LinkerLoad.h"
#include "UObject/Package.h"
#include "Asset_Delete.generated.h"


// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------

UCLASS()
class UWing_Asset_Delete : public UWingHandler
{
	GENERATED_BODY()

public:
	UPROPERTY(EditAnywhere, meta=(Description="Asset to delete"))
	FString Asset;

	UPROPERTY(EditAnywhere, meta=(Description="If true, skip reference check and force delete"))
	bool Force = false;

	virtual void Register() override
	{
		UWingServer::AddHandler(this,
			TEXT("Delete a .uasset after verifying no references. "
			"Use force=true to skip the reference check."));
	}
	virtual void Handle() override
	{
		// Verify the asset file exists on disk
		FString PackageFilename = FPackageName::LongPackageNameToFilename(
			Asset, FPackageName::GetAssetPackageExtension());
		PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);

		if (!IFileManager::Get().FileExists(*PackageFilename))
		{
			WingOut::Stdout.Printf(TEXT("ERROR: Asset file not found on disk: %s\n"), *PackageFilename);
			return;
		}

		// Check references
		IAssetRegistry& Registry = *IAssetRegistry::Get();
		TArray<FName> Referencers;
		Registry.GetReferencers(FName(*Asset), Referencers);

		// Filter out self-references
		Referencers.RemoveAll([this](const FName& Ref) {
			return Ref.ToString() == Asset;
		});

		if (Referencers.Num() > 0 && !Force)
		{
			WingOut::Stdout.Printf(TEXT("ERROR: Asset is still referenced by %d package(s):\n"), Referencers.Num());
			for (const FName& Ref : Referencers)
			{
				FString RefStr = Ref.ToString();
				UPackage* RefPackage = FindPackage(nullptr, *RefStr);
				WingOut::Stdout.Printf(TEXT("  %s%s\n"), *RefStr,
					RefPackage ? TEXT(" (loaded)") : TEXT(" (on-disk only)"));
			}
			WingOut::Stdout.Print(TEXT("Use force=true to skip the reference check.\n"));
			return;
		}

		// Force delete: unload the package from memory first
		if (Force && Referencers.Num() > 0)
		{
			WingOut::Stdout.Printf(TEXT("WARNING: Force-deleting despite %d referencer(s).\n"), Referencers.Num());
		}

		// Mark the package, and all the objects in it, as NOT
		// GC Roots.  Also, make them undiscoverable. 
		UPackage* Package = FindPackage(nullptr, *Asset);
		if (Package)
		{
			// Collect all objects in this package
			TArray<UObject*> ObjectsInPackage;
			GetObjectsWithPackage(Package, ObjectsInPackage);

			// Clear flags and remove from root to allow GC
			for (UObject* Obj : ObjectsInPackage)
			{
				if (Obj)
				{
					Obj->ClearFlags(RF_Standalone | RF_Public);
					Obj->RemoveFromRoot();
				}
			}
			Package->ClearFlags(RF_Standalone | RF_Public);
			Package->RemoveFromRoot();
		}

		// The loader that loaded the package might still
		// have a file lock on it.  Unlock the file.
		ResetLoaders(Package);

		// Delete the file on disk
		bool bDeleted = IFileManager::Get().Delete(*PackageFilename, false, true);

		if (!bDeleted)
		{
			WingOut::Stdout.Printf(TEXT("ERROR: Failed to delete file from disk: %s\n"), *PackageFilename);
			return;
		}

		// Trigger an asset registry rescan so it notices the deletion
		FString PackageDir;
		int32 LastSlash;
		if (Asset.FindLastChar(TEXT('/'), LastSlash))
		{
			PackageDir = Asset.Left(LastSlash);
			Registry.ScanPathsSynchronous({PackageDir}, true);
		}

		WingOut::Stdout.Printf(TEXT("Deleted %s\n"), *Asset);
	}
};
