// prune_action.cpp — see prune_action.h for the contract this TU preserves.
// main.cpp owns the API pointers; this TU gets them extern.

#include "shell/actions/prune_action.h"

#include <string>
#include <vector>

#include "core/version/app_version.h"  // extStateNamespace — the printed recovery line must be channel-correct
#include "shell/persist/session.h"  // ReaSamplerSession — pruneDryRun / pruneOrphanSet / pruneReclaim

#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_ShowConsoleMsg
#define REAPERAPI_WANT_ShowMessageBox
#include "reaper_plugin_functions.h"

namespace reasampler {

// The guarded DESTRUCTIVE step and the SOLE file-deletion entry in ReaSampler.
// Dry-run FIRST (read-only); only when orphans exist, a blocking CONFIRM with the
// manifest; on explicit Yes, delete EXACTLY that set (recomputed fresh — confirmed ∩
// freshOrphans). Zero orphans => informational only, no confirm shown. No ext-state
// write, no undo point (file deletion is not REAPER-undoable).
void doBankPruneFolder(ReaSamplerSession& session) {
    const reclaim::PruneReport report = session.pruneDryRun();

    // FAIL-SAFE: tracking state the authority could not read makes the protected
    // set unknowable, so the prune HALTS outright rather than proceed with degraded
    // protection. Both blockers can fire at once; report each one that did.
    if (report.blockedByTracking) {
        // Every printed recovery line names THIS build's ext-state namespace: a beta
        // user handed the stable spelling would clear the stable channel's key in
        // their project and still be blocked.
        const std::string& ns = version::extStateNamespace();
        std::string msg =
            "ReaSampler prune: ABORTED -- the file-tracking state could not be read. "
            "Nothing was deleted.\n";
        if (report.ledgerUnreadable) {
            msg += "The stored file-tracking ledger is malformed. It has been left "
                   "intact rather than overwritten, so it can be repaired or cleared:\n"
                   "  reaper.SetProjExtState(0, \"" + ns + "\", \"owned_files\", \"\")\n"
                   "Clearing it makes every existing bank file un-reclaimable (they stop "
                   "being attributable to ReaSampler); no file is lost. Reopen the "
                   "project afterwards -- the block is held for the rest of this "
                   "session, and until then no new capture is persisted to the ledger "
                   "either.\n";
        }
        if (report.ledgerFutureVersion) {
            msg += "The stored file-tracking ledger was written by a NEWER version of "
                   "ReaSampler than this one, so its records cannot be read safely. It "
                   "has been left intact and will NOT be overwritten. Reopen the project "
                   "with that newer version -- do NOT clear this key from here, that "
                   "would discard tracking records this build cannot see. The block is "
                   "held for the rest of this session, and until then no new capture is "
                   "persisted to the ledger either.\n";
        }
        if (!report.unreadableUsageKeys.empty()) {
            msg += "One or more instance usage records could not be read or decoded. "
                   "If the owning instance is still loaded it will republish its record "
                   "on the next poll tick, clearing the abort. If the instance no longer "
                   "exists (the key is an orphaned corrupt record), clear it manually:\n"
                   "  reaper.SetProjExtState(0, \"" + ns + "\", \"<key>\", \"\")\n"
                   "Offending key(s):\n";
            for (const std::string& key : report.unreadableUsageKeys) {
                msg += "  " + key + "\n";
            }
        }
        ShowConsoleMsg(msg.c_str());
        return;
    }

    if (report.count == 0) {
        ShowConsoleMsg("ReaSampler prune: no orphaned files to reclaim.\n");
        return;
    }

    // The EXACT (untruncated) set the delete will target, captured before the confirm
    // so confirm and delete reason about the same enumeration.
    const std::vector<std::string> orphanSet = session.pruneOrphanSet();

    std::string msg =
        "ReaSampler prune will PERMANENTLY reclaim " + std::to_string(report.count) +
        " orphaned file(s), freeing " + std::to_string(report.totalBytes) + " bytes.\n\n"
        "These files are no longer referenced by any bank and were created by ReaSampler.\n"
        "They will be moved to the Recycle Bin on Windows (recoverable), or deleted on "
        "other platforms.\n\n";
    for (const std::string& rel : report.orphans) msg += "  " + rel + "\n";
    if (report.truncated) {
        msg += "  ... (" + std::to_string(report.count - report.orphans.size()) +
               " more not shown)\n";
    }
    msg += "\nReclaim these files now?";

    const int r = ShowMessageBox(msg.c_str(), "ReaSampler: prune bank folder", 4);
    if (r != 6) {  // 6 == YES; anything else cancels -> delete NOTHING (SDK ~6544)
        ShowConsoleMsg("ReaSampler prune: cancelled -- nothing deleted.\n");
        return;
    }

    const reclaim::PruneDeletionResult del = session.pruneReclaim(orphanSet);

    std::string done = "ReaSampler prune: reclaimed " +
                       std::to_string(del.reclaimedCount) + " file(s), " +
                       std::to_string(del.reclaimedBytes) + " bytes" +
                       (del.usedTrash ? " (to Recycle Bin)" : " (deleted)") + ".";
    if (del.skippedCount > 0) {
        done += " " + std::to_string(del.skippedCount) +
                " file(s) skipped (locked, or changed since the report).";
    }
    done += "\n";
    ShowConsoleMsg(done.c_str());
}

}  // namespace reasampler
