#pragma once
// play_seconds — the STORED, wall-clock-SECONDS value layer the instrument edits and
// serializes: PlaySeconds and the four stage-time structs it composes. Header-only, and split
// from sample_map so a consumer that only edits these values (the editor's deck binding) does
// not link the bank model and the WAV codec to reach one value struct. `resolvePlay`, which
// turns them into the engine's frame domain, stays in sample_map with the rest of the mapping.

#include <cstdint>

#include "core/instrument/engine/play_params.h"  // PlayMode / TriggerParams / SplineEnv / …

namespace reasampler::instrument::map {

using instrument::engine::VelocityCurve;

// THE seconds -> frames fold, and the one home for its rounding: resolvePlay resolves the whole
// bundle through it, and the audio thread's live patch (param/param_live) resolves one stage
// time through it, so a stage time can never land on a different frame depending on the writer.
// A non-positive rate yields 0 rather than inventing one; a negative time floors at 0.
inline std::int64_t secondsToFrames(double seconds, double sampleRate) {
    if (!(sampleRate > 0.0)) return 0;
    double f = seconds * sampleRate;
    if (!(f > 0.0)) return 0;  // also catches NaN
    return static_cast<std::int64_t>(f + 0.5);
}

// Daniel's standing ruling: no hardcoded sample rate anywhere in the program. The
// instrument stores/edits wall-clock performance times (AHDSR A/H/D/R, pitch-env A/D) as
// SECONDS, rate-free; the engine receives FRAMES resolved from the LIVE sample rate at
// build. Quantities anchored to the source file's timeline (start point, loop points,
// Trigger %-length + fades) stay in source frames/fractions, carried through unchanged
// (TriggerParams reused verbatim).
//
// The stored AHDSR times (seconds). sustainLevel is dimensionless (0..1), not a time; the
// three curve exponents are dimensionless too (curve_law.h owns their domain).
struct AdsrSeconds {
    double attackSeconds  = 0.003;  // tier-0 default
    double holdSeconds    = 0.0;
    double decaySeconds   = 0.0;
    double sustainLevel   = 1.0;
    double releaseSeconds = 0.060;  // tier-0 default
    double attackCurve    = util::kCurveNeutral;
    double decayCurve     = util::kCurveNeutral;
    double releaseCurve   = util::kCurveNeutral;
};

// The stored sustain-less AHD: wall-clock stage times in SECONDS, Hold as a FRACTION of the
// span left after them (AhdParams owns why a fraction, not a time).
struct AhdSeconds {
    double attackSeconds = 0.0;
    double decaySeconds  = 0.0;
    double holdFraction  = 1.0;
    double attackCurve   = util::kCurveNeutral;
    double decayCurve    = util::kCurveNeutral;
};

// The stored AHD pitch envelope. enabled + peakSemitones are dimensionless. The hold fraction
// defaults to 0 so an instance predating the stage plays as its attack-decay predecessor did.
struct PitchEnvSeconds {
    bool       enabled       = false;
    double     peakSemitones = 0.0;  // signed depth at the peak
    AhdSeconds shape{0.0, 0.0, /*holdFraction=*/0.0, util::kCurveNeutral, util::kCurveNeutral};
};

// The stored mirror of the engine's FilterParams (play_params.h, which owns what each field
// MEANS). Only the envelope differs between the two: the control positions and depths are
// rate-free already, so this block is a seconds/frames split of one field, not of the whole
// struct. The env default is a flat unity, so `enabled` is the only thing standing between a
// loaded blob and the pre-filter sound.
struct FilterSeconds {
    bool enabled = false;
    engine::filter::FilterSettings settings;
    double modAmount = 0.0;
    double velAmount = 0.0;
    double keyTrack  = 0.0;
    AdsrSeconds env{0.0, 0.0, 0.0, 1.0, 0.0};  // Gate
    AhdSeconds  trigEnv;                       // Trigger
    VelocityCurve velocityCurve = VelocityCurve::zero();
};

// The stored play bundle: wall-clock times in SECONDS, source-timeline quantities in
// frames/fractions (TriggerParams). Instrument-owned, serialized, editor-facing — distinct
// from the engine-facing PlayParams (frames).
struct PlaySeconds {
    PlayMode        playMode = PlayMode::Gate;
    AdsrSeconds     adsr;                                 // Gate amp: AHDSR (seconds)
    TriggerParams   trigger;                              // Trigger play span (%-length)
    AhdSeconds      trigAhd;                              // Trigger amp: AHD (seconds + fraction)
    PitchEngine     pitchEngine = kDefaultPitchEngine;    // product default: Preserve
    // Rate and the baseline pitch offset are both rate-FREE (a ratio and a semitone count), so
    // they carry through resolvePlay untouched; play_params.h owns what each one means.
    double          playRate = 1.0;
    double          pitchOffsetSemitones = 0.0;
    PitchEnvSeconds pitchEnv;                             // AHD pitch modulation, off by default
    VelocityCurve   pitchVelocityCurve = VelocityCurve::zero();  // velocity -> pitch, off by default
    FilterSeconds   filter;                               // per-voice filter, off by default
    // The three drawn contours, in the same slots the engine bundle carries them (play_params.h
    // owns why they sit beside the envelopes rather than inside them). Normalized over the
    // sample's own length, so resolvePlay needs no rate for them.
    SplineEnv       ampSpline;
    SplineEnv       pitchSpline;
    SplineEnv       filterSpline;
};

}  // namespace reasampler::instrument::map
