// waveform_view.h — the WAVEFORM band's interior: the drawn lane/overlay surface, plus
// frame<->pixel mapping, marker hit-test and zero-crossing snap. Unit-tested outside the
// DAW; the shell draws and marshals mouse events into it.
//
// The band maps a sample's full frame span [0, frameCount] linearly across its width.
// Markers are a generic N-named-marker set (not hardcoded specials), so a different mode
// (e.g. start + %-length end + fades) can repurpose the same machinery.

#pragma once

#include <cstdint>

#include "core/instrument/ui/editor_geometry.h"  // Rect, OverlayArea, contains
#include "core/audio/peaks.h"            // AudioSample (float), Envelope

namespace reasampler::instrument::ui {

using audio::AudioSample;

// What the waveform band actually draws: the channel lane(s), and THE rect every overlay
// riding the waveform occupies.
//
// OVERLAY CONTRACT — `overlay` is the whole band in BOTH modes, never a lane. The amp
// envelope trace and its node handles, the start/loop markers, and the loop region draw
// ONCE into `overlay`, spanning both stacked lanes in stereo. Hit-testing reads the same
// rect, so a grab in the lower lane resolves to the same overlay item as one in the upper.
// Anything that draws per lane is a duplicate and a defect.
struct WaveformSurface {
    Rect upper;          // lane 0 -> channel 0 (LEFT); the whole band when single-lane
    Rect lower;          // lane 1 -> channel 1 (RIGHT); empty() when single-lane
    OverlayArea overlay; // the full band, both modes
    int laneCount = 0;  // 0 on a degenerate band, else 1 or 2 — matches `lower`'s emptiness
                        // (2 iff lower non-empty). For a non-empty band <= 2px tall, `upper`
                        // can be empty too while this still reports 1 — unreachable through
                        // the band-stack allocator's kWaveformMinHeight floor.
};

// Resolves the surface for a waveform band. Two lanes need BOTH stereo mode and a source
// that has a second channel to show: a mono source under stereo mode is dual-mono, so a
// second lane would be the redundant duplicate single-lane mode exists to avoid.
WaveformSurface waveformSurface(const Rect& band, bool stereoMode, int sourceChannels);

// THE overlay area, standalone — same value as WaveformSurface::overlay, for the hit-test
// paths that have no channel count to hand. An overlay's rect never depends on the lane
// split, which is exactly the contract.
OverlayArea waveformOverlayArea(const Rect& band);

// The single-channel envelope lane `lane` draws, taken from a multi-channel envelope
// computed in ONE computeEnvelope pass (it already envelopes channels independently, so a
// second lane costs no second scan of the PCM). Lane 0 is the upper lane and takes channel
// 0, lane 1 the lower and channel 1 — the L-above-R order. An out-of-range lane yields an
// empty envelope, which draws as a bare midline.
audio::Envelope laneEnvelope(const audio::Envelope& env, int lane);

// Pixel width of a marker's grab region either side of its x line. Mirrors keyboard_strip's
// edge-grab idiom.
inline constexpr int kMarkerGrabWidth = 5;

// x pixel of `frame` under the linear map: frame 0 -> area.x, frame frameCount -> area.right().
// Frame is clamped to [0, frameCount] before mapping. frameCount <= 0 or a zero-width area pins
// every frame to area.x. Takes the overlay (not a lane) — see the OVERLAY CONTRACT above.
int frameToX(const OverlayArea& area, std::int64_t frameCount, std::int64_t frame);

// Inverse of frameToX: the frame a point x maps to, clamped to [0, frameCount]. A point left of
// area.x yields 0; right of area.right() yields frameCount.
std::int64_t xToFrame(const OverlayArea& area, std::int64_t frameCount, int x);

// Which marker (index into the caller's parallel `frames` array, in draw order) a grab at
// (x, y) lands on, or -1 for a miss. A marker is grabbed when x is within kMarkerGrabWidth of
// its drawn x and y is inside `area`. First marker in draw order wins an overlapping tie.
int markerAtPoint(const OverlayArea& area, std::int64_t frameCount, const std::int64_t* frames,
                  int count, int x, int y);

// Resolves a drag to a new frame: `startFrame` shifted by round(dxPixels * frameCount /
// areaWidth), clamped to [0, frameCount]. The shell applies between-marker clamps (e.g.
// start <= loopEnd) after this per-marker resolve.
std::int64_t resolveDragFrame(const OverlayArea& area, std::int64_t frameCount,
                              std::int64_t startFrame, int dxPixels);

// Nearest zero-crossing frame to `target` in the mono PCM, for loop/start snap. A crossing is a
// frame i (1 <= i < frames) where pcm[i-1] and pcm[i] differ in sign (pcm[i] == 0 snaps to i).
// Search fans out symmetrically from the clamped target; an equidistant tie resolves to the
// lower frame. No sign change anywhere (or fewer than 2 frames) returns the clamped target
// unchanged.
std::int64_t nearestZeroCrossing(const AudioSample* pcm, std::int64_t frames,
                                 std::int64_t target);

}  // namespace reasampler::instrument::ui
