#pragma once
// session — the ReaSamplerSession lifecycle owner of the persist seam. One
// class, three implementation TUs by responsibility:
//   * session.cpp      — poll() identity-transition detection (load / Save-As /
//     forked sibling / recycled pointer) + the deferred undo/redo reload drain.
//   * ext_state_io.cpp — save/load/writeAssignmentRequest: the ext-state <->
//     JSON bridge, GUID minting, bank-folder relocation (see ext_state_io.h).
//   * prune_fs.cpp     — pruneDryRun/pruneOrphanSet/pruneReclaim: the prune
//     scan and THE SINGLE FILE-DELETION AUTHORITY over user files in the bank
//     folder. Nothing else in the system deletes bank-folder bytes (a shell's
//     self-cleanup of its own transient scratch file is not this authority).
//
// Save: BankModel JSON -> SetProjExtState under namespace "reasampler" (ext
// state lives inside the .rpp, so the index travels with the project for
// free). Load: GetProjExtState -> deserialize -> resolve each entry's bank
// file against the CURRENT project dir, so a project opened from a new
// location still finds its bank. Save-As: relocate the physical bank folder
// so the wavs end up under the new .rpp; the index's relative paths stay valid.
//
// REAPER-free header — all REAPER API calls live in the three TUs.

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

#include "core/capture/tail_control.h"
#include "core/model/bank_book.h"
#include "core/model/bank_model.h"
#include "core/model/owned_manifest.h"
#include "core/reclaim/prune_reconcile.h"
#include "core/version/app_version.h"
#include "core/view/view_mode_model.h"

namespace reasampler {

// Owns the session's BankBook (pool + named banks) and drives persistence
// against the active REAPER project. One instance lives for the extension's
// lifetime. Tracks the project identity last seen so the timer tick can
// detect a project load (a different project became active, so load the
// index from ext state) vs. a Save-As (same project, path changed, so
// relocate the bank folder under the new .rpp).
//
// Identity is layered GUID-primary: the minted GUID (content-based, immune to
// REAPER recycling a closed project's ReaProject* address) is checked first;
// the live pointer disambiguates only the same-GUID case — a forked sibling
// (same GUID, different object -> Load) vs. a genuine Save-As (same GUID,
// same object, new path -> relocate). Two prior designs each broke one
// direction: GUID-only misread a Save-As fork as the parent project;
// pointer-primary misread a recycled ReaProject* address as no-op. GUID-first
// catches recycling; the pointer then separates fork from Save-As.
//
// The book is exposed for the capture/action layer to mutate; persist only
// reads it on save and replaces it on load.
class ReaSamplerSession {
public:
    ReaSamplerSession() = default;

    // Pool + named banks + active-bank id; persist serializes under `banks`.
    BankBook& book() { return book_; }
    const BankBook& book() const { return book_; }

    // The capture add-target: the active bank's BankModel (defaults to the pool).
    model::BankModel& bank() { return book_.activeIndex(); }
    const model::BankModel& bank() const { return book_.activeIndex(); }

    // Design-View model; persists MODEL STATE only (visibility on open is the view shell's job).
    ViewModeModel& view() { return view_; }
    const ViewModeModel& view() const { return view_; }

    // Docked panel's tail setting, authoritative here so it travels inside the .rpp.
    capture::TailSetting& tail() { return tail_; }
    const capture::TailSetting& tail() const { return tail_; }

    // Project-relative files the capture path itself created; prune consumes it.
    model::OwnedFileManifest& owned() { return owned_; }
    const model::OwnedFileManifest& owned() const { return owned_; }

    // The version that last wrote the active project: PreVersioning (no
    // stamp), Unknown (malformed), or Stamped.
    const version::WritingVersion& writingVersion() const { return writingVersion_; }

    // Monotonic per project; recovered on load, written on every saveToActiveProject().
    std::int64_t bankGeneration() const { return bankGeneration_; }

    // Call at every bank-CONTENT mutation that changes what a live instance
    // would play, NOT the organizational verbs (create/rename/reorder a
    // bank). Rides the next persist. Over-bumping is safe; under-bumping
    // misses a hands-free refresh, so call sites err toward bumping.
    void bumpBankGeneration() { ++bankGeneration_; }

