#include "core/audio/peaks.h"

#include <algorithm>
#include <climits>
#include <cmath>
#include <cstdint>

// peaks — pure implementation. See peaks.h.
//
// One linear pass per channel. Frame->bin partition uses integer arithmetic so it's exact for
// any frameCount/binCount pairing: bin b owns [b*frameCount/binCount, (b+1)*frameCount/binCount)
// — earlier bins absorb the remainder, no rounding drift, no dropped tail.

namespace reasampler::audio {

Envelope computeEnvelope(const std::vector<AudioSample>& interleaved,
                         std::size_t channelCount,
                         std::size_t frameCount,
                         std::size_t binCount) {
    Envelope envelope(channelCount);
    if (channelCount == 0) {
        return envelope;
    }

    // Never read past what the buffer actually holds, even if frameCount overstates it.
    const std::size_t availableFrames = interleaved.size() / channelCount;
    const std::size_t frames = std::min(frameCount, availableFrames);

    for (std::size_t ch = 0; ch < channelCount; ++ch) {
        ChannelEnvelope& bins = envelope[ch];
        bins.assign(binCount, MinMax{}); // empty/degenerate bins default to {0,0}

        for (std::size_t b = 0; b < binCount; ++b) {
            // Guard b*frames / (b+1)*frames overflow: binCount is caller-controlled and
            // unbounded. Unreachable in practice (would OOM first) but guarded to avoid UB.
            if (frames > 0 && b >= SIZE_MAX / frames) {
                continue;
            }
            const std::size_t begin = (b * frames) / binCount;
            const std::size_t end   = ((b + 1) * frames) / binCount;
            if (begin >= end) {
                continue; // empty span (binCount > frames) -> keep {0,0}
            }

            const AudioSample first = interleaved[begin * channelCount + ch];
            AudioSample lo = first;
            AudioSample hi = first;
            for (std::size_t f = begin + 1; f < end; ++f) {
                const AudioSample s = interleaved[f * channelCount + ch];
                lo = std::min(lo, s);
                hi = std::max(hi, s);
            }
            bins[b] = MinMax{lo, hi};
        }
    }

    return envelope;
}

MinMax columnMinMax(const ChannelEnvelope& bins, int columnCount, int col) {
    const int nbins = static_cast<int>(bins.size());
    if (columnCount <= 0 || nbins == 0) return MinMax{};

    if (col < 0) col = 0;
    if (col >= columnCount) col = columnCount - 1;

    // Half-open bin range for this column, mirroring computeEnvelope's partition. 64-bit
    // products: col*nbins can exceed int range for a large oversampled envelope.
    const std::int64_t begin64 = (static_cast<std::int64_t>(col) * nbins) / columnCount;
    const std::int64_t end64 =
        (static_cast<std::int64_t>(col) + 1) * nbins / columnCount;
    const int colBinBegin = static_cast<int>(begin64);

    // When the column spans no full bin (more columns than bins), use the enclosing bin.
    const int scanEnd = (end64 > begin64) ? static_cast<int>(end64) : colBinBegin + 1;
    const int clampedEnd = (scanEnd <= nbins) ? scanEnd : nbins;

    MinMax result = bins[static_cast<std::size_t>(colBinBegin)];
    for (int b = colBinBegin + 1; b < clampedEnd; ++b) {
        const MinMax& mm = bins[static_cast<std::size_t>(b)];
        if (mm.min < result.min) result.min = mm.min;
        if (mm.max > result.max) result.max = mm.max;
    }
    return result;
}

std::size_t lastFrameAboveThreshold(const std::vector<AudioSample>& interleaved,
                                    std::size_t channelCount,
                                    std::size_t frameCount,
                                    AudioSample linearThreshold) {
    if (channelCount == 0) return kNoFrameAboveThreshold;

    const std::size_t availableFrames = interleaved.size() / channelCount;
    const std::size_t frames = std::min(frameCount, availableFrames);
    if (frames == 0) return kNoFrameAboveThreshold;

    // Scan backward; `f` runs frames..1 so `f-1` never wraps.
    for (std::size_t f = frames; f > 0; --f) {
        const std::size_t frame = f - 1;
        const std::size_t base = frame * channelCount;
        AudioSample peak = 0.0f;
        for (std::size_t c = 0; c < channelCount; ++c) {
            const AudioSample a = std::fabs(interleaved[base + c]);
            peak = std::max(peak, a);
        }
        if (peak > linearThreshold) return frame;
    }
    return kNoFrameAboveThreshold;
}

} // namespace reasampler::audio
