// envelope_overlay.h — staged-envelope -> polyline geometry for the Sample-view overlay.
// Engine-free by design (no sample_map/sampler_core dependency); mirror of waveform_view /
// param_slider. The shell packs whichever envelope is overlay-active into StageEnvelope and
// draws the polyline plus a handle at each node (envelope_edit does the hit-test).

#pragma once

#include <cstdint>
#include <vector>

#include "core/instrument/ui/editor_geometry.h"  // Rect — the shared geometry idiom
#include "core/instrument/ui/param_taper.h"      // kStageTimeMaxSeconds + the stage-time taper
#include "core/util/curve_law.h"                 // the ONE per-segment curve law

namespace reasampler::instrument::ui {

// Which LAYOUT POLICY an envelope takes — see core/instrument/CLAUDE.md's "envelope overlay"
// section for why (decided by sustain-stage presence, not by which processor it modulates).
// Ahdsr right-anchors its release; Ahd maps 1:1 onto the waveform's own time axis.
enum class EnvKind { Ahdsr, Ahd };

// Gate nodes: Origin -> AttackEnd -> HoldEnd -> DecayEnd(sustain) -> ReleaseStart -> ReleaseEnd.
// AHD nodes:  Origin -> AttackEnd -> HoldEnd -> DecayEnd.
// The three *Curve nodes are the round mid-segment knots whose vertical drag sets that
// segment's curve exponent. Shared by envelope_overlay (forward/draw) and envelope_edit
// (inverse/edit).
enum class EnvNode {
    Origin,        // t=0, level 0 — not draggable
    AttackEnd,     // attack ramp top — sets attackSeconds
    HoldEnd,       // hold plateau end — AHDSR: holdSeconds; AHD: holdFraction
    DecayEnd,      // AHDSR: decay settles to sustain (X = decay, Y = sustain); AHD: decay end
    ReleaseStart,  // AHDSR: sustain plateau end — sets releaseSeconds (drags on X, inverted)
    ReleaseEnd,    // AHDSR: the envelope's end point — ANCHORED to the right edge, not draggable
    AttackCurve,   // mid-attack knot — sets attackCurve
    DecayCurve,    // mid-decay knot — sets decayCurve
    ReleaseCurve,  // mid-release knot — sets releaseCurve (AHDSR only)
};

// The envelope the overlay draws. One struct for both policies: `kind` selects which fields
// are read, so a single pack/unpack pair serves the amp, pitch, and filter envelopes.
struct StageEnvelope {
    EnvKind kind = EnvKind::Ahdsr;

    // AHDSR: seconds at the schematic param-domain scale, plus a dimensionless sustain level.
    double attackSeconds  = 0.003;
    double holdSeconds    = 0.0;
    double decaySeconds   = 0.0;
    double sustainLevel   = 1.0;
    double releaseSeconds = 0.060;

    // AHD: attack/decay seconds plus the Hold FRACTION of the span left after them, laid over
    // [originSeconds, originSeconds + spanSeconds) of the waveform's own time axis.
    double holdFraction  = 1.0;
    double originSeconds = 0.0;
    double spanSeconds   = 0.0;

    // Per-segment curve exponents (release is AHDSR-only). curve_law.h owns the domain.
    double attackCurve  = util::kCurveNeutral;
    double decayCurve   = util::kCurveNeutral;
    double releaseCurve = util::kCurveNeutral;
};

// One polyline vertex: pixel point plus which node it is. `level` is redundant with y, carried
// for inspection. `knot` marks the round mid-segment curve handles, which draw differently and
// are not part of the traced line.
struct EnvVertex {
    EnvNode node = EnvNode::Origin;
    int x = 0;
    int y = 0;
    double level = 0.0;
    bool knot = false;

    bool operator==(const EnvVertex& o) const {
        return node == o.node && x == o.x && y == o.y && level == o.level && knot == o.knot;
    }
};

// Minimum pixel separation between consecutive AHDSR nodes, so zero-duration stages (tier-0
// defaults) still render as distinct, grabbable handles. Larger than envelope_edit's grab
// radius (6) so a click can never tie between neighbours.
inline constexpr int kGateNodeSepPx = 8;

// The AHDSR schematic's per-stage time domain (seconds) — the four timed stages A/H/D/R each
// span at most this. An ALIAS of the taper's own domain end, not a second constant: a maxed knob
// lands exactly at the canvas edge (at which point the sustain plateau has shrunk to nothing)
// only while the two agree.
inline constexpr double kGateStageMaxSeconds = kStageTimeMaxSeconds;

// Width of ONE of the AHDSR schematic's four equal stage slots, independent of the sample's
// actual duration. A stage of `t` seconds fills slot * timeNormFromSeconds(t) pixels of it — the
// axis IS the knob's taper, so a node's position within its slot is that knob's needle position
// drawn a second way. That is what keeps a 3 ms attack legible at a 10 s ceiling (linear in
// seconds put it under a pixel) and what lets envelope_edit's inverse stay the EXACT inverse of
// this draw. Only the AHDSR schematic is tapered; an AHD stays 1:1 wall-clock.
double gateStageSlotPx(const Rect& area);

// Maps a staged envelope to polyline vertices inside `area` over a sample of `totalSeconds`
// duration. y maps level [0,1] across [area.bottom()-1, area.y] (level 1 at the top); the
// traced vertices come first in draw order (Origin first), then the curve knots.
//
// The AHDSR x-axis is a bounded schematic independent of totalSeconds (it does NOT line up with
// the waveform under it) with its ReleaseEnd anchored to the right edge; the AHD x-axis is
// wall-clock, 1:1 with the waveform. Every vertex is clamped inside the canvas: x in
// [area.x, area.right()-1], y in [area.y, area.bottom()-1]. A degenerate area or
// totalSeconds <= 0 yields the flat two-point baseline [Origin, end at level 0]. Takes the
// waveform overlay (not a lane) — see waveform_view.h's overlay contract.
std::vector<EnvVertex> buildEnvelopePolyline(const StageEnvelope& env, const OverlayArea& area,
                                             double totalSeconds);

// Maps a time (seconds) to a pixel x inside `area`, linear and clamped at both ends. Shared
// with envelope_edit's node hit-test so the drawn handle and its grab region agree.
int timeToX(const Rect& area, double totalSeconds, double t);

// Maps a level [0,1] to a pixel y inside `area` (level 1 at the top, 0 at the bottom row),
// clamped. Shared with envelope_edit's node hit-test.
int levelToY(const Rect& area, double level);

// The normalized phi a curve knot's TRUNCATED integer x actually lands at within its bounding
// segment [x0, x1] — exactly 0.5 only when the span is even. Shared with envelope_edit's knot
// drag so the draw and its inverse read the same phi off the same formula rather than two
// copies that could drift apart. x0 == x1 (no interior) returns 0.5; callers never place a knot
// there.
double knotPhi(int x0, int x1);

// The A/H/D split of an AHD's span, in seconds — the pure-UI mirror of the engine's fitAhd, so
// the drawn stage boundaries land where the voice actually puts them. Attack takes at most the
// span and Decay at most what Attack left, so Hold's fraction of the remainder can never push
// the sum past the span; there is no clamp on the sum because none is possible.
struct AhdSplit {
    double attack = 0.0;
    double hold   = 0.0;
    double decay  = 0.0;
    double total  = 0.0;
};
AhdSplit splitAhdSeconds(const StageEnvelope& env);

}  // namespace reasampler::instrument::ui
