#pragma once
// export_plan — the pure export decision: which bank entries ship, what each one is
// named inside the package, what is absent, and therefore whether the export may
// proceed at all. Values in, verdict out — the shell probes the filesystem and hands
// the results here. Pure: no filesystem, no host types.

#include <string>
#include <vector>

#include "core/model/bank_model.h"
#include "core/model/slot_map.h"
#include "core/package/package_manifest.h"

namespace reasampler::package {

// What the shell's filesystem probe found for one indexed entry. Missing and
// Unreadable stay distinct all the way to the refusal message: the file is gone vs.
// the file is there and will not open, which have opposite recoveries.
enum class SourceFileState {
    Present,
    Missing,
    Unreadable,
};

struct ExportCandidate {
    model::Sample sample;
    SourceFileState fileState = SourceFileState::Missing;
};

// One bank as the planner sees it: the display name that rides in the manifest
// envelope, the members in bank insertion order, and the bank's display positions.
struct ExportInputs {
    std::string bankDisplayName;
    std::vector<ExportCandidate> candidates;
    model::SlotMap slots;
};

// Why an indexed entry cannot ship.
enum class ExclusionReason {
    FileMissing,
    FileUnreadable,
    // The index record itself cannot be written: an empty id, or a relativePath the
    // format's nested-path rule refuses. Not something a confirm can proceed past.
    RecordUnrepresentable,
};

struct ExcludedEntry {
    std::string sampleId;
    std::string displayName;
    std::string relativePath;
    ExclusionReason reason = ExclusionReason::FileMissing;
};

enum class ExportVerdict {
    Ready,       // every candidate ships
    Incomplete,  // a file is absent or unreadable; the rest may ship behind an explicit confirm
    Refused,     // an index record the format cannot represent — no confirm path
};

struct ExportPlan {
    ExportVerdict verdict = ExportVerdict::Ready;

    // Entries in bank order, each carrying its transport name and its record. The
    // shell measures `byteLength`/`byteHash` from the payload, so they are 0/"" here;
    // `exportTimestamp` is the shell's clock read and is 0 here too.
    PackageManifest manifest;

    // Where each shipping entry's bytes are read from, parallel to
    // `manifest.entries` — the record's own relativePath is normalized to the bare
    // package name (see the transport-name note in this directory's CLAUDE.md), so
    // the source spelling has to survive separately.
    std::vector<std::string> sourceRelativePaths;

    std::vector<ExcludedEntry> excluded;
};

ExportPlan planExport(const ExportInputs& in);

// The smallest repair of one bare file name that satisfies isValidEntryName —
// separators, reserved characters and control bytes to '_', an over-long name
// truncated on a UTF-8 boundary, and an underscore prefix for the reserved forms
// ("." / ".." / a DOS device name). Never returns a name isValidEntryName refuses.
// Why sanitize rather than relay the codec's refusal: this directory's own
// CLAUDE.md, "Obligation on the export track."
std::string sanitizeEntryName(const std::string& rawFileName);

}  // namespace reasampler::package
