#pragma once
// peaks — waveform min/max envelope (thumbnail) computation from raw interleaved PCM. We compute
// our own thumbnails rather than depending on REAPER's peak API: we own the file format, so this
// is simpler, testable, and dependency-free.

#include <cstddef>
#include <vector>

namespace reasampler::audio {

// REAPER's native audio buffer format (interleaved 32-bit float), consumed directly with no
// lossy conversion. Named AudioSample rather than Sample to avoid colliding with bank_model's
// metadata struct of the same short name.
using AudioSample = float;

// One bin's extremes across the samples that fell in it. min <= max always; an empty bin
// (more bins than frames) is {0, 0}.
struct MinMax {
    AudioSample min = 0.0f;
    AudioSample max = 0.0f;

    bool operator==(const MinMax& o) const { return min == o.min && max == o.max; }
};

// One channel's envelope: exactly `binCount` bins, in time order.
using ChannelEnvelope = std::vector<MinMax>;

// Per-channel envelopes: outer index is channel (channelCount entries, order preserved — never
// mixed or folded), inner is that channel's bins.
using Envelope = std::vector<ChannelEnvelope>;

// Computes a per-channel min/max envelope from interleaved PCM.
//
//   interleaved  frame-interleaved samples: [f0c0, f0c1, ..., f1c0, f1c1, ...]. Size must be
//                >= frameCount * channelCount; extra is ignored.
//   channelCount channels per frame (the stride). Each channel is enveloped INDEPENDENTLY — no
//                averaging, no stereo fold (channel count is preserved end to end).
//   frameCount   frames (samples-per-channel) to consider.
//   binCount     requested bins per channel. Honored exactly for any frameCount.
//
// Frame->bin partition: frames split into `binCount` contiguous spans as evenly as possible;
// when frameCount doesn't divide evenly, the remainder spreads one-frame-per-bin across the
// earliest bins, so the tail is never dropped and no bin reads out of bounds.
//
// Degenerate input (no UB, no throw): binCount == 0 -> empty bin vector per channel;
// channelCount == 0 -> empty envelope; frameCount == 0 -> binCount bins, all {0, 0}.
Envelope computeEnvelope(const std::vector<AudioSample>& interleaved,
                         std::size_t channelCount,
                         std::size_t frameCount,
                         std::size_t binCount);

// Merged min/max for display column `col` (0-based, of `columnCount` total) of a pre-computed
// ChannelEnvelope — the true extremes of every bin projecting to that column. This is the
// display-side collapse when the envelope was computed at a higher resolution than the drawn
// width, so a steep transient split across adjacent bins (e.g. {0.9,1.0} then {-1.0,-0.9})
// renders as one gap-free span instead of two separated dots.
//
// Bin->column mapping mirrors computeEnvelope's half-open partition: column col owns bins
// [col*nbins/columnCount, (col+1)*nbins/columnCount). When that range is empty (more columns
// than bins), the enclosing bin fills the column instead. columnCount <= 0 or bins.empty()
// returns {0, 0}; col is clamped to [0, columnCount-1].
MinMax columnMinMax(const ChannelEnvelope& bins, int columnCount, int col);

// Sentinel for "no frame in the scanned range peaked above threshold". SIZE_MAX is unambiguous
// since no real frame index can reach it.
inline constexpr std::size_t kNoFrameAboveThreshold =
    static_cast<std::size_t>(-1);

// Scans interleaved PCM BACKWARD for the last frame whose per-frame peak (max |sample| across
// all channels of that frame — no stereo fold) exceeds `linearThreshold`. Returns
// kNoFrameAboveThreshold if no frame exceeds it (or on degenerate input).
//
// This is the boundary primitive behind the realtime tail's decay-scan trim (see
// docs/product/capture-tail.md): the recorded tail is scanned back from the end for the last
// frame still above -72 dB, and the file truncated one frame past it. Deliberately separate from
// computeEnvelope — that answers "the min/max envelope over bins" (a thumbnail), this answers
// "the last frame above a level" (a boundary); bending a bin-oriented envelope to a frame-exact
// question is a worse fit.
//
//   linearThreshold  a LINEAR amplitude ratio (e.g. the -72 dB ratio from
//                    render_settings::autoTrimEndRatio), NOT dB. A frame counts as above when
//                    its peak is STRICTLY greater than this.
std::size_t lastFrameAboveThreshold(const std::vector<AudioSample>& interleaved,
                                    std::size_t channelCount,
                                    std::size_t frameCount,
                                    AudioSample linearThreshold);

} // namespace reasampler::audio
