#pragma once
// wav_trim — pure parse + truncate-plan for the realtime tail's PCM decay-scan trim.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only. Builds and unit-tests without REAPER.
//
// WHY THIS EXISTS (docs/product/capture-tail.md §The realtime path). The realtime
// backend records a generous tail window, then trims the trailing decay by
// truncating the recorded WAV at a frame boundary. Truncating a WAV correctly is
// not "chop the bytes": the RIFF container's size fields (the top-level RIFF chunk
// size and the `data` sub-chunk size) must be patched to the kept byte count, or
// the file is a corrupt / mis-lengthed WAV. That header arithmetic — chunk walking,
// format verification, and the size-field patch offsets — is exactly the fiddly,
// easy-to-get-wrong logic the discipline unit-tests OUTSIDE the DAW. The REAPER
// shell (capture_realtime.cpp) does only the file I/O: read the bytes, call the
// pure parse, run the decay scan, call the pure plan, write the truncated bytes.
//
// FORMAT ASSUMPTION (flagged for DAW-verify). We record 32-bit float WAV
// (capture.cpp kRenderFormatWavFloat32; realtime records via REAPER's project
// record format, which the manual procedure sets to WAV/32-bit-float). This parser
// therefore verifies canonical PCM/IEEE-float WAV: a RIFF/WAVE container, a `fmt `
// chunk declaring 32-bit float (format tag 3, or tag 0xFFFE WAVE_FORMAT_EXTENSIBLE
// with 32 bits), and a `data` chunk of interleaved little-endian float32. Anything
// else (a different depth, a non-WAV, a compressed source) is reported invalid and
// the shell SKIPS the trim (keeps the untrimmed window) rather than corrupting a
// file it does not understand. This is deliberately conservative.

#include <cstddef>
#include <cstdint>
#include <vector>

#include "peaks.h"  // AudioSample (float)

namespace reasampler {

// The parsed geometry of a canonical 32-bit-float WAV. `valid` is false when the
// bytes are not a WAV we can safely trim (see FORMAT ASSUMPTION); every other field
// is meaningful only when valid.
struct WavLayout {
    bool valid = false;

    std::uint16_t channelCount = 0;   // from `fmt ` (the interleave stride)
    std::uint32_t sampleRate   = 0;   // from `fmt ` (for frame<->seconds, if needed)

    // The `data` chunk: byte offset of its first PCM byte within the file, and its
    // declared PCM byte length. frameCount = dataByteLength / (channelCount * 4).
    std::size_t dataByteOffset = 0;
    std::size_t dataByteLength = 0;

    // Byte offset of the two little-endian uint32 size fields the truncate patch
    // rewrites: the top-level RIFF chunk size (bytes 4..7) and the `data` sub-chunk
    // size (the 4 bytes immediately before dataByteOffset).
    std::size_t riffSizeFieldOffset = 4;  // always 4 for a RIFF file
    std::size_t dataSizeFieldOffset = 0;

    std::size_t frameCount() const {
        const std::size_t bytesPerFrame = static_cast<std::size_t>(channelCount) * 4u;
        return bytesPerFrame ? dataByteLength / bytesPerFrame : 0;
    }
};

// Parses a WAV byte buffer's header geometry. Returns {valid=false} for anything
// that is not a canonical 32-bit-float RIFF/WAVE with a `fmt ` and a `data` chunk,
// or whose declared `data` length runs past the buffer. Does NOT copy PCM — it only
// locates it (extractFloatFrames does the copy). Pure + total (no throw, no UB).
WavLayout parseWavLayout(const std::vector<std::uint8_t>& bytes);

// Copies `frameCount` interleaved float frames starting at `startFrame` out of the
// WAV's `data` region into a flat [f0c0,f0c1,...] buffer (the shape peaks consumes).
// Clamps to the frames the buffer actually holds — never reads past `data`. Returns
// empty for an invalid layout or an out-of-range start. The floats are read
// little-endian via std::memcpy (no aliasing UB); on a big-endian host they would
// need a byte-swap — flagged, not handled, because the target (Windows/macOS/Linux
// on x86/ARM-LE) is little-endian and REAPER writes LE WAV.
std::vector<AudioSample> extractFloatFrames(const std::vector<std::uint8_t>& bytes,
                                            const WavLayout& layout,
                                            std::size_t startFrame,
                                            std::size_t frameCount);

// The plan to truncate a parsed WAV to `keptFrames` frames: the new total file byte
// length and the two size-field values to patch. `valid` is false if the layout is
// invalid or keptFrames exceeds the file's frames (never GROW a file — the caller
// clamps beforehand; this guards it too).
struct WavTruncatePlan {
    bool valid = false;

    std::size_t newFileByteLength = 0;   // truncate the file to exactly this length
    std::size_t dataSizeFieldOffset = 0; // where to write newDataSize (LE uint32)
    std::uint32_t newDataSize = 0;       // kept PCM byte length
    std::size_t riffSizeFieldOffset = 4; // where to write newRiffSize (LE uint32)
    std::uint32_t newRiffSize = 0;       // newFileByteLength - 8 (RIFF size excludes
                                         // the 8-byte "RIFF"+size prefix)
};

// Computes the truncate plan to keep exactly `keptFrames` frames of a parsed WAV.
// keptFrames == layout.frameCount() is a valid no-op plan (file unchanged). Pure +
// total. The shell applies it: patch the two size fields in the byte buffer, then
// truncate the file to newFileByteLength.
WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames);

} // namespace reasampler
