#include "TransformManipulator.h"
#include "../utils/Logger.h"
#include "../core/CommandHistory.h"
#include "../core/commands/TransformCommand.h"

#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/editContext.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include <pxr/usd/usdGeom/xformable.h>
#include <pxr/usd/usdGeom/xformCache.h>
#include <pxr/usd/usdGeom/xformOp.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/matrix4f.h>
#include <pxr/base/gf/vec3d.h>
#include <pxr/base/gf/rotation.h>
#include <pxr/base/gf/math.h>
#include <pxr/base/gf/line.h>
#include <pxr/base/gf/lineSeg.h>
#include <pxr/base/gf/plane.h>
#include <pxr/base/gf/ray.h>
#include <pxr/base/vt/value.h>

#include <cmath>
#include <algorithm>
#include <vector>
#include <memory>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace UsdLayerManager {

// ---------------------------------------------------------------------------
// Colours / constants
// ---------------------------------------------------------------------------
static const ImU32 kColX      = IM_COL32(214,  38,  38, 255);
static const ImU32 kColY      = IM_COL32( 38, 179,  38, 255);
static const ImU32 kColZ      = IM_COL32( 38,  90, 220, 255);
static const ImU32 kColHover  = IM_COL32(255, 128,  16, 255);
static const ImU32 kColCenter = IM_COL32(255, 255, 255, 220);
static const ImU32 kAxisColors[3] = { kColX, kColY, kColZ };

static constexpr float kTranslationLineThick = 3.0f;
static constexpr float kRotationLineThick    = 2.0f;
static constexpr float kScaleLineThick       = 3.0f;
static constexpr float kScaleCircleRadius    = 5.0f;
static constexpr float kCenterCircleRadius   = 5.0f;

// ============================================================================
// SetupCompatibleXform
//
// Fast path  : prim already has XformCommonAPI-compatible ops -> read T/R/S.
// Fallback   : decompose the composed local matrix and insert a compatible
//              op-stack (with !resetXformStack!) in the current edit layer.
//              This handles prims from references that use separate rotateX/Y/Z
//              or matrix ops without touching the reference layer.
//
// Mirrors usdtweak's per-prim check: if (xformAPI) { ... } else { ... }
// but rather than falling back to a single matrix op, we create compatible
// standard TRS ops (better for layer-based override workflows).
// ============================================================================
static bool SetupCompatibleXform(
    const pxr::UsdStageRefPtr&                 stage,
    const pxr::SdfPath&                        primPath,
    pxr::UsdTimeCode                           time,
    pxr::GfVec3d&                              outT,
    pxr::GfVec3f&                              outR,
    pxr::GfVec3f&                              outS,
    pxr::UsdGeomXformCommonAPI::RotationOrder& outRotOrder)
{
    pxr::UsdPrim prim = stage->GetPrimAtPath(primPath);
    if (!prim) return false;

    pxr::UsdGeomXformCommonAPI api(prim);
    pxr::GfVec3f pivot3f;

    if (api.GetXformVectors(&outT, &outR, &outS, &pivot3f,
                            &outRotOrder, time))
        return true;

    // Fallback -----------------------------------------------------------------
    LOG_WARNING("Prim " + primPath.GetString() +
                " has an incompatible xform stack. Creating a compatible "
                "override in the edit layer.");

    pxr::UsdGeomXformCache xc(time);
    bool reset = false;
    pxr::GfMatrix4d localMat = xc.GetLocalTransformation(prim, &reset);

    pxr::GfMatrix4d rotMat, shearMat, projMat;
    pxr::GfVec3d    scale3d, translate3d;
    if (!localMat.Factor(&rotMat, &scale3d, &shearMat, &translate3d, &projMat)) {
        rotMat = pxr::GfMatrix4d(1); scale3d = {1,1,1}; translate3d = {0,0,0};
    }

    double tw = 0, fb = 0, lr = 0, sw = 0;
    pxr::GfRotation::DecomposeRotation(
        rotMat, {1,0,0}, {0,1,0}, {0,0,1}, 1.0, &tw, &fb, &lr, &sw, false);

    outT = translate3d;
    outR = { float(pxr::GfRadiansToDegrees(tw)),
             float(pxr::GfRadiansToDegrees(fb)),
             float(pxr::GfRadiansToDegrees(lr)) };
    outS = { float(scale3d[0]), float(scale3d[1]), float(scale3d[2]) };
    outRotOrder = pxr::UsdGeomXformCommonAPI::RotationOrderXYZ;

    {
        pxr::UsdEditContext ec(stage, stage->GetEditTarget());
        pxr::UsdGeomXformable xf(prim);
        xf.SetResetXformStack(true);
        pxr::UsdGeomXformCommonAPI fresh(prim);
        fresh.SetTranslate(outT, pxr::UsdTimeCode::Default());
        fresh.SetRotate   (outR, outRotOrder, pxr::UsdTimeCode::Default());
        fresh.SetScale    (outS, pxr::UsdTimeCode::Default());
    }
    return true;
}

