// Copyright (c) 2026 HD Media. MIT licensed - see LICENSE.
//
// inspect_material_instance - read a UMaterialInstanceConstant's parent and
// currently-overridden parameter values. Complements inspect_material (which
// lists declared params on the parent material).
//
// Only OVERRIDDEN parameters appear in the output maps. Parameters inherited
// unchanged from the parent are not listed. Use inspect_material on the
// parent to see the full set of declared parameter names.
//
// Error format: "inspect_material_instance: <error_code>: <human-readable detail>".
// Stable error codes: missing_required_field, asset_not_found,
// not_a_material_instance.

#include "MCP/MCPHandler.h"

#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
#include "EditorAssetLibrary.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialInstance.h"
#include "Materials/MaterialInterface.h"
#include "UCMCPCompat.h"
#if UCMCP_ENGINE_AT_LEAST(5, 7)
// 5.7+ verified-correct path. Materials/MaterialParameters.h is a 5.7+
// header (ABSENT on 5.6: verified on Aximmetry UE 5.6 host build). Pulls
// in FMaterialParameterInfo / EMaterialParameterAssociation. The earlier
// 5.2 boundary was wrong — the standalone header lands at 5.7.
#include "Materials/MaterialParameters.h"
#else
// UE 5.0 - 5.6: Materials/MaterialParameters.h does not exist (it is a
// 5.7+ standalone header — verified absent on the Aximmetry UE 5.6 host
// tree at Engine/Source/Runtime/Engine/Public/Materials/). The only
// symbols this TU needs from it — FMaterialParameterInfo and the
// EMaterialParameterAssociation enumerators (LayerParameter /
// BlendParameter / GlobalParameter) — are defined in MaterialTypes.h
// (Engine/Source/Runtime/Engine/Public/MaterialTypes.h, struct
// FMaterialParameterInfo + enum EMaterialParameterAssociation).
// MaterialTypes.h is stable across 5.0..5.7 so it is the correct
// fallback for every pre-5.7 bucket. Verified on UE 5.6 Aximmetry
// 2026.2.0 host build (ue-5.6-compat branch).
#include "MaterialTypes.h"
#endif
#include "Engine/Texture.h"
#include "MCP/Handlers/AssetPathUtil.h"

// Compose the JSON object key for a material-instance parameter override.
// Global parameters use just `<Name>` for backward compatibility; non-global
// parameters (layer / blend) disambiguate as `<Name>:Layer:<Index>` and
// `<Name>:Blend:<Index>` so JSON keys do not collide when a layered material
// has multiple parameters sharing a name across different
// EMaterialParameterAssociation values. Without disambiguation, later
// parameters silently overwrite earlier ones in the JSON object.
//
// Mirrors the layout that FMaterialParameterInfo::AppendString serializes
// in MaterialParameters.h. EMaterialParameterAssociation is defined at
// MaterialParameters.h:24 (LayerParameter=0, BlendParameter=1,
// GlobalParameter=2).
static FString ComposeParameterKey(const FMaterialParameterInfo& Info)
{
    const FString NameStr = Info.Name.ToString();
    switch (Info.Association)
    {
    case EMaterialParameterAssociation::LayerParameter:
        return FString::Printf(TEXT("%s:Layer:%d"), *NameStr, Info.Index);
    case EMaterialParameterAssociation::BlendParameter:
        return FString::Printf(TEXT("%s:Blend:%d"), *NameStr, Info.Index);
    case EMaterialParameterAssociation::GlobalParameter:
    default:
        return NameStr;
    }
}

class FHandler_InspectMaterialInstance : public IUCMCPHandler
{
public:
    virtual FString GetMethodName() const override { return TEXT("inspect_material_instance"); }

