// native_bridge/src/SharedMemoryIPC.cpp
#include "SharedMemoryIPC.h"

#include <cstring>
#include <string>

// ── Platform shared-memory helpers (used by main.cpp on Windows and by the
//    self-check test on POSIX). The DAW (Rust) creates the mapping; the bridge
//    opens it. Layout must match the Rust struct (see src-tauri/src/shm.rs).
#ifdef _WIN32
#include <windows.h>

struct ShmHandle {
    HANDLE map = nullptr;
    void* view = nullptr;
};

ShmHandle* shm_open(const char* name) {
    ShmHandle* h = new ShmHandle();
    h->map = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, name);
    if (!h->map) { delete h; return nullptr; }
    h->view = MapViewOfFile(h->map, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedAudioBufferIPC));
    if (!h->view) { CloseHandle(h->map); delete h; return nullptr; }
    return h;
}
// Creates the mapping (used by tests/self-check; the DAW normally owns it).
ShmHandle* shm_create(const char* name) {
    ShmHandle* h = new ShmHandle();
    int wlen = MultiByteToWideChar(CP_UTF8, 0, name, -1, nullptr, 0);
    std::wstring wname(wlen, L'\0');
    MultiByteToWideChar(CP_UTF8, 0, name, -1, &wname[0], wlen);
    h->map = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
                                sizeof(SharedAudioBufferIPC), wname.c_str());
    if (!h->map) { delete h; return nullptr; }
    h->view = MapViewOfFile(h->map, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(SharedAudioBufferIPC));
    if (!h->view) { CloseHandle(h->map); delete h; return nullptr; }
    return h;
}
void shm_close(ShmHandle* h) {
    if (!h) return;
    if (h->view) UnmapViewOfFile(h->view);
    if (h->map) CloseHandle(h->map);
    delete h;
}
#else
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>

struct ShmHandle {
    int fd = -1;
    void* view = nullptr;
    std::string name;
};

ShmHandle* shm_open(const char* name) {
    size_t sz = sizeof(SharedAudioBufferIPC);
    int fd = ::shm_open(name, O_CREAT | O_RDWR, 0666);
    if (fd < 0) return nullptr;
    if (ftruncate(fd, (off_t)sz) != 0) { ::close(fd); return nullptr; }
    void* view = mmap(nullptr, sz, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (view == MAP_FAILED) { ::close(fd); return nullptr; }
    ShmHandle* h = new ShmHandle();
    h->fd = fd;
    h->view = view;
    h->name = name;
    return h;
}
void shm_close(ShmHandle* h) {
    if (!h) return;
    if (h->view) munmap(h->view, sizeof(SharedAudioBufferIPC));
    if (h->fd >= 0) ::close(h->fd);
    shm_unlink(h->name.c_str());
    delete h;
}
#endif

ShmHandle* shm_open_default() { return shm_open("SonicForge_DAW_IPC"); }

SharedAudioBufferIPC* shm_ptr(ShmHandle* h) {
    return h ? static_cast<SharedAudioBufferIPC*>(h->view) : nullptr;
}

bool shm_write_midi(ShmHandle* h, uint8_t cmd, uint8_t channel, uint8_t pitch,
                    uint8_t velocity, uint32_t sampleOffset, uint8_t data2 = 0, uint8_t data3 = 0) {
    SharedAudioBufferIPC* ipc = shm_ptr(h);
    if (!ipc) return false;
    // Ring overwrite guard: keep at most queue capacity pending events.
    if (ipc->midiQueueCount >= 64) return false;
    uint32_t i = ipc->midiQueueCount++;
    SharedAudioBufferIPC::MidiEventIPC& e = ipc->midiQueue[i];
    e.command = cmd;
    e.channel = channel;
    e.pitch = pitch;
    e.velocity = velocity;
    e.data2 = data2;
    e.data3 = data3;
    e.sampleOffset = sampleOffset;
    return true;
}

bool shm_write_control(ShmHandle* h, uint32_t type, uint32_t arg0, uint32_t arg1,
                       uint32_t channel, const char* path) {
    SharedAudioBufferIPC* ipc = shm_ptr(h);
    if (!ipc) return false;
    if (ipc->controlQueueCount >= 8) return false;
    uint32_t i = ipc->controlQueueCount++;
    SharedAudioBufferIPC::ControlEventIPC& c = ipc->controlQueue[i];
    c.type = type;
    c.arg0 = arg0;
    c.channel = channel;
    std::memset(c.arg2, 0, sizeof(c.arg2));
    if (path && type == 2 /*LOAD*/) {
        c.arg1 = (uint32_t)std::strlen(path); // path length in arg1
        if (c.arg1 < sizeof(c.arg2)) std::memcpy(c.arg2, path, c.arg1);
    } else {
        c.arg1 = arg1; // TRANSPORT playhead / OPEN_GUI hwnd etc.
    }
    return true;
}
