#include "PropertyManager.h"
#include "LayerManager.h"
#include "CommandHistory.h"
#include "commands/AttributeSetCommand.h"
#include "../utils/Logger.h"
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/vt/value.h>

PXR_NAMESPACE_USING_DIRECTIVE

namespace UsdLayerManager {

PropertyManager::PropertyManager()
    : m_stage(nullptr) {
}

PropertyManager::~PropertyManager() {
}

void PropertyManager::SetStage(UsdStageRefPtr stage) {
    m_stage = stage;
    m_currentLayer = nullptr;
}

void PropertyManager::SetCurrentLayer(const SdfLayerHandle& layer) {
    m_currentLayer = layer;
}

std::vector<PropertyInfo> PropertyManager::GetPrimProperties(const std::string& primPath) {
    UsdPrim prim = GetPrim(primPath);
    if (!prim.IsValid()) {
        return {};
    }
    return GetPrimProperties(prim);
}

std::vector<PropertyInfo> PropertyManager::GetPrimProperties(const UsdPrim& prim) {
    std::vector<PropertyInfo> props;
    if (!prim.IsValid()) return props;
    
    CollectProperties(prim, props);
    
    return props;
}

void PropertyManager::CollectProperties(const UsdPrim& prim, std::vector<PropertyInfo>& props) {
    for (const auto& prop : prim.GetAttributes()) {
        PropertyInfo info;
        info.name = prop.GetName().GetString();
        info.displayName = info.name;
        info.attribute = prop;
        info.typeName = prop.GetTypeName().GetAsToken().GetString();
        info.hasValue = prop.HasValue();
        info.layerStack = GetPropertyLayerStack(prop);
        
        if (info.hasValue) {
            info.value = ExtractValue(prop);
        }
        
        props.push_back(info);
    }
}

PropertyValue PropertyManager::ExtractValue(const UsdAttribute& attr) {
    PropertyValue result = std::string("");
    
    auto typeName = attr.GetTypeName();
    auto roleName = typeName.GetRole();
    
    if (typeName == SdfValueTypeNames->Bool) {
        bool val = false;
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->Int) {
        int val = 0;
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->Float) {
        float val = 0.0f;
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->Double) {
        double val = 0.0;
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->Float3 || typeName == SdfValueTypeNames->Vector3f) {
        GfVec3f val(0.0f);
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->Double3 || typeName == SdfValueTypeNames->Vector3d) {
        GfVec3d val(0.0);
        attr.Get(&val);
        result = val;
    } else if (typeName == SdfValueTypeNames->String || typeName == SdfValueTypeNames->Token) {
        std::string val;
        attr.Get(&val);
        result = val;
    } else {
        // Fallback: get as string representation
        VtValue vtVal;
        if (attr.Get(&vtVal)) {
            result = vtVal.GetTypeName();
        }
    }
    
    return result;
}

bool PropertyManager::SetPropertyValue(const std::string& primPath, 
                                        const std::string& propName,
                                        const PropertyValue& value) {
    UsdPrim prim = GetPrim(primPath);
    if (!prim.IsValid()) {
        LOG_ERROR("Invalid prim path: " + primPath);
        return false;
    }
    
    UsdAttribute attr = prim.GetAttribute(TfToken(propName));
    if (!attr.IsValid()) {
        LOG_ERROR("Invalid attribute: " + propName);
        return false;
    }
    
    // Capture old value for undo
    PropertyValue oldValue = ExtractValue(attr);
    SdfLayerHandle targetLayer = m_currentLayer ? m_currentLayer : m_stage->GetEditTarget().GetLayer();
    
    // Create command with closures that capture old/new values and layer context
    if (m_commandHistory) {
        auto executeFunc = [this, primPath, propName, value, targetLayer]() {
            UsdPrim p = GetPrim(primPath);
            if (!p.IsValid()) return;
            UsdAttribute a = p.GetAttribute(TfToken(propName));
            if (!a.IsValid()) return;
            
            UsdEditContext editCtx(m_stage, targetLayer);
            ApplyValue(a, value);
        };
        
        auto undoFunc = [this, primPath, propName, oldValue, targetLayer]() {
            UsdPrim p = GetPrim(primPath);
            if (!p.IsValid()) return;
            UsdAttribute a = p.GetAttribute(TfToken(propName));
            if (!a.IsValid()) return;
            
            UsdEditContext editCtx(m_stage, targetLayer);
            ApplyValue(a, oldValue);
        };
        
        std::string description = "Set " + propName + " on " + primPath;
        m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
            description, executeFunc, undoFunc
        ));
        
