#pragma once
// app_version — the REAPER-free version-identity core. The single source of
// truth for the version STRING lives in CMake (a `REASAMPLER_VERSION` variable
// threaded in via configure_file -> version_generated.h); this module re-exports
// it as the canonical constant and owns every pure operation on it: the
// exact-string render, the parse/compare arithmetic, and the "which version
// wrote this project" result persist reads back from ext state (absent stamp =
// pre-versioning, never an error).
//
// This module is also the single source of truth for CHANNEL IDENTITY. A
// compile-time flag (`-DREASAMPLER_CHANNEL=beta`, threaded through
// version_generated.h as REASAMPLER_CHANNEL_IS_BETA) selects stable (the
// default, byte-for-byte today's identity) or a fully isolated beta build.
// Every channel-qualified identity string the shells register with REAPER — the
// display suffix, the ext-state namespace, the command-id prefix, the
// Actions-list name prefix, the binary/dock idents — is derived here from the
// one channel bit, so no scattered #ifdef forks live across translation units.
//
// PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library
// only.
//
// Leading-zero fidelity (Daniel-fixed): the displayed/stamped string preserves
// the configured version EXACTLY as written — padded and unpadded versions are
// both legitimate (e.g. "0.9.8" and "0.9.80" are different versions; "0.9.01"
// renders its zero-padded patch verbatim). That's why the version STRING is the
// authoritative artifact, not a reconstruction from numeric components — CMake's
// `project(VERSION)` may normalize a numeric patch field, so we never round-trip
// the string through integers to render it. Guarded against
// reconstruct-from-components regressions by app_version_padding_tests (the
// padded canary build); it does NOT guard against the CMakeLists.txt
// source-of-truth line itself changing — that's guarded by the comment block at
// the top of CMakeLists.txt.

#include <optional>
#include <string>

