//
// Copyright 2025 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//

#ifndef PXR_USD_SDR_SHADER_NODE_METADATA_H
#define PXR_USD_SDR_SHADER_NODE_METADATA_H

#include "pxr/pxr.h"
#include "pxr/base/tf/staticTokens.h"
#include "pxr/base/vt/dictionary.h"
#include "pxr/usd/sdr/api.h"
#include "pxr/usd/sdr/declare.h"

PXR_NAMESPACE_OPEN_SCOPE

// Note: Metadata keys that are generated by parsers should start with
// "__SDR__" to reduce the risk of collision with metadata actually in the
// shader.
//
// Enum names should correlate directly to named API e.g. "GetLabel" or
// "GetSdrUsdEncodingVersion"
#define SDR_NODE_METADATA_TOKENS   \
    ((Category, "category"))       \
    ((Role, "role"))               \
    ((Departments, "departments")) \
    ((Help, "help"))               \
    ((Label, "label"))             \
    ((Pages, "pages"))             \
    ((OpenPages, "openPages"))     \
    ((PagesShownIf, "pagesShownIf")) \
    ((Primvars, "primvars"))       \
    ((ImplementationName, "__SDR__implementationName"))\
    ((Target, "__SDR__target"))    \
    ((SdrUsdEncodingVersion, "sdrUsdEncodingVersion")) \
    ((SdrDefinitionNameFallbackPrefix, "sdrDefinitionNameFallbackPrefix"))

// Note: The concept of context can be queried with the GetContext() method.
// Sdr categorizes shaders by the context in which they are used inside of a
// renderer. For instance during 'pattern' evaluation to feed into a surface
// or volume shader. For BXDFs used in 'surface' and 'volume'
// rendering situations.
#define SDR_NODE_CONTEXT_TOKENS         \
    ((Pattern, "pattern"))              \
    ((Surface, "surface"))              \
    ((Volume, "volume"))                \
    ((Displacement, "displacement"))    \
    ((Light, "light"))                  \
    ((DisplayFilter, "displayFilter"))  \
    ((LightFilter, "lightFilter"))      \
    ((PixelFilter, "pixelFilter"))      \
    ((SampleFilter, "sampleFilter"))    

#define SDR_NODE_ROLE_TOKENS         \
    ((Primvar, "primvar"))           \
    ((Texture, "texture"))           \
    ((Field, "field"))               \
    ((Math, "math"))                 \

TF_DECLARE_PUBLIC_TOKENS(SdrNodeMetadata, SDR_API, SDR_NODE_METADATA_TOKENS);
TF_DECLARE_PUBLIC_TOKENS(SdrNodeContext, SDR_API, SDR_NODE_CONTEXT_TOKENS);
TF_DECLARE_PUBLIC_TOKENS(SdrNodeRole, SDR_API, SDR_NODE_ROLE_TOKENS);


/// SdrShaderNodeMetadata contains generic and named metadata for
/// SdrShaderNode.
///
/// Named metadata are key-value items that are standardized and
/// documented for interchange. Named metadata items are defined to
/// have certain types that expect certain encodings.
/// The Has/Set/Get methods for named key-value items provide views
/// into the requested metadata item's unpacked VtValues.
///
/// NOTE: This metadata is retrievable from SdrShaderNode with
/// SdrShaderNode::GetMetadataObject
class SdrShaderNodeMetadata
{
public:
    /// Ingest metadata from the legacy SdrTokenMap structure.
    ///
    /// For named metadata items, conversions to richer types from
    /// the SdrTokenMap values are attempted.
    SDR_API
    SdrShaderNodeMetadata(const SdrTokenMap& legacyMetadata);

    /// Ingest metadata from an initializer list for the legacy SdrTokenMap.
    ///
    /// This helps provide automatic, implicit backwards compatibility for
    /// users that construct SdrTokenMap metadata via an initializer list
    /// in the SdrShaderNode constructor.
    SdrShaderNodeMetadata(
        const std::initializer_list<std::pair<TfToken, std::string>>& init
    ): SdrShaderNodeMetadata(_LegacyCtorFromInitializer(init)) {}

    SDR_API
    explicit SdrShaderNodeMetadata(const VtDictionary& items);

    SDR_API
    explicit SdrShaderNodeMetadata(VtDictionary&& items);

    SdrShaderNodeMetadata() {}

    /// Returns whether this metadata contains an item with the given key.
    SDR_API
    bool HasItem(const TfToken& key) const;

    /// Set a key-value item for this metadata.
    ///
    /// If the key-value item already exists, it will be overwritten by the
    /// provided arguments.
    ///
    /// Clears the item if given an empty VtValue.
    ///
    /// Issues a TF_CODING_ERROR if the key is in "named metadata" and
    /// the value's contained type does not match the type
    /// registered by the named metadata item. In this case, no action
    /// is taken to set the item.
    SDR_API
    void SetItem(const TfToken& key, const VtValue& value);

    /// \overload
    template <typename T>
    void SetItem(const TfToken& key, const T& value) {
        SetItem(key, VtValue(value));
    }

