#pragma once
// The shared capture seam: CaptureRequest/CaptureResult (types both backends
// speak), OfflineRenderBackend, and the helpers both backends share (naming, the
// mono collapse, the Sample stamp).
// Realtime's async begin/tick/abort surface lives in capture_realtime_shell.h.
//
// REAPER-free on purpose (bank_model only) so callers can depend on the seam
// without dragging the SDK into every include site; the .cpp is the REAPER TU.

#include <string>
#include <vector>

#include "core/model/bank_model.h"
#include "core/capture/capture_name.h"     // CaptureName — label + file-stem base
#include "core/capture/render_settings.h"  // TailMode — the three-state tail contract
#include "core/capture/wav_codec.h"        // MonoCollapseOutcome — the collapse's report

// Forward-declared, never dereferenced here — only the REAPER-facing .cpp touches these.
class MediaTrack;
class ReaProject;

namespace reasampler::capture {

using model::Sample;

// 32-bit float is the default; rationale lives in capture.cpp next to the sink-config bytes.
enum class WavBitDepth {
    Int16,
    Int24,
    Float32,
};

// Where the render lands. TWO VALUES, never a caller-supplied path string: the
// backend resolves each to a directory itself, which is what makes "write into the
// bank folder" inexpressible from the ProjectMedia side and vice versa.
enum class CaptureDestination {
    Bank,          // <projectDir>/reasampler_bank — every capture path
    ProjectMedia,  // the project's recording path — the render-in-place verb only
};

// One capture, independent of source mode.
struct CaptureRequest {
    SourceMode sourceMode = SourceMode::MasterMix;

    // Sample-accurate render bounds in project seconds — NO rounding.
    double startSeconds = 0.0;
    double endSeconds   = 0.0;

    // 1.0 = fully wet, 0.0 = fully dry. Every current capture action sets 1.0;
    // true pre-FX dry isn't available via RENDER_SETTINGS (needs FX-bypass-around-render
    // or the realtime pre-FX path) so this stays a seam for that future work.
    double wetDry = 1.0;

    // Track GUID(s) when source mode is track-scoped (SelectedTracks); empty otherwise.
    // The backend only copies these onto the Sample — it never reads selection itself.
    std::vector<std::string> trackGuids;

    // Render tail (docs/product/capture-tail.md §The three tail states). None = exact
    // bounds, no added silence — the only mode valid for null-test/verify captures.
    // tailMs applies only to Manual (clamped to 8s by the pure mapping); Auto uses
    // the 8s cap + -72 dB trim internally, None ignores it.
    TailMode tailMode = TailMode::None;
    double   tailMs   = 0.0;

    // 0 sampleRate => follow project rate.
    int         sampleRate   = 0;
    // What the RENDER is asked for (RENDER_CHANNELS / the realtime record mode), not
    // what the capture lands as: a dual-mono render is collapsed to 1 channel after
    // the fact, and the Sample's count comes from the produced file.
    int         channelCount = 2;
    WavBitDepth bitDepth      = WavBitDepth::Float32;

    // Sanitized by capture_paths. uniqueTag (disambiguator) is supplied by the
    // backend caller so the pure naming logic stays testable.
    std::string baseName  = "capture";
    std::string uniqueTag;

    // The label the bank shows, which may legitimately differ from the file stem: the
    // stem must survive sanitizeStem, the label carries the source name verbatim. Empty
    // means "the stem base is also the label" — what a caller that names nothing else gets.
    std::string displayName;

    // The one home for that fallback rule; both backends populate Sample::displayName
    // from here rather than each spelling the condition out.
    std::string label() const { return displayName.empty() ? baseName : displayName; }

