#pragma once
// app_version — the REAPER-free version-identity core (Phase V, V1 + V4). 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 a within-channel forward
// migration will lean on, and the "which version wrote this project" result that
// persist reads back from ext state (absent stamp = pre-versioning, never an error).
//
// V4 (beta-in-isolation) extends this module into the SINGLE SOURCE OF TRUTH FOR
// CHANNEL IDENTITY too. A compile-time flag (`-DREASAMPLER_CHANNEL=beta`, threaded
// through the same configure_file'd version_generated.h as REASAMPLER_CHANNEL_IS_BETA)
// selects stable (the default, absent-flag build — 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 the translation units;
// the shells just consume these accessors. This keeps "what makes a beta a beta" one
// auditable definition and makes the channel-derived rendering unit-testable.
//
// PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library
// only. Builds and unit-tests without REAPER (mirror of bank_model / tail_control).
//
// Leading-zero fidelity (V1, Daniel-fixed): the displayed/stamped string is EXACTLY
// "0.9.01" — two-digit zero-padded patch. That exactness is why the version STRING is
// the authoritative artifact (sourced verbatim from the one CMake variable), 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.

#include <optional>
#include <string>

namespace reasampler {

// --- Channel identity (V4, beta-in-isolation) ---------------------------------------
//
// The build channel, fixed at compile time by REASAMPLER_CHANNEL_IS_BETA (0 = stable,
// the default absent-flag build; 1 = beta, from -DREASAMPLER_CHANNEL=beta). Stable is
// today's build with byte-identical identity in EVERY string below — any divergence on
// the stable channel is a defect. Beta forks every identity so a beta binary coexists
// with stable in one REAPER (both are dlopen'd at startup) 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 CMake string ("0.9.01"). Beta:
// that string plus a plain "-beta" suffix ("0.9.01-beta") — a plain suffix, NOT a
// git-describe decoration (V2, Daniel-fixed). This is what the show-version action and
// the bank-panel readout display. It is 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. This is
// the NUMERIC TRIPLE ONLY ("0.9.01") on BOTH channels: it deliberately carries NO channel
// suffix, so (a) parseVersion classifies it as Stamped when its own channel reads it back
// (a "-beta"-suffixed stamp would classify as Unknown — the V4 stamp-classifiability
// requirement), and (b) stable's stamp value is byte-identical regardless of the channel
// build. The channel is carried by the ISOLATED namespace (see extStateNamespace), never
// baked into the stamp. Distinct from appVersion() precisely so the display can say
// "-beta" while the stamp stays classifiable and stable-identical.
const std::string& stampVersion();

// The project ext-state namespace this channel reads and writes. Stable: "reasampler"
// (byte-identical to the pre-V4 build). Beta: "reasampler_beta". FOREVER-STABLE per
// channel once shipped — changing either orphans every already-saved project's state.
//
// ISOLATION SEMANTICS (V4, accepted — not a bug): a channel reads/writes ONLY its own
// namespace. A project saved by stable shows empty/default ReaSampler state when opened
// in beta, and vice versa. There is NO cross-namespace read, migration, or fallback in
// this wave — that isolation is the safety property (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_" (byte-identical to the shipped ids). Beta:
// "CEREBELLUM_REASAMPLER_BETA_", a DISTINCT forever-family so beta and stable actions
// never collide in REAPER's one Actions list and their keybindings stay independent.
// Callers concatenate their per-action suffix onto this (e.g. prefix + "CAPTURE_TRACK").
// PERMANENT once a beta ships — mark any minted id FOREVER-STABLE like stable's.
const std::string& commandIdPrefix();

// The Actions-list DISPLAY-NAME prefix, so two coexisting channels are distinguishable in
// REAPER's Actions list. Stable: "ReaSampler: " (unchanged). 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 (which is the authoritative
// artifact name); exposed here for any in-binary self-identification. 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 are
// distinguishable and do not fight over one persisted dock slot (a REAPER-global
// collision surface — DockWindowAddEx's identstr keys the saved dock position).
//   dockTitle()  — the visible dock tab title. Stable: "ReaSampler Bank".
//                  Beta: "ReaSampler Bank beta".
//   dockIdent()  — the persisted dock-position ident. Stable: "reasampler_bank_panel".
//                  Beta: "reasampler_bank_panel_beta". FOREVER-STABLE per channel.
const std::string& dockTitle();
const std::string& dockIdent();

// --- VST3 instrument identity (S18, beta-in-isolation) ------------------------------
//
// The ReaSampler 9000 VST3 instrument forks its plugin identity per channel exactly as the
// extension forks its binary/dock idents above — one channel per binary, all derived from
// the ONE channel bit here, so the VST shell carries no #ifdef fork. These are the VST's
// analogues of binaryName()/dockTitle(): the on-disk module name and the human-facing name.
//
//   vstOutputName() — the CMake OUTPUT_NAME base for the .vst3 module. Stable:
//                     "reasampler_9000" (byte-identical to pre-S18). Beta:
//                     "reasampler_9000_beta". Mirrors the CMake target's OUTPUT_NAME (the
//                     authoritative artifact name); exposed here so the one derivation lives
//                     in this module. FOREVER-STABLE per channel — the on-disk filename a
//                     REAPER project's saved instance path may reference.
//   vstPluginName() — the factory display name (FX browser), editor title band, and S6
//                     embed-strip label. Stable: "ReaSampler 9000". Beta:
//                     "ReaSampler 9000 beta". Sourced from here, never a literal in
//                     reasampler_vst.h / vst_entry.cpp / the editor / the embed strip.
//
// NOTE: the VST3 CLASS UID is NOT here — a UID is not a string derivation but 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 REASAMPLER_CHANNEL_IS_BETA bit. This module owns the string
// identity; reasampler_vst.h owns the binary UID identity. The version display the factory
// stamps into PClassInfo2 reuses appVersion() (it already renders "-beta" on beta) — 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, actions.cpp)
// 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. It deliberately does NOT round-trip back to the display string (the leading
// zero is a rendering concern owned by the authoritative string, not reconstructable
// from the integer patch). parseVersion returns nullopt on malformed input.
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
