// app_version.cpp — implementation of the pure version-identity core. See
// app_version.h for the contract. The version STRING and the channel bit both
// come from version_generated.h (CMake configure_file from REASAMPLER_VERSION +
// REASAMPLER_CHANNEL) — this TU re-exports them and owns every pure derivation.
// No #ifdef forks leak beyond this file; the shells consume the accessors
// below, so channel identity is one auditable definition.

#include "core/version/app_version.h"

#include <string>

#include "version_generated.h"  // REASAMPLER_VERSION_STRING + REASAMPLER_CHANNEL_IS_BETA

namespace reasampler::version {

namespace {
// The one channel predicate every derivation below branches on. constexpr so
// the branches fold at compile time.
constexpr bool kIsBeta = (REASAMPLER_CHANNEL_IS_BETA != 0);
}  // namespace

Channel channel() { return kIsBeta ? Channel::Beta : Channel::Stable; }

bool isBeta() { return kIsBeta; }

const std::string& appVersion() {
    static const std::string kVersion =
        kIsBeta ? std::string(REASAMPLER_VERSION_STRING) + "-beta"
                : std::string(REASAMPLER_VERSION_STRING);
    return kVersion;
}

const std::string& stampVersion() {
    static const std::string kStamp = REASAMPLER_VERSION_STRING;
    return kStamp;
}

const std::string& extStateNamespace() {
    static const std::string kNs = kIsBeta ? "reasampler_beta" : "reasampler";
    return kNs;
}

const std::string& commandIdPrefix() {
    static const std::string kPrefix =
        kIsBeta ? "CEREBELLUM_REASAMPLER_BETA_" : "CEREBELLUM_REASAMPLER_";
    return kPrefix;
}

const std::string& actionDisplayPrefix() {
    static const std::string kDisp = kIsBeta ? "ReaSampler beta: " : "ReaSampler: ";
    return kDisp;
}

const std::string& binaryName() {
    static const std::string kName =
        kIsBeta ? "reaper_reasampler_beta" : "reaper_reasampler";
    return kName;
}

const std::string& dockTitle() {
    static const std::string kTitle =
        kIsBeta ? "ReaSampler Bank beta" : "ReaSampler Bank";
    return kTitle;
}

const std::string& dockIdent() {
    static const std::string kIdent =
        kIsBeta ? "reasampler_bank_panel_beta" : "reasampler_bank_panel";
    return kIdent;
}

const std::string& vstOutputName() {
    static const std::string kName =
        kIsBeta ? "reasampler_9000_beta" : "reasampler_9000";
    return kName;
}

const std::string& vstPluginName() {
    static const std::string kName =
        kIsBeta ? "ReaSampler 9000 beta" : "ReaSampler 9000";
    return kName;
}

std::string channelCommandId(const std::string& suffix) {
    return commandIdPrefix() + suffix;
}

std::string channelActionName(const std::string& phrase) {
    return actionDisplayPrefix() + phrase;
}

std::optional<Version> parseVersion(const std::string& s) {
    // Split on '.' into exactly three non-empty all-digit components. No sign, no
    // whitespace, no trailing garbage. Leading zeros are allowed (0.9.01 parses to
    // {0,9,1}) — the display fidelity of the zero is the string's job, not this parse's.
    Version v;
    int component = 0;      // 0=major, 1=minor, 2=patch
    long long acc = 0;      // current component accumulator (long long guards overflow)
    bool digitsInComponent = false;
    int* const out[3] = {&v.major, &v.minor, &v.patch};

    for (char c : s) {
        if (c == '.') {
            if (!digitsInComponent) return std::nullopt;  // empty component (".", "1..2")
            if (component >= 2) return std::nullopt;       // too many dots
            *out[component] = static_cast<int>(acc);
            ++component;
            acc = 0;
            digitsInComponent = false;
            continue;
        }
        if (c < '0' || c > '9') return std::nullopt;        // non-digit (sign, letter, ws)
        acc = acc * 10 + (c - '0');
        if (acc > 1'000'000'000LL) return std::nullopt;     // absurdly large -> reject
        digitsInComponent = true;
    }
    if (component != 2 || !digitsInComponent) return std::nullopt;  // too few components
    *out[2] = static_cast<int>(acc);
    return v;
}

bool versionLess(const Version& a, const Version& b) {
    if (a.major != b.major) return a.major < b.major;
    if (a.minor != b.minor) return a.minor < b.minor;
    return a.patch < b.patch;
}

WritingVersion classifyWritingVersion(const std::string& rawStamp) {
    WritingVersion wv;
    if (rawStamp.empty()) {
        wv.kind = WritingVersion::Kind::PreVersioning;  // no stamp -> pre-versioning
        return wv;
    }
    std::optional<Version> parsed = parseVersion(rawStamp);
    if (!parsed) {
        wv.kind = WritingVersion::Kind::Unknown;  // present but malformed -> ignored
        wv.raw  = rawStamp;
        return wv;
    }
    wv.kind   = WritingVersion::Kind::Stamped;
    wv.raw    = rawStamp;
    wv.parsed = *parsed;
    return wv;
}

} // namespace reasampler::version