    /// Get the VtValue for the given key.
    ///
    /// If the value is found in the legacy SdrTokenMap metadata, the returned
    /// value will be a VtValue holding std::string.
    ///
    /// If a key-value item doesn't exist for the given key in this metadata,
    /// returns an empty VtValue.
    SDR_API
    VtValue GetItemValue(const TfToken& key) const;

    /// Convenience to get an item value as T.
    ///
    /// If any value fails conversion or doesn't exist in the metadata, a
    /// default constructed value for T is returned.
    template <typename T>
    T GetItemValueAs(const TfToken& key) const {
        const VtValue v = GetItemValue(key);
        if (!v.IsEmpty()) {
            const VtValue converted = VtValue::Cast<T>(v);
            if (!converted.IsEmpty()) {
                return converted.UncheckedGet<T>();
            }
        }

        return {};
    }

    /// Clear the metadata item for the given key if it exists.
    SDR_API
    void ClearItem(const TfToken& key);

    /// Get all key-value items.
    const VtDictionary& GetItems() const & { return _items; }

    /// Get all key-value items by-value.
    VtDictionary GetItems() && { return std::move(_items); }

    /// Named metadata
    ///
    /// {@

    SDR_API
    bool HasLabel() const;
    SDR_API
    TfToken GetLabel() const;
    SDR_API
    void SetLabel(const TfToken& v);
    SDR_API
    void ClearLabel();

    SDR_API
    bool HasCategory() const;
    SDR_API
    TfToken GetCategory() const;
    SDR_API
    void SetCategory(const TfToken& v);
    SDR_API
    void ClearCategory();

    /// \name Role
    ///
    /// Role is used to annotate the role that the shader node plays inside
    /// a shader network.
    /// {@
    SDR_API
    bool HasRole() const;
    SDR_API
    TfToken GetRole() const;
    SDR_API
    void SetRole(const TfToken& v);
    SDR_API
    void ClearRole();
    /// @}

    SDR_API
    bool HasHelp() const;
    SDR_API
    std::string GetHelp() const;
    SDR_API
    void SetHelp(const std::string& v);
    SDR_API
    void ClearHelp();

    SDR_API
    bool HasDepartments() const;
    SDR_API
    SdrTokenVec GetDepartments() const;
    SDR_API
    void SetDepartments(const SdrTokenVec& v);
    SDR_API
    void ClearDepartments();

    /// \deprecated
    ///
    /// SdrShaderNode::GetPages is computed via SdrShaderProperty's Pages
    /// metadata. The Pages node metadata API is redundant and therefore
    /// deprecated.
    ///
    /// {@
    SDR_API
    bool HasPages() const;
    SDR_API
    SdrTokenVec GetPages() const;
    SDR_API
    void SetPages(const SdrTokenVec& v);
    SDR_API
    void ClearPages();
    /// @}

    SDR_API
    bool HasOpenPages() const;
    SDR_API
    SdrTokenVec GetOpenPages() const;
    SDR_API
    void SetOpenPages(const SdrTokenVec& v);
    SDR_API
    void ClearOpenPages();

    /// \name PagesShownIf
    ///
    /// PagesShownIf is encoded as an SdrTokenMap, where each key
    /// is a page name and each value is a "shownIf" style expression.
    ///
    /// \sa SdrShaderProperty::GetShownIf
    ///
    /// A nested page may be represented with ":" separating subpages.
    ///
    /// {@
    SDR_API
    bool HasPagesShownIf() const;
    SDR_API
    SdrTokenMap GetPagesShownIf() const;
    SDR_API
    void SetPagesShownIf(const SdrTokenMap& v);
    SDR_API
    void ClearPagesShownIf();
    /// @}

    SDR_API
    bool HasPrimvars() const;
    SDR_API
    SdrStringVec GetPrimvars() const;
    SDR_API
    void SetPrimvars(const SdrStringVec& v);
    SDR_API
    void ClearPrimvars();

    SDR_API
    bool HasImplementationName() const;
    SDR_API
    std::string GetImplementationName() const;
    SDR_API
    void SetImplementationName(const std::string& v);
    SDR_API
    void ClearImplementationName();

    SDR_API
    bool HasSdrUsdEncodingVersion() const;
    SDR_API
    int GetSdrUsdEncodingVersion() const;
    SDR_API
    void SetSdrUsdEncodingVersion(const int& v);
    SDR_API
    void ClearSdrUsdEncodingVersion();

    SDR_API
    bool HasSdrDefinitionNameFallbackPrefix() const;
    SDR_API
    std::string GetSdrDefinitionNameFallbackPrefix() const;
    SDR_API
    void SetSdrDefinitionNameFallbackPrefix(const std::string& v);
    SDR_API
    void ClearSdrDefinitionNameFallbackPrefix();

    /// @}

private:
    friend class SdrShaderNode;

    // Deprecated function for legacy metadata support.
    //
    // Unnamed metadata with non-string values are not returned in
    // the legacy map.
    SdrTokenMap _EncodeLegacyMetadata() const;

    static
    SdrTokenMap _LegacyCtorFromInitializer(
        std::initializer_list<std::pair<TfToken, std::string>> f)
    {
        return SdrTokenMap(f.begin(), f.end());
    }

    VtDictionary _items;
};

PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_USD_SDR_SHADER_NODE_METADATA_H
