#pragma once

#include "../core/UsdSceneRenderer.h"
#include "../core/ViewportCamera.h"
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/base/gf/bbox3d.h>
#include <cstdint>
#include <string>

namespace UsdLayerManager {

/// Shader-ball preview: mirrors the material currently open in the Material
/// Editor onto a scratch sphere and renders it through an independent Hydra
/// instance, with its own renderer-delegate switch (mirrors the viewport's
/// dropdown so the swatch can preview Storm, Cycles, etc. independently of
/// the main viewport's choice).
class MaterialPreviewRenderer {
public:
    /// Mirrors the material subtree at materialPath (composed via a reference
    /// to sourceStage's root layer) onto the internal scratch stage and binds
    /// it to the preview sphere. graphRevision is a cheap caller-computed
    /// hash of the current node/link graph + values — compared against the
    /// last call so unrelated frames don't pay for a re-render (real cost
    /// with a path-traced delegate).
    ///
    /// previewNodePath (optional, Hypershade-style): the shader node selected
    /// in the graph; the ball then previews that node's output instead of the
    /// material's own surface. Terminal (token-typed) outputs are connected
    /// as the surface directly; pattern outputs are routed into the
    /// diffuseColor of a scratch UsdPreviewSurface. Pass an empty path to
    /// preview the whole material.
    void SetMaterial(const pxr::UsdStageRefPtr& sourceStage,
                     const pxr::SdfPath& materialPath,
                     size_t graphRevision,
                     const pxr::SdfPath& previewNodePath = pxr::SdfPath(),
                     const std::string& previewNodeOutput = std::string(),
                     bool previewOutputIsTerminal = false);

    /// Renders only if something changed since the last call. Returns the GL
    /// color texture ID suitable for ImGui::Image().
    uint32_t Render(int width, int height);

    /// Click-drag orbit input, in pixel deltas (mirrors Maya's swatch orbit).
    void OrbitDrag(float deltaX, float deltaY);

    void SetColorCorrection(int ccMode, const std::string& ocioDisplay,
                            const std::string& ocioView,
                            const std::string& ocioColorSpace,
                            const std::string& ocioLook);

    /// Switches the shader-ball's render delegate (what RenderRendererDropdown
    /// does on click). No-op if already this plugin. Exposed so other
    /// consumers of a material preview (e.g. NodeThumbnailCache's in-canvas
    /// thumbnails) can mirror this renderer's current choice.
    void SetRendererPlugin(const pxr::TfToken& pluginId);
    /// Current render delegate (empty until EnsureInitialized() has run).
    pxr::TfToken GetRendererPlugin() const;

    /// True once the last Render() call's image is final. Progressive
    /// delegates (Arnold/Cycles/Embree) return false for many calls after a
    /// material/renderer change while they accumulate samples — callers that
    /// cache the rendered texture (e.g. NodeThumbnailCache) must keep
    /// re-rendering until this is true, or they'll cache an early noisy (or
    /// still-black) frame.
    bool IsConverged() const { return m_renderer.IsConverged(); }

    /// Renderer-delegate picker UI (matches the viewport's dropdown pattern).
    /// width: ImGui item width (-1 = fill available).
    void RenderRendererDropdown(float width = -1.0f);

    /// HDR-environment picker (External / Room / Interior / Sunset). Presets
    /// map to bundled resources/hdri/*.exr dome-light textures; a preset
    /// whose file is missing falls back to the distant key light.
    /// width: ImGui item width (-1 = fill available).
    void RenderLightingDropdown(float width = -1.0f);

    /// Preview-geometry picker (Sphere / Cube / Cylinder / Teapot / Hair /
    /// Cloud Volume). Shapes whose asset is missing are omitted.
    /// width: ImGui item width (-1 = fill available).
    void RenderShapeDropdown(float width = -1.0f);

private:
    void EnsureInitialized();
    void ApplyLightPreset(int index);
    /// Activates the chosen preview shape (deactivating the others) and
    /// reframes the camera to its bounds.
    void ApplyPreviewShape(int index);
    /// Compensates the dome-light pole convention per render delegate: Storm
    /// samples with the pole along local +Y, spec-following delegates
    /// (hdArnold, hdCycles) along local +Z. Called on preset and renderer
    /// switches so the HDR environment reads identically everywhere.
    void UpdateDomeOrientation();

    pxr::UsdStageRefPtr m_previewStage;
    UsdSceneRenderer     m_renderer;
    ViewportCamera       m_camera;
    pxr::GfBBox3d        m_previewBBox;

    pxr::SdfPath m_materialPath;          // path (identical in source & scratch stage)
    size_t       m_lastGraphRevision = 0;
    pxr::SdfPath m_previewNodePath;       // node whose output the ball previews (empty = whole material)
    std::string  m_previewNodeOutput;
    int          m_lightPreset = 0;
    int          m_previewShape = 0;
    bool         m_initialized = false;
    bool         m_dirty = true;
    int          m_lastWidth = 0;
    int          m_lastHeight = 0;
};

} // namespace UsdLayerManager
