//
// 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")) /* deprecated */            \
    ((Domain, "domain"))                                 \
    ((Subdomain, "subdomain"))                           \
    ((Context, "context"))                               \
    ((Role, "role"))                                     \
    ((TargetRenderer, "targetRenderer"))                 \
    ((Collections, "collections"))                       \
    ((Departments, "departments")) /* deprecated */      \
    ((Help, "help"))                                     \
    ((Label, "label"))                                   \
    ((Pages, "pages")) /* deprecated */                  \
    ((OpenPages, "openPages"))                           \
    ((PagesShownIf, "pagesShownIf"))                     \
    ((Primvars, "primvars"))                             \
    ((ImplementationName, "__SDR__implementationName"))  \
    ((Target, "__SDR__target")) /* deprecated */         \
    ((SdrUsdEncodingVersion, "sdrUsdEncodingVersion"))   \
    ((SdrDefinitionNameFallbackPrefix, "sdrDefinitionNameFallbackPrefix"))

#define SDR_NODE_DOMAIN_TOKENS  \
    ((Rendering, "rendering"))  \
    ((General, "general"))

#define SDR_NODE_SUBDOMAIN_TOKENS  \
    /* rendering domain */         \
    ((Shading, "shading"))         \
    ((Filtering, "filtering"))     \
    ((Lighting, "lighting"))       \
    ((Rendering, "rendering"))

#define SDR_NODE_CONTEXT_TOKENS         \
    /* shading subdomain */             \
    ((Pattern, "pattern"))              \
    ((Surface, "surface"))              \
    ((Volume, "volume"))                \
    ((Displacement, "displacement"))    \
    /* lighting subdomain */            \
    ((Light, "light"))                  \
    ((LightFilter, "lightFilter"))      \
    /* filtering subdomain*/            \
    ((DisplayFilter, "displayFilter"))  \
    ((PixelFilter, "pixelFilter"))      \
    ((SampleFilter, "sampleFilter"))    \
    ((VolumeFilter, "volumeFilter"))    \
    ((EnergyFilter, "energyFilter"))    \
    /* rendering subdomain */           \
    ((Integrator, "integrator"))        \
    ((Projection, "projection"))

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


