// envelope_overlay.h — amp-envelope -> polyline geometry for the Sample-view envelope overlay.
// Engine-free by design (no sample_map/sampler_core dependency); mirror of waveform_view /
// param_slider. The shell packs the one parameter set's AdsrSeconds/TriggerParams into
// AmpEnvelope 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

namespace reasampler::instrument::ui {

// Local mirror of sampler_core's PlayMode, kept here so this module stays engine-free.
enum class EnvMode { Gate, Trigger };

// Gate nodes:    Origin -> AttackEnd -> HoldEnd -> DecayEnd(sustain) -> ReleaseStart -> ReleaseEnd.
// Trigger nodes: Origin -> FadeInEnd -> FadeOutStart -> LengthEnd(playEnd).
// Shared by envelope_overlay (forward/draw map) and envelope_edit (inverse/edit map).
enum class EnvNode {
    Origin,        // t=0, level 0 — not draggable
    AttackEnd,     // Gate: attack ramp top — sets attackSeconds
    HoldEnd,       // Gate: hold plateau end — sets holdSeconds
    DecayEnd,      // Gate: decay settles to sustain — sets decaySeconds (X) and sustainLevel (Y)
    ReleaseStart,  // Gate: sustain plateau end — drawing-only, not draggable
    ReleaseEnd,    // Gate: release tail end — sets releaseSeconds
    FadeInEnd,     // Trigger: fade-in top — sets fadeInFraction
    FadeOutStart,  // Trigger: fade-out start — sets fadeOutFraction
    LengthEnd,     // Trigger: playEnd terminal — sets lengthFraction
};

// Amp-envelope params the overlay draws. Trigger's fadeIn/fadeOutFraction are derived from
// TriggerParams' frame counts, not a direct field copy — see the trigger_seam gotcha in
// core/instrument/CLAUDE.md.
struct AmpEnvelope {
    EnvMode mode = EnvMode::Gate;

    // Gate (AHDSR): seconds, 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;

    // Trigger: fractions of the played span.
    double lengthFraction  = 1.0;
    double fadeInFraction  = 0.0;
    double fadeOutFraction = 0.0;
};

// One polyline vertex: pixel point plus which node it is. level is redundant with y, carried for
// inspection.
struct EnvVertex {
    EnvNode node = EnvNode::Origin;
    int x = 0;
    int y = 0;
    double level = 0.0;

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

// Fraction of canvas width reserved for the Gate sustain-plateau display; the remaining width
// carries A/H/D/R at the param-domain scale. Shared with envelope_edit.
inline constexpr double kGateSustainDisplayFraction = 0.15;

// Minimum pixel separation between consecutive Gate 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;

// Gate schematic's per-stage time domain (seconds) — the timed region represents four stages
// end-to-end at this max each. Must match the shell's stage-slider ceiling so a maxed slider
// lands exactly at the canvas edge.
inline constexpr double kGateStageMaxSeconds = 2.0;

// Pixel width of the Gate timed region (area width minus the sustain reserve), floored at 1 for
// a non-empty area; 0 for a zero/negative-width area.
int gateTimedWidth(const Rect& area);

// Pixels per second of the Gate timed region, independent of the sample's actual duration.
// Shared by buildEnvelopePolyline and envelope_edit's drag inverse so a dragged handle tracks
// the cursor 1:1.
double gatePxPerSecond(const Rect& area);

// Maps an amp 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); vertices
// are in draw order, Origin first.
//
// Gate's x-axis is a bounded schematic independent of totalSeconds (does NOT line up with the
// waveform under it); Trigger's x-axis is PCM-aligned wall-clock. 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 AmpEnvelope& 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);

}  // namespace reasampler::instrument::ui