namespace reasampler::version {

// --- Channel identity (beta-in-isolation) --------------------------------------------
//
// The build channel, fixed at compile time by REASAMPLER_CHANNEL_IS_BETA (0 =
// stable, the default; 1 = beta, from -DREASAMPLER_CHANNEL=beta). Stable is
// byte-identical to today's build in every string below — any divergence there
// is a defect. Beta forks every identity so a beta binary coexists with stable
// in one REAPER without colliding on project ext-state, keybindings, or any
// REAPER-global registration.
enum class Channel { Stable, Beta };

// The channel this build was compiled for. Constant per binary.
Channel channel();

// True on the beta build only. Convenience over channel() == Channel::Beta.
bool isBeta();

// The user-visible version render. Stable: exactly the configured CMake string.
// Beta: that string plus a plain "-beta" suffix (not a git-describe decoration).
// What the show-version action and bank-panel readout display — NOT the
// ext-state stamp value (see stampVersion).
const std::string& appVersion();

// The ext-state STAMP value — the writing-version recorded into a saved
// project. The numeric triple only, no channel suffix, on BOTH channels: (a) so
// parseVersion classifies it as Stamped when its own channel reads it back (a
// "-beta"-suffixed stamp would classify as Unknown), and (b) so stable's stamp
// is byte-identical regardless of channel build. The channel is carried by the
// isolated namespace (see extStateNamespace), never baked into the stamp.
const std::string& stampVersion();

// The project ext-state namespace this channel reads and writes. Stable:
// "reasampler". Beta: "reasampler_beta". FOREVER-STABLE per channel once
// shipped — changing either orphans every already-saved project's state.
//
// ISOLATION (accepted, not a bug): a channel reads/writes ONLY its own
// namespace — a project saved by stable shows empty/default state when opened
// in beta, and vice versa. No cross-namespace read, migration, or fallback: a
// beta can never read or rewrite a stable project's bank/view/tail state.
const std::string& extStateNamespace();

// The FOREVER-STABLE command-id prefix every bindable action mints its id from.
// Stable: "CEREBELLUM_REASAMPLER_". Beta: "CEREBELLUM_REASAMPLER_BETA_", a
// distinct forever-family so beta and stable actions never collide in REAPER's
// one Actions list. Callers concatenate their per-action suffix onto this.
const std::string& commandIdPrefix();

// The Actions-list display-name prefix, so two coexisting channels are
// distinguishable. Stable: "ReaSampler: ". Beta: "ReaSampler beta: ". Callers
// build a gaccel desc as actionDisplayPrefix() + "capture selected track", etc.
const std::string& actionDisplayPrefix();

// The binary/module OUTPUT NAME base. Stable: "reaper_reasampler". Beta:
// "reaper_reasampler_beta". Mirrors the CMake OUTPUT_NAME. REAPER dlopen's any
// reaper_* module, so both channels load side-by-side.
const std::string& binaryName();

// The docked bank-panel identity strings, channel-qualified so the two panels
// don't fight over one persisted dock slot (DockWindowAddEx's identstr keys the
// saved dock position).
//   dockTitle()  — visible dock tab title. Stable: "ReaSampler Bank".
//   dockIdent()  — persisted dock-position ident. Stable: "reasampler_bank_panel".
//                  FOREVER-STABLE per channel.
const std::string& dockTitle();
const std::string& dockIdent();

// --- VST3 instrument identity (beta-in-isolation) ------------------------------
//
// The ReaSampler 9000 VST3 instrument forks its plugin identity per channel
// exactly as the extension forks its binary/dock idents above. These are the
// VST's analogues of binaryName()/dockTitle(): the on-disk module name and the
// human-facing name.
//
//   vstOutputName() — CMake OUTPUT_NAME base for the .vst3 module. Stable:
//                     "reasampler_9000". FOREVER-STABLE per channel — the
//                     on-disk filename a saved project's instance may reference.
//   vstPluginName() — factory display name (FX browser), editor title band, and
//                     embed-strip label. Stable: "ReaSampler 9000". Sourced from
//                     here, never a literal in reasampler_vst.h / vst_entry.cpp /
//                     the editor / the embed strip.
//
// The VST3 CLASS UID is NOT here — it's a compile-time FUID/INLINE_UID constant
// the factory needs in brace-init form; it lives in reasampler_vst.h,
// channel-selected by the same bit. This module owns the string identity;
// reasampler_vst.h owns the binary UID identity. The factory's PClassInfo2
// version display reuses appVersion() — no separate VST version accessor.
const std::string& vstOutputName();
const std::string& vstPluginName();

// --- Channel-qualified action id / name builders ------------------------------------
//
// The two composition helpers every action-registering shell (main.cpp, action_registry)
// funnels through, so command ids and Actions-list names are qualified IDENTICALLY on
// every channel from ONE definition — no shell re-implements the concatenation.
//
// channelCommandId(suffix): commandIdPrefix() + suffix. `suffix` is the per-action tail
//   WITHOUT the family prefix (e.g. "CAPTURE_TRACK", "SHOW_VERSION"). Stable yields the
//   exact shipped id ("CEREBELLUM_REASAMPLER_CAPTURE_TRACK"); beta yields the isolated
//   forever-family id ("CEREBELLUM_REASAMPLER_BETA_CAPTURE_TRACK"). FOREVER-STABLE per
//   channel — a suffix, once shipped, is as permanent as the prefix.
// channelActionName(phrase): actionDisplayPrefix() + phrase. `phrase` is the action's
//   human description WITHOUT the "ReaSampler: " lead (e.g. "capture selected track(s)").
//   Stable yields "ReaSampler: capture selected track(s)"; beta prefixes "ReaSampler beta: "
//   so the two channels' actions are distinguishable in one Actions list.
std::string channelCommandId(const std::string& suffix);
std::string channelActionName(const std::string& phrase);

// A parsed semver triple. Kept minimal — major.minor.patch as integers, for
// ordering only. Deliberately does NOT round-trip back to the display string
// (the leading zero is a rendering concern, not reconstructable from the
// integer patch).
struct Version {
    int major = 0;
    int minor = 0;
    int patch = 0;
};

// Parse "x.y.z" (each component a non-negative integer, leading zeros allowed) into a
// Version. Returns nullopt on anything malformed: wrong component count, non-digits, a
// leading `-`, empty components, or trailing garbage. Used for ordering two stamps and
// for validating a stored stamp before comparing.
std::optional<Version> parseVersion(const std::string& s);

// Numeric ordering by (major, minor, patch). a < b iff a precedes b. So
// 0.9.01 < 0.9.02 < 0.10.01 (numeric compare, NOT lexicographic — 10 > 9).
bool versionLess(const Version& a, const Version& b);

// The writing-version a project was last saved with, recovered from its ext-state
// stamp. A project saved before this feature shipped has NO stamp — that is the
// explicit `preVersioning` case (kind), not an error and not a warning. A present-but-
// malformed value is `unknown` (also silent — a corrupt stamp is ignored, never
// throws). A well-formed value is `stamped` and carries the exact stored string plus
// its parsed triple for comparison.
struct WritingVersion {
    enum class Kind {
        PreVersioning,  // no stamp stored — a project written before versioning shipped
        Unknown,        // a stamp was stored but is not parseable — ignored, not an error
        Stamped,        // a well-formed stamp
    };
    Kind        kind    = Kind::PreVersioning;
    std::string raw;              // the exact stored string (empty for PreVersioning)
    Version     parsed;          // meaningful only when kind == Stamped
};

// Classify a raw stored stamp value (exactly what GetProjExtState returned for the
// version key). Empty -> PreVersioning; non-empty but unparseable -> Unknown; parseable
// -> Stamped. Pure so the three-way classification is test-pinned; persist calls this
// with the raw ext-state read and never has to reason about the cases itself.
WritingVersion classifyWritingVersion(const std::string& rawStamp);

} // namespace reasampler::version
