#pragma once

#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "WingTypes.h"
#include "WingProperty.h"
#include "WingFactories.h"
#include "Create_ClassArg.generated.h"

struct FWingClassArgConfig
{
	const TCHAR *Factory;
	const TCHAR *Base;
	bool ValidPointer = true;        // eg, not nullptr
	bool ConcreteClass = false;      // eg, not abstract class
	bool LatestVersion = true;       // eg, not deprecated and no newer version.
	bool ShowInDropDown = true;      // eg, not hide drop down
	bool NativeClass = false;        // eg, not generated class
	bool TrueSubclass = false;       // eg, not the base class itself
	bool CanCreateAsset = false;     // calls 'CanCreateAssetOfClass'
	bool BlueprintBase = false;      // 
	bool Blueprintable = false;      //
	bool CanCreateBlueprint = false; //
};

UCLASS()
class UWingClassArg : public UObject
{
	GENERATED_BODY()
public:
	FWingClassArgConfig Config;

	UPROPERTY()
	TSubclassOf<UFactory> FactoryClass;

	UPROPERTY()
	TObjectPtr<UClass> BaseClass;
};

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

	UPROPERTY(editAnywhere)
	FString Path;

	UPROPERTY(EditAnywhere)
	FString Class;

public:
	void RegisterConfigs(TArrayView<const FWingClassArgConfig> Configs)
	{
		// Build a map of factory name -> UClass for quick lookup.
		TMap<FString, UClass*> FactoryMap;
		TArray<UClass*> FactoryClasses;
		GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
		for (UClass* C : FactoryClasses) FactoryMap.Add(C->GetName(), C);

		for (const FWingClassArgConfig& Cfg : Configs)
		{
			// Look up the factory class.
			UClass** FoundFactory = FactoryMap.Find(Cfg.Factory);
			if (!FoundFactory)
			{
				UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: factory '%s' not found, skipping"), Cfg.Factory);
				continue;
			}

			// Look up the base class.
			UClass* BaseClass = FindObject<UClass>(nullptr, Cfg.Base);
			if (!BaseClass)
			{
				UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: base class '%s' not found, skipping"), Cfg.Base);
				continue;
			}

			// Create the config object.
			UWingClassArg* Arg = NewObject<UWingClassArg>(this);
			Arg->Config = Cfg;
			Arg->FactoryClass = *FoundFactory;
			Arg->BaseClass = BaseClass;

			// Register the command.
			FString FactoryName = WingFactories::DeriveFactoryName(*FoundFactory);
			FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
			FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
			UWingServer::AddHandler(this, CommandName, Arg, EWingHandlerKind::Create, *FoundFactory, Doc);
		}
	}

	virtual void Register() override
	{
		RegisterConfigs({

			// --- Standard candidates ---

			{ .Factory = TEXT("AudioSynesthesiaNRTFactory"),
			  .Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRT"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("AudioSynesthesiaNRTSettingsFactory"),
			  .Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRTSettings"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("AudioSynesthesiaSettingsFactory"),
			  .Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaSettings"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("CurveFactory"),
			  .Base = TEXT("/Script/Engine.CurveBase"),
			  .ConcreteClass = true, .CanCreateAsset = true },

			{ .Factory = TEXT("DataAssetFactory"),
			  .Base = TEXT("/Script/Engine.DataAsset"),
			  .ConcreteClass = true, .CanCreateAsset = true },

			{ .Factory = TEXT("InputAction_Factory"),
			  .Base = TEXT("/Script/EnhancedInput.InputAction"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("InputMappingContext_Factory"),
			  .Base = TEXT("/Script/EnhancedInput.InputMappingContext"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("PhysicalMaterialFactoryNew"),
			  .Base = TEXT("/Script/PhysicsCore.PhysicalMaterial"),
			  .ConcreteClass = true, .CanCreateAsset = true },

			{ .Factory = TEXT("PhysicalMaterialMaskFactory"),
			  .Base = TEXT("/Script/Engine.PhysicalMaterialMask"),
			  .ConcreteClass = true, .CanCreateAsset = true },

			{ .Factory = TEXT("SoundSourceEffectFactory"),
			  .Base = TEXT("/Script/Engine.SoundEffectSourcePreset"),
			  .ConcreteClass = true },

			{ .Factory = TEXT("SoundSubmixEffectFactory"),
			  .Base = TEXT("/Script/Engine.SoundEffectSubmixPreset"),
			  .ConcreteClass = true },

			// --- Slightly unusual ---

			{ .Factory = TEXT("CameraAssetFactory"),
			  .Base = TEXT("/Script/GameplayCameras.CameraDirector"),
			  .ValidPointer = false, .ConcreteClass = true },

			{ .Factory = TEXT("SlateWidgetStyleAssetFactory"),
			  .Base = TEXT("/Script/SlateCore.SlateWidgetStyleContainerBase"),
			  .ConcreteClass = true, .TrueSubclass = true },

			{ .Factory = TEXT("TemplateSequenceFactoryNew"),
			  .Base = TEXT("/Script/Engine.Actor") },

			// --- Blueprint factories ---

			{ .Factory = TEXT("BlueprintFactory"),
			  .Base = TEXT("/Script/CoreUObject.Object"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("BlueprintMacroFactory"),
			  .Base = TEXT("/Script/CoreUObject.Object"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			// BlueprintFunctionLibraryFactory is handled by Create_NoArgs.

			// BlueprintInterfaceFactory is handled by Create_NoArgs.

			{ .Factory = TEXT("WidgetBlueprintFactory"),
			  .Base = TEXT("/Script/CoreUObject.Object"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("EditorUtilityBlueprintFactory"),
			  .Base = TEXT("/Script/CoreUObject.Object"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("EditorUtilityWidgetBlueprintFactory"),
			  .Base = TEXT("/Script/CoreUObject.Object"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("InterchangeBlueprintPipelineBaseFactory"),
			  .Base = TEXT("/Script/InterchangeCore.InterchangePipelineBase"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("InterchangeEditorBlueprintPipelineBaseFactory"),
			  .Base = TEXT("/Script/InterchangeEditorPipelines.InterchangeEditorPipelineBase"),
			  .Blueprintable = true, .CanCreateBlueprint = true },

			{ .Factory = TEXT("ControlRigBlueprintFactory"),
			  .Base = TEXT("/Script/ControlRig.ControlRig"),
			  .NativeClass = true, .BlueprintBase = true, .Blueprintable = true, .CanCreateBlueprint = true },
		});
	}

	virtual void Handle() override
	{
		// Get the configuration.
		UWingClassArg *ConfObj = Cast<UWingClassArg>(Configuration->Config.Get());
		if (ConfObj == nullptr)
		{
			WingOut::Stdout.Print(TEXT("ERROR: Create_ClassArg config object not set properly (bug)\n"));
			return;
		}
		FWingClassArgConfig &Config = ConfObj->Config;

		// Verify that the specified path is good.
		if (!WingFactories::CheckNewAssetPath(Path, WingOut::Stdout)) return;
	
		// Parse the class string.
		UClass *ClassObj = nullptr;
		if (!Class.IsEmpty())
		{
			UWingTypes::Requirements Req;
			Req.Blueprintable = Config.Blueprintable;
			Req.AllowContainer = false;
			Req.AllowNone = !Config.ValidPointer;
			Req.IsChildOf = ConfObj->BaseClass;
			FEdGraphPinType PinType;
			if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
			ClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
		}

		// Check constraints.
		if (Config.ValidPointer && (ClassObj == nullptr))
		{
			WingOut::Stdout.Print(TEXT("ERROR: factory requires a non-empty class to be specified."));
			return;
		}
		if (ClassObj != nullptr)
		{
			if (Config.ConcreteClass && ClassObj->HasAnyClassFlags(CLASS_Abstract))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is abstract.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.LatestVersion && ClassObj->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is deprecated or has a newer version.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.ShowInDropDown && ClassObj->HasAnyClassFlags(CLASS_HideDropDown))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is hidden.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.NativeClass && !ClassObj->HasAnyClassFlags(CLASS_Native))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not a native class.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.TrueSubclass && (ClassObj == ConfObj->BaseClass))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is the base class itself, a subclass is required.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.CanCreateAsset && !ClassObj->CanCreateAssetOfClass())
			{
				WingOut::Stdout.Printf(TEXT("ERROR: cannot create an asset of class '%s'.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.BlueprintBase && !ClassObj->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_IsBlueprintBase))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not marked as a blueprint base.\n"), *ClassObj->GetName());
				return;
			}
			if (Config.CanCreateBlueprint && !FKismetEditorUtilities::CanCreateBlueprintOfClass(ClassObj))
			{
				WingOut::Stdout.Printf(TEXT("ERROR: cannot create a blueprint of class '%s'.\n"), *ClassObj->GetName());
				return;
			}
		}

		// Make the factory.
		UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), ConfObj->FactoryClass);
		if (Factory == nullptr)
		{
			WingOut::Stdout.Printf(TEXT("ERROR: factory creation failed (bug)\n"));
			return;
		}

		// Set the 'Class' property.
		TArray<FWingProperty> Props = FWingProperty::GetVisible(Factory, true);
		FWingProperty::Remove(Props, TEXT("BlueprintType"));
		if (Props.Num() != 1)
		{
			WingOut::Stdout.Printf(TEXT("ERROR: Factory unexpectedly has multiple config properties (bug)\n"));
			return;
		}
		if (!CastField<FClassProperty>(Props[0].Prop))
		{
			WingOut::Stdout.Printf(TEXT("ERROR: Factory was expected to have a 'class' property (bug)\n"));
			return;
		}
		if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return;

		// Create the asset using the factory.
		UObject *Object = WingFactories::CreateAsset(
			Path, Factory, nullptr, WingOut::Stdout);
		if (Object == nullptr) return;
		WingOut::Stdout.Printf(TEXT("Created.\n"));
	}
};
