// Copyright 2020-2024 CesiumGS, Inc. and Contributors

#include "CesiumPropertyTextureProperty.h"
#include "CesiumGltfPrimitiveComponent.h"
#include "UnrealMetadataConversions.h"

#include <CesiumGltf/MetadataConversions.h>
#include <cstdint>
#include <limits>

namespace {
/**
 * Callback on a std::any, assuming that it contains a
 * PropertyTexturePropertyView of the specified type. If the type does not
 * match, the callback is performed on an invalid PropertyTexturePropertyView
 * instead.
 *
 * @param property The std::any containing the property.
 * @param callback The callback function.
 *
 * @tparam TProperty The property type.
 * @tparam Normalized Whether the PropertyTexturePropertyView is normalized.
 * @tparam TResult The type of the output from the callback function.
 * @tparam Callback The callback function type.
 */
template <
    typename TProperty,
    bool Normalized,
    typename TResult,
    typename Callback>
TResult
propertyTexturePropertyCallback(const std::any& property, Callback&& callback) {
  const CesiumGltf::PropertyTexturePropertyView<TProperty, Normalized>*
      pProperty = std::any_cast<
          CesiumGltf::PropertyTexturePropertyView<TProperty, Normalized>>(
          &property);
  if (pProperty) {
    return callback(*pProperty);
  }

  return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
}

/**
 * Callback on a std::any, assuming that it contains a
 * PropertyTexturePropertyView on a scalar type. If the valueType does not have
 * a valid component type, the callback is performed on an invalid
 * PropertyTexturePropertyView instead.
 *
 * @param property The std::any containing the property.
 * @param valueType The FCesiumMetadataValueType of the property.
 * @param callback The callback function.
 *
 * @tparam Normalized Whether the PropertyTexturePropertyView is normalized.
 * @tparam TResult The type of the output from the callback function.
 * @tparam Callback The callback function type.
 */
template <bool Normalized, typename TResult, typename Callback>
TResult scalarPropertyTexturePropertyCallback(
    const std::any& property,
    const FCesiumMetadataValueType& valueType,
    Callback&& callback) {
  switch (valueType.ComponentType) {
  case ECesiumMetadataComponentType::Int8:
    return propertyTexturePropertyCallback<
        int8_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint8:
    return propertyTexturePropertyCallback<
        uint8_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Int16:
    return propertyTexturePropertyCallback<
        int16_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint16:
    return propertyTexturePropertyCallback<
        uint16_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Int32:
    return propertyTexturePropertyCallback<
        int32_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint32:
    return propertyTexturePropertyCallback<
        uint32_t,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Float32:
    return propertyTexturePropertyCallback<float, false, TResult, Callback>(
        property,
        std::forward<Callback>(callback));
  default:
    return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
  }
}

/**
 * Callback on a std::any, assuming that it contains a
 * PropertyTexturePropertyView on a scalar array type. If the valueType does not
 * have a valid component type, the callback is performed on an invalid
 * PropertyTexturePropertyView instead.
 *
 * @param property The std::any containing the property.
 * @param valueType The FCesiumMetadataValueType of the property.
 * @param callback The callback function.
 *
 * @tparam Normalized Whether the PropertyTexturePropertyView is normalized.
 * @tparam TResult The type of the output from the callback function.
 * @tparam Callback The callback function type.
 */
template <bool Normalized, typename TResult, typename Callback>
TResult scalarArrayPropertyTexturePropertyCallback(
    const std::any& property,
    const FCesiumMetadataValueType& valueType,
    Callback&& callback) {
  switch (valueType.ComponentType) {
  case ECesiumMetadataComponentType::Int8:
    return propertyTexturePropertyCallback<
        CesiumGltf::PropertyArrayView<int8_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint8:
    return propertyTexturePropertyCallback<
        CesiumGltf::PropertyArrayView<uint8_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Int16:
    return propertyTexturePropertyCallback<
        CesiumGltf::PropertyArrayView<int16_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint16:
    return propertyTexturePropertyCallback<
        CesiumGltf::PropertyArrayView<uint16_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  default:
    return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
  }
}

/**
 * Callback on a std::any, assuming that it contains a
 * PropertyTexturePropertyView on a glm::vecN type. If the valueType does not
 * have a valid component type, the callback is performed on an invalid
 * PropertyTexturePropertyView instead.
 *
 * @param property The std::any containing the property.
 * @param valueType The FCesiumMetadataValueType of the property.
 * @param callback The callback function.
 *
 * @tparam N The dimensions of the glm::vecN
 * @tparam Normalized Whether the PropertyTexturePropertyView is normalized.
 * @tparam TResult The type of the output from the callback function.
 * @tparam Callback The callback function type.
 */
template <glm::length_t N, bool Normalized, typename TResult, typename Callback>
TResult vecNPropertyTexturePropertyCallback(
    const std::any& property,
    const FCesiumMetadataValueType& valueType,
    Callback&& callback) {
  switch (valueType.ComponentType) {
  case ECesiumMetadataComponentType::Int8:
    return propertyTexturePropertyCallback<
        glm::vec<N, int8_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint8:
    return propertyTexturePropertyCallback<
        glm::vec<N, uint8_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Int16:
    return propertyTexturePropertyCallback<
        glm::vec<N, int16_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  case ECesiumMetadataComponentType::Uint16:
    return propertyTexturePropertyCallback<
        glm::vec<N, uint16_t>,
        Normalized,
        TResult,
        Callback>(property, std::forward<Callback>(callback));
  default:
    return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
  }
}

/**
 * Callback on a std::any, assuming that it contains a
 * PropertyTexturePropertyView on a glm::vecN type. If the valueType does not
 * have a valid component type, the callback is performed on an invalid
 * PropertyTexturePropertyView instead.
 *
 * @param property The std::any containing the property.
 * @param valueType The FCesiumMetadataValueType of the property.
 * @param callback The callback function.
 *
 * @tparam Normalized Whether the PropertyTexturePropertyView is normalized.
 * @tparam TResult The type of the output from the callback function.
 * @tparam Callback The callback function type.
 */
template <bool Normalized, typename TResult, typename Callback>
TResult vecNPropertyTexturePropertyCallback(
    const std::any& property,
    const FCesiumMetadataValueType& valueType,
    Callback&& callback) {
  if (valueType.Type == ECesiumMetadataType::Vec2) {
    return vecNPropertyTexturePropertyCallback<
        2,
        Normalized,
        TResult,
        Callback>(property, valueType, std::forward<Callback>(callback));
  }

  if (valueType.Type == ECesiumMetadataType::Vec3) {
    return vecNPropertyTexturePropertyCallback<
        3,
        Normalized,
        TResult,
        Callback>(property, valueType, std::forward<Callback>(callback));
  }

  if (valueType.Type == ECesiumMetadataType::Vec4) {
    return vecNPropertyTexturePropertyCallback<
        4,
        Normalized,
        TResult,
        Callback>(property, valueType, std::forward<Callback>(callback));
  }

  return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
}

template <typename TResult, typename Callback>
TResult propertyTexturePropertyCallback(
    const std::any& property,
    const FCesiumMetadataValueType& valueType,
    bool normalized,
    Callback&& callback) {

  if (valueType.bIsArray && valueType.Type != ECesiumMetadataType::Scalar) {

    // Only scalar property arrays are supported.
    return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
  }

  if (valueType.bIsArray) {
    return normalized ? scalarArrayPropertyTexturePropertyCallback<
                            true,
                            TResult,
                            Callback>(
                            property,
                            valueType,
                            std::forward<Callback>(callback))
                      : scalarArrayPropertyTexturePropertyCallback<
                            false,
                            TResult,
                            Callback>(
                            property,
                            valueType,
                            std::forward<Callback>(callback));
  }

  switch (valueType.Type) {
  case ECesiumMetadataType::Scalar:
    return normalized
               ? scalarPropertyTexturePropertyCallback<true, TResult, Callback>(
                     property,
                     valueType,
                     std::forward<Callback>(callback))
               : scalarPropertyTexturePropertyCallback<
                     false,
                     TResult,
                     Callback>(
                     property,
                     valueType,
                     std::forward<Callback>(callback));
  case ECesiumMetadataType::Vec2:
  case ECesiumMetadataType::Vec3:
  case ECesiumMetadataType::Vec4:
    return normalized
               ? vecNPropertyTexturePropertyCallback<true, TResult, Callback>(
                     property,
                     valueType,
                     std::forward<Callback>(callback))
               : vecNPropertyTexturePropertyCallback<false, TResult, Callback>(
                     property,
                     valueType,
                     std::forward<Callback>(callback));
  default:
    return callback(CesiumGltf::PropertyTexturePropertyView<uint8_t>());
  }
}

} // namespace