// ============================================================================
// Stage / selection
// ============================================================================
void TransformManipulator::SetStage(pxr::UsdStageRefPtr stage)
{
    m_stage = stage; m_primPath = pxr::SdfPath(); m_isDragging = false;
}

void TransformManipulator::SetSelectedPrim(const pxr::SdfPath& path)
{
    m_primPath = path; m_isDragging = false;
}

// ============================================================================
// GetGizmoAxes
// World  mode: fixed {1,0,0}, {0,1,0}, {0,0,1}
// Object mode: rows 0..2 of local-to-world matrix (normalised world-space
//              local axis directions — mirrors usdtweak's
//              ComputeManipulatorToWorldTransform().GetRow3(i))
// ============================================================================
void TransformManipulator::GetGizmoAxes(pxr::GfVec3d axes[3]) const
{
    axes[0] = {1,0,0}; axes[1] = {0,1,0}; axes[2] = {0,0,1};
    if (m_transformSpace == TransformSpace::World) return;
    if (!m_stage || m_primPath.IsEmpty()) return;
    pxr::UsdPrim prim = m_stage->GetPrimAtPath(m_primPath);
    if (!prim) return;
    pxr::UsdGeomXformCache xc(m_displayTime);
    pxr::GfMatrix4d l2w = xc.GetLocalToWorldTransform(prim);
    for (int i = 0; i < 3; ++i) {
        pxr::GfVec3d row(l2w[i][0], l2w[i][1], l2w[i][2]);
        double n = row.GetLength();
        if (n > 1e-9) axes[i] = row / n;
    }
}

// ============================================================================
// WorldToScreen   p_clip = (p_world, 1) * VP   (USD row-vector convention)
// ============================================================================
bool TransformManipulator::WorldToScreen(const pxr::GfVec3d& world,
                                          const pxr::GfMatrix4d& vp,
                                          int vW, int vH,
                                          const ImVec2& imgPos, ImVec2& out)
{
    double cx = vp[0][0]*world[0]+vp[1][0]*world[1]+vp[2][0]*world[2]+vp[3][0];
    double cy = vp[0][1]*world[0]+vp[1][1]*world[1]+vp[2][1]*world[2]+vp[3][1];
    double cw = vp[0][3]*world[0]+vp[1][3]*world[1]+vp[2][3]*world[2]+vp[3][3];
    if (cw <= 0.0) return false;
    double iw = 1.0/cw;
    out = ImVec2(imgPos.x + float((cx*iw+1.0)*0.5*vW),
                 imgPos.y + float((1.0-cy*iw)*0.5*vH));
    return true;
}

// ============================================================================
// ComputeMouseRay   p_world = p_clip * VP^{-1}
// ============================================================================
pxr::GfRay TransformManipulator::ComputeMouseRay(const pxr::GfMatrix4d& vp,
                                                   const ImVec2& mouse,
                                                   const ImVec2& imgPos,
                                                   int vW, int vH)
{
    const pxr::GfRay kFB(pxr::GfVec3d(0), pxr::GfVec3d(0,0,-1));
    float nx =  2.f*(mouse.x-imgPos.x)/float(vW)-1.f;
    float ny =  1.f-2.f*(mouse.y-imgPos.y)/float(vH);
    double det = 0;
    pxr::GfMatrix4d vi = vp.GetInverse(&det);
    if (std::abs(det) < 1e-12) return kFB;
    auto up = [&](double z) -> pxr::GfVec3d {
        double wx=nx*vi[0][0]+ny*vi[1][0]+z*vi[2][0]+vi[3][0];
        double wy=nx*vi[0][1]+ny*vi[1][1]+z*vi[2][1]+vi[3][1];
        double wz=nx*vi[0][2]+ny*vi[1][2]+z*vi[2][2]+vi[3][2];
        double ww=nx*vi[0][3]+ny*vi[1][3]+z*vi[2][3]+vi[3][3];
        if (std::abs(ww)<1e-12) return pxr::GfVec3d(0);
        return {wx/ww, wy/ww, wz/ww};
    };
    pxr::GfVec3d dir = up(1.0)-up(-1.0);
    double n = dir.GetLength();
    if (n < 1e-9) return kFB;
    return pxr::GfRay(up(-1.0), dir/n);
}

