#pragma once
// realtime_record — the REAPER-free logic behind the realtime-record backend (M8).
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only. The realtime backend (capture.cpp)
// drives the transport, the temp track, the send routing, and the file move —
// all REAPER-bound and DAW-verified. The genuinely pure, easy-to-get-wrong
// pieces are split out here and unit-tested outside the DAW:
//
//   1. the record-mode/recipe bookkeeping: given a capture scope + a desired
//      FX-tap point (post-fader / pre-FX / post-FX-pre-fader), the I_RECMODE and
//      I_RECMODE_FLAGS integer values the temp track must carry.
//   2. the recorded-file -> Sample mapping: given a finished capture (the
//      recorded file's project-relative path + the request's own bounds/format),
//      the populated Sample handed to bank_model. Mirrors the inline Sample
//      population OfflineRenderBackend does — factored out so it is tested once,
//      without a DAW, and shared shape with the offline path is guaranteed.
//
// The I_RECMODE / I_RECMODE_FLAGS bit MEANINGS are transcribed verbatim from
// reaper_plugin_functions.h line ~2197-2198 (see kRecMode* constants); the CHOICE
// of which values each scope needs is this module's logic and is tested.

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

#include "bank_model.h"  // Sample, SourceMode (pure)

namespace reasampler {

// --- I_RECMODE values (verbatim from SDK header ~2197) -----------------------
//
// I_RECMODE : int * : record mode, 0=input, 1=stereo out, 2=none,
//   3=stereo out w/latency compensation, 4=midi output, 5=mono out,
//   6=mono out w/ latency compensation, 7=midi overdub, 8=midi replace.
//
// We record a track's OUTPUT (the scoped signal routed into the temp track),
// latency-compensated, so the recorded file lines up sample-accurately with the
// source. Stereo vs mono is chosen by the request's channel count.
inline constexpr int kRecModeStereoOutLatComp = 3;  // stereo out w/latency comp
inline constexpr int kRecModeMonoOutLatComp   = 6;  // mono out w/latency comp

// --- I_RECMODE_FLAGS values (verbatim from SDK header ~2198) ------------------
//
// I_RECMODE_FLAGS : int * : record mode flags, &3=output recording mode
//   (0=post fader, 1=pre-fx, 2=post-fx/pre-fader).
//
// This is the ONLY documented pre-FX tap in the whole SDK — offline render has no
// pre-FX bit (see render_settings.h note + the M10 null-test note in PLAN.md).
// The realtime backend is therefore the true pre-FX "dry" path.
inline constexpr int kRecOutPostFader       = 0;  // &3==0: post-fader (fully wet)
inline constexpr int kRecOutPreFx           = 1;  // &3==1: pre-FX (true dry)
inline constexpr int kRecOutPostFxPreFader  = 2;  // &3==2: post-FX, pre-fader

// The tap point on the source track's output the temp track records from.
// Orthogonal to the record mode (stereo/mono); this only sets the &3 flags bits.
enum class OutputTap {
    PostFader,       // fully wet, after this track's fader (kRecOutPostFader)
    PreFx,           // true dry, before this track's FX     (kRecOutPreFx)
    PostFxPreFader,  // wet FX, before the fader              (kRecOutPostFxPreFader)
};

// The concrete record-mode values a temp track must carry to capture the scoped
// output. `recMode` sets I_RECMODE (stereo/mono, latency-compensated); `recModeFlags`
// sets the &3 output-recording tap bits (higher bits are left at their default 0
// here — we only own the tap-point bits).
struct RecordModePlan {
    int recMode      = kRecModeStereoOutLatComp;
    int recModeFlags = kRecOutPostFader;
};

// Maps (channelCount, tap) to the record-mode values.
//   channelCount <= 1 -> mono-out latency-comp; otherwise stereo-out latency-comp.
//   tap               -> the &3 output-recording bits.
// Pure so the "which I_RECMODE for N channels + this tap" rule is unit-tested
// without a DAW; the shell reads the request and applies these via
// SetMediaTrackInfo_Value(I_RECMODE / I_RECMODE_FLAGS).
RecordModePlan recordModePlanFor(int channelCount, OutputTap tap);

// Maps a wetDry value to the output tap point. 1.0 (fully wet) -> PostFader; any
// value < 1.0 -> PreFx (true dry — the realtime backend's distinguishing
// capability). Kept pure + separate from recordModePlanFor so the wet/dry ->
// tap decision is tested on its own; PostFxPreFader is not selected by wetDry
// (it is an explicit future option, not on the wet/dry axis).
OutputTap outputTapForWetDry(double wetDry);

// --- Recorded-file -> Sample mapping ----------------------------------------
//
// The inputs a finished realtime capture yields, gathered by the shell into a
// pure struct so the Sample population is a single tested transform (mirror of
// the inline population in OfflineRenderBackend::capture).
struct RecordedCapture {
    // Project-relative path of the recorded file (relative-paths-only invariant;
    // the shell resolves REAPER's recorded absolute path back to project-relative).
    std::string relativePath;