const int64 FCesiumPropertyTextureProperty::getTexCoordSetIndex() const {
  return propertyTexturePropertyCallback<int64>(
      this->_property,
      this->_valueType,
      this->_normalized,
      [](const auto& view) -> int64 { return view.getTexCoordSetIndex(); });
}

const CesiumGltf::Sampler* FCesiumPropertyTextureProperty::getSampler() const {
  return propertyTexturePropertyCallback<const CesiumGltf::Sampler*>(
      this->_property,
      this->_valueType,
      this->_normalized,
      [](const auto& view) -> const CesiumGltf::Sampler* {
        return view.getSampler();
      });
}

const CesiumGltf::ImageAsset* FCesiumPropertyTextureProperty::getImage() const {
  return propertyTexturePropertyCallback<const CesiumGltf::ImageAsset*>(
      this->_property,
      this->_valueType,
      this->_normalized,
      [](const auto& view) -> const CesiumGltf::ImageAsset* {
        return view.getImage();
      });
}

const std::optional<CesiumGltf::KhrTextureTransform>
FCesiumPropertyTextureProperty::getTextureTransform() const {
  return propertyTexturePropertyCallback<
      std::optional<CesiumGltf::KhrTextureTransform>>(
      this->_property,
      this->_valueType,
      this->_normalized,
      [](const auto& view) -> std::optional<CesiumGltf::KhrTextureTransform> {
        return view.getTextureTransform();
      });
}

