/**
 * arcrun logic component Worker
 *
 * POST / → JSON input → WASM (WASI preview1 stdin/stdout) → JSON output
 *
 * WASM is statically bundled at build time via wrangler.toml [[wasm_modules]].
 * Each logic component gets its own Worker at {name}.arcrun.dev.
 */

import componentWasm from '../component.wasm' assert { type: 'webassembly' };

import { Hono } from 'hono';
import { cors } from 'hono/cors';

const app = new Hono();

app.use('*', cors());

app.get('/', (c) => c.json({ ok: true, component: COMPONENT_ID }));

app.post('/', async (c) => {
  let input: unknown;
  try {
    input = await c.req.json();
  } catch {
    return c.json({ success: false, error: 'request body must be JSON' }, 400);
  }

  try {
    const result = await runWasm(componentWasm, input);
    return c.json(result);
  } catch (e) {
    return c.json({ success: false, error: e instanceof Error ? e.message : String(e) }, 500);
  }
});

export default app;

// ── WASM runner (WASI preview1 stdin/stdout) ─────────────────────────────────

declare const COMPONENT_ID: string;  // injected via [vars] in wrangler.toml

async function runWasm(wasmModule: WebAssembly.Module, input: unknown): Promise<unknown> {
  const stdinBytes = new TextEncoder().encode(JSON.stringify(input));
  let stdinOffset = 0;

  const stdoutChunks: Uint8Array[] = [];
  let memory: WebAssembly.Memory | null = null;

  const getView = () => new DataView(memory!.buffer);

  const wasi: WebAssembly.Imports = {
    wasi_snapshot_preview1: {
      fd_write(fd: number, iovs: number, iovs_len: number, nwritten_ptr: number): number {
        if (fd !== 1 && fd !== 2) return 76; // ENOSYS
        const view = getView();
        let total = 0;
        for (let i = 0; i < iovs_len; i++) {
          const base = view.getUint32(iovs + i * 8, true);
          const len  = view.getUint32(iovs + i * 8 + 4, true);
          if (len === 0) continue;
          const chunk = new Uint8Array(memory!.buffer, base, len);
          const copy = new Uint8Array(len);
          copy.set(chunk);
          if (fd === 1) stdoutChunks.push(copy);
          total += len;
        }
        view.setUint32(nwritten_ptr, total, true);
        return 0;
      },
      fd_read(fd: number, iovs: number, iovs_len: number, nread_ptr: number): number {
        if (fd !== 0) return 76;
        const view = getView();
        let total = 0;
        for (let i = 0; i < iovs_len; i++) {
          const base = view.getUint32(iovs + i * 8, true);
          const len  = view.getUint32(iovs + i * 8 + 4, true);
          const remaining = stdinBytes.length - stdinOffset;
          if (remaining <= 0) break;
          const toCopy = Math.min(len, remaining);
          new Uint8Array(memory!.buffer, base, toCopy).set(
            stdinBytes.subarray(stdinOffset, stdinOffset + toCopy)
          );
          stdinOffset += toCopy;
          total += toCopy;
        }
        view.setUint32(nread_ptr, total, true);
        return 0;
      },
      proc_exit(code: number): never { throw new Error(`wasm exit: ${code}`); },
      random_get(ptr: number, len: number): number {
        crypto.getRandomValues(new Uint8Array(memory!.buffer, ptr, len));
        return 0;
      },
      fd_seek: () => 76, fd_close: () => 0,
      fd_fdstat_get: () => 76, fd_prestat_get: () => 76,
      fd_prestat_dir_name: () => 76, environ_get: () => 0,
      environ_sizes_get: (cp: number, sp: number) => {
        if (memory) { const v = getView(); v.setUint32(cp,0,true); v.setUint32(sp,0,true); }
        return 0;
      },
      args_get: () => 0,
      args_sizes_get: (ap: number, bp: number) => {
        if (memory) { const v = getView(); v.setUint32(ap,0,true); v.setUint32(bp,0,true); }
        return 0;
      },
      clock_time_get: (_id: number, _prec: bigint, tp: number) => {
        if (memory) getView().setBigUint64(tp, BigInt(Date.now()) * 1_000_000n, true);
        return 0;
      },
      clock_res_get: () => 76, poll_oneoff: () => 76, sched_yield: () => 0,
      proc_raise: () => 76, sock_accept: () => 76, sock_recv: () => 76,
      sock_send: () => 76, sock_shutdown: () => 76,
      path_open: () => 76, path_create_directory: () => 76,
      path_remove_directory: () => 76, path_rename: () => 76,
      path_unlink_file: () => 76, path_filestat_get: () => 76,
      path_readlink: () => 76, path_symlink: () => 76, path_link: () => 76,
    },
    // u6u host functions (no-op for pure logic components)
    u6u: { http_request: () => 1 },
  };

  const instance = await WebAssembly.instantiate(wasmModule, wasi);
  memory = instance.exports.memory as WebAssembly.Memory;

  const promising = (WebAssembly as unknown as Record<string, unknown>)['promising'] as
    ((fn: () => void) => () => Promise<void>) | undefined;
  const startFn = (instance.exports._start ?? instance.exports.main) as () => void;
  if (typeof startFn !== 'function') throw new Error('WASM missing _start or main export');
  try {
    if (promising) { await promising(startFn)(); } else { startFn(); }
  } catch (e) {
    if (!(e instanceof Error && e.message === 'wasm exit: 0')) throw e;
  }

  const decoder = new TextDecoder();
  const total = stdoutChunks.reduce((n, c) => n + c.length, 0);
  const merged = new Uint8Array(total);
  let off = 0;
  for (const chunk of stdoutChunks) { merged.set(chunk, off); off += chunk.length; }
  const stdout = decoder.decode(merged).trim();

  if (!stdout) throw new Error('WASM component produced no output');
  return JSON.parse(stdout);
}
