/*
 * platform_web.c — Emscripten/WebAssembly stub implementation
 *
 * The web version cannot spawn child processes, so these functions
 * return "not supported". A real implementation would use Emscripten's
 * pthreads or asyncify if ffmpeg integration is needed.
 */

#include "platform.h"
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>      /* ENOSYS */
#include <emscripten.h> /* EM_ASM */

/* ── Helpers ─────────────────────────────────────────────────────────── */

static char last_error_msg[256] = { 0 };

static void set_last_error(const char *fmt, ...) {
    va_list args;
    va_start(args, fmt);
    vsnprintf(last_error_msg, sizeof(last_error_msg), fmt, args);
    va_end(args);
}

/* ── Platform API ────────────────────────────────────────────────────── */

PlatformError Platform_SpawnChild(const char *program, const char **argv,
                                  PlatformSpawnHandle *out_handle) {
    (void)program;
    (void)argv;
    set_last_error("Spawning child processes is not supported on the web");
    out_handle->last_error = ENOSYS;
    return PLATFORM_NOT_SUPPORTED;
}

PlatformError Platform_WaitForChild(PlatformSpawnHandle *handle) {
    (void)handle;
    return PLATFORM_NOT_SUPPORTED;
}

SpawnStatus Platform_GetExitStatus(const PlatformSpawnHandle *handle, int *code) {
    (void)handle;
    (void)code;
    return SPAWN_WAITING;
}

void Platform_CloseSpawnHandle(PlatformSpawnHandle *handle) {
    if (!handle) return;
    handle->handle = NULL;
    handle->status = SPAWN_WAITING;
    handle->exit_code = 0;
    handle->last_error = 0;
}

const char *Platform_GetLastErrorMessage(void) {
    return last_error_msg;
}

const char *Platform_GetTempDir(void) {
    /*
     * Emscripten's virtual filesystem: "/" works as root.
     * For persistent downloads/uploads, consider IDBFS mount.
     */
    return "/";
}

void Platform_OfferFileToUser(const char *path) {
    if (!path) return;
    /*
     * The file was just written into the in-memory (MEMFS) filesystem, which
     * the user can't see. Read it back and hand it to the browser as a
     * download, then unlink the temporary copy so we don't leak heap memory.
     */
    EM_ASM({
        var p = UTF8ToString($0);
        try {
            var data = FS.readFile(p);                 // Uint8Array
            var name = p.split('/').pop() || 'download';
            var blob = new Blob([data], { type: 'application/octet-stream' });
            var url  = URL.createObjectURL(blob);
            var a = document.createElement('a');
            a.href = url;
            a.download = name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            setTimeout(function() { URL.revokeObjectURL(url); }, 1000);
            try { FS.unlink(p); } catch (e) {}
        } catch (e) {
            console.error('rspektrum: download failed for ' + p + ': ' + e);
        }
    }, path);
}

/*
 * File upload.
 *
 * The web build's filesystem is an in-memory sandbox, so the app's own file
 * browser can only ever list files this process already wrote — useless for
 * opening a capture off the user's disk. Instead we drive a real
 * <input type="file"> and copy the chosen file into MEMFS, where the ordinary
 * LoadWavFile path can read it like any other.
 *
 * The picker resolves in a browser event long after the C call returns, so the
 * result is parked in these globals and collected by polling from the main
 * loop. g_uploadReady is written from JS (see EM_ASM below) and read from C.
 */
static char g_uploadPath[512];
static volatile int g_uploadReady = 0;

/* Called from JS once the file's bytes are in MEMFS. */
EMSCRIPTEN_KEEPALIVE
void rspektrum_upload_done(const char *path) {
    if (!path) return;
    strncpy(g_uploadPath, path, sizeof(g_uploadPath) - 1);
    g_uploadPath[sizeof(g_uploadPath) - 1] = '\0';
    g_uploadReady = 1;
}

bool Platform_NeedsFileUpload(void) { return true; }

void Platform_RequestFileUpload(void) {
    EM_ASM({
        // Reuse one hidden input across calls: creating a fresh element per
        // click leaks nodes, and some browsers ignore a picker opened from an
        // element that isn't in the document.
        var input = document.getElementById('rspektrum-upload');
        if (!input) {
            input = document.createElement('input');
            input.type = 'file';
            input.id = 'rspektrum-upload';
            input.accept = '.wav,.wave,audio/*';
            input.style.display = 'none';
            document.body.appendChild(input);
        }
        input.onchange = function(ev) {
            var file = ev.target.files && ev.target.files[0];
            if (!file) return;
            var reader = new FileReader();
            reader.onload = function() {
                try {
                    var bytes = new Uint8Array(reader.result);
                    // Keep the original name so the UI can show something
                    // meaningful; sanitise it into a flat MEMFS path.
                    var safe = file.name.replace(/[^A-Za-z0-9._-]/g, '_');
                    var path = '/uploads/' + safe;
                    try { FS.mkdir('/uploads'); } catch (e) {}
                    try { FS.unlink(path); } catch (e) {}
                    FS.writeFile(path, bytes);
                    ccall('rspektrum_upload_done', null, ['string'], [path]);
                } catch (e) {
                    console.error('rspektrum: upload failed: ' + e);
                }
            };
            reader.readAsArrayBuffer(file);
            // Allow re-picking the same file next time.
            ev.target.value = '';
        };
        input.click();
    });
}

bool Platform_TakeUploadedFile(char *outPath, int cap) {
    if (!g_uploadReady || !outPath || cap <= 0) return false;
    g_uploadReady = 0;
    strncpy(outPath, g_uploadPath, (size_t)cap - 1);
    outPath[cap - 1] = '\0';
    return true;
}