ECesiumPropertyTexturePropertyStatus
UCesiumPropertyTexturePropertyBlueprintLibrary::
    GetPropertyTexturePropertyStatus(
        UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return Property._status;
}

ECesiumMetadataBlueprintType
UCesiumPropertyTexturePropertyBlueprintLibrary::GetBlueprintType(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return CesiumMetadataValueTypeToBlueprintType(Property._valueType);
}

ECesiumMetadataBlueprintType
UCesiumPropertyTexturePropertyBlueprintLibrary::GetArrayElementBlueprintType(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  if (!Property._valueType.bIsArray) {
    return ECesiumMetadataBlueprintType::None;
  }

  FCesiumMetadataValueType valueType(Property._valueType);
  valueType.bIsArray = false;

  return CesiumMetadataValueTypeToBlueprintType(valueType);
}

FCesiumMetadataValueType
UCesiumPropertyTexturePropertyBlueprintLibrary::GetValueType(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return Property._valueType;
}

int64 UCesiumPropertyTexturePropertyBlueprintLibrary::GetArraySize(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<int64>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> int64 { return view.arrayCount(); });
}

int64 UCesiumPropertyTexturePropertyBlueprintLibrary::
    GetGltfTextureCoordinateSetIndex(
        UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return Property.getTexCoordSetIndex();
}

int64 UCesiumPropertyTexturePropertyBlueprintLibrary::GetUnrealUVChannel(
    const UPrimitiveComponent* Component,
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  const UCesiumGltfPrimitiveComponent* pPrimitive =
      Cast<UCesiumGltfPrimitiveComponent>(Component);
  if (!pPrimitive) {
    return -1;
  }

  int64_t texCoordSetIndex = UCesiumPropertyTexturePropertyBlueprintLibrary::
      GetGltfTextureCoordinateSetIndex(Property);
  const CesiumPrimitiveData& primData = pPrimitive->getPrimitiveData();
  auto textureCoordinateIndexIt =
      primData.GltfToUnrealTexCoordMap.find(texCoordSetIndex);
  if (textureCoordinateIndexIt == primData.GltfToUnrealTexCoordMap.end()) {
    return -1;
  }

  return textureCoordinateIndexIt->second;
}

PRAGMA_DISABLE_DEPRECATION_WARNINGS
FString UCesiumPropertyTexturePropertyBlueprintLibrary::GetSwizzle(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  const std::string swizzle =
      propertyTexturePropertyCallback<const std::string>(
          Property._property,
          Property._valueType,
          Property._normalized,
          [](const auto& view) { return view.getSwizzle(); });
  return UTF8_TO_TCHAR(swizzle.c_str());
}

int64 UCesiumPropertyTexturePropertyBlueprintLibrary::GetComponentCount(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<int64>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) {
        return static_cast<int64>(view.getChannels().size());
      });
}
PRAGMA_ENABLE_DEPRECATION_WARNINGS

