// envelope_overlay.h — PURE amp-envelope → polyline geometry for the S-VIEW-3 Sample-view
// envelope overlay. NO VST3, NO REAPER, NO SWELL/LICE types at the boundary. The mirror of
// waveform_view / param_slider: the params→pixel polyline math lives here, unit-tested outside
// the DAW, while the editor shell (reasampler_editor.cpp) traces the polyline in an accent hue
// and draws the node handles (via envelope_edit's hit-test).
//
// WHAT IT DRAWS. The amp envelope over the Sample view's hero waveform at accurate wall-clock
// time (Simpler / Phase-Plant grammar):
//   * Gate    -> the AHDSR shape: attack ramp 0->1, hold plateau at 1, decay 1->sustain,
//                sustain plateau, release sustain->0.
//   * Trigger -> the fade/%-length shape: fade-in 0->1, unity plateau, fade-out 1->0 anchored
//                to playEnd (= lengthFraction of the post-start span).
// The horizontal axis is wall-clock TIME across the waveform rect; the vertical axis is LEVEL
// (0 at rect bottom, 1 at rect top). The overlay shares the waveform's time base so the drawn
// shape lines up with the PCM under it: the same [0, frameCount] span waveform_view maps, so the
// envelope's own duration is placed at the SAME frames the voice plays it over.
//
// DELIBERATELY ENGINE-FREE (house pattern — param_slider does the same). It does NOT depend on
// sample_map / sampler_core (which would drag bank_book / wav_trim in). The shell reads the
// zone's AdsrSeconds / TriggerParams and packs them into the small AmpEnvelope view struct here.
// AHDSR times are wall-clock SECONDS (rate-free, matching the stored domain — Daniel's no-
// hardcoded-rate ruling); Trigger fades are FRACTIONS of the play span. The one rate-bound input
// is the total sample duration in seconds, which the shell resolves once from the live rate and
// the frame count and passes in — this module never sees a sample rate.
//
// It reuses editor_geometry's Rect + contains(), the one shared geometry idiom.

#pragma once

#include <cstdint>
#include <vector>

#include "editor_geometry.h"  // Rect — the shared geometry idiom

namespace reasampler::vst {

// The play mode the overlay draws — a LOCAL mirror of sampler_core's PlayMode kept here so the
// geometry module stays engine-free (the shell maps the zone's PlayMode to this). Same two cases.
enum class EnvMode { Gate, Trigger };

// Which breakpoint a polyline vertex / node is. The shell draws a draggable handle at each of
// these; envelope_edit hit-tests against them. Kept in one enum shared by overlay + edit so the
// forward map (draw) and inverse map (edit) name the same nodes.
//
// Gate nodes:    Origin -> AttackEnd -> HoldEnd -> DecayEnd(=sustain corner) -> ReleaseStart
//                -> ReleaseEnd. The sustain node is DecayEnd (its Y is the sustain level);
//                ReleaseStart is a drawing-only plateau-end vertex.
// Trigger nodes: Origin -> FadeInEnd -> FadeOutStart -> LengthEnd(playEnd, level 0). The fade-out
//                ramp is the FadeOutStart->LengthEnd segment; LengthEnd is the playEnd terminal.
enum class EnvNode {
    Origin,        // t=0, level 0 (both modes) — not draggable (fixed anchor)
    AttackEnd,     // Gate: top of the attack ramp (level 1) — X sets attackSeconds
    HoldEnd,       // Gate: end of the hold plateau (level 1) — X sets holdSeconds
    DecayEnd,      // Gate: decay settles to sustain — the SUSTAIN node (X sets decaySeconds,
                   //       Y sets sustainLevel)
    ReleaseStart,  // Gate: end of the sustain plateau / start of the release (sustain level) —
                   //       a DRAWING vertex only, not a draggable handle (release is edited at
                   //       ReleaseEnd; this vertex tracks its X = sample end, Y = sustain level)
    ReleaseEnd,    // Gate: end of the release tail (level 0) — X sets releaseSeconds
    FadeInEnd,     // Trigger: top of the fade-in (level 1) — X sets fadeInFraction
    FadeOutStart,  // Trigger: end of the unity plateau / start of the fade-out (level 1) —
                   //          X sets fadeOutFraction
    LengthEnd,     // Trigger: the playEnd terminal / %-length (level 0) — X sets lengthFraction
};

// The amp-envelope params the overlay draws — the small view struct the shell packs from the
// zone's stored AdsrSeconds / TriggerParams. Engine-free by design (no sampler_core include).
//
// Gate fields (SECONDS, wall-clock):   attack / hold / decay / release; sustain is a LEVEL 0..1.
//   These map 1-to-1 with the stored AdsrSeconds fields — no conversion required.
//
// Trigger fields (FRACTIONS of play):  fadeIn / fadeOut as a fraction of the played span;
//                                       lengthFraction is the played span as a fraction of the
//                                       post-start sample length (matching TriggerParams).
//
// TRIGGER SEAM — CONVERSION REQUIRED ON BOTH PATHS (Wave 2 shell author, read this):
//   TriggerParams (sampler_core.h) stores Trigger fades as SOURCE FRAMES:
//     fadeInFrames  (int64_t) — 0->1 ramp length in source frames
//     fadeOutFrames (int64_t) — 1->0 ramp length in source frames
//   AmpEnvelope stores them as FRACTIONS of the played span:
//     fadeInFraction  = fadeInFrames  / playLengthFrames
//     fadeOutFraction = fadeOutFrames / playLengthFrames
//   where playLengthFrames = round(lengthFraction * (frameCount - startFrame)).
//   This is a NON-TRIVIAL derived view — NOT a direct field copy. The shell owes a
//   converter on BOTH directions:
//     PACK   (draw):   frames -> fraction  (TriggerParams -> AmpEnvelope, needs frameCount + rate)
//     UNPACK (commit): fraction -> frames  (AmpEnvelope  -> TriggerParams, same inputs)
//   lengthFraction maps 1-to-1 with TriggerParams::lengthFraction and needs no conversion.
//
// Unused fields for the active mode are ignored.
struct AmpEnvelope {
    EnvMode mode = EnvMode::Gate;

