#pragma once
// prune_reconcile — the safety-critical "which files are orphans" decision, computed
// with NO filesystem I/O and NO REAPER types. Unit-tested outside the DAW before any
// I/O exists — this decides which bytes get deleted.
//
// The one computation:
//     orphans = (owned ∩ present) − referenced
//   * present    — files enumerated in the resolved current bank folder (shell).
//   * referenced — every project-relative path referenced by ANY bank in the book,
//                  pool included (union across the whole book — see BankBook::
//                  referencedPaths). A file referenced by any bank — including via a
//                  COPY into a second bank — is NEVER an orphan (the prune null test).
//   * owned      — the tracking ledger's paths: files the bank system itself created.
//                  A present-but-unowned (hand-dropped) file is NEVER reclaimed.
//
// The three guardrails fall straight out of the set algebra:
//   * ∩ present    — never proposes deleting a file that is not on disk (an owned-
//                    but-absent ledger record yields no orphan, no error).
//   * ∩ owned      — never a hand-dropped file (ownership attribution).
//   * − referenced — never a file any bank references (union safety, prune null test).
//
// Path representation: EXACT-STRING match everywhere — Sample.relativePath,
// OriginLedger::contains, BankModel all use raw std::string equality: no
// separator normalization, no case-folding, no trailing-slash trimming. Feeding a
// consistent spelling across the three inputs is the shell's contract (it enumerates
// the folder, unions the book, and reads the ledger against the SAME resolved
// current folder). Diverging from exact match here (e.g. case-insensitive compare)
// would be the unsafe direction — it could let one spelling of a referenced file be
// treated as an orphan under another.

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

