// realtime_lifecycle.cpp — the in-flight realtime-capture state machine + globals.
// See the header.
//
// Compiled into the reaper_reasampler module. Includes reaper_plugin_functions.h
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API
// pointers; here they are extern.

#include "shell/capture/realtime_lifecycle.h"

#include "shell/persist/session.h"  // ReaSamplerSession

#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_EnumProjects
#define REAPERAPI_WANT_GetProjectName
#define REAPERAPI_WANT_ShowConsoleMsg
#include "reaper_plugin_functions.h"

namespace reasampler::capture {

RealtimeRecordBackend g_rtBackend;
RealtimeCaptureHandle g_rtCapture;
ReaProject*           g_rtCaptureProject = nullptr;

// Commits a finished realtime capture (a Done tick/abort with an Ok result): adds
// the Sample to the active bank, persists + MarkProjectDirty. Shared by the
// tick-completion and abort paths. On a non-Ok result, logs the failure only.
void CommitRealtimeResult(ReaSamplerSession& session, const CaptureResult& res)
{
    if (res.status != CaptureStatus::Ok)
    {
        ShowConsoleMsg(("ReaSampler realtime capture failed: " + res.message + "\n").c_str());
        return;
    }
    session.bank().add(res.sample);
    session.recordCreated(res.sample, tracking::OriginKind::Capture);
    // A capture add changes what a live instance could play, so bump the generation
    // before persisting to refresh instances.
    session.bumpBankGeneration();
    session.saveToActiveProject();  // persist book + ledger + generation + MarkProjectDirty
}

// Advances any in-flight realtime capture one tick. Detects a project switch
// mid-capture and aborts+restores so the capture never leaks across projects.
// Called from OnTimer before session.poll() so poll's own project-switch handling
// sees an already-cleaned-up project.
void DriveRealtimeCapture(ReaSamplerSession& session)
{
    if (!g_rtCapture) return;

    // If the active project is no longer the one the capture belongs to, a project
    // switch happened mid-record: abort + restore into the original project the
    // state is bound to, and drop it — never finalize into the new project.
    ReaProject* active = EnumProjects(-1, nullptr, 0);
    if (active != g_rtCaptureProject)
    {
        RealtimeTickResult r = g_rtBackend.abort(*g_rtCapture);
        // Log without persisting — we restored into the original project but must
        // not persist into the now-active foreign one: the ledger and bank we would
        // have to write belong to the project we just left. A Failed abort surfaces
        // abort()'s own message, distinguishing a clean tab-switch abort from the
        // closed-project case (nothing restored because the pointers were already
        // freed).
        //
        // The abort already moved the recorded WAV into the ORIGINAL project's bank
        // folder, so it survives here untracked — deleting a user's just-recorded
        // audio is the destructive direction and is not this shell's call, so the
        // path is NAMED instead and the operator decides (docs/TODO.md).
        //
        // RealtimeTickStatus::Done implies CaptureStatus::Ok (RealtimeRecordBackend::
        // abort only sets Done on that path) — the Ok branch below is therefore the
        // whole Done case; anything else is Failed and falls to the last branch.
        if (r.status == RealtimeTickStatus::Done && r.result.status == CaptureStatus::Ok) {
            // g_rtCaptureProject is still valid here (reset only below) — it names the
            // ORIGINAL project, not whatever is active now, which is the whole point:
            // the user has already switched away from it.
            char nameBuf[512] = {0};
            GetProjectName(g_rtCaptureProject, nameBuf, sizeof(nameBuf));
            const std::string projName = nameBuf[0] ? nameBuf : "(unsaved project)";
            ShowConsoleMsg(("ReaSampler realtime capture: project switched mid-record -- "
                            "captured audio restored into the original project (" +
                            projName + "); not persisted to avoid crossing projects. The "
                            "recorded file was left in that project's bank folder as '" +
                            r.result.sample.relativePath +
                            "', untracked. Re-importing it there does NOT adopt this file "
                            "-- it copies the audio in under a new name -- so after "
                            "re-importing, delete this untracked original by hand.\n")
                               .c_str());
        } else {
            ShowConsoleMsg(("ReaSampler realtime capture: project changed mid-record -- " +
                            r.result.message + "\n").c_str());
        }
        g_rtCapture.reset();
        g_rtCaptureProject = nullptr;
        return;
    }

    RealtimeTickResult r = g_rtBackend.tick(*g_rtCapture);
    if (r.status == RealtimeTickStatus::InProgress) return;

    // Terminal (Done or Failed): commit/log and drop the in-flight state.
    CommitRealtimeResult(session, r.result);
    g_rtCapture.reset();
    g_rtCaptureProject = nullptr;
}

void AbortRealtimeCaptureForUnload(ReaSamplerSession& session)
{
    if (!g_rtCapture) return;
    RealtimeTickResult r = g_rtBackend.abort(*g_rtCapture);
    CommitRealtimeResult(session, r.result);
    g_rtCapture.reset();
    g_rtCaptureProject = nullptr;
}

}  // namespace reasampler::capture
