// param_id.h — the VST3 parameter identity space: the frozen id table, its DeckParam binding,
// the deck-group units, and the exposed set DERIVED from the commit predicate. Pure: no VST3
// type appears here, so the whole contract is provable without a host.

#pragma once

#include <cstdint>
#include <vector>

#include "core/instrument/ui/deck_groups.h"  // DeckParam + deckParamCommit (the predicate)

namespace reasampler::instrument::param {

using ui::DeckParam;

// A host records this number into automation lanes inside project files this repo does not own
// and cannot migrate.
//
// THE PARAMETER-ID TABLE IS FOREVER-FROZEN, on the same footing as the extension's
// "STABLE_FOREVER_STRING" command ids, the two VST3 class UIDs (core/wire/reasampler_uid.h) and
// the params-payload field order (map/component_state_io.h):
//   - No id is ever reassigned, reused or re-pointed. A control whose meaning genuinely changes
//     takes a NEW id; the old one is marked dead here and never re-issued.
//   - No exposed parameter's normalization ever changes — not its taper, not either range
//     endpoint, not its stepCount. The normalization IS the meaning of every recorded point.
//   - A parameter's meaning never depends on a mode. The Gate-face and Trigger-face stage times
//     are separate stored fields and take separate ids.
//   - A new control takes the next free slot inside its own group's block, never the next number
//     at the end of the table.
// Display strings, titles and precision are NOT frozen — they are what a user reads, not what a
// lane stores.
using ParamId = std::uint32_t;

// Blocks of 100 per deck group in SIGNAL-FLOW order, steps of 10 within a block, a curve dial at
// its outer knob's id + 1. Blocks start at 1000 so the first legitimate id is not also the most
// likely bug value (a default-initialised ParamId). Nine free slots between neighbours put a
// control added later numerically beside its siblings instead of at the end of the table.
// 1500-1599 (VELOCITY) and 1600-1699 (VOICE) are RESERVED and empty — a control either group
// ever gains lands in its own range rather than in whatever range happened to be free.
enum : ParamId {
    kParamKeyTrackPitch      = 1000,
    kParamRate               = 1010,
    kParamPitchOffset        = 1020,

    kParamPitchEnvAttack     = 1100,
    kParamPitchEnvAttackCurve= 1101,
    kParamPitchEnvHold       = 1110,
    kParamPitchEnvDecay      = 1120,
    kParamPitchEnvDecayCurve = 1121,
    kParamPitchEnvDepth      = 1130,

    kParamFilterMorph        = 1200,
    kParamFilterCutoff       = 1210,
    kParamFilterQ            = 1220,
    kParamFilterDrive        = 1230,
    kParamFilterModAmount    = 1240,
    kParamFilterVelAmount    = 1250,
    kParamKeyTrackFilter     = 1260,

    kParamFilterEnvAttack       = 1300,
    kParamFilterEnvAttackCurve  = 1301,
    kParamFilterEnvHold         = 1310,
    kParamFilterEnvDecay        = 1320,
    kParamFilterEnvDecayCurve   = 1321,
    kParamFilterEnvSustain      = 1330,
    kParamFilterEnvRelease      = 1340,
    kParamFilterEnvReleaseCurve = 1341,
    kParamFilterTrigAttack      = 1350,
    kParamFilterTrigAttackCurve = 1351,
    kParamFilterTrigHold        = 1360,
    kParamFilterTrigDecay       = 1370,
    kParamFilterTrigDecayCurve  = 1371,

    kParamAmpAttack          = 1400,
    kParamAmpAttackCurve     = 1401,
    kParamAmpHold            = 1410,
    kParamAmpDecay           = 1420,
    kParamAmpDecayCurve      = 1421,
    kParamAmpSustain         = 1430,
    kParamAmpRelease         = 1440,
    kParamAmpReleaseCurve    = 1441,
    kParamTriggerLength      = 1450,
    kParamAmpTrigAttack      = 1460,
    kParamAmpTrigAttackCurve = 1461,
    kParamAmpTrigHold        = 1470,
    kParamAmpTrigDecay       = 1480,
    kParamAmpTrigDecayCurve  = 1481,

    kParamMasterGain         = 1700,
};

// IUnitInfo units, one per deck group that carries an exposed parameter. 0 is the SDK's root
// unit, so these start at 1. Softer than the id freeze but user-facing and cached by some hosts.
enum : std::int32_t {
    kUnitRoot      = 0,
    kUnitPitch     = 1,
    kUnitPitchEnv  = 2,
    kUnitFilter    = 3,
    kUnitFilterEnv = 4,
    kUnitAmp       = 5,
    kUnitMaster    = 6,
};

struct ParamRow {
    ParamId       id;
    DeckParam     deck;
    std::int32_t  unit;
    const char*   title;       // survives truncation
    const char*   shortTitle;  // distinct, for a narrow host column
};

// The WHOLE frozen assignment, in ascending id — which is also the presentation order, so
// identity order and presentation order agree by construction rather than by maintenance.
// Membership of the parameter list is NOT decided here: a row is issued to the host only if
// isExposed() says so. A row whose control is not exposed today keeps its number reserved for
// the day that control gains a live path, which is what the block-and-step scheme buys.
const std::vector<ParamRow>& paramTable();

// A control is an exposed VST3 parameter IF AND ONLY IF its commit class is Live or
// NoteOnLatched. Derived from deckParamCommit, never hand-maintained: the list follows the
// predicate, and the predicate is never bent to fill the list.
bool isExposed(DeckParam deck);

// paramTable() filtered by isExposed, still in ascending id. This is exactly what the host is
// told, in the order it is told.
const std::vector<ParamRow>& exposedParams();

// The row for an id, or null when the id is unknown or its control is not exposed.
const ParamRow* exposedRowFor(ParamId id);

// The id a control is numbered as, or 0 when the control has no row at all. Answers for
// unexposed rows too — the number is a property of the table, not of today's membership.
ParamId paramIdFor(DeckParam deck);

}  // namespace reasampler::instrument::param
