// editor_input_curve.cpp — the velocity-curve popup's input: the modal click routing,
// node grab/add/Alt-delete inside the curve box, the live node drag, the right-click
// delete, and the popup's hover. Band-independent (the sheet floats over the whole face).
// Windows-only.

#include "shell/instrument/reasampler_editor.h"

#ifdef _WIN32

#include "core/instrument/ui/curve_popup.h"     // computeCurvePopup / popupOutsideSheet
#include "shell/instrument/editor_internal.h"   // curveBoxFromRect
#include "shell/instrument/reasampler_processor.h"

namespace reasampler::vst {

using namespace reasampler::ui;
using namespace reasampler::instrument::ui;

bool ReaSamplerEditor::handlePopupMouseDown(int w, int h, int x, int y) {
    // While open the sheet is modal over the face — it owns every left-click. Close click /
    // outside-wash click dismiss (outside only when no drag is in flight); in-box clicks
    // route to the curve machinery; anything else on the sheet is swallowed.
    if (!curvePopupOpen_) return false;
    const CurvePopupLayout pl = computeCurvePopup(w, h);
    if (contains(pl.close, x, y)) {
        curvePopupOpen_ = false;
        invalidate();
        return true;
    }
    if (contains(pl.curveBox, x, y)) {
        handleCurveMouseDown(pl.curveBox, x, y);
        return true;
    }
    if (popupOutsideSheet(pl, x, y) && drag_ == DragKind::kNone) {
        curvePopupOpen_ = false;
        invalidate();
    }
    return true;
}

void ReaSamplerEditor::handleCurveMouseDown(const Rect& r, int x, int y) {
    const VelocityCurve::Box box = curveBoxFromRect(r);
    if (box.width <= 0 || box.height <= 1) return;

    int idx = params_.velocityCurve.pointAtPixel(box, x, y);

    // Modifier-click (Alt) deletes an interior node — a discrete, final edit committed at once
    // (deletePoint refuses the two endpoints, so an Alt-click on them is a safe no-op).
    if (idx >= 0 && (GetKeyState(VK_MENU) & 0x8000) != 0) {
        if (params_.velocityCurve.deletePoint(static_cast<std::size_t>(idx))) {
            hover_ = HoverTarget{};  // a stale kCurveNode index would light a shifted node
            commitAndReload();
        }
        return;
    }

    // Snapshot BEFORE any mutation so a capture-loss rollback also cancels an in-flight ADD
    // (mirror of the other parameter-editing drags' dragStartParams_ contract).
    dragStartParams_ = params_;

    // Empty-space click inside the mapping box: add a control point via the pure inverse map,
    // then grab it. Box-gated (not just contains(r,x,y)) because the inset ring must not add a
    // point — it would clamp to velocity 0/127, stacking an undeletable duplicate on an
    // endpoint. A ring click can still grab an existing node (handled above).
    if (idx < 0) {
        const bool inBox = (x >= box.left && x < box.left + box.width &&
                            y >= box.top  && y < box.top  + box.height);
        if (inBox) {
            const VelocityPoint p = VelocityCurve::pointFromPixel(box, x, y);
            idx = static_cast<int>(params_.velocityCurve.addPoint(p.velocity, p.amp));
        }
    }

    if (idx < 0) return;  // ring click with no node hit — nothing to grab

    drag_ = DragKind::kCurveNode;
    curvePointIndex_ = idx;
    dragStartCurve_ = params_.velocityCurve;  // AFTER the add — resolvePointDrag's delta base
    dragCurveRect_ = r;
    dragStartX_ = x;
    dragStartY_ = y;
    invalidate();  // live feedback; the commit lands on WM_LBUTTONUP
}

void ReaSamplerEditor::dragCurve(int x, int y) {
    // Resolve the grabbed control point from the pixel delta through the pure inverse map
    // (box + neighbour-X + endpoint-pin clamps), against the grab-time curve + box (absolute
    // delta — the mirror of the envelope-node drag). Live feedback only; commit on release.
    if (curvePointIndex_ < 0) return;
    params_.velocityCurve = VelocityCurve::resolvePointDrag(
        dragStartCurve_, static_cast<std::size_t>(curvePointIndex_),
        curveBoxFromRect(dragCurveRect_), x - dragStartX_, y - dragStartY_);
    invalidate();
}

void ReaSamplerEditor::onMouseRDown(int x, int y) {
    // Right-click on a popup curve node deletes it — the primary delete affordance; Alt-click
    // and drag-off remain as landed alternates. Commits immediately through the same path as
    // Alt-click; deletePoint's endpoint guard makes an endpoint right-click a safe no-op.
    // Right-clicks act only while the popup is open, and never during an in-flight left drag.
    if (!processor_ || view_ == View::kBrowse || !curvePopupOpen_) return;
    if (drag_ != DragKind::kNone) return;
    RECT rc{};
    GetClientRect(childHwnd_, &rc);
    const CurvePopupLayout pl = computeCurvePopup(rc.right - rc.left, rc.bottom - rc.top);
    if (!contains(pl.curveBox, x, y)) return;
    const VelocityCurve::Box box = curveBoxFromRect(pl.curveBox);
    const int idx = params_.velocityCurve.pointAtPixel(box, x, y);
    if (idx < 0) return;
    if (params_.velocityCurve.deletePoint(static_cast<std::size_t>(idx))) {
        hover_ = HoverTarget{};  // a stale kCurveNode index would light a shifted node
        commitAndReload();
    }
}

ReaSamplerEditor::HoverTarget ReaSamplerEditor::hoverCurvePopup(int w, int h, int x,
                                                                int y) const {
    const CurvePopupLayout pl = computeCurvePopup(w, h);
    if (contains(pl.close, x, y)) return {HoverKind::kPopupClose, -1};
    if (!contains(pl.curveBox, x, y)) return {};
    // A curve node under the pointer lights accent-hot.
    const int idx =
        params_.velocityCurve.pointAtPixel(curveBoxFromRect(pl.curveBox), x, y);
    if (idx < 0) return {};
    return {HoverKind::kCurveNode, idx};
}

}  // namespace reasampler::vst

#endif  // _WIN32
