// param_merge.h — the audio thread's block-boundary merge DECISION, with no host type and no
// atomic in it: which held automation points still outrank the model, which the model has caught
// up on and are released, and whether the result is worth republishing. The AUTHORITY MODEL it
// implements is stated in `shell/instrument/CLAUDE.md`; this is its testable half.

#pragma once

#include <cstddef>

#include "core/instrument/engine/live_params.h"
#include "core/instrument/ui/deck_groups.h"  // DeckParam (the ordinal space slots are indexed by)

namespace reasampler::instrument::param {

using ui::DeckParam;

// The DeckParam ordinal space. One slot per control, indexed by ordinal, so a lookup is an index
// rather than a search on the audio thread.
inline constexpr std::size_t kDeckParamSlots = static_cast<std::size_t>(DeckParam::kCount);

// One control's automation state as the merge sees it.
struct AutomationSlot {
    double norm = 0.0;    // the last point this lane delivered
    bool held = false;    // that point still outranks the model
    bool folded = false;  // the model has since been rewritten to carry THAT point
};

// Patches every still-held slot over `block`, and RELEASES each slot the model has caught up on.
//
// The release is what BOUNDS a point's authority. A lane outranks a plug-in-side set only while
// it is driving; a value it delivered once, already folded back into the model, outranks nothing.
// Without the release a single automation point would defeat every later state restore, bake
// reset and knob move for the life of the instance — which is the failure this function exists
// to make impossible, and which `test_param_merge` is the test of.
//
// RT-SAFE: no allocation, no lock, no transcendental beyond the tapers' own.
void mergeAutomation(engine::LiveValues& block, AutomationSlot* slots, std::size_t count,
                     int sampleRate);

// Whether a point of `normalized` for a slot in this state actually moves the block. False for a
// point equal to a hold that is still standing — the ordinary read-mode steady state, where a
// host delivers one point per block over a flat lane segment. Republishing there would drive
// `VoiceEngine::applyLiveToActive` over every sounding voice — a `std::pow`, two envelope φ
// re-fits and the filter ramp aims, per voice — for a value that did not move. Once the hold has
// been RELEASED the answer is true again, because some other writer may have moved the model
// since.
inline bool automationPointMoves(const AutomationSlot& slot, double normalized) {
    return !slot.held || slot.norm != normalized;
}

}  // namespace reasampler::instrument::param