        return true;
    } else {
        // Fallback: apply directly without undo support
        try {
            if (m_currentLayer) {
                UsdEditContext editCtx(m_stage, m_currentLayer);
                return ApplyValue(attr, value);
            } else {
                return ApplyValue(attr, value);
            }
        } catch (const std::exception& e) {
            LOG_ERROR(std::string("Failed to set property: ") + e.what());
            return false;
        }
    }
}

bool PropertyManager::SetPropertyValueInLayer(const std::string& primPath,
                                                const std::string& propName,
                                                const PropertyValue& value,
                                                const SdfLayerHandle& layer) {
    UsdPrim prim = GetPrim(primPath);
    if (!prim.IsValid()) return false;
    
    UsdAttribute attr = prim.GetAttribute(TfToken(propName));
    if (!attr.IsValid()) return false;
    
    // Capture old value for undo
    PropertyValue oldValue = ExtractValue(attr);
    
    // Create command with closures that capture old/new values and layer context
    if (m_commandHistory) {
        auto executeFunc = [this, primPath, propName, value, layer]() {
            UsdPrim p = GetPrim(primPath);
            if (!p.IsValid()) return;
            UsdAttribute a = p.GetAttribute(TfToken(propName));
            if (!a.IsValid()) return;
            
            UsdEditContext editCtx(m_stage, layer);
            ApplyValue(a, value);
        };
        
        auto undoFunc = [this, primPath, propName, oldValue, layer]() {
            UsdPrim p = GetPrim(primPath);
            if (!p.IsValid()) return;
            UsdAttribute a = p.GetAttribute(TfToken(propName));
            if (!a.IsValid()) return;
            
            UsdEditContext editCtx(m_stage, layer);
            ApplyValue(a, oldValue);
        };
        
        std::string layerName = layer ? LayerManager::ExtractDisplayName(layer->GetIdentifier()) : "default";
        std::string description = "Set " + propName + " on " + primPath + " in " + layerName;
        m_commandHistory->Push(std::make_unique<AttributeSetCommand>(
            description, executeFunc, undoFunc
        ));
        
        return true;
    } else {
        // Fallback: apply directly without undo support
        try {
            UsdEditContext editCtx(m_stage, layer);
            return ApplyValue(attr, value);
        } catch (const std::exception& e) {
            LOG_ERROR(std::string("Failed to set property: ") + e.what());
            return false;
        }
    }
}

std::string PropertyManager::GetPropertyLayerStack(const UsdAttribute& attr) {
    if (!attr.IsValid()) return "";
    
    SdfPropertySpecHandleVector propStack = attr.GetPropertyStack();
    std::string result;
    
    for (size_t i = 0; i < propStack.size(); i++) {
        if (i > 0) result += " -> ";
        
        SdfLayerHandle layer = propStack[i]->GetLayer();
        if (layer) {
            result += LayerManager::ExtractDisplayName(layer->GetIdentifier());
        }
    }
    
    return result;
}

std::vector<std::string> PropertyManager::GetPrimPaths() {
    std::vector<std::string> paths;
    if (!m_stage) return paths;
    
    paths.push_back(m_stage->GetPseudoRoot().GetPath().GetString());
    
    for (const auto& prim : m_stage->Traverse()) {
        paths.push_back(prim.GetPath().GetString());
    }
    
    return paths;
}

UsdPrim PropertyManager::GetPrim(const std::string& path) {
    if (!m_stage || path.empty()) {
        return UsdPrim();
    }
    
    SdfPath sdfPath(path);
    return m_stage->GetPrimAtPath(sdfPath);
}

bool PropertyManager::ApplyValue(const UsdAttribute& attr, const PropertyValue& value) {
    return std::visit([&](auto&& val) -> bool {
        using T = std::decay_t<decltype(val)>;
        
        if constexpr (std::is_same_v<T, bool>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, int>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, float>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, double>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, std::string>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, GfVec3f>) {
            return attr.Set(val);
        } else if constexpr (std::is_same_v<T, GfVec3d>) {
            return attr.Set(val);
        } else {
            return false;
        }
    }, value);
}

} // namespace UsdLayerManager