TArray<int64> UCesiumPropertyTexturePropertyBlueprintLibrary::GetChannels(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<TArray<int64>>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> TArray<int64> {
        const std::vector<int64_t>& channels = view.getChannels();
        TArray<int64> result;
        result.Reserve(static_cast<int32>(channels.size()));
        for (size_t i = 0; i < channels.size(); i++) {
          result.Emplace(channels[i]);
        }

        return result;
      });
}

uint8 UCesiumPropertyTexturePropertyBlueprintLibrary::GetByte(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    uint8 DefaultValue) {
  return propertyTexturePropertyCallback<uint8>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, DefaultValue](const auto& view) -> uint8 {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (maybeValue) {
          auto value = *maybeValue;
          return CesiumGltf::MetadataConversions<uint8, decltype(value)>::
              convert(value)
                  .value_or(DefaultValue);
        }
        return DefaultValue;
      });
}

int32 UCesiumPropertyTexturePropertyBlueprintLibrary::GetInteger(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    int32 DefaultValue) {
  return propertyTexturePropertyCallback<int32>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, DefaultValue](const auto& view) -> int32 {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (maybeValue) {
          auto value = *maybeValue;
          return CesiumGltf::MetadataConversions<int32, decltype(value)>::
              convert(value)
                  .value_or(DefaultValue);
        }
        return DefaultValue;
      });
}

float UCesiumPropertyTexturePropertyBlueprintLibrary::GetFloat(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    float DefaultValue) {
  return propertyTexturePropertyCallback<float>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, DefaultValue](const auto& view) -> float {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (maybeValue) {
          auto value = *maybeValue;
          return CesiumGltf::MetadataConversions<float, decltype(value)>::
              convert(value)
                  .value_or(DefaultValue);
        }
        return DefaultValue;
      });
}

double UCesiumPropertyTexturePropertyBlueprintLibrary::GetFloat64(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    double DefaultValue) {
  return propertyTexturePropertyCallback<double>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, DefaultValue](const auto& view) -> double {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (maybeValue) {
          auto value = *maybeValue;
          return CesiumGltf::MetadataConversions<double, decltype(value)>::
              convert(value)
                  .value_or(DefaultValue);
        }
        return DefaultValue;
      });
}

FIntPoint UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntPoint(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    const FIntPoint& DefaultValue) {
  return propertyTexturePropertyCallback<FIntPoint>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, &DefaultValue](const auto& view) -> FIntPoint {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (!maybeValue) {
          return DefaultValue;
        }
        auto value = *maybeValue;
        if constexpr (CesiumGltf::IsMetadataString<decltype(value)>::value) {
          return UnrealMetadataConversions::toIntPoint(
              *maybeValue,
              DefaultValue);
        } else {
          auto maybeVec2 = CesiumGltf::
              MetadataConversions<glm::ivec2, decltype(value)>::convert(value);
          return maybeVec2 ? UnrealMetadataConversions::toIntPoint(*maybeVec2)
                           : DefaultValue;
        }
      });
}

FVector2D UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector2D(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    const FVector2D& DefaultValue) {
  return propertyTexturePropertyCallback<FVector2D>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, &DefaultValue](const auto& view) -> FVector2D {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (!maybeValue) {
          return DefaultValue;
        }
        auto value = *maybeValue;
        if constexpr (CesiumGltf::IsMetadataString<decltype(value)>::value) {
          return UnrealMetadataConversions::toVector2D(value, DefaultValue);
        } else {
          auto maybeVec2 = CesiumGltf::
              MetadataConversions<glm::dvec2, decltype(value)>::convert(value);
          return maybeVec2 ? UnrealMetadataConversions::toVector2D(*maybeVec2)
                           : DefaultValue;
        }
      });
}

FIntVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetIntVector(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    const FIntVector& DefaultValue) {
  return propertyTexturePropertyCallback<FIntVector>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, &DefaultValue](const auto& view) -> FIntVector {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (!maybeValue) {
          return DefaultValue;
        }
        auto value = *maybeValue;
        if constexpr (CesiumGltf::IsMetadataString<decltype(value)>::value) {
          return UnrealMetadataConversions::toIntVector(value, DefaultValue);
        } else {
          auto maybeVec3 = CesiumGltf::
              MetadataConversions<glm::ivec3, decltype(value)>::convert(value);
          return maybeVec3 ? UnrealMetadataConversions::toIntVector(*maybeVec3)
                           : DefaultValue;
        }
      });
}