    // Serializes book/view/tail to ext state, clears the retired legacy
    // `bank_index` key. No-ops with no active/saved project. Returns true iff
    // a persist happened, so a caller can skip an undo block when nothing was written.
    bool saveToActiveProject();

    // Report-only prune dry-run: feeds the pure core with (present,
    // referenced, owned), where `referenced` = book references union every
    // live instance's held captures (usage_scan + sample_usage decide
    // liveness). FAIL-SAFE: an unreadable usage record sets
    // abortedUnreadableUsage with an EMPTY orphan set. Read-only throughout.
    reclaim::PruneReport pruneDryRun() const;

    // The full (untruncated) orphan set, same compute as pruneDryRun. The
    // prune action confirms this set before deleting it. Read-only.
    std::vector<std::string> pruneOrphanSet() const;

    // Delete the confirmed orphan set — the sole file-deletion path,
    // callable only after an explicit user confirm. Re-enumerates and runs
    // the pure core fresh, deleting exactly `confirmed ∩ freshOrphans` so a
    // file that vanished or became referenced since confirm is skipped, and
    // an orphan the user did not see is never swept. Trash-preferred
    // (Windows Recycle Bin; unlink elsewhere). Does not modify the book or
    // OwnedFileManifest, writes no ext-state. No-ops when nothing to delete;
    // does not prompt.
    reclaim::PruneDeletionResult pruneReclaim(
        const std::vector<std::string>& confirmed) const;

    // Write the ingest assignment request (`assign_request` key): "the active
    // sampler instance should now play THIS sample." `wire` is pre-encoded
    // (assignment_request.h); a sibling one-shot write, not part of
    // saveToActiveProject's blob. Returns true iff written.
    bool writeAssignmentRequest(const std::string& wire);

    // Detects a project load or Save-As and reacts. Driven by REAPER's
    // "timer" register; idempotent per tick. Also drains a pending undo/redo
    // reload (requestReload): the identity classifier alone would read an
    // undo/redo as NoOp since identity is unchanged, so the projectconfig
    // hook's reload flag is honored FIRST, before the identity check.
    void poll();

    // Request a reload of book_/view_/tail_ on the next poll() tick. Raised
    // by the projectconfig hook only on an undo/redo state restore. Deferred
    // because the hook fires BEFORE REAPER restores the <EXTSTATE> block —
    // reading synchronously there would return the pre-undo value.
    void requestReload();

    // Load signal for the reapply-on-open glue: poll() raises this whenever
    // it (re)loads the view model; consumeLoadSignal() returns true once and
    // clears it. Signal-based since persist stays model-only (never calls
    // the view shell); main.cpp owns the glue.
    bool consumeLoadSignal();

private:
    BankBook book_;
    ViewModeModel view_;             // reset to default on a project with no stored view_state
    capture::TailSetting tail_;      // reset to default (None / 2s) with no stored tail key
    model::OwnedFileManifest owned_; // reset to empty/stored on EVERY load path, never inherited
    version::WritingVersion writingVersion_;  // recovered per load; PreVersioning default
    std::int64_t bankGeneration_ = 0;         // recovered per load (absent -> 0); monotonic

    // Project identity last observed by poll(). GUID is primary; the pointer
    // disambiguates the same-GUID case. Held as void* (compare-only, never
    // dereferenced) so the header stays REAPER-free.
    void*       lastProject_ = nullptr;
    std::string lastGuid_;                // "" until the first saved project is seen
    std::string lastRppPath_;
    bool        primed_ = false;          // false until the first poll() observes state
    bool        loadPending_ = false;     // raised by loadFromProject; drained by consumeLoadSignal
    bool        reloadRequested_ = false; // raised by requestReload; drained by poll

    // Load the book from `proj`'s ext state (`banks`, else legacy
    // `bank_index` migrated into the pool); also restores view_/tail_/owned_.
    void loadFromProject(void* proj, const std::string& projectDir);
};

} // namespace reasampler