namespace reasampler::reclaim {

// The dry-run prune result — report only, no deletion. The shell fills this from
// pruneOrphans() + a per-file size stat; a confirmed delete later acts on the SAME
// set behind the confirm guardrail. Filesystem-free by design (the shell does the
// I/O; this is the tallied outcome), so the aggregation is unit-testable.
//
//   * count       — number of orphan files (authoritative tally, exact even when
//                   `orphans` below is a truncated display list).
//   * totalBytes  — sum of the on-disk sizes of the orphan files, in bytes. A file
//                   the stat could not size contributes 0 (never negative).
//   * orphans     — the orphan file list, project-relative, in folder-enumeration
//                   order (deterministic). MAY be truncated for a large set (the
//                   shell's display cap); `count` stays exact regardless.
//   * truncated   — true iff `orphans` holds fewer than `count` entries.
//   * blockedByTracking — true iff the tracking authority could not answer the
//                   protection question: the orphan computation was NOT performed
//                   (count 0, empty list) and the prune must HALT — deleting with
//                   degraded protection is the data-loss direction. Set by the scan
//                   shell, never by buildPruneReport (which stays a pure tally).
//   * ledgerUnreadable / ledgerFutureVersion / unreadableUsageKeys — which side
//                   blocked, so the action can tell the operator what to recover. The
//                   two ledger flags need opposite advice (a corrupt blob may be
//                   cleared; a newer build's blob must not be). The key names are the
//                   exact "rsusage_<guid>" spellings.
struct PruneReport {
    std::size_t              count = 0;
    std::uint64_t            totalBytes = 0;
    std::vector<std::string> orphans;
    bool                     truncated = false;
    bool                     blockedByTracking = false;
    bool                     ledgerUnreadable = false;
    bool                     ledgerFutureVersion = false;
    std::vector<std::string> unreadableUsageKeys;
};

// The outcome of an actual prune DELETION. The shell fills this as it deletes the
// confirmed orphan set, reporting what it ACTUALLY reclaimed (not what it intended)
// so a locked/vanished file shows up as a skip, never a false claim. Filesystem-free
// by design, so the count/byte aggregation is unit-testable.
//
//   * reclaimedCount — files actually removed from disk BY THIS CALL (trash or
//                      unlink). Already-absent files are NOT counted.
//   * reclaimedBytes — sum of the on-disk sizes of the files actually removed.
//   * skippedCount   — files not reclaimed: stale entries dropped from the
//                      fresh-orphan intersection, files that vanished between plan
//                      and delete, and real delete failures. Never an error/crash.
//   * usedTrash      — true iff deletions were routed to the OS trash/recycle bin
//                      (recoverable); false iff the platform fell back to hard unlink.
struct PruneDeletionResult {
    std::size_t   reclaimedCount = 0;
    std::uint64_t reclaimedBytes = 0;
    std::size_t   skippedCount = 0;
    bool          usedTrash = false;
};

// Computes the prune orphan set: (owned ∩ present) − referenced.
//
// Returns the subset of `present` that is BOTH owned AND unreferenced, in the order
// they appear in `present` (deterministic — mirrors the insertion-order determinism
// the index/ledger keep). Duplicate spellings within `present` are de-duplicated
// in the result (a folder enumeration yields distinct names, but the core does not
// rely on that).
//
// Pure: no I/O, no REAPER, no hidden state. All three inputs are project-relative
// path strings, compared by exact std::string equality (see header note).
std::vector<std::string> pruneOrphans(const std::vector<std::string>& present,
                                      const std::vector<std::string>& referenced,
                                      const std::vector<std::string>& owned);

// Unions two referenced-path sets into one: the bank's own referencedPaths() PLUS
// the paths held by live ReaSampler 9000 instances (sample_usage::usageHeldPaths).
// Order-preserving (`primary` first, then the `extra` paths not already present),
// exact-string de-dup — the same comparison convention as everything above, so
// feeding the result to pruneOrphans keeps the `− referenced` guardrail byte-exact.
// A path held ONLY by an instance (its bank entry was deleted while the instance
// kept its ref) is protected exactly like a bank-referenced one.
//
// Pure: no I/O, no REAPER. Kept here (not in the shells) so "instance usage makes a
// file un-prunable" is provable at the prune layer itself.
std::vector<std::string> mergeReferenced(const std::vector<std::string>& primary,
                                         const std::vector<std::string>& extra);

// Tallies a dry-run PruneReport from a computed orphan set and a per-path size
// lookup. Pure (no I/O): the shell does the folder stat and passes sizes in
// `sizeByPath`; this owns the count/byte-sum/display-truncation decision.
//
//   * count      == orphans.size() (authoritative tally, exact regardless of the cap).
//   * totalBytes == sum of sizeByPath[o] over EVERY orphan o (not just displayed); a
//                   path missing from sizeByPath contributes 0 (never negative).
//   * orphans    == the first `displayCap` orphans in input order (the deterministic
//                   order pruneOrphans preserved); the whole set when count <=
//                   displayCap. displayCap == 0 means "no display cap".
//   * truncated  == count > orphans.size().
//
// Kept separate from pruneOrphans so the safety-critical set algebra stays a pure
// function of three sets, while the presentation tally is its own testable step.
PruneReport buildPruneReport(const std::vector<std::string>& orphans,
                             const std::unordered_map<std::string, std::uint64_t>& sizeByPath,
                             std::size_t displayCap);

// Computes the confirm-time delete plan: the intersection of the set the user was
// SHOWN and confirmed (`confirmed`) with a FRESH pure-core orphan output
// (`freshOrphans`) taken at delete time. Returns exactly `confirmed ∩ freshOrphans`,
// in the order of `confirmed` (deterministic — the same order the confirm listed).
//
// This is the staleness guard, and it protects in BOTH directions so that "what was
// shown is what is deleted" holds no matter what changed between confirm and delete:
//   * A confirmed path that is NO LONGER a fresh orphan — vanished (gone from
//     `present`), now referenced (gone from `− referenced`), or ownership changed —
//     is DROPPED (a skip, never a wrongful delete of a now-referenced file). Because
//     `freshOrphans` is itself a pure-core output, the plan can never contain a
//     referenced or hand-dropped file: the guard survives recompute.
//   * A path that became an orphan AFTER the confirm (in `freshOrphans` but not
//     `confirmed`) is NOT deleted — it was never shown, so it's never swept without
//     its own confirm.
//
// Pure: no I/O, no REAPER. Duplicate spellings in `confirmed` are de-duplicated in
// the result (mirrors pruneOrphans; a confirmed set from a real scan holds distinct
// names).
std::vector<std::string> pruneDeletePlan(const std::vector<std::string>& confirmed,
                                         const std::vector<std::string>& freshOrphans);

} // namespace reasampler::reclaim