    // Default Bank: every existing entry point renders into the bank untouched.
    CaptureDestination destination = CaptureDestination::Bank;
};

// Every failure is an explicit code, never a thrown exception across the REAPER boundary.
enum class CaptureStatus {
    Ok,
    NoProject,           // no active project to render / resolve a bank folder
    EmptyRange,          // start >= end: nothing to render
    UnsupportedMode,     // backend does not implement this source mode
    UnsupportedFormat,   // requested bit depth has no known REAPER blob (Float32 only)
    RenderFailed,        // the render action ran but produced no output file
    TransportBusy,       // realtime backend: transport already playing/recording — refused
    MultiTrackSelection, // a selected-tracks render over >1 track — would render N files
    BoundsMismatch,      // the rendered file's frames are not the requested window's — or
                         // could not be measured to say (render_bounds_gate)
};

struct CaptureResult {
    CaptureStatus status = CaptureStatus::RenderFailed;
    Sample        sample;        // valid only when status == Ok
    std::string   message;       // human-readable detail for the console log

    // The file the render actually landed, absolute — the only handle a caller that
    // banks nothing has on its own output (sample.relativePath is empty on the
    // ProjectMedia destination). Set on the Ok path only.
    std::string   absolutePath;
};

// Deterministic offline-render backend: master mix / time selection / selected
// tracks / selected items / razor area, all wet-only, optional tail. Source
// selection + range are resolved by the caller and handed in via CaptureRequest —
// the backend drives RENDER_* and never reads the DAW selection itself.
// SourceMode::Realtime returns UnsupportedMode. Non-destructive: restores every
// RENDER_* setting it touches on every path. Plain concrete class — see the
// no-shared-interface note in capture_realtime_shell.h before adding one back.
class OfflineRenderBackend {
public:
    CaptureResult capture(const CaptureRequest& request);
};

// Mints the filesystem-safe disambiguating tag for one capture's file stem +
// Sample id: "<prefix><unix-epoch-seconds>-<n>", <n> a per-session monotonic
// counter. Wall-clock seconds alone collide when batch capture drives short
// renders back-to-back, silently overwriting the first file. `prefix` is the
// backend's family marker ("" offline, "rt-" realtime).
std::string makeUniqueTag(const std::string& prefix);

// Composes one capture's label + file-stem base (core/capture/capture_name) from the
// resolved source-track names, reading the LOCAL clock for the discriminator — the one
// impure step, kept here so the composition itself stays pure and tested. `ordinal` is a
// batch unit's number (0 for a single capture); `fallback` is the scope literal, used
// only when no source name resolved.
CaptureName captureNameFor(const std::vector<std::string>& sourceNames,
                           int ordinal, const std::string& fallback);

// Rewrites a just-captured WAV in place as a 1-channel file when its channels are
// bit-identical (the pure `collapseToMono` decides). Every other file is left
// untouched, byte for byte, so the not-collapsed path is exactly what the backend
// produced. Must run BEFORE stampCaptureSample, which measures the landed file.
// A Failed outcome is ALSO logged to the console here, because a successful capture's
// CaptureResult::message is not printed by any caller — the return value alone would
// leave a genuine I/O failure indistinguishable from a legitimately stereo capture.
// `consoleLabel` matches each caller's own console-prefix convention (offline:
// "ReaSampler capture"; realtime: "ReaSampler realtime capture").
MonoCollapseOutcome collapseCapturedFileToMono(const std::string& absolutePath,
                                               const char* consoleLabel = "ReaSampler capture");

// Stamps the metadata shared by both backends onto `s`: trackGuids (echoed from the
// request) + channelCount (measured from the produced file's `fmt`; 0/unknown as the
// fallback for a file that cannot be parsed — never the request's value, which is
// always 2 and was never actually measured), resolved sampleRate (request rate,
// else PROJECT_SRATE from `rateProj`), captureTempo, the capture-start time signature
// (TimeMap_GetTimeSigAtTime against `timeSigProj` — offline passes nullptr for the
// active project, realtime pins the record's own project), the WAV-aware
// contentHash of `absolutePath` (left empty when unreadable), and createdTimestamp.
// Per-backend bits (id, paths, bounds, tier, realtime's length override) stay
// with each caller.
void stampCaptureSample(Sample& s, const CaptureRequest& req,
                        ReaProject* rateProj, ReaProject* timeSigProj,
                        const std::string& absolutePath);

} // namespace reasampler::capture
