// envelope_edit.h — node hit-test + pixel-delta -> clamped-param inverse map for the draggable
// envelope nodes. Mirror of card_drag/waveform_view: drag arithmetic lives here, unit-tested
// outside the DAW; the shell draws handles, captures the grab, and feeds pixel deltas back in.
//
// envelope_overlay owns the params->polyline forward (draw) map; this module owns the inverse
// (edit) map + hit-test. Both read/write the same AmpEnvelope fields (shell re-reads the one
// parameter set every paint), so a node drag and a slider edit are two views on one source of
// truth.
//
// A drag can never produce a param a slider couldn't: nodes are monotonic in time (clamped
// between time predecessor/successor) and range-clamped to the same per-param [min,max] the
// slider uses (EnvClampBounds, caller-supplied since those maxima live shell-side).
//
// Time-only nodes drag on X; DecayEnd (the sustain node) drags on both axes (X = decay time,
// Y = sustain level). Origin and the drawing-only ReleaseStart are not draggable. A node is only
// editable in its own mode (Gate nodes ignore drags in Trigger mode and vice versa).

#pragma once

#include <cstdint>
#include <vector>

#include "core/instrument/ui/editor_geometry.h"    // Rect
#include "core/instrument/ui/envelope_overlay.h"   // EnvNode, EnvMode, AmpEnvelope, EnvVertex, timeToX/levelToY

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 sliders map [0,1] onto.
// Lower bound is always 0; the monotonic-in-time constraint tightens further at edit time.
// Defaults are placeholders; the shell overrides with its live slider domain.
struct EnvClampBounds {
    double maxAttackSeconds  = 4.0;
    double maxHoldSeconds    = 4.0;
    double maxDecaySeconds   = 4.0;
    double maxReleaseSeconds = 4.0;
    double maxFadeInFraction  = 1.0;
    double maxFadeOutFraction = 1.0;
    double maxLengthFraction  = 1.0;
    // sustainLevel 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;
// Origin/ReleaseStart and nodes from the other mode never hit. Nearest node within the radius
// wins (Chebyshev distance); an exact tie goes to the earlier draw-order node — this only matters
// for Trigger's zero-fade-out coincidence (FadeOutStart overlaps LengthEnd and wins, so the fade
// can be dragged open from zero). Gate nodes never coincide (forward map enforces
// kGateNodeSepPx), so every Gate handle is independently grabbable.
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 AmpEnvelope& env, const OverlayArea& area, double totalSeconds, int x,
                    int y);

// Resolves a drag of `node` to a new AmpEnvelope. `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, shifted via the same linear map as timeToX, clamped to
//     [0, per-param max] and to its monotonic-in-time neighbours.
//   * Y delta -> the level param, only for DecayEnd; clamped to [0,1]. Ignored for time-only nodes.
//   * A non-draggable node, an other-mode node, a zero-size area, or totalSeconds <= 0 returns
//     `grabEnv` unchanged.
// Only the dragged node's param(s) change. Pure.
AmpEnvelope resolveNodeDrag(const AmpEnvelope& grabEnv, EnvNode node, const OverlayArea& area,
                            double totalSeconds, const EnvClampBounds& bounds,
                            int dxPixels, int dyPixels);

}  // namespace reasampler::instrument::ui