/// \deprecated
/// SdrNodeMetadata->Category, SdrNodeMetadata->Departments,
/// SdrNodeMetadata->Pages, SdrNodeMetadata->Target are deprecated.
///
/// All other tokens will remain intact. Note that clients can still
/// set metadata with keys such as "category" and "departments" -- the
/// convenience API is simply removed.
TF_DECLARE_PUBLIC_TOKENS(SdrNodeMetadata, SDR_API, SDR_NODE_METADATA_TOKENS);
TF_DECLARE_PUBLIC_TOKENS(SdrNodeDomain, SDR_API, SDR_NODE_DOMAIN_TOKENS);
TF_DECLARE_PUBLIC_TOKENS(SdrNodeSubdomain, SDR_API, SDR_NODE_SUBDOMAIN_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:
    SDR_API
    SdrShaderNodeMetadata();

    /// 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);

    /// 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); }

    /// \name Default values
    /// Default values are populated to SdrShaderNodeMetadata at
    /// construction time, if the corresponding metadata items are
    /// not already specified by the ingested dictionary.
    /// 
    /// Note that metadata items with default values can still
    /// be modified post-construction, and that clearing
    /// those items will remove them from the metadata object
    /// rather than resetting them to their default value.
    /// {@

    /// This method returns a non-empty VtValue if and only if the
    /// key represents a metadata item with a default value.
    SDR_API
    static VtValue GetDefaultValue(const TfToken& key);

    /// Returns all metadata items that have default values.
    ///
    /// The result is a dictionary with metadata keys and their
    /// associated default values.
    SDR_API
    static const VtDictionary& GetDefaultValues();
    /// @}

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

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

    /// \name Category
    ///
    /// Deprecated metadata item.
    ///
    /// \deprecated
    /// {@

    /// \deprecated
    SDR_API
    bool HasCategory() const;
    /// \deprecated
    SDR_API
    TfToken GetCategory() const;
    /// \deprecated
    SDR_API
    void SetCategory(const TfToken& v);
    /// \deprecated
    SDR_API
    void ClearCategory();
    /// @}

    /// \name Domain
    ///
    /// Domain defines groups for the broadest system categories.
    ///
    /// Domain has a default value of "rendering". This means that
    /// at SdrShaderNodeMetadata initialization time, unspecified
    /// domains will be initialized to SdrNodeDomain->Rendering.
    ///
    /// SdrNodeDomain contains common domain token values.
    /// {@
    SDR_API
    bool HasDomain() const;
    SDR_API
    TfToken GetDomain() const;
    SDR_API
    void SetDomain(const TfToken& v);
    SDR_API
    void ClearDomain();
    /// @}

    /// \name Subdomain
    ///
    /// Subdomain defines subsystems within the domain.
    ///
    /// SdrNodeSubdomain contains common subdomain token values.
    /// {@
    SDR_API
    bool HasSubdomain() const;
    SDR_API
    TfToken GetSubdomain() const;
    SDR_API
    void SetSubdomain(const TfToken& v);
    SDR_API
    void ClearSubdomain();
    ///@}

    /// \name Context
    ///
    /// Context describes a node's usage group within its subdomain.
    ///
    /// SdrNodeContext contains common context token values.
    /// {@
    SDR_API
    bool HasContext() const;
    SDR_API
    TfToken GetContext() const;
    SDR_API
    void SetContext(const TfToken& v);
    SDR_API
    void ClearContext();
    ///@}

    /// \name Role
    ///
    /// Role provides finer granularity for contexts that contain many nodes.
    /// {@
    SDR_API
    bool HasRole() const;
    SDR_API
    TfToken GetRole() const;
    SDR_API
    void SetRole(const TfToken& v);
    SDR_API
    void ClearRole();
    /// @}

    /// \name Target Renderer
    ///
    /// When set, the target renderer item describes that a node was designed
    /// for (but not necessarily limited to) a specific renderer.
    ///
    /// For example, if a node sets its target renderer to "FooRenderer",
    /// "BarRenderer" may still be able render the node partially, although
    /// this isn't guaranteed. "BarRenderer" can check the node's target
    /// renderer in addition to the node's shadingSystem for compatibility
    /// hints.
    ///
    /// If a node's target renderer is unspecified, renderers should
    /// rely on a node's shadingSystem as usual to determine compatibility.
    /// {@
    SDR_API
    bool HasTargetRenderer() const;
    SDR_API
    TfToken GetTargetRenderer() const;
    SDR_API
    void SetTargetRenderer(const TfToken& v);
    SDR_API
    void ClearTargetRenderer();
    /// @}

    /// \name Collections
    ///
    /// Collections provides a way to group nodes across different categories
    /// in the domain-subdomain-context-role-function hierarchy.
    ///
    /// For example, a parser plugin author may decide that all lama nodes
    /// should have "lama" in its Collections metadata. Or that all nodes that
    /// have something to do with subsurface scattering be annotated with a
    /// "subsurface" collection.
    /// {@
    SDR_API
    bool HasCollections() const;
    SDR_API
    SdrTokenVec GetCollections() const;
    SDR_API
    void SetCollections(const SdrTokenVec& v);
    SDR_API
    void ClearCollections();
    /// @}

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

    /// \name Departments
    ///
    /// Deprecated metadata item.
    ///
    /// \deprecated
    /// {@

    /// \deprecated
    SDR_API
    bool HasDepartments() const;
    /// \deprecated
    SDR_API
    SdrTokenVec GetDepartments() const;
    /// \deprecated
    SDR_API
    void SetDepartments(const SdrTokenVec& v);
    /// \deprecated
    SDR_API
    void ClearDepartments();
    /// @}

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

    // This method sets metadata items that have default values
    // as indicated by GetDefaultValues.
    //
    // Note: This method is invoked at the ends of this class's
    // constructors.
    void _SetDefaultInitializations();

    VtDictionary _items;
};

PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_USD_SDR_SHADER_NODE_METADATA_H