    virtual TSharedPtr<FJsonObject> Handle(const TSharedPtr<FJsonObject>& Params, FString& OutError) override
    {
        // --- validate required params ---------------------------------------

        if (!Params.IsValid())
        {
            OutError = TEXT("inspect_material_instance: missing_required_field: 'path' is required");
            return nullptr;
        }

        FString InputPath;
        if (!Params->TryGetStringField(TEXT("path"), InputPath) || InputPath.IsEmpty())
        {
            OutError = TEXT("inspect_material_instance: missing_required_field: 'path' is required and must not be empty");
            return nullptr;
        }

        // --- load and cast asset --------------------------------------------

        const FString ObjectPath = UCMCPAssetPath::ToObjectPath(InputPath);
        const FString PackagePath = UCMCPAssetPath::ToPackagePath(InputPath);

        UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(ObjectPath);
        if (!LoadedAsset)
        {
            OutError = FString::Printf(
                TEXT("inspect_material_instance: asset_not_found: '%s' is not in the asset registry"),
                *InputPath);
            return nullptr;
        }
        UMaterialInstanceConstant* MIC = Cast<UMaterialInstanceConstant>(LoadedAsset);
        if (!MIC)
        {
            OutError = FString::Printf(
                TEXT("inspect_material_instance: not_a_material_instance: '%s' is a %s, not a UMaterialInstanceConstant"),
                *InputPath, *LoadedAsset->GetClass()->GetName());
            return nullptr;
        }

        // --- build the result JSON ------------------------------------------

        TSharedPtr<FJsonObject> Out = MakeShared<FJsonObject>();
        Out->SetBoolField(TEXT("ok"), true);
        Out->SetStringField(TEXT("name"), MIC->GetName());
        Out->SetStringField(TEXT("package_path"), PackagePath);

        // Parent: TObjectPtr<UMaterialInterface> at MaterialInstance.h:647.
        // Stringify via GetPathName when present; empty string when parented
        // to nothing (rare but possible for transient or partially-init MIs).
        if (MIC->Parent.Get())
        {
            Out->SetStringField(TEXT("parent_path"), MIC->Parent->GetPathName());
        }
        else
        {
            Out->SetStringField(TEXT("parent_path"), TEXT(""));
        }

        // Scalar overrides. ScalarParameterValues at MaterialInstance.h:750;
        // FScalarParameterValue at MaterialInstance.h:63 with ParameterInfo
        // (line 76) and ParameterValue (line 79). Keys go through
        // ComposeParameterKey to disambiguate Layer/Blend parameters that
        // share a Name with global parameters; see header comment above.
        TSharedRef<FJsonObject> ScalarJson = MakeShared<FJsonObject>();
        for (const FScalarParameterValue& SV : MIC->ScalarParameterValues)
        {
            ScalarJson->SetNumberField(ComposeParameterKey(SV.ParameterInfo),
                static_cast<double>(SV.ParameterValue));
        }
        Out->SetObjectField(TEXT("scalar_overrides"), ScalarJson);

        // Vector overrides. FLinearColor → {r, g, b, a}.
        TSharedRef<FJsonObject> VectorJson = MakeShared<FJsonObject>();
        for (const FVectorParameterValue& VV : MIC->VectorParameterValues)
        {
            TSharedRef<FJsonObject> ColorJson = MakeShared<FJsonObject>();
            ColorJson->SetNumberField(TEXT("r"), VV.ParameterValue.R);
            ColorJson->SetNumberField(TEXT("g"), VV.ParameterValue.G);
            ColorJson->SetNumberField(TEXT("b"), VV.ParameterValue.B);
            ColorJson->SetNumberField(TEXT("a"), VV.ParameterValue.A);
            VectorJson->SetObjectField(ComposeParameterKey(VV.ParameterInfo), ColorJson);
        }
        Out->SetObjectField(TEXT("vector_overrides"), VectorJson);

        // Texture overrides. ParameterValue is TObjectPtr<UTexture> at
        // MaterialInstance.h:240; emit asset path or empty string if null.
        TSharedRef<FJsonObject> TextureJson = MakeShared<FJsonObject>();
        for (const FTextureParameterValue& TV : MIC->TextureParameterValues)
        {
            const FString AssetPath = TV.ParameterValue
                ? TV.ParameterValue->GetPathName()
                : FString();
            TextureJson->SetStringField(ComposeParameterKey(TV.ParameterInfo), AssetPath);
        }
        Out->SetObjectField(TEXT("texture_overrides"), TextureJson);

        return Out;
    }
};

TSharedRef<IUCMCPHandler> Make_Handler_InspectMaterialInstance()
{
    return MakeShared<FHandler_InspectMaterialInstance>();
}