    // Gate (AHDSR), seconds + a dimensionless sustain level.
    double attackSeconds  = 0.003;
    double holdSeconds    = 0.0;
    double decaySeconds   = 0.0;
    double sustainLevel   = 1.0;
    double releaseSeconds = 0.060;

    // Trigger, fractions of the play span (fadeIn/fadeOut) and of the post-start length.
    // NOTE: fadeInFraction/fadeOutFraction are DERIVED from TriggerParams::fadeInFrames/
    // fadeOutFrames — see the TRIGGER SEAM note above. A converter is owed on both the
    // pack (draw) and unpack (commit) paths; these fields are NOT a direct TriggerParams copy.
    double lengthFraction  = 1.0;   // (0,1] of the post-start span that plays (1-to-1 with TriggerParams)
    double fadeInFraction  = 0.0;   // 0->1 ramp as a fraction of the played span (DERIVED — see above)
    double fadeOutFraction = 0.0;   // 1->0 ramp as a fraction of the played span (DERIVED — see above)
};

// One polyline vertex: a pixel point plus which node it is. The shell draws a line through the
// points in order (the amp curve) and a draggable handle at each vertex whose node is not Origin.
// Level is carried alongside (0..1) for callers that want to label/inspect; it is redundant with y.
struct EnvVertex {
    EnvNode node = EnvNode::Origin;
    int x = 0;      // pixel x inside the overlay rect
    int y = 0;      // pixel y inside the overlay rect (top = level 1, bottom = level 0)
    double level = 0.0;  // 0..1, the vertex's amplitude (redundant with y; for inspection)

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

// Map an amp envelope to its polyline vertices inside `area`, over a sample of `totalSeconds`
// wall-clock duration. `area` is the waveform rect (left/top inclusive, right/bottom exclusive);
// x maps time 0..totalSeconds across [area.left, area.right], y maps level 0..1 across
// [area.bottom-1 .. area.top] (level 1 at the TOP). The polyline reads left-to-right in draw
// order, Origin first.
//
// TIME BASE. The envelope's own segment durations are placed on the SAME time axis the waveform
// occupies, so the curve lines up with the PCM:
//   * Gate: attack/hold/decay run from t=0; the sustain plateau runs to the note-off. Since the
//     overlay has no held note-off to draw against, the sustain plateau is drawn to the END of
//     the sample (totalSeconds) and the release tail is drawn AFTER that boundary — i.e. the
//     release is appended past the sample end (the standard "release after key-up at end of
//     view" convention). When attack+hold+decay already exceed totalSeconds the plateau collapses
//     to zero width (nodes clamp to the sample end) and release still trails past it.
//   * Trigger: the played span is lengthFraction * totalSeconds; fade-in/out are fractions OF
//     that played span. Nodes past the played span never appear (LengthEnd/FadeOutEnd sit at the
//     played span's right edge).
//
// A time beyond totalSeconds (the Gate release tail) maps past area.right — the shell clips at
// paint time (the same way waveform_view lets a frame past the count pin the marker). A degenerate
// area (zero width/height) or totalSeconds <= 0 yields the two-point flat baseline [Origin, end at
// level 0] so the shell always has a drawable line. Pure — same inputs, same polyline.
std::vector<EnvVertex> buildEnvelopePolyline(const AmpEnvelope& env, const Rect& area,
                                             double totalSeconds);

// Map a time (seconds) to a pixel x inside `area`: t=0 -> area.left, t=totalSeconds -> area.right,
// linear. t is NOT clamped on the high side (a Gate release past the sample end maps past
// area.right, by design — see buildEnvelopePolyline); t < 0 pins to area.left. A zero-width area
// or totalSeconds <= 0 yields area.left. Pure — the shared time->x map both the polyline and the
// node hit-test (envelope_edit) use, so the drawn handle and its grab region agree.
int timeToX(const Rect& area, double totalSeconds, double t);

// Map a level (0..1) to a pixel y inside `area`: level 1 -> area.top, level 0 -> area.bottom-1
// (so the full-amplitude line sits at the top edge and silence at the bottom pixel row). level is
// clamped to [0,1]. A zero-height area yields area.top. Pure — the shared level->y map the polyline
// and the node hit-test share.
int levelToY(const Rect& area, double level);

}  // namespace reasampler::vst
