#include "core/capture/capture_paths.h"

#include <cassert>
#include <cctype>
#include <filesystem>

namespace reasampler::capture {

std::string normalizeSlashes(const std::string& path) {
    std::string out = path;
    for (char& c : out) {
        if (c == '\\') c = '/';
    }
    // Strip a trailing slash but preserve a lone "/" (root).
    if (out.size() > 1 && out.back() == '/') {
        out.pop_back();
    }
#ifdef _WIN32
    for (char& c : out) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
#endif
    return out;
}

std::string sanitizeStem(const std::string& baseName) {
    std::string out;
    out.reserve(baseName.size());
    for (unsigned char c : baseName) {
        const bool keep = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
                          (c >= '0' && c <= '9') || c == '.' || c == '_' ||
                          c == '-';
        out.push_back(keep ? static_cast<char>(c) : '_');
    }
    // Collapse to a stable default if nothing alnum survived.
    bool hasAlnum = false;
    for (unsigned char c : out) {
        if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
            (c >= '0' && c <= '9')) {
            hasAlnum = true;
            break;
        }
    }
    if (out.empty() || !hasAlnum) {
        return "capture";
    }
    return out;
}

RenderPaths deriveRenderPaths(const std::string& absoluteDir,
                              const std::string& baseName,
                              const std::string& uniqueTag) {
    std::string stem = sanitizeStem(baseName);
    if (!uniqueTag.empty()) {
        stem += "_" + sanitizeStem(uniqueTag);
    }

    RenderPaths r;
    r.fileStem    = stem;              // stem only — REAPER appends the extension
    r.fileName    = stem + ".wav";
    r.absoluteDir = normalizeSlashes(absoluteDir);
    return r;
}

BankPaths deriveBankPaths(const std::string& projectDir,
                          const std::string& baseName,
                          const std::string& uniqueTag) {
    const std::string dir = normalizeSlashes(projectDir);

    // Precondition: caller must resolve a non-empty project directory — an
    // empty one would otherwise fall back to a bare relative path (forbidden).
    // Assert in debug; leave absoluteDir empty in release so a caller that
    // ignores it fails at the render/stat step, not silently onto CWD.
    assert(!dir.empty() && "deriveBankPaths: projectDir must not be empty");

    const RenderPaths r = deriveRenderPaths(
        dir.empty() ? std::string{} : dir + "/" + kBankSubfolder, baseName, uniqueTag);

    BankPaths p;
    p.fileStem     = r.fileStem;
    p.fileName     = r.fileName;
    p.relativePath = bankRelativeForName(r.fileName);
    p.absoluteDir  = r.absoluteDir;
    return p;
}

std::string bankRelativeForName(const std::string& fileName) {
    if (fileName.empty()) return {};
    return std::string(kBankSubfolder) + "/" + fileName;
}

std::string resolveBankFile(const std::string& projectDir,
                            const std::string& relativePath) {
    if (projectDir.empty() || relativePath.empty()) {
        return {};
    }
    const std::string dir = normalizeSlashes(projectDir);
    const std::string rel = normalizeSlashes(relativePath);
    if (dir.empty() || rel.empty()) {
        return {};
    }
    return dir + "/" + rel;
}

std::string projectDirOfRpp(const std::string& rppPath) {
    if (rppPath.empty()) return {};  // unsaved project: keep empty, no fallback
    std::string dir = std::filesystem::path(rppPath).parent_path().string();
    return normalizeSlashes(dir);
}

BankRelocation deriveRelocationPlan(const std::string& oldProjectDir,
                                    const std::string& newProjectDir) {
    BankRelocation r;
    if (oldProjectDir.empty() || newProjectDir.empty()) {
        return r;  // needed=false, empty dirs — nothing to relocate
    }
    const std::string oldDir = normalizeSlashes(oldProjectDir);
    const std::string newDir = normalizeSlashes(newProjectDir);

    r.oldBankDir = oldDir + "/" + kBankSubfolder;
    r.newBankDir = newDir + "/" + kBankSubfolder;
    r.needed = (oldDir != newDir);  // Save-in-place leaves the dir unchanged
    return r;
}

ProjectTransition classifyProjectTransition(bool sameProjectObject,
                                            const std::string& lastGuid,
                                            const std::string& lastPath,
                                            const std::string& currentGuid,
                                            const std::string& currentPath) {
    // See capture_paths.h for the GUID-primary rationale and rule order.
    if (currentGuid != lastGuid) {
        return ProjectTransition::Load;
    }
    if (!sameProjectObject) {
        return ProjectTransition::Load;  // forked sibling: same GUID, different object
    }
    if (currentPath != lastPath) {
        return ProjectTransition::SaveAsRelocate;
    }
    return ProjectTransition::NoOp;
}

} // namespace reasampler::capture
