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

#include "shell/actions/package_export_action.h"

#include <cstddef>
#include <cstdint>
#include <ctime>
#include <string>
#include <vector>

#include "core/capture/capture_paths.h"  // projectDirOfRpp, sanitizeStem
#include "shell/package/export_bank.h"
#include "shell/package/package_pickers.h"
#include "shell/persist/session.h"

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

namespace reasampler {

namespace {

constexpr const char* kUnsavedProjectMsg =
    "ReaSampler export: save the project first -- an unsaved project has no bank folder "
    "to read from.\n";

std::string currentProjectDir() {
    std::vector<char> buf(4096, '\0');
    EnumProjects(-1, buf.data(), static_cast<int>(buf.size()));
    return capture::projectDirOfRpp(std::string(buf.data()));
}

// 6 == YES; anything else cancels (SDK ~6544).
bool confirmed(const std::string& msg, const char* title) {
    return ShowMessageBox(msg.c_str(), title, 4) == 6;
}

std::string entryLine(const package::ExcludedEntry& e) {
    const char* why = e.reason == package::ExclusionReason::FileMissing      ? "missing"
                      : e.reason == package::ExclusionReason::FileUnreadable ? "unreadable"
                                                                             : "unusable index record";
    return "  " + (e.displayName.empty() ? e.sampleId : e.displayName) + "  [" + why +
           "]  " + e.relativePath + "\n";
}

// `maxLines` == 0 lists everything (the console record); a positive cap keeps a
// confirm dialog readable on a bank with hundreds of absent files, prune's own
// truncate-the-confirm-not-the-report discipline.
std::string excludedManifest(const std::vector<package::ExcludedEntry>& excluded,
                             std::size_t maxLines) {
    std::string msg;
    std::size_t shown = 0;
    for (const package::ExcludedEntry& e : excluded) {
        if (maxLines != 0 && shown == maxLines) {
            msg += "  ... (" + std::to_string(excluded.size() - shown) +
                   " more, listed in the console)\n";
            break;
        }
        msg += entryLine(e);
        ++shown;
    }
    return msg;
}

void reportOutcome(const ExportOutcome& out, const std::string& destPath) {
    switch (out.status) {
        case ExportStatus::Written:
            ShowConsoleMsg(("ReaSampler export: wrote " + std::to_string(out.entriesWritten) +
                            " entry/entries (" + std::to_string(out.bytesWritten) +
                            " bytes) to " + destPath + "\n")
                               .c_str());
            return;
        case ExportStatus::SourceReadFailed:
            ShowConsoleMsg(("ReaSampler export: ABORTED -- \"" + out.offendingName +
                            "\" could not be read. Nothing was written.\n")
                               .c_str());
            return;
        case ExportStatus::SourceChanged:
            ShowConsoleMsg(("ReaSampler export: ABORTED -- \"" + out.offendingName +
                            "\" changed on disk while the package was being written. "
                            "Nothing was written; run the export again.\n")
                               .c_str());
            return;
        case ExportStatus::EncodeFailed:
            ShowConsoleMsg("ReaSampler export: ABORTED -- this bank could not be encoded "
                           "as a package. Nothing was written.\n");
            return;
        // Both refusals are re-derived from a FRESH plan, so reaching them after the
        // survey means the bank changed under the export, not that the user declined.
        case ExportStatus::RefusedIncomplete:
        case ExportStatus::RefusedUnrepresentable:
            ShowConsoleMsg("ReaSampler export: ABORTED -- the bank changed between the "
                           "report and the write. Nothing was written; run the export "
                           "again.\n");
            return;
        case ExportStatus::RefusedDestinationExists:
            ShowConsoleMsg("ReaSampler export: cancelled -- nothing was written.\n");
            return;
        case ExportStatus::NoSuchBank:
            ShowConsoleMsg("ReaSampler export: that bank no longer exists.\n");
            return;
        case ExportStatus::NoProjectDir:
            ShowConsoleMsg(kUnsavedProjectMsg);
            return;
        case ExportStatus::WriteFailed:
            ShowConsoleMsg(("ReaSampler export: FAILED writing " + destPath +
                            ". No package was left behind; any file already at that path is "
                            "untouched.\n")
                               .c_str());
            return;
    }
}

}  // namespace

void doBankPackageExport(const ReaSamplerSession& session, const std::string& bankId) {
    const std::string projectDir = currentProjectDir();
    if (projectDir.empty()) {
        ShowConsoleMsg(kUnsavedProjectMsg);
        return;
    }

    // Report before acting, and before the picker opens: a refusal the user cannot
    // act on should not cost them a trip through a save dialog first.
    const ExportSurvey survey = surveyBankExport(session, projectDir, bankId);
    if (!survey.bankFound) {
        ShowConsoleMsg("ReaSampler export: no such bank.\n");
        return;
    }
    const std::string bankName = survey.plan.manifest.bankDisplayName;

    bool allowIncomplete = false;
    if (survey.plan.verdict == package::ExportVerdict::Refused) {
        ShowConsoleMsg(("ReaSampler export: ABORTED -- \"" + bankName +
                        "\" holds index record(s) a package cannot carry. Nothing was "
                        "written.\n" +
                        excludedManifest(survey.plan.excluded, 0))
                           .c_str());
        return;
    }
    if (survey.plan.verdict == package::ExportVerdict::Incomplete) {
        const std::string headline =
            "ReaSampler export: \"" + bankName + "\" has " +
            std::to_string(survey.plan.excluded.size()) +
            " entry/entries whose file is missing or unreadable:\n";
        ShowConsoleMsg((headline + excludedManifest(survey.plan.excluded, 0)).c_str());
        if (!confirmed(headline + excludedManifest(survey.plan.excluded, 10) +
                           "\nExport the " +
                           std::to_string(survey.plan.manifest.entries.size()) +
                           " present entry/entries anyway?",
                       "ReaSampler: incomplete bank")) {
            ShowConsoleMsg("ReaSampler export: cancelled -- nothing was written.\n");
            return;
        }
        allowIncomplete = true;
    }

    // The bank's own name, not the project's: the artifact is a bank, and a user
    // exporting three banks from one project needs three distinguishable files.
    const std::string suggested =
        projectDir + "/" + capture::sanitizeStem(bankName) + ".rsbank";
    std::string dest;
    bool appended = false;
    if (!pickPackageSavePath(suggested, dest, &appended)) return;  // user cancelled the picker

    ExportRequest req;
    req.projectDir = projectDir;
    req.bankId = bankId;
    req.destAbsPath = dest;
    req.exportTimestamp = static_cast<std::int64_t>(std::time(nullptr));
    req.allowIncomplete = allowIncomplete;
    // The dialog's own overwrite confirm covered exactly this path when the picker
    // did not need to append `.rsbank` to reach it — asking again would be a second
    // prompt for the same consent. An appended path is one the dialog never saw, so
    // that case still falls through to exportBank's own refusal and the confirm below.
    req.allowOverwrite = !appended;

    ExportOutcome out = exportBank(session, req);
    if (out.status == ExportStatus::RefusedDestinationExists) {
        if (!confirmed("A file already exists at:\n\n  " + dest +
                           "\n\nReplace it with this bank package?",
                       "ReaSampler: replace package")) {
            ShowConsoleMsg("ReaSampler export: cancelled -- nothing was written.\n");
            return;
        }
        req.allowOverwrite = true;
        out = exportBank(session, req);
    }
    reportOutcome(out, dest);
}

}  // namespace reasampler
