#pragma once
// loop_marks.h — the loop enable's state machine: what the waveform band SHOWS for a stored
// loop, and what a marker edit WRITES back. `SampleLoop::hasLoop` is the single authority;
// collapse-to-off and drag-to-create are shortcuts onto it, not a second state. Pure values
// only — the shell supplies the stored side and applies the result.

#include <cstdint>
#include <optional>

#include "core/instrument/engine/play_params.h"  // SampleLoop

namespace reasampler::instrument::ui {

using ::reasampler::SampleLoop;

// What the four marks are showing. `parked` separates the two OFF states: a pair sitting on
// defaultLoopBounds because nothing was ever set (there is a loop to "set") from a real span
// the user switched off (there is not).
struct LoopMarks {
    std::int64_t start = 0;
    std::int64_t loopStart = 0;
    std::int64_t loopEnd = 0;
    std::int64_t crossfade = 0;  // pre-seam fade, SOURCE frames
    bool hasLoop = false;
    bool parked = false;
};

// The stored side: the parameter set's loop override, the bank's loop intrinsic (consulted only
// when there is no override — the override always supersedes it), the crossfade length, and the
// start point.
struct StoredLoop {
    std::optional<SampleLoop> override_;
    std::optional<SampleLoop> intrinsic;
    std::int64_t crossfade = 0;
    std::optional<std::int64_t> startPoint;
};

// Reads the stored loop into what the band shows. A span the engine could not honour —
// collapsed, inverted, or outside [0, frameCount] — re-parks on defaultLoopBounds so two
// coincident handles can never become ungrabbable; a VALID span keeps its own positions
// whatever the enable says, which is what makes the enable a toggle rather than a delete
// button.
LoopMarks resolveLoopMarks(const StoredLoop& stored, std::int64_t frameCount);

// The write side, the inverse of resolveLoopMarks.
struct LoopWrite {
    SampleLoop loop;
    std::int64_t crossfade = 0;
    std::int64_t start = 0;
};

// Folds collapse-to-off in: a span dragged onto itself is the OFF gesture, recorded as such so
// the next resolve re-offers the default handles. The crossfade goes with the SPAN, not with
// the enable — zeroed only when the span is destroyed. That preserves the original zeroing
// rule's reason rather than overruling it: a stale length could silently re-apply against a
// span that no longer exists, but a retained span retains its clamp bound too, so nothing is
// stale.
LoopWrite applyLoopMarks(const LoopMarks& m);

}  // namespace reasampler::instrument::ui
