// loop_span.cpp — see loop_span.h. Pure math; no host types.

#include "core/instrument/engine/loop/loop_span.h"

namespace reasampler::instrument::engine::loop {

ResolvedLoop resolveLoop(const SampleLoop& loop, std::int64_t crossfadeFrames,
                         std::int64_t frameCount, bool gateMode) {
    ResolvedLoop out;
    // Trigger is a one-shot by definition, so the loop is not merely unused there — it is
    // absent, and the read path branches on this one flag.
    if (!gateMode || !loop.hasLoop) return out;
    if (loop.start < 0 || loop.end <= loop.start || loop.end > frameCount) return out;

    out.active = true;
    out.start = loop.start;
    out.end = loop.end;
    out.length = loop.end - loop.start;

    // The incoming tap reads at `pos - length`, i.e. over [start - crossfade, start) — so the
    // fade cannot outrun the material ahead of the loop, nor the loop itself.
    std::int64_t xf = crossfadeFrames;
    if (xf < 0) xf = 0;
    const std::int64_t bound = maxCrossfade(out.start, out.length);
    if (xf > bound) xf = bound;
    out.crossfade = xf;
    out.fadeBegin = static_cast<double>(out.end - xf);
    // xf == 1 has no fractional region to normalize (crossfadeWeight's d <= 0 check already
    // catches its only frame) — guard rather than divide by zero.
    out.fadeInv = xf > 1 ? 1.0 / static_cast<double>(xf - 1) : 0.0;
    return out;
}

LoopBounds defaultLoopBounds(std::int64_t frameCount) {
    if (frameCount <= 0) return LoopBounds{};
    return LoopBounds{frameCount - frameCount / 4, frameCount};
}

}  // namespace reasampler::instrument::engine::loop
