#pragma once
// wav_codec — the pure owner of the WAV/RIFF byte format: chunk walker, layout
// parse, float32 build, size-field patch, and the WAV-aware content hash — one
// chunk traversal shared by all of them so hashing and decoding cannot desync.
// Handles 32-bit float WAV only (RIFF/WAVE, `fmt ` tag 3 or 0xFFFE-extensible
// w/ float subformat, float32 `data`); anything else parses as invalid.

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

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

namespace reasampler::capture {

using audio::AudioSample;

// --- Layout parse ------------------------------------------------------------

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

    std::uint16_t channelCount = 0;   // from `fmt ` (interleave stride)
    std::uint32_t sampleRate   = 0;

    // The `data` chunk: PCM byte offset + declared length.
    // frameCount = dataByteLength / (channelCount * 4).
    std::size_t dataByteOffset = 0;
    std::size_t dataByteLength = 0;

    // Offsets of the two LE uint32 size fields the truncate patch rewrites.
    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; {valid=false} for anything not a
// canonical float32 RIFF/WAVE, or a `data` length running past the buffer.
// Does not copy PCM, only locates it. 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, clamped to frames
// actually present; never reads past `data`. Reads little-endian via memcpy —
// target is x86/ARM-LE only, no big-endian byte-swap.
std::vector<AudioSample> extractFloatFrames(const std::vector<std::uint8_t>& bytes,
                                            const WavLayout& layout,
                                            std::size_t startFrame,
                                            std::size_t frameCount);

// --- Truncate plan + size-field patch ---------------------------------------

// The plan to truncate a parsed WAV to `keptFrames` frames. `valid` is false if
// the layout is invalid or keptFrames exceeds the file's frames (never grow).
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. The shell
// applies it: patch the two size fields (patchU32LE), then truncate to
// newFileByteLength.
WavTruncatePlan planWavTruncate(const WavLayout& layout, std::size_t keptFrames);

// Patches a little-endian uint32 into a byte buffer at `off`. Caller guarantees
// off + 4 <= bytes.size() (the plan's offsets came from a valid parse of the same
// buffer).
void patchU32LE(std::vector<std::uint8_t>& bytes, std::size_t off, std::uint32_t v);

// --- Float32 WAV build -------------------------------------------------------

// Builds a minimal canonical float32 RIFF/WAVE byte buffer from interleaved
// double samples (narrowed to float by cast). Round-trips through
// parseWavLayout/extractFloatFrames.
std::vector<std::uint8_t> buildFloat32Wav(int nch, std::uint32_t rate,
                                          std::size_t frameCount,
                                          const std::vector<double>& interleaved);

// --- Lossless mono collapse ---------------------------------------------------

// The outcome of the bit-identical mono collapse. `collapsed == false` means the
// caller must leave the source file exactly as it is — it writes nothing.
struct MonoCollapse {
    bool collapsed = false;
    std::vector<std::uint8_t> bytes;  // the rebuilt 1-channel WAV; empty unless collapsed
};

// Collapses a multi-channel float32 WAV to one channel when EVERY channel of EVERY
// frame carries the identical float BIT PATTERN. Bit equality, never an epsilon and
// never `==` on floats: +0.0/-0.0 and two NaNs with differing payloads are NOT
// identical and are never folded. Frame count, sample rate and bit depth are
// preserved — only the interleave stride changes — so the collapse cannot lose
// information, and a lossy downmix (summing differing channels) is not something
// this can express.
//
// Declines for: bytes that do not parse; a file already at one channel; a zero-frame
// file (no frame of evidence to act on); any differing channel pair.
//
// The rebuild is a canonical minimal WAV, so non-audio chunks (a renderer's `bext`
// timestamp, iXML, LIST) do not survive it. That much hashWavContent already skips —
// but the collapse rewrites the `fmt ` body and the `data` payload too, which moves
// the file's content identity; see this directory's CLAUDE.md for what that costs,
// including the bext/source-position consequence beyond hashing.
MonoCollapse collapseToMono(const std::vector<std::uint8_t>& bytes);

// A buffer after the collapse has had its say, PAIRED with the parse of the bytes
// actually returned — so a caller that hashes `bytes`, reads a channel count off
// `layout` and then writes `bytes` cannot describe one buffer while writing another.
struct CollapsedWav {
    std::vector<std::uint8_t> bytes;  // the rebuilt 1-channel WAV, or the input verbatim
    WavLayout layout;                 // the parse OF `bytes`
    bool collapsed = false;
};

// `collapseToMono` over a whole buffer, for a caller that goes on to hash and measure
// the result rather than rewrite a file (`shell/capture`'s collapseCapturedFileToMono is
// the file-side path over the same predicate). Takes the buffer by value: a decline hands
// those same bytes straight back. A rebuild that does not parse back is discarded rather
// than returned, so an invalid `layout` can only ever mean the INPUT was not a usable WAV.
CollapsedWav applyMonoCollapse(std::vector<std::uint8_t> bytes);

// How applying the collapse to a captured FILE ended. `Declined` is collapseToMono's own
// "nothing to do"; `Failed` is a read that never happened or a warranted rewrite that did
// not land. The capture is intact and correctly measured in every case — only the report
// tells them apart, which is why the two must not share one value.
enum class MonoCollapseOutcome {
    Declined,
    Collapsed,
    Failed,
};

// The capture message's collapse suffix — empty for Declined, so a capture that had
// nothing to collapse reads exactly as it did before the collapse existed. Shared by
// both backends so one outcome cannot be reported two ways. NOT user-observable on its
// own: CaptureResult::message on a successful capture is never printed by any caller, so
// the Collapsed/Failed text this returns reaches no one today — the one observable
// channel for a genuine Failed outcome is the backends' own reportCollapseFailure
// console line.
std::string monoCollapseSuffix(MonoCollapseOutcome outcome);

// --- Content identity (dedup hashes) -----------------------------------------

// Deterministic FNV-1a 64-bit content hash over `len` bytes, as 16-char lowercase
// hex. Fills Sample::contentHash for the confirm-on-last-reference dedup guardrail.
std::string hashBytes(const std::uint8_t* data, std::size_t len);

// WAV-aware content hash: hashes only the `fmt ` body + `data` payload, skipping
// other chunks. WHY: REAPER's offline renderer embeds a render-varying `bext`
// timestamp chunk even with no BWF metadata requested, so two renders of
// identical audio would otherwise hash differently and never dedup. Prefixed
// with tag byte 'W' so it can't collide with a same-size hashBytes result.
// Falls back to whole-file hashBytes (no prefix) for a file that doesn't parse.
std::string hashWavContent(const std::vector<std::uint8_t>& bytes);

} // namespace reasampler::capture