// ============================================================================
// ComputeScreenFactor  (ImGuizmo constant-apparent-size formula)
// ============================================================================
float TransformManipulator::ComputeScreenFactor(const pxr::GfMatrix4d& vp,
                                                 const pxr::GfVec3d& pivot,
                                                 int vW, int vH, float frac)
{
    double pw=vp[0][3]*pivot[0]+vp[1][3]*pivot[1]+vp[2][3]*pivot[2]+vp[3][3];
    if (pw<=0) return 1.f;
    double iw=1/pw;
    double px=(vp[0][0]*pivot[0]+vp[1][0]*pivot[1]+vp[2][0]*pivot[2]+vp[3][0])*iw;
    double py=(vp[0][1]*pivot[0]+vp[1][1]*pivot[1]+vp[2][1]*pivot[2]+vp[3][1])*iw;
    float dr=(float)vW/(float)std::max(vH,1), mx=0;
    const pxr::GfVec3d kA[3]={{1,0,0},{0,1,0},{0,0,1}};
    for (auto& ax : kA) {
        pxr::GfVec3d t=pivot+ax;
        double tw=vp[0][3]*t[0]+vp[1][3]*t[1]+vp[2][3]*t[2]+vp[3][3];
        if (tw<=0) continue;
        double ti=1/tw;
        float dx=float((vp[0][0]*t[0]+vp[1][0]*t[1]+vp[2][0]*t[2]+vp[3][0])*ti-px);
        float dy=float((vp[0][1]*t[0]+vp[1][1]*t[1]+vp[2][1]*t[2]+vp[3][1])*ti-py);
        if (dr<1) dx*=dr; else dy/=dr;
        mx=std::max(mx,std::sqrt(dx*dx+dy*dy));
    }
    return (mx<1e-6f)?1.f:frac/mx;
}

// ============================================================================
// PointToSegmentDist
// ============================================================================
float TransformManipulator::PointToSegmentDist(ImVec2 p, ImVec2 a, ImVec2 b)
{
    float dx=b.x-a.x,dy=b.y-a.y,lsq=dx*dx+dy*dy;
    if (lsq<1e-6f){float ex=p.x-a.x,ey=p.y-a.y;return std::sqrt(ex*ex+ey*ey);}
    float t=std::max(0.f,std::min(1.f,((p.x-a.x)*dx+(p.y-a.y)*dy)/lsq));
    float cx=a.x+t*dx-p.x,cy=a.y+t*dy-p.y;
    return std::sqrt(cx*cx+cy*cy);
}

// ============================================================================
// HitTestAxes  (Move / Scale)
// ============================================================================
int TransformManipulator::HitTestAxes(const pxr::GfMatrix4d& vp,
                                       const pxr::GfVec3d& pivot, float sf,
                                       const ImVec2& imgPos, int vW, int vH,
                                       const ImVec2& mouse,
                                       const pxr::GfVec3d axes[3]) const
{
    ImVec2 pivSS;
    if (!WorldToScreen(pivot, vp, vW, vH, imgPos, pivSS)) return -1;
    float best=10.f; int hit=-1;
    for (int i=0;i<3;++i) {
        ImVec2 tipSS;
        if (!WorldToScreen(pivot+axes[i]*sf,vp,vW,vH,imgPos,tipSS)) continue;
        float d=PointToSegmentDist(mouse,pivSS,tipSS);
        if (d<best){best=d;hit=i;}
    }
    return hit;
}

// ============================================================================
// HitTestRotateRings  (front-facing half-arc proximity)
// ============================================================================
int TransformManipulator::HitTestRotateRings(const pxr::GfMatrix4d& vp,
                                              const pxr::GfVec3d& pivot, float sf,
                                              const pxr::GfVec3d& camEye,
                                              const ImVec2& imgPos, int vW, int vH,
                                              const ImVec2& mouse,
                                              const pxr::GfVec3d axes[3]) const
{
    static constexpr int   kSeg=32;
    static constexpr float kDF=1.2f;
    float r=sf*kDF;
    pxr::GfVec3d c2s=pivot-camEye;
    double l=c2s.GetLength();
    if (l<1e-9) c2s={0,0,-1}; else c2s/=l;
    float best=10.f; int hit=-1;
    for (int axis=0;axis<3;++axis) {
        const pxr::GfVec3d& u=(axis==0)?axes[1]:axes[0];
        const pxr::GfVec3d& v=(axis<2) ?axes[2]:axes[1];
        float ap=float(c2s[0]*u[0]+c2s[1]*u[1]+c2s[2]*u[2]);
        float bp=float(c2s[0]*v[0]+c2s[1]*v[1]+c2s[2]*v[2]);
        float as=std::atan2(bp,ap)+float(M_PI)*0.5f;
        ImVec2 prev; bool hp=false;
        for (int s=0;s<=kSeg;++s) {
            float a=as+float(M_PI)*(float(s)/float(kSeg));
            pxr::GfVec3d p=pivot+u*(r*std::cos(a))+v*(r*std::sin(a));
            ImVec2 ss;
            if (!WorldToScreen(p,vp,vW,vH,imgPos,ss)){hp=false;continue;}
            if (hp){float d=PointToSegmentDist(mouse,prev,ss);if(d<best){best=d;hit=axis;}}
            prev=ss; hp=true;
        }
    }
    return hit;
}

