import { safeRun } from "../util";
import { ConsoleLogger } from "./custom_logger";
import { ControllerMessage, WorkerMessage } from "./worker";

let loadedFunctions = new Map<string, Function>();
let pendingRequests = new Map<
  number,
  {
    resolve: (result: unknown) => void;
    reject: (e: any) => void;
  }
>();

function workerPostMessage(msg: ControllerMessage) {
  if (typeof window !== "undefined" && window.parent !== window) {
    window.parent.postMessage(msg, "*");
  } else {
    self.postMessage(msg);
  }
}

declare global {
  function syscall(name: string, ...args: any[]): Promise<any>;
  // function require(moduleName: string): any;
}

let syscallReqId = 0;

self.syscall = async (name: string, ...args: any[]) => {
  return await new Promise((resolve, reject) => {
    syscallReqId++;
    pendingRequests.set(syscallReqId, { resolve, reject });
    workerPostMessage({
      type: "syscall",
      id: syscallReqId,
      name,
      args,
    });
  });
};

let loadedModules = new Map<string, any>();

// @ts-ignore
self.require = (moduleName: string): any => {
  // console.log("Loading", moduleName, loadedModules.get(moduleName));
  return loadedModules.get(moduleName);
};

// @ts-ignore
self.console = new ConsoleLogger((level, message) => {
  workerPostMessage({ type: "log", level, message });
}, false);

function wrapScript(code: string) {
  return `return (${code})["default"]`;
}

self.addEventListener("message", (event: { data: WorkerMessage }) => {
  safeRun(async () => {
    let data = event.data;
    switch (data.type) {
      case "load":
        let fn2 = new Function(wrapScript(data.code!));
        loadedFunctions.set(data.name!, fn2());
        workerPostMessage({
          type: "inited",
          name: data.name,
        });
        break;
      case "load-dependency":
        // console.log("Received dep", data.name);
        let fn3 = new Function(`return ${data.code!}`);
        let v = fn3();
        loadedModules.set(data.name!, v);
        // console.log("Dep val", v);
        workerPostMessage({
          type: "dependency-inited",
          name: data.name,
        });
        break;
      case "invoke":
        let fn = loadedFunctions.get(data.name!);
        if (!fn) {
          throw new Error(`Function not loaded: ${data.name}`);
        }
        try {
          let result = await Promise.resolve(fn(...(data.args || [])));
          workerPostMessage({
            type: "result",
            id: data.id,
            result: result,
          } as ControllerMessage);
        } catch (e: any) {
          workerPostMessage({
            type: "result",
            id: data.id,
            error: e.message,
            stack: e.stack,
          });
          // console.error("Error invoking function", data.name, e.message);
          // throw e;
        }

        break;
      case "syscall-response":
        let syscallId = data.id!;
        const lookup = pendingRequests.get(syscallId);
        if (!lookup) {
          console.log(
            "Current outstanding requests",
            pendingRequests,
            "looking up",
            syscallId
          );
          throw Error("Invalid request id");
        }
        pendingRequests.delete(syscallId);
        if (data.error) {
          lookup.reject(new Error(data.error));
        } else {
          lookup.resolve(data.result);
        }
        break;
    }
  });
});
