import fs from 'node:fs/promises';
import { Worker } from 'node:worker_threads';

const MEMORY_PAGES = 256;
const MAX_MEMORY_PAGES = 4096;
const control = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));

const [runtimePath, pythonPath, rubyPath] = process.argv.slice(2);
if (!runtimePath || !pythonPath || !rubyPath) {
  throw new Error('usage: node run.mjs <runtime.wasm> <python.wasm> <ruby.wasm>');
}

const runtimeModule = await WebAssembly.compile(await fs.readFile(runtimePath));
const languageModules = await Promise.all(
  [pythonPath, rubyPath].map(async path => WebAssembly.compile(await fs.readFile(path))),
);
const memory = new WebAssembly.Memory({
  initial: MEMORY_PAGES,
  maximum: MAX_MEMORY_PAGES,
  shared: true,
});
if (!(memory.buffer instanceof SharedArrayBuffer)) {
  throw new Error('WebAssembly memory is not shared');
}

let worker;
let pendingRequest = 0;
const uiRuntime = await WebAssembly.instantiate(runtimeModule, {
  env: {
    memory,
    request_parse(languageId, sourceAddress, sourceLength, oldTree, requestAddress) {
      if (pendingRequest !== 0) {
        throw new Error('Rust requested another parse before the prior request completed');
      }
      pendingRequest = requestAddress;
      if (oldTree) {
        Atomics.store(control, 0, 0);
      }
      worker.postMessage(
        oldTree
          ? { languageId, sourceAddress, sourceLength, oldTree }
          : { languageId, sourceAddress, sourceLength },
      );
    },
    log(messageAddress, messageLength) {
      console.log(
        new TextDecoder().decode(
          new Uint8Array(memory.buffer, messageAddress, messageLength),
        ),
      );
    },
    notify_parsing_started() {
      throw new Error('UI runtime unexpectedly paused for parsing');
    },
  },
});

worker = new Worker(new URL('./worker.mjs', import.meta.url), {
  workerData: { control, runtimeModule, languageModules, memory },
});

try {
  await new Promise((resolve, reject) => {
    let finished = false;
    worker.once('error', reject);
    worker.once('exit', code => {
      if (!finished) {
        reject(new Error(`worker exited before completing the test with code ${code}`));
      }
    });
    worker.on('message', message => {
      try {
        if (message?.ready === true) {
          uiRuntime.exports.start();
          return;
        }
        if (message?.parsing) {
          console.log('parsing...');
          Atomics.store(control, 0, 1);
          Atomics.notify(control, 0);
          return;
        }
        if (!Number.isInteger(message?.tree) || message.tree === 0) {
          throw new Error('worker did not return a tree');
        }
        const request = pendingRequest;
        if (request === 0) {
          throw new Error('worker returned a tree without a pending request');
        }
        pendingRequest = 0;
        if (uiRuntime.exports.tree_ready(request, message.tree)) {
          finished = true;
          resolve();
        }
      } catch (error) {
        reject(error);
      }
    });
  });
} finally {
  await worker.terminate();
}