// ============================================================================
// Draw helpers
// ============================================================================
void TransformManipulator::DrawMoveGizmo(ImDrawList* dl, const pxr::GfMatrix4d& vp,
                                          const pxr::GfVec3d& pivot, float sf,
                                          const ImVec2& imgPos, int vW, int vH,
                                          const pxr::GfVec3d axes[3])
{
    ImVec2 pivSS;
    if (!WorldToScreen(pivot,vp,vW,vH,imgPos,pivSS)) return;
    for (int i=0;i<3;++i) {
        ImU32 col=(i==m_dragAxis||i==m_hoveredAxis)?kColHover:kAxisColors[i];
        ImVec2 shSS,tipSS;
        if (!WorldToScreen(pivot+axes[i]*sf*0.78f,vp,vW,vH,imgPos,shSS)) continue;
        if (!WorldToScreen(pivot+axes[i]*sf,       vp,vW,vH,imgPos,tipSS)) continue;
        dl->AddLine(pivSS,shSS,col,kTranslationLineThick);
        float adx=tipSS.x-shSS.x,ady=tipSS.y-shSS.y,al=std::sqrt(adx*adx+ady*ady);
        if (al<1.f) continue;
        float px=-ady/al,py=adx/al;
        float tl=std::sqrt((tipSS.x-pivSS.x)*(tipSS.x-pivSS.x)+(tipSS.y-pivSS.y)*(tipSS.y-pivSS.y));
        float hw=tl*0.12f;
        dl->AddTriangleFilled(tipSS,{shSS.x+px*hw,shSS.y+py*hw},{shSS.x-px*hw,shSS.y-py*hw},col);
    }
    dl->AddCircleFilled(pivSS,kCenterCircleRadius,kColCenter,16);
}

void TransformManipulator::DrawRotateGizmo(ImDrawList* dl, const pxr::GfMatrix4d& vp,
                                            const pxr::GfVec3d& pivot, float sf,
                                            const pxr::GfVec3d& camEye,
                                            const ImVec2& imgPos, int vW, int vH,
                                            const pxr::GfVec3d axes[3])
{
    static constexpr int kSeg=64; static constexpr float kDF=1.2f;
    float r=sf*kDF;
    pxr::GfVec3d c2s=pivot-camEye;
    double cl=c2s.GetLength();
    if (cl<1e-9) c2s={0,0,-1}; else c2s/=cl;
    for (int axis=0;axis<3;++axis) {
        ImU32 col=(axis==m_dragAxis||axis==m_hoveredAxis)?kColHover:kAxisColors[axis];
        float lw=(axis==m_dragAxis||axis==m_hoveredAxis)?kRotationLineThick+1.5f:kRotationLineThick;
        const pxr::GfVec3d& u=(axis==0)?axes[1]:axes[0];
        const pxr::GfVec3d& v=(axis<2) ?axes[2]:axes[1];
        float ap=float(c2s[0]*u[0]+c2s[1]*u[1]+c2s[2]*u[2]);
        float bp=float(c2s[0]*v[0]+c2s[1]*v[1]+c2s[2]*v[2]);
        float as=std::atan2(bp,ap)+float(M_PI)*0.5f;
        std::vector<ImVec2> pts; pts.reserve(kSeg+1);
        for (int s=0;s<=kSeg;++s) {
            float a=as+float(M_PI)*(float(s)/float(kSeg));
            pxr::GfVec3d p=pivot+u*(r*std::cos(a))+v*(r*std::sin(a));
            ImVec2 ss;
            if (WorldToScreen(p,vp,vW,vH,imgPos,ss)) pts.push_back(ss);
        }
        if (pts.size()>1)
            dl->AddPolyline(pts.data(),int(pts.size()),col,ImDrawFlags_None,lw);
    }
}

void TransformManipulator::DrawScaleGizmo(ImDrawList* dl, const pxr::GfMatrix4d& vp,
                                           const pxr::GfVec3d& pivot, float sf,
                                           const ImVec2& imgPos, int vW, int vH,
                                           const pxr::GfVec3d axes[3])
{
    ImVec2 pivSS;
    if (!WorldToScreen(pivot,vp,vW,vH,imgPos,pivSS)) return;
    for (int i=0;i<3;++i) {
        ImU32 col=(i==m_dragAxis||i==m_hoveredAxis)?kColHover:kAxisColors[i];
        ImVec2 tipSS;
        if (!WorldToScreen(pivot+axes[i]*sf,vp,vW,vH,imgPos,tipSS)) continue;
        dl->AddLine(pivSS,tipSS,col,kScaleLineThick);
        dl->AddCircleFilled(tipSS,kScaleCircleRadius,col,16);
    }
    // Center dot: axis 3 = uniform scale handle
    bool centerActive = (m_dragAxis==3||m_hoveredAxis==3);
    ImU32 centerCol   = centerActive ? kColHover : kColCenter;
    float centerR     = centerActive ? kCenterCircleRadius+3.f : kCenterCircleRadius+1.f;
    dl->AddCircleFilled(pivSS,centerR,centerCol,16);
}