    // The disambiguating tag that named the file (feeds the Sample id, so id and
    // file name stay consistent — same discipline as the offline path).
    std::string uniqueTag;

    // Echoed from the request (exact bounds — no re-measuring the file).
    SourceMode  sourceMode   = SourceMode::Realtime;
    double      startSeconds = 0.0;
    double      endSeconds   = 0.0;
    double      wetDry       = 1.0;
    std::string displayName;
    std::vector<std::string> trackGuids;

    int    channelCount = 0;
    int    sampleRate   = 0;   // 0 when the project rate was unknown (as offline)
    double captureTempo = 0.0; // BPM at capture time (shell reads Master_GetTempo)
    // Time signature at capture start (L7 F1; shell reads TimeMap_GetTimeSigAtTime).
    // 0/0 = unstamped (matches the Sample default; formatter renders a blank read-out).
    int    captureTimeSigNum   = 0;
    int    captureTimeSigDenom = 0;
    std::int64_t createdTimestamp = 0;  // unix epoch seconds (shell reads the clock)
};

// Builds the Sample for a finished realtime capture. Deliberately identical in
// shape to OfflineRenderBackend's population: exact request bounds (no rounding),
// scratch tier, empty content hash (does not dedup), lengthSeconds = end - start.
// PPQ/beats are left 0 (a musical-placement concern deferred exactly as offline).
Sample sampleFromRecordedCapture(const RecordedCapture& cap);

// --- Async record-phase state machine (M8 rework) ----------------------------
//
// A realtime record spans many timer ticks (CSurf_OnRecord starts the transport on
// REAPER's audio thread and returns immediately — it does NOT block until the range
// completes). The completion decision — "given where the transport is now, should
// the tick keep waiting, stop-and-flush, finalize, or give up?" — is pure and
// exactly the kind of off-by-one/edge logic a unit test locks without a DAW. It is
// factored out here; the REAPER shell only reads the transport/clock/file and applies
// the verdict (stop, wait for the file to flush, then finalize/abort + restore).
//
// The lifecycle has TWO waits, not one:
//   1. the RECORD wait (Recording): the transport is running; we wait for the play
//      cursor to reach the range end — OR the user stops early — OR a wall-clock
//      safety ceiling trips (a started-but-never-advancing transport, §3 of review).
//   2. the FLUSH wait (Finalizing): the transport is stopped but REAPER closes/flushes
//      the recorded take on the AUDIO thread — the file may not be fully written/closed
//      for a tick or two. We defer the file move until the file exists AND is stable
//      (§2 of review), bounded by a flush ceiling so a file that never appears fails
//      cleanly rather than hanging.

// Where an in-progress capture is in its lifecycle.
//   Recording  — live: transport running, shell keeps ticking.
//   Finalizing — live-but-stopped: transport halted, shell stops the transport once
//                then ticks waiting for the recorded file to flush/stabilize.
//   Done       — terminal: the file is flushed + stable, finalize (move + Sample) now.
//   Failed     — terminal: the flush ceiling tripped without a stable file — give up
//                (RenderFailed) + restore. (A record that produced NO file at all also
//                lands here via the shell's finalize returning RenderFailed.)
// Only Recording and Finalizing are live phases the shell advances per tick; Done and
// Failed are the shell's verdict to act on (finalize-or-fail, then restore).
enum class RecordPhase {
    Recording,
    Finalizing,
    Done,
    Failed
};

// A distilled transport reading for the pure transition, so the state machine never
// touches a REAPER type. `recording` is (GetPlayStateEx & 4) != 0; `playPosition`
// is GetPlayPositionEx (latency-compensated what-you-hear position).
struct TransportReading {
    bool   recording    = false;
    double playPosition = 0.0;
};

// Everything the pure transition needs beyond the current phase, gathered by the
// shell each tick so the machine stays REAPER-free AND owns every timing/ceiling
// decision (the shell only reads and reports; it never decides a transition itself).
struct RecordTickInputs {
    TransportReading transport;

