// action_registry.cpp — see action_registry.h. Needs no REAPER API pointers:
// rec->Register is a member call on the dispatch struct REAPER hands the entry point.

#include "shell/actions/action_registry.h"

#include <cstring>
#include <deque>
#include <string>

#include "core/version/app_version.h"  // channelCommandId / channelActionName

namespace reasampler {

namespace {

using version::channelActionName;
using version::channelCommandId;

// std::deque never invalidates references on push_back, so a c_str() handed to
// REAPER stays valid until process exit. Memoized by suffix so register and
// mirror-unregister get the SAME id pointer.
std::deque<std::string> g_strStore;

// std::deque so element addresses never move after push_back — REAPER keeps each
// &accel until the mirror-unregister.
struct TableEntry {
    ActionTableRow row;
    const char* id = nullptr;   // interned channel-qualified command id
    int cmd = 0;                // minted command id (0 = mint failed / inert row)
    gaccel_register_t accel{};  // Actions-list entry; address handed to REAPER
};
std::deque<TableEntry> g_table;

}  // namespace

const char* channelIdFor(const char* suffix) {
    const std::string composed = channelCommandId(suffix);
    for (const std::string& s : g_strStore)
        if (s == composed) return s.c_str();
    g_strStore.push_back(composed);
    return g_strStore.back().c_str();
}

int registerAction(reaper_plugin_info_t* rec, const char* suffix,
                   gaccel_register_t& accel, const char* phrase) {
    const char* id = channelIdFor(suffix);
    const int cmd = rec->Register("command_id", (void*)id);
    if (cmd) {
        g_strStore.push_back(channelActionName(phrase));
        accel.accel.cmd = cmd;
        accel.desc      = g_strStore.back().c_str();
        rec->Register("gaccel", (void*)&accel);
    }
    return cmd;
}

void registerActionTable(reaper_plugin_info_t* rec, const ActionTableRow* rows,
                         std::size_t count) {
    g_table.clear();  // idempotent-by-construction: a re-register never doubles-up rows
    for (std::size_t i = 0; i < count; ++i) {
        g_table.push_back(TableEntry{rows[i]});
        TableEntry& e = g_table.back();
        e.id  = channelIdFor(e.row.suffix);
        e.cmd = registerAction(rec, e.row.suffix, e.accel, e.row.phrase);
    }
}

bool actionTableHandleCommand(int command) {
    if (command == 0) return false;
    for (const TableEntry& e : g_table)
        if (e.cmd != 0 && command == e.cmd) {
            e.row.run(e.row.arg);
            return true;
        }
    return false;
}

int actionTableCommandId(const char* suffix) {
    for (const TableEntry& e : g_table)
        if (std::strcmp(e.row.suffix, suffix) == 0) return e.cmd;
    return 0;
}

void unregisterActionTable(reaper_plugin_info_t* rec) {
    // Reverse table order, mirroring the register loop. Each '-command_id'
    // re-presents the SAME interned pointer channelIdFor handed out at register.
    for (auto it = g_table.rbegin(); it != g_table.rend(); ++it) {
        rec->Register("-gaccel", (void*)&it->accel);
        rec->Register("-command_id", (void*)it->id);
    }
}

}  // namespace reasampler
