#pragma once
// prune_reconcile — the pure core of Phase R (Reclaim), Wave 1. The safety-critical
// "which files are orphans" decision, computed with NO filesystem I/O and NO REAPER
// types. The mirror of view_mode_model's reconcile(liveGuids), one level DOWN: it
// reconciles FILES ON DISK against REFERENCED FILES (the union across every bank),
// where reconcile reconciled membership entries against live tracks.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes, NO filesystem calls. Standard library only. Unit-tested outside
// the DAW — this is the safety-critical part (it decides which bytes get deleted in
// R2/R3), so it is hard-tested here before any I/O exists.
//
// -- The one computation ------------------------------------------------------
//
//     orphans = (owned ∩ present) − referenced
//
//   * present    — files enumerated in the resolved current bank folder (R2 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 owned-file manifest: the files the bank system itself created
//                  (OwnedFileManifest). A present-but-unowned (hand-dropped) file is
//                  NEVER reclaimed — prune reclaims only the system's own leavings.
//
// The three settled guardrails fall straight out of the set algebra:
//   * ∩ present    — never proposes deleting a file that is not on disk (an owned-
//                    but-absent manifest entry yields no orphan, no error).
//   * ∩ owned      — never a hand-dropped file (ownership attribution, fork R-D).
//   * − referenced — never a file any bank references (union safety, prune null test).
//
// -- Path representation: EXACT-STRING match (safety-critical) -----------------
//
// Every path in the model is a project-relative string compared VERBATIM: Sample.
// relativePath, OwnedFileManifest::contains (p == relativePath), and BankIndex all
// use raw std::string equality — no separator normalization, no case-folding, no
// trailing-slash trimming. This core MATCHES that convention exactly: it compares
// the raw strings the shell supplies. Feeding a consistent spelling across the three
// inputs is the R2 shell's contract (it enumerates the folder, unions the book, and
// reads the manifest 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 {

// The dry-run prune result (Phase R, Wave 2 — report only, no deletion). The thin
// prune shell (persist) fills this from pruneOrphans() + a per-file size stat and hands
// it to the report surface; R3 will act on the SAME set behind the confirm guardrail.
// REAPER-free / filesystem-free by design (the shell does the I/O; this is just the
// tallied outcome), so the count/size aggregation is unit-testable outside the DAW.
//
//   * count       — number of orphan files (== orphans.size(); the 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 (reclaimable
//                   space). A file the stat could not size contributes 0 (never negative).
//   * orphans     — the orphan file list as project-relative index-spelled paths, in
//                   folder-enumeration order (deterministic). MAY be truncated for a large
//                   set (the shell's display cap); `count` stays exact regardless, and
//                   `truncated` says whether the list was clipped.
//   * truncated   — true iff `orphans` holds fewer than `count` entries (a large set was
//                   clipped for display); false when the list is complete.
struct PruneReport {
    std::size_t              count = 0;
    std::uint64_t            totalBytes = 0;
    std::vector<std::string> orphans;
    bool                     truncated = false;
};

// The outcome of an actual prune DELETION (Phase R, Wave 3 — R3). The prune shell fills
// this as it deletes the confirmed orphan set, reporting what it ACTUALLY reclaimed (not
// what it intended to) so a locked/vanished file shows up as a skip, never a false claim.
// REAPER-free / filesystem-free by design (the shell does the deletion; this is the
// tallied outcome), so the count/byte aggregation is unit-testable outside the DAW.
//
//   * reclaimedCount — number of files actually removed from disk BY THIS CALL (trash or
//                      unlink). Already-absent files are NOT counted here.
//   * reclaimedBytes — sum of the on-disk sizes of the files actually removed, in bytes.
//   * skippedCount   — files that could not be or were not reclaimed: stale entries that
//                      dropped out of the fresh-orphan intersection, files that vanished
//                      between the plan and the delete call (already absent), and real
//                      delete failures (locked, conversion error). Never an error/crash.
//   * usedTrash      — true iff the 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 output — mirror of the insertion-order
// determinism the index / manifest keep; the R2 dry-run reports a stable file list).
// 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);

// 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 the sizes in `sizeByPath`;
// this owns the count / byte-sum / display-truncation decision so it is unit-testable.
//
//   * count      == orphans.size() (the authoritative tally, exact regardless of the cap).
//   * totalBytes == the sum of sizeByPath[o] over EVERY orphan o (not just the displayed
//                   ones); a path missing from sizeByPath contributes 0 (an orphan whose
//                   size could not be stat'd — never negative, never dropped from the sum).
//   * orphans    == the first `displayCap` orphans in input order (the deterministic
//                   folder-enumeration order pruneOrphans preserved); the whole set when
//                   count <= displayCap. displayCap == 0 means "no display cap" (whole set).
//   * truncated  == count > orphans.size() (a large set was clipped for display).
//
// Kept separate from pruneOrphans so the safety-critical set algebra stays a pure function
// of three sets, while the presentation tally (which the R2 dry-run and R3 confirm both
// need) is its own small, 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 R3 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 — a file that vanished (gone from
//     `present`), or that some bank now references (gone from `− referenced`), or whose
//     ownership changed — is DROPPED (a skip, never an error, 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 is 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