    // Wall-clock seconds since begin() (the shell reads a steady clock). Drives the
    // record safety ceiling: a transport that starts but never advances to the range
    // end (stuck / looping) would otherwise keep the machine in Recording forever.
    double elapsedSeconds = 0.0;

    // Wall-clock seconds spent in the Finalizing phase (since the transport stop).
    // Drives the flush ceiling: bound the deferred-finalize wait so a file that never
    // stabilizes fails cleanly instead of hanging.
    double finalizingSeconds = 0.0;

    // Whether the recorded take's file exists AND is stable/closed this tick (the
    // shell resolves the take source path and checks size-stable-across-a-tick).
    // Only consulted in Finalizing.
    bool fileReady = false;
};

// --- Safety ceilings (named constants, review §2/§3) -------------------------
//
// kRecordMarginSeconds: added to the record's nominal duration (end - start) to form
// the record wall-clock ceiling. Generous so a normal record (with pre-roll, count-in,
// or transport latency) never trips it; tight enough that a stuck transport is force-
// terminated within a few seconds of overrun.
inline constexpr double kRecordMarginSeconds = 5.0;

// kFinalizeFlushCeilingSeconds: the max wall-clock the Finalizing phase waits for the
// recorded file to flush/stabilize before giving up (RenderFailed). REAPER closes the
// take on the audio thread within a tick or two in practice; this is a generous bound.
inline constexpr double kFinalizeFlushCeilingSeconds = 5.0;

// The pure transition: given the current phase, this tick's inputs, and the record
// range end, return the next phase. Total + deterministic.
//
// From Recording:
//   * recording AND cursor < end AND under the record ceiling -> Recording (wait)
//   * recording AND cursor >= end                             -> Finalizing (reached end)
//   * NOT recording                                           -> Finalizing (stopped early)
//   * recording BUT over the record ceiling (end-start+margin)-> Finalizing (stuck: forced)
// From Finalizing:
//   * fileReady                                               -> Done (flushed + stable)
//   * over the flush ceiling without a stable file            -> Failed (give up)
//   * otherwise                                               -> Finalizing (keep flushing)
// Done and Failed are sticky: feeding a terminal phase back returns it unchanged, so a
// late tick before teardown finishes cannot flip the verdict (the idempotence the
// shell's single-restore relies on).
RecordPhase advanceRecordPhase(RecordPhase current,
                               const RecordTickInputs& inputs,
                               double rangeStartSeconds,
                               double rangeEndSeconds);

// True once the shell must STOP the transport and begin the flush wait — i.e. the
// phase has left Recording (Finalizing/Done/Failed). Used by the shell to fire the
// (idempotent) transport stop exactly on the Recording -> Finalizing edge.
bool isStopRequested(RecordPhase phase);

// True for the phases the shell must ACT on to conclude (finalize-or-fail + restore).
// Only Done and Failed are terminal; Recording and Finalizing are live.
bool isTerminalPhase(RecordPhase phase);

} // namespace reasampler
