// envelope_edit.h — node hit-test + pixel-delta -> clamped-param inverse map for the draggable
// envelope nodes and their mid-segment curve knots. The inverse of envelope_overlay's forward
// (draw) map; both read/write the same StageEnvelope fields, so node drag / knot drag / knob
// edit are one model — see core/instrument/CLAUDE.md's "envelope overlay" section for why.
// Mirror of card_drag/waveform_view: drag arithmetic lives here, unit-tested outside the DAW.
//
// Time-only nodes drag on X; DecayEnd in an AHDSR drags on both axes (X = decay time, Y =
// sustain level); a curve knot drags on Y alone (curve_law.h owns the exponent domain). Origin
// is never draggable, and neither is an AHDSR's ReleaseEnd — anchored to the right edge, with
// release dragged from ReleaseStart instead. A node is only editable in its own kind.

#pragma once

#include <cstdint>
#include <vector>

#include "core/instrument/ui/editor_geometry.h"    // Rect
#include "core/instrument/ui/envelope_overlay.h"   // EnvNode, EnvKind, StageEnvelope, EnvVertex

namespace reasampler::instrument::ui {

// Pick radius (px) around a node's drawn point, in both x and y. Mirrors waveform_view's
// kMarkerGrabWidth.
inline constexpr int kNodeGrabRadius = 6;

// Per-param clamp bounds the shell supplies — the same maxima its knobs map [0,1] onto.
// Lower bound is always 0. Defaults are placeholders; the shell overrides with its live domain.
struct EnvClampBounds {
    double maxAttackSeconds  = 4.0;
    double maxHoldSeconds    = 4.0;
    double maxDecaySeconds   = 4.0;
    double maxReleaseSeconds = 4.0;
    // sustainLevel is always [0,1] and the hold FRACTION is always [0,1] — no shell knob needed.
};

// Which node a grab at (x, y) lands on, given the current envelope/rect/duration (the same
// inputs buildEnvelopePolyline drew from). `hit` is false for a point off every draggable node.
// Nearest node within the radius wins (Chebyshev distance); an exact tie goes to the earlier
// draw-order node, and since knots are appended last, a coincident endpoint handle wins over a
// knot rather than the drag silently becoming a curve edit. An AHD's DecayEnd is excluded
// entirely when it coincides with HoldEnd AND holdFraction == 1.0 — there it cannot move
// (resolveNodeDrag's derivative is 0), so it is dropped rather than won by draw order.
struct NodeHit {
    bool    hit  = false;
    EnvNode node = EnvNode::Origin;  // meaningful only when hit == true
};
// Takes the waveform overlay (not a lane) — see waveform_view.h's overlay contract.
NodeHit nodeAtPoint(const StageEnvelope& env, const OverlayArea& area, double totalSeconds,
                    int x, int y);

// Resolves a drag of `node` to a new StageEnvelope. `grabEnv` is the envelope as of grab time
// (the shell snapshots it on button-down so the delta is absolute, not accumulated);
// `dxPixels`/`dyPixels` is the pixel delta since grab.
//   * X delta -> the node's time param, through the same map the forward draw used — the tapered
//     slot on an AHDSR, 1:1 wall clock on an AHD — clamped to [0, per-param max].
//   * Y delta -> the level param (AHDSR DecayEnd's sustain) or, on a knot, the segment's curve
//     exponent. Ignored for time-only nodes.
//   * `mods` carries the shared interaction law (param_taper.h): Ctrl scales the pixel delta,
//     Shift snaps the resolved param to a whole unit of its own category before the clamp. The
//     shell RE-ANCHORS on every modifier transition, so `mods` is constant across one delta.
//   * A non-draggable node, an other-kind node, a zero-size area, or totalSeconds <= 0 returns
//     `grabEnv` unchanged.
// Only the dragged node's param(s) change. Pure.
StageEnvelope resolveNodeDrag(const StageEnvelope& grabEnv, EnvNode node, const OverlayArea& area,
                              double totalSeconds, const EnvClampBounds& bounds,
                              int dxPixels, int dyPixels, const DragModifiers& mods = {});

}  // namespace reasampler::instrument::ui