// ============================================================================
// Render
// ============================================================================
void TransformManipulator::Render(ImDrawList* dl, const pxr::GfMatrix4d& vp,
                                   const pxr::GfVec3d& pivot,
                                   const pxr::GfVec3d& camEye,
                                   const ImVec2& imgPos, int vW, int vH)
{
    if (m_mode==ManipulatorMode::Select) return;
    if (!m_stage||m_primPath.IsEmpty()||!dl||vW<=0||vH<=0) return;
    float sf=ComputeScreenFactor(vp,pivot,vW,vH,0.15f);
    pxr::GfVec3d axes[3]; GetGizmoAxes(axes);
    switch (m_mode) {
        case ManipulatorMode::Move:   DrawMoveGizmo  (dl,vp,pivot,sf,imgPos,vW,vH,axes); break;
        case ManipulatorMode::Rotate: DrawRotateGizmo(dl,vp,pivot,sf,camEye,imgPos,vW,vH,axes); break;
        case ManipulatorMode::Scale:  DrawScaleGizmo (dl,vp,pivot,sf,imgPos,vW,vH,axes); break;
        default: break;
    }
}

// ============================================================================
// HandleInput
//
// Full port of usdtweak's three manipulators with World/Object space switch.
//
// TRANSLATE  (PositionManipulator::OnUpdate)
//   GfFindClosestPoints(mouseRay, axisLine) -> signed scalar delta along axis.
//   Applied ABSOLUTE each frame: newTranslate = origTranslate + parentSpaceDelta.
//   Parent-space conversion: w2p.TransformDir(worldDeltaVec).
//
// ROTATE  (RotationManipulator::OnUpdate + space switch)
//   Clock-hand: intersect mouse ray with ring plane (GfPlane) -> rotateTo vector.
//   worldRotation = GfRotation(dragStartClockHand, currentClockHand).
//   axisSign      = dot(planeNormal, worldRotation.GetAxis()) > 0 ? 1 : -1.
//   deltaAxis     = canonical axis e_a for the dragged ring (both spaces).
//   deltaRotation = GfRotation(deltaAxis * axisSign, angle).
//   resultingRotation:
//     Object => GfMatrix4d(1).SetRotate(delta) * initRot   [delta in prim local frame]
//     World  => initRot * GfMatrix4d(1).SetRotate(delta)   [delta about world axis]
//   DecomposeRotation with FIXED initRot rows as reference axes + current hints.
//
// SCALE  (ScaleManipulator::OnUpdate)
//   Same axis-line projection as Translate.
//   ratio = curT / originT (axis-line parameter, robust for off-origin pivots).
//   newScale[axis] = origScale[axis] * ratio  (absolute from drag start).
// ============================================================================
bool TransformManipulator::HandleInput(const pxr::GfMatrix4d& vp,
                                        const pxr::GfVec3d& pivot,
                                        const pxr::GfVec3d& camEye,
                                        const ImVec2& imgPos,
                                        int vW, int vH, bool hovered)
{
    if (m_mode==ManipulatorMode::Select) return false;
    if (!m_stage||m_primPath.IsEmpty())  return false;

    ImGuiIO& io=ImGui::GetIO();
    ImVec2 mouse=io.MousePos;
    float sf=ComputeScreenFactor(vp,pivot,vW,vH,0.15f);
    pxr::GfVec3d axes[3]; GetGizmoAxes(axes);

    // Hover update
    if (!m_isDragging && hovered) {
        if (m_mode==ManipulatorMode::Rotate) {
            m_hoveredAxis=HitTestRotateRings(vp,pivot,sf,camEye,imgPos,vW,vH,mouse,axes);
        } else {
            // Scale center check FIRST: the pivot is an endpoint of all three
            // axis segments, so HitTestAxes always hits an axis (typically X)
            // when the mouse is at the center.  Check the center dot before
            // falling through to the per-axis test.
            m_hoveredAxis=-1;
            if (m_mode==ManipulatorMode::Scale) {
                ImVec2 pivSS;
                if (WorldToScreen(pivot,vp,vW,vH,imgPos,pivSS)) {
                    float dx=mouse.x-pivSS.x, dy=mouse.y-pivSS.y;
                    if (std::sqrt(dx*dx+dy*dy)<kCenterCircleRadius+6.f)
                        m_hoveredAxis=3;
                }
            }
            if (m_hoveredAxis<0)
                m_hoveredAxis=HitTestAxes(vp,pivot,sf,imgPos,vW,vH,mouse,axes);
        }
    }

    bool consumed=false;

    // =========================================================================
    // BEGIN drag
    // =========================================================================
    if (hovered && !m_isDragging &&
        ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !io.KeyAlt)
    {
        int hit=-1;
        if (m_mode==ManipulatorMode::Rotate) {
            hit=HitTestRotateRings(vp,pivot,sf,camEye,imgPos,vW,vH,mouse,axes);
        } else {
            // Scale center check before per-axis test (same reason as hover).
            if (m_mode==ManipulatorMode::Scale) {
                ImVec2 pivSS;
                if (WorldToScreen(pivot,vp,vW,vH,imgPos,pivSS)) {
                    float dx=mouse.x-pivSS.x, dy=mouse.y-pivSS.y;
                    if (std::sqrt(dx*dx+dy*dy)<kCenterCircleRadius+6.f)
                        hit=3;
                }
            }
            if (hit<0)
                hit=HitTestAxes(vp,pivot,sf,imgPos,vW,vH,mouse,axes);
        }

        if (hit>=0) {
            m_isDragging=true; m_dragAxis=hit; consumed=true;

            pxr::GfVec3d trans; pxr::GfVec3f rot,scale;
            pxr::UsdGeomXformCommonAPI::RotationOrder rotOrder;
            if (!SetupCompatibleXform(m_stage,m_primPath,m_displayTime,
                                      trans,rot,scale,rotOrder)) {
                m_isDragging=false; return false;
            }
            m_dragOriginalTranslate=trans; m_dragOriginalRotate=rot;
            m_dragOriginalScale=scale;     m_dragOriginalRotOrder=rotOrder;
            m_dragStartTranslate=trans;    m_dragStartRotate=rot;
            m_dragStartScale=scale;
            // Snapshot keyframe presence before live writes author samples.
            m_dragPreEditSamples=TransformCommand::QuerySamplePresence(
                m_stage,m_primPath,rotOrder,m_editTime);

            pxr::GfRay ray=ComputeMouseRay(vp,mouse,imgPos,vW,vH);

            if (m_mode==ManipulatorMode::Move) {
                // usdtweak: _axisLine = GfLine(pivot, objectTransform.GetRow3(axis))
                m_dragMoveAxisLine=pxr::GfLine(pivot,axes[m_dragAxis]);
                pxr::GfVec3d rpt;
                pxr::GfFindClosestPoints(ray,m_dragMoveAxisLine,&rpt,&m_dragMoveOriginOnAxis);
            }
            else if (m_mode==ManipulatorMode::Rotate) {
                // Ring plane in world space.
                // usdtweak: _planeNormal3d = manipCoords.GetRow3(_selectedAxis)
                m_dragRotatePlaneNormal=axes[m_dragAxis];
                pxr::GfPlane plane(m_dragRotatePlaneNormal,pivot);
                double dist=0;
                if (ray.Intersect(plane,&dist) && dist>0)
                    m_dragRotateFrom=pivot-ray.GetPoint(dist);
                else
                    m_dragRotateFrom=axes[(m_dragAxis+1)%3];

                // Capture initial rotation matrix (local->parent, fixed for whole drag).
                // usdtweak: _rotateMatrixOnBegin = GetOpTransform(opType, VtValue(rotation))
                pxr::UsdGeomXformOp::Type opType=
                    pxr::UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(rotOrder);
                m_dragRotateInitialRotMat=
                    pxr::UsdGeomXformOp::GetOpTransform(opType,pxr::VtValue(rot));

                // Capture delta axis and space mode at drag start.
                //
                // The delta axis is the CANONICAL axis e_a for the dragged ring
                // in BOTH spaces; only the composition order (below) differs.
                //
                //   Object: e_a applied in the prim's own local frame via
                //           deltaM * initRot.  Because Rot(e_a) fixes e_a, the
                //           prim's current local axis (e_a * initRot) stays put,
                //           so the object rotates purely about its own axis
                //           (Maya local mode).
                //   World : e_a applied after initRot via initRot * deltaM, i.e.
                //           rotation about the fixed world axis.
                //
                // NOTE: using initRot.GetRow3(axis) (a parent-space vector) with
                // deltaM * initRot is WRONG -- the axis is then applied in the
                // local frame it does not belong to, which makes the object
                // tumble once it already carries a rotation.
                static const pxr::GfVec3d kAxis[3]={{1,0,0},{0,1,0},{0,0,1}};
                m_dragRotateDeltaAxis=kAxis[m_dragAxis];
                m_dragRotateObjectSpace=(m_transformSpace==TransformSpace::Object);
            }
            else if (m_mode==ManipulatorMode::Scale) {
                if (m_dragAxis<3) {
                    // Single-axis scale: project onto world-space axis line
                    m_dragScaleAxisLine=pxr::GfLine(pivot,axes[m_dragAxis]);
                    pxr::GfVec3d rpt;
                    pxr::GfFindClosestPoints(ray,m_dragScaleAxisLine,&rpt,&m_dragScaleOriginOnAxis);
                } else {
                    // Uniform scale (axis 3): mouse starts at the center circle,
                    // so the axis-line origin parameter oT ≈ 0 and cT/oT blows up.
                    // Use screen-space pixel delta instead: store drag-start mouse
                    // position in m_dragScaleOriginOnAxis.x/y, compute ratio as
                    // 1 + deltaX * sensitivity each frame.
                    m_dragScaleOriginOnAxis={double(mouse.x),double(mouse.y),0.0};
                }
            }
        }
    }

    // =========================================================================
    // DRAG update
    // =========================================================================
    if (m_isDragging) {
        consumed=true;
        if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
            if (m_dragAxis<0||m_dragAxis>3) { m_isDragging=false;m_dragAxis=-1;return consumed; }

            pxr::GfRay ray=ComputeMouseRay(vp,mouse,imgPos,vW,vH);

            // -----------------------------------------------------------------
            // TRANSLATE
            // usdtweak: translation[_selectedAxis] += delta
            //           where delta = signed distance along axis line.
            // We extend to support both Object and World space via parent-space
            // conversion of the world-space delta vector.
            // -----------------------------------------------------------------
            if (m_mode==ManipulatorMode::Move) {
                pxr::GfVec3d rpt,cur;
                pxr::GfFindClosestPoints(ray,m_dragMoveAxisLine,&rpt,&cur);
                double oT=0,cT=0;
                m_dragMoveAxisLine.FindClosestPoint(m_dragMoveOriginOnAxis,&oT);
                m_dragMoveAxisLine.FindClosestPoint(cur,&cT);
                double delta=cT-oT;

                // World-space delta vector along the gizmo axis.
                pxr::GfVec3d worldDelta=axes[m_dragAxis]*delta;

                // Convert to parent space.
                // (For Object mode: axes[i] is the local axis in world space.
                //  w2p.TransformDir maps it to the local axis in parent space,
                //  which equals initRot.GetRow3(i). Adding this to the stored
                //  parent-space translation is geometrically correct.)
                pxr::GfVec3d parentDelta=worldDelta;
                pxr::UsdPrim prim=m_stage->GetPrimAtPath(m_primPath);
                if (prim) {
                    pxr::UsdGeomXformCache xc(m_displayTime);
                    pxr::UsdPrim parent=prim.GetParent();
                    if (parent && !parent.IsPseudoRoot()) {
                        pxr::GfMatrix4d p2w=xc.GetLocalToWorldTransform(parent);
                        double det=0; pxr::GfMatrix4d w2p=p2w.GetInverse(&det);
                        if (std::abs(det)>1e-9) parentDelta=w2p.TransformDir(worldDelta);
                    }
                }

                ApplyTranslate(m_dragOriginalTranslate+parentDelta);
            }

            // -----------------------------------------------------------------
            // ROTATE
            // Direct port of RotationManipulator::OnUpdate with space switch.
            // -----------------------------------------------------------------
            else if (m_mode==ManipulatorMode::Rotate) {
                pxr::GfPlane plane(m_dragRotatePlaneNormal,pivot);
                double dist=0;
                if (!ray.Intersect(plane,&dist)||dist<=0) return consumed;
                pxr::GfVec3d rotateTo=pivot-ray.GetPoint(dist);
                if (m_dragRotateFrom.GetLength()<1e-9||rotateTo.GetLength()<1e-9)
                    return consumed;

                // usdtweak: const GfRotation worldRotation(_rotateFrom, rotateTo)
                pxr::GfRotation worldRotation(m_dragRotateFrom,rotateTo);

                // usdtweak: axisSign = _planeNormal3d * worldRotation.GetAxis() > 0
                double axisSign=
                    (pxr::GfDot(m_dragRotatePlaneNormal,worldRotation.GetAxis())>0.0)
                    ?1.0:-1.0;

                // usdtweak: const GfRotation deltaRotation(localPlaneNormal*axisSign, angle)
                // deltaAxis is the canonical axis e_a for the dragged ring.
                pxr::GfRotation deltaRot(m_dragRotateDeltaAxis*axisSign,
                                         worldRotation.GetAngle());
                pxr::GfMatrix4d deltaM=pxr::GfMatrix4d(1.0).SetRotate(deltaRot);

                // resultingRotation:
                //   Object (rotate about prim's own axis): deltaM * initRot
                //   World  (rotate about fixed world axis): initRot * deltaM
                pxr::GfMatrix4d result=
                    m_dragRotateObjectSpace
                    ? deltaM * m_dragRotateInitialRotMat
                    : m_dragRotateInitialRotMat * deltaM;

                // Decompose `result` (the new total local rotation matrix) back
                // into XformCommonAPI Euler angles.
                //
                // SetRotate() interprets the written angles as rotations about the
                // CANONICAL x/y/z axes, so the decompose axes MUST be canonical.
                // The previous port used the rows of initRot here -- i.e. the prim's
                // already-rotated frame -- which equals the canonical axes only when
                // the prim is at zero rotation.  That mismatch is exactly why the
                // authored result desynced from the manipulator once the prim already
                // carried a rotation: the angles were measured in the rotated frame
                // but written as if they were canonical-frame angles.
                //
                // (Decomposition assumes RotationOrderXYZ -- the XformCommonAPI
                //  default; non-XYZ orders are mapped tw->X, fb->Y, lr->Z as before.)
                static const pxr::GfVec3d kCanonX(1,0,0), kCanonY(0,1,0), kCanonZ(0,0,1);

                double tw=pxr::GfDegreesToRadians(double(m_dragStartRotate[0]));
                double fb=pxr::GfDegreesToRadians(double(m_dragStartRotate[1]));
                double lr=pxr::GfDegreesToRadians(double(m_dragStartRotate[2]));
                double sw=0;

                pxr::GfRotation::DecomposeRotation(
                    result,kCanonX,kCanonY,kCanonZ,1.0,&tw,&fb,&lr,&sw,/*useHint=*/true);

                pxr::GfVec3f newRot(float(pxr::GfRadiansToDegrees(tw)),
                                    float(pxr::GfRadiansToDegrees(fb)),
                                    float(pxr::GfRadiansToDegrees(lr)));
                ApplyRotate(newRot,m_dragOriginalRotOrder);
                m_dragStartRotate=newRot;
            }

            // -----------------------------------------------------------------
            // SCALE
            // usdtweak: scale[_selectedAxis] = _scalesOnBegin[i][axis] * ratio
            //           ratio = mouseOnAxis.GetLength() / originOnAxis.GetLength()
            // We use axis-line T parameters (distance from pivot) which is more
            // robust when the pivot is not at the world origin.
            // -----------------------------------------------------------------
            else if (m_mode==ManipulatorMode::Scale) {
                pxr::GfVec3f ns=m_dragOriginalScale;
                if (m_dragAxis<3) {
                    // Single-axis: project onto world-space axis line.
                    pxr::GfVec3d rpt,cur;
                    pxr::GfFindClosestPoints(ray,m_dragScaleAxisLine,&rpt,&cur);
                    double oT=0,cT=0;
                    m_dragScaleAxisLine.FindClosestPoint(m_dragScaleOriginOnAxis,&oT);
                    m_dragScaleAxisLine.FindClosestPoint(cur,&cT);
                    double ratio=(std::abs(oT)>1e-6)?cT/oT:1.0;
                    ns[m_dragAxis]=std::max(0.001f,float(double(m_dragOriginalScale[m_dragAxis])*ratio));
                } else {
                    // Uniform scale: screen-space pixel delta → ratio.
                    // Right drag = grow, left drag = shrink.
                    // 200px right ≈ ×2 (sensitivity 0.005 per pixel).
                    double deltaX=double(mouse.x)-m_dragScaleOriginOnAxis[0];
                    double ratio=std::max(0.001,1.0+deltaX*0.005);
                    for (int i=0;i<3;++i)
                        ns[i]=std::max(0.001f,float(double(m_dragOriginalScale[i])*ratio));
                }
                ApplyScale(ns); m_dragStartScale=ns;
            }

        } else {
            // Mouse released.
            bool moved=
                (m_dragStartTranslate!=m_dragOriginalTranslate)||
                (m_dragStartRotate   !=m_dragOriginalRotate   )||
                (m_dragStartScale    !=m_dragOriginalScale    );
            if (moved && m_commandHistory && m_stage && !m_primPath.IsEmpty()) {
                auto cmd=std::make_unique<TransformCommand>(
                    m_stage, m_primPath,
                    m_stage->GetEditTarget().GetLayer(),
                    m_dragOriginalTranslate,m_dragOriginalRotate,m_dragOriginalScale,
                    m_dragStartTranslate,   m_dragStartRotate,   m_dragStartScale,
                    m_dragOriginalRotOrder, m_editTime, m_dragPreEditSamples,
                    "Transform "+m_primPath.GetName());
                m_commandHistory->Push(std::move(cmd));
            }
            m_isDragging=false; m_dragAxis=-1;
        }
    }
    return consumed;
}