FVector UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    const FVector& DefaultValue) {
  return propertyTexturePropertyCallback<FVector>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, &DefaultValue](const auto& view) -> FVector {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (!maybeValue) {
          return DefaultValue;
        }
        auto value = *maybeValue;
        if constexpr (CesiumGltf::IsMetadataString<decltype(value)>::value) {
          return UnrealMetadataConversions::toVector(value, DefaultValue);
        } else {
          auto maybeVec3 = CesiumGltf::
              MetadataConversions<glm::dvec3, decltype(value)>::convert(value);
          return maybeVec3 ? UnrealMetadataConversions::toVector(*maybeVec3)
                           : DefaultValue;
        }
      });
}

FVector4 UCesiumPropertyTexturePropertyBlueprintLibrary::GetVector4(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV,
    const FVector4& DefaultValue) {
  return propertyTexturePropertyCallback<FVector4>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV, &DefaultValue](const auto& view) -> FVector4 {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return DefaultValue;
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (!maybeValue) {
          return DefaultValue;
        }
        auto value = *maybeValue;
        if constexpr (CesiumGltf::IsMetadataString<decltype(value)>::value) {
          return UnrealMetadataConversions::toVector(value, DefaultValue);
        } else {
          auto maybeVec4 = CesiumGltf::
              MetadataConversions<glm::dvec4, decltype(value)>::convert(value);
          return maybeVec4 ? UnrealMetadataConversions::toVector4(*maybeVec4)
                           : DefaultValue;
        }
      });
}

FCesiumPropertyArray UCesiumPropertyTexturePropertyBlueprintLibrary::GetArray(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV) {
  return propertyTexturePropertyCallback<FCesiumPropertyArray>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV](const auto& view) -> FCesiumPropertyArray {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return FCesiumPropertyArray();
        }
        auto maybeValue = view.get(UV.X, UV.Y);
        if (maybeValue) {
          auto value = *maybeValue;
          if constexpr (CesiumGltf::IsMetadataArray<decltype(value)>::value) {
            return FCesiumPropertyArray(value);
          }
        }
        return FCesiumPropertyArray();
      });
}

FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV](const auto& view) -> FCesiumMetadataValue {
        if (view.status() !=
                CesiumGltf::PropertyTexturePropertyViewStatus::Valid &&
            view.status() != CesiumGltf::PropertyTexturePropertyViewStatus::
                                 EmptyPropertyWithDefault) {
          return FCesiumMetadataValue();
        }

        return FCesiumMetadataValue(view.get(UV.X, UV.Y));
      });
}

FCesiumMetadataValue
UCesiumPropertyTexturePropertyBlueprintLibrary::GetRawValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property,
    const FVector2D& UV) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [&UV](const auto& view) -> FCesiumMetadataValue {
        if (view.status() !=
            CesiumGltf::PropertyTexturePropertyViewStatus::Valid) {
          return FCesiumMetadataValue();
        }

        return FCesiumMetadataValue(view.getRaw(UV.X, UV.Y));
      });
}

bool UCesiumPropertyTexturePropertyBlueprintLibrary::IsNormalized(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return Property._normalized;
}

FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetOffset(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no offset is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.offset()));
      });
}

FCesiumMetadataValue UCesiumPropertyTexturePropertyBlueprintLibrary::GetScale(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no scale is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.scale()));
      });
}

FCesiumMetadataValue
UCesiumPropertyTexturePropertyBlueprintLibrary::GetMinimumValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no min is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.min()));
      });
}

FCesiumMetadataValue
UCesiumPropertyTexturePropertyBlueprintLibrary::GetMaximumValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no max is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.max()));
      });
}

FCesiumMetadataValue
UCesiumPropertyTexturePropertyBlueprintLibrary::GetNoDataValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no "no data" value is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.noData()));
      });
}

FCesiumMetadataValue
UCesiumPropertyTexturePropertyBlueprintLibrary::GetDefaultValue(
    UPARAM(ref) const FCesiumPropertyTextureProperty& Property) {
  return propertyTexturePropertyCallback<FCesiumMetadataValue>(
      Property._property,
      Property._valueType,
      Property._normalized,
      [](const auto& view) -> FCesiumMetadataValue {
        // Returns an empty value if no default value is specified.
        return FCesiumMetadataValue(
            CesiumGltf::propertyValueViewToCopy(view.defaultValue()));
      });
}