// ============================================================================
// USD write helpers — write ABSOLUTE values
// ============================================================================
void TransformManipulator::ApplyTranslate(const pxr::GfVec3d& t)
{
    if (!m_stage||m_primPath.IsEmpty()) return;
    pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
    pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
    pxr::UsdGeomXformCommonAPI(p).SetTranslate(t,m_editTime);
    m_dragStartTranslate=t;
}

void TransformManipulator::ApplyRotate(const pxr::GfVec3f& r,
                                        pxr::UsdGeomXformCommonAPI::RotationOrder ro)
{
    if (!m_stage||m_primPath.IsEmpty()) return;
    pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
    pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
    pxr::UsdGeomXformCommonAPI(p).SetRotate(r,ro,m_editTime);
    m_dragStartRotate=r;
}

void TransformManipulator::ApplyScale(const pxr::GfVec3f& s)
{
    if (!m_stage||m_primPath.IsEmpty()) return;
    pxr::UsdPrim p=m_stage->GetPrimAtPath(m_primPath); if (!p) return;
    pxr::UsdEditContext ec(m_stage,m_stage->GetEditTarget());
    pxr::UsdGeomXformCommonAPI(p).SetScale(s,m_editTime);
    m_dragStartScale=s;
}

} // namespace UsdLayerManager
